@varlock/bumpy 0.0.2 → 1.1.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 (43) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/config-schema.json +327 -0
  3. package/dist/add-BmNL5VwL.mjs +323 -0
  4. package/dist/{ai-CQhUyHAG.mjs → ai-sMYUf3lP.mjs} +21 -4
  5. package/dist/{apply-release-plan-D6TSrcwX.mjs → apply-release-plan-0kH62jhu.mjs} +35 -26
  6. package/dist/bump-file-DVqR3k67.mjs +157 -0
  7. package/dist/{changelog-github-Du62krXi.mjs → changelog-github-DkACMj0j.mjs} +23 -21
  8. package/dist/check-BjWF6SJm.mjs +65 -0
  9. package/dist/{ci-D6LQbR38.mjs → ci-DY58ugIi.mjs} +138 -91
  10. package/dist/{ci-setup-C6FlOfW5.mjs → ci-setup-BQwktQEe.mjs} +3 -3
  11. package/dist/cli.mjs +36 -41
  12. package/dist/commit-message-BwsowSds.mjs +23 -0
  13. package/dist/{config-BkwIEaQg.mjs → config-B-Qg3DZH.mjs} +30 -24
  14. package/dist/fs-DYR2XuFE.mjs +81 -0
  15. package/dist/generate-DX46X-rW.mjs +186 -0
  16. package/dist/{git-CGHVXXKw.mjs → git-YDedMddc.mjs} +54 -2
  17. package/dist/index.d.mts +68 -39
  18. package/dist/index.mjs +9 -9
  19. package/dist/init-DkTPs_WQ.mjs +196 -0
  20. package/dist/{names-Ck8cun7B.mjs → names-C-TuOPbd.mjs} +1 -1
  21. package/dist/{js-yaml-DpZfOoD4.mjs → package-manager-Clsmr-9r.mjs} +79 -1
  22. package/dist/picomatch-DMmqYjgq.mjs +1870 -0
  23. package/dist/{publish-D_7RqEYL.mjs → publish-CGB4TIKD.mjs} +26 -25
  24. package/dist/{publish-pipeline-ChnqW8nR.mjs → publish-pipeline-CXuqce1N.mjs} +24 -19
  25. package/dist/release-plan-JNir7bSM.mjs +264 -0
  26. package/dist/{semver-BTzYh8vc.mjs → semver-BJzWIuRz.mjs} +13 -3
  27. package/dist/{shell-Dj7JRD_q.mjs → shell-CY7OD48z.mjs} +20 -2
  28. package/dist/{status--Q8yAxQ4.mjs → status-EGYqULJg.mjs} +26 -22
  29. package/dist/{version-cAUkfYPx.mjs → version-BcfidiVX.mjs} +23 -22
  30. package/dist/{workspace-CxEKakDm.mjs → workspace-DWXlwcH4.mjs} +3 -3
  31. package/package.json +16 -1
  32. package/skills/add-change/SKILL.md +18 -14
  33. package/dist/add-BjyVIUlr.mjs +0 -175
  34. package/dist/changeset-UCZdSRDv.mjs +0 -108
  35. package/dist/check-jIwike9F.mjs +0 -51
  36. package/dist/fs-0AtnPUUe.mjs +0 -51
  37. package/dist/generate-Btrsn1qi.mjs +0 -177
  38. package/dist/init-B0q3wEQW.mjs +0 -22
  39. package/dist/migrate-CfQNwD0T.mjs +0 -121
  40. package/dist/package-manager-DcI5TdDE.mjs +0 -80
  41. package/dist/release-plan-BEzwApuK.mjs +0 -173
  42. /package/dist/{clack-CDRCHrC-.mjs → clack-C6bVkGxf.mjs} +0 -0
  43. /package/dist/{dep-graph-E-9-eQ2J.mjs → dep-graph-DiLeAhl9.mjs} +0 -0
@@ -1,11 +1,11 @@
1
1
  import { n as log, t as colorize } from "./logger-C2dEe5Su.mjs";
2
- import { a as loadConfig } from "./config-BkwIEaQg.mjs";
3
- import { n as detectWorkspaces } from "./package-manager-DcI5TdDE.mjs";
4
- import { n as discoverWorkspace } from "./workspace-CxEKakDm.mjs";
5
- import { t as DependencyGraph } from "./dep-graph-E-9-eQ2J.mjs";
6
- import { n as runArgsAsync, o as tryRunArgs } from "./shell-Dj7JRD_q.mjs";
7
- import { a as pushWithTags, i as listTags, r as hasUncommittedChanges } from "./git-CGHVXXKw.mjs";
8
- import { t as publishPackages } from "./publish-pipeline-ChnqW8nR.mjs";
2
+ import { a as loadConfig } from "./config-B-Qg3DZH.mjs";
3
+ import { n as detectWorkspaces } from "./package-manager-Clsmr-9r.mjs";
4
+ import { n as discoverWorkspace } from "./workspace-DWXlwcH4.mjs";
5
+ import { t as DependencyGraph } from "./dep-graph-DiLeAhl9.mjs";
6
+ import { r as runArgsAsync, s as tryRunArgs } from "./shell-CY7OD48z.mjs";
7
+ import { a as hasUncommittedChanges, o as listTags, s as pushWithTags } from "./git-YDedMddc.mjs";
8
+ import { t as publishPackages } from "./publish-pipeline-CXuqce1N.mjs";
9
9
  //#region src/core/github-release.ts
