@williamthorsen/release-kit 14.1.0 → 14.2.1

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 (92) hide show
  1. package/.meta/changelog.json +136 -0
  2. package/.readyup/kits/default.js +63 -6
  3. package/.readyup/kits/npm-auto-publish.js +47 -35
  4. package/.readyup/manifest.json +12 -7
  5. package/CHANGELOG.md +39 -0
  6. package/README.md +2 -16
  7. package/dist/esm/assertTaggedBaseline.d.ts +2 -2
  8. package/dist/esm/attachChangelogDiagnostics.d.ts +2 -2
  9. package/dist/esm/bin/release-kit.js +5 -4
  10. package/dist/esm/buildChangelogEntries.d.ts +3 -3
  11. package/dist/esm/buildChangelogEntries.js +3 -3
  12. package/dist/esm/buildDependencyGraph.d.ts +11 -11
  13. package/dist/esm/buildDependencyGraph.js +13 -13
  14. package/dist/esm/buildReleaseSummary.d.ts +3 -3
  15. package/dist/esm/buildReleaseSummary.js +4 -12
  16. package/dist/esm/changelogArchive.d.ts +14 -0
  17. package/dist/esm/changelogArchive.js +21 -0
  18. package/dist/esm/changelogJsonFile.d.ts +2 -2
  19. package/dist/esm/changelogOverrides.d.ts +9 -8
  20. package/dist/esm/changelogOverrides.js +3 -6
  21. package/dist/esm/createGithubReleaseCommand.d.ts +2 -1
  22. package/dist/esm/createGithubReleaseCommand.js +7 -4
  23. package/dist/esm/deriveDirectoryConfig.d.ts +13 -0
  24. package/dist/esm/deriveDirectoryConfig.js +59 -0
  25. package/dist/esm/deriveWorkspaceConfig.js +0 -1
  26. package/dist/esm/discoverWorkspaces.d.ts +3 -3
  27. package/dist/esm/findTransitiveDependents.js +3 -3
  28. package/dist/esm/findUnroutedEntryScopes.d.ts +2 -2
  29. package/dist/esm/formatDirectoryPublishSkip.d.ts +3 -0
  30. package/dist/esm/formatDirectoryPublishSkip.js +3 -0
  31. package/dist/esm/formatExcludedSkip.d.ts +5 -2
  32. package/dist/esm/formatExcludedSkip.js +2 -2
  33. package/dist/esm/formatProjectPublishSkip.d.ts +3 -0
  34. package/dist/esm/formatProjectPublishSkip.js +3 -0
  35. package/dist/esm/generateChangelogs.d.ts +7 -7
  36. package/dist/esm/generateChangelogs.js +2 -2
  37. package/dist/esm/help/prepareHelp.d.ts +1 -1
  38. package/dist/esm/help/prepareHelp.js +7 -5
  39. package/dist/esm/init/templates.js +1 -1
  40. package/dist/esm/loadConfig.d.ts +12 -12
  41. package/dist/esm/loadConfig.js +79 -30
  42. package/dist/esm/prepareCommand.d.ts +1 -1
  43. package/dist/esm/prepareCommand.js +47 -46
  44. package/dist/esm/prepareTarget.d.ts +103 -0
  45. package/dist/esm/prepareTarget.js +274 -0
  46. package/dist/esm/previewTagPrefixes.d.ts +5 -3
  47. package/dist/esm/previewTagPrefixes.js +58 -13
  48. package/dist/esm/propagateBumps.js +1 -1
  49. package/dist/esm/publishCommand.js +13 -1
  50. package/dist/esm/readChangelogSections.d.ts +6 -4
  51. package/dist/esm/readChangelogSections.js +18 -4
  52. package/dist/esm/releasePlan.d.ts +4 -2
  53. package/dist/esm/releasePlan.js +34 -12
  54. package/dist/esm/releasePrepareTargets.d.ts +47 -0
  55. package/dist/esm/releasePrepareTargets.js +335 -0
  56. package/dist/esm/renderChangelogMarkdown.d.ts +23 -0
  57. package/dist/esm/renderChangelogMarkdown.js +47 -11
  58. package/dist/esm/reportPrepare.d.ts +2 -3
  59. package/dist/esm/reportPrepare.js +24 -12
  60. package/dist/esm/resolveCommandTags.d.ts +3 -1
  61. package/dist/esm/resolveCommandTags.js +15 -22
  62. package/dist/esm/resolveReleaseDeclaration.d.ts +45 -0
  63. package/dist/esm/resolveReleaseDeclaration.js +153 -0
  64. package/dist/esm/resolveReleaseTags.d.ts +14 -3
  65. package/dist/esm/resolveReleaseTags.js +36 -15
  66. package/dist/esm/resolveReleaseTargets.d.ts +29 -0
  67. package/dist/esm/resolveReleaseTargets.js +131 -0
  68. package/dist/esm/sync-labels/generateCommand.js +2 -1
  69. package/dist/esm/sync-labels/resolveLabels.d.ts +4 -3
  70. package/dist/esm/sync-labels/resolveLabels.js +30 -1
  71. package/dist/esm/sync-labels/types.d.ts +2 -0
  72. package/dist/esm/types.d.ts +251 -167
  73. package/dist/esm/types.js +22 -0
  74. package/dist/esm/validateConfig.js +80 -21
  75. package/dist/esm/validateOverridesCommand.d.ts +1 -1
  76. package/dist/esm/validateOverridesCommand.js +17 -28
  77. package/docs/api.md +25 -58
  78. package/docs/changelogs.md +26 -6
  79. package/docs/configuration.md +79 -16
  80. package/docs/labels.md +25 -2
  81. package/docs/project-releases.md +42 -2
  82. package/docs/publishing.md +3 -3
  83. package/docs/releasing.md +4 -4
  84. package/docs/work-types.md +1 -1
  85. package/package.json +3 -3
  86. package/presets/labels/common.yaml +11 -0
  87. package/dist/esm/releasePrepare.d.ts +0 -36
  88. package/dist/esm/releasePrepare.js +0 -230
  89. package/dist/esm/releasePrepareMono.d.ts +0 -29
  90. package/dist/esm/releasePrepareMono.js +0 -503
  91. package/dist/esm/releasePrepareProject.d.ts +0 -47
  92. package/dist/esm/releasePrepareProject.js +0 -164
