From 84c75f50aa6b51d5940335ca2b9b6cbc06ab5576 Mon Sep 17 00:00:00 2001 From: sudacode Date: Thu, 9 Jul 2026 02:26:00 -0700 Subject: [PATCH] fix(release): skip gh attribution without CI token --- package.json | 2 +- scripts/build-changelog.test.ts | 96 +++++++++++++++++++++++++++++++++ scripts/build-changelog.ts | 9 ++++ 3 files changed, 106 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 38ac396f..bd1ff51f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "subminer", "productName": "SubMiner", "desktopName": "SubMiner.desktop", - "version": "0.18.0-beta.3", + "version": "0.18.0-beta.4", "description": "All-in-one sentence mining overlay with AnkiConnect and dictionary integration", "packageManager": "bun@1.3.5", "main": "dist/main-entry.js", diff --git a/scripts/build-changelog.test.ts b/scripts/build-changelog.test.ts index 779e530b..807672f7 100644 --- a/scripts/build-changelog.test.ts +++ b/scripts/build-changelog.test.ts @@ -1213,6 +1213,102 @@ test('writeChangelogArtifacts appends contributor attribution and a new-contribu } }); +test('writeChangelogArtifacts skips contributor attribution in GitHub Actions without a token', async () => { + const { writeChangelogArtifacts } = await loadModule(); + const workspace = createWorkspace('release-notes-actions-no-token'); + const projectRoot = path.join(workspace, 'SubMiner'); + const originalActions = process.env.GITHUB_ACTIONS; + const originalGhToken = process.env.GH_TOKEN; + const originalGithubToken = process.env.GITHUB_TOKEN; + const originalPath = process.env.PATH; + const originalWarn = console.warn; + const warnings: string[] = []; + + fs.mkdirSync(path.join(projectRoot, 'changes'), { recursive: true }); + fs.writeFileSync(path.join(projectRoot, 'CHANGELOG.md'), '# Changelog\n', 'utf8'); + fs.writeFileSync( + path.join(projectRoot, 'changes', '001.md'), + ['type: added', 'area: release', '', '- Added a feature.'].join('\n'), + 'utf8', + ); + + try { + process.env.GITHUB_ACTIONS = 'true'; + delete process.env.GH_TOKEN; + delete process.env.GITHUB_TOKEN; + process.env.PATH = workspace; + console.warn = (message?: unknown) => { + warnings.push(String(message)); + }; + + writeChangelogArtifacts({ + cwd: projectRoot, + version: '0.6.0', + date: '2026-05-06', + deps: { runClaude: defaultStubClaude().runClaude }, + }); + + assert.deepEqual(warnings, []); + const releaseNotes = fs.readFileSync( + path.join(projectRoot, 'release', 'release-notes.md'), + 'utf8', + ); + assert.doesNotMatch(releaseNotes, /## What's Changed/); + } finally { + console.warn = originalWarn; + if (originalActions === undefined) { + delete process.env.GITHUB_ACTIONS; + } else { + process.env.GITHUB_ACTIONS = originalActions; + } + if (originalGhToken === undefined) { + delete process.env.GH_TOKEN; + } else { + process.env.GH_TOKEN = originalGhToken; + } + if (originalGithubToken === undefined) { + delete process.env.GITHUB_TOKEN; + } else { + process.env.GITHUB_TOKEN = originalGithubToken; + } + if (originalPath === undefined) { + delete process.env.PATH; + } else { + process.env.PATH = originalPath; + } + fs.rmSync(workspace, { recursive: true, force: true }); + } +}); + +test('shouldSkipDefaultContributionLookup skips GitHub Actions without a gh token', async () => { + const { shouldSkipDefaultContributionLookup } = await loadModule(); + + assert.equal( + shouldSkipDefaultContributionLookup({ + GITHUB_ACTIONS: 'true', + GH_TOKEN: undefined, + GITHUB_TOKEN: undefined, + }), + true, + ); + assert.equal( + shouldSkipDefaultContributionLookup({ + GITHUB_ACTIONS: 'true', + GH_TOKEN: 'ghs_test', + GITHUB_TOKEN: undefined, + }), + false, + ); + assert.equal( + shouldSkipDefaultContributionLookup({ + GITHUB_ACTIONS: undefined, + GH_TOKEN: undefined, + GITHUB_TOKEN: undefined, + }), + false, + ); +}); + test('writeReleaseNotesForVersion preserves committed contributor attribution before installation', async () => { const { writeReleaseNotesForVersion } = await loadModule(); const workspace = createWorkspace('release-notes-preserve-attribution'); diff --git a/scripts/build-changelog.ts b/scripts/build-changelog.ts index 1194a97d..df6ca317 100644 --- a/scripts/build-changelog.ts +++ b/scripts/build-changelog.ts @@ -345,6 +345,12 @@ function resolveFragmentRelativePath(fragmentPath: string, cwd: string): string return path.relative(cwd, fragmentPath).split(path.sep).join('/'); } +export function shouldSkipDefaultContributionLookup( + env: Partial> = process.env, +): boolean { + return env.GITHUB_ACTIONS === 'true' && !env.GH_TOKEN && !env.GITHUB_TOKEN; +} + // Walks git history + the GitHub API to attribute each released fragment to the // PR (and author) that introduced it. One git call and one gh call per fragment, // plus one gh call per unique author for the first-contribution check. Best @@ -354,6 +360,9 @@ function defaultResolveContributions(fragmentPaths: string[], cwd: string): Cont if (fragmentPaths.length === 0) { return []; } + if (shouldSkipDefaultContributionLookup()) { + return []; + } try { const slug = execFileSync(