@williamthorsen/release-kit 5.2.0 → 5.3.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 +69 -25
- package/README.md +238 -44
- package/cliff.toml.template +4 -39
- package/dist/esm/.cache +1 -1
- package/dist/esm/bin/release-kit.js +59 -0
- package/dist/esm/buildChangelogEntries.js +6 -0
- package/dist/esm/buildEmptyReleaseEntry.d.ts +2 -0
- package/dist/esm/buildEmptyReleaseEntry.js +16 -0
- package/dist/esm/changelogJsonFile.d.ts +2 -0
- package/dist/esm/changelogJsonFile.js +11 -1
- package/dist/esm/changelogJsonUtils.d.ts +2 -1
- package/dist/esm/changelogJsonUtils.js +9 -0
- package/dist/esm/changelogOverrides.d.ts +53 -0
- package/dist/esm/changelogOverrides.js +424 -0
- package/dist/esm/checkWorkTypesDrift.js +3 -2
- package/dist/esm/createGithubReleaseCommand.js +0 -9
- package/dist/esm/defaults.js +1 -1
- package/dist/esm/generateChangelogs.d.ts +0 -3
- package/dist/esm/generateChangelogs.js +1 -35
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/loadConfig.d.ts +1 -1
- package/dist/esm/releasePrepare.js +68 -11
- package/dist/esm/releasePrepareMono.js +103 -56
- package/dist/esm/releasePrepareProject.d.ts +4 -1
- package/dist/esm/releasePrepareProject.js +74 -18
- package/dist/esm/renderChangelogMarkdown.d.ts +12 -0
- package/dist/esm/renderChangelogMarkdown.js +32 -0
- package/dist/esm/renderReleaseNotes.js +6 -1
- package/dist/esm/resolveReleaseNotesConfig.js +3 -1
- package/dist/esm/runGitCliff.d.ts +1 -0
- package/dist/esm/runGitCliff.js +7 -0
- package/dist/esm/syncWorkTypes.js +3 -2
- package/dist/esm/types.d.ts +98 -31
- package/dist/esm/types.js +60 -0
- package/dist/esm/validateConfig.js +84 -345
- package/dist/esm/validateOverridesCommand.d.ts +13 -0
- package/dist/esm/validateOverridesCommand.js +119 -0
- package/dist/esm/work-types.json +23 -17
- package/dist/esm/work-types.schema.json +28 -1
- package/dist/esm/workTypesData.d.ts +8 -0
- package/dist/esm/workTypesData.js +20 -17
- package/dist/esm/workTypesUtils.d.ts +1 -0
- package/dist/esm/workTypesUtils.js +8 -0
- package/package.json +4 -3
- package/dist/esm/writeSyntheticChangelog.d.ts +0 -9
- package/dist/esm/writeSyntheticChangelog.js +0 -27
package/README.md
CHANGED
|
@@ -2,42 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
Version-bumping and changelog-generation toolkit for release workflows.
|
|
4
4
|
|
|
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
|
|
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 from `git-cliff --context` output rendered in-process (with optional [editorial overrides](#editorial-overrides)).
|
|
6
6
|
|
|
7
7
|
<!-- section:release-notes -->
|
|
8
|
-
## Release notes — v5.
|
|
8
|
+
## Release notes — v5.3.0 (2026-05-10)
|
|
9
9
|
|
|
10
10
|
### 🎉 Features
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- Enable editorial overrides for changelog entries (#387)
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Allows `release-kit` consumers to skip or correct historical changelog entries by means of an overrides file.
|
|
15
15
|
|
|
16
|
-
-
|
|
16
|
+
- Decentralize changelog overrides to per-scope .meta/ files (#391)
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Adds support for workspace-scoped editorial-override files for `release-kit`-generated changelogs. A repo-root file applies overrides to every workspace's changelog; a workspace-tier file applies only to that workspace.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
- Add section markers and authenticated upstream fetch (#393)
|
|
21
|
+
|
|
22
|
+
A new `markers` block in `work-types.json` describes the breaking-changes emoji and label, making them available for use by consumers.
|
|
21
23
|
|
|
22
|
-
-
|
|
24
|
+
`work-types check` and `work-types sync` now authenticate when `GITHUB_TOKEN` is set, so they can reach private upstream repositories.
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
- Validate changelog overrides from the command line (#395)
|
|
25
27
|
|
|
26
|
-
-
|
|
28
|
+
Adds a `release-kit overrides validate` subcommand that audits every `.meta/changelog-overrides.json` file across the project root and per-workspace scopes in one pass. The command reports schema errors, ambiguous-prefix collisions, and stale-key warnings with tiered exit codes so CI can choose its own failure threshold. The same validation is also available via a library function exported by the package.
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
### 🐛 Bug fixes
|
|
29
31
|
|
|
30
|
-
-
|
|
32
|
+
- Suppress git-cliff stale-version warnings on prepare (#373)
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
Fixes an issue where `release-kit prepare` repeatedly printed git-cliff's "A new version of git-cliff is available" notice — twice per release unit, so 2 × N times for an N-package monorepo run — while never updating the locally cached git-cliff binary. Each `prepare` run now revalidates the npm cache once before any cliff work, so the binary stays current with upstream releases and the notice no longer surfaces on every per-workspace invocation.
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
- Use synthetic changelogs for forced empty-range releases (#376)
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
Fixes an issue where `release-kit prepare` with `--force`, `--bump=X`, or `--set-version` would invoke git-cliff against units that had no commits since their last tag, surfacing confusing `WARN git_cliff > There is already a tag (...)` lines (twice per affected unit) and silently leaving `CHANGELOG.md` and `.meta/changelog.json` stale. Empty-range bumps now write a synthetic `Notes / Forced version bump.` entry to both files instead of invoking git-cliff. Applies to all three release stages: single-package, per-workspace, and project. Prior changelog history is preserved on every path.
|
|
37
39
|
|
|
38
|
-
-
|
|
40
|
+
- Accept `breakingPolicies` field in config files (#394)
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
Fixes an issue where setting `breakingPolicies` in `release-kit.config.ts` was rejected as an unknown field, leaving per-work-type breaking-policy configuration unreachable from the config file. Each entry accepts `'forbidden'`, `'optional'`, or `'required'`; an empty object opts out of enforcement.
|
|
41
43
|
<!-- /section:release-notes -->
|
|
42
44
|
|
|
43
45
|
## Installation
|
|
@@ -68,7 +70,7 @@ Example output from `prepare --dry-run` in a monorepo:
|
|
|
68
70
|
📦 1.2.0 → 1.3.0 (minor)
|
|
69
71
|
[dry-run] Would bump packages/arrays/package.json
|
|
70
72
|
Generating changelogs...
|
|
71
|
-
[dry-run] Would
|
|
73
|
+
[dry-run] Would write packages/arrays/CHANGELOG.md
|
|
72
74
|
🏷️ arrays-v1.3.0
|
|
73
75
|
|
|
74
76
|
── strings ─────────────────────────────────────
|
|
@@ -78,7 +80,7 @@ Example output from `prepare --dry-run` in a monorepo:
|
|
|
78
80
|
📦 0.5.1 → 0.5.2 (patch)
|
|
79
81
|
[dry-run] Would bump packages/strings/package.json
|
|
80
82
|
Generating changelogs...
|
|
81
|
-
[dry-run] Would
|
|
83
|
+
[dry-run] Would write packages/strings/CHANGELOG.md
|
|
82
84
|
🏷️ strings-v0.5.2
|
|
83
85
|
|
|
84
86
|
✅ Release preparation complete.
|
|
@@ -93,7 +95,7 @@ That's it for most repos. The CLI auto-discovers workspaces and applies sensible
|
|
|
93
95
|
1. **Workspace discovery**: reads `pnpm-workspace.yaml` and resolves its `packages` globs to find workspace directories. Each directory containing a `package.json` becomes a workspace. If no workspace file is found, the repo is treated as a single-package project.
|
|
94
96
|
2. **Config loading**: loads `.config/release-kit.config.ts` (if present) via [jiti](https://github.com/unjs/jiti) and merges it with discovered defaults.
|
|
95
97
|
3. **Commit analysis**: for each workspace, finds commits since the last version tag, parses them for type and scope, and determines the appropriate version bump.
|
|
96
|
-
4. **Version bump + changelog**: bumps `package.json` versions and
|
|
98
|
+
4. **Version bump + changelog**: bumps `package.json` versions, builds structured `ChangelogEntry[]` from `git-cliff --context`, applies any [editorial overrides](#editorial-overrides) from per-scope `.meta/changelog-overrides.json` files, and renders both `CHANGELOG.md` and `.meta/changelog.json` from that single source. `git-cliff` is invoked only for its `--context` JSON; markdown rendering happens in-process so `.meta/changelog.json` and `CHANGELOG.md` always agree.
|
|
97
99
|
5. **Release tags file**: writes computed tags to `tmp/.release-tags` for the release workflow to read when tagging and pushing.
|
|
98
100
|
|
|
99
101
|
## Commit format
|
|
@@ -236,7 +238,7 @@ When configured, each `release-kit prepare` run additionally:
|
|
|
236
238
|
|
|
237
239
|
- Computes commits since the last project tag (`<tagPrefix><version>`), filtered to the union of every contributing workspace's paths.
|
|
238
240
|
- Bumps the root `package.json`'s `version` field using the same bump-derivation rules as workspaces (or the `--bump=...` override).
|
|
239
|
-
- Regenerates the root `./CHANGELOG.md`
|
|
241
|
+
- Regenerates the root `./CHANGELOG.md` from the structured `ChangelogEntry[]` produced by `git-cliff --context` (scoped to the project's `tagPrefix` and contributing paths) and any matching editorial overrides.
|
|
240
242
|
- Emits `./.meta/changelog.json` (when `changelogJson.enabled`).
|
|
241
243
|
- With `--with-release-notes`, additionally emits `./docs/RELEASE_NOTES.v<version>.md`.
|
|
242
244
|
- Appends the project tag to `tmp/.release-tags` so `release-kit commit` and `release-kit tag` pick it up alongside per-workspace tags.
|
|
@@ -310,33 +312,33 @@ The canonical taxonomy lives in `packages/release-kit/src/work-types.json` and i
|
|
|
310
312
|
|
|
311
313
|
| Tier | Key | Header | Aliases | `!` policy |
|
|
312
314
|
| -------- | ----------- | ------------------------- | ------------- | ------------ |
|
|
313
|
-
|
|
|
314
|
-
|
|
|
315
|
-
|
|
|
316
|
-
|
|
|
317
|
-
|
|
|
318
|
-
|
|
|
319
|
-
|
|
|
320
|
-
|
|
|
321
|
-
|
|
|
322
|
-
|
|
|
323
|
-
|
|
|
324
|
-
|
|
|
325
|
-
|
|
|
326
|
-
|
|
|
327
|
-
|
|
|
315
|
+
| public | `feat` | 🎉 Features | `feature` | optional |
|
|
316
|
+
| public | `drop` | 🪦 Removed | | **required** |
|
|
317
|
+
| public | `deprecate` | 🗑️ Deprecated | | forbidden |
|
|
318
|
+
| public | `fix` | 🐛 Bug fixes | `bugfix` | forbidden |
|
|
319
|
+
| public | `sec` | 🔒 Security | `security` | optional |
|
|
320
|
+
| public | `perf` | ⚡ Performance | `performance` | forbidden |
|
|
321
|
+
| internal | `internal` | 🏗️ Internal features | `utility` | forbidden |
|
|
322
|
+
| internal | `refactor` | ♻️ Refactoring | | forbidden |
|
|
323
|
+
| internal | `tests` | 🧪 Tests | `test` | forbidden |
|
|
324
|
+
| process | `tooling` | ⚙️ Tooling | | forbidden |
|
|
325
|
+
| process | `ci` | 👷 CI | | forbidden |
|
|
326
|
+
| process | `deps` | 📦 Dependencies | `dep` | forbidden |
|
|
327
|
+
| process | `ai` | 🤖 Agentic support | | forbidden |
|
|
328
|
+
| process | `docs` | 📚 Documentation | `doc` | forbidden |
|
|
329
|
+
| process | `fmt` | (excluded from changelog) | | forbidden |
|
|
328
330
|
|
|
329
331
|
#### Tier semantics
|
|
330
332
|
|
|
331
|
-
-
|
|
332
|
-
-
|
|
333
|
-
-
|
|
333
|
+
- **`public`** — visible to all audiences. `public`-tier sections appear in both public release notes and dev changelogs.
|
|
334
|
+
- **`internal`** — dev-only. `internal`-tier sections appear in dev changelogs but not in public-facing release notes.
|
|
335
|
+
- **`process`** — dev-only. Same audience treatment as `internal`.
|
|
334
336
|
|
|
335
|
-
Section render order is **tier order (
|
|
337
|
+
Section render order is **tier order (`public` → `internal` → `process`), then row order within tier**. The bundled `cliff.toml.template` encodes this order via hidden `<!-- NN -->` HTML-comment prefixes on each parser's `group` value; tera's `group_by` filter sorts groups lexicographically (now monotonic by row number), and the body template's `striptags` filter erases the prefix from rendered headings.
|
|
336
338
|
|
|
337
339
|
#### `docs` reclassification
|
|
338
340
|
|
|
339
|
-
`docs`/Documentation has moved from the all-audience tier (where it lived before this taxonomy was formalised) to the dev-only
|
|
341
|
+
`docs`/Documentation has moved from the all-audience tier (where it lived before this taxonomy was formalised) to the dev-only `process` tier. **Documentation commits no longer appear in public-facing release notes.** They still appear in `CHANGELOG.md` and `changelog.json` under the `audience: 'dev'` classification.
|
|
340
342
|
|
|
341
343
|
#### `utility` alias
|
|
342
344
|
|
|
@@ -382,6 +384,22 @@ Items whose commit subject carries the `!` prefix (e.g. `feat!`, `drop!`, `feat(
|
|
|
382
384
|
|
|
383
385
|
Only the prefix `!` triggers this marker. A `BREAKING CHANGE:` body footer on its own does **not** retroactively mark a changelog item as breaking — the changelog signal is tied to the commit-prefix policy. This avoids surprise breaking-marker appearances for older commits written under earlier conventions.
|
|
384
386
|
|
|
387
|
+
The emoji and label of this marker are sourced from the `markers.breaking` entry in `work-types.json` (see [Section markers](#section-markers)) so consumers that render their own breaking-changes section draw from the same SSOT.
|
|
388
|
+
|
|
389
|
+
### Section markers
|
|
390
|
+
|
|
391
|
+
Alongside `tiers` and `types`, `work-types.json` exposes a top-level `markers` object for cross-cutting section markers — visual indicators that aren't tied to a specific work type. Today the canonical entry is `breaking`; additional keys (e.g., security advisories, migration notices) can be added without a schema change.
|
|
392
|
+
|
|
393
|
+
```jsonc
|
|
394
|
+
{
|
|
395
|
+
"markers": {
|
|
396
|
+
"breaking": { "emoji": "🚨", "label": "Breaking" },
|
|
397
|
+
},
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
Entries store plain text only — the SSOT is format-agnostic, so consumers apply their own emphasis (Markdown bold, ANSI escape, HTML `<strong>`) when constructing the rendered form. release-kit's own renderer constructs the per-bullet prefix as `${emoji} **${label}:** ` from this entry.
|
|
402
|
+
|
|
385
403
|
#### `fmt`
|
|
386
404
|
|
|
387
405
|
`fmt:` commits are recognized by `parseCommitMessage` (they contribute to a patch bump) but `fmt` carries `excludedFromChangelog: true`. The bundled `cliff.toml.template` skips `fmt:` commits at the parser level, so they never appear in `CHANGELOG.md`, `changelog.json`, or release notes. The label and emoji are present in `work-types.json` for schema parity with the codeassembly upstream but never render.
|
|
@@ -390,7 +408,169 @@ Only the prefix `!` triggers this marker. A `BREAKING CHANGE:` body footer on it
|
|
|
390
408
|
|
|
391
409
|
Work types from your config are merged with these defaults by key — your entries override or extend, they don't replace the full set. Release-notes sections are rendered in the declaration order of the merged work-types record, with any unknown titles trailing the known ones.
|
|
392
410
|
|
|
393
|
-
The default `devOnlySections` (excluded from public release notes but still written to `CHANGELOG.md`) are derived from the
|
|
411
|
+
The default `devOnlySections` (excluded from public release notes but still written to `CHANGELOG.md`) are derived from the `internal` and `process` tiers (excluding `fmt`). Override via `changelogJson.devOnlySections` in your config; matching is decorator-tolerant, so a bare-name override like `['Internal features']` keeps working against the emoji-prefixed and prefix-decorated default titles.
|
|
412
|
+
|
|
413
|
+
## Editorial overrides
|
|
414
|
+
|
|
415
|
+
Generated changelogs occasionally need editorial correction — typos, redacted scope, reworded entries, or historical commits whose bodies carry verbatim PR-template scaffolding (`## What`, `## Why`, etc.) that renders as literal text in user-facing release notes. Rewriting git history is not viable, and any in-place edit to `CHANGELOG.md` or `.meta/changelog.json` is overwritten on the next release because release-kit regenerates both artifacts from scratch.
|
|
416
|
+
|
|
417
|
+
Override files are the supported escape hatch. Drop a checked-in JSON file at the conventional path for the scope you want to influence, keyed by commit hash, and `release-kit prepare` applies the overrides between `buildChangelogEntries` and serialization. Both `CHANGELOG.md` and `.meta/changelog.json` reflect the post-override view, so downstream consumers (the GitHub Release body, the in-app release-notes page, etc.) see the same content.
|
|
418
|
+
|
|
419
|
+
### File-location convention
|
|
420
|
+
|
|
421
|
+
| Scope | Path | Applies to |
|
|
422
|
+
| ------------------- | ---------------------------------------------- | ------------------------------------------------------- |
|
|
423
|
+
| Project (root) | `.meta/changelog-overrides.json` | The project changelog and every workspace's changelog |
|
|
424
|
+
| Workspace | `packages/<ws>/.meta/changelog-overrides.json` | Only that workspace's changelog |
|
|
425
|
+
| Single-package mode | `.meta/changelog-overrides.json` | The package's changelog (collapses to the project case) |
|
|
426
|
+
|
|
427
|
+
Filenames have no leading dot — the `.meta/` directory already provides the visibility property and parallels its sibling artifacts (`changelog.json`, `label-map.json`).
|
|
428
|
+
|
|
429
|
+
### Composition: per-key shadowing
|
|
430
|
+
|
|
431
|
+
When a workspace's changelog is rendered, both files are consulted:
|
|
432
|
+
|
|
433
|
+
- The root file's overrides apply globally.
|
|
434
|
+
- The workspace file's overrides apply only to that workspace.
|
|
435
|
+
- When the **same hash key** (string-equal, byte-for-byte) appears in both files, the **workspace entry wins entirely** for that workspace's changelog — no field-level merge, the workspace entry replaces the root entry.
|
|
436
|
+
- Different prefix strings that happen to resolve to the same commit do **not** shadow; they fall through to the existing ambiguous-prefix error so you can correct your override file.
|
|
437
|
+
- Other keys in the root file still apply for that workspace.
|
|
438
|
+
|
|
439
|
+
The project-level changelog applies only the root file. Per-workspace files describe per-workspace editorial intent and have no meaning at the aggregated project tier.
|
|
440
|
+
|
|
441
|
+
### Stale-key warnings
|
|
442
|
+
|
|
443
|
+
A key that doesn't match any commit gets a stale-reference warning. The warning's scope mirrors the file's scope:
|
|
444
|
+
|
|
445
|
+
- **Per-workspace files** are warned against their own apply context. A key in `packages/foo/.meta/changelog-overrides.json` that doesn't match any commit in foo's changelog is unambiguously stale and is warned immediately.
|
|
446
|
+
- **Root file** keys are aggregated globally — a root key that matches in any workspace or in the project changelog is non-stale; a root key matched nowhere is warned exactly once after all batches complete.
|
|
447
|
+
|
|
448
|
+
### File shape
|
|
449
|
+
|
|
450
|
+
```json
|
|
451
|
+
{
|
|
452
|
+
"82962311": {
|
|
453
|
+
"audience": "skip"
|
|
454
|
+
},
|
|
455
|
+
"abc1234d": {
|
|
456
|
+
"body": "Cleaned-up prose without the original PR-template scaffolding."
|
|
457
|
+
},
|
|
458
|
+
"ef567890": {
|
|
459
|
+
"description": "Rewritten headline that fixes the typo",
|
|
460
|
+
"body": "Optional replacement body."
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Per-entry fields are all optional, but at least one must be present per entry:
|
|
466
|
+
|
|
467
|
+
| Field | Type | Effect |
|
|
468
|
+
| ------------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
469
|
+
| `audience` | `'all' \| 'dev' \| 'skip'` | `'skip'` removes the entry entirely. `'all'` and `'dev'` are reserved for a future audience-reclassification feature (see below). |
|
|
470
|
+
| `description` | `string` | Replaces the entry's bullet headline. Other fields are preserved. |
|
|
471
|
+
| `body` | `string` | Replaces the entry's body (the prose that renders below the bullet). Other fields are preserved. |
|
|
472
|
+
| `breaking` | `boolean` | Toggles the `🚨 **Breaking:** ` marker on the bullet. |
|
|
473
|
+
|
|
474
|
+
### Hash-prefix matching
|
|
475
|
+
|
|
476
|
+
Keys can be either the full 40-character commit SHA or a non-ambiguous prefix. The matcher walks every `ChangelogItem.hash` value present in the entry tree and resolves each override key to its set of matching hashes:
|
|
477
|
+
|
|
478
|
+
- **Exact prefix match (1 hit)** — the override applies. A 7-character prefix is usually unambiguous within a single repo's history; longer prefixes are always safe.
|
|
479
|
+
- **No matches (0 hits)** — the override is treated as a stale reference (probably from a rebase or branch deletion) and a warning is logged. The release continues.
|
|
480
|
+
- **Ambiguous prefix (2+ hits)** — the release aborts with an error naming the key and the matching hashes. Lengthen the prefix or use the full SHA.
|
|
481
|
+
|
|
482
|
+
Override application errors abort the run with a non-zero exit; warnings (zero-match keys) are non-fatal and surface on `PrepareResult.warnings`.
|
|
483
|
+
|
|
484
|
+
### Validation
|
|
485
|
+
|
|
486
|
+
The override file is validated when `release-kit prepare` loads it. Each error names the offending key so you can locate it in your file:
|
|
487
|
+
|
|
488
|
+
- Missing file → empty map, no error (the no-op default — projects that do not need overrides skip the file entirely).
|
|
489
|
+
- Malformed JSON → error.
|
|
490
|
+
- Wrong top-level shape (e.g., array, primitive) → error.
|
|
491
|
+
- Unknown fields on an entry → error.
|
|
492
|
+
- Wrong field types (e.g., `description` as a number) → error.
|
|
493
|
+
- An entry with no fields set → error (a copy-paste mistake more often than not).
|
|
494
|
+
- `audience: 'all'` or `audience: 'dev'` → error in the current release: only `'skip'` is supported (see below).
|
|
495
|
+
|
|
496
|
+
#### Standalone validation: `release-kit overrides validate`
|
|
497
|
+
|
|
498
|
+
For a fast overrides-only health check (locally or as a CI gate), run:
|
|
499
|
+
|
|
500
|
+
```sh
|
|
501
|
+
pnpm exec release-kit overrides validate
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
This walks every `.meta/changelog-overrides.json` file across the project tier and per-workspace tier, reporting three classes of finding:
|
|
505
|
+
|
|
506
|
+
| Class | Examples | Exit code |
|
|
507
|
+
| ------------------- | ------------------------------------------------------------------------------------------- | --------- |
|
|
508
|
+
| Schema/parse errors | malformed JSON, unknown fields, wrong field types, no-field entries, unsupported `audience` | `2` |
|
|
509
|
+
| Ambiguous-prefix | an override key resolves to 2+ commit hashes | `2` |
|
|
510
|
+
| Stale-key warnings | an override key resolves to no commit in its applicable scope | `1` |
|
|
511
|
+
|
|
512
|
+
Exit code semantics:
|
|
513
|
+
|
|
514
|
+
- `0` — clean (no errors, no stale keys).
|
|
515
|
+
- `1` — only stale-key warnings.
|
|
516
|
+
- `2` — schema/parse or ambiguous-prefix errors (errors dominate when both classes are present).
|
|
517
|
+
|
|
518
|
+
Tier-aware stale-key semantics match `release-kit prepare`'s match-set exactly: a workspace-tier key is stale if it does not match in its own workspace's history; a root-tier key is stale only if it matches in **no** scope (no workspace AND not the project release window).
|
|
519
|
+
|
|
520
|
+
The same logic is also exposed programmatically via the `validateAllChangelogOverrides` function exported from `@williamthorsen/release-kit`, for callers that want to integrate the check into their own tooling.
|
|
521
|
+
|
|
522
|
+
### Audience semantics: v1 supports `'skip'` only
|
|
523
|
+
|
|
524
|
+
The on-disk format declares the full `'all' | 'dev' | 'skip'` audience vocabulary so the file format will not need to change when the v2 reclassification feature ships. In the current release, only `'skip'` is supported at runtime; `'all'` and `'dev'` are rejected with an explicit "not yet supported" error.
|
|
525
|
+
|
|
526
|
+
The eventual v2 behavior will let an override move a single item to a different audience section (e.g., reclassifying a `Documentation` entry as `Internal features` to keep it out of public-facing release notes). v1 deliberately leaves that as a separate change so the override mechanism can ship now and the section-split logic can land additively later.
|
|
527
|
+
|
|
528
|
+
### Worked example 1: cleaning up scaffolded historical commits (root file)
|
|
529
|
+
|
|
530
|
+
Suppose a year-old commit `82962311` was authored from a PR template that left `## What` / `## Why` headings in the body, and that commit now appears in your in-app release notes as literal Markdown headings. Add an override at the project tier:
|
|
531
|
+
|
|
532
|
+
```json
|
|
533
|
+
// .meta/changelog-overrides.json
|
|
534
|
+
{
|
|
535
|
+
"82962311": {
|
|
536
|
+
"body": "Add the in-app release-notes page with version-aware navigation."
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
On the next `release-kit prepare` run, the matched item's body is replaced before the JSON and Markdown artifacts are written. The original git history is untouched.
|
|
542
|
+
|
|
543
|
+
### Worked example 2: suppressing a cross-attribution spillover (workspace file)
|
|
544
|
+
|
|
545
|
+
Release-kit attributes commits to workspaces by file path, so a commit that primarily belongs to one workspace can land in another's changelog if it touched files there. Suppose commit `1ce3d2f` renamed the `audit-deps` package to `v11y-check` (scope `v11y-check`) but also edited `packages/nmr/src/default-scripts.ts` and `packages/nmr/README.md`. The commit correctly appears in `packages/v11y-check/CHANGELOG.md`, but it also spills into `packages/nmr/CHANGELOG.md` where it isn't the right editorial framing.
|
|
546
|
+
|
|
547
|
+
Drop a workspace-tier override at `packages/nmr/.meta/changelog-overrides.json`:
|
|
548
|
+
|
|
549
|
+
```json
|
|
550
|
+
// packages/nmr/.meta/changelog-overrides.json
|
|
551
|
+
{
|
|
552
|
+
"1ce3d2f": {
|
|
553
|
+
"audience": "skip"
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
The commit is now suppressed in nmr's changelog only — it still appears in v11y-check's, where it belongs. A root-tier `'skip'` would have removed it from both, which is the wrong outcome.
|
|
559
|
+
|
|
560
|
+
### Rendering pipeline change
|
|
561
|
+
|
|
562
|
+
Prior versions of release-kit shelled out to `git-cliff` for both structured `--context` JSON and rendered Markdown (cliff's body template). After this change, `git-cliff` is invoked only for `--context` JSON; release-kit's in-process `renderChangelogMarkdown` produces `CHANGELOG.md` from the same `ChangelogEntry[]` that drives `.meta/changelog.json`. The two artifacts can no longer disagree.
|
|
563
|
+
|
|
564
|
+
The bundled `cliff.toml.template`'s body template has been emptied (the `[git].commit_parsers` section is still load-bearing for `--context` group assignment); custom `.config/git-cliff.toml` files no longer need a body template.
|
|
565
|
+
|
|
566
|
+
Observable output differences from the prior cliff-rendered format:
|
|
567
|
+
|
|
568
|
+
- Trailers (`Signed-off-by:`, `Co-authored-by:`, `Closes #N`, GitHub PR URLs) are stripped from rendered bodies.
|
|
569
|
+
- Items whose commit subject carries the `!` breaking marker render with a `🚨 **Breaking:** ` prefix.
|
|
570
|
+
- Empty version entries (releases with no commits routed to a section) are omitted; the prior cliff template rendered them as bare headings.
|
|
571
|
+
- The footer comment is now `<!-- Generated by release-kit. Do not edit this file. Use .meta/changelog-overrides.json to override entries. -->`.
|
|
572
|
+
|
|
573
|
+
The first release that ships under the new renderer will produce a one-time noisy diff in `CHANGELOG.md` (whitespace, trailers, breaking markers). Subsequent releases stabilize.
|
|
394
574
|
|
|
395
575
|
## CLI reference
|
|
396
576
|
|
|
@@ -550,6 +730,18 @@ The check is non-blocking initially: until codeassembly publishes its `work-type
|
|
|
550
730
|
|
|
551
731
|
These commands are also exposed as `nmr work-types:check` / `nmr work-types:sync` from any package directory.
|
|
552
732
|
|
|
733
|
+
#### Authenticated fetches
|
|
734
|
+
|
|
735
|
+
When the upstream codeassembly repo is private, both `check` and `sync` need a GitHub token to fetch the canonical `work-types.json`. Set `GITHUB_TOKEN` in the environment and the commands send `Authorization: Bearer <token>` automatically; without it, requests are unauthenticated and a private upstream will return 404.
|
|
736
|
+
|
|
737
|
+
```sh
|
|
738
|
+
# Source from `gh auth` for local runs:
|
|
739
|
+
export GITHUB_TOKEN=$(gh auth token)
|
|
740
|
+
pnpm exec release-kit work-types check
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
The token needs `contents: read` on the codeassembly repo (fine-grained PAT scope) or the equivalent classic-PAT scope. A token without sufficient scope still produces a 404 — same response as a missing upstream — so a misconfigured token degrades to the transitional-warning path rather than failing loudly. CI wiring against private upstream is deferred until either codeassembly is publicly readable or a cross-repo PAT is provisioned as a workflow secret.
|
|
744
|
+
|
|
553
745
|
### `release-kit sync-labels`
|
|
554
746
|
|
|
555
747
|
Manage GitHub label definitions via config-driven YAML files.
|
|
@@ -621,9 +813,11 @@ The bundled template provides a generic git-cliff configuration that:
|
|
|
621
813
|
|
|
622
814
|
- Strips issue-ticket prefixes matching `^[A-Z]+-\d+\s+` (e.g., `TOOL-123 `, `AFG-456 `)
|
|
623
815
|
- Handles both `type: description` and `workspace|type: description` commit formats
|
|
624
|
-
- Groups commits by work type
|
|
816
|
+
- Groups commits by work type via `[git].commit_parsers`
|
|
817
|
+
|
|
818
|
+
The body template is intentionally empty: release-kit reads cliff's `--context` JSON output and renders `CHANGELOG.md` in-process via `renderChangelogMarkdown` (see [Editorial overrides](#editorial-overrides) for the rationale). The `[git].commit_parsers` section remains load-bearing for `--context` group assignment.
|
|
625
819
|
|
|
626
|
-
To customize, scaffold a local copy with `release-kit init --with-config` and edit `.config/git-cliff.toml`.
|
|
820
|
+
To customize, scaffold a local copy with `release-kit init --with-config` and edit `.config/git-cliff.toml`. Edit only the `[git]` section — body-template changes have no effect.
|
|
627
821
|
|
|
628
822
|
## External dependencies
|
|
629
823
|
|
package/cliff.toml.template
CHANGED
|
@@ -18,45 +18,10 @@
|
|
|
18
18
|
# unique group (not per parser entry) — all parsers routing to the same group
|
|
19
19
|
# share the same prefix.
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
All notable changes to this project will be documented in this file."""
|
|
26
|
-
# Render each commit as a bullet with the description (stripped of ticket ID
|
|
27
|
-
# and scope|type prefix). If the commit has a body, render it as indented
|
|
28
|
-
# continuation paragraphs under the bullet.
|
|
29
|
-
body = """{%- if version %}
|
|
30
|
-
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
|
31
|
-
{%- else %}
|
|
32
|
-
## [unreleased]
|
|
33
|
-
{%- endif -%}
|
|
34
|
-
{%- set non_merge = commits | filter(attribute="merge_commit", value=false) -%}
|
|
35
|
-
{%- for group, group_commits in non_merge | group_by(attribute="group") %}
|
|
36
|
-
|
|
37
|
-
### {{ group | striptags | trim | upper_first }}
|
|
38
|
-
|
|
39
|
-
{% for commit in group_commits -%}
|
|
40
|
-
{%- set subject = commit.message | split(pat="\n") | first -%}
|
|
41
|
-
{%- set desc = subject | split(pat=": ") | slice(start=1) | join(sep=": ") -%}
|
|
42
|
-
{%- set body_lines = commit.message | split(pat="\n") | slice(start=2) -%}
|
|
43
|
-
{%- set body_text = body_lines | join(sep="\n") | trim -%}
|
|
44
|
-
{%- if not loop.first %}
|
|
45
|
-
|
|
46
|
-
{% endif -%}
|
|
47
|
-
- {{ desc | upper_first }}
|
|
48
|
-
{%- if body_text %}
|
|
49
|
-
|
|
50
|
-
{{ body_text | split(pat="\n") | join(sep="\n ") }}
|
|
51
|
-
{%- endif -%}
|
|
52
|
-
{%- endfor -%}
|
|
53
|
-
{%- endfor %}
|
|
54
|
-
"""
|
|
55
|
-
footer = """
|
|
56
|
-
|
|
57
|
-
<!-- generated by git-cliff -->
|
|
58
|
-
"""
|
|
59
|
-
trim = false
|
|
21
|
+
# Markdown rendering is handled in-process by `renderChangelogMarkdown.ts`. cliff is invoked
|
|
22
|
+
# only for `--context` JSON output (see `buildChangelogEntries.ts`), so no `[changelog]` block
|
|
23
|
+
# is needed. The `[git].commit_parsers` section below remains load-bearing for group
|
|
24
|
+
# assignment in the `--context` payload.
|
|
60
25
|
|
|
61
26
|
[git]
|
|
62
27
|
conventional_commits = false
|
package/dist/esm/.cache
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
18bd566d86a692ebdc23649a475e7d5e01562a58df99b6091e222e890cef8f80
|
|
@@ -13,6 +13,7 @@ import { syncLabelsInitCommand } from "../sync-labels/initCommand.js";
|
|
|
13
13
|
import { syncLabelsCommand } from "../sync-labels/syncCommand.js";
|
|
14
14
|
import { syncWorkTypes } from "../syncWorkTypes.js";
|
|
15
15
|
import { tagCommand } from "../tagCommand.js";
|
|
16
|
+
import { validateOverridesCommand } from "../validateOverridesCommand.js";
|
|
16
17
|
const VERSION = readPackageVersion(import.meta.url);
|
|
17
18
|
function showUsage() {
|
|
18
19
|
console.info(`
|
|
@@ -27,6 +28,7 @@ Commands:
|
|
|
27
28
|
create-github-release Create GitHub Releases from changelog.json for tags on HEAD
|
|
28
29
|
show-tag-prefixes Show derived and declared legacy tag prefixes per workspace
|
|
29
30
|
init Initialize release-kit in the current repository
|
|
31
|
+
overrides Manage editorial changelog overrides
|
|
30
32
|
sync-labels Manage GitHub label synchronization
|
|
31
33
|
work-types Check for or sync work-type taxonomy drift against the upstream canonical
|
|
32
34
|
|
|
@@ -174,6 +176,35 @@ legacy tag prefixes. Surfaces any release-shaped tags whose prefix is neither a
|
|
|
174
176
|
derived prefix nor declared in \`legacyIdentities\`, with a copy-pasteable
|
|
175
177
|
config snippet.
|
|
176
178
|
|
|
179
|
+
Options:
|
|
180
|
+
--help, -h Show this help message
|
|
181
|
+
`);
|
|
182
|
+
}
|
|
183
|
+
function showOverridesHelp() {
|
|
184
|
+
console.info(`
|
|
185
|
+
Usage: release-kit overrides <subcommand> [options]
|
|
186
|
+
|
|
187
|
+
Manage editorial changelog overrides.
|
|
188
|
+
|
|
189
|
+
Subcommands:
|
|
190
|
+
validate Validate every \`.meta/changelog-overrides.json\` file across all scopes
|
|
191
|
+
|
|
192
|
+
Options:
|
|
193
|
+
--help, -h Show this help message
|
|
194
|
+
`);
|
|
195
|
+
}
|
|
196
|
+
function showOverridesValidateHelp() {
|
|
197
|
+
console.info(`
|
|
198
|
+
Usage: release-kit overrides validate
|
|
199
|
+
|
|
200
|
+
Validate every \`.meta/changelog-overrides.json\` file across the project and per-workspace
|
|
201
|
+
scopes. Reports schema/parse errors, ambiguous-prefix errors, and stale-key warnings.
|
|
202
|
+
|
|
203
|
+
Exit codes:
|
|
204
|
+
0 Clean \u2014 no errors, no stale keys
|
|
205
|
+
1 Stale-key warnings only (no errors)
|
|
206
|
+
2 Schema/parse or ambiguous-prefix errors (errors dominate)
|
|
207
|
+
|
|
177
208
|
Options:
|
|
178
209
|
--help, -h Show this help message
|
|
179
210
|
`);
|
|
@@ -389,6 +420,34 @@ if (command === "sync-labels") {
|
|
|
389
420
|
showSyncLabelsHelp();
|
|
390
421
|
process.exit(1);
|
|
391
422
|
}
|
|
423
|
+
if (command === "overrides") {
|
|
424
|
+
const subcommand = flags[0];
|
|
425
|
+
const subflags = flags.slice(1);
|
|
426
|
+
if (subcommand === "--help" || subcommand === "-h" || subcommand === void 0) {
|
|
427
|
+
showOverridesHelp();
|
|
428
|
+
process.exit(0);
|
|
429
|
+
}
|
|
430
|
+
if (subcommand === "validate") {
|
|
431
|
+
if (subflags.some((f) => f === "--help" || f === "-h")) {
|
|
432
|
+
showOverridesValidateHelp();
|
|
433
|
+
process.exit(0);
|
|
434
|
+
}
|
|
435
|
+
if (subflags.length > 0) {
|
|
436
|
+
console.error(`Error: Unknown option: ${subflags[0]}`);
|
|
437
|
+
process.exit(1);
|
|
438
|
+
}
|
|
439
|
+
const result = await validateOverridesCommand();
|
|
440
|
+
if (result.exitCode === 0) {
|
|
441
|
+
console.info(result.message);
|
|
442
|
+
} else {
|
|
443
|
+
console.error(result.message);
|
|
444
|
+
}
|
|
445
|
+
process.exit(result.exitCode);
|
|
446
|
+
}
|
|
447
|
+
console.error(`Error: Unknown subcommand: ${subcommand}`);
|
|
448
|
+
showOverridesHelp();
|
|
449
|
+
process.exit(1);
|
|
450
|
+
}
|
|
392
451
|
if (command === "work-types") {
|
|
393
452
|
const subcommand = flags[0];
|
|
394
453
|
const subflags = flags.slice(1);
|
|
@@ -69,6 +69,9 @@ function toCliffContextCommit(value) {
|
|
|
69
69
|
if (typeof value.group === "string") {
|
|
70
70
|
commit.group = value.group;
|
|
71
71
|
}
|
|
72
|
+
if (typeof value.id === "string") {
|
|
73
|
+
commit.id = value.id;
|
|
74
|
+
}
|
|
72
75
|
return commit;
|
|
73
76
|
}
|
|
74
77
|
function transformReleases(releases, devOnlySections) {
|
|
@@ -98,6 +101,9 @@ function transformReleases(releases, devOnlySections) {
|
|
|
98
101
|
if (breaking) {
|
|
99
102
|
item.breaking = true;
|
|
100
103
|
}
|
|
104
|
+
if (commit.id !== void 0 && commit.id !== "") {
|
|
105
|
+
item.hash = commit.id;
|
|
106
|
+
}
|
|
101
107
|
items.push(item);
|
|
102
108
|
}
|
|
103
109
|
const sections = [];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
function buildEmptyReleaseEntry(version, date) {
|
|
2
|
+
return {
|
|
3
|
+
version,
|
|
4
|
+
date,
|
|
5
|
+
sections: [
|
|
6
|
+
{
|
|
7
|
+
title: "Notes",
|
|
8
|
+
audience: "dev",
|
|
9
|
+
items: [{ description: "Forced version bump." }]
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export {
|
|
15
|
+
buildEmptyReleaseEntry
|
|
16
|
+
};
|
|
@@ -2,3 +2,5 @@ import type { ChangelogEntry, ReleaseConfig } from './types.ts';
|
|
|
2
2
|
export declare function resolveChangelogJsonPath(config: Pick<ReleaseConfig, 'changelogJson'>, changelogPath: string): string;
|
|
3
3
|
export declare function writeChangelogJson(filePath: string, entries: ChangelogEntry[]): string;
|
|
4
4
|
export declare function upsertChangelogJson(filePath: string, entries: ChangelogEntry[]): string;
|
|
5
|
+
export declare function upsertChangelogJsonAndReturn(filePath: string, entries: ChangelogEntry[]): ChangelogEntry[];
|
|
6
|
+
export declare function mergeChangelogEntriesWithDisk(filePath: string, entries: ChangelogEntry[]): ChangelogEntry[];
|
|
@@ -14,11 +14,19 @@ function writeChangelogJson(filePath, entries) {
|
|
|
14
14
|
return filePath;
|
|
15
15
|
}
|
|
16
16
|
function upsertChangelogJson(filePath, entries) {
|
|
17
|
+
upsertChangelogJsonAndReturn(filePath, entries);
|
|
18
|
+
return filePath;
|
|
19
|
+
}
|
|
20
|
+
function upsertChangelogJsonAndReturn(filePath, entries) {
|
|
17
21
|
const existing = readExistingEntries(filePath);
|
|
18
22
|
const merged = mergeEntries(entries, existing);
|
|
19
23
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
20
24
|
writeFileSync(filePath, stringify(merged, { maxLength: 100 }) + "\n", "utf8");
|
|
21
|
-
return
|
|
25
|
+
return merged;
|
|
26
|
+
}
|
|
27
|
+
function mergeChangelogEntriesWithDisk(filePath, entries) {
|
|
28
|
+
const existing = readExistingEntries(filePath);
|
|
29
|
+
return mergeEntries(entries, existing);
|
|
22
30
|
}
|
|
23
31
|
function sortNewestFirst(entries) {
|
|
24
32
|
return [...entries].sort((a, b) => compareVersionsDescending(a.version, b.version));
|
|
@@ -62,7 +70,9 @@ function compareVersionsDescending(a, b) {
|
|
|
62
70
|
return 0;
|
|
63
71
|
}
|
|
64
72
|
export {
|
|
73
|
+
mergeChangelogEntriesWithDisk,
|
|
65
74
|
resolveChangelogJsonPath,
|
|
66
75
|
upsertChangelogJson,
|
|
76
|
+
upsertChangelogJsonAndReturn,
|
|
67
77
|
writeChangelogJson
|
|
68
78
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ChangelogEntry } from './types.ts';
|
|
1
|
+
import type { ChangelogEntry, ChangelogItem } from './types.ts';
|
|
2
|
+
export declare function isChangelogItem(value: unknown): value is ChangelogItem;
|
|
2
3
|
export declare function isChangelogEntry(value: unknown): value is ChangelogEntry;
|
|
3
4
|
export declare function extractVersion(tag: string): string;
|
|
4
5
|
export declare function readChangelogEntries(filePath: string): ChangelogEntry[] | undefined;
|