diff --git a/.bumpy/ci-check-channel-bump-files.md b/.bumpy/ci-check-channel-bump-files.md new file mode 100644 index 0000000..b271f08 --- /dev/null +++ b/.bumpy/ci-check-channel-bump-files.md @@ -0,0 +1,5 @@ +--- +'@varlock/bumpy': patch +--- + +`ci check` now reads bump files in channel directories, so promotion PRs (channel → main) and graduation PRs (channel → channel) correctly report the cycle's pending releases instead of failing with "no bump files found". Channel-dir bump files render with their subdir path (`next/feature.md`) so view/edit links resolve. diff --git a/packages/bumpy/src/commands/ci.ts b/packages/bumpy/src/commands/ci.ts index dc94834..df1d17c 100644 --- a/packages/bumpy/src/commands/ci.ts +++ b/packages/bumpy/src/commands/ci.ts @@ -97,7 +97,13 @@ export async function ciCheckCommand(rootDir: string, opts: CheckOptions): Promi const config = await loadConfig(rootDir); const { packages } = await discoverWorkspace(rootDir, config); const depGraph = new DependencyGraph(packages); - const { bumpFiles: allBumpFiles, errors: parseErrors } = await readBumpFiles(rootDir); + // Read channel dirs too: on promotion (channel → main) and graduation (channel → + // channel) PRs, the pending bump files live in `.bumpy//`. Feature PRs + // never have shipped channel files in their diff vs the PR base, so this only + // surfaces files where they're genuinely pending. + const { bumpFiles: allBumpFiles, errors: parseErrors } = await readBumpFiles(rootDir, { + channels: channelNames(config), + }); // Skip on the version PR branch (and channel release PR branches) — they move/consume // bump files by design @@ -1073,7 +1079,8 @@ export function formatReleasePlanComment( lines.push(`#### Bump files in this PR`); lines.push(''); for (const bf of bumpFiles) { - const filename = `${bf.id}.md`; + // Channel-dir files (pending on promotion/graduation PRs) live at `.bumpy//` + const filename = bf.channel ? `${bf.channel}/${bf.id}.md` : `${bf.id}.md`; const parts: string[] = [`\`${filename}\``]; if (repo) { parts.push( diff --git a/packages/bumpy/test/core/bump-file-channels.test.ts b/packages/bumpy/test/core/bump-file-channels.test.ts index 387eb3d..ac03688 100644 --- a/packages/bumpy/test/core/bump-file-channels.test.ts +++ b/packages/bumpy/test/core/bump-file-channels.test.ts @@ -2,7 +2,12 @@ import { describe, test, expect } from 'bun:test'; import { mkdir, writeFile } from 'node:fs/promises'; import { existsSync } from 'node:fs'; import { resolve } from 'node:path'; -import { readBumpFiles, moveBumpFilesToChannel, recoverDeletedBumpFiles } from '../../src/core/bump-file.ts'; +import { + readBumpFiles, + moveBumpFilesToChannel, + recoverDeletedBumpFiles, + filterBranchBumpFiles, +} from '../../src/core/bump-file.ts'; import { createTempGitRepo, cleanupTempDir, gitInDir } from '../helpers.ts'; async function writeBumpFileAt(dir: string, relPath: string, pkgName = 'pkg-a', bump = 'minor'): Promise { @@ -41,6 +46,19 @@ describe('readBumpFiles with channels', () => { } }); + test('channel-dir files match diff paths in filterBranchBumpFiles (promotion PR shape)', async () => { + const dir = await createTempGitRepo(); + try { + await writeBumpFileAt(dir, '.bumpy/next/shipped-feature.md'); + const { bumpFiles } = await readBumpFiles(dir, { channels: ['next'] }); + // A promotion PR's diff vs main lists the channel-dir path + const { branchBumpFiles } = filterBranchBumpFiles(bumpFiles, ['.bumpy/next/shipped-feature.md'], dir); + expect(branchBumpFiles.map((bf) => bf.id)).toEqual(['shipped-feature']); + } finally { + await cleanupTempDir(dir); + } + }); + test('flags duplicate IDs across root and channel dirs as an error', async () => { const dir = await createTempGitRepo(); try { diff --git a/packages/bumpy/test/core/ci-channel-comment.test.ts b/packages/bumpy/test/core/ci-channel-comment.test.ts index b548ecd..7b50132 100644 --- a/packages/bumpy/test/core/ci-channel-comment.test.ts +++ b/packages/bumpy/test/core/ci-channel-comment.test.ts @@ -44,3 +44,21 @@ describe('formatReleasePlanComment — prerelease channel', () => { expect(comment).toContain('Promote to a stable release by merging `next`'); }); }); + +describe('formatReleasePlanComment — promotion PR (channel-dir bump files, stable target)', () => { + // On a promotion PR (next → main) the pending bump files live in `.bumpy/next/` + const promotionPlan = makeReleasePlan( + [makeRelease('@myorg/core', '1.2.0', { type: 'minor', oldVersion: '1.1.0', bumpFiles: ['feat'] })], + [{ ...makeBumpFile('feat', [{ name: '@myorg/core', type: 'minor' }], 'Add a feature'), channel: 'next' }], + ); + const comment = formatReleasePlanComment(promotionPlan, promotionPlan.bumpFiles, '1', 'next', 'npm'); + + test('renders channel-dir bump files with their subdir path', () => { + expect(comment).toContain('`next/feat.md`'); + }); + + test('shows the stable plan (no channel banner, no preid suffix)', () => { + expect(comment).toContain('1.1.0 → **1.2.0**'); + expect(comment).not.toContain('prerelease channel'); + }); +});