fix(ci): release notes script (#32827)
(cherry picked from commit f47b02f91d6d55e0b0281609c37e6b1676626115)
This commit is contained in:
@@ -154,8 +154,8 @@ jobs:
|
|||||||
previous=$(grep -A1 -xF "${TAG}" <<< "${tags}" | tail -1)
|
previous=$(grep -A1 -xF "${TAG}" <<< "${tags}" | tail -1)
|
||||||
echo "generating notes from ${previous}"
|
echo "generating notes from ${previous}"
|
||||||
|
|
||||||
mise //:notes --tag "${TAG}" --previous "${previous}" > "${RUNNER_TEMP}/notes.md"
|
mise //:notes --tag "${TAG}" --previous "${previous}"
|
||||||
cat "${RUNNER_TEMP}/notes.md"
|
cat misc/release/notes.md
|
||||||
|
|
||||||
- name: Create draft release
|
- name: Create draft release
|
||||||
env:
|
env:
|
||||||
@@ -171,7 +171,7 @@ jobs:
|
|||||||
--draft \
|
--draft \
|
||||||
--verify-tag \
|
--verify-tag \
|
||||||
--prerelease="${PRERELEASE}" \
|
--prerelease="${PRERELEASE}" \
|
||||||
--notes-file "${RUNNER_TEMP}/notes.md" \
|
--notes-file misc/release/notes.md \
|
||||||
docker/docker-compose.yml \
|
docker/docker-compose.yml \
|
||||||
docker/docker-compose.rootless.yml \
|
docker/docker-compose.rootless.yml \
|
||||||
docker/example.env \
|
docker/example.env \
|
||||||
|
|||||||
@@ -28,3 +28,4 @@ vite.config.js.timestamp-*
|
|||||||
.devcontainer/.env*
|
.devcontainer/.env*
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
*.tsbuildInfo
|
*.tsbuildInfo
|
||||||
|
misc/release/notes.md
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Command, Option } from 'commander';
|
import { Command, Option } from 'commander';
|
||||||
import {
|
import {
|
||||||
|
FileSystem,
|
||||||
GitClient,
|
GitClient,
|
||||||
GitHubClient,
|
GitHubClient,
|
||||||
type NotesOptions,
|
type NotesOptions,
|
||||||
@@ -63,10 +64,11 @@ export const cli = (argv: string[]) => {
|
|||||||
.action(async ({ token, ...options }: NotesOptions & { token: string }) => {
|
.action(async ({ token, ...options }: NotesOptions & { token: string }) => {
|
||||||
try {
|
try {
|
||||||
const service = new NotesService(
|
const service = new NotesService(
|
||||||
|
new FileSystem(),
|
||||||
new GitClient(),
|
new GitClient(),
|
||||||
new GitHubClient(token),
|
new GitHubClient(token),
|
||||||
);
|
);
|
||||||
process.stdout.write(await service.handleNotes(options));
|
await service.handleNotes(options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error instanceof Error ? error.message : error);
|
console.error(error instanceof Error ? error.message : error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { Public } from 'src/types.js';
|
|||||||
import { beforeEach, describe, expect, it, type Mocked, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, type Mocked, vi } from 'vitest';
|
||||||
import {
|
import {
|
||||||
type Contributor,
|
type Contributor,
|
||||||
|
type FileSystem,
|
||||||
type GitClient,
|
type GitClient,
|
||||||
type GitHubClient,
|
type GitHubClient,
|
||||||
NotesService,
|
NotesService,
|
||||||
@@ -37,6 +38,7 @@ const changelog = (...lines: string[]) =>
|
|||||||
const body = (notes: string) => notes.slice(notes.indexOf("## What's Changed"));
|
const body = (notes: string) => notes.slice(notes.indexOf("## What's Changed"));
|
||||||
|
|
||||||
describe(NotesService.name, () => {
|
describe(NotesService.name, () => {
|
||||||
|
let fs: Mocked<Public<FileSystem>>;
|
||||||
let git: Mocked<Public<GitClient>>;
|
let git: Mocked<Public<GitClient>>;
|
||||||
let github: Mocked<Public<GitHubClient>>;
|
let github: Mocked<Public<GitHubClient>>;
|
||||||
let sut: NotesService;
|
let sut: NotesService;
|
||||||
@@ -88,12 +90,16 @@ describe(NotesService.name, () => {
|
|||||||
github.getFirstTimers.mockResolvedValue(firstTimers);
|
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(() => {
|
beforeEach(() => {
|
||||||
|
fs = { writeFileSync: vi.fn() };
|
||||||
git = { log: vi.fn() };
|
git = { log: vi.fn() };
|
||||||
github = { getPullRequests: vi.fn(), getFirstTimers: 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 () => {
|
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');
|
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 () => {
|
it('should start with the template and end with the full changelog', async () => {
|
||||||
setup([{ number: 1 }]);
|
setup([{ number: 1 }]);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { execFileSync } from 'node:child_process';
|
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 { dirname, join } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import type { Public } from 'src/types.js';
|
import type { Public } from 'src/types.js';
|
||||||
@@ -15,6 +15,7 @@ const REPO = 'immich-app/immich';
|
|||||||
const Files = {
|
const Files = {
|
||||||
Config: join(root, '.github/release.yml'),
|
Config: join(root, '.github/release.yml'),
|
||||||
Template: join(root, 'misc/release/notes.tmpl'),
|
Template: join(root, 'misc/release/notes.tmpl'),
|
||||||
|
Output: join(root, 'misc/release/notes.md'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NotesOptions = {
|
export type NotesOptions = {
|
||||||
@@ -55,6 +56,12 @@ type PullRequestNode = {
|
|||||||
const PULL_REQUEST_BATCH = 50;
|
const PULL_REQUEST_BATCH = 50;
|
||||||
const SEARCH_BATCH = 20;
|
const SEARCH_BATCH = 20;
|
||||||
|
|
||||||
|
export class FileSystem {
|
||||||
|
writeFileSync(path: string, data: string) {
|
||||||
|
writeFileSync(path, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class GitClient {
|
export class GitClient {
|
||||||
log(previous: string, tag: string) {
|
log(previous: string, tag: string) {
|
||||||
return execFileSync(
|
return execFileSync(
|
||||||
@@ -189,6 +196,7 @@ export class GitHubClient {
|
|||||||
|
|
||||||
export class NotesService {
|
export class NotesService {
|
||||||
constructor(
|
constructor(
|
||||||
|
private fs: Public<FileSystem>,
|
||||||
private git: Public<GitClient>,
|
private git: Public<GitClient>,
|
||||||
private github: Public<GitHubClient>,
|
private github: Public<GitHubClient>,
|
||||||
) {}
|
) {}
|
||||||
@@ -198,8 +206,9 @@ export class NotesService {
|
|||||||
const pulls = await this.getPullRequests(commits);
|
const pulls = await this.getPullRequests(commits);
|
||||||
const changes = this.selectChanges(commits, pulls);
|
const changes = this.selectChanges(commits, pulls);
|
||||||
const firstTimers = await this.getFirstTimers(changes);
|
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[] {
|
private parseCommits(log: string): Commit[] {
|
||||||
|
|||||||
Reference in New Issue
Block a user