@williamthorsen/release-kit 10.5.0 → 10.7.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/.meta/changelog.json +1570 -0
- package/.readyup/kits/default.js +127 -4
- package/.readyup/kits/npm-auto-publish.js +87 -23
- package/.readyup/manifest.json +14 -9
- package/CHANGELOG.md +83 -0
- package/README.md +68 -963
- package/dist/esm/bin/release-kit.js +4 -3
- package/dist/esm/buildChangelogEntries.js +5 -0
- package/dist/esm/buildDependencyGraph.d.ts +6 -5
- package/dist/esm/buildDependencyGraph.js +5 -1
- package/dist/esm/changelogJsonUtils.d.ts +5 -7
- package/dist/esm/changelogJsonUtils.js +0 -13
- package/dist/esm/changelogOverrides.js +8 -0
- package/dist/esm/extractMigration.d.ts +14 -0
- package/dist/esm/extractMigration.js +18 -0
- package/dist/esm/init/scaffold.js +14 -6
- package/dist/esm/packsPath.d.ts +15 -0
- package/dist/esm/packsPath.js +47 -0
- package/dist/esm/planVersionBump.d.ts +2 -1
- package/dist/esm/planVersionBump.js +4 -0
- package/dist/esm/propagateBumps.d.ts +1 -3
- package/dist/esm/propagateBumps.js +2 -2
- package/dist/esm/releasePrepare.js +0 -16
- package/dist/esm/releasePrepareMono.js +3 -24
- package/dist/esm/reportPrepare.js +4 -2
- package/dist/esm/runGitCliff.d.ts +16 -23
- package/dist/esm/runGitCliff.js +4 -10
- package/dist/esm/sync-labels/resolveLabels.d.ts +5 -3
- package/dist/esm/sync-labels/resolveLabels.js +21 -6
- package/dist/esm/types.d.ts +13 -2
- package/dist/esm/validateConfig.js +1 -1
- package/docs/api.md +73 -0
- package/docs/changelogs.md +70 -0
- package/docs/configuration.md +105 -0
- package/docs/editorial-overrides.md +144 -0
- package/docs/labels.md +83 -0
- package/docs/project-releases.md +74 -0
- package/docs/publishing.md +30 -0
- package/docs/readiness-checks.md +18 -0
- package/docs/releasing.md +108 -0
- package/docs/work-types.md +121 -0
- package/package.json +16 -12
- package/dist/esm/readCurrentVersion.d.ts +0 -2
- package/dist/esm/readCurrentVersion.js +0 -18
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Project releases
|
|
2
|
+
|
|
3
|
+
How to add a project-level release to a monorepo, what the `project` block accepts, and how that release interacts with `prepare`'s flags.
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import type { ReleaseKitConfig } from '@williamthorsen/release-kit/config';
|
|
9
|
+
|
|
10
|
+
const config: ReleaseKitConfig = {
|
|
11
|
+
// Empty object is enough to opt in. Every non-excluded workspace contributes.
|
|
12
|
+
project: {},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default config;
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
When configured, each `release-kit prepare` run additionally:
|
|
19
|
+
|
|
20
|
+
- Computes commits since the last project tag (`<tagPrefix><version>`), filtered to `paths` (by default, the union of every contributing workspace's paths).
|
|
21
|
+
- Bumps the root `package.json`'s `version` field using the same bump-derivation rules as workspaces (or the `--bump=...` override).
|
|
22
|
+
- Regenerates the root `./CHANGELOG.md` from the structured `ChangelogEntry[]` produced by `git-cliff --context` (scoped to the project's `tagPrefix` and the same `paths`) and any matching editorial overrides.
|
|
23
|
+
- Emits `./.meta/changelog.json` (when `changelogJson.enabled`).
|
|
24
|
+
- With `--with-release-notes`, additionally emits `./docs/RELEASE_NOTES.v<version>.md`.
|
|
25
|
+
- Appends the project tag to `tmp/.release-tags` so `release-kit commit` and `release-kit tag` pick it up alongside per-workspace tags.
|
|
26
|
+
|
|
27
|
+
If no commit under `paths` has landed since the last project tag, the project release is silently skipped — same behavior as a per-workspace skip.
|
|
28
|
+
|
|
29
|
+
## `ProjectConfig`
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
interface ProjectConfig {
|
|
33
|
+
paths?: string[]; // Defaults to the union of every contributing workspace's paths
|
|
34
|
+
tagPrefix?: string; // Defaults to 'v'
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| Field | Default | Description |
|
|
39
|
+
| ----------- | ----------------------- | -------------------------------------------------------------------- |
|
|
40
|
+
| `paths` | Contributing-path union | Patterns selecting the commits the project release considers |
|
|
41
|
+
| `tagPrefix` | `'v'` | Prefix for project tags. The full tag is `${tagPrefix}${newVersion}` |
|
|
42
|
+
|
|
43
|
+
By default the contributing workspaces are implicit: every non-excluded discovered workspace contributes its `<dir>/**` glob, and the project release considers commits under their union.
|
|
44
|
+
|
|
45
|
+
Declare `paths` to choose the window yourself. Each entry reaches two matchers: `git log` as a pathspec, and `git-cliff --include-path` as a glob. A declared value **replaces** the workspace union rather than extending it: `paths: ['docs/**']` drops every workspace commit from the project release.
|
|
46
|
+
|
|
47
|
+
A repo whose content lives at the root — where no commit matches any `<dir>/**` glob, so the project release would skip every run — declares the whole tree:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const config: ReleaseKitConfig = {
|
|
51
|
+
project: { paths: ['**'] },
|
|
52
|
+
};
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`'**'` is the whole-tree pattern, matching root-level files, dotfiles, and nested paths alike.
|
|
56
|
+
|
|
57
|
+
Terminate a directory scope with `/**`. The two matchers agree on `'aws/**'` but diverge on a bare `'aws'`, which git reads as the whole subtree and git-cliff matches against nothing; `'.'` is the same trap for the repo root. A release under such an entry finds commits, bumps the version, and writes the tag, while its changelog gains no entry for them — and nothing reports an error.
|
|
58
|
+
|
|
59
|
+
Validation rules:
|
|
60
|
+
|
|
61
|
+
- The root `package.json` must exist and declare a `version` field. release-kit reports an error at config-load time if either is missing.
|
|
62
|
+
- The `project` block is rejected in single-package mode (the package's own release already covers the whole repo, so a project tier would only duplicate it).
|
|
63
|
+
- `paths`, when declared, must hold at least one non-empty string. An empty array is rejected: it reaches git and git-cliff as no filter at all, which is the inverse of how it reads.
|
|
64
|
+
- Unknown fields inside `project` are rejected.
|
|
65
|
+
|
|
66
|
+
CLI flag interactions:
|
|
67
|
+
|
|
68
|
+
- `--dry-run` previews project artifacts alongside workspace artifacts; no files are written.
|
|
69
|
+
- `--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.
|
|
70
|
+
- `--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.
|
|
71
|
+
- `--only` narrows the run to the named workspaces and skips the project release, which is reported as a warning. The project release rolls up every contributing workspace, and `--only` changes which workspaces those are, so rolling up a narrowed set would release a project version covering work the run excluded. Run a full `prepare` (no `--only`) to include the project release.
|
|
72
|
+
- `--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.
|
|
73
|
+
|
|
74
|
+
How `--bump` and `--force` combine for every target is in [Releasing](releasing.md#release-kit-prepare).
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Publishing
|
|
2
|
+
|
|
3
|
+
How `release-kit publish` and `release-kit create-github-release` choose which packages to publish and which tags get a GitHub Release.
|
|
4
|
+
|
|
5
|
+
## `release-kit publish`
|
|
6
|
+
|
|
7
|
+
Publish packages that have release tags on HEAD. The publish workflow's reusable workflow `publish.reusable.yaml` invokes this command in CI.
|
|
8
|
+
|
|
9
|
+
### Publishability filter
|
|
10
|
+
|
|
11
|
+
`publish` and `create-github-release` operate only on workspaces where `package.json#private` is absent or `false`. A workspace marked `private: true` is "versioned but not published": it can still be tagged by `release-kit tag` and get a `CHANGELOG.md` entry, but it is skipped by both `release-kit publish` (no registry publish) and `release-kit create-github-release` (no GitHub Release). `private: true` alone routes a workspace out of publish and announce — no extra release config is required. Other commands ignore this filter and operate on private workspaces unchanged.
|
|
12
|
+
|
|
13
|
+
An unpublishable tag is always skipped cleanly, never fatal. For `release-kit publish`, whether the skip is announced depends on how the tag was resolved:
|
|
14
|
+
|
|
15
|
+
- **Without `--tags`** (implicit resolution): unpublishable tags on HEAD are silently filtered. The pre-publish listing shows only the publishable subset. If the filter empties the set, `release-kit publish` prints `Nothing to publish.` and exits 0.
|
|
16
|
+
- **With `--tags`** (explicit naming): each named tag pointing at an unpublishable workspace is skipped with a warning, and any publishable tags named in the same command still publish.
|
|
17
|
+
|
|
18
|
+
`release-kit create-github-release` skips unpublishable workspaces with a warning regardless of `--tags`; an all-private tag set is a clean no-op.
|
|
19
|
+
|
|
20
|
+
Example warning when an explicit tag is unpublishable:
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
Skipping basic-v1.0.0 (packages/basic): package.json#private is true.
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## `release-kit create-github-release`
|
|
27
|
+
|
|
28
|
+
Create GitHub Releases from `changelog.json` for tags on HEAD. Independent of `npm publish`: invoking this command creates Releases for publishable packages whether or not they were published to a registry. Private workspaces are skipped (see [Publishability filter](#publishability-filter)).
|
|
29
|
+
|
|
30
|
+
When `--tags` is omitted, every release tag pointing at HEAD is processed. The CLI requires the `gh` CLI on `PATH` and `contents: write` permission. The bundled `create-github-release.reusable.yaml` GitHub Actions workflow runs this command in CI.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Readiness checks
|
|
2
|
+
|
|
3
|
+
What release-kit's two readyup kits check, how to run each, and what `npm-auto-publish` needs from the npm session.
|
|
4
|
+
|
|
5
|
+
release-kit publishes two [readyup](https://www.npmjs.com/package/readyup) kits that check a consuming repo against the release it has installed: the `default` kit covers release-kit's own setup — workflows matching the current templates, config free of removed fields, sync-labels wiring — and `npm-auto-publish` covers a repo's OIDC-based npm publishing setup. Both ship inside the package, so they check against the version you installed rather than whatever a repository ref happens to point at, and a check added in a release reaches your repo on upgrade.
|
|
6
|
+
|
|
7
|
+
The [README](../README.md#readiness-checks) shows how to name release-kit in the readyup config.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
rdy run --packages # every kit each listed package publishes
|
|
11
|
+
rdy run --from npm:@williamthorsen/release-kit # the default kit alone
|
|
12
|
+
rdy run --from npm:@williamthorsen/release-kit npm-auto-publish # the npm-auto-publish kit
|
|
13
|
+
rdy list --from npm:@williamthorsen/release-kit # what release-kit publishes
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`--packages` is the form that survives release-kit publishing further kits.
|
|
17
|
+
|
|
18
|
+
`npm-auto-publish` queries the npm registry for each package's trusted publisher, so it needs an npm session elevated by two-factor authentication, and is meant to be invoked deliberately rather than swept up by an unattended run. A session that cannot answer those queries, whether it is missing a login, missing the elevation, or facing an unreachable registry, is reported once by the `npm session can answer trust queries` gate. The rows that would repeat the failed query then stand down: the trusted-publisher rows in every case, and `published to npm` where the registry itself is unreachable. Each package's `package.json` checks report throughout, since they read no registry.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
How to scaffold the release workflows, what `release-kit prepare` does with its flags, and how the release workflow runs a release.
|
|
4
|
+
|
|
5
|
+
The [README](../README.md#quick-start) shows the first run.
|
|
6
|
+
|
|
7
|
+
## `release-kit init`
|
|
8
|
+
|
|
9
|
+
Initialize release-kit in the current repository. By default, scaffolds only the GitHub Actions workflow files. Use `--with-config` to also scaffold configuration files.
|
|
10
|
+
|
|
11
|
+
Scaffolded files:
|
|
12
|
+
|
|
13
|
+
- `.github/workflows/create-github-release.yaml` — workflow that creates a GitHub Release on tag push, independent of npm publish
|
|
14
|
+
- `.github/workflows/publish.yaml` — workflow that delegates to a reusable publish workflow
|
|
15
|
+
- `.github/workflows/release.yaml` — workflow that delegates to a reusable release workflow
|
|
16
|
+
- `.config/release-kit.config.ts` — starter config with commented-out customization examples (with `--with-config`)
|
|
17
|
+
- `.config/git-cliff.toml` — copied from the bundled template (with `--with-config`)
|
|
18
|
+
|
|
19
|
+
## `release-kit prepare`
|
|
20
|
+
|
|
21
|
+
Run release preparation with automatic workspace discovery.
|
|
22
|
+
|
|
23
|
+
Workspace names for `--only` match the package directory name (e.g., `arrays`, `release-kit`).
|
|
24
|
+
|
|
25
|
+
`--bump` and `--force` are orthogonal: `--bump` is purely a level chooser; `--force` is purely a release trigger. Examples:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
# Release every target at its natural bump level (no flags).
|
|
29
|
+
release-kit prepare
|
|
30
|
+
|
|
31
|
+
# Force a release even when no bump-worthy commits exist; defaults to patch
|
|
32
|
+
# per target, with each target keeping its natural bump if one is derivable.
|
|
33
|
+
release-kit prepare --force
|
|
34
|
+
|
|
35
|
+
# Force a release at a uniform level across every releasing target.
|
|
36
|
+
release-kit prepare --force --bump=minor
|
|
37
|
+
|
|
38
|
+
# --bump=X alone is a level chooser, NOT a trigger. If a target has no
|
|
39
|
+
# bump-worthy commits, it skips with a "Pass --force..." reason. If it has
|
|
40
|
+
# bump-worthy commits, the override applies.
|
|
41
|
+
release-kit prepare --bump=minor
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Previewing release notes with `--with-release-notes`
|
|
45
|
+
|
|
46
|
+
`--with-release-notes` writes two versioned files per workspace after each workspace's `changelog.json` is produced:
|
|
47
|
+
|
|
48
|
+
- `<workspacePath>/docs/README.v<version>.md` — the workspace `README.md` with release notes injected at the `<!-- section:release-notes -->` marker.
|
|
49
|
+
- `<workspacePath>/docs/RELEASE_NOTES.v<version>.md` — the standalone release notes for this version.
|
|
50
|
+
|
|
51
|
+
The publish-time inject-and-revert lifecycle is unchanged; previews are additive, deterministic, and safe to regenerate. When `changelogJson.enabled` is `false`, prepare reports a warning and skips preview generation. In dry-run mode, planned writes are logged and no files are created.
|
|
52
|
+
|
|
53
|
+
Because preview filenames are versioned, committing them will accumulate files over time. The recommended `.gitignore` entry for monorepos is:
|
|
54
|
+
|
|
55
|
+
```gitignore
|
|
56
|
+
packages/*/docs/*.v*.md
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For single-package repos:
|
|
60
|
+
|
|
61
|
+
```gitignore
|
|
62
|
+
docs/*.v*.md
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Setting an explicit version with `--set-version`
|
|
66
|
+
|
|
67
|
+
The `--set-version` flag is a first-class escape hatch for the cases where commit-derived bump logic produces the wrong version — most notably, promoting a pre-1.0 package to 1.0.0. Pre-1.0 packages collapse a `feat!` breaking change to a minor bump (matching semantic-release's `initialMajor: false` and release-please's `bump-minor-pre-major`), so a deliberate promotion to 1.0.0 must be requested explicitly.
|
|
68
|
+
|
|
69
|
+
The flag validates that:
|
|
70
|
+
|
|
71
|
+
- The value is canonical `N.N.N` semver (pre-release suffixes are rejected).
|
|
72
|
+
- The target is strictly greater than the current version (numeric comparison on each component).
|
|
73
|
+
- In monorepo mode, `--only` is set and resolves to exactly one workspace.
|
|
74
|
+
|
|
75
|
+
`--set-version` is mutually exclusive with `--bump` and `--force`. The rest of the pipeline (changelog generation, tag creation, commit summary, propagation to dependents) runs unchanged, so dependents receive a propagated patch bump triggered by the overridden version.
|
|
76
|
+
|
|
77
|
+
Promoting a pre-1.0 package to 1.0.0 in a monorepo:
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
release-kit prepare --only arrays --set-version 1.0.0
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
An empty changelog section is expected for a bare promotion, because the changelog is generated from commits since the last tag. To include a narrative entry, land a descriptive release commit (e.g., a `feat!` describing the stable API) before running `prepare`.
|
|
84
|
+
|
|
85
|
+
## GitHub Actions workflow
|
|
86
|
+
|
|
87
|
+
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:
|
|
88
|
+
|
|
89
|
+
| Input | Type | Description |
|
|
90
|
+
| ------- | ------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
91
|
+
| `only` | string | Workspaces to release (comma-separated, leave empty for all) |
|
|
92
|
+
| `bump` | choice | Override bump type: `patch`, `minor`, `major` (empty = auto-detect) |
|
|
93
|
+
| `force` | boolean | Release even when no commits or no bump-worthy commits exist (defaults to patch; combine with `bump` for a different level) |
|
|
94
|
+
|
|
95
|
+
For repos that need a self-contained workflow instead of the reusable one, the scaffolded file can be expanded. The key steps are: checkout with full history (`fetch-depth: 0`), run `release-kit prepare` with optional `--only`, `--bump`, and `--force` flags, check for changes, read tags from `tmp/.release-tags`, then commit, tag, and push.
|
|
96
|
+
|
|
97
|
+
### Triggering a release
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
# All workspaces
|
|
101
|
+
gh workflow run release.yaml
|
|
102
|
+
|
|
103
|
+
# Specific workspace(s)
|
|
104
|
+
gh workflow run release.yaml -f only=arrays
|
|
105
|
+
gh workflow run release.yaml -f only=arrays,strings -f bump=minor
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Or use the GitHub UI: Actions > Release > Run workflow.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Work types and tiers
|
|
2
|
+
|
|
3
|
+
The taxonomy against which release-kit parses commits: its tiers, the breaking-change policy, section markers, customization, and the commands that compare it with its upstream.
|
|
4
|
+
|
|
5
|
+
The canonical taxonomy lives in `packages/release-kit/src/work-types.json` and is split into three tiers that drive section rendering and audience classification.
|
|
6
|
+
|
|
7
|
+
| Tier | Key | Header | Aliases | `!` policy |
|
|
8
|
+
| -------- | ----------- | ------------------------- | ------------- | ------------ |
|
|
9
|
+
| public | `feat` | 🎉 Features | `feature` | optional |
|
|
10
|
+
| public | `drop` | 🪦 Removed | | **required** |
|
|
11
|
+
| public | `deprecate` | 🗑️ Deprecated | | forbidden |
|
|
12
|
+
| public | `fix` | 🐛 Bug fixes | `bugfix` | forbidden |
|
|
13
|
+
| public | `sec` | 🔒 Security | `security` | optional |
|
|
14
|
+
| public | `perf` | ⚡ Performance | `performance` | forbidden |
|
|
15
|
+
| internal | `internal` | 🏗️ Internal features | `utility` | forbidden |
|
|
16
|
+
| internal | `refactor` | ♻️ Refactoring | | forbidden |
|
|
17
|
+
| internal | `tests` | 🧪 Tests | `test` | forbidden |
|
|
18
|
+
| process | `tooling` | ⚙️ Tooling | | forbidden |
|
|
19
|
+
| process | `ci` | 👷 CI | | forbidden |
|
|
20
|
+
| process | `deps` | 📦 Dependencies | `dep` | forbidden |
|
|
21
|
+
| process | `ai` | 🤖 Agentic support | | forbidden |
|
|
22
|
+
| process | `docs` | 📚 Documentation | `doc` | forbidden |
|
|
23
|
+
| process | `fmt` | (excluded from changelog) | | forbidden |
|
|
24
|
+
|
|
25
|
+
## Tier semantics
|
|
26
|
+
|
|
27
|
+
- **`public`** — visible to all audiences. `public`-tier sections appear in both public release notes and dev changelogs.
|
|
28
|
+
- **`internal`** — dev-only. `internal`-tier sections appear in dev changelogs but not in public-facing release notes.
|
|
29
|
+
- **`process`** — dev-only. Same audience treatment as `internal`.
|
|
30
|
+
|
|
31
|
+
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.
|
|
32
|
+
|
|
33
|
+
## `utility` alias
|
|
34
|
+
|
|
35
|
+
`utility:` is a backward-compat alias for `internal:`. Both forms parse to the same canonical type, route to the same `🏗️ Internal features` section, and are subject to the same `!` policy.
|
|
36
|
+
|
|
37
|
+
## `!` (breaking change) policy
|
|
38
|
+
|
|
39
|
+
Each work-type carries a `breakingPolicy` value:
|
|
40
|
+
|
|
41
|
+
- `optional` (`feat`, `sec`) — `!` is allowed; both `type:` and `type!:` parse cleanly.
|
|
42
|
+
- `forbidden` (most types) — `!` is a policy violation. The premise: types like `internal!`, `perf!`, `fix!` are contradictory; an internal change cannot break a consumer contract, a pure perf change preserves the contract, and a bug-fix is by definition not a contract change.
|
|
43
|
+
- `required` (`drop`) — bare `drop:` is a policy violation; only `drop!:` is accepted. The premise: removing a feature always breaks consumers; the `!` form makes that explicit.
|
|
44
|
+
|
|
45
|
+
### Two-tier policy enforcement
|
|
46
|
+
|
|
47
|
+
The `!` policy operates at two distinct levels with different semantics:
|
|
48
|
+
|
|
49
|
+
- **Write-time** (commit-msg hook) — strict rejection. Policy violations are blocked at the gate where the author can act on them immediately. _Hook-based enforcement is tracked separately and is not yet shipped._
|
|
50
|
+
- **Release-time** (`parseCommitMessage`) — tolerant warn-and-continue. Commits already in the log cannot be rewritten, so a policy-violating commit is parsed using its canonical type with `breaking: false` (the `!` is dropped from the parse) and a `onPolicyViolation` callback fires. Callers (`decideRelease` etc.) can collect these warnings and surface them in the release report. A single legacy `internal!` in a year-old log does not block releases.
|
|
51
|
+
|
|
52
|
+
A `BREAKING CHANGE:` body footer on a `forbidden`-policy type triggers the same warning path as the prefix `!` does — the spirit of the policy is "internal/perf/etc. cannot be breaking", which must apply to both surfaces.
|
|
53
|
+
|
|
54
|
+
The release-prepare orchestrators (`releasePrepare`, `releasePrepareMono`, `releasePrepareProject`) apply `DEFAULT_BREAKING_POLICIES` automatically. Violations encountered while parsing each workspace's or project's commit window are collected onto the corresponding result's `policyViolations` field and rendered under the section in the prepare report:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
arrays
|
|
58
|
+
Found 1 commits since arrays-v1.0.0
|
|
59
|
+
⚠️ 1 policy violation:
|
|
60
|
+
· def5678 'internal!: refactor cache' — type 'internal' at prefix surface
|
|
61
|
+
Bumping versions (patch)...
|
|
62
|
+
📦 1.0.0 → 1.0.1 (patch)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
To customize, set `breakingPolicies` in `release-kit.config.ts` — provide a partial map to override individual types, or `{}` to disable enforcement entirely (the parser falls back to `'optional'` for any missing type). Violations remain warnings, never failures.
|
|
66
|
+
|
|
67
|
+
## `🚨 **Breaking:**` bullet marker
|
|
68
|
+
|
|
69
|
+
Items whose commit subject carries the `!` prefix (e.g. `feat!`, `drop!`, `feat(api)!`) are rendered with a `🚨 **Breaking:** ` prefix on the bullet:
|
|
70
|
+
|
|
71
|
+
```markdown
|
|
72
|
+
- 🚨 **Breaking:** Drop legacy /v1 endpoint
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
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.
|
|
76
|
+
|
|
77
|
+
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.
|
|
78
|
+
|
|
79
|
+
## Section markers
|
|
80
|
+
|
|
81
|
+
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.
|
|
82
|
+
|
|
83
|
+
```jsonc
|
|
84
|
+
{
|
|
85
|
+
"markers": {
|
|
86
|
+
"breaking": { "emoji": "🚨", "label": "Breaking" },
|
|
87
|
+
},
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
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.
|
|
92
|
+
|
|
93
|
+
## `fmt`
|
|
94
|
+
|
|
95
|
+
`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.
|
|
96
|
+
|
|
97
|
+
## Custom work types
|
|
98
|
+
|
|
99
|
+
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.
|
|
100
|
+
|
|
101
|
+
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.
|
|
102
|
+
|
|
103
|
+
## `release-kit work-types`
|
|
104
|
+
|
|
105
|
+
Manage the canonical work-types taxonomy used by changelog and release-notes generation.
|
|
106
|
+
|
|
107
|
+
The check is non-blocking initially: until codeassembly publishes its `work-types.json`, the upstream URL returns 404 and `check` exits 0 with a warning. CI flip to a blocking check is tracked as a follow-up once the upstream ships.
|
|
108
|
+
|
|
109
|
+
These commands are also exposed as `nmr work-types:check` / `nmr work-types:sync` from any package directory.
|
|
110
|
+
|
|
111
|
+
### Authenticated fetches
|
|
112
|
+
|
|
113
|
+
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.
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
# Source from `gh auth` for local runs:
|
|
117
|
+
export GITHUB_TOKEN=$(gh auth token)
|
|
118
|
+
pnpm exec release-kit work-types check
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
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.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/release-kit",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.7.0",
|
|
4
4
|
"description": "Version-bumping and changelog-generation toolkit for release workflows",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"changelog",
|
|
@@ -38,31 +38,35 @@
|
|
|
38
38
|
"release-kit": "./bin/release-kit.js"
|
|
39
39
|
},
|
|
40
40
|
"files": [
|
|
41
|
+
".meta/changelog.json",
|
|
41
42
|
".readyup/kits/*.js",
|
|
42
43
|
".readyup/manifest.json",
|
|
44
|
+
"CHANGELOG.md",
|
|
43
45
|
"bin",
|
|
44
|
-
"dist/*",
|
|
45
46
|
"cliff.toml.template",
|
|
47
|
+
"dist/*",
|
|
48
|
+
"docs",
|
|
49
|
+
"!docs/*.v*.md",
|
|
46
50
|
"presets/**",
|
|
47
|
-
"schemas/**"
|
|
48
|
-
"CHANGELOG.md"
|
|
51
|
+
"schemas/**"
|
|
49
52
|
],
|
|
50
53
|
"dependencies": {
|
|
51
|
-
"@williamthorsen/nmr-core": "0.
|
|
52
|
-
"@williamthorsen/toolbelt.errors": "0.
|
|
54
|
+
"@williamthorsen/nmr-core": "0.11.1",
|
|
55
|
+
"@williamthorsen/toolbelt.errors": "0.7.0",
|
|
53
56
|
"glob": "13.0.6",
|
|
54
57
|
"json-stringify-pretty-compact": "4.0.0",
|
|
55
58
|
"semver": "7.8.5",
|
|
56
59
|
"yaml": "2.9.0",
|
|
57
|
-
"zod": "4.4
|
|
60
|
+
"zod": "4.5.4"
|
|
58
61
|
},
|
|
59
62
|
"devDependencies": {
|
|
60
63
|
"@types/semver": "7.8.0",
|
|
61
|
-
"@williamthorsen/toolbelt.filesystem": "0.8.
|
|
62
|
-
"@williamthorsen/toolbelt.testing": "0.5.
|
|
63
|
-
"@williamthorsen/toolbelt.vitest": "0.
|
|
64
|
-
"readyup": "0.
|
|
65
|
-
"smol-toml": "1.8.0"
|
|
64
|
+
"@williamthorsen/toolbelt.filesystem": "0.8.3",
|
|
65
|
+
"@williamthorsen/toolbelt.testing": "0.5.1",
|
|
66
|
+
"@williamthorsen/toolbelt.vitest": "0.9.0",
|
|
67
|
+
"readyup": "0.36.0",
|
|
68
|
+
"smol-toml": "1.8.0",
|
|
69
|
+
"vitest": "4.1.11"
|
|
66
70
|
},
|
|
67
71
|
"engines": {
|
|
68
72
|
"node": ">=24"
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
|
-
function hasVersionField(value) {
|
|
4
|
-
return typeof value === 'object' && value !== null && 'version' in value && typeof value.version === 'string';
|
|
5
|
-
}
|
|
6
|
-
export function readCurrentVersion(filePath) {
|
|
7
|
-
try {
|
|
8
|
-
const content = readFileSync(filePath, 'utf8');
|
|
9
|
-
const parsed = JSON.parse(content);
|
|
10
|
-
if (hasVersionField(parsed)) {
|
|
11
|
-
return parsed.version;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
catch (error) {
|
|
15
|
-
console.warn(`Failed to read current version from ${filePath}: ${describeError(error)}`);
|
|
16
|
-
}
|
|
17
|
-
return undefined;
|
|
18
|
-
}
|