@williamthorsen/release-kit 14.0.0 → 14.2.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.
Files changed (110) hide show
  1. package/.meta/changelog.json +173 -0
  2. package/.readyup/kits/default.js +64 -7
  3. package/.readyup/kits/npm-auto-publish.js +127 -75
  4. package/.readyup/manifest.json +22 -7
  5. package/CHANGELOG.md +47 -0
  6. package/README.md +18 -4
  7. package/dist/esm/assertReleasesOnNpm.d.ts +7 -0
  8. package/dist/esm/assertReleasesOnNpm.js +56 -0
  9. package/dist/esm/assertTaggedBaseline.d.ts +2 -2
  10. package/dist/esm/attachChangelogDiagnostics.d.ts +2 -2
  11. package/dist/esm/bin/release-kit.js +10 -6
  12. package/dist/esm/buildChangelogEntries.d.ts +3 -3
  13. package/dist/esm/buildChangelogEntries.js +3 -3
  14. package/dist/esm/buildDependencyGraph.d.ts +11 -11
  15. package/dist/esm/buildDependencyGraph.js +13 -13
  16. package/dist/esm/buildReleaseSummary.d.ts +3 -3
  17. package/dist/esm/buildReleaseSummary.js +4 -12
  18. package/dist/esm/changelogArchive.d.ts +14 -0
  19. package/dist/esm/changelogArchive.js +21 -0
  20. package/dist/esm/changelogJsonFile.d.ts +2 -2
  21. package/dist/esm/changelogOverrides.d.ts +9 -8
  22. package/dist/esm/changelogOverrides.js +3 -6
  23. package/dist/esm/createGithubReleaseCommand.d.ts +2 -1
  24. package/dist/esm/createGithubReleaseCommand.js +7 -4
  25. package/dist/esm/deriveDirectoryConfig.d.ts +13 -0
  26. package/dist/esm/deriveDirectoryConfig.js +59 -0
  27. package/dist/esm/deriveWorkspaceConfig.js +2 -3
  28. package/dist/esm/discoverWorkspaces.d.ts +3 -3
  29. package/dist/esm/enumerateReleaseWindows.d.ts +1 -1
  30. package/dist/esm/enumerateReleaseWindows.js +1 -3
  31. package/dist/esm/findTransitiveDependents.js +3 -3
  32. package/dist/esm/findUnroutedEntryScopes.d.ts +2 -2
  33. package/dist/esm/formatDirectoryPublishSkip.d.ts +3 -0
  34. package/dist/esm/formatDirectoryPublishSkip.js +3 -0
  35. package/dist/esm/formatExcludedSkip.d.ts +5 -2
  36. package/dist/esm/formatExcludedSkip.js +2 -2
  37. package/dist/esm/formatProjectPublishSkip.d.ts +3 -0
  38. package/dist/esm/formatProjectPublishSkip.js +3 -0
  39. package/dist/esm/generateChangelogs.d.ts +7 -7
  40. package/dist/esm/generateChangelogs.js +2 -2
  41. package/dist/esm/help/prepareHelp.d.ts +1 -1
  42. package/dist/esm/help/prepareHelp.js +7 -5
  43. package/dist/esm/init/hasPublishablePackage.d.ts +10 -0
  44. package/dist/esm/init/hasPublishablePackage.js +28 -0
  45. package/dist/esm/init/initCommand.js +23 -13
  46. package/dist/esm/init/scaffold.d.ts +6 -1
  47. package/dist/esm/init/scaffold.js +10 -7
  48. package/dist/esm/init/templates.js +1 -1
  49. package/dist/esm/init/updateTemplatesCommand.d.ts +2 -1
  50. package/dist/esm/init/updateTemplatesCommand.js +12 -6
  51. package/dist/esm/isPublishableManifest.d.ts +2 -0
  52. package/dist/esm/isPublishableManifest.js +5 -0
  53. package/dist/esm/loadConfig.d.ts +12 -12
  54. package/dist/esm/loadConfig.js +79 -30
  55. package/dist/esm/matchesTagPrefix.d.ts +2 -0
  56. package/dist/esm/matchesTagPrefix.js +4 -0
  57. package/dist/esm/npmRegistry.d.ts +35 -0
  58. package/dist/esm/npmRegistry.js +58 -0
  59. package/dist/esm/prepareCommand.d.ts +1 -1
  60. package/dist/esm/prepareCommand.js +50 -36
  61. package/dist/esm/prepareTarget.d.ts +103 -0
  62. package/dist/esm/prepareTarget.js +274 -0
  63. package/dist/esm/previewTagPrefixes.d.ts +5 -3
  64. package/dist/esm/previewTagPrefixes.js +60 -14
  65. package/dist/esm/propagateBumps.js +1 -1
  66. package/dist/esm/publishCommand.js +13 -1
  67. package/dist/esm/readChangelogSections.d.ts +6 -4
  68. package/dist/esm/readChangelogSections.js +18 -4
  69. package/dist/esm/releasePlan.d.ts +4 -2
  70. package/dist/esm/releasePlan.js +34 -12
  71. package/dist/esm/releasePrepareTargets.d.ts +47 -0
  72. package/dist/esm/releasePrepareTargets.js +335 -0
  73. package/dist/esm/renderChangelogMarkdown.d.ts +23 -0
  74. package/dist/esm/renderChangelogMarkdown.js +47 -11
  75. package/dist/esm/reportPrepare.d.ts +2 -3
  76. package/dist/esm/reportPrepare.js +24 -12
  77. package/dist/esm/resolveCommandTags.d.ts +3 -1
  78. package/dist/esm/resolveCommandTags.js +15 -22
  79. package/dist/esm/resolveReleaseDeclaration.d.ts +45 -0
  80. package/dist/esm/resolveReleaseDeclaration.js +153 -0
  81. package/dist/esm/resolveReleaseTags.d.ts +14 -3
  82. package/dist/esm/resolveReleaseTags.js +36 -15
  83. package/dist/esm/resolveReleaseTargets.d.ts +29 -0
  84. package/dist/esm/resolveReleaseTargets.js +131 -0
  85. package/dist/esm/sync-labels/generateCommand.js +2 -1
  86. package/dist/esm/sync-labels/resolveLabels.d.ts +4 -3
  87. package/dist/esm/sync-labels/resolveLabels.js +30 -1
  88. package/dist/esm/sync-labels/templates.js +1 -1
  89. package/dist/esm/sync-labels/types.d.ts +2 -0
  90. package/dist/esm/types.d.ts +251 -167
  91. package/dist/esm/types.js +22 -0
  92. package/dist/esm/validateConfig.js +80 -21
  93. package/dist/esm/validateOverridesCommand.d.ts +1 -1
  94. package/dist/esm/validateOverridesCommand.js +17 -28
  95. package/docs/api.md +25 -58
  96. package/docs/changelogs.md +26 -6
  97. package/docs/configuration.md +80 -17
  98. package/docs/labels.md +30 -5
  99. package/docs/project-releases.md +42 -2
  100. package/docs/publishing.md +3 -3
  101. package/docs/releasing.md +12 -4
  102. package/docs/work-types.md +1 -1
  103. package/package.json +6 -6
  104. package/presets/labels/common.yaml +11 -0
  105. package/dist/esm/releasePrepare.d.ts +0 -36
  106. package/dist/esm/releasePrepare.js +0 -230
  107. package/dist/esm/releasePrepareMono.d.ts +0 -29
  108. package/dist/esm/releasePrepareMono.js +0 -503
  109. package/dist/esm/releasePrepareProject.d.ts +0 -47
  110. package/dist/esm/releasePrepareProject.js +0 -164