@@ -1,4 +1,140 @@
1
1
  [
2
+ {
3
+ "version": "14.2.1",
4
+ "date": "2026-10-13",
5
+ "sections": [
6
+ {
7
+ "title": "📦 Dependencies",
8
+ "audience": "dev",
9
+ "items": [{ "description": "Bumped `@williamthorsen/change-grammar` to 0.2.1" }]
10
+ }
11
+ ]
12
+ },
13
+ {
14
+ "version": "14.2.0",
15
+ "date": "2026-10-12",
16
+ "sections": [
17
+ {
18
+ "title": "🎉 Features",
19
+ "audience": "all",
20
+ "items": [
21
+ {
22
+ "description": "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)",
23
+ "hash": "29e43c8066d15d956e60964e616a1bb51c3cd6bd",
24
+ "entry": 1
25
+ },
26
+ {
27
+ "description": "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)",
28
+ "hash": "29e43c8066d15d956e60964e616a1bb51c3cd6bd",
29
+ "entry": 2
30
+ },
31
+ {
32
+ "description": "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)",
33
+ "hash": "a92f374228e4a2ccc1f7053b1565cbf081c241b8",
34
+ "entry": 1
35
+ },
36
+ {
37
+ "description": "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)",
38
+ "hash": "b24ced5a34b7f3baacbbf2bbe2aca5e0eb08d548",
39
+ "entry": 1
40
+ },
41
+ {
42
+ "description": "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)",
43
+ "hash": "6427c9642c3517bc4a0a6c8b8f6ff1012bd0adcf",
44
+ "entry": 1
45
+ },
46
+ {
47
+ "description": "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)",
48
+ "hash": "6427c9642c3517bc4a0a6c8b8f6ff1012bd0adcf",
49
+ "entry": 2
50
+ },
51
+ {
52
+ "description": "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)",
53
+ "hash": "06d7ada2cd352d9ba9cfbf1cc30195b2273488d9",
54
+ "entry": 1
55
+ },
56
+ {
57
+ "description": "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)",
58
+ "hash": "06d7ada2cd352d9ba9cfbf1cc30195b2273488d9",
59
+ "entry": 2
60
+ },
61
+ {
62
+ "description": "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)",
63
+ "hash": "0368850a5719d9a9ee21db6a82063adc44b4ea70",
64
+ "entry": 1
65
+ },
66
+ {
67
+ "description": "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)",
68
+ "hash": "0368850a5719d9a9ee21db6a82063adc44b4ea70",
69
+ "entry": 2
70
+ },
71
+ {
72
+ "description": "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)",
73
+ "hash": "0368850a5719d9a9ee21db6a82063adc44b4ea70",
74
+ "entry": 3
75
+ }
76
+ ]
77
+ },
78
+ {
79
+ "title": "🐛 Bug fixes",
80
+ "audience": "all",
81
+ "items": [
82
+ {
83
+ "description": "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)",
84
+ "hash": "7faff3726a51003c80060ba5fd98235338997145",
85
+ "entry": 1
86
+ },
87
+ {
88
+ "description": "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)",
89
+ "hash": "7211951adb8e3e7b05a35196096d67bc33890801",
90
+ "entry": 2
91
+ },
92
+ {
93
+ "description": "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)",
94
+ "hash": "ffab419c87725051d04a1d817fc4be8b7222694b",
95
+ "entry": 1
96
+ },
97
+ {
98
+ "description": "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)",
99
+ "hash": "ffab419c87725051d04a1d817fc4be8b7222694b",
100
+ "entry": 2
101
+ },
102
+ {
103
+ "description": "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)",
104
+ "hash": "ffab419c87725051d04a1d817fc4be8b7222694b",
105
+ "entry": 3
106
+ }
107
+ ]
108
+ },
109
+ {
110
+ "title": "♻️ Refactoring",
111
+ "audience": "dev",
112
+ "items": [
113
+ {
114
+ "description": "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)",
115
+ "hash": "7211951adb8e3e7b05a35196096d67bc33890801",
116
+ "entry": 1
117
+ }
118
+ ]
119
+ },
120
+ {
121
+ "title": "📚 Documentation",
122
+ "audience": "dev",
123
+ "items": [
124
+ {
125
+ "description": "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)",
126
+ "hash": "7211951adb8e3e7b05a35196096d67bc33890801",
127
+ "entry": 3
128
+ },
129
+ {
130
+ "description": "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)",
131
+ "hash": "ffab419c87725051d04a1d817fc4be8b7222694b",
132
+ "entry": 4
133
+ }
134
+ ]
135
+ }
136
+ ]
137
+ },
2
138
  {
3
139
  "version": "14.1.0",
4
140
  "date": "2026-10-11",
@@ -16,6 +16,9 @@ import {
16
16
  readFile
17
17
  } from "readyup/check-utils";
18
18
 
19
+ // src/changelogArchive.ts
20
+ var CHANGELOG_ARCHIVE_DIRECTORY = "changelogs";
21
+
19
22
  // src/init/detectRepoType.ts
20
23
  import { existsSync, readFileSync } from "node:fs";
21
24
 
@@ -98,7 +101,7 @@ function translateGlob(pattern) {
98
101
 
99
102
  // .readyup/kits/default.ts
100
103
  function getMinVersion() {
101
- const picked = { "version": "14.1.0" };
104
+ const picked = { "version": "14.2.1" };
102
105
  if (typeof picked["version"] !== "string") {
103
106
  throw new TypeError("release-kit/package.json: 'version' must be a string");
104
107
  }
@@ -114,9 +117,9 @@ var CONFIG_EXPORT_PATTERNS = [
114
117
  /export\s*\{[^}]*\b(?:config|default)\b[^}]*\}/,
115
118
  /export\s*\*/
116
119
  ];
117
- var COMMON_PRESET_HASH = "618bb5cbcbdd4f4f0c2a777add7e14d992d48483350dd9651bf8cf4c1fa9d418";
120
+ var COMMON_PRESET_HASH = "1d3e2df1ccd7deea4707fa07a4ae171d761344b65c1a311467fc0f66b70ee646";
118
121
  var SYNC_LABELS_WORKFLOW_HASH = "0a88829d154c0de7cdd4246fede99e9311a89ab4199034dbe601663d641ab1e4";
119
- var RELEASE_WORKFLOW_HASH_MONOREPO = "4a20ff3315155500f64a27133c54eb1bc1f07a4acc370ec0d83176109041c60d";
122
+ var RELEASE_WORKFLOW_HASH_MONOREPO = "f63f4cabb57f94ee6550f17a9874c7a346d46554595ddbcd46fdd3208d613437";
120
123
  var RELEASE_WORKFLOW_HASH_SINGLE = "d26dd4ca186632b388ea46bd885721124c03d19918e511baa40821d2d760da69";
121
124
  var PUBLISH_WORKFLOW_HASH_MONOREPO = "0afa9ffe914f3dc8f043e68252ebc604c8cc1a953422fcea37a909a4def370ee";
122
125
  var PUBLISH_WORKFLOW_HASH_SINGLE = "6f31183e0a1e66be791a19266c3b028dadbd9fe010f7fc4452f3f8970c937b43";
@@ -257,6 +260,13 @@ var default_default = defineRdyKit({
257
260
  severity: "warn",
258
261
  check: () => labelsHaveCurrentPresetHash("common", COMMON_PRESET_HASH),
259
262
  fix: "Run `release-kit sync-labels generate` to incorporate updated common labels"
263
+ },
264
+ {
265
+ name: "scope labels in .github/labels.yaml have no description",
266
+ severity: "warn",
267
+ quiet: true,
268
+ check: () => scopeLabelsHaveNoDescriptions(),
269
+ fix: "Remove `description` from these scope labels in the repoLabels block of .config/release-kit.config.ts, so that GitHub groups them compactly, then run `release-kit sync-labels generate`"
260
270
  }
261
271
  ]
262
272
  }
@@ -272,6 +282,16 @@ var default_default = defineRdyKit({
272
282
  check: () => packagesShipChangelog(),
273
283
  fix: 'Add "CHANGELOG.md" to the files field of each affected package.json'
274
284
  },
285
+ {
286
+ name: "published packages ship the changelog archives",
287
+ severity: "warn",
288
+ skip: () => {
289
+ if (!hasPublishablePackages()) return "no publishable packages";
290
+ return changelogArchivesAreEnabled() ? false : "changelogMarkdown.keepVersions is not set";
291
+ },
292
+ check: () => packagesShipChangelogArchives(),
293
+ fix: 'Add "changelogs/" to the files field of each affected package.json, so that the links from CHANGELOG.md to its archive files resolve in the published package'
294
+ },
275
295
  {
276
296
  name: "changelog.json generation is enabled",
277
297
  severity: "recommend",
@@ -335,6 +355,11 @@ var default_default = defineRdyKit({
335
355
  }
336
356
  ]
337
357
  });
358
+ function changelogArchivesAreEnabled() {
359
+ const content = readFile(".config/release-kit.config.ts");
360
+ if (content === void 0) return false;
361
+ return /changelogMarkdown\s*:\s*\{[^}]*keepVersions\s*:/.test(content);
362
+ }
338
363
  function changelogJsonIsEnabled() {
339
364
  const content = readFile(".config/release-kit.config.ts");
340
365
  if (content === void 0) return true;
@@ -355,6 +380,9 @@ function labelsHaveCurrentPresetHash(presetName, expectedHash) {
355
380
  function packagesShipChangelog() {
356
381
  return reportWorkspacesOmitting("CHANGELOG.md");
357
382
  }
383
+ function packagesShipChangelogArchives() {
384
+ return reportWorkspacesOmitting(`${CHANGELOG_ARCHIVE_DIRECTORY}/CHANGELOG-v1.md`, `${CHANGELOG_ARCHIVE_DIRECTORY}/`);
385
+ }
358
386
  function packagesShipChangelogJson() {
359
387
  return reportWorkspacesOmitting(resolveChangelogJsonOutputPath());
360
388
  }
@@ -390,7 +418,7 @@ function releaseNotesInjectsIntoReadme() {
390
418
  if (content === void 0) return false;
391
419
  return /shouldInjectIntoReadme\s*:\s*true/.test(content);
392
420
  }
393
- function reportWorkspacesOmitting(path) {
421
+ function reportWorkspacesOmitting(path, label = path) {
394
422
  const failing = [];
395
423
  const packageWorkspaces = discoverWorkspaces({ filter: (w) => w.isPackage });
396
424
  for (const { dir, packageJson } of packageWorkspaces) {
@@ -401,7 +429,7 @@ function reportWorkspacesOmitting(path) {
401
429
  if (failing.length === 0) return true;
402
430
  return {
403
431
  ok: false,
404
- detail: `files field omits ${path}: ${failing.join(", ")}`
432
+ detail: `files field omits ${label}: ${failing.join(", ")}`
405
433
  };
406
434
  }
407
435
  function resolveChangelogJsonOutputPath() {
@@ -410,6 +438,32 @@ function resolveChangelogJsonOutputPath() {
410
438
  const match = /changelogJson\s*:\s*\{[^}]*outputPath\s*:\s*['"]([^'"]+)['"]/.exec(content);
411
439
  return match?.[1] ?? DEFAULT_CHANGELOG_JSON_PATH;
412
440
  }
441
+ function scopeLabelsHaveNoDescriptions() {
442
+ const content = readFile(".github/labels.yaml");
443
+ if (content === void 0) return true;
444
+ const described = [];
445
+ let currentName;
446
+ for (const line of content.split("\n")) {
447
+ const nameMatch = /^- name:\s*(.*?)\s*$/.exec(line);
448
+ if (nameMatch) {
449
+ currentName = stripYamlQuotes(nameMatch[1] ?? "");
450
+ continue;
451
+ }
452
+ const descriptionMatch = /^\s+description:\s*(.*?)\s*$/.exec(line);
453
+ if (descriptionMatch && currentName?.startsWith("scope:")) {
454
+ if (stripYamlQuotes(descriptionMatch[1] ?? "") !== "") described.push(currentName);
455
+ }
456
+ }
457
+ if (described.length === 0) return true;
458
+ return {
459
+ ok: false,
460
+ detail: `scope labels with a description: ${described.join(", ")}`
461
+ };
462
+ }
463
+ function stripYamlQuotes(value) {
464
+ const match = /^(['"])(.*)\1$/.exec(value);
465
+ return match?.[2] ?? value;
466
+ }
413
467
  export {
414
468
  COMMON_PRESET_HASH,
415
469
  CREATE_GITHUB_RELEASE_WORKFLOW_HASH_MONOREPO,
@@ -420,12 +474,15 @@ export {
420
474
  RELEASE_WORKFLOW_HASH_MONOREPO,
421
475
  RELEASE_WORKFLOW_HASH_SINGLE,
422
476
  SYNC_LABELS_WORKFLOW_HASH,
477
+ changelogArchivesAreEnabled,
423
478
  changelogJsonIsEnabled,
424
479
  configFileExportsConfig,
425
480
  default_default as default,
426
481
  packagesShipChangelog,
482
+ packagesShipChangelogArchives,
427
483
  packagesShipChangelogJson,
428
484
  readmeHasReleaseNotesMarkers,
429
485
  readmesHaveReleaseNotesMarkers,
430
- resolveChangelogJsonOutputPath
486
+ resolveChangelogJsonOutputPath,
487
+ scopeLabelsHaveNoDescriptions
431
488
  };
@@ -90,6 +90,7 @@ function runNpmJson(args) {
90
90
  // .readyup/kits/npm-auto-publish.ts
91
91
  var OTP_ERROR_CODE = "EOTP";
92
92
  var PUBLISH_WORKFLOW_FILE = "publish.yaml";
93
+ var NO_GITHUB_REMOTE_FIX = "Add an origin remote that points to the GitHub repo, then run this check again";
93
94
  var repoChecklist = defineRdyChecklist({
94
95
  name: "repo",
95
96
  checks: [
@@ -212,10 +213,8 @@ function buildWorkspaceCheck(workspace) {
212
213
  const capability = getCachedTrustCapability();
213
214
  return capability.ok ? false : capability.detail;
214
215
  },
215
- check: () => checkTrustedPublisher(displayName),
216
- get fix() {
217
- return `Run: npm trust github ${displayName} --repo ${getCachedOwnerRepo()} --file ${PUBLISH_WORKFLOW_FILE}`;
218
- }
216
+ check: () => describeTrustedPublisher(getTrustQueryResult(displayName), displayName, getCachedOwnerRepo()),
217
+ fix: formatTrustCommand(displayName, "<owner>/<repo>")
219
218
  }
220
219
  ]
221
220
  },
@@ -236,25 +235,18 @@ function buildWorkspaceCheck(workspace) {
236
235
  function checkProvenanceMatchesVisibility() {
237
236
  const workflowPath = ".github/workflows/publish.yaml";
238
237
  const content = readFile(workflowPath);
238
+ const ownerRepo = getCachedOwnerRepo();
239
+ if (!ownerRepo.ok) {
240
+ return classifyProvenanceSetting(content, ownerRepo);
241
+ }
239
242
  let visibility;
240
243
  try {
241
- visibility = isRepoPrivate() ? "private" : "public";
244
+ visibility = isRepoPrivate(ownerRepo.value) ? "private" : "public";
242
245
  } catch {
243
246
  visibility = "unknown";
244
247
  }
245
248
  return classifyProvenanceSetting(content, visibility);
246
249
  }
247
- function checkTrustedPublisher(packageName) {
248
- const result = getTrustQueryResult(packageName);
249
- switch (result.status) {
250
- case "configured":
251
- return { ok: true };
252
- case "not-configured":
253
- return { ok: false, detail: "The npm registry does not list any trusted publisher for this package" };
254
- default:
255
- return { ok: false, detail: result.detail };
256
- }
257
- }
258
250
  function classifyNpmAuth(result) {
259
251
  if (result.exitOk) {
260
252
  return { status: "authenticated" };
@@ -279,6 +271,9 @@ function classifyProvenanceSetting(workflowContent, visibility) {
279
271
  fix: "Check the file's permissions"
280
272
  };
281
273
  }
274
+ if (typeof visibility !== "string") {
275
+ return { ok: false, detail: visibility.detail, fix: NO_GITHUB_REMOTE_FIX };
276
+ }
282
277
  if (visibility === "unknown") {
283
278
  return {
284
279
  ok: false,
@@ -336,12 +331,18 @@ function classifyTrustQuery(result, expectedRepo, expectedFile) {
336
331
  if (relationships.length === 0) {
337
332
  return { status: "not-configured" };
338
333
  }
334
+ if (!expectedRepo.ok) {
335
+ return {
336
+ status: "error",
337
+ detail: `Cannot compare the trusted publisher with this repo; found ${describeTrustRelationships(relationships)}`
338
+ };
339
+ }
339
340
  const isMatch = relationships.some(
340
- (relationship) => relationship.type === "github" && relationship.repository === expectedRepo && relationship.file === expectedFile
341
+ (relationship) => relationship.type === "github" && relationship.repository === expectedRepo.value && relationship.file === expectedFile
341
342
  );
342
343
  return isMatch ? { status: "configured" } : {
343
344
  status: "mismatched",
344
- detail: `Expected github ${expectedRepo} (${expectedFile}); found ${describeTrustRelationships(relationships)}`
345
+ detail: `Expected github ${expectedRepo.value} (${expectedFile}); found ${describeTrustRelationships(relationships)}`
345
346
  };
346
347
  }
347
348
  function describeNpmPackageLookup(lookup) {
@@ -357,6 +358,13 @@ function describeNpmPackageLookup(lookup) {
357
358
  fix: "Restore access to the npm registry, then run this check again"
358
359
  };
359
360
  }
361
+ function describeTrustedPublisher(result, packageName, ownerRepo) {
362
+ if (result.status === "configured") {
363
+ return { ok: true };
364
+ }
365
+ const detail = result.status === "not-configured" ? "The npm registry does not list any trusted publisher for this package" : result.detail;
366
+ return ownerRepo.ok ? { ok: false, detail, fix: formatTrustCommand(packageName, ownerRepo.value) } : { ok: false, detail: `${detail}. ${ownerRepo.detail}`, fix: NO_GITHUB_REMOTE_FIX };
367
+ }
360
368
  function describeTrustRelationships(relationships) {
361
369
  return relationships.map((relationship) => {
362
370
  const type = typeof relationship.type === "string" ? relationship.type : "(unknown)";
@@ -365,32 +373,21 @@ function describeTrustRelationships(relationships) {
365
373
  return `${type} ${repository} (${file})`;
366
374
  }).join(", ");
367
375
  }
376
+ function formatTrustCommand(packageName, ownerRepo) {
377
+ return `Run: npm trust github ${packageName} --repo ${ownerRepo} --file ${PUBLISH_WORKFLOW_FILE}`;
378
+ }
368
379
  var getCachedNpmAuthStatus = /* @__PURE__ */ (() => {
369
380
  let cached;
370
381
  return () => cached ??= classifyNpmAuth(runNpmJson(["whoami", "--json"]));
371
382
  })();
372
383
  var getCachedOwnerRepo = /* @__PURE__ */ (() => {
373
384
  let cached;
374
- return () => cached ??= getOwnerRepo();
385
+ return () => cached ??= resolveOwnerRepo();
375
386
  })();
376
387
  var getCachedTrustCapability = /* @__PURE__ */ (() => {
377
388
  let cached;
378
389
  return () => cached ??= resolveTrustCapability();
379
390
  })();
380
- function getOwnerRepo() {
381
- const url = execSync("git remote get-url origin", {
382
- encoding: "utf8"
383
- }).trim();
384
- const sshMatch = url.match(/git@github\.com:(.+?)(?:\.git)?$/);
385
- if (sshMatch?.[1]) {
386
- return sshMatch[1];
387
- }
388
- const httpsMatch = url.match(/github\.com\/(.+?)(?:\.git)?$/);
389
- if (httpsMatch?.[1]) {
390
- return httpsMatch[1];
391
- }
392
- throw new Error(`Cannot parse GitHub owner/repo from remote URL: ${url}`);
393
- }
394
391
  var trustQueryResults = /* @__PURE__ */ new Map();
395
392
  function getTrustQueryResult(packageName) {
396
393
  const cached = trustQueryResults.get(packageName);
@@ -419,13 +416,17 @@ function hasTokenReferences() {
419
416
  }
420
417
  return false;
421
418
  }
422
- function isRepoPrivate() {
423
- const ownerRepo = getOwnerRepo();
419
+ function isRepoPrivate(ownerRepo) {
424
420
  const result = execSync(`gh api repos/${ownerRepo} --jq .private`, {
425
421
  encoding: "utf8"
426
422
  }).trim();
427
423
  return result === "true";
428
424
  }
425
+ function parseGitHubOwnerRepo(url) {
426
+ const match = /git@github\.com:(.+?)(?:\.git)?$/.exec(url) ?? /github\.com\/(.+?)(?:\.git)?$/.exec(url);
427
+ const ownerRepo = match?.[1];
428
+ return ownerRepo === void 0 ? { ok: false, detail: `The origin remote does not point to GitHub: ${url}` } : { ok: true, value: ownerRepo };
429
+ }
429
430
  function probeTrustQuery() {
430
431
  const probeName = selectProbeName();
431
432
  return probeName === void 0 ? void 0 : getTrustQueryResult(probeName);
@@ -449,6 +450,15 @@ function readTrustRelationships(stdout) {
449
450
  }
450
451
  return "type" in parsed ? [parsed] : [];
451
452
  }
453
+ function resolveOwnerRepo() {
454
+ let url;
455
+ try {
456
+ url = execSync("git remote get-url origin", { encoding: "utf8", stdio: "pipe" }).trim();
457
+ } catch {
458
+ return { ok: false, detail: "The repo does not have a readable origin remote" };
459
+ }
460
+ return parseGitHubOwnerRepo(url);
461
+ }
452
462
  function resolveTrustCapability() {
453
463
  const auth = getCachedNpmAuthStatus();
454
464
  return classifyTrustCapability(auth, auth.status === "authenticated" ? probeTrustQuery() : void 0);
@@ -473,7 +483,9 @@ export {
473
483
  classifyTrustQuery,
474
484
  npm_auto_publish_default as default,
475
485
  describeNpmPackageLookup,
486
+ describeTrustedPublisher,
476
487
  packagesChecklist,
488
+ parseGitHubOwnerRepo,
477
489
  selectProbeName,
478
490
  skipIfNotPublishable,
479
491
  skipIfNothingPublishable
@@ -8,18 +8,23 @@
8
8
  "esbuildVersion": "0.28.2",
9
9
  "inputs": [
10
10
  {
11
- "hash": "dee24ab4",
11
+ "hash": "a9f00459",
12
12
  "kind": "module",
13
13
  "path": "kits/default.ts"
14
14
  },
15
15
  {
16
- "hash": "4aa0967e",
16
+ "hash": "d9e9e87f",
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": "dee24ab4",
49
- "targetHash": "25d17b58"
53
+ "sourceHash": "a9f00459",
54
+ "targetHash": "6119af3f"
50
55
  },
51
56
  {
52
57
  "checklists": [
@@ -56,7 +61,7 @@
56
61
  "esbuildVersion": "0.28.2",
57
62
  "inputs": [
58
63
  {
59
- "hash": "70c0bb99",
64
+ "hash": "79c773cf",
60
65
  "kind": "module",
61
66
  "path": "kits/npm-auto-publish.ts"
62
67
  },
@@ -80,8 +85,8 @@
80
85
  "path": "kits/npm-auto-publish.js",
81
86
  "readyupVersion": "0.40.0",
82
87
  "source": "kits/npm-auto-publish.ts",
83
- "sourceHash": "70c0bb99",
84
- "targetHash": "d97d6123"
88
+ "sourceHash": "79c773cf",
89
+ "targetHash": "38ddb04a"
85
90
  }
86
91
  ]
87
92
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,45 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 14.2.1 — 2026-10-13
6
+
7
+ ### 📦 Dependencies
8
+
9
+ - Bumped `@williamthorsen/change-grammar` to 0.2.1
10
+
11
+ ## 14.2.0 — 2026-10-12
12
+
13
+ ### 🎉 Features
14
+
15
+ - 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)
16
+ - 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)
17
+ - 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)
18
+ - 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)
19
+ - 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)
20
+ - 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)
21
+ - 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)
22
+ - 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)
23
+ - 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)
24
+ - 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)
25
+ - 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)
26
+
27
+ ### 🐛 Bug fixes
28
+
29
+ - 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)
30
+ - 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)
31
+ - 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)
32
+ - 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)
33
+ - 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)
34
+
35
+ ### ♻️ Refactoring
36
+
37
+ - 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)
38
+
39
+ ### 📚 Documentation
40
+
41
+ - 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)
42
+ - 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)
43
+
5
44
  ## 14.1.0 — 2026-10-11
6
45
 
7
46
  ### 🎉 Features
package/README.md CHANGED
@@ -6,21 +6,7 @@ Version-bumping and changelog-generation toolkit for release workflows.
6
6
 
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
- <!-- section:release-notes -->
10
- ## Release notes — v14.1.0 (2026-10-11)
11
-
12
- ### 🎉 Features
13
-
14
- - 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)
15
- - 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)
16
- - 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)
17
-
18
- ### 🐛 Bug fixes
19
-
20
- - 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)
21
- - 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)
22
- - 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)
23
- <!-- /section:release-notes -->
9
+ <!-- section:release-notes --><!-- /section:release-notes -->
24
10
 
