From 96a1e80c6f74e3f99cbf2fb15236d01007d48225 Mon Sep 17 00:00:00 2001 From: Yeachan-Heo Date: Thu, 6 Aug 2026 21:14:15 +0900 Subject: [PATCH] fix(ci): reject deleted package changelogs The released-heading guard caught emptied files but treated a deleted head file as exempt. A deleted package changelog loses the same released history and must fail through the existing violation contract. Tested: bun test scripts/changelog-history-guard.test.ts --- scripts/changelog-history-guard.test.ts | 10 ++++++++-- scripts/changelog-history-guard.ts | 12 ++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/scripts/changelog-history-guard.test.ts b/scripts/changelog-history-guard.test.ts index ce23011e22..8e5604ee5a 100644 --- a/scripts/changelog-history-guard.test.ts +++ b/scripts/changelog-history-guard.test.ts @@ -62,9 +62,15 @@ describe("compareHistory", () => { expect(violation?.removed).toEqual(["0.12.11"]); }); - test("ignores a file absent on either side", () => { + test("ignores a file absent at the base", () => { expect(compareHistory("packages/x/CHANGELOG.md", undefined, FULL)).toBeUndefined(); - expect(compareHistory("packages/x/CHANGELOG.md", FULL, undefined)).toBeUndefined(); + }); + + test("catches a changelog deleted at the head", () => { + const violation = compareHistory("packages/x/CHANGELOG.md", FULL, undefined); + expect(violation?.removed).toEqual(["0.12.12", "0.12.11"]); + expect(violation?.baseHeadingCount).toBe(2); + expect(violation?.headHeadingCount).toBe(0); }); test("does not flag reordering or rewording that keeps every version", () => { diff --git a/scripts/changelog-history-guard.ts b/scripts/changelog-history-guard.ts index bae3e389ee..f0b99a8c6c 100644 --- a/scripts/changelog-history-guard.ts +++ b/scripts/changelog-history-guard.ts @@ -61,17 +61,21 @@ export function releaseHeadings(text: string): string[] { /** * Compare one changelog across a range. * - * A file that did not exist at the base cannot have lost history, and a file - * deleted at the head is a different review conversation (and is visible in the - * diff as a deletion), so both return no violation here. + * A file that did not exist at the base cannot have lost history. A file that + * existed at the base but is deleted at the head has lost every released + * section, so deletion fails closed through the same violation contract. */ export function compareHistory( file: string, baseText: string | undefined, headText: string | undefined, ): ChangelogHistoryViolation | undefined { - if (baseText === undefined || headText === undefined) return undefined; + if (baseText === undefined) return undefined; const before = releaseHeadings(baseText); + if (headText === undefined) { + if (before.length === 0) return undefined; + return { file, removed: before, baseHeadingCount: before.length, headHeadingCount: 0 }; + } const after = new Set(releaseHeadings(headText)); const removed = before.filter(version => !after.has(version)); if (removed.length === 0) return undefined;