@varlock/bumpy 0.0.2 → 1.0.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 (32) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/dist/add-CgCjs4d-.mjs +313 -0
  3. package/dist/{ai-CQhUyHAG.mjs → ai-sMYUf3lP.mjs} +21 -4
  4. package/dist/{apply-release-plan-D6TSrcwX.mjs → apply-release-plan-CczGWJTk.mjs} +28 -24
  5. package/dist/bump-file-CCLXMLA8.mjs +143 -0
  6. package/dist/{changelog-github-Du62krXi.mjs → changelog-github-Cd8uJHZI.mjs} +22 -20
  7. package/dist/{check-jIwike9F.mjs → check-BOoxpWqk.mjs} +9 -9
  8. package/dist/{ci-D6LQbR38.mjs → ci-Bhx--Tj6.mjs} +116 -72
  9. package/dist/{ci-setup-C6FlOfW5.mjs → ci-setup-qz4Y3v7T.mjs} +1 -1
  10. package/dist/cli.mjs +32 -30
  11. package/dist/{config-BkwIEaQg.mjs → config-XZWUL3ma.mjs} +27 -22
  12. package/dist/fs-DYR2XuFE.mjs +81 -0
  13. package/dist/{generate-Btrsn1qi.mjs → generate-gYKTpvex.mjs} +8 -8
  14. package/dist/index.d.mts +55 -37
  15. package/dist/index.mjs +8 -8
  16. package/dist/{init-B0q3wEQW.mjs → init-lA9E5pEc.mjs} +2 -2
  17. package/dist/{migrate-CfQNwD0T.mjs → migrate-DmOYgmfD.mjs} +10 -10
  18. package/dist/{names-Ck8cun7B.mjs → names-9VubBmL0.mjs} +1 -1
  19. package/dist/{package-manager-DcI5TdDE.mjs → package-manager-VCe10bjc.mjs} +1 -1
  20. package/dist/{publish-D_7RqEYL.mjs → publish-Cun-zQ1b.mjs} +21 -20
  21. package/dist/{publish-pipeline-ChnqW8nR.mjs → publish-pipeline-BwBuKCIk.mjs} +22 -17
  22. package/dist/release-plan-Bi5QNSEo.mjs +264 -0
  23. package/dist/{semver-BTzYh8vc.mjs → semver-DfQyVLM_.mjs} +13 -3
  24. package/dist/{status--Q8yAxQ4.mjs → status-CfE63ti5.mjs} +25 -21
  25. package/dist/{version-cAUkfYPx.mjs → version-19vVt9dv.mjs} +16 -12
  26. package/dist/{workspace-CxEKakDm.mjs → workspace-C5ULTyUN.mjs} +3 -3
  27. package/package.json +13 -1
  28. package/skills/add-change/SKILL.md +8 -12
  29. package/dist/add-BjyVIUlr.mjs +0 -175
  30. package/dist/changeset-UCZdSRDv.mjs +0 -108
  31. package/dist/fs-0AtnPUUe.mjs +0 -51
  32. package/dist/release-plan-BEzwApuK.mjs +0 -173
package/dist/cli.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as log, t as colorize } from "./logger-C2dEe5Su.mjs";
3
- import { n as findRoot } from "./config-BkwIEaQg.mjs";
3
+ import { n as findRoot } from "./config-XZWUL3ma.mjs";
4
4
  //#region src/cli.ts
5
5
  const args = process.argv.slice(2);
6
6
  const command = args[0];