@@ -8,18 +8,23 @@
8
8
  "esbuildVersion": "0.28.2",
9
9
  "inputs": [
10
10
  {
11
- "hash": "7a8522cd",
11
+ "hash": "a9f00459",
12
12
  "kind": "module",
13
13
  "path": "kits/default.ts"
14
14
  },
15
15
  {
16
- "hash": "6eab98d1",
16
+ "hash": "14ecf16a",
17
17
  "kind": "inline",
18
18
  "path": "../package.json",
19
19
  "paths": [
20
20
  "version"
21
21
  ]
22
22
  },
23
+ {
24
+ "hash": "40404aa0",
25
+ "kind": "module",
26
+ "path": "../src/changelogArchive.ts"
27
+ },
23
28
  {
24
29
  "hash": "43bb0452",
25
30
  "kind": "module",
@@ -45,8 +50,8 @@
45
50
  "path": "kits/default.js",
46
51
  "readyupVersion": "0.40.0",
47
52
  "source": "kits/default.ts",
48
- "sourceHash": "7a8522cd",
49
- "targetHash": "4e21467f"
53
+ "sourceHash": "a9f00459",
54
+ "targetHash": "dada6894"
50
55
  },
51
56
  {
52
57
  "checklists": [
@@ -56,7 +61,7 @@
56
61
  "esbuildVersion": "0.28.2",
57
62
  "inputs": [
58
63
  {
59
- "hash": "d9910d60",
64
+ "hash": "79c773cf",
60
65
  "kind": "module",
61
66
  "path": "kits/npm-auto-publish.ts"
62
67
  },
@@ -64,14 +69,24 @@
64
69
  "hash": "d360febf",
65
70
  "kind": "module",
66
71
  "path": "../src/init/hasProvenance.ts"
72
+ },
73
+ {
74
+ "hash": "3e39b9be",
75
+ "kind": "module",
76
+ "path": "../src/npmRegistry.ts"
77
+ },
78
+ {
79
+ "hash": "1424b343",
80
+ "kind": "module",
81
+ "path": "../src/typeGuards.ts"
67
82
  }
68
83
  ],
69
84
  "name": "npm-auto-publish",
70
85
  "path": "kits/npm-auto-publish.js",
71
86
  "readyupVersion": "0.40.0",
72
87
  "source": "kits/npm-auto-publish.ts",
73
- "sourceHash": "d9910d60",
74
- "targetHash": "2a6931b6"
88
+ "sourceHash": "79c773cf",
89
+ "targetHash": "38ddb04a"
75
90
  }
76
91
  ]