25
11
  ## Installation
26
12
 
@@ -176,4 +162,4 @@ rdy run --sources
176
162
  - [Work types and tiers](docs/work-types.md): Tiers, the `!` policy, markers, and custom work types
177
163
  - [Labels](docs/labels.md): `sync-labels`, label configuration, the workflow's triggers, and where the `label-map.json` schema is published
178
164
  - [Readiness checks](docs/readiness-checks.md): Both readyup kits, and how to run each
179
- - [Programmatic API](docs/api.md): `deriveWorkspaceConfig()`, the script-based approach, and `resolveReleaseTags`
165
+ - [Programmatic API](docs/api.md): `defineConfig()` for typing a config file, and validating override files from code
@@ -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]
@@ -180,7 +180,8 @@ Usage: release-kit create-github-release [options]
180
180
 
181
181
  Create GitHub Releases from changelog.json for tags on HEAD. Private
182
182
  (package.json#private) workspaces are skipped with a warning, and the command
183
- 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.
184
185
 
185
186
  Options:
186
187
  --config <path> Config file, relative to the current directory
@@ -244,7 +245,7 @@ Usage: release-kit publish [options]
244
245
  Publish packages that have release tags on HEAD. Operates only on workspaces whose
245
246
  package.json#private is absent or false. Without --tags, unpublishable workspaces are
246
247
  silently filtered out. With --tags, an unpublishable tag is skipped with a warning while
247
- any publishable tags are still published.
248
+ any publishable tags are still published. A project tag is never published.
248
249
 
249
250
  Options:
250
251
  --config <path> Config file, relative to the current directory