@varlock/bumpy 1.10.2 → 1.12.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 CHANGED
@@ -171,8 +171,6 @@ jobs:
171
171
 
172
172
  </details>
173
173
 
174
- You can also use `bumpy ci release --auto-publish` to version + publish directly on merge without the intermediate PR.
175
-
176
174
  ### Token setup
177
175
 
178
176
  The default `github.token` works for basic functionality, but GitHub's anti-recursion guard means PRs created by the default token won't trigger other workflows - so your regular CI (tests, linting, etc.) won't run automatically on the Version Packages PR. To fix this, provide a `BUMPY_GH_TOKEN` secret using either a **fine-grained PAT** or a **GitHub App token**. See the [full token setup guide](https://github.com/dmno-dev/bumpy/blob/main/docs/github-actions.md#token-setup) for details.
@@ -151,6 +151,16 @@
151
151
  "enum": ["pack", "in-place", "none"],
152
152
  "description": "How to handle workspace:/catalog: protocol resolution. \"pack\" = use PM's pack to build a clean tarball, \"in-place\" = rewrite package.json before publish, \"none\" = don't resolve.",
153
153
  "default": "pack"
154
+ },
155
+ "provenance": {
156
+ "type": "boolean",
157
+ "description": "Attach provenance attestation when publishing via npm. Requires a supported CI environment with OIDC (GitHub Actions, GitLab CI, etc.). Only works with publishManager \"npm\".",
158
+ "default": false
159
+ },
160
+ "npmStaged": {
161
+ "type": "boolean",
162
+ "description": "Use npm staged publishing (`npm stage publish`). Stages the publish on npmjs.com, requiring manual 2FA approval before going live. Only works with publishManager \"npm\" and requires npm >= 11.15.0.",
163
+ "default": false
154
164
  }
155
165
  },
156
166
  "additionalProperties": false
@@ -1,15 +1,14 @@
1
1
  import { n as log, r as require_picocolors, s as __toESM } from "./logger-BgksGFuf.mjs";
2
2
  import { n as exists, t as ensureDir } from "./fs-CBXKZhoU.mjs";
3
- import { a as loadConfig, o as loadPackageConfig, r as getBumpyDir } from "./config-D_4GYDJi.mjs";
4
- import { a as writeBumpFile, o as discoverPackages, r as readBumpFiles, s as discoverWorkspace, t as filterBranchBumpFiles } from "./bump-file-B9DpXK5X.mjs";
5
- import { r as getChangedFiles } from "./git-JGLQtk-M.mjs";
3
+ import { a as loadConfig, r as getBumpyDir } from "./config-D_4GYDJi.mjs";
4
+ import { a as writeBumpFile, r as readBumpFiles, s as discoverWorkspace, t as filterBranchBumpFiles } from "./bump-file-B7hmXZlB.mjs";
5
+ import { i as getChangedFiles } from "./git-DJJ64SW9.mjs";
6
6
  import { l as pt, o as gt, r as Ot, s as mt, t as unwrap, u as wt } from "./clack-W95rXis0.mjs";
7
7
  import { n as slugify, t as randomName } from "./names-COooXAFg.mjs";
8
- import { n as findChangedPackages, r as require_picomatch } from "./check-pbyTB4KC.mjs";
9
- import { relative, resolve } from "node:path";
8
+ import { n as findChangedPackages } from "./check-CsF0zh8r.mjs";
9
+ import { resolve } from "node:path";
10
10
  import * as readline from "node:readline";
11
11
  //#region src/prompts/bump-select.ts
12
- var import_picomatch = /* @__PURE__ */ __toESM(require_picomatch(), 1);
13
12
  var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors(), 1);
