@varlock/bumpy 1.13.2 → 1.14.0-rc.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.
- package/README.md +3 -1
- package/config-schema.json +43 -0
- package/dist/{add-Dt1hddMt.mjs → add-C9rU_89s.mjs} +4 -4
- package/dist/{apply-release-plan-DD2R7SL2.mjs → apply-release-plan-DxTsUSqa.mjs} +11 -2
- package/dist/{bump-file-B7hmXZlB.mjs → bump-file-mRJeReRJ.mjs} +43 -8
- package/dist/{changelog-CbaET5V6.mjs → changelog-DuFhnJRO.mjs} +3 -3
- package/dist/{changelog-github-DXDnWkrB.mjs → changelog-github-jLOtwuWj.mjs} +2 -2
- package/dist/channels-CFXZkyGd.mjs +75 -0
- package/dist/{check-Dvi0DIqC.mjs → check-DIl9Dz68.mjs} +18 -6
- package/dist/{ci-B7gF6CFP.mjs → ci-hO7tAbCN.mjs} +391 -23
- package/dist/cli.mjs +30 -15
- package/dist/{config-D_4GYDJi.mjs → config-0we4ISZX.mjs} +5 -1
- package/dist/{generate-DohUlhu3.mjs → generate-B2OMt_64.mjs} +3 -3
- package/dist/{git-BWPimLgc.mjs → git-DAWj8LyV.mjs} +12 -1
- package/dist/index.d.mts +46 -4
- package/dist/index.mjs +7 -7
- package/dist/prerelease-Blnk8FE1.mjs +186 -0
- package/dist/{publish-VYBhDYFM.mjs → publish-Cz0e4KYT.mjs} +105 -14
- package/dist/{publish-pipeline-BPedWvKS.mjs → publish-pipeline-BD8mLbL9.mjs} +2 -2
- package/dist/{release-plan-mK7iGeGq.mjs → release-plan-C84pcBi-.mjs} +12 -17
- package/dist/status-CrMvvvNy.mjs +232 -0
- package/dist/{types-Bkh-igOJ.mjs → types-lpiG-Zxh.mjs} +1 -0
- package/dist/version-BUUf8vKC.mjs +192 -0
- package/package.json +1 -1
- package/dist/status-DxzKPM8d.mjs +0 -129
- package/dist/version-Cm0nRAFF.mjs +0 -123
- /package/dist/{commit-message-CSWVKPJ-.mjs → commit-message-BwsowSds.mjs} +0 -0
- /package/dist/{init-BCkm6Nfa.mjs → init-DkAY5hjc.mjs} +0 -0
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { n as log, t as colorize } from "./logger-BgksGFuf.mjs";
|
|
2
|
-
import { a as loadConfig } from "./config-
|
|
2
|
+
import { a as loadConfig } from "./config-0we4ISZX.mjs";
|
|
3
3
|
import { n as detectPackageManager } from "./package-manager-Db_vTztt.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { a as recoverDeletedBumpFiles, c as discoverWorkspace, i as readBumpFiles, t as filterBranchBumpFiles } from "./bump-file-mRJeReRJ.mjs";
|
|
5
|
+
import { o as DependencyGraph, t as assembleReleasePlan } from "./release-plan-C84pcBi-.mjs";
|
|
6
6
|
import { n as runArgs, r as runArgsAsync, s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
|
|
7
|
-
import { a as getChangedFiles,
|
|
7
|
+
import { a as getChangedFiles, p as withGitToken } from "./git-DAWj8LyV.mjs";
|
|
8
8
|
import { t as randomName } from "./names-COooXAFg.mjs";
|
|
9
|
-
import { n as findChangedPackages } from "./check-
|
|
10
|
-
import {
|
|
9
|
+
import { n as findChangedPackages } from "./check-DIl9Dz68.mjs";
|
|
10
|
+
import { channelNames, detectReleaseBranch, matchChannelByBranch, resolveChannels } from "./channels-CFXZkyGd.mjs";
|
|
11
|
+
import { n as formatChannelVersionSummary, t as buildChannelReleasePlan } from "./prerelease-Blnk8FE1.mjs";
|
|
12
|
+
import { t as resolveCommitMessage } from "./commit-message-BwsowSds.mjs";
|
|
11
13
|
import { appendFileSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
14
|
import { createHash } from "node:crypto";
|
|
13
15
|
//#region src/commands/ci.ts
|
|
@@ -76,15 +78,19 @@ async function ciCheckCommand(rootDir, opts) {
|
|
|
76
78
|
const { packages } = await discoverWorkspace(rootDir, config);
|
|
77
79
|
const depGraph = new DependencyGraph(packages);
|
|
78
80
|
const { bumpFiles: allBumpFiles, errors: parseErrors } = await readBumpFiles(rootDir);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
const prBranchName = detectPrBranch(rootDir);
|
|
82
|
+
const releasePrBranches = new Set([config.versionPr.branch, ...[...resolveChannels(config).values()].map((c) => c.versionPr.branch)]);
|
|
83
|
+
if (prBranchName && releasePrBranches.has(prBranchName)) {
|
|
84
|
+
log.dim(" Skipping — this is a release PR branch.");
|
|
81
85
|
return;
|
|
82
86
|
}
|
|
83
87
|
const inCI = !!process.env.CI;
|
|
84
88
|
const shouldComment = opts.comment ?? inCI;
|
|
85
89
|
const prNumber = detectPrNumber();
|
|
86
90
|
const pm = await detectPackageManager(rootDir);
|
|
87
|
-
const
|
|
91
|
+
const compareBranch = process.env.GITHUB_BASE_REF || config.baseBranch;
|
|
92
|
+
const prChannel = matchChannelByBranch(config, process.env.GITHUB_BASE_REF || null);
|
|
93
|
+
const changedFiles = getChangedFiles(rootDir, compareBranch);
|
|
88
94
|
const { branchBumpFiles: prBumpFiles, emptyBumpFileIds } = filterBranchBumpFiles(allBumpFiles, changedFiles, rootDir, parseErrors);
|
|
89
95
|
if (parseErrors.length > 0) for (const err of parseErrors) log.error(err);
|
|
90
96
|
if (prBumpFiles.length === 0) {
|
|
@@ -108,14 +114,15 @@ async function ciCheckCommand(rootDir, opts) {
|
|
|
108
114
|
if (willFail) process.exit(1);
|
|
109
115
|
return;
|
|
110
116
|
}
|
|
111
|
-
const plan = assembleReleasePlan(prBumpFiles, packages, depGraph, config);
|
|
112
|
-
|
|
117
|
+
const plan = assembleReleasePlan(prBumpFiles, packages, depGraph, config, prChannel ? { prereleasePreid: prChannel.preid } : {});
|
|
118
|
+
const releaseSuffix = prChannel ? `-${prChannel.preid}.?` : "";
|
|
119
|
+
log.bold(`${prBumpFiles.length} bump file(s) → ${plan.releases.length} package(s) to release${prChannel ? ` on the "${prChannel.name}" channel (@${prChannel.tag})` : ""}\n`);
|
|
113
120
|
for (const r of plan.releases) {
|
|
114
121
|
const tag = r.isDependencyBump ? " (dep)" : r.isCascadeBump ? " (cascade)" : "";
|
|
115
|
-
console.log(` ${r.name}: ${r.oldVersion} → ${colorize(r.newVersion
|
|
122
|
+
console.log(` ${r.name}: ${r.oldVersion} → ${colorize(`${r.newVersion}${releaseSuffix}`, "cyan")}${tag}`);
|
|
116
123
|
}
|
|
117
124
|
if (plan.warnings.length > 0) for (const w of plan.warnings) log.warn(w);
|
|
118
|
-
if (shouldComment && prNumber) await postOrUpdatePrComment(prNumber, formatReleasePlanComment(plan, prBumpFiles, prNumber, detectPrBranch(rootDir), pm, plan.warnings, parseErrors, emptyBumpFileIds), rootDir);
|
|
125
|
+
if (shouldComment && prNumber) await postOrUpdatePrComment(prNumber, formatReleasePlanComment(plan, prBumpFiles, prNumber, detectPrBranch(rootDir), pm, plan.warnings, parseErrors, emptyBumpFileIds, prChannel), rootDir);
|
|
119
126
|
if (parseErrors.length > 0 && !opts.noFail) process.exit(1);
|
|
120
127
|
const coveredPackages = new Set(plan.releases.map((r) => r.name));
|
|
121
128
|
for (const bf of prBumpFiles) for (const release of bf.releases) coveredPackages.add(release.name);
|
|
@@ -136,11 +143,16 @@ async function ciPlanCommand(rootDir) {
|
|
|
136
143
|
const config = await loadConfig(rootDir);
|
|
137
144
|
const { packages } = await discoverWorkspace(rootDir, config);
|
|
138
145
|
const depGraph = new DependencyGraph(packages);
|
|
139
|
-
const { bumpFiles, errors: parseErrors } = await readBumpFiles(rootDir);
|
|
146
|
+
const { bumpFiles, errors: parseErrors } = await readBumpFiles(rootDir, { channels: channelNames(config) });
|
|
140
147
|
if (parseErrors.length > 0) {
|
|
141
148
|
for (const err of parseErrors) log.error(err);
|
|
142
149
|
throw new Error("Bump file parse errors must be fixed before planning.");
|
|
143
150
|
}
|
|
151
|
+
const channel = matchChannelByBranch(config, detectReleaseBranch(rootDir));
|
|
152
|
+
if (channel) {
|
|
153
|
+
await ciChannelPlan(rootDir, config, channel, packages, depGraph, bumpFiles);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
144
156
|
let output;
|
|
145
157
|
const plan = bumpFiles.length > 0 ? assembleReleasePlan(bumpFiles, packages, depGraph, config) : null;
|
|
146
158
|
if (plan && plan.releases.length > 0) output = {
|
|
@@ -157,7 +169,7 @@ async function ciPlanCommand(rootDir) {
|
|
|
157
169
|
packageNames: plan.releases.map((r) => r.name)
|
|
158
170
|
};
|
|
159
171
|
else {
|
|
160
|
-
const { findUnpublishedPackages } = await import("./publish-
|
|
172
|
+
const { findUnpublishedPackages } = await import("./publish-Cz0e4KYT.mjs");
|
|
161
173
|
const unpublished = await findUnpublishedPackages(packages, config);
|
|
162
174
|
if (unpublished.length > 0) output = {
|
|
163
175
|
mode: "publish",
|
|
@@ -220,9 +232,16 @@ function writeGitHubOutput(key, value) {
|
|
|
220
232
|
async function ciReleaseCommand(rootDir, opts) {
|
|
221
233
|
const config = await loadConfig(rootDir);
|
|
222
234
|
ensureGitIdentity(rootDir, config);
|
|
235
|
+
const releaseBranch = detectReleaseBranch(rootDir);
|
|
236
|
+
const channel = matchChannelByBranch(config, releaseBranch);
|
|
237
|
+
if (channel) {
|
|
238
|
+
await ciChannelRelease(rootDir, config, channel, opts);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (Object.keys(config.channels || {}).length > 0 && releaseBranch && releaseBranch !== config.baseBranch) throw new Error(`"bumpy ci release" ran on branch "${releaseBranch}", which is neither the base branch ("${config.baseBranch}") nor a configured channel branch. Refusing to release — add the branch to "channels" in .bumpy/_config.json or fix the workflow trigger.`);
|
|
223
242
|
const { packages } = await discoverWorkspace(rootDir, config);
|
|
224
243
|
const depGraph = new DependencyGraph(packages);
|
|
225
|
-
const { bumpFiles, errors: releaseParseErrors } = await readBumpFiles(rootDir);
|
|
244
|
+
const { bumpFiles, errors: releaseParseErrors } = await readBumpFiles(rootDir, { channels: channelNames(config) });
|
|
226
245
|
if (releaseParseErrors.length > 0) {
|
|
227
246
|
for (const err of releaseParseErrors) log.error(err);
|
|
228
247
|
throw new Error("Bump file parse errors must be fixed before releasing.");
|
|
@@ -234,7 +253,7 @@ async function ciReleaseCommand(rootDir, opts) {
|
|
|
234
253
|
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...";
|
|
235
254
|
log.info(msg);
|
|
236
255
|
const recoveredBumpFiles = recoverDeletedBumpFiles(rootDir);
|
|
237
|
-
const { publishCommand } = await import("./publish-
|
|
256
|
+
const { publishCommand } = await import("./publish-Cz0e4KYT.mjs");
|
|
238
257
|
await publishCommand(rootDir, {
|
|
239
258
|
tag: opts.tag,
|
|
240
259
|
recoveredBumpFiles
|
|
@@ -258,7 +277,7 @@ async function ciReleaseCommand(rootDir, opts) {
|
|
|
258
277
|
*/
|
|
259
278
|
async function autoPublish(rootDir, config, plan, tag) {
|
|
260
279
|
log.step("Running bumpy version...");
|
|
261
|
-
const { versionCommand } = await import("./version-
|
|
280
|
+
const { versionCommand } = await import("./version-BUUf8vKC.mjs");
|
|
262
281
|
await versionCommand(rootDir);
|
|
263
282
|
log.step("Committing version changes...");
|
|
264
283
|
runArgs([
|
|
@@ -287,7 +306,7 @@ async function autoPublish(rootDir, config, plan, tag) {
|
|
|
287
306
|
], { cwd: rootDir });
|
|
288
307
|
}
|
|
289
308
|
log.step("Running bumpy publish...");
|
|
290
|
-
const { publishCommand } = await import("./publish-
|
|
309
|
+
const { publishCommand } = await import("./publish-Cz0e4KYT.mjs");
|
|
291
310
|
await publishCommand(rootDir, { tag });
|
|
292
311
|
}
|
|
293
312
|
/**
|
|
@@ -361,7 +380,7 @@ async function createVersionPr(rootDir, plan, config, packageDirs, branchName) {
|
|
|
361
380
|
branch
|
|
362
381
|
], { cwd: rootDir });
|
|
363
382
|
log.step("Running bumpy version...");
|
|
364
|
-
const { versionCommand } = await import("./version-
|
|
383
|
+
const { versionCommand } = await import("./version-BUUf8vKC.mjs");
|
|
365
384
|
await versionCommand(rootDir);
|
|
366
385
|
runArgs([
|
|
367
386
|
"git",
|
|
@@ -457,6 +476,347 @@ async function createVersionPr(rootDir, plan, config, packageDirs, branchName) {
|
|
|
457
476
|
baseBranch
|
|
458
477
|
], { cwd: rootDir });
|
|
459
478
|
}
|
|
479
|
+
/** Read the push event's before/after range, if running on a GitHub Actions push event */
|
|
480
|
+
function getPushEventRange() {
|
|
481
|
+
if (process.env.GITHUB_EVENT_NAME !== "push") return null;
|
|
482
|
+
const path = process.env.GITHUB_EVENT_PATH;
|
|
483
|
+
if (!path) return null;
|
|
484
|
+
try {
|
|
485
|
+
const payload = JSON.parse(readFileSync(path, "utf-8"));
|
|
486
|
+
if (payload.before && payload.after && !/^0+$/.test(payload.before)) return {
|
|
487
|
+
before: payload.before,
|
|
488
|
+
after: payload.after
|
|
489
|
+
};
|
|
490
|
+
} catch {}
|
|
491
|
+
return null;
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Bump file IDs added to `.bumpy/<channel>/` by the push that triggered this run.
|
|
495
|
+
*
|
|
496
|
+
* This is the channel publish trigger: merging the release PR moves files into the
|
|
497
|
+
* channel dir; ordinary feature merges don't touch it. Re-running on the same push is
|
|
498
|
+
* idempotent — packages already published from this commit are skipped via the
|
|
499
|
+
* gitHead recorded on the registry.
|
|
500
|
+
*/
|
|
501
|
+
function detectChannelMoves(rootDir, channel) {
|
|
502
|
+
const range = getPushEventRange();
|
|
503
|
+
let diffRange;
|
|
504
|
+
if (range) diffRange = `${range.before}..${range.after}`;
|
|
505
|
+
else {
|
|
506
|
+
if (!tryRunArgs([
|
|
507
|
+
"git",
|
|
508
|
+
"rev-parse",
|
|
509
|
+
"--verify",
|
|
510
|
+
"HEAD^"
|
|
511
|
+
], { cwd: rootDir })) {
|
|
512
|
+
log.warn("Cannot diff against the previous commit (shallow clone?) — channel publish trigger unavailable.\n Use `fetch-depth: 0` in your checkout step, or run `bumpy publish` manually on the channel branch.");
|
|
513
|
+
return [];
|
|
514
|
+
}
|
|
515
|
+
diffRange = "HEAD^..HEAD";
|
|
516
|
+
}
|
|
517
|
+
const out = tryRunArgs([
|
|
518
|
+
"git",
|
|
519
|
+
"diff",
|
|
520
|
+
"--name-only",
|
|
521
|
+
"--diff-filter=A",
|
|
522
|
+
"--no-renames",
|
|
523
|
+
diffRange,
|
|
524
|
+
"--",
|
|
525
|
+
`.bumpy/${channel.name}/`
|
|
526
|
+
], { cwd: rootDir });
|
|
527
|
+
if (!out) return [];
|
|
528
|
+
return out.split("\n").filter((f) => f.endsWith(".md") && !f.endsWith("README.md")).map((f) => f.split("/").pop().replace(/\.md$/, ""));
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* CI release on a channel branch. Two independent steps, both of which can run
|
|
532
|
+
* in the same invocation:
|
|
533
|
+
*
|
|
534
|
+
* 1. **Publish** — if this push moved bump files into `.bumpy/<channel>/` (a release
|
|
535
|
+
* PR merge), publish the cycle as prereleases. Versions are derived (targets from
|
|
536
|
+
* bump files, counters from the registry) and never committed.
|
|
537
|
+
* 2. **Release PR** — if pending bump files exist (root or other channels' dirs),
|
|
538
|
+
* create/update the file-move release PR.
|
|
539
|
+
*/
|
|
540
|
+
async function ciChannelRelease(rootDir, config, channel, opts) {
|
|
541
|
+
log.bold(`Channel "${channel.name}" (branch "${channel.branch}")\n`);
|
|
542
|
+
const { packages } = await discoverWorkspace(rootDir, config);
|
|
543
|
+
const { bumpFiles, errors: parseErrors } = await readBumpFiles(rootDir, { channels: channelNames(config) });
|
|
544
|
+
if (parseErrors.length > 0) {
|
|
545
|
+
for (const err of parseErrors) log.error(err);
|
|
546
|
+
throw new Error("Bump file parse errors must be fixed before releasing.");
|
|
547
|
+
}
|
|
548
|
+
const pending = bumpFiles.filter((bf) => bf.channel !== channel.name);
|
|
549
|
+
if (opts.autoPublish) {
|
|
550
|
+
if (pending.length > 0) {
|
|
551
|
+
const { channelVersion } = await import("./version-BUUf8vKC.mjs");
|
|
552
|
+
if (await channelVersion(rootDir, config, channel, { commit: true })) runArgs([
|
|
553
|
+
"git",
|
|
554
|
+
"push",
|
|
555
|
+
"--no-verify"
|
|
556
|
+
], { cwd: rootDir });
|
|
557
|
+
}
|
|
558
|
+
const { publishCommand } = await import("./publish-Cz0e4KYT.mjs");
|
|
559
|
+
await publishCommand(rootDir, {
|
|
560
|
+
channel: channel.name,
|
|
561
|
+
tag: opts.tag
|
|
562
|
+
});
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
const movedIds = detectChannelMoves(rootDir, channel);
|
|
566
|
+
const shouldPublish = movedIds.length > 0 && opts.assertMode !== "version-pr";
|
|
567
|
+
if (shouldPublish) {
|
|
568
|
+
log.step(`Release PR merge detected (${movedIds.map((id) => `${id}.md`).join(", ")}) — publishing prereleases...`);
|
|
569
|
+
const { publishCommand } = await import("./publish-Cz0e4KYT.mjs");
|
|
570
|
+
await publishCommand(rootDir, {
|
|
571
|
+
channel: channel.name,
|
|
572
|
+
tag: opts.tag
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
if (opts.assertMode === "publish") {
|
|
576
|
+
if (!shouldPublish) throw new Error("Expected mode \"publish\" but this push did not move bump files into the channel dir. Either remove --expect-mode, or gate this step on the output of \"bumpy ci plan\".");
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (pending.length > 0) await createChannelReleasePr(rootDir, config, channel, packages, opts.branch);
|
|
580
|
+
else if (!shouldPublish) log.info(`Nothing to do on channel "${channel.name}" — no pending bump files, no release PR merge in this push.`);
|
|
581
|
+
}
|
|
582
|
+
/**
|
|
583
|
+
* Create or update the channel's release PR. Unlike the stable version PR, its diff
|
|
584
|
+
* 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.
|
|
587
|
+
*/
|
|
588
|
+
async function createChannelReleasePr(rootDir, config, channel, packages, branchOverride) {
|
|
589
|
+
const branch = validateBranchName(branchOverride || channel.versionPr.branch);
|
|
590
|
+
const baseBranch = validateBranchName(channel.branch);
|
|
591
|
+
const existingPr = tryRunArgs([
|
|
592
|
+
"gh",
|
|
593
|
+
"pr",
|
|
594
|
+
"list",
|
|
595
|
+
"--head",
|
|
596
|
+
branch,
|
|
597
|
+
"--json",
|
|
598
|
+
"number",
|
|
599
|
+
"--jq",
|
|
600
|
+
".[0].number"
|
|
601
|
+
], { cwd: rootDir });
|
|
602
|
+
log.step(`Creating branch ${branch}...`);
|
|
603
|
+
if (tryRunArgs([
|
|
604
|
+
"git",
|
|
605
|
+
"rev-parse",
|
|
606
|
+
"--verify",
|
|
607
|
+
branch
|
|
608
|
+
], { cwd: rootDir }) !== null) {
|
|
609
|
+
runArgs([
|
|
610
|
+
"git",
|
|
611
|
+
"checkout",
|
|
612
|
+
branch
|
|
613
|
+
], { cwd: rootDir });
|
|
614
|
+
runArgs([
|
|
615
|
+
"git",
|
|
616
|
+
"reset",
|
|
617
|
+
"--hard",
|
|
618
|
+
baseBranch
|
|
619
|
+
], { cwd: rootDir });
|
|
620
|
+
} else runArgs([
|
|
621
|
+
"git",
|
|
622
|
+
"checkout",
|
|
623
|
+
"-b",
|
|
624
|
+
branch
|
|
625
|
+
], { cwd: rootDir });
|
|
626
|
+
const { channelVersion } = await import("./version-BUUf8vKC.mjs");
|
|
627
|
+
const result = await channelVersion(rootDir, config, channel);
|
|
628
|
+
if (!result) {
|
|
629
|
+
log.info("No pending bump files to move.");
|
|
630
|
+
runArgs([
|
|
631
|
+
"git",
|
|
632
|
+
"checkout",
|
|
633
|
+
baseBranch
|
|
634
|
+
], { cwd: rootDir });
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
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
|
+
};
|
|
653
|
+
const versionSummary = formatChannelVersionSummary(displayPlan.releases);
|
|
654
|
+
const prTitle = versionSummary ? `${channel.versionPr.title}: ${versionSummary}` : channel.versionPr.title;
|
|
655
|
+
runArgs([
|
|
656
|
+
"git",
|
|
657
|
+
"add",
|
|
658
|
+
"-A",
|
|
659
|
+
".bumpy/"
|
|
660
|
+
], { cwd: rootDir });
|
|
661
|
+
if (!tryRunArgs([
|
|
662
|
+
"git",
|
|
663
|
+
"status",
|
|
664
|
+
"--porcelain"
|
|
665
|
+
], { cwd: rootDir })) {
|
|
666
|
+
log.info("No changes to commit.");
|
|
667
|
+
runArgs([
|
|
668
|
+
"git",
|
|
669
|
+
"checkout",
|
|
670
|
+
baseBranch
|
|
671
|
+
], { cwd: rootDir });
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
runArgs([
|
|
675
|
+
"git",
|
|
676
|
+
"commit",
|
|
677
|
+
"-F",
|
|
678
|
+
"-"
|
|
679
|
+
], {
|
|
680
|
+
cwd: rootDir,
|
|
681
|
+
input: `${prTitle}\n\nShipped: ${result.movedFiles.map((bf) => `${bf.id}.md`).join(", ")}`
|
|
682
|
+
});
|
|
683
|
+
pushWithToken(rootDir, branch, config);
|
|
684
|
+
const repo = process.env.GITHUB_REPOSITORY;
|
|
685
|
+
const noPatWarning = !process.env.BUMPY_GH_TOKEN && !!repo;
|
|
686
|
+
const packageDirs = new Map([...packages.values()].map((p) => [p.name, p.relativeDir]));
|
|
687
|
+
const preamble = buildChannelPrPreamble(config, channel);
|
|
688
|
+
let prNumber = null;
|
|
689
|
+
if (existingPr) {
|
|
690
|
+
prNumber = validatePrNumber(existingPr);
|
|
691
|
+
const prBody = formatVersionPrBody(displayPlan, preamble, packageDirs, repo, prNumber, noPatWarning);
|
|
692
|
+
log.step(`Updating existing PR #${prNumber}...`);
|
|
693
|
+
await withPatToken(() => runArgsAsync([
|
|
694
|
+
"gh",
|
|
695
|
+
"pr",
|
|
696
|
+
"edit",
|
|
697
|
+
prNumber,
|
|
698
|
+
"--title",
|
|
699
|
+
prTitle,
|
|
700
|
+
"--body-file",
|
|
701
|
+
"-"
|
|
702
|
+
], {
|
|
703
|
+
cwd: rootDir,
|
|
704
|
+
input: prBody
|
|
705
|
+
}));
|
|
706
|
+
log.success(`🐸 Updated PR #${prNumber}`);
|
|
707
|
+
} else {
|
|
708
|
+
log.step("Creating release PR...");
|
|
709
|
+
const prBody = formatVersionPrBody(displayPlan, preamble, packageDirs, repo, null, noPatWarning);
|
|
710
|
+
const createResult = await withPatToken(() => runArgsAsync([
|
|
711
|
+
"gh",
|
|
712
|
+
"pr",
|
|
713
|
+
"create",
|
|
714
|
+
"--title",
|
|
715
|
+
prTitle,
|
|
716
|
+
"--body-file",
|
|
717
|
+
"-",
|
|
718
|
+
"--base",
|
|
719
|
+
baseBranch,
|
|
720
|
+
"--head",
|
|
721
|
+
branch
|
|
722
|
+
], {
|
|
723
|
+
cwd: rootDir,
|
|
724
|
+
input: prBody
|
|
725
|
+
}));
|
|
726
|
+
log.success(`🐸 Created PR: ${createResult}`);
|
|
727
|
+
prNumber = createResult?.match(/\/pull\/(\d+)/)?.[1] ?? null;
|
|
728
|
+
if (repo && prNumber) {
|
|
729
|
+
const updatedBody = formatVersionPrBody(displayPlan, preamble, packageDirs, repo, prNumber, noPatWarning);
|
|
730
|
+
await withPatToken(() => runArgsAsync([
|
|
731
|
+
"gh",
|
|
732
|
+
"pr",
|
|
733
|
+
"edit",
|
|
734
|
+
prNumber,
|
|
735
|
+
"--body-file",
|
|
736
|
+
"-"
|
|
737
|
+
], {
|
|
738
|
+
cwd: rootDir,
|
|
739
|
+
input: updatedBody
|
|
740
|
+
}));
|
|
741
|
+
}
|
|
742
|
+
if (!process.env.BUMPY_GH_TOKEN) pushWithToken(rootDir, branch, config);
|
|
743
|
+
}
|
|
744
|
+
if (channel.versionPr.automerge && prNumber) await enableAutoMerge(rootDir, prNumber);
|
|
745
|
+
runArgs([
|
|
746
|
+
"git",
|
|
747
|
+
"checkout",
|
|
748
|
+
baseBranch
|
|
749
|
+
], { cwd: rootDir });
|
|
750
|
+
}
|
|
751
|
+
function buildChannelPrPreamble(config, channel) {
|
|
752
|
+
return [
|
|
753
|
+
config.versionPr.preamble,
|
|
754
|
+
"",
|
|
755
|
+
`> 🔀 **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.`
|
|
757
|
+
].join("\n");
|
|
758
|
+
}
|
|
759
|
+
/** Enable GitHub auto-merge on a PR, trying the available merge methods in order */
|
|
760
|
+
async function enableAutoMerge(rootDir, prNumber) {
|
|
761
|
+
const validPr = validatePrNumber(prNumber);
|
|
762
|
+
for (const method of [
|
|
763
|
+
"--squash",
|
|
764
|
+
"--merge",
|
|
765
|
+
"--rebase"
|
|
766
|
+
]) try {
|
|
767
|
+
await withPatToken(() => runArgsAsync([
|
|
768
|
+
"gh",
|
|
769
|
+
"pr",
|
|
770
|
+
"merge",
|
|
771
|
+
validPr,
|
|
772
|
+
"--auto",
|
|
773
|
+
method
|
|
774
|
+
], { cwd: rootDir }));
|
|
775
|
+
log.dim(` Auto-merge enabled (${method.slice(2)})`);
|
|
776
|
+
return;
|
|
777
|
+
} catch {}
|
|
778
|
+
log.warn(" Failed to enable auto-merge — check repository merge settings and token permissions.");
|
|
779
|
+
}
|
|
780
|
+
/** Channel-aware `ci plan`: reports what `ci release` would do on this channel branch */
|
|
781
|
+
async function ciChannelPlan(rootDir, config, channel, packages, depGraph, bumpFiles) {
|
|
782
|
+
const pending = bumpFiles.filter((bf) => bf.channel !== channel.name);
|
|
783
|
+
const movedIds = detectChannelMoves(rootDir, channel);
|
|
784
|
+
let mode = "nothing";
|
|
785
|
+
let releases = [];
|
|
786
|
+
if (pending.length > 0 || movedIds.length > 0) {
|
|
787
|
+
mode = pending.length > 0 ? "version-pr" : "publish";
|
|
788
|
+
const stablePlan = assembleReleasePlan(bumpFiles, packages, depGraph, config, { prereleasePreid: channel.preid });
|
|
789
|
+
try {
|
|
790
|
+
releases = (await buildChannelReleasePlan(stablePlan, channel, packages, rootDir, { forDisplay: true })).plan.releases;
|
|
791
|
+
} catch {
|
|
792
|
+
releases = stablePlan.releases.map((r) => ({
|
|
793
|
+
...r,
|
|
794
|
+
newVersion: `${r.newVersion}-${channel.preid}.?`
|
|
795
|
+
}));
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
const output = {
|
|
799
|
+
mode,
|
|
800
|
+
channel: channel.name,
|
|
801
|
+
bumpFiles: bumpFiles.map((bf) => ({
|
|
802
|
+
id: bf.id,
|
|
803
|
+
summary: bf.summary,
|
|
804
|
+
releases: bf.releases.map((r) => ({
|
|
805
|
+
name: r.name,
|
|
806
|
+
type: r.type
|
|
807
|
+
})),
|
|
808
|
+
shipped: bf.channel === channel.name
|
|
809
|
+
})),
|
|
810
|
+
releases: releases.map((r) => formatPlanRelease(r, packages, config)),
|
|
811
|
+
packageNames: releases.map((r) => r.name)
|
|
812
|
+
};
|
|
813
|
+
const json = JSON.stringify(output, null, 2);
|
|
814
|
+
console.log(json);
|
|
815
|
+
writeGitHubOutput("mode", output.mode);
|
|
816
|
+
writeGitHubOutput("channel", channel.name);
|
|
817
|
+
writeGitHubOutput("packages", JSON.stringify(output.packageNames));
|
|
818
|
+
writeGitHubOutput("json", JSON.stringify(output));
|
|
819
|
+
}
|
|
460
820
|
const FROG_IMG_BASE = "https://raw.githubusercontent.com/dmno-dev/bumpy/main/images";
|
|
461
821
|
function buildAddBumpFileLink(prBranch) {
|
|
462
822
|
if (!prBranch) return null;
|
|
@@ -479,13 +839,15 @@ function pmRunCommand(pm) {
|
|
|
479
839
|
if (pm === "yarn") return "yarn bumpy";
|
|
480
840
|
return "npx bumpy";
|
|
481
841
|
}
|
|
482
|
-
function formatReleasePlanComment(plan, bumpFiles, prNumber, prBranch, pm, warnings = [], parseErrors = [], emptyBumpFileIds = []) {
|
|
842
|
+
function formatReleasePlanComment(plan, bumpFiles, prNumber, prBranch, pm, warnings = [], parseErrors = [], emptyBumpFileIds = [], channel = null) {
|
|
483
843
|
const repo = process.env.GITHUB_REPOSITORY;
|
|
484
844
|
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.**";
|
|
485
847
|
const preamble = [
|
|
486
848
|
`<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>`,
|
|
487
849
|
"",
|
|
488
|
-
|
|
850
|
+
headline,
|
|
489
851
|
"<br clear=\"left\" />"
|
|
490
852
|
].join("\n");
|
|
491
853
|
lines.push(preamble);
|
|
@@ -507,10 +869,16 @@ function formatReleasePlanComment(plan, bumpFiles, prNumber, prBranch, pm, warni
|
|
|
507
869
|
lines.push("");
|
|
508
870
|
for (const r of releases) {
|
|
509
871
|
const suffix = r.isDependencyBump ? " _(dep)_" : r.isCascadeBump ? " _(cascade)_" : "";
|
|
510
|
-
lines.push(`- \`${r.name}\` ${r.oldVersion} → **${r.newVersion}**${suffix}`);
|
|
872
|
+
lines.push(`- \`${r.name}\` ${r.oldVersion} → **${r.newVersion}${versionSuffix}**${suffix}`);
|
|
511
873
|
}
|
|
512
874
|
lines.push("");
|
|
513
875
|
}
|
|
876
|
+
if (channel) {
|
|
877
|
+
const examplePkg = plan.releases.find((r) => !r.isDependencyBump && !r.isCascadeBump)?.name ?? plan.releases[0]?.name;
|
|
878
|
+
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.`);
|
|
880
|
+
lines.push("");
|
|
881
|
+
}
|
|
514
882
|
lines.push(`#### Bump files in this PR`);
|
|
515
883
|
lines.push("");
|
|
516
884
|
for (const bf of bumpFiles) {
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { n as log, t as colorize } from "./logger-BgksGFuf.mjs";
|
|
3
|
-
import { n as findRoot } from "./config-
|
|
3
|
+
import { n as findRoot } from "./config-0we4ISZX.mjs";
|
|
4
4
|
//#region src/cli.ts
|
|
5
5
|
const args = process.argv.slice(2);
|
|
6
6
|
const command = args[0];
|
|
@@ -25,13 +25,13 @@ async function main() {
|
|
|
25
25
|
switch (command) {
|
|
26
26
|
case "init": {
|
|
27
27
|
const rootDir = await findRoot();
|
|
28
|
-
const { initCommand } = await import("./init-
|
|
28
|
+
const { initCommand } = await import("./init-DkAY5hjc.mjs");
|
|
29
29
|
await initCommand(rootDir, { force: flags.force === true });
|
|
30
30
|
break;
|
|
31
31
|
}
|
|
32
32
|
case "add": {
|
|
33
33
|
const rootDir = await findRoot();
|
|
34
|
-
const { addCommand } = await import("./add-
|
|
34
|
+
const { addCommand } = await import("./add-C9rU_89s.mjs");
|
|
35
35
|
await addCommand(rootDir, {
|
|
36
36
|
packages: flags.packages,
|
|
37
37
|
message: flags.message,
|
|
@@ -43,25 +43,29 @@ async function main() {
|
|
|
43
43
|
}
|
|
44
44
|
case "status": {
|
|
45
45
|
const rootDir = await findRoot();
|
|
46
|
-
const { statusCommand } = await import("./status-
|
|
46
|
+
const { statusCommand } = await import("./status-CrMvvvNy.mjs");
|
|
47
47
|
await statusCommand(rootDir, {
|
|
48
48
|
json: flags.json === true,
|
|
49
49
|
packagesOnly: flags.packages === true,
|
|
50
50
|
bumpType: flags.bump,
|
|
51
51
|
filter: flags.filter,
|
|
52
|
-
verbose: flags.verbose === true
|
|
52
|
+
verbose: flags.verbose === true,
|
|
53
|
+
channel: flags.channel
|
|
53
54
|
});
|
|
54
55
|
break;
|
|
55
56
|
}
|
|
56
57
|
case "version": {
|
|
57
58
|
const rootDir = await findRoot();
|
|
58
|
-
const { versionCommand } = await import("./version-
|
|
59
|
-
await versionCommand(rootDir, {
|
|
59
|
+
const { versionCommand } = await import("./version-BUUf8vKC.mjs");
|
|
60
|
+
await versionCommand(rootDir, {
|
|
61
|
+
commit: flags.commit === true,
|
|
62
|
+
channel: flags.channel
|
|
63
|
+
});
|
|
60
64
|
break;
|
|
61
65
|
}
|
|
62
66
|
case "generate": {
|
|
63
67
|
const rootDir = await findRoot();
|
|
64
|
-
const { generateCommand } = await import("./generate-
|
|
68
|
+
const { generateCommand } = await import("./generate-B2OMt_64.mjs");
|
|
65
69
|
await generateCommand(rootDir, {
|
|
66
70
|
from: flags.from,
|
|
67
71
|
dryRun: flags["dry-run"] === true,
|
|
@@ -71,7 +75,7 @@ async function main() {
|
|
|
71
75
|
}
|
|
72
76
|
case "check": {
|
|
73
77
|
const rootDir = await findRoot();
|
|
74
|
-
const { checkCommand } = await import("./check-
|
|
78
|
+
const { checkCommand } = await import("./check-DIl9Dz68.mjs").then((n) => n.t);
|
|
75
79
|
const hookValue = flags.hook;
|
|
76
80
|
if (hookValue && hookValue !== "pre-commit" && hookValue !== "pre-push") {
|
|
77
81
|
log.error(`Invalid --hook value "${hookValue}". Expected "pre-commit" or "pre-push".`);
|
|
@@ -80,7 +84,8 @@ async function main() {
|
|
|
80
84
|
await checkCommand(rootDir, {
|
|
81
85
|
strict: flags.strict === true,
|
|
82
86
|
noFail: flags["no-fail"] === true,
|
|
83
|
-
hook: hookValue
|
|
87
|
+
hook: hookValue,
|
|
88
|
+
base: flags.base
|
|
84
89
|
});
|
|
85
90
|
break;
|
|
86
91
|
}
|
|
@@ -89,17 +94,17 @@ async function main() {
|
|
|
89
94
|
const subcommand = args[1];
|
|
90
95
|
const ciFlags = parseFlags(args.slice(2));
|
|
91
96
|
if (subcommand === "check") {
|
|
92
|
-
const { ciCheckCommand } = await import("./ci-
|
|
97
|
+
const { ciCheckCommand } = await import("./ci-hO7tAbCN.mjs");
|
|
93
98
|
await ciCheckCommand(rootDir, {
|
|
94
99
|
comment: ciFlags.comment !== void 0 ? ciFlags.comment === true : void 0,
|
|
95
100
|
strict: ciFlags.strict === true,
|
|
96
101
|
noFail: ciFlags["no-fail"] === true
|
|
97
102
|
});
|
|
98
103
|
} else if (subcommand === "plan") {
|
|
99
|
-
const { ciPlanCommand } = await import("./ci-
|
|
104
|
+
const { ciPlanCommand } = await import("./ci-hO7tAbCN.mjs");
|
|
100
105
|
await ciPlanCommand(rootDir);
|
|
101
106
|
} else if (subcommand === "release") {
|
|
102
|
-
const { ciReleaseCommand } = await import("./ci-
|
|
107
|
+
const { ciReleaseCommand } = await import("./ci-hO7tAbCN.mjs");
|
|
103
108
|
const expectModeFlag = ciFlags["expect-mode"];
|
|
104
109
|
const autoPublishFlag = ciFlags["auto-publish"] === true;
|
|
105
110
|
if (expectModeFlag !== void 0 && expectModeFlag !== "version-pr" && expectModeFlag !== "publish") {
|
|
@@ -127,12 +132,13 @@ async function main() {
|
|
|
127
132
|
}
|
|
128
133
|
case "publish": {
|
|
129
134
|
const rootDir = await findRoot();
|
|
130
|
-
const { publishCommand } = await import("./publish-
|
|
135
|
+
const { publishCommand } = await import("./publish-Cz0e4KYT.mjs");
|
|
131
136
|
await publishCommand(rootDir, {
|
|
132
137
|
dryRun: flags["dry-run"] === true,
|
|
133
138
|
tag: flags.tag,
|
|
134
139
|
noPush: flags["no-push"] === true,
|
|
135
|
-
filter: flags.filter
|
|
140
|
+
filter: flags.filter,
|
|
141
|
+
channel: flags.channel
|
|
136
142
|
});
|
|
137
143
|
break;
|
|
138
144
|
}
|
|
@@ -186,8 +192,11 @@ function printHelp() {
|
|
|
186
192
|
--strict Fail if any changed package is uncovered (default: only fail if no bump files at all)
|
|
187
193
|
--no-fail Warn only, never exit 1
|
|
188
194
|
--hook <context> Hook context: "pre-commit" or "pre-push" (controls which bump files count)
|
|
195
|
+
--base <branch> Branch to compare against (default: baseBranch; use the channel branch for channel PRs)
|
|
189
196
|
version [--commit] Apply bump files and bump versions
|
|
197
|
+
(on a channel branch: moves pending bump files into .bumpy/<channel>/)
|
|
190
198
|
publish Publish versioned packages
|
|
199
|
+
(on a channel branch: derives prerelease versions and publishes to the channel dist-tag)
|
|
191
200
|
ci check PR check — report pending releases, comment on PR
|
|
192
201
|
ci plan Report what ci release would do (JSON + GitHub Actions outputs)
|
|
193
202
|
ci release Release — create version PR or auto-publish
|
|
@@ -211,12 +220,18 @@ function printHelp() {
|
|
|
211
220
|
--bump <types> Filter by bump type (e.g., "major", "minor,patch")
|
|
212
221
|
--filter <names> Filter by package name/glob (e.g., "@myorg/*")
|
|
213
222
|
--verbose Show bump file details
|
|
223
|
+
--channel <name> Show channel status (default: inferred from the current branch)
|
|
214
224
|
|
|
215
225
|
Publish options:
|
|
216
226
|
--dry-run Preview without publishing
|
|
217
227
|
--tag <tag> npm dist-tag (e.g., "next", "beta")
|
|
218
228
|
--no-push Skip pushing git tags to remote
|
|
219
229
|
--filter <names> Publish only matching packages (e.g., "@myorg/*")
|
|
230
|
+
--channel <name> Publish a prerelease channel (default: inferred from the current branch)
|
|
231
|
+
|
|
232
|
+
Version options:
|
|
233
|
+
--commit Create a git commit with the version changes
|
|
234
|
+
--channel <name> Channel override (default: inferred from the current branch)
|
|
220
235
|
|
|
221
236
|
CI check options:
|
|
222
237
|
--comment Force PR comment on/off (auto-detected in CI)
|