@williamthorsen/release-kit 10.4.4 → 10.6.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 (105) hide show
  1. package/.meta/changelog.json +1502 -0
  2. package/.readyup/kits/default.js +120 -4
  3. package/.readyup/kits/npm-auto-publish.js +11 -3
  4. package/.readyup/manifest.json +16 -11
  5. package/CHANGELOG.md +57 -0
  6. package/README.md +64 -4
  7. package/dist/esm/assertCleanWorkingTree.d.ts +5 -0
  8. package/dist/esm/buildChangelogEntries.d.ts +22 -0
  9. package/dist/esm/buildChangelogEntries.js +5 -0
  10. package/dist/esm/buildDependencyGraph.d.ts +16 -0
  11. package/dist/esm/buildEmptyReleaseEntry.d.ts +8 -0
  12. package/dist/esm/buildReleaseSummary.d.ts +10 -0
  13. package/dist/esm/buildSyntheticChangelogEntry.d.ts +6 -0
  14. package/dist/esm/bumpVersion.d.ts +8 -0
  15. package/dist/esm/changelogJsonFile.d.ts +11 -0
  16. package/dist/esm/changelogJsonUtils.d.ts +16 -2
  17. package/dist/esm/changelogJsonUtils.js +0 -13
  18. package/dist/esm/changelogOverrides.d.ts +143 -0
  19. package/dist/esm/changelogOverrides.js +8 -0
  20. package/dist/esm/checkWorkTypesDrift.d.ts +25 -0
  21. package/dist/esm/collectPolicyViolations.d.ts +9 -0
  22. package/dist/esm/commitCommand.d.ts +6 -0
  23. package/dist/esm/compareVersions.d.ts +7 -0
  24. package/dist/esm/createGithubRelease.d.ts +30 -0
  25. package/dist/esm/createGithubReleaseCommand.d.ts +7 -0
  26. package/dist/esm/createTags.d.ts +5 -0
  27. package/dist/esm/decideRelease.d.ts +37 -0
  28. package/dist/esm/defaults.d.ts +25 -0
  29. package/dist/esm/defineConfig.d.ts +9 -0
  30. package/dist/esm/deleteFileIfExists.d.ts +1 -0
  31. package/dist/esm/deriveWorkspaceConfig.d.ts +9 -0
  32. package/dist/esm/detectPackageManager.d.ts +6 -0
  33. package/dist/esm/detectUndeclaredTagPrefixes.d.ts +18 -0
  34. package/dist/esm/determineBumpFromCommits.d.ts +5 -0
  35. package/dist/esm/determineBumpType.d.ts +12 -0
  36. package/dist/esm/discoverWorkspaces.d.ts +12 -0
  37. package/dist/esm/extractMigration.d.ts +14 -0
  38. package/dist/esm/extractMigration.js +18 -0
  39. package/dist/esm/format.d.ts +3 -0
  40. package/dist/esm/formatPrivateSkip.d.ts +6 -0
  41. package/dist/esm/generateChangelogs.d.ts +25 -0
  42. package/dist/esm/getCommitsSinceTarget.d.ts +16 -0
  43. package/dist/esm/hasPrettierConfig.d.ts +6 -0
  44. package/dist/esm/help/prepareHelp.d.ts +6 -0
  45. package/dist/esm/init/checks.d.ts +4 -0
  46. package/dist/esm/init/detectRepoType.d.ts +7 -0
  47. package/dist/esm/init/initCommand.d.ts +6 -0
  48. package/dist/esm/init/parseJsonRecord.d.ts +4 -0
  49. package/dist/esm/init/scaffold.d.ts +3 -1
  50. package/dist/esm/init/templates.d.ts +13 -0
  51. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +39 -0
  52. package/dist/esm/injectSection.d.ts +6 -0
  53. package/dist/esm/loadConfig.d.ts +46 -0
  54. package/dist/esm/loadValidatedConfig.d.ts +8 -0
  55. package/dist/esm/packsPath.d.ts +15 -0
  56. package/dist/esm/packsPath.js +47 -0
  57. package/dist/esm/parseCommitMessage.d.ts +42 -0
  58. package/dist/esm/parseRequestedTags.d.ts +7 -0
  59. package/dist/esm/planReleaseNotesPreviews.d.ts +20 -0
  60. package/dist/esm/planVersionBump.d.ts +12 -0
  61. package/dist/esm/prepareCommand.d.ts +10 -0
  62. package/dist/esm/previewTagPrefixes.d.ts +21 -1
  63. package/dist/esm/propagateBumps.d.ts +15 -0
  64. package/dist/esm/publish.d.ts +1 -0
  65. package/dist/esm/publishCommand.d.ts +4 -0
  66. package/dist/esm/pushCommand.d.ts +4 -0
  67. package/dist/esm/pushRelease.d.ts +6 -0
  68. package/dist/esm/readCurrentVersion.d.ts +1 -0
  69. package/dist/esm/releaseFiles.d.ts +22 -0
  70. package/dist/esm/releasePlan.d.ts +23 -0
  71. package/dist/esm/releasePrepare.d.ts +40 -0
  72. package/dist/esm/releasePrepare.js +0 -3
  73. package/dist/esm/releasePrepareMono.d.ts +8 -0
  74. package/dist/esm/releasePrepareMono.js +0 -4
  75. package/dist/esm/releasePrepareProject.d.ts +42 -0
  76. package/dist/esm/renderChangelogMarkdown.d.ts +17 -0
  77. package/dist/esm/renderReleaseNotes.d.ts +25 -0
  78. package/dist/esm/reportPrepare.d.ts +14 -0
  79. package/dist/esm/reportPrepare.js +4 -2
  80. package/dist/esm/resolveCliffConfigPath.d.ts +10 -0
  81. package/dist/esm/resolveCommandTags.d.ts +13 -1
  82. package/dist/esm/resolveReleaseNotesConfig.d.ts +14 -0
  83. package/dist/esm/resolveReleaseTags.d.ts +22 -0
  84. package/dist/esm/runGitCliff.d.ts +37 -1
  85. package/dist/esm/runGitCliff.js +4 -10
  86. package/dist/esm/showTagPrefixesCommand.d.ts +11 -0
  87. package/dist/esm/stripScope.d.ts +7 -0
  88. package/dist/esm/sync-labels/generateCommand.d.ts +17 -0
  89. package/dist/esm/sync-labels/initCommand.d.ts +9 -0
  90. package/dist/esm/sync-labels/presets.d.ts +6 -0
  91. package/dist/esm/sync-labels/resolveLabels.d.ts +10 -0
  92. package/dist/esm/sync-labels/retiredConfig.d.ts +8 -0
  93. package/dist/esm/sync-labels/syncCommand.d.ts +6 -0
  94. package/dist/esm/sync-labels/templates.d.ts +14 -0
  95. package/dist/esm/sync-labels/types.d.ts +4 -0
  96. package/dist/esm/syncWorkTypes.d.ts +18 -0
  97. package/dist/esm/tagCommand.d.ts +1 -0
  98. package/dist/esm/typeGuards.d.ts +2 -0
  99. package/dist/esm/types.d.ts +302 -0
  100. package/dist/esm/validateConfig.d.ts +14 -1
  101. package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +21 -0
  102. package/dist/esm/validateOverridesCommand.d.ts +30 -0
  103. package/dist/esm/workTypesData.d.ts +27 -0
  104. package/dist/esm/workTypesUtils.d.ts +17 -0
  105. package/package.json +12 -11
