fix(ci): release notes script (#32827)

(cherry picked from commit f47b02f91d6d55e0b0281609c37e6b1676626115)
This commit is contained in:
Jason Rasmussen
2026-10-31 07:23:57 +03:00
committed by Codex
parent b3fe177869
commit e15bdf9af3
5 changed files with 38 additions and 8 deletions
+3 -3
View File
@@ -154,8 +154,8 @@ jobs:
previous=$(grep -A1 -xF "${TAG}" <<< "${tags}" | tail -1)
echo "generating notes from ${previous}"
mise //:notes --tag "${TAG}" --previous "${previous}" > "${RUNNER_TEMP}/notes.md"
cat "${RUNNER_TEMP}/notes.md"
mise //:notes --tag "${TAG}" --previous "${previous}"
cat misc/release/notes.md
- name: Create draft release
env:
@@ -171,7 +171,7 @@ jobs:
--draft \
--verify-tag \
--prerelease="${PRERELEASE}" \
--notes-file "${RUNNER_TEMP}/notes.md" \
--notes-file misc/release/notes.md \
docker/docker-compose.yml \
docker/docker-compose.rootless.yml \
docker/example.env \
+1
View File
@@ -28,3 +28,4 @@ vite.config.js.timestamp-*
.devcontainer/.env*
*.tsbuildinfo
*.tsbuildInfo
misc/release/notes.md
+3 -1
View File
@@ -1,5 +1,6 @@
import { Command, Option } from 'commander';
import {
FileSystem,
GitClient,
GitHubClient,
type NotesOptions,
@@ -63,10 +64,11 @@ export const cli = (argv: string[]) => {
.action(async ({ token, ...options }: NotesOptions & { token: string }) => {
try {
const service = new NotesService(
new FileSystem(),
new GitClient(),
new GitHubClient(token),
);
process.stdout.write(await service.handleNotes(options));
await service.handleNotes(options);
} catch (error) {
console.error(error instanceof Error ? error.message : error);
process.exit(1);
+20 -2
View File
@@ -2,6 +2,7 @@ import type { Public } from 'src/types.js';
import { beforeEach, describe, expect, it, type Mocked, vi } from 'vitest';
import {
type Contributor,
type FileSystem,
type GitClient,
type GitHubClient,
NotesService,
@@ -37,6 +38,7 @@ const changelog = (...lines: string[]) =>
const body = (notes: string) => notes.slice(notes.indexOf("## What's Changed"));
describe(NotesService.name, () => {
let fs: Mocked<Public<FileSystem>>;
let git: Mocked<Public<GitClient>>;
let github: Mocked<Public<GitHubClient>>;
let sut: NotesService;
@@ -88,12 +90,16 @@ describe(NotesService.name, () => {
github.getFirstTimers.mockResolvedValue(firstTimers);
};
const run = () => sut.handleNotes({ tag: 'v3.3.0', previous: 'v3.2.4' });
const run = async () => {
await sut.handleNotes({ tag: 'v3.3.0', previous: 'v3.2.4' });
return fs.writeFileSync.mock.lastCall?.[1] as string;
};
beforeEach(() => {
fs = { writeFileSync: vi.fn() };
git = { log: vi.fn() };
github = { getPullRequests: vi.fn(), getFirstTimers: vi.fn() };
sut = new NotesService(git, github);
sut = new NotesService(fs, git, github);
});
it('should compare the previous tag with the new tag', async () => {
@@ -104,6 +110,18 @@ describe(NotesService.name, () => {
expect(git.log).toHaveBeenCalledWith('v3.2.4', 'v3.3.0');
});
it('should write the notes to misc/release/notes.md', async () => {
setup([{ number: 1 }]);
await run();
expect(fs.writeFileSync).toHaveBeenCalledOnce();
expect(fs.writeFileSync).toHaveBeenCalledWith(
expect.stringMatching(/\/misc\/release\/notes\.md$/),
expect.any(String),
);
});
it('should start with the template and end with the full changelog', async () => {
setup([{ number: 1 }]);
+11 -2
View File
@@ -1,5 +1,5 @@
import { execFileSync } from 'node:child_process';
import { existsSync, readFileSync } from 'node:fs';
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import type { Public } from 'src/types.js';
@@ -15,6 +15,7 @@ const REPO = 'immich-app/immich';
const Files = {
Config: join(root, '.github/release.yml'),
Template: join(root, 'misc/release/notes.tmpl'),
Output: join(root, 'misc/release/notes.md'),
};
export type NotesOptions = {
@@ -55,6 +56,12 @@ type PullRequestNode = {
const PULL_REQUEST_BATCH = 50;
const SEARCH_BATCH = 20;
export class FileSystem {
writeFileSync(path: string, data: string) {
writeFileSync(path, data);
}
}
export class GitClient {
log(previous: string, tag: string) {
return execFileSync(
@@ -189,6 +196,7 @@ export class GitHubClient {
export class NotesService {
constructor(
private fs: Public<FileSystem>,
private git: Public<GitClient>,
private github: Public<GitHubClient>,
) {}
@@ -198,8 +206,9 @@ export class NotesService {
const pulls = await this.getPullRequests(commits);
const changes = this.selectChanges(commits, pulls);
const firstTimers = await this.getFirstTimers(changes);
const notes = this.render({ tag, previous, changes, firstTimers });
return this.render({ tag, previous, changes, firstTimers });
this.fs.writeFileSync(Files.Output, notes);
}
private parseCommits(log: string): Commit[] {