10
10
  /** Get the current HEAD commit SHA */
11
11
  function getHeadSha(rootDir) {
@@ -16,7 +16,7 @@ function getHeadSha(rootDir) {
16
16
  ], { cwd: rootDir });
17
17
  }
18
18
  /** Create individual GitHub releases for each published package */
19
- async function createIndividualReleases(releases, changesets, rootDir, opts = {}) {
19
+ async function createIndividualReleases(releases, bumpFiles, rootDir, opts = {}) {
20
20
  if (!isGhAvailable()) {
21
21
  log.dim(" gh CLI not found — skipping GitHub releases");
22
22
  return;
@@ -24,7 +24,7 @@ async function createIndividualReleases(releases, changesets, rootDir, opts = {}
24
24
  const headSha = getHeadSha(rootDir);
25
25
  for (const release of releases) {
26
26
  const tag = `${release.name}@${release.newVersion}`;
27
- const body = buildReleaseBody(release, changesets);
27
+ const body = buildReleaseBody(release, bumpFiles);
28
28
  const title = `${release.name} v${release.newVersion}`;
29
29
  if (opts.dryRun) {
30
30
  log.dim(` Would create GitHub release: ${title}`);
@@ -50,7 +50,7 @@ async function createIndividualReleases(releases, changesets, rootDir, opts = {}
50
50
  }
51
51
  }
52
52
  /** Create a single aggregated GitHub release for all published packages */
53
- async function createAggregateRelease(releases, changesets, rootDir, opts = {}) {
53
+ async function createAggregateRelease(releases, bumpFiles, rootDir, opts = {}) {
54
54
  if (!isGhAvailable()) {
55
55
  log.dim(" gh CLI not found — skipping GitHub release");
56
56
  return;
@@ -58,7 +58,7 @@ async function createAggregateRelease(releases, changesets, rootDir, opts = {})
58
58
  if (releases.length === 0) return;
59
59
  const date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
60
60
  const { tag, title } = resolveAggregateTagAndTitle(date, listTags(`release-${date}*`, { cwd: rootDir }), opts.title);
61
- const body = buildAggregateBody(releases, changesets);
61
+ const body = buildAggregateBody(releases, bumpFiles);
62
62
  if (opts.dryRun) {
63
63
  log.dim(` Would create aggregate GitHub release: ${title}`);
64
64
  log.dim(` Tag: ${tag}`);
@@ -88,16 +88,16 @@ async function createAggregateRelease(releases, changesets, rootDir, opts = {})
88
88
  log.warn(`Failed to create aggregate GitHub release: ${err instanceof Error ? err.message : err}`);
89
89
  }
90
90
  }
91
- function buildReleaseBody(release, changesets) {
91
+ function buildReleaseBody(release, bumpFiles) {
92
92
  const lines = [];
93
- const relevant = changesets.filter((cs) => release.changesets.includes(cs.id));
93
+ const relevant = bumpFiles.filter((bf) => release.bumpFiles.includes(bf.id));
94
94
  if (relevant.length > 0) {
95
- for (const cs of relevant) if (cs.summary) lines.push(`- ${cs.summary.split("\n")[0]}`);
95
+ for (const bf of relevant) if (bf.summary) lines.push(`- ${bf.summary.split("\n")[0]}`);
96
96
  }
97
97
  if (release.isDependencyBump && relevant.length === 0) lines.push("- Updated dependencies");
98
98
  return lines.join("\n") || "No changelog entries.";
99
99
  }
100
- function buildAggregateBody(releases, changesets) {
100
+ function buildAggregateBody(releases, bumpFiles) {
101
101
  const lines = [];
102
102
  const groups = [
103
103
  ["Major Changes", releases.filter((r) => r.type === "major")],
@@ -109,9 +109,9 @@ function buildAggregateBody(releases, changesets) {
109
109
  lines.push(`## ${heading}\n`);
110
110
  for (const release of group) {
111
111
  lines.push(`### ${release.name} v${release.newVersion}\n`);
112
- const relevant = changesets.filter((cs) => release.changesets.includes(cs.id));
112
+ const relevant = bumpFiles.filter((bf) => release.bumpFiles.includes(bf.id));
113
113
  if (relevant.length > 0) {
114
- for (const cs of relevant) if (cs.summary) lines.push(`- ${cs.summary.split("\n")[0]}`);
114
+ for (const bf of relevant) if (bf.summary) lines.push(`- ${bf.summary.split("\n")[0]}`);
115
115
  } else if (release.isDependencyBump) lines.push("- Updated dependencies");
116
116
  else if (release.isCascadeBump) lines.push("- Version bump via cascade rule");
117
117
  lines.push("");
@@ -148,7 +148,7 @@ async function publishCommand(rootDir, opts) {
148
148
  }
149
149
  let toPublish = await findUnpublishedPackages(packages, config);
150
150
  if (opts.filter) {
151
- const { matchGlob } = await import("./config-BkwIEaQg.mjs").then((n) => n.t);
151
+ const { matchGlob } = await import("./config-B-Qg3DZH.mjs").then((n) => n.t);
152
152
  const patterns = opts.filter.split(",").map((p) => p.trim());
153
153
  toPublish = toPublish.filter((r) => patterns.some((p) => matchGlob(r.name, p)));
154
154
  }
@@ -157,8 +157,9 @@ async function publishCommand(rootDir, opts) {
157
157
  return;
158
158
  }
159
159
  const releasePlan = {
160
- changesets: [],
161
- releases: toPublish
160
+ bumpFiles: [],
161
+ releases: toPublish,
162
+ warnings: []
162
163
  };
163
164
  if (opts.dryRun) log.bold("Dry run — would publish:");
164
165
  else log.bold("Publishing:");
@@ -168,7 +169,7 @@ async function publishCommand(rootDir, opts) {
168
169
  dryRun: opts.dryRun,
169
170
  tag: opts.tag
170
171
  }, catalogs, detectedPm);
171
- if (result.published.length > 0) log.success(`Published ${result.published.length} package(s)`);
172
+ if (result.published.length > 0) log.success(`🐸 Published ${result.published.length} package(s)`);
172
173
  if (result.skipped.length > 0) log.dim(`Skipped ${result.skipped.length}: ${result.skipped.map((s) => s.name).join(", ")}`);
173
174
  if (result.failed.length > 0) {
174
175
  log.error(`Failed ${result.failed.length}: ${result.failed.map((f) => `${f.name} (${f.error})`).join(", ")}`);
@@ -186,11 +187,11 @@ async function publishCommand(rootDir, opts) {
186
187
  const aggConfig = config.aggregateRelease;
187
188
  const isAggregate = aggConfig === true || typeof aggConfig === "object" && aggConfig.enabled;
188
189
  const aggTitle = typeof aggConfig === "object" ? aggConfig.title : void 0;
189
- if (isAggregate) await createAggregateRelease(publishedReleases, releasePlan.changesets, rootDir, {
190
+ if (isAggregate) await createAggregateRelease(publishedReleases, releasePlan.bumpFiles, rootDir, {
190
191
  dryRun: opts.dryRun,
191
192
  title: aggTitle
192
193
  });
193
- else await createIndividualReleases(publishedReleases, releasePlan.changesets, rootDir, { dryRun: opts.dryRun });
194
+ else await createIndividualReleases(publishedReleases, releasePlan.bumpFiles, rootDir, { dryRun: opts.dryRun });
194
195
  }
195
196
  }
196
197
  /**
@@ -211,7 +212,7 @@ async function findUnpublishedPackages(packages, _config) {
211
212
  type: "patch",
212
213
  oldVersion: pkg.version,
213
214
  newVersion: pkg.version,
214
- changesets: [],
215
+ bumpFiles: [],
215
216
  isDependencyBump: false,
216
217
  isCascadeBump: false
217
218
  });
@@ -219,7 +220,7 @@ async function findUnpublishedPackages(packages, _config) {
219
220
  return unpublished;
220
221
  }
221
222
  async function checkIfPublished(name, version, pkgConfig) {
222
- const { runAsync, runArgsAsync, tryRunArgs } = await import("./shell-Dj7JRD_q.mjs").then((n) => n.i);
223
+ const { runAsync, runArgsAsync, tryRunArgs } = await import("./shell-CY7OD48z.mjs").then((n) => n.a);
223
224
  if (pkgConfig?.checkPublished) try {
224
225
  return (await runAsync(pkgConfig.checkPublished)).trim() === version;
225
226
  } catch {
@@ -1,9 +1,9 @@
1
1
  import { n as log, t as colorize } from "./logger-C2dEe5Su.mjs";
2
- import { a as readJson, c as writeJson } from "./fs-0AtnPUUe.mjs";
3
- import { r as resolveCatalogDep } from "./package-manager-DcI5TdDE.mjs";
4
- import { a as sq, n as runArgsAsync, o as tryRunArgs, r as runAsync } from "./shell-Dj7JRD_q.mjs";
5
- import { r as stripProtocol } from "./semver-BTzYh8vc.mjs";
6
- import { o as tagExists, t as createTag } from "./git-CGHVXXKw.mjs";
2
+ import { a as readJson, l as updateJsonNestedField } from "./fs-DYR2XuFE.mjs";
3
+ import { r as resolveCatalogDep } from "./package-manager-Clsmr-9r.mjs";
4
+ import { i as runAsync, o as sq, r as runArgsAsync, s as tryRunArgs } from "./shell-CY7OD48z.mjs";
5
+ import { r as stripProtocol } from "./semver-BJzWIuRz.mjs";
6
+ import { c as tagExists, t as createTag } from "./git-YDedMddc.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";
@@ -160,7 +160,7 @@ async function packThenPublish(pkg, pkgConfig, config, packManager, opts) {
160
160
  log.dim(` Would publish with: ${publishArgs.join(" ")}`);
161
161
  return;
162
162
  }
163
- const tarball = parseTarballPath(await runArgsAsync(packArgs, { cwd: pkg.dir }), pkg.dir);
163
+ const tarball = parseTarballPath(await runArgsAsync(packArgs, { cwd: pkg.dir }), pkg.dir, packManager);
164
164
  try {
165
165
  const publishArgs = buildPublishArgs(pkg, pkgConfig, config, opts, tarball);
166
166
  log.dim(` Publishing: ${publishArgs.join(" ")}`);
@@ -179,14 +179,22 @@ async function npmPublishDirect(pkg, pkgConfig, config, opts) {
179
179
  }
180
180
  function getPackArgs(pm) {
181
181
  switch (pm) {
182
- case "pnpm": return ["pnpm", "pack"];
182
+ case "pnpm": return [
183
+ "pnpm",
184
+ "pack",
185
+ "--json"
186
+ ];
183
187
  case "bun": return [
184
188
  "bun",
185
189
  "pm",
186
190
  "pack"
187
191
  ];
188
192
  case "yarn": return ["yarn", "pack"];
189
- default: return ["npm", "pack"];
193
+ default: return [
194
+ "npm",
195
+ "pack",
196
+ "--json"
197
+ ];
190
198
  }
191
199
  }
192
200
  function buildPublishArgs(pkg, pkgConfig, config, opts, tarball) {
@@ -204,12 +212,14 @@ function buildPublishArgs(pkg, pkgConfig, config, opts, tarball) {
204
212
  }
205
213
  /**
206
214
  * Parse the tarball path from pack command output.
207
- * Each PM has different output formats:
208
- * npm/pnpm: tarball filename on the last line
209
- * bun: tarball filename mid-output, summary lines after
210
- * yarn: 'success Wrote tarball to "/path/to/foo.tgz".'
215
+ * npm/pnpm use --json for structured output; bun/yarn fall back to regex parsing.
211
216
  */
212
- function parseTarballPath(output, cwd) {
217
+ function parseTarballPath(output, cwd, pm) {
218
+ if (pm === "npm" || pm === "pnpm") try {
219
+ const parsed = JSON.parse(output);
220
+ const entry = Array.isArray(parsed) ? parsed[0] : parsed;
221
+ if (entry?.filename) return resolve(cwd, entry.filename);
222
+ } catch {}
213
223
  const tgzMatch = output.match(/(?:^|["'\s])([^\s"']*\.tgz)/m);
214
224
  if (tgzMatch) {
215
225
  const tarball = tgzMatch[1];
@@ -239,7 +249,6 @@ function createGitTag(release, rootDir, opts) {
239
249
  async function resolveProtocolsInPlace(pkg, packages, releasePlan, catalogs) {
240
250
  const pkgJsonPath = resolve(pkg.dir, "package.json");
241
251
  const pkgJson = await readJson(pkgJsonPath);
242
- let modified = false;
243
252
  const releaseMap = new Map(releasePlan.releases.map((r) => [r.name, r]));
244
253
  for (const depField of [
245
254
  "dependencies",
@@ -265,13 +274,9 @@ async function resolveProtocolsInPlace(pkg, packages, releasePlan, catalogs) {
265
274
  resolved = `${cleanRange === "*" ? "^" : cleanRange}${version}`;
266
275
  } else resolved = cleanRange;
267
276
  }
268
- if (resolved) {
269
- deps[depName] = resolved;
270
- modified = true;
271
- }
277
+ if (resolved) await updateJsonNestedField(pkgJsonPath, depField, depName, resolved);
272
278
  }
273
279
  }
274
- if (modified) await writeJson(pkgJsonPath, pkgJson);
275
280
  }
276
281
  //#endregion
277
282
  export { publishPackages as t };
@@ -0,0 +1,264 @@
1
+ import { h as maxBump, l as DEFAULT_BUMP_RULES, m as hasCascade, p as bumpLevel, s as matchGlob } from "./config-B-Qg3DZH.mjs";
2
+ import { n as satisfies, t as bumpVersion } from "./semver-BJzWIuRz.mjs";
3
+ //#region src/core/release-plan.ts
4
+ /**
5
+ * Build a release plan from pending bump files, the dependency graph, and config.
6
+ * This is the core algorithm of bumpy.
7
+ *
8
+ * The propagation loop runs three phases until stable:
9
+ * Phase A — fix out-of-range dependencies (always runs)
10
+ * Phase B — enforce fixed/linked group constraints
11
+ * Phase C — apply cascades and proactive propagation rules
12
+ */
13
+ function assembleReleasePlan(bumpFiles, packages, depGraph, config) {
14
+ if (bumpFiles.length === 0) return {
15
+ bumpFiles: [],
16
+ releases: [],
17
+ warnings: []
18
+ };
19
+ const planned = /* @__PURE__ */ new Map();
20
+ const warnings = [];
21
+ const cascadeOverrides = /* @__PURE__ */ new Map();
22
+ const suppressedPackages = /* @__PURE__ */ new Set();
23
+ for (const bf of bumpFiles) for (const release of bf.releases) {
24
+ if (!packages.has(release.name)) continue;
25
+ const bump = release.type;
26
+ if (bump === "none") {
27
+ suppressedPackages.add(release.name);
28
+ continue;
29
+ }
30
+ const existing = planned.get(release.name);
31
+ if (existing) {
32
+ existing.type = maxBump(existing.type, bump);
33
+ existing.bumpFiles.add(bf.id);
34
+ } else planned.set(release.name, {
35
+ type: bump,
36
+ suppressed: false,
37
+ isDependencyBump: false,
38
+ isCascadeBump: false,
39
+ bumpFiles: new Set([bf.id])
40
+ });
41
+ if (hasCascade(release)) {
42
+ if (!cascadeOverrides.has(release.name)) cascadeOverrides.set(release.name, /* @__PURE__ */ new Map());
43
+ const overrides = cascadeOverrides.get(release.name);
44
+ for (const [pattern, bumpType] of Object.entries(release.cascade)) {
45
+ const existing = overrides.get(pattern);
46
+ overrides.set(pattern, maxBump(existing, bumpType));
47
+ }
48
+ }
49
+ }
50
+ for (const name of suppressedPackages) if (!planned.has(name)) planned.set(name, {
51
+ type: "patch",
52
+ suppressed: true,
53
+ isDependencyBump: false,
54
+ isCascadeBump: false,
55
+ bumpFiles: /* @__PURE__ */ new Set()
56
+ });
57
+ let changed = true;
58
+ let iterations = 0;
59
+ const MAX_ITERATIONS = 100;
60
+ while (changed && iterations < MAX_ITERATIONS) {
61
+ changed = false;
62
+ iterations++;
63
+ for (const [pkgName, bump] of planned) {
64
+ if (bump.suppressed) continue;
65
+ const pkg = packages.get(pkgName);
66
+ const newVersion = bumpVersion(pkg.version, bump.type);
67
+ const dependents = depGraph.getDependents(pkgName);
68
+ for (const dep of dependents) {
69
+ if (dep.depType === "devDependencies") continue;
70
+ const currentVersion = pkg.version;
71
+ if (satisfies(newVersion, dep.versionRange, currentVersion)) continue;
72
+ let depBump;
73
+ if (dep.depType === "peerDependencies") depBump = bump.type;
74
+ else depBump = "patch";
75
+ if (planned.get(dep.name)?.suppressed) throw new Error(`Cannot suppress bump for '${dep.name}' (via 'none' in bump file) — '${pkgName}' is bumping to ${newVersion} which breaks the declared range '${dep.versionRange}'. Either widen the range or remove the 'none' entry.`);
76
+ if (dep.depType === "peerDependencies" && depBump !== "patch") {
77
+ let resolvedRange = dep.versionRange.replace(/^workspace:/, "");
78
+ if (resolvedRange === "^" || resolvedRange === "~") resolvedRange = `${resolvedRange}${pkg.version}`;
79
+ if (/^\^0(\.|$)/.test(resolvedRange)) warnings.push(`${dep.name} gets a ${depBump} bump because ${pkgName}@${newVersion} is out of range for its peer dep "${dep.versionRange}" (resolves to ${resolvedRange}). npm treats ^ on 0.x as minor-breaking. Consider using >=0.x ranges for pre-1.0 peer deps.`);
80
+ }
81
+ if (applyBump(planned, dep.name, depBump, true, false, bump.bumpFiles)) changed = true;
82
+ }
83
+ }
84
+ for (const group of config.fixed) {
85
+ let groupBump;
86
+ for (const nameOrGlob of group) for (const [name, bump] of planned) {
87
+ if (bump.suppressed) continue;
88
+ if (matchGlob(name, nameOrGlob)) groupBump = maxBump(groupBump, bump.type);
89
+ }
90
+ if (!groupBump) continue;
91
+ for (const nameOrGlob of group) for (const [name] of packages) {
92
+ if (!matchGlob(name, nameOrGlob)) continue;
93
+ const existing = planned.get(name);
94
+ if (existing && existing.suppressed) continue;
95
+ if (existing) {
96
+ const newType = maxBump(existing.type, groupBump);
97
+ if (newType !== existing.type) {
98
+ existing.type = newType;
99
+ changed = true;
100
+ }
101
+ } else {
102
+ planned.set(name, {
103
+ type: groupBump,
104
+ suppressed: false,
105
+ isDependencyBump: false,
106
+ isCascadeBump: false,
107
+ bumpFiles: /* @__PURE__ */ new Set()
108
+ });
109
+ changed = true;
110
+ }
111
+ }
112
+ }
113
+ for (const group of config.linked) {
114
+ let groupBump;
115
+ for (const nameOrGlob of group) for (const [name, bump] of planned) {
116
+ if (bump.suppressed) continue;
117
+ if (matchGlob(name, nameOrGlob)) groupBump = maxBump(groupBump, bump.type);
118
+ }
119
+ if (!groupBump) continue;
120
+ for (const nameOrGlob of group) for (const [name] of packages) {
121
+ if (!matchGlob(name, nameOrGlob)) continue;
122
+ const existing = planned.get(name);
123
+ if (!existing || existing.suppressed) continue;
124
+ const newType = maxBump(existing.type, groupBump);
125
+ if (newType !== existing.type) {
126
+ existing.type = newType;
127
+ changed = true;
128
+ }
129
+ }
130
+ }
131
+ if (config.updateInternalDependencies !== "out-of-range") for (const [pkgName, bump] of planned) {
132
+ if (bump.suppressed) continue;
133
+ if (config.updateInternalDependencies === "minor" && bumpLevel(bump.type) < bumpLevel("minor")) continue;
134
+ const bfOverrides = cascadeOverrides.get(pkgName);
135
+ if (bfOverrides) for (const [pattern, cascadeBumpType] of bfOverrides) for (const [targetName] of packages) {
136
+ if (!matchGlob(targetName, pattern)) continue;
137
+ if (planned.get(targetName)?.suppressed) continue;
138
+ if (applyBump(planned, targetName, cascadeBumpType, false, true, bump.bumpFiles)) changed = true;
139
+ }
140
+ const cascadeTo = packages.get(pkgName)?.bumpy?.cascadeTo;
141
+ if (cascadeTo) for (const [pattern, rule] of Object.entries(cascadeTo)) {
142
+ if (!shouldTrigger(bump.type, rule.trigger)) continue;
143
+ const cascadeBump = rule.bumpAs === "match" ? bump.type : rule.bumpAs;
144
+ for (const [targetName] of packages) {
145
+ if (!matchGlob(targetName, pattern)) continue;
146
+ if (planned.get(targetName)?.suppressed) continue;
147
+ if (applyBump(planned, targetName, cascadeBump, false, true, bump.bumpFiles)) changed = true;
148
+ }
149
+ }
150
+ const dependents = depGraph.getDependents(pkgName);
151
+ for (const dep of dependents) {
152
+ const rule = resolveRule(dep.name, dep.depType, packages, config);
153
+ if (!rule) continue;
154
+ if (!shouldTrigger(bump.type, rule.trigger)) continue;
155
+ if (planned.get(dep.name)?.suppressed) continue;
156
+ const depBump = rule.bumpAs === "match" ? bump.type : rule.bumpAs;
157
+ if (applyBump(planned, dep.name, depBump, true, false, bump.bumpFiles)) changed = true;
158
+ }
159
+ }
160
+ else for (const [pkgName, bump] of planned) {
161
+ if (bump.suppressed) continue;
162
+ const bfOverrides = cascadeOverrides.get(pkgName);
163
+ if (bfOverrides) for (const [pattern, cascadeBumpType] of bfOverrides) for (const [targetName] of packages) {
164
+ if (!matchGlob(targetName, pattern)) continue;
165
+ if (planned.get(targetName)?.suppressed) continue;
166
+ if (applyBump(planned, targetName, cascadeBumpType, false, true, bump.bumpFiles)) changed = true;
167
+ }
168
+ const cascadeTo = packages.get(pkgName)?.bumpy?.cascadeTo;
169
+ if (cascadeTo) for (const [pattern, rule] of Object.entries(cascadeTo)) {
170
+ if (!shouldTrigger(bump.type, rule.trigger)) continue;
171
+ const cascadeBump = rule.bumpAs === "match" ? bump.type : rule.bumpAs;
172
+ for (const [targetName] of packages) {
173
+ if (!matchGlob(targetName, pattern)) continue;
174
+ if (planned.get(targetName)?.suppressed) continue;
175
+ if (applyBump(planned, targetName, cascadeBump, false, true, bump.bumpFiles)) changed = true;
176
+ }
177
+ }
178
+ }
179
+ }
180
+ for (const [name, bump] of planned) {
181
+ if (!bump.suppressed) continue;
182
+ const pkg = packages.get(name);
183
+ for (const [depName, depBump] of planned) {
184
+ if (depBump.suppressed) continue;
185
+ const depPkg = packages.get(depName);
186
+ const newDepVersion = bumpVersion(depPkg.version, depBump.type);
187
+ for (const depType of [
188
+ "dependencies",
189
+ "peerDependencies",
190
+ "optionalDependencies"
191
+ ]) {
192
+ const range = pkg[depType]?.[depName];
193
+ if (!range) continue;
194
+ if (!satisfies(newDepVersion, range, depPkg.version)) throw new Error(`Cannot suppress bump for '${name}' (via 'none' in bump file) — '${depName}' is bumping to ${newDepVersion} which breaks the declared range '${range}'. Either widen the range or remove the 'none' entry.`);
195
+ }
196
+ }
197
+ }
198
+ const releases = [];
199
+ for (const [name, bump] of planned) {
200
+ if (bump.suppressed) continue;
201
+ const pkg = packages.get(name);
202
+ if (!pkg) continue;
203
+ const newVersion = bumpVersion(pkg.version, bump.type);
204
+ releases.push({
205
+ name,
206
+ type: bump.type,
207
+ oldVersion: pkg.version,
208
+ newVersion,
209
+ bumpFiles: [...bump.bumpFiles],
210
+ isDependencyBump: bump.isDependencyBump,
211
+ isCascadeBump: bump.isCascadeBump
212
+ });
213
+ }
214
+ releases.sort((a, b) => a.name.localeCompare(b.name));
215
+ for (const [name, pkg] of packages) for (const [depName, range] of Object.entries(pkg.peerDependencies)) if (range === "workspace:*" && packages.has(depName)) warnings.push(`${name} has peer dep "${depName}": "workspace:*" — this will be published as a fixed range which may not match your intent. Consider using "workspace:^" instead.`);
216
+ return {
217
+ bumpFiles,
218
+ releases,
219
+ warnings
220
+ };
221
+ }
222
+ /** Apply a bump to a package, upgrading if already planned. Returns true if anything changed. */
223
+ function applyBump(planned, name, type, isDependencyBump, isCascadeBump, sourceBumpFiles) {
224
+ const existing = planned.get(name);
225
+ if (existing) {
226
+ if (existing.suppressed) return false;
227
+ const newType = maxBump(existing.type, type);
228
+ if (newType === existing.type) return false;
229
+ existing.type = newType;
230
+ if (isDependencyBump) existing.isDependencyBump = true;
231
+ if (isCascadeBump) existing.isCascadeBump = true;
232
+ for (const bf of sourceBumpFiles) existing.bumpFiles.add(bf);
233
+ return true;
234
+ }
235
+ planned.set(name, {
236
+ type,
237
+ suppressed: false,
238
+ isDependencyBump,
239
+ isCascadeBump,
240
+ bumpFiles: new Set(sourceBumpFiles)
241
+ });
242
+ return true;
243
+ }
244
+ /** Check if a bump level meets the trigger threshold */
245
+ function shouldTrigger(bumpType, trigger) {
246
+ return bumpLevel(bumpType) >= bumpLevel(trigger);
247
+ }
248
+ /**
249
+ * Resolve the dependency bump rule for a specific dependent + dep type.
250
+ * Priority: per-package depType rules > global depType rules > defaults
251
+ * Returns false if the rule is disabled.
252
+ */
253
+ function resolveRule(dependentName, depType, packages, config) {
254
+ const dependent = packages.get(dependentName);
255
+ if (dependent?.bumpy?.dependencyBumpRules && depType in dependent.bumpy.dependencyBumpRules) return dependent.bumpy.dependencyBumpRules[depType];
256
+ if (depType in config.dependencyBumpRules) return config.dependencyBumpRules[depType];
257
+ const defaultRule = DEFAULT_BUMP_RULES[depType];
258
+ return defaultRule !== void 0 ? defaultRule : {
259
+ trigger: "patch",
260
+ bumpAs: "patch"
261
+ };
262
+ }
263
+ //#endregion
264
+ export { assembleReleasePlan as t };
@@ -1341,11 +1341,21 @@ function bumpVersion(version, type) {
1341
1341
  if (!result) throw new Error(`Failed to bump ${version} by ${type}`);
1342
1342
  return result;
1343
1343
  }
1344
- /** Check if a version satisfies a range */
1345
- function satisfies(version, range) {
1344
+ /**
1345
+ * Check if a version satisfies a range.
1346
+ * @param version - The version to check
1347
+ * @param range - The version range (may include workspace: or catalog: protocol)
1348
+ * @param currentVersion - The dependency's current version, used to resolve workspace:^ and workspace:~
1349
+ */
1350
+ function satisfies(version, range, currentVersion) {
1346
1351
  if (range.startsWith("workspace:")) {
1347
1352
  const cleanRange = range.slice(10);
1348
- if (!cleanRange || cleanRange === "*" || cleanRange === "^" || cleanRange === "~") return true;
1353
+ if (!cleanRange || cleanRange === "*") return true;
1354
+ if (cleanRange === "^" || cleanRange === "~") {
1355
+ if (!currentVersion) return true;
1356
+ const resolved = `${cleanRange}${currentVersion}`;
1357
+ return import_semver.default.satisfies(version, resolved);
1358
+ }
1349
1359
  return import_semver.default.satisfies(version, cleanRange);
1350
1360
  }
1351
1361
  if (range.startsWith("catalog:")) return true;
@@ -1,7 +1,8 @@
1
1
  import { a as __exportAll } from "./logger-C2dEe5Su.mjs";
2
- import { exec, execFile, execFileSync } from "node:child_process";
2
+ import { exec, execFile, execFileSync, execSync } from "node:child_process";
3
3
  //#region src/utils/shell.ts
4
4
  var shell_exports = /* @__PURE__ */ __exportAll({
5
+ run: () => run,
5
6
  runArgs: () => runArgs,
6
7
  runArgsAsync: () => runArgsAsync,
7
8
  runAsync: () => runAsync,
@@ -19,6 +20,23 @@ function sq(value) {
19
20
  function checkIntercept(args, opts) {
20
21
  return null;
21
22
  }
23
+ function run(cmd, opts) {
24
+ const result = checkIntercept(cmd.split(/\s+/), opts);
25
+ if (result?.intercepted) {
26
+ if ("error" in result) throw new Error(result.error);
27
+ return result.result;
28
+ }
29
+ return execSync(cmd, {
30
+ cwd: opts?.cwd,
31
+ input: opts?.input,
32
+ encoding: "utf-8",
33
+ stdio: [
34
+ opts?.input ? "pipe" : "pipe",
35
+ "pipe",
36
+ "pipe"
37
+ ]
38
+ }).trim();
39
+ }
22
40
  function runAsync(cmd, opts) {
23
41
  const result = checkIntercept(cmd.split(/\s+/), opts);
24
42
  if (result?.intercepted) {
@@ -89,4 +107,4 @@ function tryRunArgs(args, opts) {
89
107
  }
90
108
  }
91
109
  //#endregion
92
- export { sq as a, shell_exports as i, runArgsAsync as n, tryRunArgs as o, runAsync as r, runArgs as t };
110
+ export { shell_exports as a, runAsync as i, runArgs as n, sq as o, runArgsAsync as r, tryRunArgs as s, run as t };
@@ -1,41 +1,41 @@
1
1
  import { n as log, t as colorize } from "./logger-C2dEe5Su.mjs";
2
- import { a as loadConfig } from "./config-BkwIEaQg.mjs";
3
- import { t as discoverPackages } from "./workspace-CxEKakDm.mjs";
4
- import { t as DependencyGraph } from "./dep-graph-E-9-eQ2J.mjs";
5
- import { r as readChangesets } from "./changeset-UCZdSRDv.mjs";
6
- import { t as assembleReleasePlan } from "./release-plan-BEzwApuK.mjs";
2
+ import { a as loadConfig } from "./config-B-Qg3DZH.mjs";
3
+ import { t as discoverPackages } from "./workspace-DWXlwcH4.mjs";
4
+ import { t as DependencyGraph } from "./dep-graph-DiLeAhl9.mjs";
5
+ import { r as readBumpFiles } from "./bump-file-DVqR3k67.mjs";
6
+ import { t as assembleReleasePlan } from "./release-plan-JNir7bSM.mjs";
7
7
  //#region src/commands/status.ts
8
8
  async function statusCommand(rootDir, opts) {
9
9
  const config = await loadConfig(rootDir);
10
10
  const packages = await discoverPackages(rootDir, config);
11
11
  const depGraph = new DependencyGraph(packages);
12
- const changesets = await readChangesets(rootDir);
13
- if (changesets.length === 0) {
12
+ const bumpFiles = await readBumpFiles(rootDir);
13
+ if (bumpFiles.length === 0) {
14
14
  if (opts.json) console.log(JSON.stringify({
15
- changesets: [],
15
+ bumpFiles: [],
16
16
  releases: [],
17
17
  packageNames: []
18
18
  }, null, 2));
19
- else if (!opts.packagesOnly) log.info("No pending changesets.");
19
+ else if (!opts.packagesOnly) log.info("No pending bump files.");
20
20
  process.exit(1);
21
21
  }
22
- const plan = assembleReleasePlan(changesets, packages, depGraph, config);
22
+ const plan = assembleReleasePlan(bumpFiles, packages, depGraph, config);
23
23
  let releases = plan.releases;
24
24
  if (opts.bumpType) {
25
25
  const types = opts.bumpType.split(",").map((t) => t.trim());
26
26
  releases = releases.filter((r) => types.includes(r.type));
27
27
  }
28
28
  if (opts.filter) {
29
- const { matchGlob } = await import("./config-BkwIEaQg.mjs").then((n) => n.t);
29
+ const { matchGlob } = await import("./config-B-Qg3DZH.mjs").then((n) => n.t);
30
30
  const patterns = opts.filter.split(",").map((p) => p.trim());
31
31
  releases = releases.filter((r) => patterns.some((p) => matchGlob(r.name, p)));
32
32
  }
33
33
  if (opts.json) {
34
34
  const jsonOutput = {
35
- changesets: plan.changesets.map((cs) => ({
36
- id: cs.id,
37
- summary: cs.summary,
38
- releases: cs.releases.map((r) => ({
35
+ bumpFiles: plan.bumpFiles.map((bf) => ({
36
+ id: bf.id,
37
+ summary: bf.summary,
38
+ releases: bf.releases.map((r) => ({
39
39
  name: r.name,
40
40
  type: r.type
41
41
  }))
@@ -46,7 +46,7 @@ async function statusCommand(rootDir, opts) {
46
46
  oldVersion: r.oldVersion,
47
47
  newVersion: r.newVersion,
48
48
  dir: packages.get(r.name)?.relativeDir,
49
- changesets: r.changesets,
49
+ bumpFiles: r.bumpFiles,
50
50
  isDependencyBump: r.isDependencyBump,
51
51
  isCascadeBump: r.isCascadeBump
52
52
  })),
@@ -59,7 +59,7 @@ async function statusCommand(rootDir, opts) {
59
59
  for (const r of releases) console.log(r.name);
60
60
  return;
61
61
  }
62
- log.bold(`${changesets.length} changeset(s) pending\n`);
62
+ log.bold(`${bumpFiles.length} bump file(s) pending\n`);
63
63
  if (releases.length === 0) {
64
64
  log.warn("No packages match the current filters.");
65
65
  return;
@@ -87,12 +87,16 @@ async function statusCommand(rootDir, opts) {
87
87
  for (const r of group) printRelease(r, packages);
88
88
  console.log();
89
89
  }
90
+ if (plan.warnings.length > 0) {
91
+ for (const w of plan.warnings) log.warn(w);
92
+ console.log();
93
+ }
90
94
  if (opts.verbose) {
91
- log.bold("Changesets:");
92
- for (const cs of plan.changesets) {
93
- console.log(` ${colorize(cs.id, "cyan")}`);
94
- for (const r of cs.releases) console.log(` ${r.name}: ${r.type}`);
95
- if (cs.summary) console.log(` ${colorize(cs.summary.split("\n")[0], "dim")}`);
95
+ log.bold("Bump files:");
96
+ for (const bf of plan.bumpFiles) {
97
+ console.log(` ${colorize(bf.id, "cyan")}`);
98
+ for (const r of bf.releases) console.log(` ${r.name}: ${r.type}`);
99
+ if (bf.summary) console.log(` ${colorize(bf.summary.split("\n")[0], "dim")}`);
96
100
  }
97
101
  }
98
102
  }