@varlock/bumpy 1.14.0-rc.0 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,7 +8,7 @@ import { a as getChangedFiles, p as withGitToken } from "./git-DAWj8LyV.mjs";
8
8
  import { t as randomName } from "./names-COooXAFg.mjs";
9
9
  import { n as findChangedPackages } from "./check-DIl9Dz68.mjs";
10
10
  import { channelNames, detectReleaseBranch, matchChannelByBranch, resolveChannels } from "./channels-CFXZkyGd.mjs";
11
- import { n as formatChannelVersionSummary, t as buildChannelReleasePlan } from "./prerelease-Blnk8FE1.mjs";
11
+ import { n as channelDisplayPlan, r as formatChannelVersionSummary, t as buildChannelReleasePlan } from "./prerelease-B2PVfXkm.mjs";
12
12
  import { t as resolveCommitMessage } from "./commit-message-BwsowSds.mjs";
13
13
  import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
14
14
  import { createHash } from "node:crypto";
@@ -77,9 +77,10 @@ async function ciCheckCommand(rootDir, opts) {
77
77
  const config = await loadConfig(rootDir);
78
78
  const { packages } = await discoverWorkspace(rootDir, config);
79
79
  const depGraph = new DependencyGraph(packages);
80
- const { bumpFiles: allBumpFiles, errors: parseErrors } = await readBumpFiles(rootDir);
80
+ const { bumpFiles: allBumpFiles, errors: parseErrors } = await readBumpFiles(rootDir, { channels: channelNames(config) });
81
81
  const prBranchName = detectPrBranch(rootDir);
82
- const releasePrBranches = new Set([config.versionPr.branch, ...[...resolveChannels(config).values()].map((c) => c.versionPr.branch)]);
82
+ const channels = resolveChannels(config);
83
+ const releasePrBranches = new Set([config.versionPr.branch, ...[...channels.values()].map((c) => c.versionPr.branch)]);
83
84
  if (prBranchName && releasePrBranches.has(prBranchName)) {
84
85
  log.dim(" Skipping — this is a release PR branch.");
85
86
  return;
@@ -115,14 +116,14 @@ async function ciCheckCommand(rootDir, opts) {
115
116
  return;
116
117
  }
117
118
  const plan = assembleReleasePlan(prBumpFiles, packages, depGraph, config, prChannel ? { prereleasePreid: prChannel.preid } : {});
118
- const releaseSuffix = prChannel ? `-${prChannel.preid}.?` : "";
119
+ const releaseSuffix = prChannel ? `-${prChannel.preid}.x` : "";
119
120
  log.bold(`${prBumpFiles.length} bump file(s) → ${plan.releases.length} package(s) to release${prChannel ? ` on the "${prChannel.name}" channel (@${prChannel.tag})` : ""}\n`);
120
121
  for (const r of plan.releases) {
121
122
  const tag = r.isDependencyBump ? " (dep)" : r.isCascadeBump ? " (cascade)" : "";
122
123
  console.log(` ${r.name}: ${r.oldVersion} → ${colorize(`${r.newVersion}${releaseSuffix}`, "cyan")}${tag}`);
123
124
  }
124
125
  if (plan.warnings.length > 0) for (const w of plan.warnings) log.warn(w);
125
- if (shouldComment && prNumber) await postOrUpdatePrComment(prNumber, formatReleasePlanComment(plan, prBumpFiles, prNumber, detectPrBranch(rootDir), pm, plan.warnings, parseErrors, emptyBumpFileIds, prChannel), rootDir);
126
+ if (shouldComment && prNumber) await postOrUpdatePrComment(prNumber, formatReleasePlanComment(plan, prBumpFiles, prNumber, detectPrBranch(rootDir), pm, plan.warnings, parseErrors, emptyBumpFileIds, prChannel, channels), rootDir);
126
127
  if (parseErrors.length > 0 && !opts.noFail) process.exit(1);
127
128
  const coveredPackages = new Set(plan.releases.map((r) => r.name));
128
129
  for (const bf of prBumpFiles) for (const release of bf.releases) coveredPackages.add(release.name);
@@ -169,7 +170,7 @@ async function ciPlanCommand(rootDir) {
169
170
  packageNames: plan.releases.map((r) => r.name)
170
171
  };
171
172
  else {
172
- const { findUnpublishedPackages } = await import("./publish-Cz0e4KYT.mjs");
173
+ const { findUnpublishedPackages } = await import("./publish-DWxi552d.mjs");
173
174
  const unpublished = await findUnpublishedPackages(packages, config);
174
175
  if (unpublished.length > 0) output = {
175
176
  mode: "publish",
@@ -253,7 +254,7 @@ async function ciReleaseCommand(rootDir, opts) {
253
254
  const msg = bumpFiles.length === 0 ? "No pending bump files — checking for unpublished packages..." : "Bump files found but no packages would be released — checking for unpublished packages...";
254
255
  log.info(msg);
255
256
  const recoveredBumpFiles = recoverDeletedBumpFiles(rootDir);
256
- const { publishCommand } = await import("./publish-Cz0e4KYT.mjs");
257
+ const { publishCommand } = await import("./publish-DWxi552d.mjs");
257
258
  await publishCommand(rootDir, {
258
259
  tag: opts.tag,
259
260
  recoveredBumpFiles
@@ -277,7 +278,7 @@ async function ciReleaseCommand(rootDir, opts) {
277
278
  */
278
279
  async function autoPublish(rootDir, config, plan, tag) {
279
280
  log.step("Running bumpy version...");
280
- const { versionCommand } = await import("./version-BUUf8vKC.mjs");
281
+ const { versionCommand } = await import("./version-CMJUopVj.mjs");
281
282
  await versionCommand(rootDir);
282
283
  log.step("Committing version changes...");
283
284
  runArgs([
@@ -306,7 +307,7 @@ async function autoPublish(rootDir, config, plan, tag) {
306
307
  ], { cwd: rootDir });
307
308
  }
308
309
  log.step("Running bumpy publish...");
309
- const { publishCommand } = await import("./publish-Cz0e4KYT.mjs");
310
+ const { publishCommand } = await import("./publish-DWxi552d.mjs");
310
311
  await publishCommand(rootDir, { tag });
311
312
  }
312
313
  /**
@@ -380,7 +381,7 @@ async function createVersionPr(rootDir, plan, config, packageDirs, branchName) {
380
381
  branch
381
382
  ], { cwd: rootDir });
382
383
  log.step("Running bumpy version...");
383
- const { versionCommand } = await import("./version-BUUf8vKC.mjs");
384
+ const { versionCommand } = await import("./version-CMJUopVj.mjs");
384
385
  await versionCommand(rootDir);
385
386
  runArgs([
386
387
  "git",
@@ -475,6 +476,93 @@ async function createVersionPr(rootDir, plan, config, packageDirs, branchName) {
475
476
  "checkout",
476
477
  baseBranch
477
478
  ], { cwd: rootDir });
479
+ await closePromotedChannelReleasePrs(rootDir, config, plan.bumpFiles);
480
+ }
481
+ /**
482
+ * Channels whose dirs gained bump files in the triggering push — i.e. this push is
483
+ * the promotion/graduation merge that delivered them. (Same range detection as the
484
+ * channel publish trigger.)
485
+ */
486
+ function detectArrivedChannelFiles(rootDir, config) {
487
+ const range = getPushEventRange();
488
+ let diffRange;
489
+ if (range) diffRange = `${range.before}..${range.after}`;
490
+ else {
491
+ if (!tryRunArgs([
492
+ "git",
493
+ "rev-parse",
494
+ "--verify",
495
+ "HEAD^"
496
+ ], { cwd: rootDir })) return /* @__PURE__ */ new Set();
497
+ diffRange = "HEAD^..HEAD";
498
+ }
499
+ const out = tryRunArgs([
500
+ "git",
501
+ "diff",
502
+ "--name-only",
503
+ "--diff-filter=A",
504
+ "--no-renames",
505
+ diffRange,
506
+ "--",
507
+ ".bumpy/"
508
+ ], { cwd: rootDir });
509
+ if (!out) return /* @__PURE__ */ new Set();
510
+ const knownChannels = new Set(channelNames(config));
511
+ const arrived = /* @__PURE__ */ new Set();
512
+ for (const f of out.split("\n")) {
513
+ if (!f.endsWith(".md") || f.endsWith("README.md")) continue;
514
+ const parts = f.split("/");
515
+ if (parts.length === 3 && knownChannels.has(parts[1])) arrived.add(parts[1]);
516
+ }
517
+ return arrived;
518
+ }
519
+ /**
520
+ * Close lingering channel release PRs whose cycles were promoted: once a channel's
521
+ * bump files are pending on this branch (via a promotion or graduation merge), the
522
+ * source channel's own release PR is obsolete — merging it would re-publish a cycle
523
+ * that's already moving to its next stage. A fresh release PR is created automatically
524
+ * if new work lands on the channel branch.
525
+ *
526
+ * Only channels whose files arrived in the TRIGGERING push are considered: the files
527
+ * stay pending here until our version/release PR merges, and re-closing on every
528
+ * later push in that window would kill the release PR of a newly restarted cycle.
529
+ */
530
+ async function closePromotedChannelReleasePrs(rootDir, config, bumpFiles, currentChannel) {
531
+ const arrived = detectArrivedChannelFiles(rootDir, config);
532
+ const promoted = [...new Set(bumpFiles.map((bf) => bf.channel))].filter((name) => name != null && name !== currentChannel?.name && arrived.has(name));
533
+ if (promoted.length === 0) return;
534
+ const channels = resolveChannels(config);
535
+ for (const name of promoted) {
536
+ const channel = channels.get(name);
537
+ if (!channel) continue;
538
+ const pr = tryRunArgs([
539
+ "gh",
540
+ "pr",
541
+ "list",
542
+ "--head",
543
+ channel.versionPr.branch,
544
+ "--json",
545
+ "number",
546
+ "--jq",
547
+ ".[0].number"
548
+ ], { cwd: rootDir });
549
+ if (!pr) continue;
550
+ const validPr = validatePrNumber(pr);
551
+ log.step(`Closing release PR #${validPr} — the "${name}" cycle's changes are pending here now...`);
552
+ try {
553
+ await withPatToken(() => runArgsAsync([
554
+ "gh",
555
+ "pr",
556
+ "close",
557
+ validPr,
558
+ "--comment",
559
+ `Closing — the \`${name}\` cycle's bump files were promoted and are now pending a release here. A new release PR will be created automatically if more changes land on \`${channel.branch}\`.`
560
+ ], { cwd: rootDir }));
561
+ log.success(`🐸 Closed obsolete release PR #${validPr}`);
562
+ } catch (e) {
563
+ log.warn(` Failed to close release PR #${validPr}: ${e}`);
564
+ }
565
+ }
478
566
  }
479
567
  /** Read the push event's before/after range, if running on a GitHub Actions push event */
480
568
  function getPushEventRange() {
@@ -548,14 +636,14 @@ async function ciChannelRelease(rootDir, config, channel, opts) {
548
636
  const pending = bumpFiles.filter((bf) => bf.channel !== channel.name);
549
637
  if (opts.autoPublish) {
550
638
  if (pending.length > 0) {
551
- const { channelVersion } = await import("./version-BUUf8vKC.mjs");
639
+ const { channelVersion } = await import("./version-CMJUopVj.mjs");
552
640
  if (await channelVersion(rootDir, config, channel, { commit: true })) runArgs([
553
641
  "git",
554
642
  "push",
555
643
  "--no-verify"
556
644
  ], { cwd: rootDir });
557
645
  }
558
- const { publishCommand } = await import("./publish-Cz0e4KYT.mjs");
646
+ const { publishCommand } = await import("./publish-DWxi552d.mjs");
559
647
  await publishCommand(rootDir, {
560
648
  channel: channel.name,
561
649
  tag: opts.tag
@@ -566,7 +654,7 @@ async function ciChannelRelease(rootDir, config, channel, opts) {
566
654
  const shouldPublish = movedIds.length > 0 && opts.assertMode !== "version-pr";
567
655
  if (shouldPublish) {
568
656
  log.step(`Release PR merge detected (${movedIds.map((id) => `${id}.md`).join(", ")}) — publishing prereleases...`);
569
- const { publishCommand } = await import("./publish-Cz0e4KYT.mjs");
657
+ const { publishCommand } = await import("./publish-DWxi552d.mjs");
570
658
  await publishCommand(rootDir, {
571
659
  channel: channel.name,
572
660
  tag: opts.tag
@@ -582,8 +670,8 @@ async function ciChannelRelease(rootDir, config, channel, opts) {
582
670
  /**
583
671
  * Create or update the channel's release PR. Unlike the stable version PR, its diff
584
672
  * is pure file moves (pending bump files → `.bumpy/<channel>/`) — no versions, no
585
- * changelogs. Computed prerelease versions appear in the PR title and body as
586
- * point-in-time narrative; the registry wins at publish time.
673
+ * changelogs. The PR title/body show targets with a wildcard counter (`1.2.0-rc.x`),
674
+ * derived purely from committed state; the exact counter is assigned at publish time.
587
675
  */
588
676
  async function createChannelReleasePr(rootDir, config, channel, packages, branchOverride) {
589
677
  const branch = validateBranchName(branchOverride || channel.versionPr.branch);
@@ -623,7 +711,7 @@ async function createChannelReleasePr(rootDir, config, channel, packages, branch
623
711
  "-b",
624
712
  branch
625
713
  ], { cwd: rootDir });
626
- const { channelVersion } = await import("./version-BUUf8vKC.mjs");
714
+ const { channelVersion } = await import("./version-CMJUopVj.mjs");
627
715
  const result = await channelVersion(rootDir, config, channel);
628
716
  if (!result) {
629
717
  log.info("No pending bump files to move.");
@@ -634,22 +722,7 @@ async function createChannelReleasePr(rootDir, config, channel, packages, branch
634
722
  ], { cwd: rootDir });
635
723
  return;
636
724
  }
637
- let displayPlan = result.cyclePlan;
638
- let displayIsExact = false;
639
- try {
640
- const built = await buildChannelReleasePlan(result.cyclePlan, channel, packages, rootDir, { forDisplay: true });
641
- if (built.plan.releases.length > 0) {
642
- displayPlan = built.plan;
643
- displayIsExact = true;
644
- }
645
- } catch {}
646
- if (!displayIsExact) displayPlan = {
647
- ...displayPlan,
648
- releases: displayPlan.releases.map((r) => ({
649
- ...r,
650
- newVersion: `${r.newVersion}-${channel.preid}.?`
651
- }))
652
- };
725
+ const displayPlan = channelDisplayPlan(result.cyclePlan, channel, packages);
653
726
  const versionSummary = formatChannelVersionSummary(displayPlan.releases);
654
727
  const prTitle = versionSummary ? `${channel.versionPr.title}: ${versionSummary}` : channel.versionPr.title;
655
728
  runArgs([
@@ -747,13 +820,14 @@ async function createChannelReleasePr(rootDir, config, channel, packages, branch
747
820
  "checkout",
748
821
  baseBranch
749
822
  ], { cwd: rootDir });
823
+ await closePromotedChannelReleasePrs(rootDir, config, result.movedFiles, channel);
750
824
  }
751
825
  function buildChannelPrPreamble(config, channel) {
752
826
  return [
753
827
  config.versionPr.preamble,
754
828
  "",
755
829
  `> 🔀 **Prerelease channel \`${channel.name}\`** — merging this PR publishes the versions below to the \`@${channel.tag}\` dist-tag.`,
756
- `> The diff only moves bump files into \`.bumpy/${channel.name}/\` — prerelease versions are derived at publish time and never committed. Version numbers shown here are estimates; the registry wins at publish.`
830
+ `> The diff only moves bump files into \`.bumpy/${channel.name}/\` — prerelease versions are derived at publish time and never committed. The \`.x\` counter is assigned from the registry at publish time.`
757
831
  ].join("\n");
758
832
  }
759
833
  /** Enable GitHub auto-merge on a PR, trying the available merge methods in order */
@@ -839,11 +913,13 @@ function pmRunCommand(pm) {
839
913
  if (pm === "yarn") return "yarn bumpy";
840
914
  return "npx bumpy";
841
915
  }
842
- function formatReleasePlanComment(plan, bumpFiles, prNumber, prBranch, pm, warnings = [], parseErrors = [], emptyBumpFileIds = [], channel = null) {
916
+ function formatReleasePlanComment(plan, bumpFiles, prNumber, prBranch, pm, warnings = [], parseErrors = [], emptyBumpFileIds = [], channel = null, allChannels = null) {
843
917
  const repo = process.env.GITHUB_REPOSITORY;
844
918
  const lines = [];
845
- const versionSuffix = channel ? `-${channel.preid}.?` : "";
846
- const headline = channel ? `**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.` : "**The changes in this PR will be included in the next version bump.**";
919
+ const versionSuffix = channel ? `-${channel.preid}.x` : "";
920
+ const promotedChannels = channel ? [] : [...new Set(bumpFiles.map((bf) => bf.channel))].filter((c) => c != null);
921
+ const channelTag = (name) => `\`@${allChannels?.get(name)?.tag ?? name}\``;
922
+ const headline = channel ? `**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.` : promotedChannels.length > 0 ? `**This PR promotes the ${promotedChannels.map((c) => `\`${c}\``).join(", ")} prerelease cycle${promotedChannels.length > 1 ? "s" : ""} to a stable release.** 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.` : "**The changes in this PR will be included in the next version bump.**";
847
923
  const preamble = [
848
924
  `<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>`,
849
925
  "",
@@ -876,14 +952,15 @@ function formatReleasePlanComment(plan, bumpFiles, prNumber, prBranch, pm, warni
876
952
  if (channel) {
877
953
  const examplePkg = plan.releases.find((r) => !r.isDependencyBump && !r.isCascadeBump)?.name ?? plan.releases[0]?.name;
878
954
  const installHint = examplePkg ? ` (e.g. \`npm i ${examplePkg}@${channel.tag}\`)` : "";
879
- lines.push(`> 🔀 Published to the \`@${channel.tag}\` dist-tag${installHint}. Prerelease versions are derived at publish time — the \`.?\` counter is filled in from the registry. Promote to a stable release by merging \`${channel.branch}\` into your base branch.`);
955
+ lines.push(`> 🔀 Published to the \`@${channel.tag}\` dist-tag${installHint}. Prerelease versions are derived at publish time — the \`.x\` counter is filled in from the registry. Promote to a stable release by merging \`${channel.branch}\` into your base branch.`);
880
956
  lines.push("");
881
957
  }
882
958
  lines.push(`#### Bump files in this PR`);
883
959
  lines.push("");
884
960
  for (const bf of bumpFiles) {
885
- const filename = `${bf.id}.md`;
961
+ const filename = bf.channel ? `${bf.channel}/${bf.id}.md` : `${bf.id}.md`;
886
962
  const parts = [`\`${filename}\``];
963
+ if (bf.channel) parts.push(`_(shipped on ${channelTag(bf.channel)})_`);
887
964
  if (repo) {
888
965
  parts.push(`([view diff](https://github.com/${repo}/pull/${prNumber}/changes#diff-${sha256Hex(`.bumpy/${filename}`)}))`);
889
966
  if (prBranch) parts.push(`([edit](https://github.com/${repo}/edit/${prBranch}/.bumpy/${filename}))`);
package/dist/cli.mjs CHANGED
@@ -43,7 +43,7 @@ async function main() {
43
43
  }
44
44
  case "status": {
45
45
  const rootDir = await findRoot();
46
- const { statusCommand } = await import("./status-CrMvvvNy.mjs");
46
+ const { statusCommand } = await import("./status-DIzi-Iai.mjs");
47
47
  await statusCommand(rootDir, {
48
48
  json: flags.json === true,
49
49
  packagesOnly: flags.packages === true,
@@ -56,7 +56,7 @@ async function main() {
56
56
  }
57
57
  case "version": {
58
58
  const rootDir = await findRoot();
59
- const { versionCommand } = await import("./version-BUUf8vKC.mjs");
59
+ const { versionCommand } = await import("./version-CMJUopVj.mjs");
60
60
  await versionCommand(rootDir, {
61
61
  commit: flags.commit === true,
62
62
  channel: flags.channel
@@ -94,17 +94,17 @@ async function main() {
94
94
  const subcommand = args[1];
95
95
  const ciFlags = parseFlags(args.slice(2));
96
96
  if (subcommand === "check") {
97
- const { ciCheckCommand } = await import("./ci-hO7tAbCN.mjs");
97
+ const { ciCheckCommand } = await import("./ci-BRJNl-VN.mjs");
98
98
  await ciCheckCommand(rootDir, {
99
99
  comment: ciFlags.comment !== void 0 ? ciFlags.comment === true : void 0,
100
100
  strict: ciFlags.strict === true,
101
101
  noFail: ciFlags["no-fail"] === true
102
102
  });
103
103
  } else if (subcommand === "plan") {
104
- const { ciPlanCommand } = await import("./ci-hO7tAbCN.mjs");
104
+ const { ciPlanCommand } = await import("./ci-BRJNl-VN.mjs");
105
105
  await ciPlanCommand(rootDir);
106
106
  } else if (subcommand === "release") {
107
- const { ciReleaseCommand } = await import("./ci-hO7tAbCN.mjs");
107
+ const { ciReleaseCommand } = await import("./ci-BRJNl-VN.mjs");
108
108
  const expectModeFlag = ciFlags["expect-mode"];
109
109
  const autoPublishFlag = ciFlags["auto-publish"] === true;
110
110
  if (expectModeFlag !== void 0 && expectModeFlag !== "version-pr" && expectModeFlag !== "publish") {
@@ -132,7 +132,7 @@ async function main() {
132
132
  }
133
133
  case "publish": {
134
134
  const rootDir = await findRoot();
135
- const { publishCommand } = await import("./publish-Cz0e4KYT.mjs");
135
+ const { publishCommand } = await import("./publish-DWxi552d.mjs");
136
136
  await publishCommand(rootDir, {
137
137
  dryRun: flags["dry-run"] === true,
138
138
  tag: flags.tag,
@@ -157,7 +157,7 @@ async function main() {
157
157
  }
158
158
  case "--version":
159
159
  case "-v":
160
- console.log(`bumpy 1.13.2`);
160
+ console.log(`bumpy 1.14.0`);
161
161
  break;
162
162
  case "help":
163
163
  case "--help":
@@ -177,7 +177,7 @@ async function main() {
177
177
  }
178
178
  function printHelp() {
179
179
  console.log(`
180
- ${colorize(`🐸 bumpy v1.13.2`, "bold")} - Modern monorepo versioning
180
+ ${colorize(`🐸 bumpy v1.14.0`, "bold")} - Modern monorepo versioning
181
181
 
182
182
  Usage: bumpy <command> [options]
183
183
 
@@ -175,12 +175,31 @@ async function writeChannelVersionsInPlace(plan, packages) {
175
175
  for (const [path, content] of originals) await writeText(path, content);
176
176
  };
177
177
  }
178
+ /**
179
+ * Derive display versions for a channel cycle without touching the registry:
180
+ * each target gets a wildcard counter (`1.2.0-rc.x`). Everything here comes from
181
+ * committed state (bump files + config), so PR titles/bodies and commit messages
182
+ * can never disagree with what eventually publishes. Unpublishable packages are
183
+ * dropped, mirroring the filter in `buildChannelReleasePlan`.
184
+ */
185
+ function channelDisplayPlan(stablePlan, channel, packages) {
186
+ const releases = stablePlan.releases.filter((r) => {
187
+ const pkg = packages.get(r.name);
188
+ return !!pkg && !(pkg.private && !pkg.bumpy?.publishCommand);
189
+ }).map((r) => ({
190
+ ...r,
191
+ newVersion: `${r.newVersion}-${channel.preid}.x`
192
+ }));
193
+ return {
194
+ ...stablePlan,
195
+ releases
196
+ };
197
+ }
178
198
  /** One-line summary of a channel plan's versions, for PR titles and commit messages */
179
199
  function formatChannelVersionSummary(releases) {
180
200
  if (releases.length === 0) return "";
181
- const lead = releases.filter((r) => !r.isDependencyBump && !r.isCascadeBump && !r.isGroupBump)[0] ?? releases[0];
182
- const rest = releases.length - 1;
183
- return rest > 0 ? `${lead.name}@${lead.newVersion} (+${rest} more)` : `${lead.name}@${lead.newVersion}`;
201
+ if (releases.length === 1) return `${releases[0].name}@${releases[0].newVersion}`;
202
+ return `${releases.length} packages`;
184
203
  }
185
204
  //#endregion
186
- export { formatChannelVersionSummary as n, writeChannelVersionsInPlace as r, buildChannelReleasePlan as t };
205
+ export { writeChannelVersionsInPlace as i, channelDisplayPlan as n, formatChannelVersionSummary as r, buildChannelReleasePlan as t };
@@ -8,8 +8,8 @@ import { i as loadFormatter, n as generateChangelogEntry } from "./changelog-DuF
8
8
  import { f as tagExists, l as hasUncommittedChanges, n as forcePushTag } from "./git-DAWj8LyV.mjs";
9
9
  import { n as willUseOidcExclusively, t as publishPackages } from "./publish-pipeline-BD8mLbL9.mjs";
10
10
  import { channelNames, resolveActiveChannel } from "./channels-CFXZkyGd.mjs";
11
- import { r as writeChannelVersionsInPlace, t as buildChannelReleasePlan } from "./prerelease-Blnk8FE1.mjs";
12
- import { CI_PLAN_CACHE_PATH } from "./ci-hO7tAbCN.mjs";
11
+ import { i as writeChannelVersionsInPlace, t as buildChannelReleasePlan } from "./prerelease-B2PVfXkm.mjs";
12
+ import { CI_PLAN_CACHE_PATH } from "./ci-BRJNl-VN.mjs";
13
13
  //#region src/core/github-release.ts
14
14
  var import_semver = /* @__PURE__ */ __toESM(require_semver(), 1);
15
15
  /** Get the current HEAD commit SHA */
@@ -4,7 +4,7 @@ import { i as readBumpFiles, s as discoverPackages, t as filterBranchBumpFiles }
4
4
  import { o as DependencyGraph, t as assembleReleasePlan } from "./release-plan-C84pcBi-.mjs";
5
5
  import { a as getChangedFiles, o as getCurrentBranch } from "./git-DAWj8LyV.mjs";
6
6
  import { channelNames, resolveActiveChannel } from "./channels-CFXZkyGd.mjs";
7
- import { t as buildChannelReleasePlan } from "./prerelease-Blnk8FE1.mjs";
7
+ import { t as buildChannelReleasePlan } from "./prerelease-B2PVfXkm.mjs";
8
8
  //#region src/commands/status.ts
9
9
  async function statusCommand(rootDir, opts) {
10
10
  const config = await loadConfig(rootDir);
@@ -125,7 +125,7 @@ async function channelVersion(rootDir, config, channel, opts = {}) {
125
125
  log.step("Cycle targets (counters are derived from the registry at publish time):");
126
126
  for (const r of cyclePlan.releases) {
127
127
  const tag = r.isDependencyBump ? " (dep)" : r.isCascadeBump ? " (cascade)" : "";
128
- console.log(` ${r.name}: ${r.oldVersion} → ${colorize(`${r.newVersion}-${channel.preid}.?`, "cyan")}${tag}`);
128
+ console.log(` ${r.name}: ${r.oldVersion} → ${colorize(`${r.newVersion}-${channel.preid}.x`, "cyan")}${tag}`);
129
129
  }
130
130
  await moveBumpFilesToChannel(rootDir, pending, channel.name);
131
131
  log.success(`🐸 Moved ${pending.length} bump file(s) — no versions written (prereleases are derived, not committed)`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@varlock/bumpy",
3
- "version": "1.14.0-rc.0",
3
+ "version": "1.14.0",
4
4
  "description": "Modern monorepo versioning and changelog tool",
5
5
  "keywords": [
6
6
  "bump",