@varlock/bumpy 1.2.0 → 1.2.2
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 +49 -34
- package/dist/{add-yP81c9_q.mjs → add-DEqGa5gI.mjs} +8 -8
- package/dist/apply-release-plan-Bi9OSWks.mjs +57 -0
- package/dist/{bump-file-Br2bTaWp.mjs → bump-file-BTsntOO-.mjs} +63 -24
- package/dist/{changelog-github-DkACMj0j.mjs → changelog-github-CEaDCtTk.mjs} +49 -18
- package/dist/changelog-xKuL0IKx.mjs +109 -0
- package/dist/{check-D_0exKi6.mjs → check-D3eXRyKJ.mjs} +12 -7
- package/dist/{ci-CvaikKX1.mjs → ci-BVTwTUUK.mjs} +131 -40
- package/dist/{ci-setup-CARJFhcE.mjs → ci-setup-D1NCzbNH.mjs} +3 -3
- package/dist/cli.mjs +14 -14
- package/dist/{config-D7Umr-fT.mjs → config-CJIj8xG3.mjs} +2 -80
- package/dist/{generate-BOLrTYWR.mjs → generate-wHN6Ll6p.mjs} +6 -6
- package/dist/{git-YDedMddc.mjs → git-D0__HP86.mjs} +11 -2
- package/dist/index.d.mts +14 -4
- package/dist/index.mjs +10 -8
- package/dist/{init-DJhMaceS.mjs → init-DND7zRGD.mjs} +3 -3
- package/dist/{publish-CPZwqyWh.mjs → publish-BwidFqbo.mjs} +9 -9
- package/dist/{publish-pipeline-BFt96o_h.mjs → publish-pipeline-BvLIu7WF.mjs} +4 -4
- package/dist/{release-plan-CNOuSI-d.mjs → release-plan-21H89Cx1.mjs} +3 -2
- package/dist/{status-skGX8uU7.mjs → status-CDGxgXWd.mjs} +39 -18
- package/dist/types-CSM0c2-m.mjs +80 -0
- package/dist/{version-CnXcbqi1.mjs → version-ClkaCNTE.mjs} +14 -10
- package/dist/{workspace-BHsAPUmC.mjs → workspace-c9-TqXed.mjs} +2 -2
- package/package.json +1 -1
- package/dist/apply-release-plan-CPzu6JcF.mjs +0 -146
- /package/dist/{ai-STKnq09z.mjs → ai-C66IfTzs.mjs} +0 -0
- /package/dist/{clack-C6bVkGxf.mjs → clack-CJT1JFFa.mjs} +0 -0
- /package/dist/{commit-message-BwsowSds.mjs → commit-message-DOIfDxfj.mjs} +0 -0
- /package/dist/{dep-graph-DiLeAhl9.mjs → dep-graph-E-9-eQ2J.mjs} +0 -0
- /package/dist/{names-C-TuOPbd.mjs → names-CBy7d8K_.mjs} +0 -0
- /package/dist/{package-manager-ByJ0wKYh.mjs → package-manager-CClZtIHP.mjs} +0 -0
- /package/dist/{picomatch-DMmqYjgq.mjs → picomatch-TGJi--_I.mjs} +0 -0
- /package/dist/{semver-BJzWIuRz.mjs → semver-DfQyVLM_.mjs} +0 -0
- /package/dist/{shell-CY7OD48z.mjs → shell-u3bYGxNy.mjs} +0 -0
package/README.md
CHANGED
|
@@ -3,29 +3,30 @@
|
|
|
3
3
|
<img src="https://raw.githubusercontent.com/dmno-dev/bumpy/refs/heads/main/images/github-readme-banner.png" alt="Bumpy banner">
|
|
4
4
|
</a>
|
|
5
5
|
</p>
|
|
6
|
-
<br/>
|
|
7
6
|
<p align="center">
|
|
8
7
|
<a href="https://npmjs.com/package/@varlock/bumpy"><img src="https://img.shields.io/npm/v/@varlock/bumpy.svg" alt="npm package"></a>
|
|
9
8
|
<a href="https://github.com/dmno-dev/bumpy/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@varlock/bumpy.svg" alt="license"></a>
|
|
10
9
|
<a href="https://github.com/dmno-dev/bumpy/actions/workflows/ci.yaml"><img src="https://img.shields.io/github/actions/workflow/status/dmno-dev/bumpy/ci.yaml?style=flat&logo=github&label=CI" alt="build status"></a>
|
|
11
10
|
<a href="https://chat.dmno.dev"><img src="https://img.shields.io/badge/chat-discord-5865F2?style=flat&logo=discord" alt="discord chat"></a>
|
|
12
11
|
</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">Brought to you by <a href="https://varlock.dev">Varlock</a> 🧙♂️🔐 <a href="https://varlock.dev">check it out to secure your secrets</a></p>
|
|
13
14
|
<br/>
|
|
14
15
|
|
|
15
16
|
# @varlock/bumpy 🐸
|
|
16
17
|
|
|
17
|
-
A modern package versioning and changelog generation tool
|
|
18
|
+
A modern package versioning, release, and changelog generation tool. Built for monorepos, but works great in simpler projects too.
|
|
18
19
|
|
|
19
20
|
## How It Works
|
|
20
21
|
|
|
21
|
-
Bumpy uses **bump files** (you may know them as "changesets" if coming from [that tool 🦋](https://github.com/changesets/changesets))
|
|
22
|
+
Bumpy uses **bump files** (you may know them as "changesets" if coming from [that tool 🦋](https://github.com/changesets/changesets)) - small markdown files that declare an intent to release packages with a bump level (patch/minor/major), and a description that ends up in changelogs. Developers create these files as part of their PRs, and these files are then used to consolidate changes, generate changelogs, and trigger publishing. Specifically:
|
|
22
23
|
|
|
23
24
|
- Devs/agents create bump files as part of their PRs (using `bumpy add` or manually)
|
|
24
25
|
- A pre-push git hook can enforce bump files exist for changed packages
|
|
25
26
|
- In CI, a workflow checks PRs for bump files, leaves a comment on the PR detailing changed packages
|
|
26
27
|
- As PRs merge to the base branch, a "release PR" is kept up to date
|
|
27
28
|
- Shows what packages will be released and their changelogs
|
|
28
|
-
|
|
29
|
+
- Including packages bumped automatically due to dependency relationships
|
|
29
30
|
- When release PR is merged, publishing is triggered
|
|
30
31
|
- Oending bump files are deleted and packages are published with updated versions and changelogs
|
|
31
32
|
|
|
@@ -47,15 +48,15 @@ Fixed locale fallback logic in utils.
|
|
|
47
48
|
|
|
48
49
|
## Features
|
|
49
50
|
|
|
50
|
-
- **All package managers**
|
|
51
|
-
- **Smart dependency propagation**
|
|
52
|
-
- **Pack-then-publish**
|
|
53
|
-
- **Flexible package management**
|
|
54
|
-
- **Non-interactive CLI**
|
|
55
|
-
- **Aggregated GitHub releases**
|
|
56
|
-
- **Auto-generate from commits**
|
|
57
|
-
- **Pluggable changelog formatters**
|
|
58
|
-
- **Zero runtime dependencies**
|
|
51
|
+
- **All package managers** - npm, pnpm, yarn, and bun workspaces
|
|
52
|
+
- **Smart dependency propagation** - configurable rules for how version bumps cascade through your dependency graph (see [version propagation docs](https://github.com/dmno-dev/bumpy/blob/main/docs/version-propagation.md))
|
|
53
|
+
- **Pack-then-publish** - by default, publishes to npm (resolving `workspace:` and `catalog:` protocols, with OIDC/provenance support). Per-package custom publish commands let you target anything - VSCode extensions, Docker images, JSR, private registries, etc.
|
|
54
|
+
- **Flexible package management** - include/exclude any package individually via per-package config, glob patterns, or `privatePackages` setting
|
|
55
|
+
- **Non-interactive CLI** - `bumpy add` works fully non-interactively for CI/CD and AI-assisted development
|
|
56
|
+
- **Aggregated GitHub releases** - optionally create a single consolidated release instead of one per package
|
|
57
|
+
- **Auto-generate from commits** - `bumpy generate` creates bump files from branch commits - works with any commit style, with enhanced detection for conventional commits
|
|
58
|
+
- **Pluggable changelog formatters** - built-in `"default"` and `"github"` formatters, or write your own
|
|
59
|
+
- **Zero runtime dependencies** - dependencies are minimal and bundled at release time
|
|
59
60
|
|
|
60
61
|
## Getting Started
|
|
61
62
|
|
|
@@ -77,10 +78,10 @@ Then set up CI to automate versioning and publishing (see below).
|
|
|
77
78
|
|
|
78
79
|
## CI / GitHub Actions
|
|
79
80
|
|
|
80
|
-
No separate action to rely on
|
|
81
|
+
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
82
|
|
|
82
|
-
- **`bumpy ci check`**
|
|
83
|
-
- **`bumpy ci release`**
|
|
83
|
+
- **`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.
|
|
84
|
+
- **`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.
|
|
84
85
|
|
|
85
86
|
_examples use bun, but works with Node.js_
|
|
86
87
|
|
|
@@ -109,7 +110,7 @@ jobs:
|
|
|
109
110
|
### Release workflow
|
|
110
111
|
|
|
111
112
|
```yaml
|
|
112
|
-
# .github/workflows/bumpy-release.yml
|
|
113
|
+
# .github/workflows/bumpy-release.yml - trusted publishing (OIDC, no secret needed)
|
|
113
114
|
name: Bumpy Release
|
|
114
115
|
on:
|
|
115
116
|
push:
|
|
@@ -137,13 +138,13 @@ jobs:
|
|
|
137
138
|
BUMPY_GH_TOKEN: ${{ secrets.BUMPY_GH_TOKEN }} # additonal PAT, needed to trigger CI checks on release PR
|
|
138
139
|
```
|
|
139
140
|
|
|
140
|
-
> **Trusted publishing setup:** Configure each package on [npmjs.com](https://docs.npmjs.com/trusted-publishers/) → Package Settings → Trusted Publishers → GitHub Actions. Specify your org/user, repo, and the workflow filename (`bumpy-release.yml`). No `NPM_TOKEN` secret needed. Requires npm >= 11.5.1
|
|
141
|
+
> **Trusted publishing setup:** Configure each package on [npmjs.com](https://docs.npmjs.com/trusted-publishers/) → Package Settings → Trusted Publishers → GitHub Actions. Specify your org/user, repo, and the workflow filename (`bumpy-release.yml`). No `NPM_TOKEN` secret needed. Requires npm >= 11.5.1 - bumpy will warn if your version is too old.
|
|
141
142
|
|
|
142
143
|
<details>
|
|
143
144
|
<summary>Alternative: token-based auth (NPM_TOKEN secret)</summary>
|
|
144
145
|
|
|
145
146
|
```yaml
|
|
146
|
-
# .github/workflows/bumpy-release.yml
|
|
147
|
+
# .github/workflows/bumpy-release.yml - token-based auth
|
|
147
148
|
name: Bumpy Release
|
|
148
149
|
on:
|
|
149
150
|
push:
|
|
@@ -174,7 +175,7 @@ You can also use `bumpy ci release --auto-publish` to version + publish directly
|
|
|
174
175
|
|
|
175
176
|
### Token setup
|
|
176
177
|
|
|
177
|
-
The default `github.token` works for basic functionality, but GitHub's anti-recursion guard means PRs created by the default token won't trigger other workflows
|
|
178
|
+
The default `github.token` works for basic functionality, but GitHub's anti-recursion guard means PRs created by the default token won't trigger other workflows - so your regular CI (tests, linting, etc.) won't run automatically on the Version Packages PR. To fix this, provide a `BUMPY_GH_TOKEN` secret using either a **fine-grained PAT** or a **GitHub App token**. See the [full token setup guide](https://github.com/dmno-dev/bumpy/blob/main/docs/github-actions.md#token-setup) for details.
|
|
178
179
|
|
|
179
180
|
Run `bumpy ci setup` for interactive guidance, or set it up manually:
|
|
180
181
|
|
|
@@ -210,30 +211,30 @@ bumpy ai setup --target cursor # creates Cursor rule file
|
|
|
210
211
|
bumpy ai setup --target codex # creates Codex instruction file
|
|
211
212
|
```
|
|
212
213
|
|
|
213
|
-
The skill teaches the AI to examine git changes, identify affected packages, choose bump levels, and create bump files with `bumpy add`. It also instructs the AI to keep existing bump files up to date as work continues on a branch
|
|
214
|
+
The skill teaches the AI to examine git changes, identify affected packages, choose bump levels, and create bump files with `bumpy add`. It also instructs the AI to keep existing bump files up to date as work continues on a branch - updating packages, bump levels, and summaries to reflect the final state of changes.
|
|
214
215
|
|
|
215
216
|
## Documentation
|
|
216
217
|
|
|
217
|
-
- [Bump file format](https://github.com/dmno-dev/bumpy/blob/main/docs/bump-files.md)
|
|
218
|
-
- [Configuration reference](https://github.com/dmno-dev/bumpy/blob/main/docs/configuration.md)
|
|
219
|
-
- [CLI reference](https://github.com/dmno-dev/bumpy/blob/main/docs/cli.md)
|
|
220
|
-
- [GitHub Actions setup](https://github.com/dmno-dev/bumpy/blob/main/docs/github-actions.md)
|
|
221
|
-
- [Version propagation](https://github.com/dmno-dev/bumpy/blob/main/docs/version-propagation.md)
|
|
218
|
+
- [Bump file format](https://github.com/dmno-dev/bumpy/blob/main/docs/bump-files.md) - syntax, bump levels, cascade control
|
|
219
|
+
- [Configuration reference](https://github.com/dmno-dev/bumpy/blob/main/docs/configuration.md) - all `.bumpy/_config.json` and per-package options
|
|
220
|
+
- [CLI reference](https://github.com/dmno-dev/bumpy/blob/main/docs/cli.md) - every command with flags and examples
|
|
221
|
+
- [GitHub Actions setup](https://github.com/dmno-dev/bumpy/blob/main/docs/github-actions.md) - CI workflows, token setup, trusted publishing
|
|
222
|
+
- [Version propagation](https://github.com/dmno-dev/bumpy/blob/main/docs/version-propagation.md) - how dependency bumps cascade through your graph
|
|
222
223
|
|
|
223
224
|
## Why files instead of conventional commits?
|
|
224
225
|
|
|
225
|
-
Tools like semantic-release infer version bumps from commit messages (`feat:` → minor, `fix:` → patch). This works for simple projects but breaks down in monorepos
|
|
226
|
+
Tools like semantic-release infer version bumps from commit messages (`feat:` → minor, `fix:` → patch). This works for simple projects but breaks down in monorepos - a single PR often touches multiple packages with different bump levels, squash merges lose per-commit metadata, and commit messages are a poor place to write user-facing changelog entries. Bump files are explicit, reviewable in the PR diff, and can describe changes in language meant for consumers rather than developers. If you prefer commit-based workflows, `bumpy generate` can bridge the gap by auto-creating bump files from your branch commits - it works with any commit style, not just conventional commits.
|
|
226
227
|
|
|
227
228
|
## Why not just use changesets?
|
|
228
229
|
|
|
229
|
-
Bumpy is built as a successor to [@changesets/changesets](https://github.com/changesets/changesets). Changesets is mature and widely adopted, but has stagnated
|
|
230
|
+
Bumpy is built as a successor to [@changesets/changesets](https://github.com/changesets/changesets). Changesets is mature and widely adopted, but has stagnated - hundreds of open issues around core design problems that are unlikely to be fixed without a rewrite. See [differences from changesets](https://github.com/dmno-dev/bumpy/blob/main/docs/differences-from-changesets.md) for a detailed comparison with links to specific issues. The biggest pain points bumpy addresses:
|
|
230
231
|
|
|
231
|
-
- **Sane dependency propagation**
|
|
232
|
-
- **Workspace protocol resolution**
|
|
233
|
-
- **Custom publish commands**
|
|
234
|
-
- **Flexible package management**
|
|
235
|
-
- **CI without a separate action**
|
|
236
|
-
- **Automatic migration**
|
|
232
|
+
- **Sane dependency propagation** - changesets hardcodes aggressive behavior where a minor bump triggers a major bump on all peer dependents. Bumpy uses a [three-phase algorithm](https://github.com/dmno-dev/bumpy/blob/main/docs/version-propagation.md) with sensible defaults and full configurability.
|
|
233
|
+
- **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.
|
|
234
|
+
- **Custom publish commands** - changesets is hardcoded to `npm publish`. Bumpy supports per-package custom publish for VSCode extensions, Docker images, JSR, etc.
|
|
235
|
+
- **Flexible package management** - changesets treats all private packages the same. Bumpy lets you include/exclude any package individually.
|
|
236
|
+
- **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.
|
|
237
|
+
- **Automatic migration** - `bumpy init` detects `.changeset/`, renames it to `.bumpy/`, migrates config, keeps pending files, and offers to uninstall `@changesets/cli`.
|
|
237
238
|
|
|
238
239
|
## Development
|
|
239
240
|
|
|
@@ -253,4 +254,18 @@ bunx bumpy --help # invoke built cli
|
|
|
253
254
|
- Tracking workspace-level / non-publishable changes
|
|
254
255
|
- More frogs 🐸
|
|
255
256
|
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
<p align="center">
|
|
260
|
+
<a href="https://varlock.dev" target="_blank" rel="noopener noreferrer">
|
|
261
|
+
<img src="https://raw.githubusercontent.com/dmno-dev/bumpy/refs/heads/main/images/github-readme-footer.jpg" alt="Bumpy was created by Varlock" >
|
|
262
|
+
</a>
|
|
263
|
+
</p>
|
|
264
|
+
<p align="center">
|
|
265
|
+
<b>Bumpy is a creation of the team behind <a href="https://varlock.dev">Varlock</a> 🧙♂️</b><br/>
|
|
266
|
+
<a href="https://varlock.dev">
|
|
267
|
+
Check it out for secure secret sorcery - get your keys out of plaintext!
|
|
268
|
+
</a>
|
|
269
|
+
</p>
|
|
270
|
+
|
|
256
271
|
<!-- note this readme is also used for the bumpy package! -->
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { n as log, o as __toESM, r as require_picocolors } from "./logger-C2dEe5Su.mjs";
|
|
2
2
|
import { n as exists, t as ensureDir } from "./fs-DnDogVn-.mjs";
|
|
3
|
-
import { a as loadConfig, o as loadPackageConfig, r as getBumpyDir, s as matchGlob } from "./config-
|
|
4
|
-
import { t as discoverPackages } from "./workspace-
|
|
5
|
-
import { t as DependencyGraph } from "./dep-graph-
|
|
6
|
-
import { i as writeBumpFile } from "./bump-file-
|
|
7
|
-
import { r as getChangedFiles } from "./git-
|
|
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-
|
|
9
|
-
import { n as slugify, t as randomName } from "./names-
|
|
10
|
-
import { t as require_picomatch } from "./picomatch-
|
|
3
|
+
import { a as loadConfig, o as loadPackageConfig, r as getBumpyDir, s as matchGlob } from "./config-CJIj8xG3.mjs";
|
|
4
|
+
import { t as discoverPackages } from "./workspace-c9-TqXed.mjs";
|
|
5
|
+
import { t as DependencyGraph } from "./dep-graph-E-9-eQ2J.mjs";
|
|
6
|
+
import { i as writeBumpFile } from "./bump-file-BTsntOO-.mjs";
|
|
7
|
+
import { r as getChangedFiles } from "./git-D0__HP86.mjs";
|
|
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-CJT1JFFa.mjs";
|
|
9
|
+
import { n as slugify, t as randomName } from "./names-CBy7d8K_.mjs";
|
|
10
|
+
import { t as require_picomatch } from "./picomatch-TGJi--_I.mjs";
|
|
11
11
|
import { relative, resolve } from "node:path";
|
|
12
12
|
import * as readline from "node:readline";
|
|
13
13
|
//#region src/prompts/bump-select.ts
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { a as readJson, c as removeFile, f as writeText, i as listFiles, l as updateJsonFields, n as exists, s as readText, u as updateJsonNestedField } from "./fs-DnDogVn-.mjs";
|
|
2
|
+
import { r as getBumpyDir } from "./config-CJIj8xG3.mjs";
|
|
3
|
+
import { a as prependToChangelog, i as loadFormatter, n as generateChangelogEntry } from "./changelog-xKuL0IKx.mjs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
//#region src/core/apply-release-plan.ts
|
|
6
|
+
/** Apply the release plan: bump versions, update changelogs, delete bump files */
|
|
7
|
+
async function applyReleasePlan(releasePlan, packages, rootDir, config) {
|
|
8
|
+
const releaseMap = new Map(releasePlan.releases.map((r) => [r.name, r]));
|
|
9
|
+
const formatter = config.changelog !== false ? await loadFormatter(config.changelog, rootDir) : null;
|
|
10
|
+
for (const release of releasePlan.releases) {
|
|
11
|
+
const pkgJsonPath = resolve(packages.get(release.name).dir, "package.json");
|
|
12
|
+
const pkgJson = await readJson(pkgJsonPath);
|
|
13
|
+
await updateJsonFields(pkgJsonPath, { version: release.newVersion });
|
|
14
|
+
for (const depField of [
|
|
15
|
+
"dependencies",
|
|
16
|
+
"devDependencies",
|
|
17
|
+
"peerDependencies",
|
|
18
|
+
"optionalDependencies"
|
|
19
|
+
]) {
|
|
20
|
+
const deps = pkgJson[depField];
|
|
21
|
+
if (!deps) continue;
|
|
22
|
+
for (const [depName, range] of Object.entries(deps)) {
|
|
23
|
+
const depRelease = releaseMap.get(depName);
|
|
24
|
+
if (!depRelease) continue;
|
|
25
|
+
await updateJsonNestedField(pkgJsonPath, depField, depName, updateRange(range, depRelease.newVersion));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (formatter) for (const release of releasePlan.releases) {
|
|
30
|
+
const changelogPath = resolve(packages.get(release.name).dir, "CHANGELOG.md");
|
|
31
|
+
const entry = await generateChangelogEntry(release, releasePlan.bumpFiles, formatter);
|
|
32
|
+
let existingContent = "";
|
|
33
|
+
if (await exists(changelogPath)) existingContent = await readText(changelogPath);
|
|
34
|
+
await writeText(changelogPath, prependToChangelog(existingContent, entry));
|
|
35
|
+
}
|
|
36
|
+
const bumpyDir = getBumpyDir(rootDir);
|
|
37
|
+
const allBumpFiles = await listFiles(bumpyDir, ".md");
|
|
38
|
+
for (const file of allBumpFiles) {
|
|
39
|
+
if (file === "README.md") continue;
|
|
40
|
+
await removeFile(resolve(bumpyDir, file));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** Update a version range to include a new version, preserving the range prefix */
|
|
44
|
+
function updateRange(range, newVersion) {
|
|
45
|
+
let protocol = "";
|
|
46
|
+
let cleanRange = range;
|
|
47
|
+
const protoMatch = range.match(/^(workspace:|catalog:)/);
|
|
48
|
+
if (protoMatch) {
|
|
49
|
+
protocol = protoMatch[1];
|
|
50
|
+
cleanRange = range.slice(protocol.length);
|
|
51
|
+
}
|
|
52
|
+
const prefix = cleanRange.match(/^(\^|~|>=|>|<=|<|=)?/)?.[1] ?? "^";
|
|
53
|
+
if (cleanRange === "*" || cleanRange === "" || cleanRange === "^" || cleanRange === "~") return range;
|
|
54
|
+
return `${protocol}${prefix}${newVersion}`;
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { applyReleasePlan as t };
|
|
@@ -1,8 +1,7 @@
|
|
|
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
|
-
import { r as getBumpyDir } from "./config-
|
|
4
|
-
import { i as jsYaml } from "./package-manager-
|
|
5
|
-
import { s as tryRunArgs } from "./shell-
|
|
2
|
+
import { r as getBumpyDir } from "./config-CJIj8xG3.mjs";
|
|
3
|
+
import { i as jsYaml } from "./package-manager-CClZtIHP.mjs";
|
|
4
|
+
import { s as tryRunArgs } from "./shell-u3bYGxNy.mjs";
|
|
6
5
|
import { resolve } from "node:path";
|
|
7
6
|
import { existsSync } from "node:fs";
|
|
8
7
|
//#region src/core/bump-file.ts
|
|
@@ -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
|
|
36
|
-
if (
|
|
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
|
|
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
|
|
79
|
-
|
|
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
|
-
|
|
83
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
143
|
+
if (releases.length === 0 && errors.length === 0) return {
|
|
144
|
+
bumpFile: null,
|
|
145
|
+
errors
|
|
146
|
+
};
|
|
114
147
|
return {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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,23 +182,26 @@ 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
|
+
const emptyBumpFileIds = [];
|
|
154
194
|
if (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
|
-
|
|
159
|
-
break;
|
|
198
|
+
if (!parseErrors.some((e) => e.includes(`"${id}"`))) emptyBumpFileIds.push(id);
|
|
160
199
|
}
|
|
161
200
|
}
|
|
162
201
|
return {
|
|
163
202
|
branchBumpFiles,
|
|
164
203
|
branchBumpFileIds,
|
|
165
|
-
|
|
204
|
+
emptyBumpFileIds
|
|
166
205
|
};
|
|
167
206
|
}
|
|
168
207
|
//#endregion
|
|
@@ -1,8 +1,23 @@
|
|
|
1
|
-
import { s as tryRunArgs } from "./shell-
|
|
1
|
+
import { s as tryRunArgs } from "./shell-u3bYGxNy.mjs";
|
|
2
|
+
import { o as sortBumpFilesByType, r as getBumpTypeForPackage } from "./changelog-xKuL0IKx.mjs";
|
|
2
3
|
//#region src/core/changelog-github.ts
|
|
4
|
+
/** Authors filtered from "Thanks" attribution by default (e.g. bots) */
|
|
5
|
+
/** Authors filtered from "Thanks" attribution by default (e.g. AI/automation bots) */
|
|
6
|
+
const DEFAULT_INTERNAL_AUTHORS = [
|
|
7
|
+
"copilot",
|
|
8
|
+
"app/copilot-swe-agent",
|
|
9
|
+
"claude",
|
|
10
|
+
"dependabot",
|
|
11
|
+
"dependabot[bot]",
|
|
12
|
+
"app/dependabot",
|
|
13
|
+
"renovate[bot]",
|
|
14
|
+
"app/renovate",
|
|
15
|
+
"github-actions[bot]",
|
|
16
|
+
"snyk-bot"
|
|
17
|
+
];
|
|
3
18
|
/**
|
|
4
19
|
* GitHub-enhanced changelog formatter.
|
|
5
|
-
* Adds PR links,
|
|
20
|
+
* Adds PR links, contributor attribution, and optionally commit links when git/gh info is available.
|
|
6
21
|
*
|
|
7
22
|
* Usage in config:
|
|
8
23
|
* "changelog": "github"
|
|
@@ -11,29 +26,41 @@ import { s as tryRunArgs } from "./shell-CY7OD48z.mjs";
|
|
|
11
26
|
* "changelog": ["github", { "internalAuthors": ["theoephraim"] }]
|
|
12
27
|
*/
|
|
13
28
|
function createGithubFormatter(options = {}) {
|
|
29
|
+
const includeCommitLink = options.includeCommitLink ?? false;
|
|
14
30
|
const thankContributors = options.thankContributors ?? true;
|
|
15
|
-
const internalAuthorsSet = new Set(
|
|
31
|
+
const internalAuthorsSet = new Set([...DEFAULT_INTERNAL_AUTHORS, ...options.internalAuthors ?? []].map((a) => a.toLowerCase()));
|
|
16
32
|
return async (ctx) => {
|
|
17
33
|
const { release, bumpFiles, date } = ctx;
|
|
18
34
|
const repoSlug = options.repo ?? detectRepo();
|
|
19
35
|
const serverUrl = process.env.GITHUB_SERVER_URL || "https://github.com";
|
|
20
36
|
const lines = [];
|
|
21
37
|
lines.push(`## ${release.newVersion}`);
|
|
22
|
-
lines.push(
|
|
23
|
-
lines.push(`_${date}_`);
|
|
38
|
+
lines.push(`<sub>${date}</sub>`);
|
|
24
39
|
lines.push("");
|
|
25
40
|
const relevantBumpFiles = bumpFiles.filter((bf) => release.bumpFiles.includes(bf.id));
|
|
26
|
-
|
|
41
|
+
const sorted = sortBumpFilesByType(relevantBumpFiles, release.name);
|
|
42
|
+
for (const bf of sorted) {
|
|
27
43
|
if (!bf.summary) continue;
|
|
44
|
+
const type = getBumpTypeForPackage(bf, release.name);
|
|
45
|
+
const tag = type !== release.type ? ` *(${type})*` : "";
|
|
28
46
|
const { cleanSummary, overrides } = extractSummaryMeta(bf.summary);
|
|
29
47
|
const gitInfo = resolveBumpFileInfo(bf.id, repoSlug, serverUrl, overrides);
|
|
30
48
|
const summaryLines = cleanSummary.split("\n");
|
|
31
49
|
const firstLine = linkifyIssueRefs(summaryLines[0], serverUrl, repoSlug);
|
|
32
|
-
const
|
|
33
|
-
|
|
50
|
+
const { links, thanks } = formatPrefix(gitInfo, serverUrl, repoSlug, includeCommitLink, thankContributors, internalAuthorsSet);
|
|
51
|
+
const parts = [
|
|
52
|
+
links,
|
|
53
|
+
tag,
|
|
54
|
+
thanks
|
|
55
|
+
].filter(Boolean);
|
|
56
|
+
const hasMeta = parts.length > 0;
|
|
57
|
+
lines.push(`- ${parts.join(" ")}${hasMeta ? " - " : ""}${firstLine}`);
|
|
34
58
|
for (let i = 1; i < summaryLines.length; i++) if (summaryLines[i].trim()) lines.push(` ${linkifyIssueRefs(summaryLines[i], serverUrl, repoSlug)}`);
|
|
35
59
|
}
|
|
36
|
-
if (release.isDependencyBump
|
|
60
|
+
if (release.isDependencyBump) {
|
|
61
|
+
const depTag = release.type !== "patch" ? ` *(patch)* -` : "";
|
|
62
|
+
lines.push(`-${depTag} Updated dependencies`);
|
|
63
|
+
}
|
|
37
64
|
if (release.isCascadeBump && !release.isDependencyBump && relevantBumpFiles.length === 0) lines.push("- Version bump via cascade rule");
|
|
38
65
|
lines.push("");
|
|
39
66
|
return lines.join("\n");
|
|
@@ -154,18 +181,22 @@ function findBumpFileCommitInfo(bumpFileId, repo) {
|
|
|
154
181
|
}
|
|
155
182
|
}
|
|
156
183
|
/**
|
|
157
|
-
* Build the prefix
|
|
158
|
-
*
|
|
184
|
+
* Build the prefix portions of a changelog line, split into links and thanks
|
|
185
|
+
* so the bump type tag can be inserted between them.
|
|
159
186
|
*/
|
|
160
|
-
function formatPrefix(info, serverUrl, repo, thankContributors, internalAuthors) {
|
|
161
|
-
const
|
|
162
|
-
if (info.prNumber && info.prUrl)
|
|
163
|
-
if (info.commitHash && repo) {
|
|
187
|
+
function formatPrefix(info, serverUrl, repo, includeCommitLink, thankContributors, internalAuthors) {
|
|
188
|
+
const linkParts = [];
|
|
189
|
+
if (info.prNumber && info.prUrl) linkParts.push(`[#${info.prNumber}](${info.prUrl})`);
|
|
190
|
+
if (includeCommitLink && info.commitHash && repo) {
|
|
164
191
|
const short = info.commitHash.slice(0, 7);
|
|
165
|
-
|
|
192
|
+
linkParts.push(`[\`${short}\`](${serverUrl}/${repo}/commit/${info.commitHash})`);
|
|
166
193
|
}
|
|
167
|
-
|
|
168
|
-
|
|
194
|
+
let thanks = "";
|
|
195
|
+
if (thankContributors && info.author && !internalAuthors.has(info.author.toLowerCase())) thanks = `Thanks [@${info.author}](${serverUrl}/${info.author})!`;
|
|
196
|
+
return {
|
|
197
|
+
links: linkParts.join(" "),
|
|
198
|
+
thanks
|
|
199
|
+
};
|
|
169
200
|
}
|
|
170
201
|
/**
|
|
171
202
|
* Linkify bare issue/PR references like #123 in text,
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { n as log } from "./logger-C2dEe5Su.mjs";
|
|
2
|
+
import { t as BUMP_LEVELS } from "./types-CSM0c2-m.mjs";
|
|
3
|
+
import { relative, resolve } from "node:path";
|
|
4
|
+
import { realpathSync } from "node:fs";
|
|
5
|
+
//#region src/core/changelog.ts
|
|
6
|
+
/** Get the bump type a bump file applies to a specific package */
|
|
7
|
+
function getBumpTypeForPackage(bf, packageName) {
|
|
8
|
+
const rel = bf.releases.find((r) => r.name === packageName);
|
|
9
|
+
return rel?.type === "none" ? "patch" : rel?.type ?? "patch";
|
|
10
|
+
}
|
|
11
|
+
/** Sort bump files by bump type for a specific package (major → minor → patch) */
|
|
12
|
+
function sortBumpFilesByType(bumpFiles, packageName) {
|
|
13
|
+
return [...bumpFiles].sort((a, b) => {
|
|
14
|
+
const aLevel = BUMP_LEVELS[getBumpTypeForPackage(a, packageName)];
|
|
15
|
+
return BUMP_LEVELS[getBumpTypeForPackage(b, packageName)] - aLevel;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
/** Default formatter — version heading with date, bullet points sorted by bump type */
|
|
19
|
+
const defaultFormatter = (ctx) => {
|
|
20
|
+
const { release, bumpFiles, date } = ctx;
|
|
21
|
+
const lines = [];
|
|
22
|
+
lines.push(`## ${release.newVersion}`);
|
|
23
|
+
lines.push(`<sub>${date}</sub>`);
|
|
24
|
+
lines.push("");
|
|
25
|
+
const relevantBumpFiles = bumpFiles.filter((bf) => release.bumpFiles.includes(bf.id));
|
|
26
|
+
const sorted = sortBumpFilesByType(relevantBumpFiles, release.name);
|
|
27
|
+
for (const bf of sorted) {
|
|
28
|
+
if (!bf.summary) continue;
|
|
29
|
+
const type = getBumpTypeForPackage(bf, release.name);
|
|
30
|
+
const tag = type !== release.type ? `*(${type})* ` : "";
|
|
31
|
+
const summaryLines = bf.summary.split("\n");
|
|
32
|
+
lines.push(`- ${tag}${summaryLines[0]}`);
|
|
33
|
+
for (let i = 1; i < summaryLines.length; i++) if (summaryLines[i].trim()) lines.push(` ${summaryLines[i]}`);
|
|
34
|
+
}
|
|
35
|
+
if (release.isDependencyBump) {
|
|
36
|
+
const tag = release.type !== "patch" ? `*(patch)* ` : "";
|
|
37
|
+
lines.push(`- ${tag}Updated dependencies`);
|
|
38
|
+
}
|
|
39
|
+
if (release.isCascadeBump && !release.isDependencyBump && relevantBumpFiles.length === 0) lines.push("- Version bump via cascade rule");
|
|
40
|
+
lines.push("");
|
|
41
|
+
return lines.join("\n");
|
|
42
|
+
};
|
|
43
|
+
const BUILTIN_FORMATTERS = {
|
|
44
|
+
default: defaultFormatter,
|
|
45
|
+
github: async () => {
|
|
46
|
+
const { createGithubFormatter } = await import("./changelog-github-CEaDCtTk.mjs");
|
|
47
|
+
return createGithubFormatter();
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Load a changelog formatter from config.
|
|
52
|
+
* Supports: "default", "./path/to/formatter.ts", or a module name.
|
|
53
|
+
*/
|
|
54
|
+
async function loadFormatter(changelog, rootDir) {
|
|
55
|
+
const [name, options] = Array.isArray(changelog) ? changelog : [changelog, {}];
|
|
56
|
+
if (name === "github") {
|
|
57
|
+
const { createGithubFormatter } = await import("./changelog-github-CEaDCtTk.mjs");
|
|
58
|
+
return createGithubFormatter(options);
|
|
59
|
+
}
|
|
60
|
+
if (typeof name === "string" && BUILTIN_FORMATTERS[name]) {
|
|
61
|
+
const builtin = BUILTIN_FORMATTERS[name];
|
|
62
|
+
if (typeof builtin === "function" && builtin.length === 0) return builtin();
|
|
63
|
+
return builtin;
|
|
64
|
+
}
|
|
65
|
+
if (typeof name === "string") try {
|
|
66
|
+
let modulePath;
|
|
67
|
+
if (name.startsWith(".")) {
|
|
68
|
+
modulePath = resolve(rootDir, name);
|
|
69
|
+
try {
|
|
70
|
+
modulePath = realpathSync(modulePath);
|
|
71
|
+
} catch {}
|
|
72
|
+
const rel = relative(realpathSync(rootDir), modulePath);
|
|
73
|
+
if (rel.startsWith("..") || resolve("/", rel) === resolve("/")) throw new Error(`Changelog formatter path "${name}" resolves outside the project root`);
|
|
74
|
+
} else modulePath = name;
|
|
75
|
+
const mod = await import(modulePath);
|
|
76
|
+
const exported = mod.default || mod.changelogFormatter;
|
|
77
|
+
if (typeof exported === "function") {
|
|
78
|
+
const result = exported(options);
|
|
79
|
+
if (typeof result === "function") return result;
|
|
80
|
+
return exported;
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Changelog module "${name}" does not export a function`);
|
|
83
|
+
} catch (err) {
|
|
84
|
+
log.warn(`Failed to load changelog formatter "${name}": ${err instanceof Error ? err.message : err}`);
|
|
85
|
+
log.warn("Falling back to default formatter");
|
|
86
|
+
return defaultFormatter;
|
|
87
|
+
}
|
|
88
|
+
return defaultFormatter;
|
|
89
|
+
}
|
|
90
|
+
/** Generate a changelog entry using the configured formatter */
|
|
91
|
+
async function generateChangelogEntry(release, bumpFiles, formatter = defaultFormatter, date = (/* @__PURE__ */ new Date()).toISOString().split("T")[0]) {
|
|
92
|
+
return formatter({
|
|
93
|
+
release,
|
|
94
|
+
bumpFiles,
|
|
95
|
+
date
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
/** Prepend a new entry to an existing CHANGELOG.md content */
|
|
99
|
+
function prependToChangelog(existingContent, newEntry) {
|
|
100
|
+
const headerMatch = existingContent.match(/^# /m);
|
|
101
|
+
if (headerMatch && headerMatch.index !== void 0) {
|
|
102
|
+
const afterTitle = existingContent.indexOf("\n##");
|
|
103
|
+
if (afterTitle !== -1) return existingContent.slice(0, afterTitle + 1) + "\n" + newEntry + "\n" + existingContent.slice(afterTitle + 1);
|
|
104
|
+
return existingContent.trimEnd() + "\n\n" + newEntry;
|
|
105
|
+
}
|
|
106
|
+
return "# Changelog\n\n" + newEntry;
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
export { prependToChangelog as a, loadFormatter as i, generateChangelogEntry as n, sortBumpFilesByType as o, getBumpTypeForPackage as r, defaultFormatter as t };
|