@williamthorsen/release-kit 5.0.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +60 -0
- package/README.md +137 -43
- package/dist/esm/.cache +1 -1
- package/dist/esm/assertCleanWorkingTree.js +1 -1
- package/dist/esm/bin/release-kit.js +1 -1
- package/dist/esm/buildChangelogEntries.d.ts +3 -0
- package/dist/esm/{generateChangelogJson.js → buildChangelogEntries.js} +7 -80
- package/dist/esm/buildDependencyGraph.d.ts +1 -0
- package/dist/esm/buildDependencyGraph.js +8 -1
- package/dist/esm/buildReleaseSummary.js +9 -1
- package/dist/esm/buildSyntheticChangelogEntry.d.ts +5 -0
- package/dist/esm/buildSyntheticChangelogEntry.js +13 -0
- package/dist/esm/changelogJsonFile.d.ts +4 -0
- package/dist/esm/changelogJsonFile.js +68 -0
- package/dist/esm/decideRelease.d.ts +25 -0
- package/dist/esm/decideRelease.js +28 -0
- package/dist/esm/defaults.d.ts +1 -0
- package/dist/esm/defaults.js +2 -0
- package/dist/esm/index.d.ts +2 -43
- package/dist/esm/index.js +0 -82
- package/dist/esm/init/templates.js +2 -2
- package/dist/esm/loadConfig.d.ts +10 -1
- package/dist/esm/loadConfig.js +96 -2
- package/dist/esm/prepareCommand.js +51 -9
- package/dist/esm/publish.d.ts +0 -1
- package/dist/esm/publish.js +3 -3
- package/dist/esm/publishCommand.js +10 -1
- package/dist/esm/releasePrepare.js +83 -39
- package/dist/esm/releasePrepareMono.js +133 -87
- package/dist/esm/releasePrepareProject.d.ts +9 -0
- package/dist/esm/releasePrepareProject.js +109 -0
- package/dist/esm/reportPrepare.js +70 -24
- package/dist/esm/types.d.ts +57 -14
- package/dist/esm/validateConfig.js +26 -0
- package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -0
- package/dist/esm/validateOnlyExcludesStrandedDependents.js +109 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/package.json +4 -1
- package/presets/labels/common.yaml +9 -6
- package/schemas/label-map.json +24 -0
- package/dist/esm/generateChangelogJson.d.ts +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,66 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [release-kit-v5.1.0] - 2026-04-30
|
|
6
|
+
|
|
7
|
+
### Bug fixes
|
|
8
|
+
|
|
9
|
+
- Make publish's clean-tree safety gate reachable (#311)
|
|
10
|
+
|
|
11
|
+
Fixes an issue where `release-kit publish` failed with pnpm's "working tree is dirty" error on a clean tree whenever `releaseNotes.shouldInjectIntoReadme: true` was configured. release-kit injects the release notes into the package README before invoking `pnpm publish`, so pnpm's own working-tree check fired on a tree release-kit had just dirtied — even though the user's tree was clean at command start.
|
|
12
|
+
|
|
13
|
+
- Make `--set-version` + `project` rejection explicit (#319)
|
|
14
|
+
|
|
15
|
+
Improves the error users see when invoking `release-kit prepare --set-version` with a `project` block configured. The combination is still rejected — as before — but now produces a single, project-aware message ("--set-version cannot be combined with a project release…") rather than the previous two-step chain (`--set-version requires --only`, then `--only cannot be combined with a project release` after the user added `--only`).
|
|
16
|
+
|
|
17
|
+
- Reject `--only` that would strand excluded dependents (#321)
|
|
18
|
+
|
|
19
|
+
Fixes a silent footgun in `release-kit prepare --only=...`: an excluded internal dependent with its own changes would be left unreleased with no runtime signal, even though the targeted workspace it depends on was being released. The command now rejects such invocations up front, naming every excluded dependent whose changes would be stranded.
|
|
20
|
+
|
|
21
|
+
- Order prerelease versions correctly in changelog sort (#334)
|
|
22
|
+
|
|
23
|
+
Fixes a latent issue in `@williamthorsen/release-kit` where prerelease version tags (e.g., `1.2.3-alpha`, `1.2.3-rc.1`) were sorted as if their prerelease component were absent, causing them to appear in the wrong position relative to releases sharing the same base version. Changelog entries are now ordered per SemVer §11: prerelease versions precede the corresponding release (`1.2.3-alpha < 1.2.3`), build metadata is ignored for ordering, and entries that fail SemVer validation sort deterministically to the bottom of the list rather than collapsing into mid-list positions.
|
|
24
|
+
|
|
25
|
+
### Documentation
|
|
26
|
+
|
|
27
|
+
- Document tag prefix collisions as general rule (#320)
|
|
28
|
+
|
|
29
|
+
Documents the strict-prefix tag-prefix collision rule as a general validation rule that applies to every release-kit consumer declaring more than one tag prefix: across active workspaces, declared legacy identities, retired packages, and the optional `project` block. Previously, the rule appeared only inside the `Project releases` validation list.
|
|
30
|
+
|
|
31
|
+
### Features
|
|
32
|
+
|
|
33
|
+
- Add `project` block for project-level release stage (#317)
|
|
34
|
+
|
|
35
|
+
Adds support for monorepos that ship a single combined deliverable to version, changelog, and release-note the project itself rather than only its constituent workspaces.
|
|
36
|
+
|
|
37
|
+
- Publish JSON Schema for `.meta/label-map.json` (#325)
|
|
38
|
+
|
|
39
|
+
Adds a JSON Schema for `.meta/label-map.json` to release-kit, packaged at `packages/release-kit/schemas/label-map.json` and shipped to npm. Consumers reference it via the stable raw URL pattern `https://github.com/williamthorsen/node-monorepo-tools/raw/release-kit-v<version>/packages/release-kit/schemas/label-map.json` — the same shape audit-deps already uses.
|
|
40
|
+
|
|
41
|
+
- Label prepare errors with the failing stage (#326)
|
|
42
|
+
|
|
43
|
+
Adds stage attribution to errors thrown during `release-kit prepare`. Errors from per-workspace bumps and changelog generation, the project release stage, and the post-release format command now begin with a stage label that identifies the failing stage and (where relevant) the affected workspace.
|
|
44
|
+
|
|
45
|
+
- Make `--force` and `--bump` orthogonal (#328)
|
|
46
|
+
|
|
47
|
+
Decouples `--force` and `--bump` so each flag has a single responsibility, and unifies skip semantics across the per-workspace and project pipelines.
|
|
48
|
+
|
|
49
|
+
### Refactoring
|
|
50
|
+
|
|
51
|
+
- Convert prepare results to discriminated unions (#330)
|
|
52
|
+
|
|
53
|
+
Tightens `ProjectPrepareResult` and `WorkspacePrepareResult` from flat-with-optionals types into status-discriminated unions, so consumers that have already narrowed on `status === 'released'` no longer need to re-guard each release-only field with `!== undefined`. The four new sub-types (`ReleasedProjectResult`, `ReleasedWorkspaceResult`, `SkippedProjectResult`, `SkippedWorkspaceResult`) are exported from the package so callers can name the variants directly. Renderer output is byte-identical to before.
|
|
54
|
+
|
|
55
|
+
- Split changelog.json generation into layered helpers (#333)
|
|
56
|
+
|
|
57
|
+
Reorganises `changelog.json` generation in `@williamthorsen/release-kit` so that producing entries (running git-cliff and shaping the output) is fully separated from persisting them (reading, merging, and writing the file). Removes the silent-discard parse-failure path at the project release stage by no longer reading the root `changelog.json` before overwriting it. Sharpens dry-run mode: `git-cliff` now runs even on dry-run so configuration mistakes surface in preview rather than only on a real release. Trims the public `index.ts` barrel from ~50 re-exports to the two type names actually consumed by external configs.
|
|
58
|
+
|
|
59
|
+
### Tests
|
|
60
|
+
|
|
61
|
+
- Cover untested project-release and config branches (#329)
|
|
62
|
+
|
|
63
|
+
Closes four mechanical test-coverage gaps in the project-level release surfaces flagged in the test review of #308. New cases exercise the `(no previous release found)` rendering for released projects, the unparseable-commit warning block on the released-project rendering path, the `readFileSync` I/O failure path in `readRootPackageVersion`, and the contributing-paths invariant in `releasePrepareProject`. No production code changes — these are pure-render and pure-derivation branches that previously had no test exercising them.
|
|
64
|
+
|
|
5
65
|
## [release-kit-v5.0.0] - 2026-04-23
|
|
6
66
|
|
|
7
67
|
### Bug fixes
|
package/README.md
CHANGED
|
@@ -5,61 +5,49 @@ Version-bumping and changelog-generation toolkit for release workflows.
|
|
|
5
5
|
Provides a self-contained CLI that auto-discovers workspaces from `pnpm-workspace.yaml`, parses conventional commits, determines version bumps, updates `package.json` files, and generates changelogs with `git-cliff`.
|
|
6
6
|
|
|
7
7
|
<!-- section:release-notes -->
|
|
8
|
-
## Release notes — v5.
|
|
8
|
+
## Release notes — v5.1.0 (2026-04-30)
|
|
9
9
|
|
|
10
10
|
### Bug fixes
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- Make publish's clean-tree safety gate reachable (#311)
|
|
13
13
|
|
|
14
|
-
Fixes a
|
|
14
|
+
Fixes an issue where `release-kit publish` failed with pnpm's "working tree is dirty" error on a clean tree whenever `releaseNotes.shouldInjectIntoReadme: true` was configured. release-kit injects the release notes into the package README before invoking `pnpm publish`, so pnpm's own working-tree check fired on a tree release-kit had just dirtied — even though the user's tree was clean at command start.
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- Improve release-notes rendering quality (#261)
|
|
19
|
-
|
|
20
|
-
Improves the quality of release notes and CHANGELOG entries generated by release-kit. Release notes sections are now ordered by work-type priority (bug fixes first, then features, then internal), and each bullet now includes the commit body text for context that a one-line title cannot provide. Refactoring commits are now excluded from the release notes.
|
|
21
|
-
|
|
22
|
-
- Scaffold release-notes injection and check markers (#267)
|
|
23
|
-
|
|
24
|
-
Adds release-notes injection to the configs scaffolded by `release-kit init`, so newly-onboarded consumers get the feature without having to discover or toggle the flag. The release-kit readyup kit gains a check that warns when a consumer's README is missing the marker pair where injected notes should land — without those markers, injection silently prepends to the top of the file, pushing the README's title below the notes.
|
|
25
|
-
|
|
26
|
-
- Split GitHub Release creation into its own workflow (#272)
|
|
27
|
-
|
|
28
|
-
Splits GitHub Release creation out of release-kit publish into a dedicated release-kit create-github-release CLI command and a matching reusable GitHub Actions workflow. Consumers that do not publish to npm can now create Releases independently, and the contents: write permission required to create a Release no longer leaks into the publish path.
|
|
16
|
+
- Make `--set-version` + `project` rejection explicit (#319)
|
|
29
17
|
|
|
30
|
-
-
|
|
18
|
+
Improves the error users see when invoking `release-kit prepare --set-version` with a `project` block configured. The combination is still rejected — as before — but now produces a single, project-aware message ("--set-version cannot be combined with a project release…") rather than the previous two-step chain (`--set-version requires --only`, then `--only cannot be combined with a project release` after the user added `--only`).
|
|
31
19
|
|
|
32
|
-
|
|
20
|
+
- Reject `--only` that would strand excluded dependents (#321)
|
|
33
21
|
|
|
34
|
-
-
|
|
22
|
+
Fixes a silent footgun in `release-kit prepare --only=...`: an excluded internal dependent with its own changes would be left unreleased with no runtime signal, even though the targeted workspace it depends on was being released. The command now rejects such invocations up front, naming every excluded dependent whose changes would be stranded.
|
|
35
23
|
|
|
36
|
-
|
|
24
|
+
- Order prerelease versions correctly in changelog sort (#334)
|
|
37
25
|
|
|
38
|
-
-
|
|
26
|
+
Fixes a latent issue in `@williamthorsen/release-kit` where prerelease version tags (e.g., `1.2.3-alpha`, `1.2.3-rc.1`) were sorted as if their prerelease component were absent, causing them to appear in the wrong position relative to releases sharing the same base version. Changelog entries are now ordered per SemVer §11: prerelease versions precede the corresponding release (`1.2.3-alpha < 1.2.3`), build metadata is ignored for ordering, and entries that fail SemVer validation sort deterministically to the bottom of the list rather than collapsing into mid-list positions.
|
|
39
27
|
|
|
40
|
-
|
|
28
|
+
### Features
|
|
41
29
|
|
|
42
|
-
- Add
|
|
30
|
+
- Add `project` block for project-level release stage (#317)
|
|
43
31
|
|
|
44
|
-
Adds
|
|
32
|
+
Adds support for monorepos that ship a single combined deliverable to version, changelog, and release-note the project itself rather than only its constituent workspaces.
|
|
45
33
|
|
|
46
|
-
-
|
|
34
|
+
- Publish JSON Schema for `.meta/label-map.json` (#325)
|
|
47
35
|
|
|
48
|
-
|
|
36
|
+
Adds a JSON Schema for `.meta/label-map.json` to release-kit, packaged at `packages/release-kit/schemas/label-map.json` and shipped to npm. Consumers reference it via the stable raw URL pattern `https://github.com/williamthorsen/node-monorepo-tools/raw/release-kit-v<version>/packages/release-kit/schemas/label-map.json` — the same shape audit-deps already uses.
|
|
49
37
|
|
|
50
|
-
|
|
38
|
+
- Label prepare errors with the failing stage (#326)
|
|
51
39
|
|
|
52
|
-
|
|
40
|
+
Adds stage attribution to errors thrown during `release-kit prepare`. Errors from per-workspace bumps and changelog generation, the project release stage, and the post-release format command now begin with a stage label that identifies the failing stage and (where relevant) the affected workspace.
|
|
53
41
|
|
|
54
|
-
|
|
42
|
+
- Make `--force` and `--bump` orthogonal (#328)
|
|
55
43
|
|
|
56
|
-
|
|
44
|
+
Decouples `--force` and `--bump` so each flag has a single responsibility, and unifies skip semantics across the per-workspace and project pipelines.
|
|
57
45
|
|
|
58
|
-
|
|
46
|
+
### Documentation
|
|
59
47
|
|
|
60
|
-
-
|
|
48
|
+
- Document tag prefix collisions as general rule (#320)
|
|
61
49
|
|
|
62
|
-
|
|
50
|
+
Documents the strict-prefix tag-prefix collision rule as a general validation rule that applies to every release-kit consumer declaring more than one tag prefix: across active workspaces, declared legacy identities, retired packages, and the optional `project` block. Previously, the rule appeared only inside the `Project releases` validation list.
|
|
63
51
|
<!-- /section:release-notes -->
|
|
64
52
|
|
|
65
53
|
## Installation
|
|
@@ -172,6 +160,7 @@ The config file supports both `export default config` and `export const config =
|
|
|
172
160
|
| `scopeAliases` | `Record<string, string>` | Maps shorthand scope names to canonical names in commits |
|
|
173
161
|
| `workTypes` | `Record<string, WorkTypeConfig>` | Work type definitions, merged with defaults by key |
|
|
174
162
|
| `retiredPackages` | `RetiredPackage[]` | Packages that once lived in this repo but have been extracted or removed; suppresses undeclared-tag-prefix warnings |
|
|
163
|
+
| `project` | `ProjectConfig` | Opt-in project-level release block. Declaring `project: {}` (even empty) enables a project-release stage in `prepare` |
|
|
175
164
|
|
|
176
165
|
All fields are optional.
|
|
177
166
|
|
|
@@ -222,10 +211,95 @@ Validation rules:
|
|
|
222
211
|
- `successor` is optional; if present, it must be a non-empty string.
|
|
223
212
|
- Full-tuple `(name, tagPrefix)` duplicates within `retiredPackages` are rejected.
|
|
224
213
|
- Two entries sharing the same `tagPrefix` but different `name`s are accepted — this documents a package renamed within the repo before being retired.
|
|
225
|
-
- A `tagPrefix` that collides with an active workspace's derived prefix or any declared `workspaces[].legacyIdentities[].tagPrefix` is rejected. A retired prefix cannot also be current or legacy.
|
|
226
214
|
|
|
227
215
|
`show-tag-prefixes` currently does not render a dedicated "Retired packages" section (deferred). Declaring a retired entry is verifiable by confirming that its `tagPrefix` stops appearing under "Undeclared tag prefixes" in the `show-tag-prefixes` output.
|
|
228
216
|
|
|
217
|
+
### Tag prefix collisions
|
|
218
|
+
|
|
219
|
+
Tag prefixes from distinct owners must not be identical or be a strict prefix of one another. An owner is one of:
|
|
220
|
+
|
|
221
|
+
- An active workspace, comprising its derived `tagPrefix` plus any declared `legacyIdentities[].tagPrefix`. Identities of the same workspace are one owner, so their prefixes are allowed to overlap (this represents the same package across renames).
|
|
222
|
+
- A `retiredPackages[]` entry (one owner per entry).
|
|
223
|
+
- The `project` block, when configured.
|
|
224
|
+
|
|
225
|
+
release-kit resolves baseline tags via `git describe --match=<prefix>*`, so a strict-prefix overlap between distinct owners would cause that glob to return cross-matches against the wrong owner's history. For example, a project prefix of `v` collides with a workspace prefix of `vue-helpers-v`, since `git describe --match=v*` would return both project tags and `vue-helpers` tags.
|
|
226
|
+
|
|
227
|
+
The rule is enforced at config load; the resulting error identifies both colliding declarations.
|
|
228
|
+
|
|
229
|
+
### Project releases
|
|
230
|
+
|
|
231
|
+
Some monorepos ship a single combined deliverable — a Chrome extension, a CLI binary, a packaged desktop app — for which the per-workspace tags and changelogs alone do not describe what the user actually receives. Declare the optional `project` block to add a project-level release stage that runs alongside the per-workspace pipeline.
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import type { ReleaseKitConfig } from '@williamthorsen/release-kit';
|
|
235
|
+
|
|
236
|
+
const config: ReleaseKitConfig = {
|
|
237
|
+
// Empty object is enough to opt in. Every non-excluded workspace contributes.
|
|
238
|
+
project: {},
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
export default config;
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
When configured, each `release-kit prepare` run additionally:
|
|
245
|
+
|
|
246
|
+
- Computes commits since the last project tag (`<tagPrefix><version>`), filtered to the union of every contributing workspace's paths.
|
|
247
|
+
- Bumps the root `package.json`'s `version` field using the same bump-derivation rules as workspaces (or the `--bump=...` override).
|
|
248
|
+
- Regenerates the root `./CHANGELOG.md` via `git-cliff`, scoped to the project's `tagPrefix` and contributing paths.
|
|
249
|
+
- Emits `./.meta/changelog.json` (when `changelogJson.enabled`).
|
|
250
|
+
- With `--with-release-notes`, additionally emits `./docs/RELEASE_NOTES.v<version>.md`.
|
|
251
|
+
- Appends the project tag to `tmp/.release-tags` so `release-kit commit` and `release-kit tag` pick it up alongside per-workspace tags.
|
|
252
|
+
|
|
253
|
+
If no contributing workspace has commits since the last project tag, the project release is silently skipped — same behavior as a per-workspace skip.
|
|
254
|
+
|
|
255
|
+
#### `ProjectConfig`
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
interface ProjectConfig {
|
|
259
|
+
tagPrefix?: string; // Defaults to 'v'
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
| Field | Default | Description |
|
|
264
|
+
| ----------- | ------- | -------------------------------------------------------------------- |
|
|
265
|
+
| `tagPrefix` | `'v'` | Prefix for project tags. The full tag is `${tagPrefix}${newVersion}` |
|
|
266
|
+
|
|
267
|
+
Contributing workspaces are implicit: every non-excluded discovered workspace contributes. There is no field to override the contributing set in this initial release; if a future consumer needs to release a workspace as a component but exclude it from the project release, that override can be added then.
|
|
268
|
+
|
|
269
|
+
Validation rules:
|
|
270
|
+
|
|
271
|
+
- The root `package.json` must exist and declare a `version` field. release-kit reports an error at config-load time if either is missing.
|
|
272
|
+
- The `project` block is rejected in single-package mode (the implicit "all non-excluded workspaces contribute" rule is meaningless in a single-package repo).
|
|
273
|
+
- Unknown fields inside `project` are rejected.
|
|
274
|
+
|
|
275
|
+
CLI flag interactions:
|
|
276
|
+
|
|
277
|
+
- `--dry-run` previews project artifacts alongside workspace artifacts; no files are written.
|
|
278
|
+
- `--bump=major|minor|patch` propagates to the project release as a level chooser. It does not trigger a release on its own when there are no commits or no bump-worthy commits.
|
|
279
|
+
- `--force` runs the project release even when no commits or no bump-worthy commits exist since the last project tag. Defaults to patch when `--bump` is not given; combine with `--bump=X` to release at a different level.
|
|
280
|
+
- `--only` is rejected with an error when `project` is configured. `--only` is a surgical, single-workspace operation; combining it with a project release that rolls up every contributing workspace would create ambiguous semantics. To release a single workspace, use a config without a `project` block, or run a full `prepare` (no `--only`) to include the project release.
|
|
281
|
+
- `--set-version` is rejected with an error when `project` is configured. `--set-version` operates on a single workspace, but a project release rolls up every contributing workspace; the two semantics don't compose. To use `--set-version`, run on a config without a `project` block.
|
|
282
|
+
|
|
283
|
+
`--bump` and `--force` are orthogonal: `--bump` is purely a level chooser; `--force` is purely a release trigger. Examples:
|
|
284
|
+
|
|
285
|
+
```sh
|
|
286
|
+
# Release every target at its natural bump level (no flags).
|
|
287
|
+
release-kit prepare
|
|
288
|
+
|
|
289
|
+
# Force a release even when no bump-worthy commits exist; defaults to patch
|
|
290
|
+
# per target, with each target keeping its natural bump if one is derivable.
|
|
291
|
+
release-kit prepare --force
|
|
292
|
+
|
|
293
|
+
# Force a release at a uniform level across every releasing target.
|
|
294
|
+
release-kit prepare --force --bump=minor
|
|
295
|
+
|
|
296
|
+
# --bump=X alone is a level chooser, NOT a trigger. If a target has no
|
|
297
|
+
# bump-worthy commits, it skips with a "Pass --force..." reason. If it has
|
|
298
|
+
# bump-worthy commits, the override applies. (Behavioral change from earlier
|
|
299
|
+
# release-kit versions, where --bump=X alone would force a release.)
|
|
300
|
+
release-kit prepare --bump=minor
|
|
301
|
+
```
|
|
302
|
+
|
|
229
303
|
### `VersionPatterns`
|
|
230
304
|
|
|
231
305
|
Defines which commit types trigger major or minor bumps. Any recognized type not listed defaults to a patch bump.
|
|
@@ -277,15 +351,15 @@ Release-notes sections are rendered in the declaration order of the merged work-
|
|
|
277
351
|
|
|
278
352
|
Run release preparation with automatic workspace discovery.
|
|
279
353
|
|
|
280
|
-
| Flag | Description
|
|
281
|
-
| ---------------------------- |
|
|
282
|
-
| `--dry-run` | Preview changes without writing files
|
|
283
|
-
| `--bump=major\|minor\|patch` | Override the bump type for all workspaces
|
|
284
|
-
| `--set-version=X.Y.Z` | Set an explicit canonical semver version; bypasses commit-derived bumps. Requires `--only` in monorepo mode.
|
|
285
|
-
| `--force` |
|
|
286
|
-
| `--only=name1,name2` | Only process the named workspaces (monorepo only)
|
|
287
|
-
| `--with-release-notes` | Write per-workspace release-notes previews under `{workspacePath}/docs/`
|
|
288
|
-
| `--help`, `-h` | Show help
|
|
354
|
+
| Flag | Description |
|
|
355
|
+
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
356
|
+
| `--dry-run` | Preview changes without writing files |
|
|
357
|
+
| `--bump=major\|minor\|patch` | Override the bump type for all workspaces |
|
|
358
|
+
| `--set-version=X.Y.Z` | Set an explicit canonical semver version; bypasses commit-derived bumps. Requires `--only` in monorepo mode. |
|
|
359
|
+
| `--force` | Release even when no commits or no bump-worthy commits exist since the last tag (defaults to patch; combine with `--bump=X` for a different level) |
|
|
360
|
+
| `--only=name1,name2` | Only process the named workspaces (monorepo only; rejected when a `project` block is configured) |
|
|
361
|
+
| `--with-release-notes` | Write per-workspace release-notes previews under `{workspacePath}/docs/` |
|
|
362
|
+
| `--help`, `-h` | Show help |
|
|
289
363
|
|
|
290
364
|
Workspace names for `--only` match the package directory name (e.g., `arrays`, `release-kit`).
|
|
291
365
|
|
|
@@ -385,6 +459,26 @@ Manage GitHub label definitions via config-driven YAML files.
|
|
|
385
459
|
|
|
386
460
|
`init` scaffolds `.config/sync-labels.config.ts` with auto-detected workspace scope labels and a `.github/workflows/sync-labels.yaml` caller workflow, then generates `.github/labels.yaml`. `generate` reads the config and writes `.github/labels.yaml`. `sync` triggers the workflow remotely — it requires the `gh` CLI and an existing workflow file.
|
|
387
461
|
|
|
462
|
+
#### Published JSON Schema for `.meta/label-map.json`
|
|
463
|
+
|
|
464
|
+
release-kit publishes a JSON Schema for `.meta/label-map.json` — a separate, generic data file that maps commit-prefix scopes and types to GitHub label names. The schema lives at `packages/release-kit/schemas/label-map.json` in this repo and is reachable via the stable raw URL:
|
|
465
|
+
|
|
466
|
+
```
|
|
467
|
+
https://github.com/williamthorsen/node-monorepo-tools/raw/release-kit-v<version>/packages/release-kit/schemas/label-map.json
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Consumers reference it from the top of their `.meta/label-map.json`:
|
|
471
|
+
|
|
472
|
+
```json
|
|
473
|
+
{
|
|
474
|
+
"$schema": "https://github.com/williamthorsen/node-monorepo-tools/raw/release-kit-v<version>/packages/release-kit/schemas/label-map.json",
|
|
475
|
+
"types": { "feat": "feature", "fix": "fix" },
|
|
476
|
+
"scopes": { "audit": "scope:audit" }
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
release-kit publishes the schema only; it does not generate `.meta/label-map.json`. Generation requires commit-prefix knowledge that lives outside release-kit (in agent-conventions tooling), and is owned by those consumers.
|
|
481
|
+
|
|
388
482
|
## GitHub Actions workflow
|
|
389
483
|
|
|
390
484
|
The `init` command scaffolds a release workflow at `.github/workflows/release.yaml` that delegates to a reusable release workflow. The scaffolded workflow accepts these inputs:
|
package/dist/esm/.cache
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
fca70d83414ef35352e152c9e916f24705c93c5556b14c16388a5b791752f075
|
|
@@ -6,7 +6,7 @@ function assertCleanWorkingTree() {
|
|
|
6
6
|
}).trim();
|
|
7
7
|
if (status.length > 0) {
|
|
8
8
|
throw new Error(
|
|
9
|
-
"Working tree has uncommitted changes. Commit or stash them
|
|
9
|
+
"Working tree has uncommitted changes. Commit or stash them, or use --no-git-checks to bypass this check."
|
|
10
10
|
);
|
|
11
11
|
}
|
|
12
12
|
}
|
|
@@ -183,7 +183,7 @@ Publish packages that have release tags on HEAD.
|
|
|
183
183
|
|
|
184
184
|
Options:
|
|
185
185
|
--dry-run Preview without publishing
|
|
186
|
-
--no-git-checks Skip
|
|
186
|
+
--no-git-checks Skip the clean-working-tree check
|
|
187
187
|
--tags=tag1,tag2 Only publish the named tags (comma-separated, full tag names)
|
|
188
188
|
--provenance Generate provenance statement (requires OIDC, not supported by classic yarn)
|
|
189
189
|
--help, -h Show this help message
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { GenerateChangelogOptions } from './generateChangelogs.ts';
|
|
2
|
+
import type { ChangelogEntry, ReleaseConfig } from './types.ts';
|
|
3
|
+
export declare function buildChangelogEntries(config: Pick<ReleaseConfig, 'cliffConfigPath' | 'changelogJson'>, tag: string, options?: GenerateChangelogOptions): ChangelogEntry[];
|
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { copyFileSync,
|
|
2
|
+
import { copyFileSync, mkdtempSync, rmSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import { extractVersion, isChangelogEntry } from "./changelogJsonUtils.js";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { extractVersion } from "./changelogJsonUtils.js";
|
|
7
6
|
import { resolveCliffConfigPath } from "./resolveCliffConfigPath.js";
|
|
8
7
|
import { isRecord, isUnknownArray } from "./typeGuards.js";
|
|
9
|
-
function
|
|
10
|
-
const outputFile = join(changelogPath, config.changelogJson.outputPath);
|
|
11
|
-
if (dryRun) {
|
|
12
|
-
return [outputFile];
|
|
13
|
-
}
|
|
8
|
+
function buildChangelogEntries(config, tag, options) {
|
|
14
9
|
const resolvedConfigPath = resolveCliffConfigPath(config.cliffConfigPath, import.meta.url);
|
|
15
10
|
let cliffConfigPath = resolvedConfigPath;
|
|
16
11
|
let tempDir;
|
|
@@ -33,15 +28,10 @@ function generateChangelogJson(config, changelogPath, tag, dryRun, options) {
|
|
|
33
28
|
});
|
|
34
29
|
const releases = parseCliffContext(contextJson);
|
|
35
30
|
const devOnlySections = new Set(config.changelogJson.devOnlySections);
|
|
36
|
-
|
|
37
|
-
const existingEntries = readExistingEntries(outputFile);
|
|
38
|
-
const merged = mergeEntries(entries, existingEntries);
|
|
39
|
-
mkdirSync(dirname(outputFile), { recursive: true });
|
|
40
|
-
writeFileSync(outputFile, stringify(merged, { maxLength: 100 }) + "\n", "utf8");
|
|
41
|
-
return [outputFile];
|
|
31
|
+
return transformReleases(releases, devOnlySections);
|
|
42
32
|
} catch (error) {
|
|
43
33
|
throw new Error(
|
|
44
|
-
`Failed to
|
|
34
|
+
`Failed to build changelog entries for tag ${tag}: ${error instanceof Error ? error.message : String(error)}`
|
|
45
35
|
);
|
|
46
36
|
} finally {
|
|
47
37
|
if (tempDir !== void 0) {
|
|
@@ -49,25 +39,6 @@ function generateChangelogJson(config, changelogPath, tag, dryRun, options) {
|
|
|
49
39
|
}
|
|
50
40
|
}
|
|
51
41
|
}
|
|
52
|
-
function generateSyntheticChangelogJson(config, changelogPath, newVersion, date, propagatedFrom, dryRun) {
|
|
53
|
-
const outputFile = join(changelogPath, config.changelogJson.outputPath);
|
|
54
|
-
if (dryRun) {
|
|
55
|
-
return [outputFile];
|
|
56
|
-
}
|
|
57
|
-
const items = propagatedFrom.map((dep) => ({
|
|
58
|
-
description: `Bumped \`${dep.packageName}\` to ${dep.newVersion}`
|
|
59
|
-
}));
|
|
60
|
-
const entry = {
|
|
61
|
-
version: newVersion,
|
|
62
|
-
date,
|
|
63
|
-
sections: [{ title: "Dependency updates", audience: "dev", items }]
|
|
64
|
-
};
|
|
65
|
-
const existingEntries = readExistingEntries(outputFile);
|
|
66
|
-
const merged = mergeEntries([entry], existingEntries);
|
|
67
|
-
mkdirSync(dirname(outputFile), { recursive: true });
|
|
68
|
-
writeFileSync(outputFile, stringify(merged, { maxLength: 100 }) + "\n", "utf8");
|
|
69
|
-
return [outputFile];
|
|
70
|
-
}
|
|
71
42
|
function parseCliffContext(json) {
|
|
72
43
|
const parsed = JSON.parse(json);
|
|
73
44
|
if (!isUnknownArray(parsed)) {
|
|
@@ -183,50 +154,6 @@ function extractBody(message) {
|
|
|
183
154
|
}
|
|
184
155
|
return lines.slice(start, end).join("\n").trim();
|
|
185
156
|
}
|
|
186
|
-
function readExistingEntries(filePath) {
|
|
187
|
-
if (!existsSync(filePath)) {
|
|
188
|
-
return [];
|
|
189
|
-
}
|
|
190
|
-
try {
|
|
191
|
-
const content = readFileSync(filePath, "utf8");
|
|
192
|
-
const parsed = JSON.parse(content);
|
|
193
|
-
if (!isUnknownArray(parsed)) {
|
|
194
|
-
return [];
|
|
195
|
-
}
|
|
196
|
-
return parsed.filter(isChangelogEntry);
|
|
197
|
-
} catch (error) {
|
|
198
|
-
console.warn(
|
|
199
|
-
`Warning: could not parse existing ${filePath}: ${error instanceof Error ? error.message : String(error)}; treating as empty`
|
|
200
|
-
);
|
|
201
|
-
return [];
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
function mergeEntries(newEntries, existingEntries) {
|
|
205
|
-
const versionMap = /* @__PURE__ */ new Map();
|
|
206
|
-
for (const entry of existingEntries) {
|
|
207
|
-
versionMap.set(entry.version, entry);
|
|
208
|
-
}
|
|
209
|
-
for (const entry of newEntries) {
|
|
210
|
-
versionMap.set(entry.version, entry);
|
|
211
|
-
}
|
|
212
|
-
return [...versionMap.values()].sort((a, b) => compareVersionsDescending(a.version, b.version));
|
|
213
|
-
}
|
|
214
|
-
function parseVersionParts(version) {
|
|
215
|
-
return version.split(".").map((s) => {
|
|
216
|
-
const n = Number(s);
|
|
217
|
-
return Number.isNaN(n) ? 0 : n;
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
function compareVersionsDescending(a, b) {
|
|
221
|
-
const partsA = parseVersionParts(a);
|
|
222
|
-
const partsB = parseVersionParts(b);
|
|
223
|
-
for (let i = 0; i < 3; i++) {
|
|
224
|
-
const diff = (partsB[i] ?? 0) - (partsA[i] ?? 0);
|
|
225
|
-
if (diff !== 0) return diff;
|
|
226
|
-
}
|
|
227
|
-
return 0;
|
|
228
|
-
}
|
|
229
157
|
export {
|
|
230
|
-
|
|
231
|
-
generateSyntheticChangelogJson
|
|
158
|
+
buildChangelogEntries
|
|
232
159
|
};
|
|
@@ -3,5 +3,6 @@ export interface DependencyGraph {
|
|
|
3
3
|
packageNameToDir: Map<string, string>;
|
|
4
4
|
dirToPackageName: Map<string, string>;
|
|
5
5
|
dependentsOf: Map<string, WorkspaceConfig[]>;
|
|
6
|
+
dependenciesOf: Map<string, Set<string>>;
|
|
6
7
|
}
|
|
7
8
|
export declare function buildDependencyGraph(workspaces: readonly WorkspaceConfig[]): DependencyGraph;
|
|
@@ -6,6 +6,7 @@ function buildDependencyGraph(workspaces) {
|
|
|
6
6
|
const packageNameToDir = /* @__PURE__ */ new Map();
|
|
7
7
|
const dirToPackageName = /* @__PURE__ */ new Map();
|
|
8
8
|
const dependentsOf = /* @__PURE__ */ new Map();
|
|
9
|
+
const dependenciesOf = /* @__PURE__ */ new Map();
|
|
9
10
|
const workspacePackages = /* @__PURE__ */ new Map();
|
|
10
11
|
for (const workspace of workspaces) {
|
|
11
12
|
const primaryPackageFile = workspace.packageFiles[0];
|
|
@@ -32,9 +33,15 @@ function buildDependencyGraph(workspaces) {
|
|
|
32
33
|
} else {
|
|
33
34
|
existing.push(workspace);
|
|
34
35
|
}
|
|
36
|
+
const forward = dependenciesOf.get(workspace.dir);
|
|
37
|
+
if (forward === void 0) {
|
|
38
|
+
dependenciesOf.set(workspace.dir, /* @__PURE__ */ new Set([depName]));
|
|
39
|
+
} else {
|
|
40
|
+
forward.add(depName);
|
|
41
|
+
}
|
|
35
42
|
}
|
|
36
43
|
}
|
|
37
|
-
return { packageNameToDir, dirToPackageName, dependentsOf };
|
|
44
|
+
return { packageNameToDir, dirToPackageName, dependentsOf, dependenciesOf };
|
|
38
45
|
}
|
|
39
46
|
function readPackageJsonSubset(filePath) {
|
|
40
47
|
let content;
|
|
@@ -2,7 +2,7 @@ import { stripScope } from "./stripScope.js";
|
|
|
2
2
|
function buildReleaseSummary(result) {
|
|
3
3
|
const sections = [];
|
|
4
4
|
for (const workspace of result.workspaces) {
|
|
5
|
-
if (workspace.status !== "released"
|
|
5
|
+
if (workspace.status !== "released") {
|
|
6
6
|
continue;
|
|
7
7
|
}
|
|
8
8
|
const commits = workspace.commits;
|
|
@@ -15,6 +15,14 @@ function buildReleaseSummary(result) {
|
|
|
15
15
|
}
|
|
16
16
|
sections.push(lines.join("\n"));
|
|
17
17
|
}
|
|
18
|
+
const project = result.project;
|
|
19
|
+
if (project !== void 0 && project.status === "released" && project.commits.length > 0) {
|
|
20
|
+
const lines = [project.tag];
|
|
21
|
+
for (const commit of project.commits) {
|
|
22
|
+
lines.push(`- ${stripScope(commit.message)}`);
|
|
23
|
+
}
|
|
24
|
+
sections.push(lines.join("\n"));
|
|
25
|
+
}
|
|
18
26
|
return sections.join("\n\n");
|
|
19
27
|
}
|
|
20
28
|
export {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
function buildSyntheticChangelogEntry(propagatedFrom, version, date) {
|
|
2
|
+
const items = propagatedFrom.map((dep) => ({
|
|
3
|
+
description: `Bumped \`${dep.packageName}\` to ${dep.newVersion}`
|
|
4
|
+
}));
|
|
5
|
+
return {
|
|
6
|
+
version,
|
|
7
|
+
date,
|
|
8
|
+
sections: [{ title: "Dependency updates", audience: "dev", items }]
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
buildSyntheticChangelogEntry
|
|
13
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ChangelogEntry, ReleaseConfig } from './types.ts';
|
|
2
|
+
export declare function resolveChangelogJsonPath(config: Pick<ReleaseConfig, 'changelogJson'>, changelogPath: string): string;
|
|
3
|
+
export declare function writeChangelogJson(filePath: string, entries: ChangelogEntry[]): string;
|
|
4
|
+
export declare function upsertChangelogJson(filePath: string, entries: ChangelogEntry[]): string;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import stringify from "json-stringify-pretty-compact";
|
|
4
|
+
import semver from "semver";
|
|
5
|
+
import { isChangelogEntry } from "./changelogJsonUtils.js";
|
|
6
|
+
import { isUnknownArray } from "./typeGuards.js";
|
|
7
|
+
function resolveChangelogJsonPath(config, changelogPath) {
|
|
8
|
+
return join(changelogPath, config.changelogJson.outputPath);
|
|
9
|
+
}
|
|
10
|
+
function writeChangelogJson(filePath, entries) {
|
|
11
|
+
const sorted = sortNewestFirst(entries);
|
|
12
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
13
|
+
writeFileSync(filePath, stringify(sorted, { maxLength: 100 }) + "\n", "utf8");
|
|
14
|
+
return filePath;
|
|
15
|
+
}
|
|
16
|
+
function upsertChangelogJson(filePath, entries) {
|
|
17
|
+
const existing = readExistingEntries(filePath);
|
|
18
|
+
const merged = mergeEntries(entries, existing);
|
|
19
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
20
|
+
writeFileSync(filePath, stringify(merged, { maxLength: 100 }) + "\n", "utf8");
|
|
21
|
+
return filePath;
|
|
22
|
+
}
|
|
23
|
+
function sortNewestFirst(entries) {
|
|
24
|
+
return [...entries].sort((a, b) => compareVersionsDescending(a.version, b.version));
|
|
25
|
+
}
|
|
26
|
+
function readExistingEntries(filePath) {
|
|
27
|
+
if (!existsSync(filePath)) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const content = readFileSync(filePath, "utf8");
|
|
32
|
+
const parsed = JSON.parse(content);
|
|
33
|
+
if (!isUnknownArray(parsed)) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
return parsed.filter(isChangelogEntry);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.warn(
|
|
39
|
+
`Warning: could not parse existing ${filePath}: ${error instanceof Error ? error.message : String(error)}; treating as empty`
|
|
40
|
+
);
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function mergeEntries(newEntries, existingEntries) {
|
|
45
|
+
const versionMap = /* @__PURE__ */ new Map();
|
|
46
|
+
for (const entry of existingEntries) {
|
|
47
|
+
versionMap.set(entry.version, entry);
|
|
48
|
+
}
|
|
49
|
+
for (const entry of newEntries) {
|
|
50
|
+
versionMap.set(entry.version, entry);
|
|
51
|
+
}
|
|
52
|
+
return sortNewestFirst(versionMap.values());
|
|
53
|
+
}
|
|
54
|
+
function compareVersionsDescending(a, b) {
|
|
55
|
+
const aValid = semver.valid(a);
|
|
56
|
+
const bValid = semver.valid(b);
|
|
57
|
+
if (aValid && bValid) return semver.rcompare(aValid, bValid);
|
|
58
|
+
if (aValid) return -1;
|
|
59
|
+
if (bValid) return 1;
|
|
60
|
+
if (a > b) return -1;
|
|
61
|
+
if (a < b) return 1;
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
resolveChangelogJsonPath,
|
|
66
|
+
upsertChangelogJson,
|
|
67
|
+
writeChangelogJson
|
|
68
|
+
};
|