@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.
Files changed (28) hide show
  1. package/README.md +23 -140
  2. package/config-schema.json +43 -0
  3. package/dist/{add-5how2kia.mjs → add-C9rU_89s.mjs} +4 -4
  4. package/dist/{apply-release-plan-DD2R7SL2.mjs → apply-release-plan-DxTsUSqa.mjs} +11 -2
  5. package/dist/{bump-file-B7hmXZlB.mjs → bump-file-mRJeReRJ.mjs} +43 -8
  6. package/dist/{changelog-CbaET5V6.mjs → changelog-DuFhnJRO.mjs} +3 -3
  7. package/dist/{changelog-github-DXDnWkrB.mjs → changelog-github-jLOtwuWj.mjs} +2 -2
  8. package/dist/channels-CFXZkyGd.mjs +75 -0
  9. package/dist/{check-CsF0zh8r.mjs → check-DIl9Dz68.mjs} +18 -6
  10. package/dist/{ci-CIamssoq.mjs → ci-hO7tAbCN.mjs} +391 -23
  11. package/dist/cli.mjs +32 -17
  12. package/dist/{config-D_4GYDJi.mjs → config-0we4ISZX.mjs} +5 -1
  13. package/dist/{generate-CvCvUaRV.mjs → generate-B2OMt_64.mjs} +3 -3
  14. package/dist/{git-DJJ64SW9.mjs → git-DAWj8LyV.mjs} +25 -4
  15. package/dist/index.d.mts +46 -4
  16. package/dist/index.mjs +7 -7
  17. package/dist/prerelease-Blnk8FE1.mjs +186 -0
  18. package/dist/{publish-h6rM58Cq.mjs → publish-Cz0e4KYT.mjs} +164 -19
  19. package/dist/{publish-pipeline-DSj14dW6.mjs → publish-pipeline-BD8mLbL9.mjs} +18 -3
  20. package/dist/{release-plan-mK7iGeGq.mjs → release-plan-C84pcBi-.mjs} +12 -17
  21. package/dist/status-CrMvvvNy.mjs +232 -0
  22. package/dist/{types-Bkh-igOJ.mjs → types-lpiG-Zxh.mjs} +1 -0
  23. package/dist/version-BUUf8vKC.mjs +192 -0
  24. package/package.json +1 -1
  25. package/dist/status-BbsDr6t7.mjs +0 -129
  26. package/dist/version-Cm0nRAFF.mjs +0 -123
  27. /package/dist/{commit-message-CSWVKPJ-.mjs → commit-message-BwsowSds.mjs} +0 -0
  28. /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-D_4GYDJi.mjs";
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 { s as discoverWorkspace } from "./bump-file-B7hmXZlB.mjs";
5
- import { a as DependencyGraph } from "./release-plan-mK7iGeGq.mjs";
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-CbaET5V6.mjs";
8
- import { c as hasUncommittedChanges, l as pushWithTags } from "./git-DJJ64SW9.mjs";
9
- import { t as publishPackages } from "./publish-pipeline-DSj14dW6.mjs";
10
- import { CI_PLAN_CACHE_PATH } from "./ci-CIamssoq.mjs";
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
- * Detects unpublished versions by comparing package.json versions against npm registry.
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-D_4GYDJi.mjs").then((n) => n.t);
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
- const releasePlan = {
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) try {
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
- pushWithTags({ cwd: rootDir });
495
- log.success("Pushed tags to remote");
496
- } catch (err) {
497
- log.warn(`Failed to push tags: ${err instanceof Error ? err.message : err}`);
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-mK7iGeGq.mjs";
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 { d as tagExists, t as createTag } from "./git-DJJ64SW9.mjs";
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-Bkh-igOJ.mjs";
3
- import { s as matchGlob } from "./config-D_4GYDJi.mjs";
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 src/core/semver.ts
1351
- var import_semver = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
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
- })))(), 1);
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(newVersion, dep.versionRange, currentVersion)) continue;
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 { DependencyGraph as a, stripProtocol as i, bumpVersion as n, satisfies as r, assembleReleasePlan as t };
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 };
@@ -56,6 +56,7 @@ const DEFAULT_PUBLISH_CONFIG = {
56
56
  const DEFAULT_CONFIG = {
57
57
  baseBranch: "main",
58
58
  access: "public",
59
+ channels: {},
59
60
  versionCommitMessage: void 0,
60
61
  changedFilePatterns: ["**"],
61
62
  changelog: "default",