77
92
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,53 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 14.2.0 — 2026-10-12
6
+
7
+ ### 🎉 Features
8
+
9
+ - Adds an optional `aliases` array to a `repoLabels.labels` entry, which `sync-labels` writes to `.github/labels.yaml` so that the sync renames each existing label named by an alias to the entry's name and keeps it on the issues and pull requests that have it. (#1015)
10
+ - Makes `release-kit sync-labels` reject a config whose alias names a label that the config resolves or appears twice, because the sync would otherwise merge one declared label into another and delete it. (#1015)
11
+ - Adds the `confidence:high` ("Clearly worth doing"), `confidence:medium` ("Worth doing, but arguable"), and `confidence:low` ("Not worth a ticket now") labels to the `common` preset of `release-kit sync-labels`, and documents in `docs/labels.md` that the family grades how advisable a recommendation is. (#1019)
12
+ - Adds a warning to the `release-kit` readyup kit that names each `scope:` label in `.github/labels.yaml` with a non-empty description, archived labels included, because GitHub's label list groups scope labels compactly only when they have no description. (#1022)
13
+ - Adds the `releases` block to `.config/release-kit.config.ts`, in which an entry renames a discovered workspace's release target, excludes the workspace with `false`, or declares a composite of named members that the root `package.json` versions, as an alternative to the `workspaces` and `project` blocks, which keep working unchanged. (#1028)
14
+ - Lets `release-kit prepare --only` select a `releases` composite by its target name, and makes a narrowed run that leaves the composite out skip it with a warning that names it. (#1028)
15
+ - Adds directory release targets to `release-kit`: A `releases` entry whose `path` is not a workspace now declares a target that `prepare` versions from the directory's `package.json` or `version.json`, whose `CHANGELOG.md` `prepare` writes, and whose tags `push` and `create-github-release` handle, while `publish` skips them because a directory does not have any package to publish to npm. (#1029)
16
+ - Adds the repo-root target `{ path: '.' }`, which versions the root `package.json`, tags `v{version}`, and writes every commit in the repo to `./CHANGELOG.md`, replacing a `project` block whose window covered the whole repo only while every workspace was excluded. (#1029)
17
+ - Adds the `changelogMarkdown.keepVersions` config option, which makes `prepare` write only the newest `keepVersions` sections to `CHANGELOG.md`, with links to `changelogs/CHANGELOG-v{major}.md` archive files that contain the older sections, and delete any archive file that is no longer needed after the option is raised or removed. (#1031)
18
+ - Extends `prepare` to read hand-written sections from the `changelogs/` archive files as well as from `CHANGELOG.md`, so that an archived section written by hand is kept when the changelog is regenerated. (#1031)
19
+ - Adds the warning "published packages ship the changelog archives" to the release-kit readyup kit, which reports each publishable package whose `files` field omits `changelogs/` while `keepVersions` is set, because the links in `CHANGELOG.md` to the archive files would otherwise be broken in the published package. (#1031)
20
+
21
+ ### 🐛 Bug fixes
22
+
23
+ - Fixes the issue that the `npm-auto-publish` kit crashed without reporting any results in a repo whose `origin` remote is missing or does not point to GitHub, and makes its trusted-publisher and provenance checks name that cause and advise adding the remote. (#1018)
24
+ - Fixes the issue that `release-kit prepare` accepted two workspaces whose directories share a name, which `--only` and the dependency graph use to identify a workspace, and makes `prepare` and `release-kit overrides validate` stop with an error that lists the path of each. (#1026)
25
+ - Fixes the issue that `release-kit push` never pushed the project tag on HEAD, and failed with `HEAD does not have any release tags.` when the project tag was the only release tag. (#1027)
26
+ - Fixes the issue that `release-kit create-github-release` did not create a GitHub Release for the project tag; a private workspace's tag is still skipped. (#1027)
27
+ - Makes `release-kit publish` skip a project tag named by `--tags` with the warning `Skipping v0.13.0: a project tag is not an npm package.`, since the project does not have an npm package to publish. (#1027)
28
+
29
+ ### ♻️ Refactoring
30
+
31
+ - Replaces `releasePrepare`, `releasePrepareMono`, and `releasePrepareProject` with `releasePrepareTargets`, which prepares a release from one list of package and project targets resolved from any config shape. (#1026)
32
+
33
+ ### 📚 Documentation
34
+
35
+ - Rewrites `docs/api.md` to describe what the `.` and `./config` entries export, and drops its sections on `deriveWorkspaceConfig`, the script-based approach, and `resolveReleaseTags`, none of which the package exports. (#1026)
36
+ - Documents in `docs/project-releases.md` the tag pattern that a scaffolded `create-github-release.yaml` needs in order to trigger on a project tag, because its default `'*-v[0-9]*.[0-9]*.[0-9]*'` filter does not match a `v<version>` tag. (#1027)
37
+
38
+ ## 14.1.0 — 2026-10-11
39
+
40
+ ### 🎉 Features
41
+
42
+ - Stops `release-kit init` from scaffolding `publish.yaml` and `create-github-release.yaml` when every package is private, even with `--force`, and removes the provenance and trusted-publisher steps from the next steps that it prints. (#995)
43
+ - Stops `release-kit update-templates` from re-creating a deleted `publish.yaml` or `create-github-release.yaml` when every package is private, while it still refreshes either workflow that exists. (#995)
44
+ - Makes `release-kit prepare` refuse, before it writes any file and in a dry run too, to release a publishable package that npm does not list or that it cannot look up, and prints `npm publish --access public` as the fix for each unlisted package. (#996)
45
+
46
+ ### 🐛 Bug fixes
47
+
48
+ - Fixes the issue that the `npm-auto-publish` kit's "published to npm" check reported a failed lookup, such as an unreachable registry or an authentication error, as an unpublished package and ignored `publishConfig.registry`. (#996)
49
+ - Fixes the issue that release-kit's changelog generation treated a floating major tag such as `v1` as a release and added a spurious release to the history, by counting a tag only when the rest of its name after the prefix is a complete SemVer version such as `1.0.0` or `1.0.0-beta.1`. (#1000)
50
+ - Fixes the issue that `release-kit show-tag-prefixes` counted every tag that starts with a workspace's prefix, including floating pointers such as `core-v1` and non-release tags such as `core-vnext`, rather than only the tags that release-kit counts as releases. (#1000)
51
+
5
52
  ## 14.0.0 — 2026-10-08
6
53
 
7
54
  ### 🎉 Features
package/README.md CHANGED
@@ -7,15 +7,29 @@ Version-bumping and changelog-generation toolkit for release workflows.
7
7
  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 the same commit history that decides each bump (with optional [editorial overrides](docs/editorial-overrides.md)).
8
8
 
9
9
  <!-- section:release-notes -->
10
- ## Release notes — v14.0.0 (2026-10-08)
10
+ ## Release notes — v14.2.0 (2026-10-12)
11
11
 
12
12
  ### 🎉 Features
13
13
 
14
- - 🚨 **Breaking:** Changes monorepo `--set-version` to take comma-separated `<workspace>@N.N.N` entries, which `prepare` accepts without `--only` and together with `--bump` and `--force`, and which release each named workspace's dependents. (#970)
14
+ - Adds an optional `aliases` array to a `repoLabels.labels` entry, which `sync-labels` writes to `.github/labels.yaml` so that the sync renames each existing label named by an alias to the entry's name and keeps it on the issues and pull requests that have it. (#1015)
15
+ - Makes `release-kit sync-labels` reject a config whose alias names a label that the config resolves or appears twice, because the sync would otherwise merge one declared label into another and delete it. (#1015)
16
+ - Adds the `confidence:high` ("Clearly worth doing"), `confidence:medium` ("Worth doing, but arguable"), and `confidence:low` ("Not worth a ticket now") labels to the `common` preset of `release-kit sync-labels`, and documents in `docs/labels.md` that the family grades how advisable a recommendation is. (#1019)
17
+ - Adds a warning to the `release-kit` readyup kit that names each `scope:` label in `.github/labels.yaml` with a non-empty description, archived labels included, because GitHub's label list groups scope labels compactly only when they have no description. (#1022)
18
+ - Adds the `releases` block to `.config/release-kit.config.ts`, in which an entry renames a discovered workspace's release target, excludes the workspace with `false`, or declares a composite of named members that the root `package.json` versions, as an alternative to the `workspaces` and `project` blocks, which keep working unchanged. (#1028)
19
+ - Lets `release-kit prepare --only` select a `releases` composite by its target name, and makes a narrowed run that leaves the composite out skip it with a warning that names it. (#1028)
20
+ - Adds directory release targets to `release-kit`: A `releases` entry whose `path` is not a workspace now declares a target that `prepare` versions from the directory's `package.json` or `version.json`, whose `CHANGELOG.md` `prepare` writes, and whose tags `push` and `create-github-release` handle, while `publish` skips them because a directory does not have any package to publish to npm. (#1029)
21
+ - Adds the repo-root target `{ path: '.' }`, which versions the root `package.json`, tags `v{version}`, and writes every commit in the repo to `./CHANGELOG.md`, replacing a `project` block whose window covered the whole repo only while every workspace was excluded. (#1029)
22
+ - Adds the `changelogMarkdown.keepVersions` config option, which makes `prepare` write only the newest `keepVersions` sections to `CHANGELOG.md`, with links to `changelogs/CHANGELOG-v{major}.md` archive files that contain the older sections, and delete any archive file that is no longer needed after the option is raised or removed. (#1031)
23
+ - Extends `prepare` to read hand-written sections from the `changelogs/` archive files as well as from `CHANGELOG.md`, so that an archived section written by hand is kept when the changelog is regenerated. (#1031)
24
+ - Adds the warning "published packages ship the changelog archives" to the release-kit readyup kit, which reports each publishable package whose `files` field omits `changelogs/` while `keepVersions` is set, because the links in `CHANGELOG.md` to the archive files would otherwise be broken in the published package. (#1031)
15
25
 
16
26
  ### 🐛 Bug fixes
17
27
 
18
- - 🚨 **Breaking:** Fixes the issue that `release-kit prepare --only` left each dependent of the named workspaces published against the old version range of its dependency, by also releasing every transitive dependent: a dependent with commits of its own at its own bump, and one without at a propagated patch bump. (#970)
28
+ - Fixes the issue that the `npm-auto-publish` kit crashed without reporting any results in a repo whose `origin` remote is missing or does not point to GitHub, and makes its trusted-publisher and provenance checks name that cause and advise adding the remote. (#1018)
29
+ - Fixes the issue that `release-kit prepare` accepted two workspaces whose directories share a name, which `--only` and the dependency graph use to identify a workspace, and makes `prepare` and `release-kit overrides validate` stop with an error that lists the path of each. (#1026)
30
+ - Fixes the issue that `release-kit push` never pushed the project tag on HEAD, and failed with `HEAD does not have any release tags.` when the project tag was the only release tag. (#1027)
31
+ - Fixes the issue that `release-kit create-github-release` did not create a GitHub Release for the project tag; a private workspace's tag is still skipped. (#1027)
32
+ - Makes `release-kit publish` skip a project tag named by `--tags` with the warning `Skipping v0.13.0: a project tag is not an npm package.`, since the project does not have an npm package to publish. (#1027)
19
33
  <!-- /section:release-notes -->
20
34
 
21
35
  ## Installation
@@ -172,4 +186,4 @@ rdy run --sources
172
186
  - [Work types and tiers](docs/work-types.md): Tiers, the `!` policy, markers, and custom work types
173
187
  - [Labels](docs/labels.md): `sync-labels`, label configuration, the workflow's triggers, and where the `label-map.json` schema is published
174
188
  - [Readiness checks](docs/readiness-checks.md): Both readyup kits, and how to run each
175
- - [Programmatic API](docs/api.md): `deriveWorkspaceConfig()`, the script-based approach, and `resolveReleaseTags`
189
+ - [Programmatic API](docs/api.md): `defineConfig()` for typing a config file, and validating override files from code
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Throws when npm doesn't list, or can't confirm, a publishable package among the given workspaces.
3
+ *
4
+ * Each workspace path is repo-relative and is read from the current directory. A private package is not looked up,
5
+ * and an empty list doesn't call the registry at all.
6
+ */
7
+ export declare function assertReleasesOnNpm(workspacePaths: readonly string[]): void;
@@ -0,0 +1,56 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { isPublishableManifest } from "./isPublishableManifest.js";
4
+ import { lookUpNpmPackage } from "./npmRegistry.js";
5
+ import { isRecord } from "./typeGuards.js";
6
+ export function assertReleasesOnNpm(workspacePaths) {
7
+ const unpublished = [];
8
+ const unverifiable = [];
9
+ for (const workspacePath of workspacePaths) {
10
+ const candidate = buildNpmCandidate(workspacePath);
11
+ if (candidate === undefined) {
12
+ continue;
13
+ }
14
+ const lookup = lookUpNpmPackage(candidate.name, candidate.registry);
15
+ if (lookup.status === 'unpublished') {
16
+ unpublished.push(candidate);
17
+ }
18
+ else if (lookup.status === 'unverifiable') {
19
+ unverifiable.push({ candidate, detail: lookup.detail });
20
+ }
21
+ }
22
+ if (unpublished.length === 0 && unverifiable.length === 0) {
23
+ return;
24
+ }
25
+ throw new Error(describeRefusal(unpublished, unverifiable));
26
+ }
27
+ function buildNpmCandidate(workspacePath) {
28
+ const manifest = JSON.parse(readFileSync(join(workspacePath, 'package.json'), 'utf8'));
29
+ if (!isRecord(manifest) || !isPublishableManifest(manifest) || typeof manifest['name'] !== 'string') {
30
+ return undefined;
31
+ }
32
+ const publishConfig = manifest['publishConfig'];
33
+ const registry = isRecord(publishConfig) ? publishConfig['registry'] : undefined;
34
+ return {
35
+ name: manifest['name'],
36
+ workspacePath,
37
+ ...(typeof registry === 'string' && { registry }),
38
+ };
39
+ }
40
+ function describeRefusal(unpublished, unverifiable) {
41
+ const sections = ['Cannot prepare the release: npm must already have every package that the release publishes.'];
42
+ if (unpublished.length > 0) {
43
+ sections.push([
44
+ 'Not on npm yet:',
45
+ ...unpublished.map(({ name, workspacePath }) => ` - ${name}: run "npm publish --access public" from ${workspacePath}`),
46
+ 'Then register each one as a trusted publisher; "rdy run --from npm:@williamthorsen/release-kit npm-auto-publish" prints the command.',
47
+ ].join('\n'));
48
+ }
49
+ if (unverifiable.length > 0) {
50
+ sections.push([
51
+ 'Could not confirm on npm:',
52
+ ...unverifiable.map(({ candidate, detail }) => ` - ${candidate.name}: ${detail}`),
53
+ ].join('\n'));
54
+ }
55
+ return sections.join('\n\n');
56
+ }
@@ -1,4 +1,4 @@
1
- import type { ReleaseConfig } from './types.js';
1
+ import type { ReleaseSettings } from './types.js';
2
2
  /** A release target whose history `prepare` has read, as the baseline check sees it. */
3
3
  export interface BaselineTarget {
4
4
  /** How the error names the target, such as `workspace 'core'`. */
@@ -26,4 +26,4 @@ export declare function assertTaggedBaseline(findings: ReadonlyArray<UntaggedBas
26
26
  * `changelog.json` files, and its previous tag is not that version under any of its prefixes. A first release has
27
27
  * none, because its changelogs do not record its version yet.
28
28
  */
29
- export declare function findUntaggedBaseline(target: BaselineTarget, config: Pick<ReleaseConfig, 'changelogJson'>): UntaggedBaseline | undefined;
29
+ export declare function findUntaggedBaseline(target: BaselineTarget, config: Pick<ReleaseSettings, 'changelogJson'>): UntaggedBaseline | undefined;
@@ -1,4 +1,4 @@
1
1
  import type { ChangelogDiagnostics } from './buildChangelogEntries.js';
2
- import type { ReleasedWorkspaceResult } from './types.js';
2
+ import type { ReleasedPackageResult } from './types.js';
3
3
  /** Attaches a history's diagnostics to a released or skipped result, omitting each empty list. */
4
- export declare function attachChangelogDiagnostics(result: Pick<ReleasedWorkspaceResult, 'malformedBlocks' | 'policyViolations' | 'undeclaredEntryTypes' | 'unroutedEntryScopes'>, diagnostics: ChangelogDiagnostics): void;
4
+ export declare function attachChangelogDiagnostics(result: Pick<ReleasedPackageResult, 'malformedBlocks' | 'policyViolations' | 'undeclaredEntryTypes' | 'unroutedEntryScopes'>, diagnostics: ChangelogDiagnostics): void;
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { parseArgsOrExit, readPackageVersion, reportError } from '@williamthorsen/nmr-core';
2
+ import { parseArgsOrExit, reportError, resolveSelfVersion } from '@williamthorsen/nmr-core';
3
3
  import { describeError } from '@williamthorsen/toolbelt.errors';
4
4
  import { commitCommand } from "../commitCommand.js";
5
5
  import { configFlagSchema } from "../configFlagSchema.js";
@@ -19,7 +19,7 @@ import { syncLabelsInitCommand } from "../sync-labels/initCommand.js";
19
19
  import { syncLabelsCommand } from "../sync-labels/syncCommand.js";
20
20
  import { tagCommand } from "../tagCommand.js";
21
21
  import { validateOverridesCommand } from "../validateOverridesCommand.js";
22
- const VERSION = readPackageVersion(import.meta.url);
22
+ const VERSION = resolveSelfVersion(import.meta.url);
23
23
  function showUsage() {
24
24
  console.info(`
25
25
  Usage: release-kit <command> [options]
@@ -108,7 +108,8 @@ function showInitHelp() {
108
108
  Usage: release-kit init [options]
109
109
 
110
110
  Initialize release-kit in the current repository.
111
- By default, scaffolds only the GitHub Actions workflow files.
111
+ By default, scaffolds only the GitHub Actions workflow files. When every
112
+ package is private, skips publish.yaml and create-github-release.yaml.
112
113
 
113
114
  Options:
114
115
  --with-config Also scaffold .config/release-kit.config.ts
@@ -124,7 +125,9 @@ Usage: release-kit update-templates [options]
124
125
  Update the GitHub Actions workflows that release-kit scaffolds to the templates
125
126
  of the installed version, showing what changed in each file. Creates a missing
126
127
  workflow that \`init\` would create, and refreshes sync-labels.yaml only when it
127
- exists. Keeps publish.yaml's provenance setting. Never touches config files.
128
+ exists. When every package is private, refreshes publish.yaml and
129
+ create-github-release.yaml only when they exist. Keeps publish.yaml's provenance
130
+ setting. Never touches config files.
128
131
 
129
132
  Options:
130
133
  --dry-run Preview changes without writing files
@@ -177,7 +180,8 @@ Usage: release-kit create-github-release [options]
177
180
 
178
181
  Create GitHub Releases from changelog.json for tags on HEAD. Private
179
182
  (package.json#private) workspaces are skipped with a warning, and the command
180
- doesn't create a Release for them.
183
+ doesn't create a Release for them. A project tag gets a Release even when the
184
+ root package is private.
181
185
 
182
186
  Options:
183
187
  --config <path> Config file, relative to the current directory
@@ -241,7 +245,7 @@ Usage: release-kit publish [options]
241
245
  Publish packages that have release tags on HEAD. Operates only on workspaces whose
242
246
  package.json#private is absent or false. Without --tags, unpublishable workspaces are
243
247
  silently filtered out. With --tags, an unpublishable tag is skipped with a warning while
244
- any publishable tags are still published.
248
+ any publishable tags are still published. A project tag is never published.
245
249
 
246
250
  Options:
247
251
  --config <path> Config file, relative to the current directory
@@ -1,6 +1,6 @@
1
1
  import { type RawCommit } from './enumerateReleaseWindows.js';
2
2
  import type { GenerateChangelogOptions } from './generateChangelogs.js';
3
- import type { ChangelogEntry, ChangelogSection, MalformedChangeRecordBlock, PolicyViolation, ReleaseConfig, ReleaseType, UndeclaredEntryType, UnroutedEntryScope } from './types.js';
3
+ import type { ChangelogEntry, ChangelogSection, MalformedChangeRecordBlock, PolicyViolation, ReleaseSettings, ReleaseType, UndeclaredEntryType, UnroutedEntryScope } from './types.js';
4
4
  /**
5
5
  * Strips a section title down to its bare name.
6
6
  *
@@ -35,13 +35,13 @@ export interface ChangelogDiagnostics {
35
35
  * window decides: its commits, its bump, and its diagnostics.
36
36
  *
37
37
  * A commit whose last `change-record` block records entries yields one item per entry routed to
38
- * `options.workspaceDir`; any other commit is classified by its title through `classifyChangelogCommit`. A `release:`
38
+ * `options.scope`; any other commit is classified by its title through `classifyChangelogCommit`. A `release:`
39
39
  * or merge subject yields nothing either way. The read records diagnostics only for the unreleased window, since the
40
40
  * released windows were reported when they were prepared.
41
41
  */
42
42
  export declare function readReleaseHistory(config: ReleaseHistoryConfig, options: GenerateChangelogOptions): ReleaseHistory;
43
43
  /** The configuration that `readReleaseHistory` reads. */
44
- export type ReleaseHistoryConfig = Pick<ReleaseConfig, 'breakingPolicies' | 'changelogJson' | 'scopeAliases' | 'versionPatterns' | 'workTypes'>;
44
+ export type ReleaseHistoryConfig = Pick<ReleaseSettings, 'breakingPolicies' | 'changelogJson' | 'scopeAliases' | 'versionPatterns' | 'workTypes'>;
45
45
  /** One read of a scope's release windows. */
46
46
  export interface ReleaseHistory {
47
47
  /** The newest matching tag that HEAD reaches; undefined when none does. */
@@ -32,8 +32,8 @@ export function readReleaseHistory(config, options) {
32
32
  const context = {
33
33
  breakingPolicies: config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES,
34
34
  devOnlySections: new Set(config.changelogJson.devOnlySections.map(stripGroupDecorations)),
35
- ...(options.workspaceDir !== undefined && {
36
- routing: { workspaceDir: options.workspaceDir, scopeAliases: config.scopeAliases ?? {} },
35
+ ...(options.scope !== undefined && {
36
+ routing: { scope: options.scope, scopeAliases: config.scopeAliases ?? {} },
37
37
  }),
38
38
  versionPatterns: config.versionPatterns ?? DEFAULT_VERSION_PATTERNS,
39
39
  workTypes: config.workTypes ?? DEFAULT_WORK_TYPES,
@@ -170,7 +170,7 @@ function isRoutedTo(scopes, routing) {
170
170
  }
171
171
  return scopes.some((scope) => {
172
172
  const resolved = routing.scopeAliases[scope] ?? scope;
173
- return resolved === '*' || resolved === routing.workspaceDir;
173
+ return resolved === '*' || resolved === routing.scope;
174
174
  });
175
175
  }
176
176
  function formatDate(timestamp) {
@@ -1,25 +1,25 @@
1
- import type { WorkspaceConfig } from './types.js';
2
- /** Identities, current versions, and `workspace:` dependency edges of the workspaces' packages. */
1
+ import type { PackageTarget } from './types.js';
2
+ /** Identities, current versions, and `workspace:` dependency edges of the package targets. */
3
3
  export interface DependencyGraph {
4
- /** Maps a package name to its workspace `dir`. */
4
+ /** Maps a package name to its target id, the workspace `dir`. */
5
5
  packageNameToDir: Map<string, string>;
6
- /** Maps a workspace `dir` to its package name (inverse of `packageNameToDir`). */
6
+ /** Maps a target id (a workspace `dir`) to its package name (inverse of `packageNameToDir`). */
7
7
  dirToPackageName: Map<string, string>;
8
- /** Maps a workspace `dir` to its current version; absent when its `package.json` doesn't have a string `version`. */
8
+ /** Maps a target id to its current version; absent when its `package.json` doesn't have a string `version`. */
9
9
  dirToVersion: Map<string, string>;
10
- /** Maps a package name to the workspaces that depend on it. */
11
- dependentsOf: Map<string, WorkspaceConfig[]>;
10
+ /** Maps a package name to the targets that depend on it. */
11
+ dependentsOf: Map<string, PackageTarget[]>;
12
12
  /**
13
- * Maps a workspace `dir` to the `workspace:`-protocol package names that it declares in `dependencies` or
13
+ * Maps a target id to the `workspace:`-protocol package names that it declares in `dependencies` or
14
14
  * `peerDependencies` (the forward counterpart of `dependentsOf`).
15
15
  */
16
16
  dependenciesOf: Map<string, Set<string>>;
17
17
  }
18
18
  /**
19
- * Builds the dependency graph from the workspaces' `package.json` files.
19
+ * Builds the dependency graph from the package targets' `package.json` files.
20
20
  *
21
- * Reads each workspace's primary `package.json` (first entry in `packageFiles`) to discover
21
+ * Reads each target's primary `package.json` (first entry in `versionFiles`) to discover
22
22
  * `workspace:` references in `dependencies` and `peerDependencies`, and to record its current
23
23
  * version.
24
24
  */
25
- export declare function buildDependencyGraph(workspaces: readonly WorkspaceConfig[]): DependencyGraph;
25
+ export declare function buildDependencyGraph(targets: readonly PackageTarget[]): DependencyGraph;
@@ -3,30 +3,30 @@ import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
3
3
  function isPackageJsonSubset(value) {
4
4
  return typeof value === 'object' && value !== null;
5
5
  }
6
- export function buildDependencyGraph(workspaces) {
6
+ export function buildDependencyGraph(targets) {
7
7
  const packageNameToDir = new Map();
8
8
  const dirToPackageName = new Map();
9
9
  const dirToVersion = new Map();
10
10
  const dependentsOf = new Map();
11
11
  const dependenciesOf = new Map();
12
- const workspacePackages = new Map();
13
- for (const workspace of workspaces) {
14
- const primaryPackageFile = workspace.packageFiles[0];
12
+ const targetPackages = new Map();
13
+ for (const target of targets) {
14
+ const primaryPackageFile = target.versionFiles[0];
15
15
  if (primaryPackageFile === undefined) {
16
16
  continue;
17
17
  }
18
18
  const pkg = readPackageJsonSubset(primaryPackageFile);
19
- workspacePackages.set(workspace, pkg);
19
+ targetPackages.set(target, pkg);
20
20
  if (typeof pkg.version === 'string') {
21
- dirToVersion.set(workspace.dir, pkg.version);
21
+ dirToVersion.set(target.id, pkg.version);
22
22
  }
23
23
  if (pkg.name === undefined) {
24
24
  continue;
25
25
  }
26
- packageNameToDir.set(pkg.name, workspace.dir);
27
- dirToPackageName.set(workspace.dir, pkg.name);
26
+ packageNameToDir.set(pkg.name, target.id);
27
+ dirToPackageName.set(target.id, pkg.name);
28
28
  }
29
- for (const [workspace, pkg] of workspacePackages) {
29
+ for (const [target, pkg] of targetPackages) {
30
30
  const allDeps = { ...pkg.dependencies, ...pkg.peerDependencies };
31
31
  for (const [depName, depVersion] of Object.entries(allDeps)) {
32
32
  if (typeof depVersion !== 'string' || !depVersion.startsWith('workspace:')) {
@@ -34,14 +34,14 @@ export function buildDependencyGraph(workspaces) {
34
34
  }
35
35
  const existing = dependentsOf.get(depName);
36
36
  if (existing === undefined) {
37
- dependentsOf.set(depName, [workspace]);
37
+ dependentsOf.set(depName, [target]);
38
38
  }
39
39
  else {
40
- existing.push(workspace);
40
+ existing.push(target);
41
41
  }
42
- const forward = dependenciesOf.get(workspace.dir);
42
+ const forward = dependenciesOf.get(target.id);
43
43
  if (forward === undefined) {
44
- dependenciesOf.set(workspace.dir, new Set([depName]));
44
+ dependenciesOf.set(target.id, new Set([depName]));
45
45
  }
46
46
  else {
47
47
  forward.add(depName);
@@ -1,6 +1,6 @@
1
1
  import type { PrepareResult } from './types.js';
2
2
  /**
3
- * Builds a release commit body from a prepare result: one section per released workspace or project that has commits,
4
- * the project last, each headed by its tag and listing its scope-stripped commit subjects.
3
+ * Builds a release commit body from a prepare result: one section per released target that has commits, in result
4
+ * order, each headed by its tag and listing its scope-stripped commit subjects.
5
5
  */
6
- export declare function buildReleaseSummary(result: Pick<PrepareResult, 'project' | 'workspaces'>): string;
6
+ export declare function buildReleaseSummary(result: Pick<PrepareResult, 'targets'>): string;
@@ -1,27 +1,19 @@
1
1
  import { stripScope } from "./stripScope.js";
2
2
  export function buildReleaseSummary(result) {
3
3
  const sections = [];
4
- for (const workspace of result.workspaces) {
5
- if (workspace.status !== 'released') {
4
+ for (const target of result.targets) {
5
+ if (target.status !== 'released') {
6
6
  continue;
7
7
  }
8
- const commits = workspace.commits;
8
+ const commits = target.commits;
9
9
  if (commits === undefined || commits.length === 0) {
10
10
  continue;
11
11
  }
12
- const lines = [workspace.tag];
12
+ const lines = [target.tag];
13
13
  for (const commit of commits) {
14
14
  lines.push(`- ${stripScope(commit.subject)}`);
15
15
  }
16
16
  sections.push(lines.join('\n'));
17
17
  }
18
- const project = result.project;
19
- if (project !== undefined && project.status === 'released' && project.commits.length > 0) {
20
- const lines = [project.tag];
21
- for (const commit of project.commits) {
22
- lines.push(`- ${stripScope(commit.subject)}`);
23
- }
24
- sections.push(lines.join('\n'));
25
- }
26
18
  return sections.join('\n\n');
27
19
  }
@@ -0,0 +1,14 @@
1
+ /** Directory, relative to a changelog directory, that contains the per-major archive files of its `CHANGELOG.md`. */
2
+ export declare const CHANGELOG_ARCHIVE_DIRECTORY = "changelogs";
3
+ /** An archive file that exists on disk. */
4
+ export interface ChangelogArchivePath {
5
+ major: number;
6
+ path: string;
7
+ }
8
+ /** Returns the path, relative to the changelog directory, of the archive file for a major version. */
9
+ export declare function describeArchivePath(major: number): string;
10
+ /**
11
+ * Lists the archive files under the changelog directory, newest major first. A missing archive directory has none;
12
+ * files whose names do not match the archive pattern are ignored.
13
+ */
14
+ export declare function listArchiveFiles(changelogDir: string): ChangelogArchivePath[];
@@ -0,0 +1,21 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ export const CHANGELOG_ARCHIVE_DIRECTORY = 'changelogs';
4
+ export function describeArchivePath(major) {
5
+ return `${CHANGELOG_ARCHIVE_DIRECTORY}/CHANGELOG-v${major}.md`;
6
+ }
7
+ export function listArchiveFiles(changelogDir) {
8
+ const archiveDir = join(changelogDir, CHANGELOG_ARCHIVE_DIRECTORY);
9
+ if (!existsSync(archiveDir)) {
10
+ return [];
11
+ }
12
+ const archives = [];
13
+ for (const name of readdirSync(archiveDir)) {
14
+ const match = ARCHIVE_FILE_PATTERN.exec(name);
15
+ if (match?.[1] !== undefined) {
16
+ archives.push({ major: Number(match[1]), path: join(archiveDir, name) });
17
+ }
18
+ }
19
+ return archives.toSorted((a, b) => b.major - a.major);
20
+ }
21
+ const ARCHIVE_FILE_PATTERN = /^CHANGELOG-v(\d+)\.md$/;
@@ -1,6 +1,6 @@
1
- import type { ChangelogEntry, ReleaseConfig } from './types.js';
1
+ import type { ChangelogEntry, ReleaseSettings } from './types.js';
2
2
  /** Resolves the path of the `changelog.json` file under a workspace's changelog directory. */
3
- export declare function resolveChangelogJsonPath(config: Pick<ReleaseConfig, 'changelogJson'>, changelogPath: string): string;
3
+ export declare function resolveChangelogJsonPath(config: Pick<ReleaseSettings, 'changelogJson'>, changelogPath: string): string;
4
4
  /** Renders changelog entries as a `changelog.json` file's complete content, sorted newest-first. */
5
5
  export declare function renderChangelogJson(entries: ChangelogEntry[]): string;
6
6
  /**
@@ -1,4 +1,4 @@
1
- import type { ChangelogEntry, ChangelogOverride, WorkspaceConfig } from './types.js';
1
+ import type { ChangelogEntry, ChangelogOverride } from './types.js';
2
2
  /** Known fields on a single override entry; presence of any other field is a validation error. */
3
3
  export declare const KNOWN_OVERRIDE_FIELDS: Set<string>;
4
4
  /** Form of an override key: a lowercase hex commit hash or prefix, optionally followed by `:<n>`, a 1-based entry position. */
@@ -102,11 +102,11 @@ export interface OverrideContext {
102
102
  globalMatchedRootKeys: Set<string>;
103
103
  }
104
104
  /**
105
- * Loads and validates the override files of the root and every workspace, and bundles them with empty warning
106
- * aggregators. Throws if any file fails to load, so that a malformed checked-in override file stops `prepare` before
107
- * any workspace writes.
105
+ * Loads and validates the override files of the root and of every workspace at the given repo-relative paths, and
106
+ * bundles them with empty warning aggregators. Throws if any file fails to load, so that a malformed checked-in
107
+ * override file stops `prepare` before any workspace writes.
108
108
  */
109
- export declare function createOverrideContext(workspaces: WorkspaceConfig[]): OverrideContext;
109
+ export declare function createOverrideContext(workspacePaths: readonly string[]): OverrideContext;
110
110
  /** A changelog item as override matching sees it. `entry` is absent for a title-derived item. */
111
111
  export interface OverrideTargetItem {
112
112
  hash: string;
@@ -164,12 +164,13 @@ export interface ValidateAllChangelogOverridesResult {
164
164
  */
165
165
  export declare function validateAllChangelogOverrides(inputs: ValidateAllChangelogOverridesInputs): ValidateAllChangelogOverridesResult;
166
166
  /**
167
- * Applies the composed root and workspace override map to a workspace's changelog entries, and records stale keys by
168
- * tier.
167
+ * Applies the composed root and workspace override map to a target's changelog entries, and records stale keys by
168
+ * tier. A target without a workspace override file, such as the project, passes `undefined` for `workspacePath` and
169
+ * applies the root tier alone.
169
170
  *
170
171
  * A workspace key that matched nothing, whether or not the root map has the same key, is stale, and it is warned about
171
172
  * at once. The function adds each root key that matched without being shadowed to `globalMatchedRootKeys`, from which
172
173
  * the caller warns, at the end of the run, about the root keys that matched nowhere. Throws if applying reports any
173
174
  * error, such as an ambiguous prefix.
174
175
  */
175
- export declare function applyWorkspaceOverrides(newEntries: ChangelogEntry[], workspacePath: string, overrideContext: OverrideContext): ReturnType<typeof applyChangelogOverrides>;
176
+ export declare function applyWorkspaceOverrides(newEntries: ChangelogEntry[], workspacePath: string | undefined, overrideContext: OverrideContext): ReturnType<typeof applyChangelogOverrides>;
@@ -336,11 +336,8 @@ export function composeOverrides(rootEntries, workspaceEntries) {
336
336
  }
337
337
  return composed;
338
338
  }
339
- export function createOverrideContext(workspaces) {
340
- const result = loadOverridesForScopes({
341
- project: '.',
342
- workspaces: workspaces.map((workspace) => workspace.workspacePath),
343
- });
339
+ export function createOverrideContext(workspacePaths) {
340
+ const result = loadOverridesForScopes({ project: '.', workspaces: [...workspacePaths] });
344
341
  if (result.errors.length > 0) {
345
342
  throw new Error(`Failed to load changelog overrides:\n - ${result.errors.join('\n - ')}`);
346
343
  }
@@ -488,7 +485,7 @@ function formatRootStaleWarning(filePath, key) {
488
485
  }
489
486
  export function applyWorkspaceOverrides(newEntries, workspacePath, overrideContext) {
490
487
  const { project, perWorkspace, overrideWarnings, globalMatchedRootKeys } = overrideContext;
491
- const workspaceOverrides = perWorkspace.get(workspacePath);
488
+ const workspaceOverrides = workspacePath === undefined ? undefined : perWorkspace.get(workspacePath);
492
489
  const composed = composeOverrides(project, workspaceOverrides);
493
490
  const applied = applyChangelogOverrides(newEntries, composed);
494
491
  if (applied.errors.length > 0) {
@@ -4,6 +4,7 @@ import { type StreamStyles } from '@williamthorsen/nmr-core';
4
4
  * a comma-separated `--tags` subset, without publishing to npm.
5
5
  *
6
6
  * A private workspace's tag is skipped with a warning and doesn't get a Release; an all-private tag set is a clean
7
- * no-op. A relative `--config` resolves against `invocationDir`.
7
+ * no-op. A project tag gets a Release whatever the root `package.json#private` says. A relative `--config` resolves
8
+ * against `invocationDir`.
8
9
  */
9
10
  export declare function createGithubReleaseCommand(argv: string[], styles: StreamStyles, invocationDir: string): Promise<void>;