@@ -25,13 +25,13 @@ async function main() {
25
25
  switch (command) {
26
26
  case "init": {
27
27
  const rootDir = await findRoot();
28
- const { initCommand } = await import("./init-B0q3wEQW.mjs");
28
+ const { initCommand } = await import("./init-lA9E5pEc.mjs");
29
29
  await initCommand(rootDir);
30
30
  break;
31
31
  }
32
32
  case "add": {
33
33
  const rootDir = await findRoot();
34
- const { addCommand } = await import("./add-BjyVIUlr.mjs");
34
+ const { addCommand } = await import("./add-CgCjs4d-.mjs");
35
35
  await addCommand(rootDir, {
36
36
  packages: flags.packages,
37
37
  message: flags.message,
@@ -42,7 +42,7 @@ async function main() {
42
42
  }
43
43
  case "status": {
44
44
  const rootDir = await findRoot();
45
- const { statusCommand } = await import("./status--Q8yAxQ4.mjs");
45
+ const { statusCommand } = await import("./status-CfE63ti5.mjs");
46
46
  await statusCommand(rootDir, {
47
47
  json: flags.json === true,
48
48
  packagesOnly: flags.packages === true,
@@ -54,13 +54,13 @@ async function main() {
54
54
  }
55
55
  case "version": {
56
56
  const rootDir = await findRoot();
57
- const { versionCommand } = await import("./version-cAUkfYPx.mjs");
57
+ const { versionCommand } = await import("./version-19vVt9dv.mjs");
58
58
  await versionCommand(rootDir);
59
59
  break;
60
60
  }
61
61
  case "generate": {
62
62
  const rootDir = await findRoot();
63
- const { generateCommand } = await import("./generate-Btrsn1qi.mjs");
63
+ const { generateCommand } = await import("./generate-gYKTpvex.mjs");
64
64
  await generateCommand(rootDir, {
65
65
  from: flags.from,
66
66
  dryRun: flags["dry-run"] === true,
@@ -70,13 +70,13 @@ async function main() {
70
70
  }
71
71
  case "migrate": {
72
72
  const rootDir = await findRoot();
73
- const { migrateCommand } = await import("./migrate-CfQNwD0T.mjs");
73
+ const { migrateCommand } = await import("./migrate-DmOYgmfD.mjs");
74
74
  await migrateCommand(rootDir, { force: flags.force === true });
75
75
  break;
76
76
  }
77
77
  case "check": {
78
78
  const rootDir = await findRoot();
79
- const { checkCommand } = await import("./check-jIwike9F.mjs");
79
+ const { checkCommand } = await import("./check-BOoxpWqk.mjs");
80
80
  await checkCommand(rootDir);
81
81
  break;
82
82
  }
@@ -85,20 +85,22 @@ async function main() {
85
85
  const subcommand = args[1];
86
86
  const ciFlags = parseFlags(args.slice(2));
87
87
  if (subcommand === "check") {
88
- const { ciCheckCommand } = await import("./ci-D6LQbR38.mjs");
88
+ const { ciCheckCommand } = await import("./ci-Bhx--Tj6.mjs");
89
89
  await ciCheckCommand(rootDir, {
90
90
  comment: ciFlags.comment !== void 0 ? ciFlags.comment === true : void 0,
91
- failOnMissing: ciFlags["fail-on-missing"] === true
91
+ failOnMissing: ciFlags["fail-on-missing"] === true,
92
+ patComments: ciFlags["pat-comments"] === true
92
93
  });
93
94
  } else if (subcommand === "release") {
94
- const { ciReleaseCommand } = await import("./ci-D6LQbR38.mjs");
95
+ const { ciReleaseCommand } = await import("./ci-Bhx--Tj6.mjs");
95
96
  await ciReleaseCommand(rootDir, {
96
97
  mode: ciFlags["auto-publish"] === true ? "auto-publish" : "version-pr",
97
98
  tag: ciFlags.tag,
98
- branch: ciFlags.branch
99
+ branch: ciFlags.branch,
100
+ patPr: ciFlags["pat-pr"] === true
99
101
  });
100
102
  } else if (subcommand === "setup") {
101
- const { ciSetupCommand } = await import("./ci-setup-C6FlOfW5.mjs");
103
+ const { ciSetupCommand } = await import("./ci-setup-qz4Y3v7T.mjs");
102
104
  await ciSetupCommand(rootDir);
103
105
  } else {
104
106
  log.error(`Unknown ci subcommand: ${subcommand}. Use "ci check", "ci release", or "ci setup".`);
@@ -108,7 +110,7 @@ async function main() {
108
110
  }
109
111
  case "publish": {
110
112
  const rootDir = await findRoot();
111
- const { publishCommand } = await import("./publish-D_7RqEYL.mjs");
113
+ const { publishCommand } = await import("./publish-Cun-zQ1b.mjs");
112
114
  await publishCommand(rootDir, {
113
115
  dryRun: flags["dry-run"] === true,
114
116
  tag: flags.tag,
@@ -122,7 +124,7 @@ async function main() {
122
124
  const subcommand = args[1];
123
125
  const aiFlags = parseFlags(args.slice(2));
124
126
  if (subcommand === "setup") {
125
- const { aiSetupCommand } = await import("./ai-CQhUyHAG.mjs");
127
+ const { aiSetupCommand } = await import("./ai-sMYUf3lP.mjs");
126
128
  await aiSetupCommand(rootDir, { target: aiFlags.target });
127
129
  } else {
128
130
  log.error(`Unknown ai subcommand: ${subcommand}. Use "ai setup".`);
@@ -132,7 +134,7 @@ async function main() {
132
134
  }
133
135
  case "--version":
134
136
  case "-v":
135
- console.log(`bumpy 0.0.2`);
137
+ console.log(`bumpy 1.0.0`);
136
138
  break;
137
139
  case "help":
138
140
  case "--help":
@@ -152,41 +154,41 @@ async function main() {
152
154
  }
153
155
  function printHelp() {
154
156
  console.log(`
155
- ${colorize(`🐸 bumpy v0.0.2`, "bold")} - Modern monorepo versioning
157
+ ${colorize(`🐸 bumpy v1.0.0`, "bold")} - Modern monorepo versioning
156
158
 
157
159
  Usage: bumpy <command> [options]
158
160
 
159
161
  Commands:
160
162
  init Initialize .bumpy/ directory
161
- add Create a new changeset
162
- generate Generate changeset from conventional commits
163
+ add Create a new bump file
164
+ generate Generate bump file from conventional commits
163
165
  status Show pending releases
164
- check Verify changed packages have changesets (for pre-push hooks)
165
- version Apply changesets and bump versions
166
+ check Verify changed packages have bump files (for pre-push hooks)
167
+ version Apply bump files and bump versions
166
168
  publish Publish versioned packages
167
169
  ci check PR check — report pending releases, comment on PR
168
170
  ci release Release — create version PR or auto-publish
169
171
  ci setup Set up a token for triggering CI on version PRs
170
172
  migrate Migrate from .changeset/ to .bumpy/
171
- ai setup Install AI skill for creating changesets
173
+ ai setup Install AI skill for creating bump files
172
174
 
173
175
  Add options:
174
176
  --packages <list> Package bumps (e.g., "pkg-a:minor,pkg-b:patch")
175
- --message <text> Changeset summary
176
- --name <name> Changeset filename
177
- --empty Create an empty changeset
177
+ --message <text> Bump file summary
178
+ --name <name> Bump file filename
179
+ --empty Create an empty bump file
178
180
 
179
181
  Generate options:
180
182
  --from <ref> Git ref to scan from (default: last version tag)
181
- --dry-run Preview without creating a changeset
182
- --name <name> Changeset filename
183
+ --dry-run Preview without creating a bump file
184
+ --name <name> Bump file filename
183
185
 
184
186
  Status options:
185
- --json Output as JSON (includes dirs, changesets, packageNames)
187
+ --json Output as JSON (includes dirs, bumpFiles, packageNames)
186
188
  --packages Output only package names, one per line
187
189
  --bump <types> Filter by bump type (e.g., "major", "minor,patch")
188
190
  --filter <names> Filter by package name/glob (e.g., "@myorg/*")
189
- --verbose Show changeset details
191
+ --verbose Show bump file details
190
192
 
191
193
  Publish options:
192
194
  --dry-run Preview without publishing
@@ -196,7 +198,7 @@ function printHelp() {
196
198
 
197
199
  CI check options:
198
200
  --comment Force PR comment on/off (auto-detected in CI)
199
- --fail-on-missing Exit 1 if no changesets found
201
+ --fail-on-missing Exit 1 if no bump files found
200
202
 
201
203
  CI release options:
202
204
  --auto-publish Version + publish directly (default: create version PR)
@@ -1,5 +1,5 @@
1
1
  import { a as __exportAll } from "./logger-C2dEe5Su.mjs";
2
- import { a as readJson, n as exists } from "./fs-0AtnPUUe.mjs";
2
+ import { a as readJson, n as exists } from "./fs-DYR2XuFE.mjs";
3
3
  import { resolve } from "node:path";
4
4
  //#region src/types.ts
5
5
  const BUMP_LEVELS = {
@@ -10,16 +10,6 @@ const BUMP_LEVELS = {
10
10
  function bumpLevel(type) {
11
11
  return BUMP_LEVELS[type];
12
12
  }
13
- function parseIsolatedBump(type) {
14
- if (type.endsWith("-isolated")) return {
15
- bump: type.replace("-isolated", ""),
16
- isolated: true
17
- };
18
- return {
19
- bump: type,
20
- isolated: false
21
- };
22
- }
23
13
  function maxBump(a, b) {
24
14
  if (!a) return b;
25
15
  return bumpLevel(a) >= bumpLevel(b) ? a : b;
@@ -31,12 +21,9 @@ const DEFAULT_BUMP_RULES = {
31
21
  },
32
22
  peerDependencies: {
33
23
  trigger: "major",
34
- bumpAs: "major"
35
- },
36
- devDependencies: {
37
- trigger: "none",
38
- bumpAs: "patch"
24
+ bumpAs: "match"
39
25
  },
26
+ devDependencies: false,
40
27
  optionalDependencies: {
41
28
  trigger: "minor",
42
29
  bumpAs: "patch"
@@ -69,6 +56,7 @@ const DEFAULT_CONFIG = {
69
56
  version: false,
70
57
  tag: false
71
58
  },
59
+ allowCustomCommands: false,
72
60
  packages: {},
73
61
  publish: { ...DEFAULT_PUBLISH_CONFIG },
74
62
  aggregateRelease: false,
@@ -82,7 +70,7 @@ const DEFAULT_CONFIG = {
82
70
  preamble: [
83
71
  `<a href="https://github.com/dmno-dev/bumpy"><img src="https://raw.githubusercontent.com/dmno-dev/bumpy/main/images/frog-talking.png" alt="bumpy-frog" width="60" align="left" style="image-rendering: pixelated;" title="Hi! I'm bumpy!" /></a>`,
84
72
  "",
85
- `This PR was created and will be kept in sync by [bumpy](https://github.com/dmno-dev/bumpy) based on your .bumpy changeset files. Merge it when you are ready to release the packages listed below:`,
73
+ `This PR was created and will be kept in sync by [bumpy](https://github.com/dmno-dev/bumpy) based on your .bumpy bump files. Merge it when you are ready to release the packages listed below:`,
86
74
  "<br clear=\"left\" />"
87
75
  ].join("\n")
88
76
  }
@@ -131,6 +119,20 @@ async function loadPackageConfig(pkgDir, rootConfig, pkgName) {
131
119
  const pkg = await readJson(resolve(pkgDir, "package.json"));
132
120
  if (pkg.bumpy && typeof pkg.bumpy === "object") pkgJsonConfig = pkg.bumpy;
133
121
  } catch {}
122
+ const disallowedKeys = [
123
+ "buildCommand",
124
+ "publishCommand",
125
+ "checkPublished"
126
+ ].filter((k) => pkgJsonConfig[k] != null);
127
+ if (disallowedKeys.length > 0 && !isCustomCommandAllowed(pkgName, rootConfig)) {
128
+ const fields = disallowedKeys.map((k) => `"${k}"`).join(", ");
129
+ throw new Error(`Package "${pkgName}" defines custom command(s) (${fields}) in its package.json "bumpy" config, but the root config does not allow this.
130
+ Custom commands execute shell commands during publishing and must be explicitly enabled.
131
+
132
+ To fix this, either:
133
+ 1. Move the command(s) to .bumpy/_config.json under "packages" (always trusted)
134
+ 2. Add "allowCustomCommands": true (or ["${pkgName}"]) to .bumpy/_config.json`);
135
+ }
134
136
  return mergePackageConfig(rootPkgConfig, pkgJsonConfig);
135
137
  }
136
138
  /** Find a package config from the root config, supporting glob patterns */
@@ -175,10 +177,6 @@ function mergePackageConfig(...configs) {
175
177
  ...result.dependencyBumpRules,
176
178
  ...cfg.dependencyBumpRules
177
179
  };
178
- if (cfg.specificDependencyRules) result.specificDependencyRules = {
179
- ...result.specificDependencyRules,
180
- ...cfg.specificDependencyRules
181
- };
182
180
  if (cfg.cascadeTo) result.cascadeTo = {
183
181
  ...result.cascadeTo,
184
182
  ...cfg.cascadeTo
@@ -186,6 +184,13 @@ function mergePackageConfig(...configs) {
186
184
  }
187
185
  return result;
188
186
  }
187
+ /** Check if a package is allowed to define custom commands via package.json */
188
+ function isCustomCommandAllowed(pkgName, config) {
189
+ const { allowCustomCommands } = config;
190
+ if (allowCustomCommands === true) return true;
191
+ if (Array.isArray(allowCustomCommands)) return allowCustomCommands.some((pattern) => matchGlob(pkgName, pattern));
192
+ return false;
193
+ }
189
194
  function getBumpyDir(rootDir) {
190
195
  return resolve(rootDir, BUMPY_DIR);
191
196
  }
@@ -212,4 +217,4 @@ function isPackageManaged(pkgName, isPrivate, config, pkgBumpy) {
212
217
  return true;
213
218
  }
214
219
  //#endregion
215
- export { loadConfig as a, BUMP_LEVELS as c, DEFAULT_PUBLISH_CONFIG as d, DEP_TYPES as f, parseIsolatedBump as g, maxBump as h, isPackageManaged as i, DEFAULT_BUMP_RULES as l, hasCascade as m, findRoot as n, loadPackageConfig as o, bumpLevel as p, getBumpyDir as r, matchGlob as s, config_exports as t, DEFAULT_CONFIG as u };
220
+ export { loadConfig as a, BUMP_LEVELS as c, DEFAULT_PUBLISH_CONFIG as d, DEP_TYPES as f, maxBump as h, isPackageManaged as i, DEFAULT_BUMP_RULES as l, hasCascade as m, findRoot as n, loadPackageConfig as o, bumpLevel as p, getBumpyDir as r, matchGlob as s, config_exports as t, DEFAULT_CONFIG as u };
@@ -0,0 +1,81 @@
1
+ import { a as __exportAll } from "./logger-C2dEe5Su.mjs";
2
+ import { access, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
3
+ //#region src/utils/fs.ts
4
+ var fs_exports = /* @__PURE__ */ __exportAll({
5
+ ensureDir: () => ensureDir,
6
+ exists: () => exists,
7
+ listFiles: () => listFiles,
8
+ readJson: () => readJson,
9
+ readText: () => readText,
10
+ removeFile: () => removeFile,
11
+ updateJsonFields: () => updateJsonFields,
12
+ updateJsonNestedField: () => updateJsonNestedField,
13
+ writeJson: () => writeJson,
14
+ writeText: () => writeText
15
+ });
16
+ async function readJson(filePath) {
17
+ const content = await readFile(filePath, "utf-8");
18
+ return JSON.parse(content);
19
+ }
20
+ async function writeJson(filePath, data, indent = 2) {
21
+ await writeFile(filePath, JSON.stringify(data, null, indent) + "\n", "utf-8");
22
+ }
23
+ /**
24
+ * Update specific top-level string fields in a JSON file without reformatting.
25
+ * Reads the raw text, does targeted regex replacements, and writes it back.
26
+ */
27
+ async function updateJsonFields(filePath, updates) {
28
+ let content = await readFile(filePath, "utf-8");
29
+ for (const [key, newValue] of Object.entries(updates)) {
30
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
31
+ const pattern = new RegExp(`("${escaped}"\\s*:\\s*)"[^"]*"`);
32
+ content = content.replace(pattern, `$1"${newValue}"`);
33
+ }
34
+ await writeFile(filePath, content, "utf-8");
35
+ }
36
+ /**
37
+ * Update a nested string field inside a top-level object in a JSON file without reformatting.
38
+ * e.g., updateJsonNestedField(path, 'dependencies', 'core', '^2.0.0')
39
+ */
40
+ async function updateJsonNestedField(filePath, parentKey, childKey, newValue) {
41
+ let content = await readFile(filePath, "utf-8");
42
+ const parentEscaped = parentKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
43
+ const childEscaped = childKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
44
+ const parentPattern = new RegExp(`("${parentEscaped}"\\s*:\\s*\\{)([^}]*)\\}`, "s");
45
+ content = content.replace(parentPattern, (match, prefix, body) => {
46
+ const childPattern = new RegExp(`("${childEscaped}"\\s*:\\s*)"[^"]*"`);
47
+ return `${prefix}${body.replace(childPattern, `$1"${newValue}"`)}}`;
48
+ });
49
+ await writeFile(filePath, content, "utf-8");
50
+ }
51
+ async function readText(filePath) {
52
+ return readFile(filePath, "utf-8");
53
+ }
54
+ async function writeText(filePath, content) {
55
+ await writeFile(filePath, content, "utf-8");
56
+ }
57
+ async function exists(filePath) {
58
+ try {
59
+ await access(filePath);
60
+ return true;
61
+ } catch {
62
+ return false;
63
+ }
64
+ }
65
+ async function listFiles(dir, ext) {
66
+ try {
67
+ const entries = await readdir(dir);
68
+ if (ext) return entries.filter((e) => e.endsWith(ext));
69
+ return entries;
70
+ } catch {
71
+ return [];
72
+ }
73
+ }
74
+ async function removeFile(filePath) {
75
+ await unlink(filePath);
76
+ }
77
+ async function ensureDir(dir) {
78
+ await mkdir(dir, { recursive: true });
79
+ }
80
+ //#endregion
81
+ export { readJson as a, updateJsonFields as c, writeText as d, listFiles as i, updateJsonNestedField as l, exists as n, readText as o, fs_exports as r, removeFile as s, ensureDir as t, writeJson as u };
@@ -1,10 +1,10 @@
1
1
  import { n as log, t as colorize } from "./logger-C2dEe5Su.mjs";
2
- import { t as ensureDir } from "./fs-0AtnPUUe.mjs";
3
- import { a as loadConfig, r as getBumpyDir } from "./config-BkwIEaQg.mjs";
4
- import { t as discoverPackages } from "./workspace-CxEKakDm.mjs";
2
+ import { t as ensureDir } from "./fs-DYR2XuFE.mjs";
3
+ import { a as loadConfig, r as getBumpyDir } from "./config-XZWUL3ma.mjs";
4
+ import { t as discoverPackages } from "./workspace-C5ULTyUN.mjs";
5
5
  import { o as tryRunArgs } from "./shell-Dj7JRD_q.mjs";
6
- import { i as writeChangeset } from "./changeset-UCZdSRDv.mjs";
7
- import { n as slugify, t as randomName } from "./names-Ck8cun7B.mjs";
6
+ import { i as writeBumpFile } from "./bump-file-CCLXMLA8.mjs";
7
+ import { n as slugify, t as randomName } from "./names-9VubBmL0.mjs";
8
8
  //#region src/commands/generate.ts
9
9
  const BUMP_MAP = {
10
10
  feat: "minor",
@@ -84,7 +84,7 @@ async function generateCommand(rootDir, opts) {
84
84
  for (const msg of info.messages) summaryLines.push(`- ${name}: ${msg}`);
85
85
  }
86
86
  if (opts.dryRun) {
87
- log.bold("Would create changeset:");
87
+ log.bold("Would create bump file:");
88
88
  for (const r of releases) console.log(` ${r.name}: ${colorize(r.type, r.type === "major" ? "red" : r.type === "minor" ? "yellow" : "green")}`);
89
89
  console.log();
90
90
  log.dim("Summary:");
@@ -93,8 +93,8 @@ async function generateCommand(rootDir, opts) {
93
93
  }
94
94
  await ensureDir(getBumpyDir(rootDir));
95
95
  const filename = opts.name ? slugify(opts.name) : randomName();
96
- await writeChangeset(rootDir, filename, releases, summaryLines.join("\n"));
97
- log.success(`Created changeset: .bumpy/${filename}.md`);
96
+ await writeBumpFile(rootDir, filename, releases, summaryLines.join("\n"));
97
+ log.success(`Created bump file: .bumpy/${filename}.md`);
98
98
  for (const r of releases) log.dim(` ${r.name}: ${r.type}`);
99
99
  }
100
100
  /** Parse raw git log output into individual commits */
package/dist/index.d.mts CHANGED
@@ -1,20 +1,16 @@
1
1
  //#region src/types.d.ts
2
2
  type BumpType = 'major' | 'minor' | 'patch';
3
- type BumpTypeWithIsolated = BumpType | 'minor-isolated' | 'patch-isolated';
3
+ type BumpTypeWithNone = BumpType | 'none';
4
4
  declare const BUMP_LEVELS: Record<BumpType, number>;
5
5
  declare function bumpLevel(type: BumpType): number;
6
- declare function parseIsolatedBump(type: BumpTypeWithIsolated): {
7
- bump: BumpType;
8
- isolated: boolean;
9
- };
10
6
  declare function maxBump(a: BumpType | undefined, b: BumpType): BumpType;
11
7
  interface DependencyBumpRule {
12
8
  /** What bump level in the dependency triggers propagation */
13
- trigger: BumpType | 'none';
9
+ trigger: BumpType;
14
10
  /** What bump to apply to the dependent */
15
11
  bumpAs: BumpType | 'match';
16
12
  }
17
- declare const DEFAULT_BUMP_RULES: Record<string, DependencyBumpRule>;
13
+ declare const DEFAULT_BUMP_RULES: Record<string, DependencyBumpRule | false>;
18
14
  type DepType = 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies';
19
15
  declare const DEP_TYPES: DepType[];
20
16
  interface PublishConfig {
@@ -44,12 +40,22 @@ interface BumpyConfig {
44
40
  ignore: string[];
45
41
  /** Package names/globs to explicitly include (overrides private + ignore) */
46
42
  include: string[];
47
- updateInternalDependencies: 'patch' | 'minor' | 'out-of-range' | 'none';
48
- dependencyBumpRules: Partial<Record<DepType, DependencyBumpRule>>;
43
+ updateInternalDependencies: 'patch' | 'minor' | 'out-of-range';
44
+ dependencyBumpRules: Partial<Record<DepType, DependencyBumpRule | false>>;
49
45
  privatePackages: {
50
46
  version: boolean;
51
47
  tag: boolean;
52
48
  };
49
+ /**
50
+ * Allow per-package custom commands (buildCommand, publishCommand, checkPublished)
51
+ * defined in package.json "bumpy" fields.
52
+ * Commands defined in the root config's `packages` map are always trusted.
53
+ *
54
+ * true = allow all packages to define custom commands
55
+ * string[] = allow only matching package names/globs
56
+ * false = only root-config commands are allowed (default)
57
+ */
58
+ allowCustomCommands: boolean | string[];
53
59
  packages: Record<string, PackageConfig>;
54
60
  publish: PublishConfig;
55
61
  /**
@@ -84,26 +90,25 @@ interface PackageConfig {
84
90
  skipNpmPublish?: boolean;
85
91
  /** Command to check if a version is already published. Should output the published version string. */
86
92
  checkPublished?: string;
87
- dependencyBumpRules?: Partial<Record<DepType, DependencyBumpRule>>;
88
- specificDependencyRules?: Record<string, DependencyBumpRule>;
93
+ dependencyBumpRules?: Partial<Record<DepType, DependencyBumpRule | false>>;
89
94
  cascadeTo?: Record<string, DependencyBumpRule>;
90
95
  }
91
96
  declare const DEFAULT_PUBLISH_CONFIG: PublishConfig;
92
97
  declare const DEFAULT_CONFIG: BumpyConfig;
93
- interface ChangesetReleaseSimple {
98
+ interface BumpFileReleaseSimple {
94
99
  name: string;
95
- type: BumpTypeWithIsolated;
100
+ type: BumpTypeWithNone;
96
101
  }
97
- interface ChangesetReleaseCascade {
102
+ interface BumpFileReleaseCascade {
98
103
  name: string;
99
- type: BumpTypeWithIsolated;
104
+ type: BumpTypeWithNone;
100
105
  cascade: Record<string, BumpType>;
101
106
  }
102
- type ChangesetRelease = ChangesetReleaseSimple | ChangesetReleaseCascade;
103
- declare function hasCascade(r: ChangesetRelease): r is ChangesetReleaseCascade;
104
- interface Changeset {
107
+ type BumpFileRelease = BumpFileReleaseSimple | BumpFileReleaseCascade;
108
+ declare function hasCascade(r: BumpFileRelease): r is BumpFileReleaseCascade;
109
+ interface BumpFile {
105
110
  id: string;
106
- releases: ChangesetRelease[];
111
+ releases: BumpFileRelease[];
107
112
  summary: string;
108
113
  }
109
114
  interface WorkspacePackage {
@@ -131,13 +136,14 @@ interface PlannedRelease {
131
136
  type: BumpType;
132
137
  oldVersion: string;
133
138
  newVersion: string;
134
- changesets: string[];
139
+ bumpFiles: string[];
135
140
  isDependencyBump: boolean;
136
141
  isCascadeBump: boolean;
137
142
  }
138
143
  interface ReleasePlan {
139
- changesets: Changeset[];
144
+ bumpFiles: BumpFile[];
140
145
  releases: PlannedRelease[];
146
+ warnings: string[];
141
147
  }
142
148
  //#endregion
143
149
  //#region src/core/config.d.ts
@@ -175,30 +181,35 @@ declare class DependencyGraph {
175
181
  topologicalSort(packages: Map<string, WorkspacePackage>): string[];
176
182
  }
177
183
  //#endregion
178
- //#region src/core/changeset.d.ts
179
- /** Read all changeset files from .bumpy/ directory, sorted by git creation order */
180
- declare function readChangesets(rootDir: string): Promise<Changeset[]>;
181
- /** Parse changeset content (for testing) */
182
- declare function parseChangeset(content: string, id: string): Changeset | null;
183
- /** Write a changeset file */
184
- declare function writeChangeset(rootDir: string, filename: string, releases: ChangesetRelease[], summary: string): Promise<string>;
184
+ //#region src/core/bump-file.d.ts
185
+ /** Read all bump files from .bumpy/ directory, sorted by git creation order */
186
+ declare function readBumpFiles(rootDir: string): Promise<BumpFile[]>;
187
+ /** Parse bump file content (for testing) */
188
+ declare function parseBumpFile(content: string, id: string): BumpFile | null;
189
+ /** Write a bump file */
190
+ declare function writeBumpFile(rootDir: string, filename: string, releases: BumpFileRelease[], summary: string): Promise<string>;
185
191
  //#endregion
186
192
  //#region src/core/release-plan.d.ts
187
193
  /**
188
- * Build a release plan from pending changesets, the dependency graph, and config.
194
+ * Build a release plan from pending bump files, the dependency graph, and config.
189
195
  * This is the core algorithm of bumpy.
196
+ *
197
+ * The propagation loop runs three phases until stable:
198
+ * Phase A — fix out-of-range dependencies (always runs)
199
+ * Phase B — enforce fixed/linked group constraints
200
+ * Phase C — apply cascades and proactive propagation rules
190
201
  */
191
- declare function assembleReleasePlan(changesets: Changeset[], packages: Map<string, WorkspacePackage>, depGraph: DependencyGraph, config: BumpyConfig): ReleasePlan;
202
+ declare function assembleReleasePlan(bumpFiles: BumpFile[], packages: Map<string, WorkspacePackage>, depGraph: DependencyGraph, config: BumpyConfig): ReleasePlan;
192
203
  //#endregion
193
204
  //#region src/core/apply-release-plan.d.ts
194
- /** Apply the release plan: bump versions, update changelogs, delete changesets */
205
+ /** Apply the release plan: bump versions, update changelogs, delete bump files */
195
206
  declare function applyReleasePlan(releasePlan: ReleasePlan, packages: Map<string, WorkspacePackage>, rootDir: string, config: BumpyConfig): Promise<void>;
196
207
  //#endregion
197
208
  //#region src/core/changelog.d.ts
198
209
  interface ChangelogContext {
199
210
  release: PlannedRelease;
200
- /** Changesets that contributed to this release */
201
- changesets: Changeset[];
211
+ /** Bump files that contributed to this release */
212
+ bumpFiles: BumpFile[];
202
213
  /** ISO date string (YYYY-MM-DD) */
203
214
  date: string;
204
215
  }
@@ -215,7 +226,7 @@ declare const defaultFormatter: ChangelogFormatter;
215
226
  */
216
227
  declare function loadFormatter(changelog: BumpyConfig['changelog'], rootDir: string): Promise<ChangelogFormatter>;
217
228
  /** Generate a changelog entry using the configured formatter */
218
- declare function generateChangelogEntry(release: PlannedRelease, changesets: Changeset[], formatter?: ChangelogFormatter, date?: string): Promise<string>;
229
+ declare function generateChangelogEntry(release: PlannedRelease, bumpFiles: BumpFile[], formatter?: ChangelogFormatter, date?: string): Promise<string>;
219
230
  /** Prepend a new entry to an existing CHANGELOG.md content */
220
231
  declare function prependToChangelog(existingContent: string, newEntry: string): string;
221
232
  //#endregion
@@ -223,14 +234,21 @@ declare function prependToChangelog(existingContent: string, newEntry: string):
223
234
  interface GithubChangelogOptions {
224
235
  /** "owner/repo" — auto-detected from gh CLI if not provided */
225
236
  repo?: string;
237
+ /** Whether to include "Thanks @user" messages for contributors (default: true) */
238
+ thankContributors?: boolean;
226
239
  /** GitHub usernames (without @) to skip "Thanks" messages for (e.g. internal team members) */
227
240
  internalAuthors?: string[];
228
241
  }
229
242
  //#endregion
230
243
  //#region src/core/semver.d.ts
231
244
  declare function bumpVersion(version: string, type: BumpType): string;
232
- /** Check if a version satisfies a range */
233
- declare function satisfies(version: string, range: string): boolean;
245
+ /**
246
+ * Check if a version satisfies a range.
247
+ * @param version - The version to check
248
+ * @param range - The version range (may include workspace: or catalog: protocol)
249
+ * @param currentVersion - The dependency's current version, used to resolve workspace:^ and workspace:~
250
+ */
251
+ declare function satisfies(version: string, range: string, currentVersion?: string): boolean;
234
252
  /** Strip workspace: protocol from version ranges */
235
253
  declare function stripProtocol(range: string): string;
236
254
  //#endregion
@@ -259,4 +277,4 @@ interface PublishResult {
259
277
  */
260
278
  declare function publishPackages(releasePlan: ReleasePlan, packages: Map<string, WorkspacePackage>, depGraph: DependencyGraph, config: BumpyConfig, rootDir: string, opts?: PublishOptions, catalogs?: CatalogMap, detectedPm?: PackageManager): Promise<PublishResult>;
261
279
  //#endregion
262
- export { BUMP_LEVELS, BumpType, BumpTypeWithIsolated, BumpyConfig, type ChangelogContext, type ChangelogFormatter, Changeset, ChangesetRelease, ChangesetReleaseCascade, ChangesetReleaseSimple, DEFAULT_BUMP_RULES, DEFAULT_CONFIG, DEFAULT_PUBLISH_CONFIG, DEP_TYPES, DepType, DependencyBumpRule, DependencyGraph, DependentInfo, type GithubChangelogOptions, PackageConfig, PackageManager, PlannedRelease, PublishConfig, ReleasePlan, WorkspacePackage, applyReleasePlan, assembleReleasePlan, bumpLevel, bumpVersion, defaultFormatter, discoverPackages, findRoot, generateChangelogEntry, getBumpyDir, hasCascade, loadConfig, loadFormatter, matchGlob, maxBump, parseChangeset, parseIsolatedBump, prependToChangelog, publishPackages, readChangesets, satisfies, stripProtocol, writeChangeset };
280
+ export { BUMP_LEVELS, BumpFile, BumpFileRelease, BumpFileReleaseCascade, BumpFileReleaseSimple, BumpType, BumpTypeWithNone, BumpyConfig, type ChangelogContext, type ChangelogFormatter, DEFAULT_BUMP_RULES, DEFAULT_CONFIG, DEFAULT_PUBLISH_CONFIG, DEP_TYPES, DepType, DependencyBumpRule, DependencyGraph, DependentInfo, type GithubChangelogOptions, PackageConfig, PackageManager, PlannedRelease, PublishConfig, ReleasePlan, WorkspacePackage, applyReleasePlan, assembleReleasePlan, bumpLevel, bumpVersion, defaultFormatter, discoverPackages, findRoot, generateChangelogEntry, getBumpyDir, hasCascade, loadConfig, loadFormatter, matchGlob, maxBump, parseBumpFile, prependToChangelog, publishPackages, readBumpFiles, satisfies, stripProtocol, writeBumpFile };
package/dist/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
- import { a as loadConfig, c as BUMP_LEVELS, d as DEFAULT_PUBLISH_CONFIG, f as DEP_TYPES, g as parseIsolatedBump, h as maxBump, l as DEFAULT_BUMP_RULES, m as hasCascade, n as findRoot, p as bumpLevel, r as getBumpyDir, s as matchGlob, u as DEFAULT_CONFIG } from "./config-BkwIEaQg.mjs";
2
- import { t as discoverPackages } from "./workspace-CxEKakDm.mjs";
1
+ import { a as loadConfig, c as BUMP_LEVELS, d as DEFAULT_PUBLISH_CONFIG, f as DEP_TYPES, h as maxBump, l as DEFAULT_BUMP_RULES, m as hasCascade, n as findRoot, p as bumpLevel, r as getBumpyDir, s as matchGlob, u as DEFAULT_CONFIG } from "./config-XZWUL3ma.mjs";
2
+ import { t as discoverPackages } from "./workspace-C5ULTyUN.mjs";
3
3
  import { t as DependencyGraph } from "./dep-graph-E-9-eQ2J.mjs";
4
- import { i as writeChangeset, n as parseChangeset, r as readChangesets } from "./changeset-UCZdSRDv.mjs";
5
- import { n as satisfies, r as stripProtocol, t as bumpVersion } from "./semver-BTzYh8vc.mjs";
6
- import { t as assembleReleasePlan } from "./release-plan-BEzwApuK.mjs";
7
- import { a as prependToChangelog, i as loadFormatter, n as defaultFormatter, r as generateChangelogEntry, t as applyReleasePlan } from "./apply-release-plan-D6TSrcwX.mjs";
8
- import { t as publishPackages } from "./publish-pipeline-ChnqW8nR.mjs";
9
- 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, parseChangeset, parseIsolatedBump, prependToChangelog, publishPackages, readChangesets, satisfies, stripProtocol, writeChangeset };
4
+ import { i as writeBumpFile, n as parseBumpFile, r as readBumpFiles } from "./bump-file-CCLXMLA8.mjs";
5
+ import { n as satisfies, r as stripProtocol, t as bumpVersion } from "./semver-DfQyVLM_.mjs";
6
+ import { t as assembleReleasePlan } from "./release-plan-Bi5QNSEo.mjs";
7
+ import { a as prependToChangelog, i as loadFormatter, n as defaultFormatter, r as generateChangelogEntry, t as applyReleasePlan } from "./apply-release-plan-CczGWJTk.mjs";
8
+ import { t as publishPackages } from "./publish-pipeline-BwBuKCIk.mjs";
9
+ 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, parseBumpFile, prependToChangelog, publishPackages, readBumpFiles, satisfies, stripProtocol, writeBumpFile };
@@ -1,5 +1,5 @@
1
1
  import { n as log } from "./logger-C2dEe5Su.mjs";
2
- import { c as writeJson, l as writeText, n as exists, t as ensureDir } from "./fs-0AtnPUUe.mjs";
2
+ import { d as writeText, n as exists, t as ensureDir, u as writeJson } from "./fs-DYR2XuFE.mjs";
3
3
  import { resolve } from "node:path";
4
4
  //#region src/commands/init.ts
5
5
  async function initCommand(rootDir) {
@@ -13,7 +13,7 @@ async function initCommand(rootDir) {
13
13
  baseBranch: "main",
14
14
  changelog: "default"
15
15
  });
16
- await writeText(resolve(bumpyDir, "README.md"), `# 🐸 Bumpy\n\nThis directory is used by [bumpy](https://github.com/dmno-dev/bumpy) to manage versioning.\n\nChangeset files (\`.md\`) in this directory describe pending version bumps.\nRun \`bumpy add\` to create a new changeset.\n`);
16
+ await writeText(resolve(bumpyDir, "README.md"), `# 🐸 Bumpy\n\nThis directory is used by [bumpy](https://github.com/dmno-dev/bumpy) to manage versioning.\n\nBump files (\`.md\`) in this directory describe pending version bumps.\nRun \`bumpy add\` to create a new bump file.\n`);
17
17
  log.success("Initialized .bumpy/ directory");
18
18
  log.dim(" Created .bumpy/_config.json");
19
19
  log.dim(" Created .bumpy/README.md");