@@ -1,6 +1,6 @@
1
- /** @noformat — @generated. Do not edit. Compiled by rdy. */
1
+ /** @noformat -- @generated. Do not edit. Compiled by rdy. */
2
2
  /* eslint-disable */
3
- export const __readyupVersion = "0.32.0";
3
+ export const __readyupVersion = "0.35.0";
4
4
 
5
5
 
6
6
  // .readyup/kits/default.ts
@@ -50,9 +50,55 @@ function detectRepoType() {
50
50
  return "single-package";
51
51
  }
52
52
 
53
+ // src/packsPath.ts
54
+ function packsPath(filesField, path) {
55
+ if (!isStringArray(filesField)) return true;
56
+ const prefixes = listAncestorPrefixes(path);
57
+ return filesField.some((entry) => {
58
+ const pattern = compileEntry(entry);
59
+ return pattern !== void 0 && prefixes.some((prefix) => pattern.test(prefix));
60
+ });
61
+ }
62
+ var GLOB_TOKEN_PATTERN = /\*\*\/|\*\*|\*|\?|[^*?]+/g;
63
+ function compileEntry(entry) {
64
+ if (entry.startsWith("!")) return void 0;
65
+ const normalized = entry.replace(/^\.?\//, "").replace(/\/+$/, "");
66
+ if (normalized === "") return void 0;
67
+ return new RegExp(`^${translateGlob(normalized)}$`);
68
+ }
69
+ function isStringArray(value) {
70
+ return Array.isArray(value) && value.every((entry) => typeof entry === "string");
71
+ }
72
+ function listAncestorPrefixes(path) {
73
+ const segments = path.split("/");
74
+ return segments.map((_segment, index) => segments.slice(0, index + 1).join("/"));
75
+ }
76
+ function translateGlob(pattern) {
77
+ let source = "";
78
+ for (const [token] of pattern.matchAll(GLOB_TOKEN_PATTERN)) {
79
+ switch (token) {
80
+ case "**/":
81
+ source += String.raw`(?:[^/]*\/)*`;
82
+ break;
83
+ case "**":
84
+ source += ".*";
85
+ break;
86
+ case "*":
87
+ source += "[^/]*";
88
+ break;
89
+ case "?":
90
+ source += "[^/]";
91
+ break;
92
+ default:
93
+ source += RegExp.escape(token);
94
+ }
95
+ }
96
+ return source;
97
+ }
98
+
53
99
  // .readyup/kits/default.ts
54
100
  function getMinVersion() {
55
- const picked = { "version": "10.4.4" };
101
+ const picked = { "version": "10.6.0" };
56
102
  if (typeof picked["version"] !== "string") {
57
103
  throw new TypeError("release-kit/package.json: 'version' must be a string");
58
104
  }
@@ -61,6 +107,7 @@ function getMinVersion() {
61
107
  function hasPublishablePackages() {
62
108
  return discoverWorkspaces({ filter: (w) => w.isPackage }).length > 0;
63
109
  }
110
+ var DEFAULT_CHANGELOG_JSON_PATH = ".meta/changelog.json";
64
111
  var CONFIG_EXPORT_PATTERNS = [
65
112
  /export\s+default\b/,
66
113
  /export\s+(?:const|let|var)\s+config\b/,
@@ -204,6 +251,32 @@ var default_default = defineRdyKit({
204
251
  }
205
252
  ]
206
253
  },
254
+ // Deliberately outside the config gate above: that check skips where the config file is absent, and a repo
255
+ // with no config file still defaults `changelogJson.enabled` to true and still publishes tarballs.
256
+ {
257
+ name: "published packages ship CHANGELOG.md",
258
+ severity: "warn",
259
+ skip: () => !hasPublishablePackages() ? "no publishable packages" : false,
260
+ check: () => packagesShipChangelog(),
261
+ fix: 'Add "CHANGELOG.md" to the files field of each affected package.json'
262
+ },
263
+ {
264
+ name: "changelog.json generation is enabled",
265
+ severity: "recommend",
266
+ skip: () => !hasPublishablePackages() ? "no publishable packages" : false,
267
+ check: () => changelogJsonIsEnabled(),
268
+ fix: "Remove changelogJson.enabled: false from .config/release-kit.config.ts so release-kit writes the machine-readable changelog that upgrade tooling reads before CHANGELOG.md",
269
+ checks: [
270
+ {
271
+ name: "published packages ship the changelog JSON",
272
+ severity: "warn",
273
+ check: () => packagesShipChangelogJson(),
274
+ get fix() {
275
+ return `Add "${resolveChangelogJsonOutputPath()}" to the files field of each affected package.json`;
276
+ }
277
+ }
278
+ ]
279
+ },
207
280
  {
208
281
  name: "config does not use removed releaseNotes.shouldCreateGithubRelease",
209
282
  severity: "error",
@@ -218,6 +291,13 @@ var default_default = defineRdyKit({
218
291
  check: () => !hasDevDependency("git-cliff"),
219
292
  fix: "pnpm remove git-cliff \u2014 release-kit handles changelog generation directly"
220
293
  },
294
+ {
295
+ name: "@changesets/cli not in devDependencies",
296
+ severity: "recommend",
297
+ quiet: true,
298
+ check: () => !hasDevDependency("@changesets/cli"),
299
+ fix: "pnpm remove @changesets/cli, then delete .changeset/ and any changeset:* scripts; release-kit supersedes changesets"
300
+ },
221
301
  {
222
302
  name: ".config/git-cliff.toml matches current template",
223
303
  severity: "warn",
@@ -257,6 +337,11 @@ var default_default = defineRdyKit({
257
337
  }
258
338
  ]
259
339
  });
340
+ function changelogJsonIsEnabled() {
341
+ const content = readFile(".config/release-kit.config.ts");
342
+ if (content === void 0) return true;
343
+ return !/changelogJson\s*:\s*\{[^}]*enabled\s*:\s*false/.test(content);
344
+ }
260
345
  function configFileExportsConfig() {
261
346
  const content = readFile(".config/release-kit.config.ts");
262
347
  if (content === void 0) return false;
@@ -269,6 +354,12 @@ function labelsHaveCurrentPresetHash(presetName, expectedHash) {
269
354
  const match = pattern.exec(content);
270
355
  return match !== null && match[1] === expectedHash;
271
356
  }
357
+ function packagesShipChangelog() {
358
+ return reportWorkspacesOmitting("CHANGELOG.md");
359
+ }
360
+ function packagesShipChangelogJson() {
361
+ return reportWorkspacesOmitting(resolveChangelogJsonOutputPath());
362
+ }
272
363
  function readmeHasReleaseNotesMarkers(content) {
273
364
  return content.includes("<!-- section:release-notes -->") && content.includes("<!-- /section:release-notes -->");
274
365
  }
@@ -301,16 +392,41 @@ function releaseNotesInjectsIntoReadme() {
301
392
  if (content === void 0) return false;
302
393
  return /shouldInjectIntoReadme\s*:\s*true/.test(content);
303
394
  }
395
+ function reportWorkspacesOmitting(path) {
396
+ const failing = [];
397
+ const packageWorkspaces = discoverWorkspaces({ filter: (w) => w.isPackage });
398
+ for (const { dir, packageJson } of packageWorkspaces) {
399
+ if (!packsPath(packageJson["files"], path)) {
400
+ failing.push(dir === "." ? "package.json" : `${dir}/package.json`);
401
+ }
402
+ }
403
+ if (failing.length === 0) return true;
404
+ return {
405
+ ok: false,
406
+ detail: `files field omits ${path}: ${failing.join(", ")}`
407
+ };
408
+ }
409
+ function resolveChangelogJsonOutputPath() {
410
+ const content = readFile(".config/release-kit.config.ts");
411
+ if (content === void 0) return DEFAULT_CHANGELOG_JSON_PATH;
412
+ const match = /changelogJson\s*:\s*\{[^}]*outputPath\s*:\s*['"]([^'"]+)['"]/.exec(content);
413
+ return match?.[1] ?? DEFAULT_CHANGELOG_JSON_PATH;
414
+ }
304
415
  export {
305
416
  CLIFF_TEMPLATE_HASH,
306
417
  COMMON_PRESET_HASH,
418
+ DEFAULT_CHANGELOG_JSON_PATH,
307
419
  PUBLISH_WORKFLOW_HASH_MONOREPO,
308
420
  PUBLISH_WORKFLOW_HASH_SINGLE,
309
421
  RELEASE_WORKFLOW_HASH_MONOREPO,
310
422
  RELEASE_WORKFLOW_HASH_SINGLE,
311
423
  SYNC_LABELS_WORKFLOW_HASH,
424
+ changelogJsonIsEnabled,
312
425
  configFileExportsConfig,
313
426
  default_default as default,
427
+ packagesShipChangelog,
428
+ packagesShipChangelogJson,
314
429
  readmeHasReleaseNotesMarkers,
315
- readmesHaveReleaseNotesMarkers
430
+ readmesHaveReleaseNotesMarkers,
431
+ resolveChangelogJsonOutputPath
316
432
  };
@@ -1,6 +1,6 @@
1
- /** @noformat — @generated. Do not edit. Compiled by rdy. */
1
+ /** @noformat -- @generated. Do not edit. Compiled by rdy. */
2
2
  /* eslint-disable */
3
- export const __readyupVersion = "0.32.0";
3
+ export const __readyupVersion = "0.35.0";
4
4
 
5
5
 
6
6
  // .readyup/kits/npm-auto-publish.ts
@@ -68,6 +68,9 @@ var packagesChecklist = defineRdyChecklist({
68
68
  },
69
69
  fix: 'Set "packageManager": "pnpm@..." in root package.json'
70
70
  },
71
+ // Unfalsifiable on readyup 0.33 and later, where discovery reports the repo root and throws on an unreadable
72
+ // root package.json rather than returning an empty list. It stays for a consumer below that, whose monorepo
73
+ // mode returns the matched directories alone and so returns nothing when the globs match nothing.
71
74
  {
72
75
  name: "At least one workspace discovered",
73
76
  check: () => discoverWorkspaces().length > 0,
@@ -86,7 +89,9 @@ var packagesChecklist = defineRdyChecklist({
86
89
  // reach the registry on every run regardless of the skip. Per-outcome wording goes in the check's detail.
87
90
  fix: 'Restore a usable npm session: log in with "npm login", or restore access to the registry, which the trusted-publisher check queries directly',
88
91
  get checks() {
89
- return discoverWorkspaces().map((workspace) => buildWorkspaceCheck(workspace));
92
+ return discoverWorkspaces({ filter: belongsInPackagesChecklist }).map(
93
+ (workspace) => buildWorkspaceCheck(workspace)
94
+ );
90
95
  }
91
96
  }
92
97
  ]
@@ -95,6 +100,9 @@ var npm_auto_publish_default = defineRdyKit({
95
100
  fixLocation: "inline",
96
101
  checklists: [repoChecklist, packagesChecklist]
97
102
  });
103
+ function belongsInPackagesChecklist(workspace) {
104
+ return !workspace.isRoot || workspace.isPackage;
105
+ }
98
106
  function buildWorkspaceCheck(workspace) {
99
107
  const displayName = workspace.name ?? "(unnamed)";
100
108
  const pkgJsonPath = path.join(workspace.dir, "package.json");
@@ -5,15 +5,15 @@
5
5
  "checklists": [
6
6
  "release-kit"
7
7
  ],
8
- "esbuildVersion": "0.28.1",
8
+ "esbuildVersion": "0.28.2",
9
9
  "inputs": [
10
10
  {
11
- "hash": "fe239d01",
11
+ "hash": "4a359cc9",
12
12
  "kind": "module",
13
13
  "path": "kits/default.ts"
14
14
  },
15
15
  {
16
- "hash": "999accdd",
16
+ "hash": "bb217228",
17
17
  "kind": "inline",
18
18
  "path": "../package.json",
19
19
  "paths": [
@@ -30,6 +30,11 @@
30
30
  "kind": "module",
31
31
  "path": "../src/init/parseJsonRecord.ts"
32
32
  },
33
+ {
34
+ "hash": "b798d024",
35
+ "kind": "module",
36
+ "path": "../src/packsPath.ts"
37
+ },
33
38
  {
34
39
  "hash": "646fe51b",
35
40
  "kind": "module",
@@ -38,30 +43,30 @@
38
43
  ],
39
44
  "name": "default",
40
45
  "path": "kits/default.js",
41
- "readyupVersion": "0.32.0",
46
+ "readyupVersion": "0.35.0",
42
47
  "source": "kits/default.ts",
43
- "sourceHash": "fe239d01",
44
- "targetHash": "e9daa536"
48
+ "sourceHash": "4a359cc9",
49
+ "targetHash": "5b3e6dcf"
45
50
  },
46
51
  {
47
52
  "checklists": [
48
53
  "repo",
49
54
  "packages"
50
55
  ],
51
- "esbuildVersion": "0.28.1",
56
+ "esbuildVersion": "0.28.2",
52
57
  "inputs": [
53
58
  {
54
- "hash": "ac484613",
59
+ "hash": "15641d4a",
55
60
  "kind": "module",
56
61
  "path": "kits/npm-auto-publish.ts"
57
62
  }
58
63
  ],
59
64
  "name": "npm-auto-publish",
60
65
  "path": "kits/npm-auto-publish.js",
61
- "readyupVersion": "0.32.0",
66
+ "readyupVersion": "0.35.0",
62
67
  "source": "kits/npm-auto-publish.ts",
63
- "sourceHash": "ac484613",
64
- "targetHash": "6f5398ef"
68
+ "sourceHash": "15641d4a",
69
+ "targetHash": "721e337b"
65
70
  }
66
71
  ]
67
72
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,63 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 10.6.0 — 2026-09-08
6
+
7
+ ### 🎉 Features
8
+
9
+ - Report a package whose tarball would omit its changelog (#768)
10
+
11
+ Adds ReadyUp checks to verify that every published package bundles its changelog files: the human-readable `CHANGELOG.md` and the machine-readable `changelog.json`.
12
+
13
+ - Add a migration field to changelog.json items (#770)
14
+
15
+ Adds an optional `migration` field to entries in the `.meta/changelog.json` that `release-kit` generates. If a commit body contains a labeled migration step, that content is copied to the `migration` field.
16
+
17
+ ### 🐛 Bug fixes
18
+
19
+ - Pin git-cliff and remove the per-run npx cache refresh (#776)
20
+
21
+ Fixes an issue where git-cliff's version was unpinned, resulting in a per-run cache refresh. An exact version is now pinned.
22
+
23
+ Removes the per-run cache refresh, which re-ran `npx` without `--prefer-offline` once per prepare and forced a registry round trip. An exact version leaves npm nothing to revalidate, so a prepare run no longer waits on registry latency.
24
+
25
+ Also silences git-cliff's INFO output, in particular the crates.io update notice.
26
+
27
+ ### ♻️ Refactoring
28
+
29
+ - Remove the unwired isChangelogItem type guard (#771)
30
+
31
+ Removes the unused `isChangelogItem` from `release-kit`'s `changelogJsonUtils.ts`, together with its tests.
32
+
33
+ Documentation is updated to record the boundary on `isChangelogEntry`: Entries parsed from `.meta/changelog.json` are shape-checked at the top level only, and strict validation applies only to the human-authored `.meta/changelog-overrides.json`.
34
+
35
+ ### 🧪 Tests
36
+
37
+ - Read console spy output with listConsoleLines rather than by hand (#781)
38
+
39
+ - Replaces the hand-rolled reads of a console spy's `mock.calls` in the `nmr`, `release-kit`, and `v11y-check` test suites with `listConsoleLines` from `@williamthorsen/toolbelt.vitest/candidate`.
40
+ - Clears the `toolbelt.vitest/no-console-calls-read` recommendation that `rdy run --packages` reported.
41
+
42
+ ## 10.5.0 — 2026-09-01
43
+
44
+ ### 🎉 Features
45
+
46
+ - Report a consuming repo that still declares @changesets/cli (#764)
47
+
48
+ Adds a check to the default ReadyUp kit in `@williamthorsen/release-kit` that reports a repo still declaring `@changesets/cli`, the release tool superseded by release-kit. The check reports at `recommend` severity; its fix directs the consumer to remove the dependency together with the `.changeset/` directory and any `changeset:*` scripts.
49
+
50
+ ### ♻️ Refactoring
51
+
52
+ - Upgrade to readyup v0.34 and exclude a private repo root from kit checks (#759)
53
+
54
+ Upgrades `readyup` to v0.34, whose `discoverWorkspaces` now reports the repo root. `release-kit`'s `npm-auto-publish` kit filters that root out unless it is publishable, so a private root is no longer listed in the kit's report.
55
+
56
+ Test suites for release-kit's ReadyUp kits have been refactored to use the patterns documented in v0.34.
57
+
58
+ - Upgrade eslint-config-typescript and mark the discards it now reports (#761)
59
+
60
+ Upgrades `@williamthorsen/eslint-config-typescript` to v12 and fixes violations surfaced by the newly added rule warning against a disposable that is discarded or that is bound to a declaration that never releases it.
61
+
5
62
  ## 10.4.4 — 2026-08-24
6
63
 
7
64
  ### ♻️ Refactoring
package/README.md CHANGED
@@ -4,7 +4,29 @@ Version-bumping and changelog-generation toolkit for release workflows.
4
4
 
5
5
  Provides a self-contained CLI that auto-discovers workspaces from `pnpm-workspace.yaml`, parses conventional commits, determines version bumps, updates `package.json` files, and generates changelogs from `git-cliff --context` output rendered in-process (with optional [editorial overrides](#editorial-overrides)).
6
6
 
7
- <!-- section:release-notes --><!-- /section:release-notes -->
7
+ <!-- section:release-notes -->
8
+ ## Release notes — v10.6.0 (2026-09-08)
9
+
10
+ ### 🎉 Features
11
+
12
+ - Report a package whose tarball would omit its changelog (#768)
13
+
14
+ Adds ReadyUp checks to verify that every published package bundles its changelog files: the human-readable `CHANGELOG.md` and the machine-readable `changelog.json`.
15
+
16
+ - Add a migration field to changelog.json items (#770)
17
+
18
+ Adds an optional `migration` field to entries in the `.meta/changelog.json` that `release-kit` generates. If a commit body contains a labeled migration step, that content is copied to the `migration` field.
19
+
20
+ ### 🐛 Bug fixes
21
+
22
+ - Pin git-cliff and remove the per-run npx cache refresh (#776)
23
+
24
+ Fixes an issue where git-cliff's version was unpinned, resulting in a per-run cache refresh. An exact version is now pinned.
25
+
26
+ Removes the per-run cache refresh, which re-ran `npx` without `--prefer-offline` once per prepare and forced a registry round trip. An exact version leaves npm nothing to revalidate, so a prepare run no longer waits on registry latency.
27
+
28
+ Also silences git-cliff's INFO output, in particular the crates.io update notice.
29
+ <!-- /section:release-notes -->
8
30
 
9
31
  ## Installation
10
32
 
@@ -64,6 +86,8 @@ That's it for most repos. The CLI auto-discovers workspaces and applies sensible
64
86
  4. **Version bump + changelog**: bumps `package.json` versions, builds structured `ChangelogEntry[]` from `git-cliff --context`, applies any [editorial overrides](#editorial-overrides) from per-scope `.meta/changelog-overrides.json` files, and renders both `CHANGELOG.md` and `.meta/changelog.json` from that single source. `git-cliff` is invoked only for its `--context` JSON; markdown rendering happens in-process so `.meta/changelog.json` and `CHANGELOG.md` always agree.
65
87
  5. **Release tags file**: writes computed tags to `tmp/.release-tags` for the release workflow to read when tagging and pushing.
66
88
 
89
+ Neither changelog reaches a published tarball on its own. npm stopped including `CHANGELOG` automatically in npm 7, so a package that declares a `files` field ships a changelog only where that field names `CHANGELOG.md` and `.meta/changelog.json`. release-kit's readyup kit reports a publishable workspace whose `files` field omits either.
90
+
67
91
  ## Commit format
68
92
 
69
93
  release-kit parses commits in these formats:
@@ -392,6 +416,40 @@ Work types from your config are merged with these defaults by key — your entri
392
416
 
393
417
  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.
394
418
 
419
+ ## `changelog.json` item schema
420
+
421
+ Each item under a section in `.meta/changelog.json` carries one required field and four optional ones:
422
+
423
+ | Field | Type | Meaning |
424
+ | ------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------- |
425
+ | `description` | `string` | The bullet headline, taken from the commit subject with the ticket ID and type prefix stripped. |
426
+ | `body` | `string` | The commit body, with trailing trailer metadata stripped. |
427
+ | `breaking` | `boolean` | Present and `true` where the commit subject carried the `!` prefix. See [`!` (breaking change) policy](#-breaking-change-policy). |
428
+ | `migration` | `string` | The migration step for a consumer. See below. |
429
+ | `hash` | `string` | The full commit SHA, and the key on which an override entry matches. Absent on synthetic propagation entries. |
430
+
431
+ Every optional field is omitted rather than emitted as `null`, so a consumer tests for presence.
432
+
433
+ ### The `migration` field
434
+
435
+ A commit body states a migration step as a paragraph opening with the literal label `Migration:`. Where one is present, `migration` carries that paragraph with the label stripped:
436
+
437
+ ```jsonc
438
+ {
439
+ "description": "Rename the workspace field",
440
+ "body": "Renames `name` to `id`.\n\nMigration: Change any uses of `name` to `id`.",
441
+ "migration": "Change any uses of `name` to `id`.",
442
+ }
443
+ ```
444
+
445
+ Three properties are worth knowing:
446
+
447
+ - **It is independent of `breaking`.** A `fix` cannot carry `!` under the default breaking policy, and a `fix` that tightens validation still imposes a migration. Filter on `migration` to find every step; filter on `breaking` to find every breaking change.
448
+ - **`body` keeps the paragraph.** The field is an extraction, not a move, so `CHANGELOG.md` and `.meta/changelog.json` go on agreeing.
449
+ - **It is derived, not authored.** `migration` is not a field an override file can set (see [File shape](#file-shape)); it is re-derived from whatever `body` an override installs, and cleared where that body carries no labeled paragraph. To change the migration text, override `body`.
450
+
451
+ The label match is exact: `migration:` and `**Migration:**` are not recognized.
452
+
395
453
  ## Editorial overrides
396
454
 
397
455
  Generated changelogs occasionally need editorial correction — typos, redacted scope, reworded entries, or historical commits whose bodies carry verbatim PR-template scaffolding (`## What`, `## Why`, etc.) that renders as literal text in user-facing release notes. Rewriting git history is not viable, and any in-place edit to `CHANGELOG.md` or `.meta/changelog.json` is overwritten on the next release because release-kit regenerates both artifacts from scratch.
@@ -453,6 +511,8 @@ Per-entry fields are all optional, but at least one must be present per entry:
453
511
  | `body` | `string` | Replaces the entry's body (the prose that renders below the bullet). Other fields are preserved. |
454
512
  | `breaking` | `boolean` | Toggles the `🚨 **Breaking:** ` marker on the bullet. |
455
513
 
514
+ There is no `migration` key: the field is derived from `body`, so an override that replaces `body` re-derives it. See [The `migration` field](#the-migration-field).
515
+
456
516
  ### Hash-prefix matching
457
517
 
458
518
  Keys can be either the full 40-character commit SHA or a non-ambiguous prefix. The matcher walks every `ChangelogItem.hash` value present in the entry tree and resolves each override key to its set of matching hashes:
@@ -908,7 +968,7 @@ These kits are no longer reachable through `rdy run --from github:williamthorsen
908
968
  This package shells out to two external tools:
909
969
 
910
970
  - **`git`** — must be available on `PATH`. Used to find tags and retrieve commit history.
911
- - **`git-cliff`** — automatically downloaded and cached via `npx` on first invocation. No need to install it as a dev dependency.
971
+ - **`git-cliff`** — pinned to an exact version and fetched via `npx` on first invocation, then served from npx's cache. No need to install it as a dev dependency. The pin moves only when a new release-kit version names a newer git-cliff, so two releases cut months apart run the same changelog generator.
912
972
 
913
973
  ## Upgrading from v4 to v5
914
974
 
@@ -1062,12 +1122,12 @@ If your format command does not accept file arguments, update it to one that doe
1062
1122
 
1063
1123
  ### v1.1.0: `git-cliff` is no longer a required dev dependency
1064
1124
 
1065
- `git-cliff` is now invoked via `npx --yes git-cliff` instead of requiring it as a dev dependency. You can remove it from your `devDependencies`. The version is not pinned, so `npx` downloads and caches the latest version on first invocation. To pin a specific version, use `npx --yes git-cliff@2.12.0` by wrapping the call in a custom script.
1125
+ `git-cliff` is now invoked via `npx` instead of requiring it as a dev dependency. You can remove it from your `devDependencies`. release-kit names an exact version in the invocation, so the version you get is decided by the release-kit version you install rather than by whatever the registry serves that day.
1066
1126
 
1067
1127
  ## Migration from changesets
1068
1128
 
1069
1129
  1. Add `@williamthorsen/release-kit` as a dev dependency.
1070
- 2. Remove `@changesets/cli` from dev dependencies.
1130
+ 2. Remove `@changesets/cli` from dev dependencies. The [default readyup kit](#readiness-checks) reports a repo that still declares it.
1071
1131
  3. Delete the `.changeset/` directory.
1072
1132
  4. Run `npx @williamthorsen/release-kit init` to scaffold workflow and config files.
1073
1133
  5. Remove `changeset:*` scripts from `package.json` (no replacement needed — the CLI handles everything).
@@ -1 +1,6 @@
1
+ /**
2
+ * Verify that the git working tree has no uncommitted changes.
3
+ *
4
+ * @throws If `git status --porcelain` reports any changes.
5
+ */
1
6
  export declare function assertCleanWorkingTree(): void;
@@ -1,4 +1,26 @@
1
1
  import type { GenerateChangelogOptions } from './generateChangelogs.js';
2
2
  import type { ChangelogEntry, ReleaseConfig } from './types.js';
3
+ /**
4
+ * Strip cliff-template decorations from a group string, returning the bare section name.
5
+ *
6
+ * The bundled `cliff.toml.template` encodes canonical row order as a hidden HTML comment
7
+ * (e.g. `"<!-- 04 -->🐛 Bug fixes"`) so tera's `group_by` filter sorts groups predictably.
8
+ * The body template's `striptags` filter erases the comment from the rendered heading,
9
+ * but downstream consumers that read the raw `group` value (changelog.json titles, the
10
+ * dev-vs-public classifier, the drift test) see the prefix and must strip it. The trailing
11
+ * `stripEmojiPrefix` keeps the helper backward-compatible with consumer overrides written
12
+ * as bare names.
13
+ */
3
14
  export declare function stripGroupDecorations(group: string): string;
15
+ /**
16
+ * Build structured changelog entries from git history using git-cliff `--context`.
17
+ *
18
+ * Pure data: invokes git-cliff, parses its `--context` output, and returns the transformed
19
+ * `ChangelogEntry[]`. Performs no `changelog.json` I/O — callers persist the entries via
20
+ * `renderChangelogJson`.
21
+ *
22
+ * Always invokes git-cliff: dry-run is the caller's concern (it governs whether the file write happens,
23
+ * not whether git-cliff runs). This means dry-run exercises the full git-cliff toolchain and surfaces missing-binary,
24
+ * malformed-config, and template-resolution failures earlier than before.
25
+ */
4
26
  export declare function buildChangelogEntries(config: Pick<ReleaseConfig, 'cliffConfigPath' | 'changelogJson'>, tag: string, options?: GenerateChangelogOptions): ChangelogEntry[];
@@ -1,6 +1,7 @@
1
1
  import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
2
2
  import { extractVersion } from "./changelogJsonUtils.js";
3
3
  import { DEFAULT_WORK_TYPES } from "./defaults.js";
4
+ import { extractMigration } from "./extractMigration.js";
4
5
  import { COMMIT_PREPROCESSOR_PATTERNS, PIPE_SCOPE_SOURCE } from "./parseCommitMessage.js";
5
6
  import { resolveCliffConfigPath } from "./resolveCliffConfigPath.js";
6
7
  import { runGitCliff } from "./runGitCliff.js";
@@ -101,6 +102,10 @@ function transformReleases(releases, devOnlySections) {
101
102
  if (breaking) {
102
103
  item.breaking = true;
103
104
  }
105
+ const migration = extractMigration(body);
106
+ if (migration !== undefined) {
107
+ item.migration = migration;
108
+ }
104
109
  if (commit.id !== undefined && commit.id !== '') {
105
110
  item.hash = commit.id;
106
111
  }
@@ -1,8 +1,24 @@
1
1
  import type { WorkspaceConfig } from './types.js';
2
+ /** Reverse adjacency map from package names to their workspace dependents. */
2
3
  export interface DependencyGraph {
4
+ /** Resolve a package name to its workspace `dir`. */
3
5
  packageNameToDir: Map<string, string>;
6
+ /** Resolve a workspace `dir` to its package name (inverse of `packageNameToDir`). */
4
7
  dirToPackageName: Map<string, string>;
8
+ /** Map a package name to the workspaces that depend on it. */
5
9
  dependentsOf: Map<string, WorkspaceConfig[]>;
10
+ /**
11
+ * Forward adjacency: map a workspace `dir` to the set of `workspace:`-protocol package
12
+ * names it declares in `dependencies` or `peerDependencies`. Complement of `dependentsOf`.
13
+ */
6
14
  dependenciesOf: Map<string, Set<string>>;
7
15
  }
16
+ /**
17
+ * Build a reverse dependency graph from the workspaces' `package.json` files.
18
+ *
19
+ * Reads each workspace's primary `package.json` (first entry in `packageFiles`) to discover
20
+ * `workspace:` references in `dependencies` and `peerDependencies`. Returns a map from each
21
+ * package name to the workspaces that depend on it, enabling upward traversal from a bumped
22
+ * package to all its dependents.
23
+ */
8
24
  export declare function buildDependencyGraph(workspaces: readonly WorkspaceConfig[]): DependencyGraph;
@@ -1,2 +1,10 @@
1
1
  import type { ChangelogEntry } from './types.js';
2
+ /**
3
+ * Build a synthetic changelog entry for a forced empty-range release.
4
+ *
5
+ * Produces a single `ChangelogEntry` with one `'Notes'` section (audience `'dev'`)
6
+ * containing a single `'Forced version bump.'` item. Used when `release-kit prepare`
7
+ * proceeds via `--force`, `--bump=X`, or `--set-version` against a unit with zero
8
+ * qualifying commits since its last tag. Pure function: no I/O.
9
+ */
2
10
  export declare function buildEmptyReleaseEntry(version: string, date: string): ChangelogEntry;
@@ -1,2 +1,12 @@
1
1
  import type { PrepareResult } from './types.js';
2
+ /**
3
+ * Build a release commit body from a prepare result.
4
+ *
5
+ * Each released workspace with commits gets a section headed by its tag,
6
+ * followed by scope-stripped commit messages as bullet points. Sections
7
+ * are separated by blank lines. The project release (when present) is
8
+ * appended as a final section using the same format. Returns an empty
9
+ * string when no released workspaces have commits and there is no project
10
+ * release.
11
+ */
2
12
  export declare function buildReleaseSummary(result: Pick<PrepareResult, 'project' | 'workspaces'>): string;
@@ -1,4 +1,10 @@
1
1
  import type { ChangelogEntry } from './types.js';
2
+ /**
3
+ * Build a synthetic changelog entry for a propagation-only bump.
4
+ *
5
+ * Produces a single `ChangelogEntry` with one `'Dependency updates'` section (audience `'dev'`)
6
+ * containing one item per propagated-from dependency. Pure function: no I/O.
7
+ */
2
8
  export declare function buildSyntheticChangelogEntry(propagatedFrom: ReadonlyArray<{
3
9
  packageName: string;
4
10
  newVersion: string;
@@ -1,2 +1,10 @@
1
1
  import type { ReleaseType } from './types.js';
2
+ /**
3
+ * Bumps a semver version string by the given release type.
4
+ *
5
+ * @param version - A semver version string (e.g., '1.2.3').
6
+ * @param releaseType - The type of release bump to apply.
7
+ * @returns The bumped version string.
8
+ * @throws If the version string is not a valid semver format.
9
+ */
2
10
  export declare function bumpVersion(version: string, releaseType: ReleaseType): string;
@@ -1,4 +1,15 @@
1
1
  import type { ChangelogEntry, ReleaseConfig } from './types.js';
2
+ /** Resolve the absolute output path for the `changelog.json` file under a workspace's changelog directory. */
2
3
  export declare function resolveChangelogJsonPath(config: Pick<ReleaseConfig, 'changelogJson'>, changelogPath: string): string;
4
+ /** Renders changelog entries as a `changelog.json` file's complete content, sorted newest-first. */
3
5
  export declare function renderChangelogJson(entries: ChangelogEntry[]): string;
6
+ /**
7
+ * Merges `entries` with the on-disk entries at `filePath` and returns the merged set in
8
+ * newest-first order, without writing.
9
+ *
10
+ * Preserves entries that exist on disk but are absent from `entries` — load-bearing for
11
+ * synthetic-entry preservation across propagation runs. Reads the file when present; treats a
12
+ * missing or malformed file as an empty existing set, so a malformed file does not abort the
13
+ * release.
14
+ */
4
15
  export declare function mergeChangelogEntriesWithDisk(filePath: string, entries: ChangelogEntry[]): ChangelogEntry[];
@@ -1,5 +1,19 @@
1
- import type { ChangelogEntry, ChangelogItem } from './types.js';
2
- export declare function isChangelogItem(value: unknown): value is ChangelogItem;
1
+ import type { ChangelogEntry } from './types.js';
2
+ /**
3
+ * Type guard for `ChangelogEntry` values parsed from JSON.
4
+ *
5
+ * Checks the entry's top level only: `sections` is verified to be an array, and its contents pass
6
+ * through unvalidated. `.meta/changelog.json` is written by release-kit and read tolerantly; strict
7
+ * validation applies to the human-authored `.meta/changelog-overrides.json`, in `changelogOverrides.ts`.
8
+ */
3
9
  export declare function isChangelogEntry(value: unknown): value is ChangelogEntry;
10
+ /** Extract the version from a tag by stripping the prefix up to the first digit. */
4
11
  export declare function extractVersion(tag: string): string;
12
+ /**
13
+ * Read and parse a changelog JSON file, returning validated entries.
14
+ *
15
+ * Returns `undefined` if the file cannot be read or does not contain a valid array.
16
+ * Silent on parse error — for the warn-and-treat-as-empty variant used by upsert callers,
17
+ * see `readExistingEntries` in `./changelogJsonFile.ts`.
18
+ */
5
19
  export declare function readChangelogEntries(filePath: string): ChangelogEntry[] | undefined;