@varlock/bumpy 1.13.1 → 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 +23 -140
- package/config-schema.json +43 -0
- package/dist/{add-5how2kia.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-CsF0zh8r.mjs → check-DIl9Dz68.mjs} +18 -6
- package/dist/{ci-CIamssoq.mjs → ci-hO7tAbCN.mjs} +391 -23
- package/dist/cli.mjs +32 -17
- package/dist/{config-D_4GYDJi.mjs → config-0we4ISZX.mjs} +5 -1
- package/dist/{generate-CvCvUaRV.mjs → generate-B2OMt_64.mjs} +3 -3
- package/dist/{git-DJJ64SW9.mjs → git-DAWj8LyV.mjs} +25 -4
- package/dist/index.d.mts +46 -4
- package/dist/index.mjs +7 -7
- package/dist/prerelease-Blnk8FE1.mjs +186 -0
- package/dist/{publish-h6rM58Cq.mjs → publish-Cz0e4KYT.mjs} +164 -19
- package/dist/{publish-pipeline-DSj14dW6.mjs → publish-pipeline-BD8mLbL9.mjs} +18 -3
- 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-BbsDr6t7.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,14 +1,17 @@
|
|
|
1
|
-
import { n as log, o as __require, t as colorize } from "./logger-BgksGFuf.mjs";
|
|
2
|
-
import { a as loadConfig } from "./config-
|
|
1
|
+
import { n as log, o as __require, s as __toESM, t as colorize } from "./logger-BgksGFuf.mjs";
|
|
2
|
+
import { a as loadConfig } from "./config-0we4ISZX.mjs";
|
|
3
3
|
import { r as detectWorkspaces } from "./package-manager-Db_vTztt.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { a as DependencyGraph } from "./release-plan-
|
|
4
|
+
import { c as discoverWorkspace, i as readBumpFiles } from "./bump-file-mRJeReRJ.mjs";
|
|
5
|
+
import { a as require_semver, o as DependencyGraph, t as assembleReleasePlan } from "./release-plan-C84pcBi-.mjs";
|
|
6
6
|
import { r as runArgsAsync, s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
|
|
7
|
-
import { i as loadFormatter, n as generateChangelogEntry } from "./changelog-
|
|
8
|
-
import {
|
|
9
|
-
import { t as publishPackages } from "./publish-pipeline-
|
|
10
|
-
import {
|
|
7
|
+
import { i as loadFormatter, n as generateChangelogEntry } from "./changelog-DuFhnJRO.mjs";
|
|
8
|
+
import { f as tagExists, l as hasUncommittedChanges, n as forcePushTag } from "./git-DAWj8LyV.mjs";
|
|
9
|
+
import { n as willUseOidcExclusively, t as publishPackages } from "./publish-pipeline-BD8mLbL9.mjs";
|
|
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
13
|
//#region src/core/github-release.ts
|
|
14
|
+
var import_semver = /* @__PURE__ */ __toESM(require_semver(), 1);
|
|
12
15
|
/** Get the current HEAD commit SHA */
|
|
13
16
|
function getHeadSha(rootDir) {
|
|
14
17
|
return tryRunArgs([
|
|
@@ -67,6 +70,7 @@ async function createIndividualReleases(releases, bumpFiles, rootDir, opts = {})
|
|
|
67
70
|
"--notes",
|
|
68
71
|
body
|
|
69
72
|
];
|
|
73
|
+
if (/-/.test(release.newVersion)) args.push("--prerelease");
|
|
70
74
|
if (headSha) args.push("--target", headSha);
|
|
71
75
|
await withReleaseToken(() => runArgsAsync(args, { cwd: rootDir }));
|
|
72
76
|
log.dim(` Created GitHub release: ${title}`);
|
|
@@ -193,7 +197,7 @@ async function findReleaseByTag(tag, rootDir) {
|
|
|
193
197
|
}
|
|
194
198
|
}
|
|
195
199
|
/** Create a draft GitHub release */
|
|
196
|
-
async function createDraftRelease(tag, title, body, rootDir, targetSha) {
|
|
200
|
+
async function createDraftRelease(tag, title, body, rootDir, targetSha, opts) {
|
|
197
201
|
const args = [
|
|
198
202
|
"gh",
|
|
199
203
|
"release",
|
|
@@ -205,6 +209,7 @@ async function createDraftRelease(tag, title, body, rootDir, targetSha) {
|
|
|
205
209
|
body,
|
|
206
210
|
"--draft"
|
|
207
211
|
];
|
|
212
|
+
if (opts?.prerelease) args.push("--prerelease");
|
|
208
213
|
if (targetSha) args.push("--target", targetSha);
|
|
209
214
|
await withReleaseToken(() => runArgsAsync(args, { cwd: rootDir }));
|
|
210
215
|
}
|
|
@@ -293,7 +298,13 @@ async function finalizeSupersededDrafts(packageName, newVersion, rootDir) {
|
|
|
293
298
|
//#region src/commands/publish.ts
|
|
294
299
|
/**
|
|
295
300
|
* Publish packages that have been versioned but not yet published.
|
|
296
|
-
*
|
|
301
|
+
*
|
|
302
|
+
* On the base branch: detects unpublished versions by comparing package.json versions
|
|
303
|
+
* against the npm registry.
|
|
304
|
+
*
|
|
305
|
+
* On a channel branch: prerelease versions are never committed, so they are computed
|
|
306
|
+
* here — targets from the cycle's bump files, counters from the registry — written
|
|
307
|
+
* transiently into the working tree, published to the channel's dist-tag, and restored.
|
|
297
308
|
*/
|
|
298
309
|
async function publishCommand(rootDir, opts) {
|
|
299
310
|
const config = await loadConfig(rootDir);
|
|
@@ -304,7 +315,21 @@ async function publishCommand(rootDir, opts) {
|
|
|
304
315
|
log.warn("You have uncommitted changes. Commit or stash them before publishing.");
|
|
305
316
|
process.exit(1);
|
|
306
317
|
}
|
|
318
|
+
const channel = resolveActiveChannel(rootDir, config, opts.channel);
|
|
319
|
+
if (channel) {
|
|
320
|
+
await publishChannel(rootDir, config, packages, catalogs, detectedPm, depGraph, channel, opts);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
307
323
|
let toPublish = await findUnpublishedWithCache(rootDir, packages, config);
|
|
324
|
+
if (Object.keys(config.channels || {}).length > 0) {
|
|
325
|
+
const prereleases = toPublish.filter((r) => import_semver.default.prerelease(r.newVersion) !== null);
|
|
326
|
+
if (prereleases.length > 0) {
|
|
327
|
+
log.error("Refusing to publish prerelease versions outside a channel:");
|
|
328
|
+
for (const r of prereleases) log.error(` • ${r.name}@${r.newVersion}`);
|
|
329
|
+
log.error("Prerelease versions should never be committed — see https://bumpy.varlock.dev/docs/prereleases");
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
308
333
|
if (opts.excludePackages && opts.excludePackages.size > 0) {
|
|
309
334
|
const excluded = toPublish.filter((r) => opts.excludePackages.has(r.name));
|
|
310
335
|
if (excluded.length > 0) {
|
|
@@ -313,7 +338,7 @@ async function publishCommand(rootDir, opts) {
|
|
|
313
338
|
}
|
|
314
339
|
}
|
|
315
340
|
if (opts.filter) {
|
|
316
|
-
const { matchGlob } = await import("./config-
|
|
341
|
+
const { matchGlob } = await import("./config-0we4ISZX.mjs").then((n) => n.t);
|
|
317
342
|
const patterns = opts.filter.split(",").map((p) => p.trim());
|
|
318
343
|
toPublish = toPublish.filter((r) => patterns.some((p) => matchGlob(r.name, p)));
|
|
319
344
|
}
|
|
@@ -323,15 +348,92 @@ async function publishCommand(rootDir, opts) {
|
|
|
323
348
|
}
|
|
324
349
|
const recoveredBumpFiles = opts.recoveredBumpFiles || [];
|
|
325
350
|
if (recoveredBumpFiles.length > 0) for (const release of toPublish) release.bumpFiles = recoveredBumpFiles.filter((bf) => bf.releases.some((r) => r.name === release.name)).map((bf) => bf.id);
|
|
326
|
-
|
|
351
|
+
await runPublishFlow(rootDir, config, packages, catalogs, detectedPm, depGraph, {
|
|
327
352
|
bumpFiles: recoveredBumpFiles,
|
|
328
353
|
releases: toPublish,
|
|
329
354
|
warnings: []
|
|
330
|
-
}
|
|
355
|
+
}, {
|
|
356
|
+
dryRun: opts.dryRun,
|
|
357
|
+
tag: opts.tag,
|
|
358
|
+
noPush: opts.noPush
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Publish a prerelease cycle from a channel branch.
|
|
363
|
+
*
|
|
364
|
+
* The cycle = every bump file on the branch (pending at root or in other channels'
|
|
365
|
+
* dirs, plus shipped in this channel's dir). The whole cycle republishes together
|
|
366
|
+
* each time so the channel dist-tag always points at one coherent, exact-pinned set.
|
|
367
|
+
*/
|
|
368
|
+
async function publishChannel(rootDir, config, packages, catalogs, detectedPm, depGraph, channel, opts) {
|
|
369
|
+
const { bumpFiles, errors: parseErrors } = await readBumpFiles(rootDir, { channels: channelNames(config) });
|
|
370
|
+
if (parseErrors.length > 0) {
|
|
371
|
+
for (const err of parseErrors) log.error(err);
|
|
372
|
+
process.exit(1);
|
|
373
|
+
}
|
|
374
|
+
if (bumpFiles.filter((bf) => bf.channel === channel.name).length === 0) {
|
|
375
|
+
log.info(`Nothing has shipped on channel "${channel.name}" yet (no bump files in .bumpy/${channel.name}/).\n Run \`bumpy version\` on the channel branch (or merge the release PR) first.`);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
log.bold(`Channel "${channel.name}" — preid "-${channel.preid}.N", dist-tag @${channel.tag}\n`);
|
|
379
|
+
const { plan, alreadyPublished, warnings } = await buildChannelReleasePlan(assembleReleasePlan(bumpFiles, packages, depGraph, config, { prereleasePreid: channel.preid }), channel, packages, rootDir);
|
|
380
|
+
for (const w of warnings) log.warn(w);
|
|
381
|
+
for (const skip of alreadyPublished) log.dim(` Skipping ${skip.name}@${skip.version} — already published from this commit`);
|
|
382
|
+
if (plan.releases.length === 0) {
|
|
383
|
+
log.info("All cycle packages already published from this commit.");
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
let toPublish = plan.releases;
|
|
387
|
+
if (opts.filter) {
|
|
388
|
+
const { matchGlob } = await import("./config-0we4ISZX.mjs").then((n) => n.t);
|
|
389
|
+
const patterns = opts.filter.split(",").map((p) => p.trim());
|
|
390
|
+
toPublish = toPublish.filter((r) => patterns.some((p) => matchGlob(r.name, p)));
|
|
391
|
+
if (toPublish.length === 0) {
|
|
392
|
+
log.info("No cycle packages match the filter.");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
let restore = null;
|
|
397
|
+
if (!opts.dryRun) restore = await writeChannelVersionsInPlace(plan, packages);
|
|
398
|
+
try {
|
|
399
|
+
await runPublishFlow(rootDir, config, packages, catalogs, detectedPm, depGraph, {
|
|
400
|
+
bumpFiles: plan.bumpFiles,
|
|
401
|
+
releases: toPublish,
|
|
402
|
+
warnings: []
|
|
403
|
+
}, {
|
|
404
|
+
dryRun: opts.dryRun,
|
|
405
|
+
tag: opts.tag ?? channel.tag,
|
|
406
|
+
noPush: opts.noPush
|
|
407
|
+
});
|
|
408
|
+
} finally {
|
|
409
|
+
if (restore) {
|
|
410
|
+
await restore();
|
|
411
|
+
log.dim(" Restored package.json files (prerelease versions are not committed)");
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* The shared publish flow: OIDC checks, draft GitHub releases, topological publish,
|
|
417
|
+
* release metadata updates, tag pushes. Used by both the stable and channel paths.
|
|
418
|
+
* Mutates `releasePlan.releases` as packages are filtered out (already published, etc.).
|
|
419
|
+
*/
|
|
420
|
+
async function runPublishFlow(rootDir, config, packages, catalogs, detectedPm, depGraph, releasePlan, opts) {
|
|
421
|
+
let toPublish = releasePlan.releases;
|
|
331
422
|
if (opts.dryRun) log.bold("Dry run — would publish:");
|
|
332
423
|
else log.bold("Publishing:");
|
|
333
424
|
for (const r of toPublish) console.log(` ${r.name}@${colorize(r.newVersion, "cyan")}`);
|
|
334
425
|
console.log();
|
|
426
|
+
if (willUseOidcExclusively(rootDir)) {
|
|
427
|
+
const newPackages = await findPackagesMissingFromNpm(toPublish, packages);
|
|
428
|
+
if (newPackages.length > 0) {
|
|
429
|
+
const logFn = opts.dryRun ? log.warn : log.error;
|
|
430
|
+
logFn(`Trusted publishing (OIDC) cannot create a new package. The following don't exist on npm yet:`);
|
|
431
|
+
for (const name of newPackages) logFn(` • ${name}`);
|
|
432
|
+
logFn(`Publish a 0.0.0 placeholder version manually to claim the name, then configure`);
|
|
433
|
+
logFn(`trusted publishing on npmjs.com. Bumpy will then publish the real version via OIDC.`);
|
|
434
|
+
if (!opts.dryRun) process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
335
437
|
const formatter = config.changelog !== false ? await loadFormatter(config.changelog, rootDir) : void 0;
|
|
336
438
|
const ghAvailable = isGhAvailable();
|
|
337
439
|
const publishTargetsByPkg = /* @__PURE__ */ new Map();
|
|
@@ -370,7 +472,7 @@ async function publishCommand(rootDir, opts) {
|
|
|
370
472
|
const title = `${release.name} v${release.newVersion}`;
|
|
371
473
|
const headSha = getHeadSha(rootDir);
|
|
372
474
|
try {
|
|
373
|
-
await createDraftRelease(tag, title, body, rootDir, headSha || void 0);
|
|
475
|
+
await createDraftRelease(tag, title, body, rootDir, headSha || void 0, { prerelease: import_semver.default.prerelease(release.newVersion) !== null });
|
|
374
476
|
log.dim(` Created draft release: ${title}`);
|
|
375
477
|
releaseMetadataByPkg.set(release.name, {
|
|
376
478
|
tag,
|
|
@@ -489,12 +591,22 @@ async function publishCommand(rootDir, opts) {
|
|
|
489
591
|
log.error(`Failed ${result.failed.length}: ${result.failed.map((f) => `${f.name} (${f.error})`).join(", ")}`);
|
|
490
592
|
process.exit(1);
|
|
491
593
|
}
|
|
492
|
-
if (!opts.dryRun && !opts.noPush && result.published.length > 0)
|
|
594
|
+
if (!opts.dryRun && !opts.noPush && result.published.length > 0) {
|
|
595
|
+
const failed = new Set(result.failed.map((f) => f.name));
|
|
596
|
+
const pushed = [];
|
|
493
597
|
log.step("Pushing tags...");
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
598
|
+
for (const release of releasePlan.releases) {
|
|
599
|
+
if (failed.has(release.name)) continue;
|
|
600
|
+
const tag = `${release.name}@${release.newVersion}`;
|
|
601
|
+
if (!tagExists(tag, { cwd: rootDir })) continue;
|
|
602
|
+
try {
|
|
603
|
+
forcePushTag(tag, { cwd: rootDir });
|
|
604
|
+
pushed.push(tag);
|
|
605
|
+
} catch (err) {
|
|
606
|
+
log.warn(` Failed to push tag ${tag}: ${err instanceof Error ? err.message : err}`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (pushed.length > 0) log.success(`Pushed ${pushed.length} tag(s) to remote`);
|
|
498
610
|
}
|
|
499
611
|
if (!ghAvailable && result.published.length > 0) await createIndividualReleases(releasePlan.releases.filter((r) => result.published.some((p) => p.name === r.name)), releasePlan.bumpFiles, rootDir, {
|
|
500
612
|
dryRun: opts.dryRun,
|
|
@@ -618,5 +730,38 @@ async function checkIfPublished(name, version, pkgConfig) {
|
|
|
618
730
|
return false;
|
|
619
731
|
}
|
|
620
732
|
}
|
|
733
|
+
/**
|
|
734
|
+
* Check whether a package exists on npm at all (any version).
|
|
735
|
+
* Returns true if the package is registered, false if it doesn't exist or the query fails.
|
|
736
|
+
*/
|
|
737
|
+
async function packageExistsOnNpm(name, registry) {
|
|
738
|
+
const args = [
|
|
739
|
+
"npm",
|
|
740
|
+
"info",
|
|
741
|
+
name,
|
|
742
|
+
"name"
|
|
743
|
+
];
|
|
744
|
+
if (registry) args.push("--registry", registry);
|
|
745
|
+
try {
|
|
746
|
+
return (await runArgsAsync(args)).trim() === name;
|
|
747
|
+
} catch {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
/**
|
|
752
|
+
* Filter `toPublish` to package names that don't exist on npm yet.
|
|
753
|
+
* Skips packages not going through the standard npm publish flow.
|
|
754
|
+
*/
|
|
755
|
+
async function findPackagesMissingFromNpm(toPublish, packages) {
|
|
756
|
+
const missing = [];
|
|
757
|
+
await Promise.all(toPublish.map(async (release) => {
|
|
758
|
+
const pkg = packages.get(release.name);
|
|
759
|
+
const pkgConfig = pkg.bumpy || {};
|
|
760
|
+
if (pkgConfig.publishCommand || pkgConfig.skipNpmPublish) return;
|
|
761
|
+
if (pkg.private && !pkgConfig.publishCommand) return;
|
|
762
|
+
if (!await packageExistsOnNpm(release.name, pkgConfig.registry)) missing.push(release.name);
|
|
763
|
+
}));
|
|
764
|
+
return missing;
|
|
765
|
+
}
|
|
621
766
|
//#endregion
|
|
622
767
|
export { findUnpublishedPackages, publishCommand };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { n as log, t as colorize } from "./logger-BgksGFuf.mjs";
|
|
2
2
|
import { a as readJson, u as updateJsonNestedField } from "./fs-CBXKZhoU.mjs";
|
|
3
3
|
import { s as resolveCatalogDep } from "./package-manager-Db_vTztt.mjs";
|
|
4
|
-
import { i as stripProtocol } from "./release-plan-
|
|
4
|
+
import { i as stripProtocol } from "./release-plan-C84pcBi-.mjs";
|
|
5
5
|
import { i as runAsync, o as sq, r as runArgsAsync, s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
|
|
6
|
-
import {
|
|
6
|
+
import { f as tagExists, t as createTag } from "./git-DAWj8LyV.mjs";
|
|
7
7
|
import { resolve } from "node:path";
|
|
8
8
|
import { unlink } from "node:fs/promises";
|
|
9
9
|
import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -23,6 +23,21 @@ function detectOidcProvider() {
|
|
|
23
23
|
if (process.env.CIRCLECI && process.env.NPM_ID_TOKEN) return "circleci";
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Returns true when OIDC trusted publishing is the only available npm auth path:
|
|
28
|
+
* an OIDC provider is detected AND no token env vars or .npmrc auth are present.
|
|
29
|
+
*
|
|
30
|
+
* Used to gate checks that only matter when OIDC will definitely be used — e.g.
|
|
31
|
+
* erroring when a brand-new package can't be bootstrapped via trusted publishing.
|
|
32
|
+
* Detection alone is leaky (id-token: write is also set for provenance), so this
|
|
33
|
+
* helper avoids false positives when a token fallback exists.
|
|
34
|
+
*/
|
|
35
|
+
function willUseOidcExclusively(rootDir) {
|
|
36
|
+
if (!detectOidcProvider()) return false;
|
|
37
|
+
if (process.env.NPM_TOKEN || process.env.NODE_AUTH_TOKEN) return false;
|
|
38
|
+
const npmrcPath = resolve(rootDir, ".npmrc");
|
|
39
|
+
return !(existsSync(npmrcPath) ? readFileSync(npmrcPath, "utf-8") : "").includes(":_authToken=");
|
|
40
|
+
}
|
|
26
41
|
const OIDC_NPM_UPGRADE_HINTS = {
|
|
27
42
|
"github-actions": "Add `actions/setup-node@v6` with `node-version: lts/*` to your workflow",
|
|
28
43
|
gitlab: "Use a Node.js image with npm >= 11.5.1 or run `npm install -g npm@latest`",
|
|
@@ -306,4 +321,4 @@ async function resolveProtocolsInPlace(pkg, packages, releasePlan, catalogs) {
|
|
|
306
321
|
}
|
|
307
322
|
}
|
|
308
323
|
//#endregion
|
|
309
|
-
export { publishPackages as t };
|
|
324
|
+
export { willUseOidcExclusively as n, publishPackages as t };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { i as __commonJSMin, s as __toESM } from "./logger-BgksGFuf.mjs";
|
|
2
|
-
import { c as maxBump, l as normalizeCascadeConfig, n as DEFAULT_BUMP_RULES, o as bumpLevel, s as hasCascade } from "./types-
|
|
3
|
-
import { s as matchGlob } from "./config-
|
|
2
|
+
import { c as maxBump, l as normalizeCascadeConfig, n as DEFAULT_BUMP_RULES, o as bumpLevel, s as hasCascade } from "./types-lpiG-Zxh.mjs";
|
|
3
|
+
import { s as matchGlob } from "./config-0we4ISZX.mjs";
|
|
4
4
|
//#region src/core/dep-graph.ts
|
|
5
5
|
var DependencyGraph = class {
|
|
6
6
|
/** Map from package name → packages that depend on it */
|
|
@@ -1347,8 +1347,8 @@ var require_subset = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
|
1347
1347
|
module.exports = subset;
|
|
1348
1348
|
}));
|
|
1349
1349
|
//#endregion
|
|
1350
|
-
//#region
|
|
1351
|
-
var
|
|
1350
|
+
//#region ../../node_modules/.bun/semver@7.7.4/node_modules/semver/index.js
|
|
1351
|
+
var require_semver = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
1352
1352
|
const internalRe = require_re();
|
|
1353
1353
|
const constants = require_constants();
|
|
1354
1354
|
const SemVer = require_semver$1();
|
|
@@ -1400,7 +1400,10 @@ var import_semver = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exp
|
|
|
1400
1400
|
compareIdentifiers: identifiers.compareIdentifiers,
|
|
1401
1401
|
rcompareIdentifiers: identifiers.rcompareIdentifiers
|
|
1402
1402
|
};
|
|
1403
|
-
}))
|
|
1403
|
+
}));
|
|
1404
|
+
//#endregion
|
|
1405
|
+
//#region src/core/semver.ts
|
|
1406
|
+
var import_semver = /* @__PURE__ */ __toESM(require_semver(), 1);
|
|
1404
1407
|
function bumpVersion(version, type) {
|
|
1405
1408
|
const result = import_semver.default.inc(version, type);
|
|
1406
1409
|
if (!result) throw new Error(`Failed to bump ${version} by ${type}`);
|
|
@@ -1433,16 +1436,7 @@ function stripProtocol(range) {
|
|
|
1433
1436
|
}
|
|
1434
1437
|
//#endregion
|
|
1435
1438
|
//#region src/core/release-plan.ts
|
|
1436
|
-
|
|
1437
|
-
* Build a release plan from pending bump files, the dependency graph, and config.
|
|
1438
|
-
* This is the core algorithm of bumpy.
|
|
1439
|
-
*
|
|
1440
|
-
* The propagation loop runs three phases until stable:
|
|
1441
|
-
* Phase A — fix out-of-range dependencies (always runs)
|
|
1442
|
-
* Phase B — enforce fixed/linked group constraints
|
|
1443
|
-
* Phase C — apply cascades and proactive propagation rules
|
|
1444
|
-
*/
|
|
1445
|
-
function assembleReleasePlan(bumpFiles, packages, depGraph, config) {
|
|
1439
|
+
function assembleReleasePlan(bumpFiles, packages, depGraph, config, opts = {}) {
|
|
1446
1440
|
if (bumpFiles.length === 0) return {
|
|
1447
1441
|
bumpFiles: [],
|
|
1448
1442
|
releases: [],
|
|
@@ -1485,11 +1479,12 @@ function assembleReleasePlan(bumpFiles, packages, depGraph, config) {
|
|
|
1485
1479
|
for (const [pkgName, bump] of planned) {
|
|
1486
1480
|
const pkg = packages.get(pkgName);
|
|
1487
1481
|
const newVersion = bumpVersion(pkg.version, bump.type);
|
|
1482
|
+
const versionForRangeCheck = opts.prereleasePreid ? `${newVersion}-${opts.prereleasePreid}.0` : newVersion;
|
|
1488
1483
|
const dependents = depGraph.getDependents(pkgName);
|
|
1489
1484
|
for (const dep of dependents) {
|
|
1490
1485
|
if (dep.depType === "devDependencies") continue;
|
|
1491
1486
|
const currentVersion = pkg.version;
|
|
1492
|
-
if (satisfies(
|
|
1487
|
+
if (satisfies(versionForRangeCheck, dep.versionRange, currentVersion)) continue;
|
|
1493
1488
|
let depBump;
|
|
1494
1489
|
if (dep.depType === "peerDependencies") depBump = bump.type;
|
|
1495
1490
|
else depBump = "patch";
|
|
@@ -1704,4 +1699,4 @@ function resolveRule(dependentName, depType, packages, config) {
|
|
|
1704
1699
|
};
|
|
1705
1700
|
}
|
|
1706
1701
|
//#endregion
|
|
1707
|
-
export {
|
|
1702
|
+
export { require_semver as a, stripProtocol as i, bumpVersion as n, DependencyGraph as o, satisfies as r, assembleReleasePlan as t };
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { n as log, t as colorize } from "./logger-BgksGFuf.mjs";
|
|
2
|
+
import { a as loadConfig } from "./config-0we4ISZX.mjs";
|
|
3
|
+
import { i as readBumpFiles, s as discoverPackages, t as filterBranchBumpFiles } from "./bump-file-mRJeReRJ.mjs";
|
|
4
|
+
import { o as DependencyGraph, t as assembleReleasePlan } from "./release-plan-C84pcBi-.mjs";
|
|
5
|
+
import { a as getChangedFiles, o as getCurrentBranch } from "./git-DAWj8LyV.mjs";
|
|
6
|
+
import { channelNames, resolveActiveChannel } from "./channels-CFXZkyGd.mjs";
|
|
7
|
+
import { t as buildChannelReleasePlan } from "./prerelease-Blnk8FE1.mjs";
|
|
8
|
+
//#region src/commands/status.ts
|
|
9
|
+
async function statusCommand(rootDir, opts) {
|
|
10
|
+
const config = await loadConfig(rootDir);
|
|
11
|
+
const packages = await discoverPackages(rootDir, config);
|
|
12
|
+
const depGraph = new DependencyGraph(packages);
|
|
13
|
+
const channel = resolveActiveChannel(rootDir, config, opts.channel);
|
|
14
|
+
if (channel) {
|
|
15
|
+
await channelStatus(rootDir, config, channel, packages, depGraph, opts);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const { bumpFiles, errors: parseErrors } = await readBumpFiles(rootDir, { channels: channelNames(config) });
|
|
19
|
+
if (parseErrors.length > 0) for (const err of parseErrors) log.error(err);
|
|
20
|
+
if (bumpFiles.length === 0) {
|
|
21
|
+
if (opts.json) console.log(JSON.stringify({
|
|
22
|
+
bumpFiles: [],
|
|
23
|
+
releases: [],
|
|
24
|
+
packageNames: []
|
|
25
|
+
}, null, 2));
|
|
26
|
+
else if (!opts.packagesOnly) log.info("No pending bump files.");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const plan = assembleReleasePlan(bumpFiles, packages, depGraph, config);
|
|
30
|
+
let branchBumpFileIds;
|
|
31
|
+
const currentBranch = getCurrentBranch({ cwd: rootDir });
|
|
32
|
+
if (currentBranch && currentBranch !== config.baseBranch) branchBumpFileIds = filterBranchBumpFiles(bumpFiles, getChangedFiles(rootDir, config.baseBranch), rootDir).branchBumpFileIds;
|
|
33
|
+
let releases = plan.releases;
|
|
34
|
+
if (opts.bumpType) {
|
|
35
|
+
const types = opts.bumpType.split(",").map((t) => t.trim());
|
|
36
|
+
releases = releases.filter((r) => types.includes(r.type));
|
|
37
|
+
}
|
|
38
|
+
if (opts.filter) {
|
|
39
|
+
const { matchGlob } = await import("./config-0we4ISZX.mjs").then((n) => n.t);
|
|
40
|
+
const patterns = opts.filter.split(",").map((p) => p.trim());
|
|
41
|
+
releases = releases.filter((r) => patterns.some((p) => matchGlob(r.name, p)));
|
|
42
|
+
}
|
|
43
|
+
if (opts.json) {
|
|
44
|
+
const jsonOutput = {
|
|
45
|
+
bumpFiles: plan.bumpFiles.map((bf) => ({
|
|
46
|
+
id: bf.id,
|
|
47
|
+
summary: bf.summary,
|
|
48
|
+
releases: bf.releases.map((r) => ({
|
|
49
|
+
name: r.name,
|
|
50
|
+
type: r.type
|
|
51
|
+
})),
|
|
52
|
+
...branchBumpFileIds ? { inCurrentBranch: branchBumpFileIds.has(bf.id) } : {}
|
|
53
|
+
})),
|
|
54
|
+
releases: releases.map((r) => {
|
|
55
|
+
const pkg = packages.get(r.name);
|
|
56
|
+
const pkgConfig = pkg?.bumpy || {};
|
|
57
|
+
return {
|
|
58
|
+
name: r.name,
|
|
59
|
+
type: r.type,
|
|
60
|
+
oldVersion: r.oldVersion,
|
|
61
|
+
newVersion: r.newVersion,
|
|
62
|
+
dir: pkg?.relativeDir,
|
|
63
|
+
bumpFiles: r.bumpFiles,
|
|
64
|
+
isDependencyBump: r.isDependencyBump,
|
|
65
|
+
isCascadeBump: r.isCascadeBump,
|
|
66
|
+
...branchBumpFileIds ? { inCurrentBranch: r.bumpFiles.some((id) => branchBumpFileIds.has(id)) } : {},
|
|
67
|
+
publishTargets: getPublishTargets(pkg, pkgConfig, config)
|
|
68
|
+
};
|
|
69
|
+
}),
|
|
70
|
+
packageNames: releases.map((r) => r.name)
|
|
71
|
+
};
|
|
72
|
+
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (opts.packagesOnly) {
|
|
76
|
+
for (const r of releases) console.log(r.name);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
log.bold(`${bumpFiles.length} bump file(s) pending\n`);
|
|
80
|
+
if (releases.length === 0) {
|
|
81
|
+
log.warn("No packages match the current filters.");
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const groups = [
|
|
85
|
+
[
|
|
86
|
+
"Major",
|
|
87
|
+
"red",
|
|
88
|
+
releases.filter((r) => r.type === "major")
|
|
89
|
+
],
|
|
90
|
+
[
|
|
91
|
+
"Minor",
|
|
92
|
+
"yellow",
|
|
93
|
+
releases.filter((r) => r.type === "minor")
|
|
94
|
+
],
|
|
95
|
+
[
|
|
96
|
+
"Patch",
|
|
97
|
+
"green",
|
|
98
|
+
releases.filter((r) => r.type === "patch")
|
|
99
|
+
]
|
|
100
|
+
];
|
|
101
|
+
for (const [label, color, group] of groups) {
|
|
102
|
+
if (group.length === 0) continue;
|
|
103
|
+
log.bold(colorize(label, color));
|
|
104
|
+
for (const r of group) printRelease(r, packages);
|
|
105
|
+
console.log();
|
|
106
|
+
}
|
|
107
|
+
if (plan.warnings.length > 0) {
|
|
108
|
+
for (const w of plan.warnings) log.warn(w);
|
|
109
|
+
console.log();
|
|
110
|
+
}
|
|
111
|
+
if (opts.verbose) {
|
|
112
|
+
log.bold("Bump files:");
|
|
113
|
+
for (const bf of plan.bumpFiles) {
|
|
114
|
+
console.log(` ${colorize(bf.id, "cyan")}`);
|
|
115
|
+
for (const r of bf.releases) console.log(` ${r.name}: ${r.type}`);
|
|
116
|
+
if (bf.summary) console.log(` ${colorize(bf.summary.split("\n")[0], "dim")}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Status on a prerelease channel: shows the cycle (shipped + pending bump files)
|
|
122
|
+
* and the derived prerelease versions. Counters come from the registry — when it's
|
|
123
|
+
* unreachable, targets render with a ".?" counter placeholder.
|
|
124
|
+
*/
|
|
125
|
+
async function channelStatus(rootDir, config, channel, packages, depGraph, opts) {
|
|
126
|
+
const { bumpFiles, errors: parseErrors } = await readBumpFiles(rootDir, { channels: channelNames(config) });
|
|
127
|
+
for (const err of parseErrors) log.error(err);
|
|
128
|
+
const shipped = bumpFiles.filter((bf) => bf.channel === channel.name);
|
|
129
|
+
const pending = bumpFiles.filter((bf) => bf.channel !== channel.name);
|
|
130
|
+
if (bumpFiles.length === 0) {
|
|
131
|
+
if (opts.json) console.log(JSON.stringify({
|
|
132
|
+
channel: channel.name,
|
|
133
|
+
bumpFiles: [],
|
|
134
|
+
releases: [],
|
|
135
|
+
packageNames: []
|
|
136
|
+
}, null, 2));
|
|
137
|
+
else if (!opts.packagesOnly) log.info(`No bump files in the "${channel.name}" cycle.`);
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
const stablePlan = assembleReleasePlan(bumpFiles, packages, depGraph, config, { prereleasePreid: channel.preid });
|
|
141
|
+
let releases = stablePlan.releases;
|
|
142
|
+
let countersExact = false;
|
|
143
|
+
try {
|
|
144
|
+
const built = await buildChannelReleasePlan(stablePlan, channel, packages, rootDir, { forDisplay: true });
|
|
145
|
+
if (built.plan.releases.length > 0) {
|
|
146
|
+
releases = built.plan.releases;
|
|
147
|
+
countersExact = true;
|
|
148
|
+
}
|
|
149
|
+
} catch {}
|
|
150
|
+
if (!countersExact) releases = releases.map((r) => ({
|
|
151
|
+
...r,
|
|
152
|
+
newVersion: `${r.newVersion}-${channel.preid}.?`
|
|
153
|
+
}));
|
|
154
|
+
if (opts.bumpType) {
|
|
155
|
+
const types = opts.bumpType.split(",").map((t) => t.trim());
|
|
156
|
+
releases = releases.filter((r) => types.includes(r.type));
|
|
157
|
+
}
|
|
158
|
+
if (opts.filter) {
|
|
159
|
+
const { matchGlob } = await import("./config-0we4ISZX.mjs").then((n) => n.t);
|
|
160
|
+
const patterns = opts.filter.split(",").map((p) => p.trim());
|
|
161
|
+
releases = releases.filter((r) => patterns.some((p) => matchGlob(r.name, p)));
|
|
162
|
+
}
|
|
163
|
+
if (opts.json) {
|
|
164
|
+
console.log(JSON.stringify({
|
|
165
|
+
channel: channel.name,
|
|
166
|
+
preid: channel.preid,
|
|
167
|
+
tag: channel.tag,
|
|
168
|
+
bumpFiles: bumpFiles.map((bf) => ({
|
|
169
|
+
id: bf.id,
|
|
170
|
+
summary: bf.summary,
|
|
171
|
+
releases: bf.releases.map((r) => ({
|
|
172
|
+
name: r.name,
|
|
173
|
+
type: r.type
|
|
174
|
+
})),
|
|
175
|
+
shipped: bf.channel === channel.name
|
|
176
|
+
})),
|
|
177
|
+
releases: releases.map((r) => {
|
|
178
|
+
const pkg = packages.get(r.name);
|
|
179
|
+
return {
|
|
180
|
+
name: r.name,
|
|
181
|
+
type: r.type,
|
|
182
|
+
oldVersion: r.oldVersion,
|
|
183
|
+
newVersion: r.newVersion,
|
|
184
|
+
dir: pkg?.relativeDir,
|
|
185
|
+
bumpFiles: r.bumpFiles,
|
|
186
|
+
isDependencyBump: r.isDependencyBump,
|
|
187
|
+
isCascadeBump: r.isCascadeBump,
|
|
188
|
+
publishTargets: getPublishTargets(pkg, pkg?.bumpy || {}, config)
|
|
189
|
+
};
|
|
190
|
+
}),
|
|
191
|
+
packageNames: releases.map((r) => r.name)
|
|
192
|
+
}, null, 2));
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (opts.packagesOnly) {
|
|
196
|
+
for (const r of releases) console.log(r.name);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
log.bold(`Channel "${channel.name}" — preid "-${channel.preid}.N", dist-tag @${channel.tag}\n`);
|
|
200
|
+
printBumpFileGroup(`Shipped on this channel (.bumpy/${channel.name}/)`, shipped);
|
|
201
|
+
printBumpFileGroup("Pending (next prerelease)", pending);
|
|
202
|
+
log.bold(`Cycle releases${countersExact ? "" : colorize(" (registry unreachable — counters unknown)", "dim")}`);
|
|
203
|
+
for (const r of releases) printRelease(r, packages);
|
|
204
|
+
console.log();
|
|
205
|
+
if (stablePlan.warnings.length > 0) for (const w of stablePlan.warnings) log.warn(w);
|
|
206
|
+
}
|
|
207
|
+
function printBumpFileGroup(label, files) {
|
|
208
|
+
log.bold(label);
|
|
209
|
+
if (files.length === 0) console.log(colorize(" (none)", "dim"));
|
|
210
|
+
for (const bf of files) {
|
|
211
|
+
const summary = bf.summary ? colorize(` — ${bf.summary.split("\n")[0]}`, "dim") : "";
|
|
212
|
+
console.log(` ${colorize(`${bf.id}.md`, "cyan")}${summary}`);
|
|
213
|
+
}
|
|
214
|
+
console.log();
|
|
215
|
+
}
|
|
216
|
+
function printRelease(r, packages) {
|
|
217
|
+
const pkg = packages.get(r.name);
|
|
218
|
+
const dir = pkg ? colorize(` (${pkg.relativeDir})`, "dim") : "";
|
|
219
|
+
const suffix = r.isDependencyBump ? colorize(" ← dependency bump", "dim") : r.isCascadeBump ? colorize(" ← cascade", "dim") : "";
|
|
220
|
+
console.log(` ${r.name}: ${r.oldVersion} → ${colorize(r.newVersion, "cyan")}${suffix}${dir}`);
|
|
221
|
+
}
|
|
222
|
+
/** Determine which publish targets a package will use */
|
|
223
|
+
function getPublishTargets(pkg, pkgConfig, _config) {
|
|
224
|
+
if (!pkg) return [];
|
|
225
|
+
if (pkg.private && !pkgConfig.publishCommand) return [];
|
|
226
|
+
const targets = [];
|
|
227
|
+
if (pkgConfig.publishCommand) targets.push({ type: "custom" });
|
|
228
|
+
if (!pkgConfig.publishCommand && !pkgConfig.skipNpmPublish) targets.push({ type: "npm" });
|
|
229
|
+
return targets;
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
export { statusCommand };
|