@@ -6,6 +6,18 @@ import { beforeAll, describe, expect, it } from "vitest";
66
77const ROOT = resolve ( __dirname , ".." , ".." , ".." ) ;
88const SCRIPT = resolve ( ROOT , ".github" , "scripts" , "generate-release-notes.sh" ) ;
9+ const RELEASE_NOTES_ENV_KEYS = [
10+ "RELEASE_NOTES_BASE_URL" ,
11+ "RELEASE_NOTES_API_KEY" ,
12+ "RELEASE_NOTES_MODEL" ,
13+ "RELEASE_NOTES_REQUEST_TIMEOUT_MS" ,
14+ ] as const ;
15+
16+ function withoutReleaseNotesEnv ( env : NodeJS . ProcessEnv ) : NodeJS . ProcessEnv {
17+ const sanitized = { ...env } ;
18+ for ( const key of RELEASE_NOTES_ENV_KEYS ) delete sanitized [ key ] ;
19+ return sanitized ;
20+ }
921
1022function git ( cwd : string , args : string [ ] ) : string {
1123 return execFileSync ( "git" , args , { cwd, encoding : "utf-8" , stdio : [ "ignore" , "pipe" , "pipe" ] } ) ;
@@ -22,10 +34,11 @@ function hasBash(): boolean {
2234
2335const describeIfBash = hasBash ( ) ? describe : describe . skip ;
2436
25- function runNotes ( cwd : string , tag : string ) : string {
37+ function runNotes ( cwd : string , tag : string , env : NodeJS . ProcessEnv = process . env ) : string {
2638 return execFileSync ( "bash" , [ SCRIPT , tag ] , {
2739 cwd,
2840 encoding : "utf-8" ,
41+ env : withoutReleaseNotesEnv ( env ) ,
2942 stdio : [ "ignore" , "pipe" , "pipe" ] ,
3043 } ) ;
3144}
@@ -56,6 +69,27 @@ function createRepo(): string {
5669 return cwd ;
5770}
5871
72+ function createSquashPromotionRepo ( ) : string {
73+ const cwd = createRepo ( ) ;
74+ git ( cwd , [ "checkout" , "-b" , "dev" ] ) ;
75+ writeText ( cwd , "src/app.txt" , "real fix\n" ) ;
76+ commitAll ( cwd , "fix: real user-facing fix (#10)" ) ;
77+ writeText ( cwd , "src/helper.txt" , "cleanup\n" ) ;
78+ commitAll ( cwd , "feat: user-visible helper feature (#11)" ) ;
79+ git ( cwd , [ "update-ref" , "refs/remotes/origin/dev" , "dev" ] ) ;
80+
81+ git ( cwd , [ "checkout" , "master" ] ) ;
82+ git ( cwd , [ "read-tree" , "--reset" , "-u" , "dev" ] ) ;
83+ commitAll ( cwd , "fix: promote dev release fixes to master" ) ;
84+ writeText ( cwd , "README.md" , "synced readme\n" ) ;
85+ writeText ( cwd , "package.json" , "{\"version\":\"1.0.1\"}\n" ) ;
86+ writeText ( cwd , "package-lock.json" , "{\"version\":\"1.0.1\",\"packages\":{\"\":{\"version\":\"1.0.1\"},\"packages/electron\":{\"version\":\"1.0.1\"}}}\n" ) ;
87+ writeText ( cwd , "packages/electron/package.json" , "{\"version\":\"1.0.1\"}\n" ) ;
88+ commitAll ( cwd , "chore: bump version to 1.0.1 [skip ci]" ) ;
89+ git ( cwd , [ "tag" , "v1.0.1" ] ) ;
90+ return cwd ;
91+ }
92+
5993describe ( "generate-release-notes.sh" , ( ) => {
6094 beforeAll ( ( ) => {
6195 expect ( existsSync ( SCRIPT ) , `script missing: ${ SCRIPT } ` ) . toBe ( true ) ;
@@ -69,6 +103,19 @@ describe("generate-release-notes.sh", () => {
69103 expect ( workflow ) . toContain ( "bash .github/scripts/generate-release-notes.sh \"$TAG\" > /tmp/release-notes.md" ) ;
70104 } ) ;
71105
106+ it ( "does not pass release-notes LLM configuration to fallback fixtures" , ( ) => {
107+ const env = withoutReleaseNotesEnv ( {
108+ PATH : process . env . PATH ,
109+ RELEASE_NOTES_BASE_URL : "http://example.test/v1" ,
110+ RELEASE_NOTES_API_KEY : "test-key" ,
111+ RELEASE_NOTES_MODEL : "test-model" ,
112+ RELEASE_NOTES_REQUEST_TIMEOUT_MS : "1" ,
113+ } ) ;
114+
115+ expect ( env ) . toMatchObject ( { PATH : process . env . PATH } ) ;
116+ for ( const key of RELEASE_NOTES_ENV_KEYS ) expect ( env [ key ] ) . toBeUndefined ( ) ;
117+ } ) ;
118+
72119} ) ;
73120
74121describeIfBash ( "generate-release-notes.sh bash behavior" , ( ) => {
@@ -91,23 +138,7 @@ describeIfBash("generate-release-notes.sh bash behavior", () => {
91138 } ) ;
92139
93140 it ( "falls back to dev history when a stable tag only contains a squash promotion" , ( ) => {
94- const cwd = createRepo ( ) ;
95- git ( cwd , [ "checkout" , "-b" , "dev" ] ) ;
96- writeText ( cwd , "src/app.txt" , "real fix\n" ) ;
97- commitAll ( cwd , "fix: real user-facing fix (#10)" ) ;
98- writeText ( cwd , "src/helper.txt" , "cleanup\n" ) ;
99- commitAll ( cwd , "feat: user-visible helper feature (#11)" ) ;
100- git ( cwd , [ "update-ref" , "refs/remotes/origin/dev" , "dev" ] ) ;
101-
102- git ( cwd , [ "checkout" , "master" ] ) ;
103- git ( cwd , [ "read-tree" , "--reset" , "-u" , "dev" ] ) ;
104- commitAll ( cwd , "fix: promote dev release fixes to master" ) ;
105- writeText ( cwd , "README.md" , "synced readme\n" ) ;
106- writeText ( cwd , "package.json" , "{\"version\":\"1.0.1\"}\n" ) ;
107- writeText ( cwd , "package-lock.json" , "{\"version\":\"1.0.1\",\"packages\":{\"\":{\"version\":\"1.0.1\"},\"packages/electron\":{\"version\":\"1.0.1\"}}}\n" ) ;
108- writeText ( cwd , "packages/electron/package.json" , "{\"version\":\"1.0.1\"}\n" ) ;
109- commitAll ( cwd , "chore: bump version to 1.0.1 [skip ci]" ) ;
110- git ( cwd , [ "tag" , "v1.0.1" ] ) ;
141+ const cwd = createSquashPromotionRepo ( ) ;
111142
112143 const notes = runNotes ( cwd , "v1.0.1" ) ;
113144
@@ -116,6 +147,34 @@ describeIfBash("generate-release-notes.sh bash behavior", () => {
116147 expect ( notes ) . not . toContain ( "promote dev release fixes" ) ;
117148 } ) ;
118149
150+ it ( "keeps the squash-promotion fallback local when ambient LLM configuration is present" , ( ) => {
151+ const cwd = createSquashPromotionRepo ( ) ;
152+ const shimDir = join ( cwd , "node-shim" ) ;
153+ const leakedConfigFile = join ( cwd , "release-notes-config-leaked" ) ;
154+ mkdirSync ( shimDir , { recursive : true } ) ;
155+ writeFileSync (
156+ join ( shimDir , "node" ) ,
157+ "#!/bin/sh\nif [ -n \"${RELEASE_NOTES_BASE_URL:-}\" ] || [ -n \"${RELEASE_NOTES_API_KEY:-}\" ] || [ -n \"${RELEASE_NOTES_MODEL:-}\" ] || [ -n \"${RELEASE_NOTES_REQUEST_TIMEOUT_MS:-}\" ]; then\n touch \"$RELEASE_NOTES_ENV_LEAK_FILE\"\n exit 91\nfi\nexec \"$REAL_NODE\" \"$@\"\n" ,
158+ { mode : 0o755 } ,
159+ ) ;
160+
161+ const notes = runNotes ( cwd , "v1.0.1" , {
162+ ...process . env ,
163+ PATH : `${ shimDir } :${ process . env . PATH } ` ,
164+ REAL_NODE : process . execPath ,
165+ RELEASE_NOTES_ENV_LEAK_FILE : leakedConfigFile ,
166+ RELEASE_NOTES_BASE_URL : "http://127.0.0.1:9/v1" ,
167+ RELEASE_NOTES_API_KEY : "test-key" ,
168+ RELEASE_NOTES_MODEL : "test-model" ,
169+ RELEASE_NOTES_REQUEST_TIMEOUT_MS : "3000" ,
170+ } ) ;
171+
172+ expect ( existsSync ( leakedConfigFile ) ) . toBe ( false ) ;
173+ expect ( notes ) . toContain ( "### Fixes" ) ;
174+ expect ( notes ) . toContain ( "real user-facing fix (#10)" ) ;
175+ expect ( notes ) . toContain ( "user-visible helper feature (#11)" ) ;
176+ } ) ;
177+
119178 it ( "uses topological sorting (git describe) rather than semver sorting to avoid pulling in old history from unrelated higher-version tags" , ( ) => {
120179 const cwd = createRepo ( ) ; // v1.0.0 is created here (commit C1)
121180
0 commit comments