14
13
  const LEVELS = [
15
14
  "skip",
@@ -208,26 +207,13 @@ async function addCommand(rootDir, opts) {
208
207
  filename = opts.name ? slugify(opts.name) : randomName();
209
208
  } else {
210
209
  mt(import_picocolors.default.bgCyan(import_picocolors.default.black(" bumpy add ")));
211
- const pkgs = await discoverPackages(rootDir, config);
210
+ const { packages: pkgs } = await discoverWorkspace(rootDir, config);
212
211
  if (pkgs.size === 0) {
213
212
  pt("No managed packages found in this workspace.");
214
213
  process.exit(1);
215
214
  }
216
- const baseBranch = config.baseBranch;
217
- const changedFiles = getChangedFiles(rootDir, baseBranch);
218
- const matchers = /* @__PURE__ */ new Map();
219
- for (const [name, pkg] of pkgs) {
220
- const patterns = (await loadPackageConfig(pkg.dir, config, name)).changedFilePatterns ?? config.changedFilePatterns;
221
- matchers.set(name, (0, import_picomatch.default)(patterns));
222
- }
223
- const changedPackageNames = /* @__PURE__ */ new Set();
224
- for (const file of changedFiles) for (const [name, pkg] of pkgs) {
225
- const pkgRelDir = relative(rootDir, pkg.dir);
226
- if (file.startsWith(pkgRelDir + "/")) {
227
- const relToPackage = file.slice(pkgRelDir.length + 1);
228
- if (matchers.get(name)(relToPackage)) changedPackageNames.add(name);
229
- }
230
- }
215
+ const changedFiles = getChangedFiles(rootDir, config.baseBranch);
216
+ const changedPackageNames = new Set(await findChangedPackages(changedFiles, pkgs, rootDir, config));
231
217
  const { bumpFiles: allBumpFiles } = await readBumpFiles(rootDir);
232
218
  const { branchBumpFiles } = filterBranchBumpFiles(allBumpFiles, changedFiles, rootDir);
233
219
  const alreadyCoveredPackages = /* @__PURE__ */ new Map();
@@ -1,6 +1,6 @@
1
1
  import { a as readJson, f as writeText, i as listFiles, n as exists, s as readText } from "./fs-CBXKZhoU.mjs";
2
2
  import { i as isPackageManaged, o as loadPackageConfig, r as getBumpyDir } from "./config-D_4GYDJi.mjs";
3
- import { i as jsYaml, n as detectWorkspaces } from "./package-manager-BQPwXwu5.mjs";
3
+ import { c as jsYaml, r as detectWorkspaces } from "./package-manager-Db_vTztt.mjs";
4
4
  import { s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
5
5
  import { relative, resolve } from "node:path";
6
6
  import { readdir, stat } from "node:fs/promises";
@@ -1,8 +1,11 @@
1
1
  import { a as __exportAll, i as __commonJSMin, n as log, s as __toESM, t as colorize } from "./logger-BgksGFuf.mjs";
2
+ import { n as exists, s as readText } from "./fs-CBXKZhoU.mjs";
3
+ import { a as DEP_TYPES } from "./types-Bkh-igOJ.mjs";
2
4
  import { a as loadConfig, o as loadPackageConfig, r as getBumpyDir } from "./config-D_4GYDJi.mjs";
3
- import { r as readBumpFiles, s as discoverWorkspace, t as filterBranchBumpFiles } from "./bump-file-B9DpXK5X.mjs";
4
- import { a as getFileStatuses, r as getChangedFiles } from "./git-JGLQtk-M.mjs";
5
- import { relative } from "node:path";
5
+ import { a as isCatalogRefAffected, i as diffCatalogMaps, n as detectPackageManager, o as parseCatalogs, t as CATALOG_FILES } from "./package-manager-Db_vTztt.mjs";
6
+ import { r as readBumpFiles, s as discoverWorkspace, t as filterBranchBumpFiles } from "./bump-file-B7hmXZlB.mjs";
7
+ import { i as getChangedFiles, n as getBaseCompareRef, o as getFileStatuses, u as readFileAtRef } from "./git-DJJ64SW9.mjs";
8
+ import { relative, resolve } from "node:path";
6
9
  //#region ../../node_modules/.bun/picomatch@4.0.4/node_modules/picomatch/lib/constants.js
7
10
  var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
8
11
  const WIN_SLASH = "\\\\/";
@@ -1996,7 +1999,32 @@ async function findChangedPackages(changedFiles, packages, rootDir, config) {
1996
1999
  if (matchers.get(name)(relToPackage)) changed.add(name);
1997
2000
  }
1998
2001
  }
2002
+ const catalogChanges = await getChangedCatalogEntries(rootDir, config.baseBranch, changedFiles);
2003
+ if (catalogChanges.size > 0) for (const [name, pkg] of packages) {
2004
+ if (changed.has(name)) continue;
2005
+ for (const depType of DEP_TYPES) {
2006
+ const deps = pkg[depType];
2007
+ let matched = false;
2008
+ for (const [depName, range] of Object.entries(deps)) if (isCatalogRefAffected(range, depName, catalogChanges)) {
2009
+ changed.add(name);
2010
+ matched = true;
2011
+ break;
2012
+ }
2013
+ if (matched) break;
2014
+ }
2015
+ }
1999
2016
  return [...changed];
2000
2017
  }
2018
+ /**
2019
+ * Compute which catalog entries changed between the base ref and HEAD.
2020
+ * Returns Map<catalogName, Set<depName>>. Empty if no catalog files changed.
2021
+ */
2022
+ async function getChangedCatalogEntries(rootDir, baseBranch, changedFiles) {
2023
+ if (!changedFiles.some((f) => CATALOG_FILES.includes(f))) return /* @__PURE__ */ new Map();
2024
+ const baseRef = getBaseCompareRef(rootDir, baseBranch);
2025
+ const pm = await detectPackageManager(rootDir);
2026
+ const afterCatalogs = parseCatalogs(pm === "pnpm" && await exists(resolve(rootDir, "pnpm-workspace.yaml")) ? await readText(resolve(rootDir, "pnpm-workspace.yaml")) : null, await exists(resolve(rootDir, "package.json")) ? await readText(resolve(rootDir, "package.json")) : null);
2027
+ return diffCatalogMaps(parseCatalogs(pm === "pnpm" ? readFileAtRef(rootDir, baseRef, "pnpm-workspace.yaml") : null, readFileAtRef(rootDir, baseRef, "package.json")), afterCatalogs);
2028
+ }
2001
2029
  //#endregion
2002
- export { findChangedPackages as n, require_picomatch as r, check_exports as t };
2030
+ export { findChangedPackages as n, check_exports as t };
@@ -1,12 +1,12 @@
1
1
  import { n as log, t as colorize } from "./logger-BgksGFuf.mjs";
2
2
  import { a as loadConfig } from "./config-D_4GYDJi.mjs";
3
- import { t as detectPackageManager } from "./package-manager-BQPwXwu5.mjs";
4
- import { i as recoverDeletedBumpFiles, r as readBumpFiles, s as discoverWorkspace, t as filterBranchBumpFiles } from "./bump-file-B9DpXK5X.mjs";
3
+ import { n as detectPackageManager } from "./package-manager-Db_vTztt.mjs";
4
+ import { i as recoverDeletedBumpFiles, r as readBumpFiles, s as discoverWorkspace, t as filterBranchBumpFiles } from "./bump-file-B7hmXZlB.mjs";
5
5
  import { a as DependencyGraph, t as assembleReleasePlan } from "./release-plan-mK7iGeGq.mjs";
6
6
  import { n as runArgs, r as runArgsAsync, s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
7
- import { r as getChangedFiles, u as withGitToken } from "./git-JGLQtk-M.mjs";
7
+ import { f as withGitToken, i as getChangedFiles } from "./git-DJJ64SW9.mjs";
8
8
  import { t as randomName } from "./names-COooXAFg.mjs";
9
- import { n as findChangedPackages } from "./check-pbyTB4KC.mjs";
9
+ import { n as findChangedPackages } from "./check-CsF0zh8r.mjs";
10
10
  import { t as resolveCommitMessage } from "./commit-message-CSWVKPJ-.mjs";
11
11
  import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
12
12
  import { createHash } from "node:crypto";
@@ -157,7 +157,7 @@ async function ciPlanCommand(rootDir) {
157
157
  packageNames: plan.releases.map((r) => r.name)
158
158
  };
159
159
  else {
160
- const { findUnpublishedPackages } = await import("./publish-B9xzQREV.mjs");
160
+ const { findUnpublishedPackages } = await import("./publish-BDA1bOZ7.mjs");
161
161
  const unpublished = await findUnpublishedPackages(packages, config);
162
162
  if (unpublished.length > 0) output = {
163
163
  mode: "publish",
@@ -212,8 +212,10 @@ function writeGitHubOutput(key, value) {
212
212
  appendFileSync(outputFile, `${key}<<${delimiter}\n${value}\n${delimiter}\n`);
213
213
  }
214
214
  /**
215
- * CI release: either auto-publish or create a version PR.
216
- * Designed for merge-to-main workflows.
215
+ * CI release: either create a version PR (bump files present) or publish unpublished
216
+ * packages (no bump files — i.e. a version PR was just merged). Pass `autoPublish` to
217
+ * collapse both steps into a single push-to-main, or `assertMode` to refuse running
218
+ * when the detected state doesn't match expectations (used by split-job workflows).
217
219
  */
218
220
  async function ciReleaseCommand(rootDir, opts) {
219
221
  const config = await loadConfig(rootDir);
@@ -225,33 +227,38 @@ async function ciReleaseCommand(rootDir, opts) {
225
227
  for (const err of releaseParseErrors) log.error(err);
226
228
  throw new Error("Bump file parse errors must be fixed before releasing.");
227
229
  }
228
- if (bumpFiles.length === 0) {
229
- log.info("No pending bump files checking for unpublished packages...");
230
- const recoveredBumpFiles = recoverDeletedBumpFiles(rootDir);
231
- const { publishCommand } = await import("./publish-B9xzQREV.mjs");
232
- await publishCommand(rootDir, {
233
- tag: opts.tag,
234
- recoveredBumpFiles
235
- });
236
- return;
237
- }
238
- const plan = assembleReleasePlan(bumpFiles, packages, depGraph, config);
239
- if (plan.releases.length === 0) {
240
- log.info("Bump files found but no packages would be released — checking for unpublished packages...");
230
+ const plan = bumpFiles.length > 0 ? assembleReleasePlan(bumpFiles, packages, depGraph, config) : null;
231
+ const detectedMode = plan && plan.releases.length > 0 ? "version-pr" : "publish";
232
+ if (opts.assertMode && opts.assertMode !== detectedMode) throw new Error(`Expected mode "${opts.assertMode}" but detected "${detectedMode}". Either remove --expect-mode, or gate this step on the output of "bumpy ci plan".`);
233
+ if (detectedMode === "publish") {
234
+ 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
+ log.info(msg);
241
236
  const recoveredBumpFiles = recoverDeletedBumpFiles(rootDir);
242
- const { publishCommand } = await import("./publish-B9xzQREV.mjs");
237
+ const { publishCommand } = await import("./publish-BDA1bOZ7.mjs");
243
238
  await publishCommand(rootDir, {
244
239
  tag: opts.tag,
245
240
  recoveredBumpFiles
246
241
  });
247
242
  return;
248
243
  }
249
- if (opts.mode === "auto-publish") await autoPublish(rootDir, config, plan, opts.tag);
244
+ if (opts.autoPublish) await autoPublish(rootDir, config, plan, opts.tag);
250
245
  else await createVersionPr(rootDir, plan, config, new Map([...packages.values()].map((p) => [p.name, p.relativeDir])), opts.branch);
251
246
  }
247
+ /**
248
+ * "Auto-publish" mode: skip the Version Packages PR and ship version+publish in one run.
249
+ *
250
+ * The only thing forfeited vs. the default flow is the preview/review gate on version
251
+ * bumps. Credentials are NOT a differentiator — a single-job non-auto-publish workflow
252
+ * also carries both PR-write and publish creds, just split across two runs. The real
253
+ * credential separation comes from the split-job pattern, which is orthogonal to (and
254
+ * incompatible with) this flag, since this collapses both paths into one execution.
255
+ *
256
+ * That incompatibility is also why --auto-publish and --expect-mode are mutually exclusive:
257
+ * --expect-mode is for split-job workflows where each job runs exactly one path.
258
+ */
252
259
  async function autoPublish(rootDir, config, plan, tag) {
253
260
  log.step("Running bumpy version...");
254
- const { versionCommand } = await import("./version-Bxin6aQC.mjs");
261
+ const { versionCommand } = await import("./version-Cm0nRAFF.mjs");
255
262
  await versionCommand(rootDir);
256
263
  log.step("Committing version changes...");
257
264
  runArgs([
@@ -280,7 +287,7 @@ async function autoPublish(rootDir, config, plan, tag) {
280
287
  ], { cwd: rootDir });
281
288
  }
282
289
  log.step("Running bumpy publish...");
283
- const { publishCommand } = await import("./publish-B9xzQREV.mjs");
290
+ const { publishCommand } = await import("./publish-BDA1bOZ7.mjs");
284
291
  await publishCommand(rootDir, { tag });
285
292
  }
286
293
  /**
@@ -354,7 +361,7 @@ async function createVersionPr(rootDir, plan, config, packageDirs, branchName) {
354
361
  branch
355
362
  ], { cwd: rootDir });
356
363
  log.step("Running bumpy version...");
357
- const { versionCommand } = await import("./version-Bxin6aQC.mjs");
364
+ const { versionCommand } = await import("./version-Cm0nRAFF.mjs");
358
365
  await versionCommand(rootDir);
359
366
  runArgs([
360
367
  "git",
@@ -1,5 +1,5 @@
1
1
  import { n as log, r as require_picocolors, s as __toESM } from "./logger-BgksGFuf.mjs";
2
- import { t as detectPackageManager } from "./package-manager-BQPwXwu5.mjs";
2
+ import { n as detectPackageManager } from "./package-manager-Db_vTztt.mjs";
3
3
  import { s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
4
4
  import { a as fe, c as ot, i as _t, n as O, o as gt, r as Ot, s as mt, t as unwrap, u as wt } from "./clack-W95rXis0.mjs";
5
5
  //#region src/commands/ci-setup.ts
package/dist/cli.mjs CHANGED
@@ -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-Cs6amsw5.mjs");
28
+ const { initCommand } = await import("./init-BCkm6Nfa.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-B_1I_sen.mjs");
34
+ const { addCommand } = await import("./add-DQA1TVHA.mjs");
35
35
  await addCommand(rootDir, {
36
36
  packages: flags.packages,
37
37
  message: flags.message,
@@ -43,7 +43,7 @@ async function main() {
43
43
  }
44
44
  case "status": {
45
45
  const rootDir = await findRoot();
46
- const { statusCommand } = await import("./status-DeMcLxiM.mjs");
46
+ const { statusCommand } = await import("./status-BbsDr6t7.mjs");
47
47
  await statusCommand(rootDir, {
48
48
  json: flags.json === true,
49
49
  packagesOnly: flags.packages === true,
@@ -55,13 +55,13 @@ async function main() {
55
55
  }
56
56
  case "version": {
57
57
  const rootDir = await findRoot();
58
- const { versionCommand } = await import("./version-Bxin6aQC.mjs");
58
+ const { versionCommand } = await import("./version-Cm0nRAFF.mjs");
59
59
  await versionCommand(rootDir, { commit: flags.commit === true });
60
60
  break;
61
61
  }
62
62
  case "generate": {
63
63
  const rootDir = await findRoot();
64
- const { generateCommand } = await import("./generate-BlZIe3aC.mjs");
64
+ const { generateCommand } = await import("./generate-CvCvUaRV.mjs");
65
65
  await generateCommand(rootDir, {
66
66
  from: flags.from,
67
67
  dryRun: flags["dry-run"] === true,
@@ -71,7 +71,7 @@ async function main() {
71
71
  }
72
72
  case "check": {
73
73
  const rootDir = await findRoot();
74
- const { checkCommand } = await import("./check-pbyTB4KC.mjs").then((n) => n.t);
74
+ const { checkCommand } = await import("./check-CsF0zh8r.mjs").then((n) => n.t);
75
75
  const hookValue = flags.hook;
76
76
  if (hookValue && hookValue !== "pre-commit" && hookValue !== "pre-push") {
77
77
  log.error(`Invalid --hook value "${hookValue}". Expected "pre-commit" or "pre-push".`);
@@ -89,24 +89,35 @@ async function main() {
89
89
  const subcommand = args[1];
90
90
  const ciFlags = parseFlags(args.slice(2));
91
91
  if (subcommand === "check") {
92
- const { ciCheckCommand } = await import("./ci-B4vNuXEb.mjs");
92
+ const { ciCheckCommand } = await import("./ci-DBZW9k4S.mjs");
93
93
  await ciCheckCommand(rootDir, {
94
94
  comment: ciFlags.comment !== void 0 ? ciFlags.comment === true : void 0,
95
95
  strict: ciFlags.strict === true,
96
96
  noFail: ciFlags["no-fail"] === true
97
97
  });
98
98
  } else if (subcommand === "plan") {
99
- const { ciPlanCommand } = await import("./ci-B4vNuXEb.mjs");
99
+ const { ciPlanCommand } = await import("./ci-DBZW9k4S.mjs");
100
100
  await ciPlanCommand(rootDir);
101
101
  } else if (subcommand === "release") {
102
- const { ciReleaseCommand } = await import("./ci-B4vNuXEb.mjs");
102
+ const { ciReleaseCommand } = await import("./ci-DBZW9k4S.mjs");
103
+ const expectModeFlag = ciFlags["expect-mode"];
104
+ const autoPublishFlag = ciFlags["auto-publish"] === true;
105
+ if (expectModeFlag !== void 0 && expectModeFlag !== "version-pr" && expectModeFlag !== "publish") {
106
+ log.error(`Invalid --expect-mode value: "${expectModeFlag}". Must be "version-pr" or "publish".`);
107
+ process.exit(1);
108
+ }
109
+ if (expectModeFlag !== void 0 && autoPublishFlag) {
110
+ log.error("--expect-mode and --auto-publish cannot be used together.");
111
+ process.exit(1);
112
+ }
103
113
  await ciReleaseCommand(rootDir, {
104
- mode: ciFlags["auto-publish"] === true ? "auto-publish" : "version-pr",
114
+ autoPublish: autoPublishFlag,
115
+ assertMode: expectModeFlag,
105
116
  tag: ciFlags.tag,
106
117
  branch: ciFlags.branch
107
118
  });
108
119
  } else if (subcommand === "setup") {
109
- const { ciSetupCommand } = await import("./ci-setup-DKjyiF2-.mjs");
120
+ const { ciSetupCommand } = await import("./ci-setup-CvA2CCmQ.mjs");
110
121
  await ciSetupCommand(rootDir);
111
122
  } else {
112
123
  log.error(`Unknown ci subcommand: ${subcommand}. Use "ci check", "ci plan", "ci release", or "ci setup".`);
@@ -116,7 +127,7 @@ async function main() {
116
127
  }
117
128
  case "publish": {
118
129
  const rootDir = await findRoot();
119
- const { publishCommand } = await import("./publish-B9xzQREV.mjs");
130
+ const { publishCommand } = await import("./publish-BDA1bOZ7.mjs");
120
131
  await publishCommand(rootDir, {
121
132
  dryRun: flags["dry-run"] === true,
122
133
  tag: flags.tag,
@@ -140,7 +151,7 @@ async function main() {
140
151
  }
141
152
  case "--version":
142
153
  case "-v":
143
- console.log(`bumpy 1.10.2`);
154
+ console.log(`bumpy 1.12.0`);
144
155
  break;
145
156
  case "help":
146
157
  case "--help":
@@ -160,7 +171,7 @@ async function main() {
160
171
  }
161
172
  function printHelp() {
162
173
  console.log(`
163
- ${colorize(`🐸 bumpy v1.10.2`, "bold")} - Modern monorepo versioning
174
+ ${colorize(`🐸 bumpy v1.12.0`, "bold")} - Modern monorepo versioning
164
175
 
165
176
  Usage: bumpy <command> [options]
166
177
 
@@ -213,6 +224,7 @@ function printHelp() {
213
224
  --no-fail Warn only, never exit 1
214
225
 
215
226
  CI release options:
227
+ --expect-mode <mode> Assert detected mode: "version-pr" or "publish" (errors if mismatched)
216
228
  --auto-publish Version + publish directly (default: create version PR)
217
229
  --tag <tag> npm dist-tag for auto-publish
218
230
  --branch <name> Branch name for version PR (default: bumpy/version-packages)
@@ -1,9 +1,9 @@
1
1
  import { n as log, t as colorize } from "./logger-BgksGFuf.mjs";
2
2
  import { t as ensureDir } from "./fs-CBXKZhoU.mjs";
3
3
  import { a as loadConfig, r as getBumpyDir } from "./config-D_4GYDJi.mjs";
4
- import { a as writeBumpFile, o as discoverPackages } from "./bump-file-B9DpXK5X.mjs";
4
+ import { a as writeBumpFile, o as discoverPackages } from "./bump-file-B7hmXZlB.mjs";
5
5
  import { s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
6
- import { n as getBranchCommits, o as getFilesChangedInCommit } from "./git-JGLQtk-M.mjs";
6
+ import { r as getBranchCommits, s as getFilesChangedInCommit } from "./git-DJJ64SW9.mjs";
7
7
  import { n as slugify, t as randomName } from "./names-COooXAFg.mjs";
8
8
  import { relative } from "node:path";
9
9
  //#region src/commands/generate.ts
@@ -141,8 +141,11 @@ function tagExists(tag, opts) {
141
141
  tag
142
142
  ], opts) === tag;
143
143
  }
144
- /** Get files changed on this branch compared to a base branch */
145
- function getChangedFiles(rootDir, baseBranch) {
144
+ /**
145
+ * Resolve the ref to use as the comparison base for "this branch vs main".
146
+ * Prefers the merge-base, falls back to `origin/<baseBranch>`.
147
+ */
148
+ function getBaseCompareRef(rootDir, baseBranch) {
146
149
  if (!tryRunArgs([
147
150
  "git",
148
151
  "rev-parse",
@@ -155,20 +158,35 @@ function getChangedFiles(rootDir, baseBranch) {
155
158
  baseBranch,
156
159
  "--depth=1"
157
160
  ], { cwd: rootDir });
161
+ return tryRunArgs([
162
+ "git",
163
+ "merge-base",
164
+ "HEAD",
165
+ `origin/${baseBranch}`
166
+ ], { cwd: rootDir }) || `origin/${baseBranch}`;
167
+ }
168
+ /** Get files changed on this branch compared to a base branch */
169
+ function getChangedFiles(rootDir, baseBranch) {
158
170
  const diff = tryRunArgs([
159
171
  "git",
160
172
  "diff",
161
173
  "--name-only",
162
- tryRunArgs([
163
- "git",
164
- "merge-base",
165
- "HEAD",
166
- `origin/${baseBranch}`
167
- ], { cwd: rootDir }) || `origin/${baseBranch}`
174
+ getBaseCompareRef(rootDir, baseBranch)
168
175
  ], { cwd: rootDir });
169
176
  if (!diff) return [];
170
177
  return diff.split("\n").filter(Boolean);
171
178
  }
179
+ /**
180
+ * Read a file's contents at a given git ref.
181
+ * Returns null if the file does not exist at that ref.
182
+ */
183
+ function readFileAtRef(rootDir, ref, path) {
184
+ return tryRunArgs([
185
+ "git",
186
+ "show",
187
+ `${ref}:${path}`
188
+ ], { cwd: rootDir });
189
+ }
172
190
  /** Get commits on the current branch since it diverged from baseBranch */
173
191
  function getBranchCommits(rootDir, baseBranch) {
174
192
  if (!tryRunArgs([
@@ -242,4 +260,4 @@ function getFileStatuses(dir, opts) {
242
260
  return statuses;
243
261
  }
244
262
  //#endregion
245
- export { getFileStatuses as a, pushWithTags as c, getCurrentBranch as i, tagExists as l, getBranchCommits as n, getFilesChangedInCommit as o, getChangedFiles as r, hasUncommittedChanges as s, createTag as t, withGitToken as u };
263
+ export { getCurrentBranch as a, hasUncommittedChanges as c, tagExists as d, withGitToken as f, getChangedFiles as i, pushWithTags as l, getBaseCompareRef as n, getFileStatuses as o, getBranchCommits as r, getFilesChangedInCommit as s, createTag as t, readFileAtRef as u };
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { a as DEP_TYPES, c as maxBump, i as DEFAULT_PUBLISH_CONFIG, l as normalizeCascadeConfig, n as DEFAULT_BUMP_RULES, o as bumpLevel, r as DEFAULT_CONFIG, s as hasCascade, t as BUMP_LEVELS } from "./types-Bkh-igOJ.mjs";
2
2
  import { a as loadConfig, n as findRoot, r as getBumpyDir, s as matchGlob } from "./config-D_4GYDJi.mjs";
3
- import { a as writeBumpFile, n as parseBumpFile, o as discoverPackages, r as readBumpFiles } from "./bump-file-B9DpXK5X.mjs";
3
+ import { a as writeBumpFile, n as parseBumpFile, o as discoverPackages, r as readBumpFiles } from "./bump-file-B7hmXZlB.mjs";
4
4
  import { a as DependencyGraph, i as stripProtocol, n as bumpVersion, r as satisfies, t as assembleReleasePlan } from "./release-plan-mK7iGeGq.mjs";
5
5
  import { a as prependToChangelog, i as loadFormatter, n as generateChangelogEntry, t as defaultFormatter } from "./changelog-CbaET5V6.mjs";
6
6
  import { t as applyReleasePlan } from "./apply-release-plan-DD2R7SL2.mjs";
7
- import { t as publishPackages } from "./publish-pipeline-BRnqVylg.mjs";
7
+ import { t as publishPackages } from "./publish-pipeline-DSj14dW6.mjs";
8
8
  export { BUMP_LEVELS, DEFAULT_BUMP_RULES, DEFAULT_CONFIG, DEFAULT_PUBLISH_CONFIG, DEP_TYPES, DependencyGraph, applyReleasePlan, assembleReleasePlan, bumpLevel, bumpVersion, defaultFormatter, discoverPackages, findRoot, generateChangelogEntry, getBumpyDir, hasCascade, loadConfig, loadFormatter, matchGlob, maxBump, normalizeCascadeConfig, parseBumpFile, prependToChangelog, publishPackages, readBumpFiles, satisfies, stripProtocol, writeBumpFile };
@@ -1,6 +1,6 @@
1
1
  import { n as log, r as require_picocolors, s as __toESM } from "./logger-BgksGFuf.mjs";
2
2
  import { a as readJson, d as writeJson, f as writeText, i as listFiles, n as exists, s as readText, t as ensureDir } from "./fs-CBXKZhoU.mjs";
3
- import { t as detectPackageManager } from "./package-manager-BQPwXwu5.mjs";
3
+ import { n as detectPackageManager } from "./package-manager-Db_vTztt.mjs";
4
4
  import { t as run } from "./shell-C8KgKnMQ.mjs";
5
5
  import { c as ot, o as gt, s as mt, t as unwrap } from "./clack-W95rXis0.mjs";
6
6
  import { resolve } from "node:path";
@@ -2072,38 +2072,92 @@ async function getWorkspaceGlobs(rootDir, pm) {
2072
2072
  } catch {}
2073
2073
  return [];
2074
2074
  }
2075
- /** Load catalog definitions from pnpm-workspace.yaml or root package.json */
2076
- async function loadCatalogs(rootDir, pm) {
2075
+ /**
2076
+ * Files that may contain catalog definitions, in the order they're applied.
2077
+ * Later entries override earlier ones (matching loadCatalogs behavior).
2078
+ */
2079
+ const CATALOG_FILES = ["pnpm-workspace.yaml", "package.json"];
2080
+ /**
2081
+ * Normalize a catalog name to its canonical form.
2082
+ * pnpm/bun treat "default" and the unnamed top-level catalog interchangeably,
2083
+ * so we store and look up the default catalog under "" regardless of which alias
2084
+ * the user wrote.
2085
+ */
2086
+ function normalizeCatalogName(name) {
2087
+ return name === "default" ? "" : name;
2088
+ }
2089
+ /** Parse catalog definitions from the raw contents of pnpm-workspace.yaml and root package.json */
2090
+ function parseCatalogs(pnpmWorkspaceYaml, rootPackageJson) {
2077
2091
  const catalogs = /* @__PURE__ */ new Map();
2078
- if (pm === "pnpm") {
2079
- const wsFile = resolve(rootDir, "pnpm-workspace.yaml");
2080
- if (await exists(wsFile)) {
2081
- const content = await readText(wsFile);
2082
- const parsed = jsYaml.load(content);
2083
- if (parsed?.catalog) catalogs.set("", parsed.catalog);
2084
- if (parsed?.catalogs) for (const [name, deps] of Object.entries(parsed.catalogs)) catalogs.set(name, deps);
2085
- }
2086
- }
2087
- try {
2088
- const pkg = await readJson(resolve(rootDir, "package.json"));
2092
+ const addNamed = (raw) => {
2093
+ for (const [name, deps] of Object.entries(raw)) catalogs.set(normalizeCatalogName(name), deps);
2094
+ };
2095
+ if (pnpmWorkspaceYaml) try {
2096
+ const parsed = jsYaml.load(pnpmWorkspaceYaml);
2097
+ if (parsed?.catalog) catalogs.set("", parsed.catalog);
2098
+ if (parsed?.catalogs) addNamed(parsed.catalogs);
2099
+ } catch {}
2100
+ if (rootPackageJson) try {
2101
+ const pkg = JSON.parse(rootPackageJson);
2089
2102
  if (pkg.catalog && typeof pkg.catalog === "object") catalogs.set("", pkg.catalog);
2090
- if (pkg.catalogs && typeof pkg.catalogs === "object") for (const [name, deps] of Object.entries(pkg.catalogs)) catalogs.set(name, deps);
2103
+ if (pkg.catalogs && typeof pkg.catalogs === "object") addNamed(pkg.catalogs);
2091
2104
  const workspaces = pkg.workspaces;
2092
2105
  if (workspaces && typeof workspaces === "object" && !Array.isArray(workspaces)) {
2093
2106
  const ws = workspaces;
2094
2107
  if (ws.catalog && typeof ws.catalog === "object") catalogs.set("", ws.catalog);
2095
- if (ws.catalogs && typeof ws.catalogs === "object") for (const [name, deps] of Object.entries(ws.catalogs)) catalogs.set(name, deps);
2108
+ if (ws.catalogs && typeof ws.catalogs === "object") addNamed(ws.catalogs);
2096
2109
  }
2097
2110
  } catch {}
2098
2111
  return catalogs;
2099
2112
  }
2113
+ /** Load catalog definitions from pnpm-workspace.yaml or root package.json */
2114
+ async function loadCatalogs(rootDir, pm) {
2115
+ let pnpmYaml = null;
2116
+ if (pm === "pnpm") {
2117
+ const wsFile = resolve(rootDir, "pnpm-workspace.yaml");
2118
+ if (await exists(wsFile)) pnpmYaml = await readText(wsFile);
2119
+ }
2120
+ let pkgJsonText = null;
2121
+ const pkgJsonPath = resolve(rootDir, "package.json");
2122
+ if (await exists(pkgJsonPath)) pkgJsonText = await readText(pkgJsonPath);
2123
+ return parseCatalogs(pnpmYaml, pkgJsonText);
2124
+ }
2125
+ /** Extract the catalog name from a `catalog:` / `catalog:<name>` range, normalizing the default alias */
2126
+ function catalogNameFromRange(range) {
2127
+ return normalizeCatalogName(range.slice(8).trim());
2128
+ }
2100
2129
  /** Resolve a specific dependency's catalog: reference */
2101
2130
  function resolveCatalogDep(depName, range, catalogs) {
2102
2131
  if (!range.startsWith("catalog:")) return null;
2103
- const catalogName = range.slice(8).trim() || "";
2104
- const catalog = catalogs.get(catalogName);
2132
+ const catalog = catalogs.get(catalogNameFromRange(range));
2105
2133
  if (!catalog) return null;
2106
2134
  return catalog[depName] ?? null;
2107
2135
  }
2136
+ /**
2137
+ * Diff two catalog states and return the set of (catalogName → changed depNames).
2138
+ * Includes added, removed, and version-changed entries.
2139
+ */
2140
+ function diffCatalogMaps(before, after) {
2141
+ const changes = /* @__PURE__ */ new Map();
2142
+ const catalogNames = new Set([...before.keys(), ...after.keys()]);
2143
+ for (const catalogName of catalogNames) {
2144
+ const beforeDeps = before.get(catalogName) ?? {};
2145
+ const afterDeps = after.get(catalogName) ?? {};
2146
+ const depNames = new Set([...Object.keys(beforeDeps), ...Object.keys(afterDeps)]);
2147
+ const changedDeps = /* @__PURE__ */ new Set();
2148
+ for (const depName of depNames) if (beforeDeps[depName] !== afterDeps[depName]) changedDeps.add(depName);
2149
+ if (changedDeps.size > 0) changes.set(catalogName, changedDeps);
2150
+ }
2151
+ return changes;
2152
+ }
2153
+ /**
2154
+ * Given a set of catalog entries that have changed, return the set of catalog
2155
+ * references (e.g. "catalog:" or "catalog:testing") that affect those entries.
2156
+ * Used to match package.json dep ranges against changed catalog entries.
2157
+ */
2158
+ function isCatalogRefAffected(range, depName, catalogChanges) {
2159
+ if (!range.startsWith("catalog:")) return false;
2160
+ return catalogChanges.get(catalogNameFromRange(range))?.has(depName) ?? false;
2161
+ }
2108
2162
  //#endregion
2109
- export { jsYaml as i, detectWorkspaces as n, resolveCatalogDep as r, detectPackageManager as t };
2163
+ export { isCatalogRefAffected as a, jsYaml as c, diffCatalogMaps as i, detectPackageManager as n, parseCatalogs as o, detectWorkspaces as r, resolveCatalogDep as s, CATALOG_FILES as t };
@@ -1,13 +1,13 @@
1
1
  import { n as log, o as __require, t as colorize } from "./logger-BgksGFuf.mjs";
2
2
  import { a as loadConfig } from "./config-D_4GYDJi.mjs";
3
- import { n as detectWorkspaces } from "./package-manager-BQPwXwu5.mjs";
4
- import { s as discoverWorkspace } from "./bump-file-B9DpXK5X.mjs";
3
+ import { r as detectWorkspaces } from "./package-manager-Db_vTztt.mjs";
4
+ import { s as discoverWorkspace } from "./bump-file-B7hmXZlB.mjs";
5
5
  import { a as DependencyGraph } from "./release-plan-mK7iGeGq.mjs";
6
6
  import { r as runArgsAsync, s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
7
7
  import { i as loadFormatter, n as generateChangelogEntry } from "./changelog-CbaET5V6.mjs";
8
- import { c as pushWithTags, s as hasUncommittedChanges } from "./git-JGLQtk-M.mjs";
9
- import { t as publishPackages } from "./publish-pipeline-BRnqVylg.mjs";
10
- import { CI_PLAN_CACHE_PATH } from "./ci-B4vNuXEb.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-DBZW9k4S.mjs";
11
11
  //#region src/core/github-release.ts
12
12
  /** Get the current HEAD commit SHA */
13
13
  function getHeadSha(rootDir) {
@@ -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
- import { r as resolveCatalogDep } from "./package-manager-BQPwXwu5.mjs";
3
+ import { s as resolveCatalogDep } from "./package-manager-Db_vTztt.mjs";
4
4
  import { i as stripProtocol } from "./release-plan-mK7iGeGq.mjs";
5
5
  import { i as runAsync, o as sq, r as runArgsAsync, s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
6
- import { l as tagExists, t as createTag } from "./git-JGLQtk-M.mjs";
6
+ import { d as tagExists, t as createTag } from "./git-DJJ64SW9.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";
@@ -1,8 +1,8 @@
1
1
  import { n as log, t as colorize } from "./logger-BgksGFuf.mjs";
2
2
  import { a as loadConfig } from "./config-D_4GYDJi.mjs";
3
- import { o as discoverPackages, r as readBumpFiles, t as filterBranchBumpFiles } from "./bump-file-B9DpXK5X.mjs";
3
+ import { o as discoverPackages, r as readBumpFiles, t as filterBranchBumpFiles } from "./bump-file-B7hmXZlB.mjs";
4
4
  import { a as DependencyGraph, t as assembleReleasePlan } from "./release-plan-mK7iGeGq.mjs";
5
- import { i as getCurrentBranch, r as getChangedFiles } from "./git-JGLQtk-M.mjs";
5
+ import { a as getCurrentBranch, i as getChangedFiles } from "./git-DJJ64SW9.mjs";
6
6
  //#region src/commands/status.ts
7
7
  async function statusCommand(rootDir, opts) {
8
8
  const config = await loadConfig(rootDir);
@@ -1,7 +1,7 @@
1
1
  import { n as log, t as colorize } from "./logger-BgksGFuf.mjs";
2
2
  import { a as loadConfig } from "./config-D_4GYDJi.mjs";
3
- import { n as detectWorkspaces } from "./package-manager-BQPwXwu5.mjs";
4
- import { o as discoverPackages, r as readBumpFiles } from "./bump-file-B9DpXK5X.mjs";
3
+ import { r as detectWorkspaces } from "./package-manager-Db_vTztt.mjs";
4
+ import { o as discoverPackages, r as readBumpFiles } from "./bump-file-B7hmXZlB.mjs";
5
5
  import { a as DependencyGraph, t as assembleReleasePlan } from "./release-plan-mK7iGeGq.mjs";
6
6
  import { n as runArgs, s as tryRunArgs } from "./shell-C8KgKnMQ.mjs";
7
7
  import { t as applyReleasePlan } from "./apply-release-plan-DD2R7SL2.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@varlock/bumpy",
3
- "version": "1.10.2",
3
+ "version": "1.12.0",
4
4
  "description": "Modern monorepo versioning and changelog tool",
5
5
  "keywords": [
6
6
  "bump",