Skip to content

Commit 9b8e69c

Browse files
authored
feat: explicit promotion callout in PR check comment (#113)
Follow-up from [#110](#110 comment looking identical to an ordinary feature PR's. A promotion PR is the highest-stakes merge in the channel flow — it ends the cycle, consolidates the changelog, and ships to `@latest` — so the comment now says so. **Detection:** stable-targeted PR (no channel match on the base) carrying bump files with `channel` set. Covers the canonical `next` → `main` PR and partial promotions from branches cut off a channel. Channel-targeted PRs keep their existing banner. **Headline** becomes: > **This PR promotes the `next` prerelease cycle to a stable release.** The changes below that already shipped to the `@next` dist-tag will be consolidated into the next stable version bump. **File list** annotates shipped files, so mixed PRs (shipped cycle + never-rc'd fixes) read at a glance: > - `next/prerelease-channels.md` _(shipped on `@next`)_ > - `ci-check-channel-bump-files.md` The shipped annotation also appears on graduation PRs (e.g. `.bumpy/alpha/` files on a beta-targeted PR), where it's equally accurate. 297 tests pass. Once merged, #110's comment should update itself with the promotion callout (next moves forward → synchronize → check-local rebuilds).
1 parent 0175938 commit 9b8e69c

3 files changed

Lines changed: 64 additions & 8 deletions

File tree

.bumpy/promotion-pr-comment.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@varlock/bumpy': patch
3+
---
4+
5+
The PR check comment now explicitly calls out promotion PRs (channel → stable): the headline explains that merging ends the prerelease cycle and ships stable, and bump files that already shipped on a channel are annotated with their dist-tag (e.g. `next/feature.md` _(shipped on `@next`)_).

packages/bumpy/src/commands/ci.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,10 @@ export async function ciCheckCommand(rootDir: string, opts: CheckOptions): Promi
108108
// Skip on the version PR branch (and channel release PR branches) — they move/consume
109109
// bump files by design
110110
const prBranchName = detectPrBranch(rootDir);
111+
const channels = resolveChannels(config);
111112
const releasePrBranches = new Set([
112113
config.versionPr.branch,
113-
...[...resolveChannels(config).values()].map((c) => c.versionPr.branch),
114+
...[...channels.values()].map((c) => c.versionPr.branch),
114115
]);
115116
if (prBranchName && releasePrBranches.has(prBranchName)) {
116117
log.dim(' Skipping — this is a release PR branch.');
@@ -228,6 +229,7 @@ export async function ciCheckCommand(rootDir: string, opts: CheckOptions): Promi
228229
parseErrors,
229230
emptyBumpFileIds,
230231
prChannel,
232+
channels,
231233
);
232234
await postOrUpdatePrComment(prNumber, comment, rootDir);
233235
}
@@ -1024,6 +1026,7 @@ export function formatReleasePlanComment(
10241026
parseErrors: string[] = [],
10251027
emptyBumpFileIds: string[] = [],
10261028
channel: ResolvedChannel | null = null,
1029+
allChannels: Map<string, ResolvedChannel> | null = null,
10271030
): string {
10281031
const repo = process.env.GITHUB_REPOSITORY;
10291032
const lines: string[] = [];
@@ -1032,9 +1035,17 @@ export function formatReleasePlanComment(
10321035
// `-<preid>.x` suffix (the exact counter is derived from the registry at publish time).
10331036
const versionSuffix = channel ? `-${channel.preid}.x` : '';
10341037

1038+
// Promotion PR: stable-targeted, carrying bump files that already shipped on a
1039+
// channel (e.g. `next` → `main`). Merging it ends the cycle and ships stable.
1040+
const promotedChannels = channel ? [] : [...new Set(bumpFiles.map((bf) => bf.channel))].filter((c) => c != null);
1041+
const channelTag = (name: string) => `\`@${allChannels?.get(name)?.tag ?? name}\``;
1042+
10351043
const headline = channel
10361044
? `**This PR targets the \`${channel.name}\` prerelease channel** — merging it ships these packages as a **prerelease** to the \`@${channel.tag}\` dist-tag, not a stable release.`
1037-
: '**The changes in this PR will be included in the next version bump.**';
1045+
: promotedChannels.length > 0
1046+
? `**This PR promotes the ${promotedChannels.map((c) => `\`${c}\``).join(', ')} prerelease cycle${promotedChannels.length > 1 ? 's' : ''} to a stable release.** ` +
1047+
`The changes below that already shipped to the ${promotedChannels.map(channelTag).join(', ')} dist-tag${promotedChannels.length > 1 ? 's' : ''} will be consolidated into the next stable version bump.`
1048+
: '**The changes in this PR will be included in the next version bump.**';
10381049
const preamble = [
10391050
`<a href="https://bumpy.varlock.dev"><img src="${FROG_IMG_BASE}/frog-clipboard.png" alt="bumpy-frog" width="60" align="left" style="image-rendering: pixelated;" title="Hi! I'm bumpy!" /></a>`,
10401051
'',
@@ -1082,6 +1093,7 @@ export function formatReleasePlanComment(
10821093
// Channel-dir files (pending on promotion/graduation PRs) live at `.bumpy/<channel>/`
10831094
const filename = bf.channel ? `${bf.channel}/${bf.id}.md` : `${bf.id}.md`;
10841095
const parts: string[] = [`\`${filename}\``];
1096+
if (bf.channel) parts.push(`_(shipped on ${channelTag(bf.channel)})_`);
10851097
if (repo) {
10861098
parts.push(
10871099
`([view diff](https://github.com/${repo}/pull/${prNumber}/changes#diff-${sha256Hex(`.bumpy/${filename}`)}))`,

packages/bumpy/test/core/ci-channel-comment.test.ts

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ import { formatReleasePlanComment } from '../../src/commands/ci.ts';
33
import { resolveChannels } from '../../src/core/channels.ts';
44
import { makeRelease, makeReleasePlan, makeBumpFile, makeConfig } from '../helpers.ts';
55

6-
const channel = resolveChannels(makeConfig({ channels: { next: { branch: 'next', preid: 'rc', tag: 'next' } } })).get(
7-
'next',
8-
)!;
6+
const allChannels = resolveChannels(makeConfig({ channels: { next: { branch: 'next', preid: 'rc', tag: 'next' } } }));
7+
const channel = allChannels.get('next')!;
98

109
const plan = makeReleasePlan(
1110
[makeRelease('@myorg/core', '1.2.0', { type: 'minor', oldVersion: '1.1.0', bumpFiles: ['feat'] })],
@@ -51,14 +50,54 @@ describe('formatReleasePlanComment — promotion PR (channel-dir bump files, sta
5150
[makeRelease('@myorg/core', '1.2.0', { type: 'minor', oldVersion: '1.1.0', bumpFiles: ['feat'] })],
5251
[{ ...makeBumpFile('feat', [{ name: '@myorg/core', type: 'minor' }], 'Add a feature'), channel: 'next' }],
5352
);
54-
const comment = formatReleasePlanComment(promotionPlan, promotionPlan.bumpFiles, '1', 'next', 'npm');
53+
const comment = formatReleasePlanComment(
54+
promotionPlan,
55+
promotionPlan.bumpFiles,
56+
'1',
57+
'next',
58+
'npm',
59+
[],
60+
[],
61+
[],
62+
null,
63+
allChannels,
64+
);
65+
66+
test('headline calls out the promotion explicitly', () => {
67+
expect(comment).toContain('promotes the `next` prerelease cycle to a stable release');
68+
expect(comment).toContain('already shipped to the `@next` dist-tag');
69+
expect(comment).not.toContain('included in the next version bump');
70+
});
5571

56-
test('renders channel-dir bump files with their subdir path', () => {
57-
expect(comment).toContain('`next/feat.md`');
72+
test('renders channel-dir bump files with their subdir path and shipped annotation', () => {
73+
expect(comment).toContain('`next/feat.md` _(shipped on `@next`)_');
5874
});
5975

6076
test('shows the stable plan (no channel banner, no preid suffix)', () => {
6177
expect(comment).toContain('1.1.0 → **1.2.0**');
6278
expect(comment).not.toContain('prerelease channel');
6379
});
80+
81+
test('mixed promotion: root bump files get no shipped annotation', () => {
82+
const mixedPlan = makeReleasePlan(promotionPlan.releases, [
83+
...promotionPlan.bumpFiles,
84+
makeBumpFile('new-fix', [{ name: '@myorg/core', type: 'patch' }], 'A fix that never shipped as an rc'),
85+
]);
86+
const mixed = formatReleasePlanComment(
87+
mixedPlan,
88+
mixedPlan.bumpFiles,
89+
'1',
90+
'next',
91+
'npm',
92+
[],
93+
[],
94+
[],
95+
null,
96+
allChannels,
97+
);
98+
expect(mixed).toContain('promotes the `next` prerelease cycle');
99+
expect(mixed).toContain('`next/feat.md` _(shipped on `@next`)_');
100+
expect(mixed).toContain('- `new-fix.md`');
101+
expect(mixed).not.toContain('`new-fix.md` _(shipped');
102+
});
64103
});

0 commit comments

Comments
 (0)