@varlock/bumpy 1.2.0 → 1.2.1

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
@@ -77,7 +77,7 @@ Then set up CI to automate versioning and publishing (see below).
77
77
 
78
78
  ## CI / GitHub Actions
79
79
 
80
- No separate action to rely on — just call `bumpy ci` directly in your workflows. Two commands handle the entire release lifecycle:
80
+ No GitHub App to install, no separate action to rely on — just call `bumpy ci` directly in your workflows. Two commands handle the entire release lifecycle:
81
81
 
82
82
  - **`bumpy ci check`** — runs on every PR. Computes the release plan from pending bump files and posts/updates a comment on the PR showing what versions would be released. Warns if any changed packages are missing bump files.
83
83
  - **`bumpy ci release`** — runs on push to main. If pending bump files exist, it opens (or updates) a "Version Packages" PR that applies all version bumps and changelog updates. If the current push _is_ the Version Packages PR being merged, it publishes the new versions, creates git tags, and creates GitHub releases.
@@ -232,7 +232,7 @@ Bumpy is built as a successor to [@changesets/changesets](https://github.com/cha
232
232
  - **Workspace protocol resolution** — changesets uses `npm publish` even in pnpm/yarn workspaces, so `workspace:^` and `catalog:` protocols are NOT resolved, resulting in broken published packages.
233
233
  - **Custom publish commands** — changesets is hardcoded to `npm publish`. Bumpy supports per-package custom publish for VSCode extensions, Docker images, JSR, etc.
234
234
  - **Flexible package management** — changesets treats all private packages the same. Bumpy lets you include/exclude any package individually.
235
- - **CI without a separate action** — just `bunx @varlock/bumpy ci check` in any workflow, no bot or action to install.
235
+ - **CI without a separate action or bot** — changesets requires installing a [GitHub App](https://github.com/apps/changeset-bot) _and_ using a [separate GitHub Action](https://github.com/changesets/action). Bumpy replaces both with two CLI commands (`bumpy ci check` + `bumpy ci release`) that run directly in your workflows — no extra repos to trust, no app installation requiring org admin approval.
236
236
  - **Automatic migration** — `bumpy init` detects `.changeset/`, renames it to `.bumpy/`, migrates config, keeps pending files, and offers to uninstall `@changesets/cli`.
237
237
 
238
238
  ## Development
@@ -3,8 +3,8 @@ import { n as exists, t as ensureDir } from "./fs-DnDogVn-.mjs";
3
3
  import { a as loadConfig, o as loadPackageConfig, r as getBumpyDir, s as matchGlob } from "./config-D7Umr-fT.mjs";
4
4
  import { t as discoverPackages } from "./workspace-BHsAPUmC.mjs";
5
5
  import { t as DependencyGraph } from "./dep-graph-DiLeAhl9.mjs";
6
- import { i as writeBumpFile } from "./bump-file-Br2bTaWp.mjs";
7
- import { r as getChangedFiles } from "./git-YDedMddc.mjs";
6
+ import { i as writeBumpFile } from "./bump-file-C3S_bzSf.mjs";
7
+ import { r as getChangedFiles } from "./git-H9S9z6g-.mjs";
8
8
  import { c as ot, d as yt, i as _t, l as pt, o as gt, r as Ot, s as mt, t as unwrap, u as wt } from "./clack-C6bVkGxf.mjs";
9
9
  import { n as slugify, t as randomName } from "./names-C-TuOPbd.mjs";
10
10
  import { t as require_picomatch } from "./picomatch-DMmqYjgq.mjs";
@@ -28,7 +28,7 @@ const defaultFormatter = (ctx) => {
28
28
  const BUILTIN_FORMATTERS = {
29
29
  default: defaultFormatter,
30
30
  github: async () => {
31
- const { createGithubFormatter } = await import("./changelog-github-DkACMj0j.mjs");
31
+ const { createGithubFormatter } = await import("./changelog-github-DZSHX3Tb.mjs");
32
32
  return createGithubFormatter();
33
33
  }
34
34
  };
@@ -39,7 +39,7 @@ const BUILTIN_FORMATTERS = {
39
39
  async function loadFormatter(changelog, rootDir) {
40
40
  const [name, options] = Array.isArray(changelog) ? changelog : [changelog, {}];
41
41
  if (name === "github") {
42
- const { createGithubFormatter } = await import("./changelog-github-DkACMj0j.mjs");
42
+ const { createGithubFormatter } = await import("./changelog-github-DZSHX3Tb.mjs");
43
43
  return createGithubFormatter(options);
44
44
  }
45
45
  if (typeof name === "string" && BUILTIN_FORMATTERS[name]) {
@@ -139,7 +139,7 @@ function updateRange(range, newVersion) {
139
139
  cleanRange = range.slice(protocol.length);
140
140
  }
141
141
  const prefix = cleanRange.match(/^(\^|~|>=|>|<=|<|=)?/)?.[1] ?? "^";
142
- if (cleanRange === "*" || cleanRange === "") return range;
142
+ if (cleanRange === "*" || cleanRange === "" || cleanRange === "^" || cleanRange === "~") return range;
143
143
  return `${protocol}${prefix}${newVersion}`;
144
144
  }
145
145
  //#endregion
@@ -1,4 +1,3 @@
1
- import { n as log } from "./logger-C2dEe5Su.mjs";
2
1
  import { f as writeText, i as listFiles, s as readText } from "./fs-DnDogVn-.mjs";
3
2
  import { r as getBumpyDir } from "./config-D7Umr-fT.mjs";
4
3
  import { i as jsYaml } from "./package-manager-ByJ0wKYh.mjs";
@@ -30,16 +29,21 @@ async function readBumpFiles(rootDir) {
30
29
  const dir = getBumpyDir(rootDir);
31
30
  const files = await listFiles(dir, ".md");
32
31
  const bumpFiles = [];
32
+ const errors = [];
33
33
  for (const file of files) {
34
34
  if (file === "README.md") continue;
35
- const bf = await parseBumpFileFromPath(resolve(dir, file));
36
- if (bf) bumpFiles.push(bf);
35
+ const result = await parseBumpFileFromPath(resolve(dir, file));
36
+ if (result.bumpFile) bumpFiles.push(result.bumpFile);
37
+ errors.push(...result.errors);
37
38
  }
38
39
  const creationOrder = getBumpFileCreationOrder(rootDir);
39
40
  if (creationOrder.size > 0) bumpFiles.sort((a, b) => {
40
41
  return (creationOrder.get(a.id) ?? Infinity) - (creationOrder.get(b.id) ?? Infinity) || a.id.localeCompare(b.id);
41
42
  });
42
- return bumpFiles;
43
+ return {
44
+ bumpFiles,
45
+ errors
46
+ };
43
47
  }
44
48
  /**
45
49
  * Use `git log` to get the commit timestamp when each bump file was first added.
@@ -75,21 +79,47 @@ async function parseBumpFileFromPath(filePath) {
75
79
  }
76
80
  /** Parse bump file content (for testing) */
77
81
  function parseBumpFile(content, id) {
78
- const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
79
- if (!match) return null;
82
+ const errors = [];
83
+ const match = content.match(/^---\n([\s\S]*?)\n?---\n?([\s\S]*)$/);
84
+ if (!match) {
85
+ errors.push(`Bump file "${id}" has no valid frontmatter (expected --- delimiters)`);
86
+ return {
87
+ bumpFile: null,
88
+ errors
89
+ };
90
+ }
80
91
  const frontmatter = match[1];
81
92
  const summary = match[2].trim();
82
- const parsed = jsYaml.load(frontmatter);
83
- if (!parsed || typeof parsed !== "object") return null;
93
+ if (!frontmatter.trim()) return {
94
+ bumpFile: null,
95
+ errors
96
+ };
97
+ let parsed;
98
+ try {
99
+ parsed = jsYaml.load(frontmatter);
100
+ } catch (e) {
101
+ errors.push(`Bump file "${id}" has invalid YAML: ${e instanceof Error ? e.message : e}`);
102
+ return {
103
+ bumpFile: null,
104
+ errors
105
+ };
106
+ }
107
+ if (!parsed || typeof parsed !== "object") {
108
+ errors.push(`Bump file "${id}" has empty or invalid frontmatter`);
109
+ return {
110
+ bumpFile: null,
111
+ errors
112
+ };
113
+ }
84
114
  const releases = [];
85
115
  for (const [name, value] of Object.entries(parsed)) {
86
116
  if (!validatePackageName(name)) {
87
- log.warn(`Skipping invalid package name in bump file "${id}": ${name}`);
117
+ errors.push(`Invalid package name "${name}" in bump file "${id}"`);
88
118
  continue;
89
119
  }
90
120
  if (typeof value === "string") {
91
121
  if (!VALID_BUMP_TYPES.has(value)) {
92
- log.warn(`Skipping unknown bump type "${value}" for ${name} in bump file "${id}"`);
122
+ errors.push(`Unknown bump type "${value}" for "${name}" in bump file "${id}" (expected: major, minor, patch, or none)`);
93
123
  continue;
94
124
  }
95
125
  releases.push({
@@ -99,7 +129,7 @@ function parseBumpFile(content, id) {
99
129
  } else if (value && typeof value === "object") {
100
130
  const obj = value;
101
131
  if (!VALID_BUMP_TYPES.has(obj.bump)) {
102
- log.warn(`Skipping unknown bump type "${obj.bump}" for ${name} in bump file "${id}"`);
132
+ errors.push(`Unknown bump type "${obj.bump}" for "${name}" in bump file "${id}" (expected: major, minor, patch, or none)`);
103
133
  continue;
104
134
  }
105
135
  const release = {
@@ -108,13 +138,19 @@ function parseBumpFile(content, id) {
108
138
  cascade: obj.cascade || {}
109
139
  };
110
140
  releases.push(release);
111
- }
141
+ } else errors.push(`Invalid value for "${name}" in bump file "${id}" — expected a bump type string or object`);
112
142
  }
113
- if (releases.length === 0) return null;
143
+ if (releases.length === 0 && errors.length === 0) return {
144
+ bumpFile: null,
145
+ errors
146
+ };
114
147
  return {
115
- id,
116
- releases,
117
- summary
148
+ bumpFile: releases.length > 0 ? {
149
+ id,
150
+ releases,
151
+ summary
152
+ } : null,
153
+ errors
118
154
  };
119
155
  }
120
156
  /** Write a bump file */
@@ -146,8 +182,12 @@ function extractBumpFileIdsFromChangedFiles(changedFiles) {
146
182
  * Filter bump files to only those added/modified on the current branch.
147
183
  * Also detects empty bump files (no releases) that still exist on disk,
148
184
  * which signal intentionally no releases needed.
185
+ *
186
+ * When `parseErrors` is provided, a file that exists on disk but didn't parse
187
+ * is only treated as an "empty bump file" if it produced no parse errors —
188
+ * otherwise it's a broken file, not an intentionally empty one.
149
189
  */
150
- function filterBranchBumpFiles(allBumpFiles, changedFiles, rootDir) {
190
+ function filterBranchBumpFiles(allBumpFiles, changedFiles, rootDir, parseErrors = []) {
151
191
  const branchBumpFileIds = extractBumpFileIdsFromChangedFiles(changedFiles);
152
192
  const branchBumpFiles = allBumpFiles.filter((bf) => branchBumpFileIds.has(bf.id));
153
193
  let hasEmptyBumpFile = false;
@@ -155,8 +195,10 @@ function filterBranchBumpFiles(allBumpFiles, changedFiles, rootDir) {
155
195
  const parsedIds = new Set(branchBumpFiles.map((bf) => bf.id));
156
196
  const bumpyDir = getBumpyDir(rootDir);
157
197
  for (const id of branchBumpFileIds) if (!parsedIds.has(id) && existsSync(resolve(bumpyDir, `${id}.md`))) {
158
- hasEmptyBumpFile = true;
159
- break;
198
+ if (!parseErrors.some((e) => e.includes(`"${id}"`))) {
199
+ hasEmptyBumpFile = true;
200
+ break;
201
+ }
160
202
  }
161
203
  }
162
204
  return {
@@ -1,8 +1,22 @@
1
1
  import { s as tryRunArgs } from "./shell-CY7OD48z.mjs";
2
2
  //#region src/core/changelog-github.ts
3
+ /** Authors filtered from "Thanks" attribution by default (e.g. bots) */
4
+ /** Authors filtered from "Thanks" attribution by default (e.g. AI/automation bots) */
5
+ const DEFAULT_INTERNAL_AUTHORS = [
6
+ "copilot",
7
+ "app/copilot-swe-agent",
8
+ "claude",
9
+ "dependabot",
10
+ "dependabot[bot]",
11
+ "app/dependabot",
12
+ "renovate[bot]",
13
+ "app/renovate",
14
+ "github-actions[bot]",
15
+ "snyk-bot"
16
+ ];
3
17
  /**
4
18
  * GitHub-enhanced changelog formatter.
5
- * Adds PR links, commit links, and contributor attribution when git/gh info is available.
19
+ * Adds PR links, contributor attribution, and optionally commit links when git/gh info is available.
6
20
  *
7
21
  * Usage in config:
8
22
  * "changelog": "github"
@@ -11,8 +25,9 @@ import { s as tryRunArgs } from "./shell-CY7OD48z.mjs";
11
25
  * "changelog": ["github", { "internalAuthors": ["theoephraim"] }]
12
26
  */
13
27
  function createGithubFormatter(options = {}) {
28
+ const includeCommitLink = options.includeCommitLink ?? false;
14
29
  const thankContributors = options.thankContributors ?? true;
15
- const internalAuthorsSet = new Set((options.internalAuthors ?? []).map((a) => a.toLowerCase()));
30
+ const internalAuthorsSet = new Set([...DEFAULT_INTERNAL_AUTHORS, ...options.internalAuthors ?? []].map((a) => a.toLowerCase()));
16
31
  return async (ctx) => {
17
32
  const { release, bumpFiles, date } = ctx;
18
33
  const repoSlug = options.repo ?? detectRepo();
@@ -29,7 +44,7 @@ function createGithubFormatter(options = {}) {
29
44
  const gitInfo = resolveBumpFileInfo(bf.id, repoSlug, serverUrl, overrides);
30
45
  const summaryLines = cleanSummary.split("\n");
31
46
  const firstLine = linkifyIssueRefs(summaryLines[0], serverUrl, repoSlug);
32
- const prefix = formatPrefix(gitInfo, serverUrl, repoSlug, thankContributors, internalAuthorsSet);
47
+ const prefix = formatPrefix(gitInfo, serverUrl, repoSlug, includeCommitLink, thankContributors, internalAuthorsSet);
33
48
  lines.push(`-${prefix ? ` ${prefix} -` : ""} ${firstLine}`);
34
49
  for (let i = 1; i < summaryLines.length; i++) if (summaryLines[i].trim()) lines.push(` ${linkifyIssueRefs(summaryLines[i], serverUrl, repoSlug)}`);
35
50
  }
@@ -157,10 +172,10 @@ function findBumpFileCommitInfo(bumpFileId, repo) {
157
172
  * Build the prefix portion of a changelog line: PR link, commit link, thanks.
158
173
  * Matches the format used by @changesets/changelog-github.
159
174
  */
160
- function formatPrefix(info, serverUrl, repo, thankContributors, internalAuthors) {
175
+ function formatPrefix(info, serverUrl, repo, includeCommitLink, thankContributors, internalAuthors) {
161
176
  const parts = [];
162
177
  if (info.prNumber && info.prUrl) parts.push(`[#${info.prNumber}](${info.prUrl})`);
163
- if (info.commitHash && repo) {
178
+ if (includeCommitLink && info.commitHash && repo) {
164
179
  const short = info.commitHash.slice(0, 7);
165
180
  parts.push(`[\`${short}\`](${serverUrl}/${repo}/commit/${info.commitHash})`);
166
181
  }
@@ -1,8 +1,8 @@
1
1
  import { n as log, o as __toESM, t as colorize } from "./logger-C2dEe5Su.mjs";
2
2
  import { a as loadConfig, o as loadPackageConfig, r as getBumpyDir } from "./config-D7Umr-fT.mjs";
3
3
  import { n as discoverWorkspace } from "./workspace-BHsAPUmC.mjs";
4
- import { r as readBumpFiles, t as filterBranchBumpFiles } from "./bump-file-Br2bTaWp.mjs";
5
- import { r as getChangedFiles } from "./git-YDedMddc.mjs";
4
+ import { r as readBumpFiles, t as filterBranchBumpFiles } from "./bump-file-C3S_bzSf.mjs";
5
+ import { r as getChangedFiles } from "./git-H9S9z6g-.mjs";
6
6
  import { t as require_picomatch } from "./picomatch-DMmqYjgq.mjs";
7
7
  import { relative } from "node:path";
8
8
  //#region src/commands/check.ts
@@ -25,7 +25,12 @@ async function checkCommand(rootDir, opts = {}) {
25
25
  log.info("No changed files detected.");
26
26
  return;
27
27
  }
28
- const { branchBumpFiles, hasEmptyBumpFile } = filterBranchBumpFiles(await readBumpFiles(rootDir), changedFiles, rootDir);
28
+ const { bumpFiles: allBumpFiles, errors: parseErrors } = await readBumpFiles(rootDir);
29
+ if (parseErrors.length > 0) {
30
+ for (const err of parseErrors) log.error(err);
31
+ process.exit(1);
32
+ }
33
+ const { branchBumpFiles, hasEmptyBumpFile } = filterBranchBumpFiles(allBumpFiles, changedFiles, rootDir);
29
34
  if (hasEmptyBumpFile) {
30
35
  log.success("Empty bump file found — no releases needed.");
31
36
  return;
@@ -4,11 +4,11 @@ import { t as detectPackageManager } from "./package-manager-ByJ0wKYh.mjs";
4
4
  import { n as discoverWorkspace } from "./workspace-BHsAPUmC.mjs";
5
5
  import { t as DependencyGraph } from "./dep-graph-DiLeAhl9.mjs";
6
6
  import { n as runArgs, r as runArgsAsync, s as tryRunArgs } from "./shell-CY7OD48z.mjs";
7
- import { r as readBumpFiles, t as filterBranchBumpFiles } from "./bump-file-Br2bTaWp.mjs";
7
+ import { r as readBumpFiles, t as filterBranchBumpFiles } from "./bump-file-C3S_bzSf.mjs";
8
8
  import { t as assembleReleasePlan } from "./release-plan-CNOuSI-d.mjs";
9
- import { r as getChangedFiles } from "./git-YDedMddc.mjs";
9
+ import { r as getChangedFiles } from "./git-H9S9z6g-.mjs";
10
10
  import { t as randomName } from "./names-C-TuOPbd.mjs";
11
- import { findChangedPackages } from "./check-D_0exKi6.mjs";
11
+ import { findChangedPackages } from "./check-BJL-YDWz.mjs";
12
12
  import { t as resolveCommitMessage } from "./commit-message-BwsowSds.mjs";
13
13
  import { createHash } from "node:crypto";
14
14
  //#region src/commands/ci.ts
@@ -79,7 +79,7 @@ async function ciCheckCommand(rootDir, opts) {
79
79
  const config = await loadConfig(rootDir);
80
80
  const { packages } = await discoverWorkspace(rootDir, config);
81
81
  const depGraph = new DependencyGraph(packages);
82
- const allBumpFiles = await readBumpFiles(rootDir);
82
+ const { bumpFiles: allBumpFiles, errors: parseErrors } = await readBumpFiles(rootDir);
83
83
  if (detectPrBranch(rootDir) === config.versionPr.branch) {
84
84
  log.dim(" Skipping — this is the version PR branch.");
85
85
  return;
@@ -89,18 +89,22 @@ async function ciCheckCommand(rootDir, opts) {
89
89
  const prNumber = detectPrNumber();
90
90
  const pm = await detectPackageManager(rootDir);
91
91
  const changedFiles = getChangedFiles(rootDir, config.baseBranch);
92
- const { branchBumpFiles: prBumpFiles, hasEmptyBumpFile } = filterBranchBumpFiles(allBumpFiles, changedFiles, rootDir);
93
- if (hasEmptyBumpFile) {
94
- log.success("Empty bump file found — no releases needed.");
95
- if (shouldComment && prNumber) await postOrUpdatePrComment(prNumber, formatNoBumpFilesComment(detectPrBranch(rootDir), pm), rootDir, opts.patComments);
96
- return;
97
- }
92
+ const { branchBumpFiles: prBumpFiles, hasEmptyBumpFile } = filterBranchBumpFiles(allBumpFiles, changedFiles, rootDir, parseErrors);
93
+ if (parseErrors.length > 0) for (const err of parseErrors) log.error(err);
98
94
  if (prBumpFiles.length === 0) {
99
- const willFail = !opts.noFail;
100
- const msg = "No bump files found in this PR.";
95
+ if (hasEmptyBumpFile && parseErrors.length === 0) {
96
+ log.success("Empty bump file found no releases needed.");
97
+ if (shouldComment && prNumber) await postOrUpdatePrComment(prNumber, formatEmptyBumpFileComment(), rootDir, opts.patComments);
98
+ return;
99
+ }
100
+ const willFail = !opts.noFail || parseErrors.length > 0;
101
+ const msg = parseErrors.length > 0 ? "Bump file(s) found but failed to parse — see errors above." : "No bump files found in this PR.";
101
102
  if (willFail) log.error(msg);
102
103
  else log.warn(msg);
103
- if (shouldComment && prNumber) await postOrUpdatePrComment(prNumber, formatNoBumpFilesComment(detectPrBranch(rootDir), pm), rootDir, opts.patComments);
104
+ if (shouldComment && prNumber) {
105
+ const prBranch = detectPrBranch(rootDir);
106
+ await postOrUpdatePrComment(prNumber, parseErrors.length > 0 ? formatBumpFileErrorsComment(parseErrors, prBranch, pm) : formatNoBumpFilesComment(prBranch, pm), rootDir, opts.patComments);
107
+ }
104
108
  if (willFail) process.exit(1);
105
109
  return;
106
110
  }
@@ -111,7 +115,8 @@ async function ciCheckCommand(rootDir, opts) {
111
115
  console.log(` ${r.name}: ${r.oldVersion} → ${colorize(r.newVersion, "cyan")}${tag}`);
112
116
  }
113
117
  if (plan.warnings.length > 0) for (const w of plan.warnings) log.warn(w);
114
- if (shouldComment && prNumber) await postOrUpdatePrComment(prNumber, formatReleasePlanComment(plan, prBumpFiles, prNumber, detectPrBranch(rootDir), pm, plan.warnings), rootDir, opts.patComments);
118
+ if (shouldComment && prNumber) await postOrUpdatePrComment(prNumber, formatReleasePlanComment(plan, prBumpFiles, prNumber, detectPrBranch(rootDir), pm, plan.warnings, parseErrors), rootDir, opts.patComments);
119
+ if (parseErrors.length > 0 && !opts.noFail) process.exit(1);
115
120
  const coveredPackages = new Set(plan.releases.map((r) => r.name));
116
121
  const missing = (await findChangedPackages(changedFiles, packages, rootDir, config)).filter((name) => !coveredPackages.has(name));
117
122
  if (missing.length > 0) {
@@ -129,10 +134,14 @@ async function ciReleaseCommand(rootDir, opts) {
129
134
  ensureGitIdentity(rootDir, config);
130
135
  const { packages } = await discoverWorkspace(rootDir, config);
131
136
  const depGraph = new DependencyGraph(packages);
132
- const bumpFiles = await readBumpFiles(rootDir);
137
+ const { bumpFiles, errors: releaseParseErrors } = await readBumpFiles(rootDir);
138
+ if (releaseParseErrors.length > 0) {
139
+ for (const err of releaseParseErrors) log.error(err);
140
+ throw new Error("Bump file parse errors must be fixed before releasing.");
141
+ }
133
142
  if (bumpFiles.length === 0) {
134
143
  log.info("No pending bump files — checking for unpublished packages...");
135
- const { publishCommand } = await import("./publish-CPZwqyWh.mjs");
144
+ const { publishCommand } = await import("./publish-DGSV607z.mjs");
136
145
  await publishCommand(rootDir, { tag: opts.tag });
137
146
  return;
138
147
  }
@@ -146,7 +155,7 @@ async function ciReleaseCommand(rootDir, opts) {
146
155
  }
147
156
  async function autoPublish(rootDir, config, plan, tag) {
148
157
  log.step("Running bumpy version...");
149
- const { versionCommand } = await import("./version-CnXcbqi1.mjs");
158
+ const { versionCommand } = await import("./version-BXrP4TIO.mjs");
150
159
  await versionCommand(rootDir);
151
160
  log.step("Committing version changes...");
152
161
  runArgs([
@@ -175,7 +184,7 @@ async function autoPublish(rootDir, config, plan, tag) {
175
184
  ], { cwd: rootDir });
176
185
  }
177
186
  log.step("Running bumpy publish...");
178
- const { publishCommand } = await import("./publish-CPZwqyWh.mjs");
187
+ const { publishCommand } = await import("./publish-DGSV607z.mjs");
179
188
  await publishCommand(rootDir, { tag });
180
189
  }
181
190
  /**
@@ -341,7 +350,7 @@ async function createVersionPr(rootDir, plan, config, packageDirs, branchName, p
341
350
  branch
342
351
  ], { cwd: rootDir });
343
352
  log.step("Running bumpy version...");
344
- const { versionCommand } = await import("./version-CnXcbqi1.mjs");
353
+ const { versionCommand } = await import("./version-BXrP4TIO.mjs");
345
354
  await versionCommand(rootDir);
346
355
  runArgs([
347
356
  "git",
@@ -371,9 +380,10 @@ async function createVersionPr(rootDir, plan, config, packageDirs, branchName, p
371
380
  input: await resolveCommitMessage(config.versionCommitMessage, plan, rootDir)
372
381
  });
373
382
  pushWithToken(rootDir, branch, config);
374
- const prBody = formatVersionPrBody(plan, config.versionPr.preamble, packageDirs);
383
+ const repo = process.env.GITHUB_REPOSITORY;
375
384
  if (existingPr) {
376
385
  const validPr = validatePrNumber(existingPr);
386
+ const prBody = formatVersionPrBody(plan, config.versionPr.preamble, packageDirs, repo, validPr);
377
387
  log.step(`Updating existing PR #${validPr}...`);
378
388
  await withPatToken(!!patPr, () => runArgsAsync([
379
389
  "gh",
@@ -392,6 +402,7 @@ async function createVersionPr(rootDir, plan, config, packageDirs, branchName, p
392
402
  } else {
393
403
  log.step("Creating version PR...");
394
404
  const prTitle = config.versionPr.title;
405
+ const prBody = formatVersionPrBody(plan, config.versionPr.preamble, packageDirs, repo, null);
395
406
  const result = await withPatToken(!!patPr, () => runArgsAsync([
396
407
  "gh",
397
408
  "pr",
@@ -409,6 +420,23 @@ async function createVersionPr(rootDir, plan, config, packageDirs, branchName, p
409
420
  input: prBody
410
421
  }));
411
422
  log.success(`🐸 Created PR: ${result}`);
423
+ if (repo) {
424
+ const newPrNumber = result?.match(/\/pull\/(\d+)/)?.[1];
425
+ if (newPrNumber) {
426
+ const updatedBody = formatVersionPrBody(plan, config.versionPr.preamble, packageDirs, repo, newPrNumber);
427
+ await withPatToken(!!patPr, () => runArgsAsync([
428
+ "gh",
429
+ "pr",
430
+ "edit",
431
+ newPrNumber,
432
+ "--body-file",
433
+ "-"
434
+ ], {
435
+ cwd: rootDir,
436
+ input: updatedBody
437
+ }));
438
+ }
439
+ }
412
440
  if (!patPr) pushWithToken(rootDir, branch, config);
413
441
  }
414
442
  runArgs([
@@ -439,7 +467,7 @@ function pmRunCommand(pm) {
439
467
  if (pm === "yarn") return "yarn bumpy";
440
468
  return "npx bumpy";
441
469
  }
442
- function formatReleasePlanComment(plan, bumpFiles, prNumber, prBranch, pm, warnings = []) {
470
+ function formatReleasePlanComment(plan, bumpFiles, prNumber, prBranch, pm, warnings = [], parseErrors = []) {
443
471
  const repo = process.env.GITHUB_REPOSITORY;
444
472
  const lines = [];
445
473
  const preamble = [
@@ -477,12 +505,18 @@ function formatReleasePlanComment(plan, bumpFiles, prNumber, prBranch, pm, warni
477
505
  const filename = `${bf.id}.md`;
478
506
  const parts = [`\`${filename}\``];
479
507
  if (repo) {
480
- parts.push(`([view diff](https://github.com/${repo}/pull/${prNumber}/files#diff-.bumpy/${filename}))`);
508
+ parts.push(`([view diff](https://github.com/${repo}/pull/${prNumber}/changes#diff-${sha256Hex(`.bumpy/${filename}`)}))`);
481
509
  if (prBranch) parts.push(`([edit](https://github.com/${repo}/edit/${prBranch}/.bumpy/${filename}))`);
482
510
  }
483
511
  lines.push(`- ${parts.join(" ")}`);
484
512
  }
485
513
  lines.push("");
514
+ if (parseErrors.length > 0) {
515
+ lines.push("#### Errors");
516
+ lines.push("");
517
+ for (const e of parseErrors) lines.push(`> :x: ${e}`);
518
+ lines.push("");
519
+ }
486
520
  if (warnings.length > 0) {
487
521
  lines.push("#### Warnings");
488
522
  lines.push("");
@@ -496,6 +530,41 @@ function formatReleasePlanComment(plan, bumpFiles, prNumber, prBranch, pm, warni
496
530
  lines.push(`_This comment is maintained by [bumpy](https://bumpy.varlock.dev)._`);
497
531
  return lines.join("\n");
498
532
  }
533
+ function formatBumpFileErrorsComment(errors, prBranch, pm) {
534
+ const runCmd = pmRunCommand(pm);
535
+ const lines = [
536
+ `<a href="https://bumpy.varlock.dev"><img src="${FROG_IMG_BASE}/frog-neutral.png" alt="bumpy-frog" width="60" align="left" style="image-rendering: pixelated;" title="Hi! I'm bumpy!" /></a>`,
537
+ "",
538
+ "**This PR has bump file(s) with errors that need to be fixed.**",
539
+ "<br clear=\"left\" />\n",
540
+ "#### Errors",
541
+ "",
542
+ ...errors.map((e) => `> :x: ${e}`),
543
+ "",
544
+ "Please fix the errors above or recreate the bump file:\n",
545
+ "```bash",
546
+ `${runCmd} add`,
547
+ "```"
548
+ ];
549
+ const addLink = buildAddBumpFileLink(prBranch);
550
+ if (addLink) {
551
+ lines.push("");
552
+ lines.push(`Or [click here to add a bump file](${addLink}) directly on GitHub.`);
553
+ }
554
+ lines.push("\n---");
555
+ lines.push(`_This comment is maintained by [bumpy](https://bumpy.varlock.dev)._`);
556
+ return lines.join("\n");
557
+ }
558
+ function formatEmptyBumpFileComment() {
559
+ return [
560
+ `<a href="https://bumpy.varlock.dev"><img src="${FROG_IMG_BASE}/frog-neutral.png" alt="bumpy-frog" width="60" align="left" style="image-rendering: pixelated;" title="Hi! I'm bumpy!" /></a>`,
561
+ "",
562
+ "**This PR includes an empty bump file — no version bump is needed.** :white_check_mark:",
563
+ "<br clear=\"left\" />",
564
+ "\n---",
565
+ `_This comment is maintained by [bumpy](https://bumpy.varlock.dev)._`
566
+ ].join("\n");
567
+ }
499
568
  function formatNoBumpFilesComment(prBranch, pm) {
500
569
  const runCmd = pmRunCommand(pm);
501
570
  const lines = [
@@ -518,18 +587,19 @@ function formatNoBumpFilesComment(prBranch, pm) {
518
587
  return lines.join("\n");
519
588
  }
520
589
  function bumpSectionHeader(type) {
521
- return `### ${`<img src="${FROG_IMG_BASE}/frog-${type}.png" alt="${type}" width="52" style="image-rendering: pixelated;" align="right" />`} ${type.charAt(0).toUpperCase() + type.slice(1)} releases`;
590
+ const label = `${type.charAt(0).toUpperCase() + type.slice(1)} releases`;
591
+ return `### ${`<a href="https://bumpy.varlock.dev" title="${label}"><img src="${FROG_IMG_BASE}/frog-${type}.png" alt="${type}" width="52" style="image-rendering: pixelated;" align="right" /></a>`} ${label}`;
522
592
  }
523
593
  /** Build inline diff links for a package's changed files in the PR */
524
- function buildDiffLinks(pkgDir) {
525
- const pkgJsonPath = `${pkgDir}/package.json`;
526
- const changelogPath = `${pkgDir}/CHANGELOG.md`;
527
- return ` <sub>${[`[package.json](#diff-${sha256Hex(pkgJsonPath)})`, `[CHANGELOG.md](#diff-${sha256Hex(changelogPath)})`].join(" · ")}</sub>`;
594
+ function buildDiffLinks(pkgDir, changesBaseUrl) {
595
+ if (!changesBaseUrl) return "";
596
+ return ` <sub>[CHANGELOG.md](${changesBaseUrl}#diff-${sha256Hex(`${pkgDir}/CHANGELOG.md`)})</sub>`;
528
597
  }
529
598
  function sha256Hex(input) {
530
599
  return createHash("sha256").update(input).digest("hex");
531
600
  }
532
- function formatVersionPrBody(plan, preamble, packageDirs) {
601
+ function formatVersionPrBody(plan, preamble, packageDirs, repo, prNumber) {
602
+ const changesBaseUrl = repo && prNumber ? `https://github.com/${repo}/pull/${prNumber}/changes` : null;
533
603
  const lines = [];
534
604
  lines.push(preamble);
535
605
  lines.push("");
@@ -551,13 +621,13 @@ function formatVersionPrBody(plan, preamble, packageDirs) {
551
621
  for (const r of releases) {
552
622
  const suffix = r.isDependencyBump ? " _(dep)_" : r.isCascadeBump ? " _(cascade)_" : "";
553
623
  const pkgDir = packageDirs.get(r.name);
554
- const diffLinks = pkgDir ? buildDiffLinks(pkgDir) : "";
624
+ const diffLinks = pkgDir ? buildDiffLinks(pkgDir, changesBaseUrl) : "";
555
625
  lines.push(`#### \`${r.name}\` ${r.oldVersion} → **${r.newVersion}**${suffix}${diffLinks}`);
556
626
  lines.push("");
557
627
  const relevantBumpFiles = plan.bumpFiles.filter((bf) => r.bumpFiles.includes(bf.id));
558
628
  if (relevantBumpFiles.length > 0) {
559
629
  for (const bf of relevantBumpFiles) if (bf.summary) {
560
- const bfLink = ` ([bump file](#diff-${sha256Hex(`.bumpy/${bf.id}.md`)}))`;
630
+ const bfLink = changesBaseUrl ? ` ([bump file](${changesBaseUrl}#diff-${sha256Hex(`.bumpy/${bf.id}.md`)}))` : "";
561
631
  const summaryLines = bf.summary.split("\n");
562
632
  lines.push(`- ${summaryLines[0]}${bfLink}`);
563
633
  for (let i = 1; i < summaryLines.length; i++) if (summaryLines[i].trim()) lines.push(` ${summaryLines[i]}`);
package/dist/cli.mjs CHANGED
@@ -31,7 +31,7 @@ async function main() {
31
31
  }
32
32
  case "add": {
33
33
  const rootDir = await findRoot();
34
- const { addCommand } = await import("./add-yP81c9_q.mjs");
34
+ const { addCommand } = await import("./add-DF6bawDT.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-skGX8uU7.mjs");
45
+ const { statusCommand } = await import("./status-S2ztf_8E.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-CnXcbqi1.mjs");
57
+ const { versionCommand } = await import("./version-BXrP4TIO.mjs");
58
58
  await versionCommand(rootDir, { commit: flags.commit === true });
59
59
  break;
60
60
  }
61
61
  case "generate": {
62
62
  const rootDir = await findRoot();
63
- const { generateCommand } = await import("./generate-BOLrTYWR.mjs");
63
+ const { generateCommand } = await import("./generate-D93b3NAD.mjs");
64
64
  await generateCommand(rootDir, {
65
65
  from: flags.from,
66
66
  dryRun: flags["dry-run"] === true,
@@ -70,7 +70,7 @@ async function main() {
70
70
  }
71
71
  case "check": {
72
72
  const rootDir = await findRoot();
73
- const { checkCommand } = await import("./check-D_0exKi6.mjs");
73
+ const { checkCommand } = await import("./check-BJL-YDWz.mjs");
74
74
  await checkCommand(rootDir, {
75
75
  strict: flags.strict === true,
76
76
  noFail: flags["no-fail"] === true
@@ -82,7 +82,7 @@ async function main() {
82
82
  const subcommand = args[1];
83
83
  const ciFlags = parseFlags(args.slice(2));
84
84
  if (subcommand === "check") {
85
- const { ciCheckCommand } = await import("./ci-CvaikKX1.mjs");
85
+ const { ciCheckCommand } = await import("./ci-C88ecvIP.mjs");
86
86
  await ciCheckCommand(rootDir, {
87
87
  comment: ciFlags.comment !== void 0 ? ciFlags.comment === true : void 0,
88
88
  strict: ciFlags.strict === true,
@@ -90,7 +90,7 @@ async function main() {
90
90
  patComments: ciFlags["pat-comments"] === true
91
91
  });
92
92
  } else if (subcommand === "release") {
93
- const { ciReleaseCommand } = await import("./ci-CvaikKX1.mjs");
93
+ const { ciReleaseCommand } = await import("./ci-C88ecvIP.mjs");
94
94
  await ciReleaseCommand(rootDir, {
95
95
  mode: ciFlags["auto-publish"] === true ? "auto-publish" : "version-pr",
96
96
  tag: ciFlags.tag,
@@ -108,7 +108,7 @@ async function main() {
108
108
  }
109
109
  case "publish": {
110
110
  const rootDir = await findRoot();
111
- const { publishCommand } = await import("./publish-CPZwqyWh.mjs");
111
+ const { publishCommand } = await import("./publish-DGSV607z.mjs");
112
112
  await publishCommand(rootDir, {
113
113
  dryRun: flags["dry-run"] === true,
114
114
  tag: flags.tag,
@@ -132,7 +132,7 @@ async function main() {
132
132
  }
133
133
  case "--version":
134
134
  case "-v":
135
- console.log(`bumpy 1.2.0`);
135
+ console.log(`bumpy 1.2.1`);
136
136
  break;
137
137
  case "help":
138
138
  case "--help":
@@ -152,7 +152,7 @@ async function main() {
152
152
  }
153
153
  function printHelp() {
154
154
  console.log(`
155
- ${colorize(`🐸 bumpy v1.2.0`, "bold")} - Modern monorepo versioning
155
+ ${colorize(`🐸 bumpy v1.2.1`, "bold")} - Modern monorepo versioning
156
156
 
157
157
  Usage: bumpy <command> [options]
158
158
 
@@ -3,8 +3,8 @@ import { t as ensureDir } from "./fs-DnDogVn-.mjs";
3
3
  import { a as loadConfig, r as getBumpyDir } from "./config-D7Umr-fT.mjs";
4
4
  import { t as discoverPackages } from "./workspace-BHsAPUmC.mjs";
5
5
  import { s as tryRunArgs } from "./shell-CY7OD48z.mjs";
6
- import { i as writeBumpFile } from "./bump-file-Br2bTaWp.mjs";
7
- import { i as getFilesChangedInCommit, n as getBranchCommits } from "./git-YDedMddc.mjs";
6
+ import { i as writeBumpFile } from "./bump-file-C3S_bzSf.mjs";
7
+ import { a as getFilesChangedInCommit, n as getBranchCommits } from "./git-H9S9z6g-.mjs";
8
8
  import { n as slugify, t as randomName } from "./names-C-TuOPbd.mjs";
9
9
  import { relative } from "node:path";
10
10
  //#region src/commands/generate.ts
@@ -26,6 +26,15 @@ function hasUncommittedChanges(opts) {
26
26
  ], opts);
27
27
  return result !== null && result.length > 0;
28
28
  }
29
+ /** Get the current branch name */
30
+ function getCurrentBranch(opts) {
31
+ return tryRunArgs([
32
+ "git",
33
+ "rev-parse",
34
+ "--abbrev-ref",
35
+ "HEAD"
36
+ ], opts);
37
+ }
29
38
  /** Check if a tag already exists */
30
39
  function tagExists(tag, opts) {
31
40
  return tryRunArgs([
@@ -127,4 +136,4 @@ function listTags(pattern, opts) {
127
136
  return result.split("\n").filter(Boolean);
128
137
  }
129
138
  //#endregion
130
- export { hasUncommittedChanges as a, tagExists as c, getFilesChangedInCommit as i, getBranchCommits as n, listTags as o, getChangedFiles as r, pushWithTags as s, createTag as t };
139
+ export { getFilesChangedInCommit as a, pushWithTags as c, getCurrentBranch as i, tagExists as l, getBranchCommits as n, hasUncommittedChanges as o, getChangedFiles as r, listTags as s, createTag as t };
package/dist/index.d.mts CHANGED
@@ -193,10 +193,18 @@ declare class DependencyGraph {
193
193
  }
194
194
  //#endregion
195
195
  //#region src/core/bump-file.d.ts
196
+ interface ReadBumpFilesResult {
197
+ bumpFiles: BumpFile[];
198
+ errors: string[];
199
+ }
196
200
  /** Read all bump files from .bumpy/ directory, sorted by git creation order */
197
- declare function readBumpFiles(rootDir: string): Promise<BumpFile[]>;
201
+ declare function readBumpFiles(rootDir: string): Promise<ReadBumpFilesResult>;
202
+ interface BumpFileParseResult {
203
+ bumpFile: BumpFile | null;
204
+ errors: string[];
205
+ }
198
206
  /** Parse bump file content (for testing) */
199
- declare function parseBumpFile(content: string, id: string): BumpFile | null;
207
+ declare function parseBumpFile(content: string, id: string): BumpFileParseResult;
200
208
  /** Write a bump file */
201
209
  declare function writeBumpFile(rootDir: string, filename: string, releases: BumpFileRelease[], summary: string): Promise<string>;
202
210
  //#endregion
@@ -245,6 +253,8 @@ declare function prependToChangelog(existingContent: string, newEntry: string):
245
253
  interface GithubChangelogOptions {
246
254
  /** "owner/repo" — auto-detected from gh CLI if not provided */
247
255
  repo?: string;
256
+ /** Whether to include commit hash links in changelog entries (default: false) */
257
+ includeCommitLink?: boolean;
248
258
  /** Whether to include "Thanks @user" messages for contributors (default: true) */
249
259
  thankContributors?: boolean;
250
260
  /** GitHub usernames (without @) to skip "Thanks" messages for (e.g. internal team members) */
@@ -288,4 +298,4 @@ interface PublishResult {
288
298
  */
289
299
  declare function publishPackages(releasePlan: ReleasePlan, packages: Map<string, WorkspacePackage>, depGraph: DependencyGraph, config: BumpyConfig, rootDir: string, opts?: PublishOptions, catalogs?: CatalogMap, detectedPm?: PackageManager): Promise<PublishResult>;
290
300
  //#endregion
291
- 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 };
301
+ export { BUMP_LEVELS, BumpFile, type BumpFileParseResult, 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, type ReadBumpFilesResult, 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
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-D7Umr-fT.mjs";
2
2
  import { t as discoverPackages } from "./workspace-BHsAPUmC.mjs";
3
3
  import { t as DependencyGraph } from "./dep-graph-DiLeAhl9.mjs";
4
- import { i as writeBumpFile, n as parseBumpFile, r as readBumpFiles } from "./bump-file-Br2bTaWp.mjs";
4
+ import { i as writeBumpFile, n as parseBumpFile, r as readBumpFiles } from "./bump-file-C3S_bzSf.mjs";
5
5
  import { n as satisfies, r as stripProtocol, t as bumpVersion } from "./semver-BJzWIuRz.mjs";
6
6
  import { t as assembleReleasePlan } from "./release-plan-CNOuSI-d.mjs";
7
- import { a as prependToChangelog, i as loadFormatter, n as defaultFormatter, r as generateChangelogEntry, t as applyReleasePlan } from "./apply-release-plan-CPzu6JcF.mjs";
8
- import { t as publishPackages } from "./publish-pipeline-BFt96o_h.mjs";
7
+ import { a as prependToChangelog, i as loadFormatter, n as defaultFormatter, r as generateChangelogEntry, t as applyReleasePlan } from "./apply-release-plan-B1Wwx3HG.mjs";
8
+ import { t as publishPackages } from "./publish-pipeline-DiwZZ5AF.mjs";
9
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 };
@@ -4,8 +4,8 @@ import { n as detectWorkspaces } from "./package-manager-ByJ0wKYh.mjs";
4
4
  import { n as discoverWorkspace } from "./workspace-BHsAPUmC.mjs";
5
5
  import { t as DependencyGraph } from "./dep-graph-DiLeAhl9.mjs";
6
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-BFt96o_h.mjs";
7
+ import { c as pushWithTags, o as hasUncommittedChanges, s as listTags } from "./git-H9S9z6g-.mjs";
8
+ import { t as publishPackages } from "./publish-pipeline-DiwZZ5AF.mjs";
9
9
  //#region src/core/github-release.ts
10
10
  /** Get the current HEAD commit SHA */
11
11
  function getHeadSha(rootDir) {
@@ -3,7 +3,7 @@ import { a as readJson, u as updateJsonNestedField } from "./fs-DnDogVn-.mjs";
3
3
  import { r as resolveCatalogDep } from "./package-manager-ByJ0wKYh.mjs";
4
4
  import { i as runAsync, o as sq, r as runArgsAsync, s as tryRunArgs } from "./shell-CY7OD48z.mjs";
5
5
  import { r as stripProtocol } from "./semver-BJzWIuRz.mjs";
6
- import { c as tagExists, t as createTag } from "./git-YDedMddc.mjs";
6
+ import { l as tagExists, t as createTag } from "./git-H9S9z6g-.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";
@@ -2,14 +2,16 @@ import { n as log, t as colorize } from "./logger-C2dEe5Su.mjs";
2
2
  import { a as loadConfig } from "./config-D7Umr-fT.mjs";
3
3
  import { t as discoverPackages } from "./workspace-BHsAPUmC.mjs";
4
4
  import { t as DependencyGraph } from "./dep-graph-DiLeAhl9.mjs";
5
- import { r as readBumpFiles } from "./bump-file-Br2bTaWp.mjs";
5
+ import { r as readBumpFiles, t as filterBranchBumpFiles } from "./bump-file-C3S_bzSf.mjs";
6
6
  import { t as assembleReleasePlan } from "./release-plan-CNOuSI-d.mjs";
7
+ import { i as getCurrentBranch, r as getChangedFiles } from "./git-H9S9z6g-.mjs";
7
8
  //#region src/commands/status.ts
8
9
  async function statusCommand(rootDir, opts) {
9
10
  const config = await loadConfig(rootDir);
10
11
  const packages = await discoverPackages(rootDir, config);
11
12
  const depGraph = new DependencyGraph(packages);
12
- const bumpFiles = await readBumpFiles(rootDir);
13
+ const { bumpFiles, errors: parseErrors } = await readBumpFiles(rootDir);
14
+ if (parseErrors.length > 0) for (const err of parseErrors) log.error(err);
13
15
  if (bumpFiles.length === 0) {
14
16
  if (opts.json) console.log(JSON.stringify({
15
17
  bumpFiles: [],
@@ -20,6 +22,9 @@ async function statusCommand(rootDir, opts) {
20
22
  process.exit(1);
21
23
  }
22
24
  const plan = assembleReleasePlan(bumpFiles, packages, depGraph, config);
25
+ let branchBumpFileIds;
26
+ const currentBranch = getCurrentBranch({ cwd: rootDir });
27
+ if (currentBranch && currentBranch !== config.baseBranch) branchBumpFileIds = filterBranchBumpFiles(bumpFiles, getChangedFiles(rootDir, config.baseBranch), rootDir).branchBumpFileIds;
23
28
  let releases = plan.releases;
24
29
  if (opts.bumpType) {
25
30
  const types = opts.bumpType.split(",").map((t) => t.trim());
@@ -38,18 +43,25 @@ async function statusCommand(rootDir, opts) {
38
43
  releases: bf.releases.map((r) => ({
39
44
  name: r.name,
40
45
  type: r.type
41
- }))
42
- })),
43
- releases: releases.map((r) => ({
44
- name: r.name,
45
- type: r.type,
46
- oldVersion: r.oldVersion,
47
- newVersion: r.newVersion,
48
- dir: packages.get(r.name)?.relativeDir,
49
- bumpFiles: r.bumpFiles,
50
- isDependencyBump: r.isDependencyBump,
51
- isCascadeBump: r.isCascadeBump
46
+ })),
47
+ ...branchBumpFileIds ? { inCurrentBranch: branchBumpFileIds.has(bf.id) } : {}
52
48
  })),
49
+ releases: releases.map((r) => {
50
+ const pkg = packages.get(r.name);
51
+ const pkgConfig = pkg?.bumpy || {};
52
+ return {
53
+ name: r.name,
54
+ type: r.type,
55
+ oldVersion: r.oldVersion,
56
+ newVersion: r.newVersion,
57
+ dir: pkg?.relativeDir,
58
+ bumpFiles: r.bumpFiles,
59
+ isDependencyBump: r.isDependencyBump,
60
+ isCascadeBump: r.isCascadeBump,
61
+ ...branchBumpFileIds ? { inCurrentBranch: r.bumpFiles.some((id) => branchBumpFileIds.has(id)) } : {},
62
+ publishTargets: getPublishTargets(pkg, pkgConfig, config)
63
+ };
64
+ }),
53
65
  packageNames: releases.map((r) => r.name)
54
66
  };
55
67
  console.log(JSON.stringify(jsonOutput, null, 2));
@@ -106,5 +118,14 @@ function printRelease(r, packages) {
106
118
  const suffix = r.isDependencyBump ? colorize(" ← dependency bump", "dim") : r.isCascadeBump ? colorize(" ← cascade", "dim") : "";
107
119
  console.log(` ${r.name}: ${r.oldVersion} → ${colorize(r.newVersion, "cyan")}${suffix}${dir}`);
108
120
  }
121
+ /** Determine which publish targets a package will use */
122
+ function getPublishTargets(pkg, pkgConfig, _config) {
123
+ if (!pkg) return [];
124
+ if (pkg.private && !pkgConfig.publishCommand) return [];
125
+ const targets = [];
126
+ if (pkgConfig.publishCommand) targets.push("custom");
127
+ if (!pkgConfig.publishCommand && !pkgConfig.skipNpmPublish) targets.push("npm");
128
+ return targets;
129
+ }
109
130
  //#endregion
110
131
  export { statusCommand };
@@ -4,16 +4,20 @@ import { n as detectWorkspaces } from "./package-manager-ByJ0wKYh.mjs";
4
4
  import { t as discoverPackages } from "./workspace-BHsAPUmC.mjs";
5
5
  import { t as DependencyGraph } from "./dep-graph-DiLeAhl9.mjs";
6
6
  import { n as runArgs, s as tryRunArgs } from "./shell-CY7OD48z.mjs";
7
- import { r as readBumpFiles } from "./bump-file-Br2bTaWp.mjs";
7
+ import { r as readBumpFiles } from "./bump-file-C3S_bzSf.mjs";
8
8
  import { t as assembleReleasePlan } from "./release-plan-CNOuSI-d.mjs";
9
- import { t as applyReleasePlan } from "./apply-release-plan-CPzu6JcF.mjs";
9
+ import { t as applyReleasePlan } from "./apply-release-plan-B1Wwx3HG.mjs";
10
10
  import { t as resolveCommitMessage } from "./commit-message-BwsowSds.mjs";
11
11
  //#region src/commands/version.ts
12
12
  async function versionCommand(rootDir, opts = {}) {
13
13
  const config = await loadConfig(rootDir);
14
14
  const packages = await discoverPackages(rootDir, config);
15
15
  const depGraph = new DependencyGraph(packages);
16
- const bumpFiles = await readBumpFiles(rootDir);
16
+ const { bumpFiles, errors: parseErrors } = await readBumpFiles(rootDir);
17
+ if (parseErrors.length > 0) {
18
+ for (const err of parseErrors) log.error(err);
19
+ throw new Error("Bump file parse errors must be fixed before versioning.");
20
+ }
17
21
  if (bumpFiles.length === 0) {
18
22
  log.info("No pending bump files.");
19
23
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@varlock/bumpy",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "Modern monorepo versioning and changelog tool",
5
5
  "keywords": [
6
6
  "bump",