@williamthorsen/release-kit 15.0.3 → 16.1.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 (146) hide show
  1. package/.meta/changelog.json +93 -0
  2. package/.readyup/kits/default.js +14 -14
  3. package/.readyup/kits/npm-auto-publish.js +4 -4
  4. package/.readyup/manifest.json +13 -13
  5. package/CHANGELOG.md +27 -0
  6. package/README.md +12 -3
  7. package/dist/esm/applyChangelogOverrides.js +6 -6
  8. package/dist/esm/assertReleasesOnNpm.js +11 -11
  9. package/dist/esm/assertTaggedBaseline.js +5 -5
  10. package/dist/esm/assertTarballsComplete.d.ts +9 -0
  11. package/dist/esm/assertTarballsComplete.js +52 -0
  12. package/dist/esm/bin/release-kit.js +16 -16
  13. package/dist/esm/buildChangelogEntries.d.ts +19 -19
  14. package/dist/esm/buildChangelogEntries.js +40 -36
  15. package/dist/esm/buildChangelogItem.js +24 -24
  16. package/dist/esm/buildDependencyGraph.js +23 -23
  17. package/dist/esm/buildReleasedResult.js +21 -19
  18. package/dist/esm/buildSyntheticChangelogEntry.js +2 -2
  19. package/dist/esm/bumpVersion.js +8 -8
  20. package/dist/esm/changelogArchive.d.ts +1 -1
  21. package/dist/esm/changelogArchive.js +2 -2
  22. package/dist/esm/changelogJsonFile.js +5 -5
  23. package/dist/esm/changelogJsonUtils.js +3 -3
  24. package/dist/esm/changelogOverrides.d.ts +5 -5
  25. package/dist/esm/changelogOverrides.js +23 -23
  26. package/dist/esm/classifyChangelogCommit.js +4 -4
  27. package/dist/esm/commitCommand.js +3 -3
  28. package/dist/esm/compareVersions.js +13 -13
  29. package/dist/esm/configSchema.d.ts +3 -2
  30. package/dist/esm/configSchema.js +2 -1
  31. package/dist/esm/createGithubRelease.d.ts +4 -4
  32. package/dist/esm/createGithubRelease.js +10 -10
  33. package/dist/esm/createGithubReleaseCommand.js +9 -9
  34. package/dist/esm/createTags.d.ts +2 -2
  35. package/dist/esm/createTags.js +8 -8
  36. package/dist/esm/decideDirectReleases.d.ts +5 -5
  37. package/dist/esm/decideDirectReleases.js +25 -25
  38. package/dist/esm/decideRelease.d.ts +1 -1
  39. package/dist/esm/decideRelease.js +2 -2
  40. package/dist/esm/deriveDirectoryConfig.d.ts +1 -1
  41. package/dist/esm/deriveDirectoryConfig.js +18 -18
  42. package/dist/esm/deriveWorkspaceConfig.js +4 -4
  43. package/dist/esm/detectPackageManager.d.ts +2 -0
  44. package/dist/esm/detectPackageManager.js +8 -5
  45. package/dist/esm/detectUndeclaredTagPrefixes.js +6 -6
  46. package/dist/esm/enumerateReleaseWindows.d.ts +1 -1
  47. package/dist/esm/enumerateReleaseWindows.js +7 -7
  48. package/dist/esm/findMissingManifestPaths.d.ts +14 -0
  49. package/dist/esm/findMissingManifestPaths.js +90 -0
  50. package/dist/esm/findTransitiveDependents.js +5 -5
  51. package/dist/esm/findUnroutedEntryScopes.js +11 -11
  52. package/dist/esm/format.d.ts +1 -1
  53. package/dist/esm/format.js +1 -1
  54. package/dist/esm/formatPrepareFiles.d.ts +3 -3
  55. package/dist/esm/formatPrepareFiles.js +10 -10
  56. package/dist/esm/formatPrepareWarnings.js +6 -6
  57. package/dist/esm/hasPrettierConfig.js +2 -2
  58. package/dist/esm/init/checks.js +5 -3
  59. package/dist/esm/init/detectRepoType.js +3 -3
  60. package/dist/esm/init/initCommand.d.ts +4 -4
  61. package/dist/esm/init/initCommand.js +7 -7
  62. package/dist/esm/init/parseJsonRecord.d.ts +1 -1
  63. package/dist/esm/init/parseJsonRecord.js +4 -4
  64. package/dist/esm/init/scaffold.d.ts +4 -4
  65. package/dist/esm/init/scaffold.js +15 -15
  66. package/dist/esm/init/templates.d.ts +10 -10
  67. package/dist/esm/init/templates.js +5 -5
  68. package/dist/esm/init/updateTemplatesCommand.d.ts +2 -2
  69. package/dist/esm/init/updateTemplatesCommand.js +14 -12
  70. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +1 -1
  71. package/dist/esm/injectReleaseNotesIntoReadme.js +6 -6
  72. package/dist/esm/injectSection.js +8 -8
  73. package/dist/esm/loadChangelogOverrides.d.ts +1 -1
  74. package/dist/esm/loadChangelogOverrides.js +9 -9
  75. package/dist/esm/loadConfig.d.ts +3 -3
  76. package/dist/esm/loadConfig.js +10 -10
  77. package/dist/esm/loadValidatedConfig.js +4 -4
  78. package/dist/esm/mergeConfig.d.ts +1 -1
  79. package/dist/esm/mergeConfig.js +27 -27
  80. package/dist/esm/monorepoConfigAssertions.js +15 -15
  81. package/dist/esm/npmRegistry.js +4 -4
  82. package/dist/esm/packFromCleanCheckout.d.ts +11 -0
  83. package/dist/esm/packFromCleanCheckout.js +81 -0
  84. package/dist/esm/packsPath.js +3 -3
  85. package/dist/esm/parseChangeRecordBlock.js +33 -33
  86. package/dist/esm/parseCommitMessage.js +5 -5
  87. package/dist/esm/planChangelogFiles.js +8 -8
  88. package/dist/esm/planReleaseNotesPreviews.js +10 -10
  89. package/dist/esm/planVersionBump.js +24 -16
  90. package/dist/esm/prepareArgs.d.ts +9 -9
  91. package/dist/esm/prepareArgs.js +11 -11
  92. package/dist/esm/prepareCommand.js +27 -24
  93. package/dist/esm/prepareComposite.d.ts +4 -4
  94. package/dist/esm/prepareComposite.js +16 -16
  95. package/dist/esm/{releasePrepareTargets.d.ts → prepareReleaseTargets.d.ts} +9 -9
  96. package/dist/esm/{releasePrepareTargets.js → prepareReleaseTargets.js} +40 -40
  97. package/dist/esm/prepareRunContext.d.ts +1 -1
  98. package/dist/esm/prepareRunContext.js +5 -5
  99. package/dist/esm/prepareTarget.d.ts +3 -3
  100. package/dist/esm/prepareTarget.js +41 -39
  101. package/dist/esm/previewTagPrefixes.js +19 -19
  102. package/dist/esm/propagateBumps.js +8 -8
  103. package/dist/esm/publish.d.ts +3 -3
  104. package/dist/esm/publish.js +6 -11
  105. package/dist/esm/publishCommand.d.ts +3 -3
  106. package/dist/esm/publishCommand.js +28 -20
  107. package/dist/esm/pushCommand.js +8 -8
  108. package/dist/esm/pushRelease.d.ts +1 -1
  109. package/dist/esm/pushRelease.js +2 -2
  110. package/dist/esm/readChangelogSections.d.ts +1 -1
  111. package/dist/esm/readChangelogSections.js +16 -16
  112. package/dist/esm/releasePlan.js +25 -25
  113. package/dist/esm/renderChangelogMarkdown.d.ts +4 -4
  114. package/dist/esm/renderChangelogMarkdown.js +18 -15
  115. package/dist/esm/renderReleaseNotes.d.ts +1 -1
  116. package/dist/esm/renderReleaseNotes.js +16 -16
  117. package/dist/esm/reportPrepare.js +34 -34
  118. package/dist/esm/resolveCommandTags.js +9 -9
  119. package/dist/esm/resolveReleaseDeclaration.d.ts +2 -2
  120. package/dist/esm/resolveReleaseDeclaration.js +22 -22
  121. package/dist/esm/resolveReleaseTags.js +17 -15
  122. package/dist/esm/resolveReleaseTargets.js +2 -2
  123. package/dist/esm/showTagPrefixesCommand.js +4 -4
  124. package/dist/esm/sync-labels/generateCommand.d.ts +3 -3
  125. package/dist/esm/sync-labels/generateCommand.js +10 -10
  126. package/dist/esm/sync-labels/initCommand.d.ts +3 -3
  127. package/dist/esm/sync-labels/initCommand.js +15 -15
  128. package/dist/esm/sync-labels/presets.js +3 -3
  129. package/dist/esm/sync-labels/resolveLabels.js +13 -13
  130. package/dist/esm/sync-labels/templates.d.ts +2 -2
  131. package/dist/esm/sync-labels/templates.js +1 -1
  132. package/dist/esm/tagCommand.js +4 -4
  133. package/dist/esm/topologicalSort.d.ts +2 -2
  134. package/dist/esm/topologicalSort.js +7 -7
  135. package/dist/esm/types.d.ts +3 -3
  136. package/dist/esm/validateAllChangelogOverrides.js +9 -9
  137. package/dist/esm/validateConfig.d.ts +1 -1
  138. package/dist/esm/validateConfig.js +60 -50
  139. package/dist/esm/validateOverridesCommand.js +2 -2
  140. package/docs/changelogs.md +5 -5
  141. package/docs/configuration.md +16 -15
  142. package/docs/publishing.md +4 -0
  143. package/docs/releasing.md +6 -0
  144. package/docs/work-types.md +1 -1
  145. package/package.json +2 -2
  146. package/presets/labels/common.yaml +3 -0
@@ -1,4 +1,97 @@
1
1
  [
2
+ {
3
+ "version": "16.1.0",
4
+ "date": "2026-11-03",
5
+ "sections": [
6
+ {
7
+ "title": "🎉 Features",
8
+ "audience": "all",
9
+ "items": [
10
+ {
11
+ "description": "Makes `prepare` and `publish` refuse a release, dry runs included, when a tarball packed from a clean checkout of HEAD lacks a file named by the package's `main`, `types`, `bin`, or `exports`, which keeps a package with missing build output from reaching the registry. (#1147)",
12
+ "hash": "c730654140e89d52ac7430987497d8b954fa7f00",
13
+ "entry": 1
14
+ },
15
+ {
16
+ "description": "Adds the `devExportConditions` config key, which lists the export conditions that resolve only inside the repo and whose targets the tarball check does not require, in addition to `source`. (#1147)",
17
+ "hash": "c730654140e89d52ac7430987497d8b954fa7f00",
18
+ "entry": 2
19
+ },
20
+ {
21
+ "description": "Adds a `content` label to the `common` label preset, for consumer-facing material such as copy, images, or displayed data rather than functionality. (#1150)",
22
+ "hash": "8c7b990041e5744c26547fe0ce8c9305f0f7c6f4",
23
+ "entry": 1
24
+ }
25
+ ]
26
+ }
27
+ ]
28
+ },
29
+ {
30
+ "version": "16.0.0",
31
+ "date": "2026-10-28",
32
+ "sections": [
33
+ {
34
+ "title": "🎉 Features",
35
+ "audience": "all",
36
+ "items": [
37
+ {
38
+ "description": "Renames the `changelogMarkdown.keepVersions` config key to `changelogMarkdown.keptVersionCount` and makes config validation reject the old key with an error that names the replacement. (#1134)",
39
+ "breaking": true,
40
+ "migration": "Rename `changelogMarkdown.keepVersions` to `changelogMarkdown.keptVersionCount` in `.config/release-kit.config.ts`, keeping the same value.",
41
+ "hash": "3d44603845a461295a5a62eee60cf151b5291f1a",
42
+ "entry": 1
43
+ }
44
+ ]
45
+ },
46
+ {
47
+ "title": "♻️ Refactoring",
48
+ "audience": "dev",
49
+ "items": [
50
+ {
51
+ "description": "Renames the boolean options, fields, and parameters in `release-kit` to take an `is`, `has`, `does`, or `should` prefix, such as `dryRun` to `isDryRun` and `exists` to `doesExist`, and leaves every CLI flag spelling unchanged. (#1132)",
52
+ "hash": "dcb78f0657c580eb7c7be94859e4185ae9f3d3be",
53
+ "entry": 1
54
+ },
55
+ {
56
+ "description": "Renames the values and type fields declared in `release-kit`'s source files from the first alphabetically through `prepareTarget.ts` so that each name ends with its kind, such as `parsedJson` for `parsed` and `unreleasedReading` for the history field `unreleased`, without changing behavior. (#1133)",
57
+ "hash": "eae8b6458dca1927393d64626d4731fc7c9f9df1",
58
+ "entry": 1
59
+ },
60
+ {
61
+ "description": "Renames values and type fields across release-kit's source so that each name ends with its kind, such as `applied` to `appliedWrites` and `TargetPrepareOptions.only` to `onlyTargetIds`. (#1134)",
62
+ "hash": "3d44603845a461295a5a62eee60cf151b5291f1a",
63
+ "entry": 2
64
+ },
65
+ {
66
+ "description": "Renames `release-kit`'s source functions and test helpers so that each name starts with a transitive verb that states its action, for example `releasePrepareTargets` to `prepareReleaseTargets` (with its module and test files), `publishWorkflow` to `renderPublishWorkflow`, and `toChangelogEntries` to `convertToChangelogEntries`, without changing the exports of `.` or `./config`. (#1137)",
67
+ "hash": "a84d9ce0652754a417b85c3f9cd14a6e400c7b51",
68
+ "entry": 1
69
+ },
70
+ {
71
+ "description": "Renames identifiers in `release-kit` so that each measurement ends with its unit, such as `timestamp` becoming `timestampSec` on `ReleaseWindow` and `SUBJECT_COLUMN_BUDGET` becoming `SUBJECT_BUDGET_COLUMNS`, and so that obscure abbreviations are spelled out, such as `pkg` becoming `packageJson` and `dep` becoming `dependency`. (#1138)",
72
+ "hash": "f75938c0bfe9365a76b15aa6b4b9d195046c8c78",
73
+ "entry": 1
74
+ }
75
+ ]
76
+ },
77
+ {
78
+ "title": "🧪 Tests",
79
+ "audience": "dev",
80
+ "items": [
81
+ {
82
+ "description": "Renames the values declared in release-kit's `src/__tests__` files, from the first one alphabetically through `prepareCommand.unit.test.ts`, so that each name ends with its kind, such as `underlying` to `underlyingError` and `unreleased` to `unreleasedReading`. (#1135)",
83
+ "hash": "b98d26e24e86835ce6edc78f53a964bcc84472fb",
84
+ "entry": 1
85
+ },
86
+ {
87
+ "description": "Suffixes the values declared in release-kit's remaining tests with their kind, such as `known` to `knownFlags`, `silent` to `silencedConsole`, and `actual` to `actualModule`, in the `src/__tests__` files after `prepareCommand.unit.test.ts` and the tests under `src/init/__tests__` and `src/sync-labels/__tests__`. (#1136)",
88
+ "hash": "1e947eb5c2d168ffd3d1cf9cf8b0b4e720eaf991",
89
+ "entry": 1
90
+ }
91
+ ]
92
+ }
93
+ ]
94
+ },
2
95
  {
3
96
  "version": "15.0.3",
4
97
  "date": "2026-10-27",
@@ -24,14 +24,14 @@ function isRecord(value) {
24
24
  }
25
25
 
26
26
  // src/init/parseJsonRecord.ts
27
- function parseJsonRecord(raw) {
28
- let parsed;
27
+ function parseJsonRecord(jsonText) {
28
+ let parsedJson;
29
29
  try {
30
- parsed = JSON.parse(raw);
30
+ parsedJson = JSON.parse(jsonText);
31
31
  } catch {
32
32
  return void 0;
33
33
  }
34
- return isRecord(parsed) ? parsed : void 0;
34
+ return isRecord(parsedJson) ? parsedJson : void 0;
35
35
  }
36
36
 
37
37
  // src/init/detectRepoType.ts
@@ -40,9 +40,9 @@ function detectRepoType() {
40
40
  return "monorepo";
41
41
  }
42
42
  if (existsSync("package.json")) {
43
- const raw = readFileSync("package.json", "utf8");
44
- const pkg = parseJsonRecord(raw);
45
- if (pkg !== void 0 && Array.isArray(pkg["workspaces"])) {
43
+ const manifestJson = readFileSync("package.json", "utf8");
44
+ const packageJson = parseJsonRecord(manifestJson);
45
+ if (packageJson !== void 0 && Array.isArray(packageJson["workspaces"])) {
46
46
  return "monorepo";
47
47
  }
48
48
  }
@@ -67,9 +67,9 @@ function packsPath(filesField, path) {
67
67
  var GLOB_TOKEN_PATTERN = /\*\*\/|\*\*|\*|\?|[^*?]+/g;
68
68
  function compileEntry(entry) {
69
69
  if (entry.startsWith("!")) return void 0;
70
- const normalized = entry.replace(/^\.?\//, "").replace(/\/+$/, "");
71
- if (normalized === "") return void 0;
72
- return new RegExp(`^${translateGlob(normalized)}$`);
70
+ const normalizedEntry = entry.replace(/^\.?\//, "").replace(/\/+$/, "");
71
+ if (normalizedEntry === "") return void 0;
72
+ return new RegExp(`^${translateGlob(normalizedEntry)}$`);
73
73
  }
74
74
  function isStringArray(value) {
75
75
  return Array.isArray(value) && value.every((entry) => typeof entry === "string");
@@ -105,7 +105,7 @@ function translateGlob(pattern) {
105
105
  function changelogArchivesAreEnabled() {
106
106
  const content = readFile(".config/release-kit.config.ts");
107
107
  if (content === void 0) return false;
108
- return /changelogMarkdown\s*:\s*\{[^}]*keepVersions\s*:/.test(content);
108
+ return /changelogMarkdown\s*:\s*\{[^}]*keptVersionCount\s*:/.test(content);
109
109
  }
110
110
  function changelogJsonIsEnabled() {
111
111
  const content = readFile(".config/release-kit.config.ts");
@@ -223,7 +223,7 @@ function stripYamlQuotes(value) {
223
223
  }
224
224
 
225
225
  // .readyup/lib/workflow-hashes.ts
226
- var COMMON_PRESET_HASH = "1d3e2df1ccd7deea4707fa07a4ae171d761344b65c1a311467fc0f66b70ee646";
226
+ var COMMON_PRESET_HASH = "5c6d7efcc8744d9d30720d24e345b04e03f2b28677ad0c6782020d91731b8aa4";
227
227
  var SYNC_LABELS_WORKFLOW_HASH = "0a88829d154c0de7cdd4246fede99e9311a89ab4199034dbe601663d641ab1e4";
228
228
  var RELEASE_WORKFLOW_HASH_MONOREPO = "f63f4cabb57f94ee6550f17a9874c7a346d46554595ddbcd46fdd3208d613437";
229
229
  var RELEASE_WORKFLOW_HASH_SINGLE = "d26dd4ca186632b388ea46bd885721124c03d19918e511baa40821d2d760da69";
@@ -234,7 +234,7 @@ var CREATE_GITHUB_RELEASE_WORKFLOW_HASH_SINGLE = "d6de961de913c67b08dfa7e07f7349
234
234
 
235
235
  // .readyup/kits/default.ts
236
236
  function getMinVersion() {
237
- const picked = { "version": "15.0.3" };
237
+ const picked = { "version": "16.1.0" };
238
238
  if (typeof picked["version"] !== "string") {
239
239
  throw new TypeError("release-kit/package.json: 'version' must be a string");
240
240
  }
@@ -405,7 +405,7 @@ var default_default = defineRdyKit({
405
405
  severity: "warn",
406
406
  skip: () => {
407
407
  if (!hasPublishablePackages()) return "no publishable packages";
408
- return !changelogArchivesAreEnabled() && "changelogMarkdown.keepVersions is not set";
408
+ return !changelogArchivesAreEnabled() && "changelogMarkdown.keptVersionCount is not set";
409
409
  },
410
410
  check: () => packagesShipChangelogArchives(),
411
411
  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'
@@ -60,16 +60,16 @@ function lookUpNpmPackage(packageName, registry) {
60
60
  return classifyNpmPackageLookup(runNpmJson(args));
61
61
  }
62
62
  function readNpmError(stdout) {
63
- let parsed;
63
+ let parsedJson;
64
64
  try {
65
- parsed = JSON.parse(stdout);
65
+ parsedJson = JSON.parse(stdout);
66
66
  } catch {
67
67
  return void 0;
68
68
  }
69
- if (!isRecord(parsed) || !isRecord(parsed["error"]) || typeof parsed["error"]["code"] !== "string") {
69
+ if (!isRecord(parsedJson) || !isRecord(parsedJson["error"]) || typeof parsedJson["error"]["code"] !== "string") {
70
70
  return void 0;
71
71
  }
72
- const { code, summary } = parsed["error"];
72
+ const { code, summary } = parsedJson["error"];
73
73
  return { code, summary: typeof summary === "string" ? summary : "" };
74
74
  }
75
75
  function runNpmJson(args) {
@@ -7,7 +7,7 @@
7
7
  "checklist": "release-kit",
8
8
  "checks": [
9
9
  "@williamthorsen/release-kit in devDependencies",
10
- "@williamthorsen/release-kit >= 15.0.3",
10
+ "@williamthorsen/release-kit >= 16.1.0",
11
11
  "release.yaml workflow exists",
12
12
  "release.yaml matches template",
13
13
  "release.yaml does not reference deprecated tag ref",
@@ -43,12 +43,12 @@
43
43
  "esbuildVersion": "0.28.2",
44
44
  "inputs": [
45
45
  {
46
- "hash": "ad3666b8",
46
+ "hash": "56566d0b",
47
47
  "kind": "module",
48
48
  "path": "kits/default.ts"
49
49
  },
50
50
  {
51
- "hash": "45fc235f",
51
+ "hash": "dafe3ffb",
52
52
  "kind": "module",
53
53
  "path": "lib/changelog-checks.ts"
54
54
  },
@@ -58,12 +58,12 @@
58
58
  "path": "lib/config-checks.ts"
59
59
  },
60
60
  {
61
- "hash": "13f15dfe",
61
+ "hash": "f652dae3",
62
62
  "kind": "module",
63
63
  "path": "lib/workflow-hashes.ts"
64
64
  },
65
65
  {
66
- "hash": "5c98064e",
66
+ "hash": "b44f7148",
67
67
  "kind": "inline",
68
68
  "path": "../package.json",
69
69
  "paths": [
@@ -71,22 +71,22 @@
71
71
  ]
72
72
  },
73
73
  {
74
- "hash": "40404aa0",
74
+ "hash": "694186f4",
75
75
  "kind": "module",
76
76
  "path": "../src/changelogArchive.ts"
77
77
  },
78
78
  {
79
- "hash": "43bb0452",
79
+ "hash": "65fa2f33",
80
80
  "kind": "module",
81
81
  "path": "../src/init/detectRepoType.ts"
82
82
  },
83
83
  {
84
- "hash": "68354f97",
84
+ "hash": "9ca0f6bf",
85
85
  "kind": "module",
86
86
  "path": "../src/init/parseJsonRecord.ts"
87
87
  },
88
88
  {
89
- "hash": "1c9cbce9",
89
+ "hash": "2210df36",
90
90
  "kind": "module",
91
91
  "path": "../src/packsPath.ts"
92
92
  },
@@ -100,8 +100,8 @@
100
100
  "path": "kits/default.js",
101
101
  "readyupVersion": "0.44.0",
102
102
  "source": "kits/default.ts",
103
- "sourceHash": "ad3666b8",
104
- "targetHash": "35d9bb56"
103
+ "sourceHash": "56566d0b",
104
+ "targetHash": "5f810bb7"
105
105
  },
106
106
  {
107
107
  "checklistChecks": [
@@ -145,7 +145,7 @@
145
145
  "path": "../src/init/hasProvenance.ts"
146
146
  },
147
147
  {
148
- "hash": "3e39b9be",
148
+ "hash": "bb8746b3",
149
149
  "kind": "module",
150
150
  "path": "../src/npmRegistry.ts"
151
151
  },
@@ -160,7 +160,7 @@
160
160
  "readyupVersion": "0.44.0",
161
161
  "source": "kits/npm-auto-publish.ts",
162
162
  "sourceHash": "682fdd4a",
163
- "targetHash": "fac6bc8b"
163
+ "targetHash": "84f31b6d"
164
164
  }
165
165
  ]
166
166
  }
package/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## 16.1.0 — 2026-11-03
6
+
7
+ ### 🎉 Features
8
+
9
+ - Makes `prepare` and `publish` refuse a release, dry runs included, when a tarball packed from a clean checkout of HEAD lacks a file named by the package's `main`, `types`, `bin`, or `exports`, which keeps a package with missing build output from reaching the registry. (#1147)
10
+ - Adds the `devExportConditions` config key, which lists the export conditions that resolve only inside the repo and whose targets the tarball check does not require, in addition to `source`. (#1147)
11
+ - Adds a `content` label to the `common` label preset, for consumer-facing material such as copy, images, or displayed data rather than functionality. (#1150)
12
+
13
+ ## 16.0.0 — 2026-10-28
14
+
15
+ ### 🎉 Features
16
+
17
+ - 🚨 **Breaking:** Renames the `changelogMarkdown.keepVersions` config key to `changelogMarkdown.keptVersionCount` and makes config validation reject the old key with an error that names the replacement. (#1134)
18
+
19
+ ### ♻️ Refactoring
20
+
21
+ - Renames the boolean options, fields, and parameters in `release-kit` to take an `is`, `has`, `does`, or `should` prefix, such as `dryRun` to `isDryRun` and `exists` to `doesExist`, and leaves every CLI flag spelling unchanged. (#1132)
22
+ - Renames the values and type fields declared in `release-kit`'s source files from the first alphabetically through `prepareTarget.ts` so that each name ends with its kind, such as `parsedJson` for `parsed` and `unreleasedReading` for the history field `unreleased`, without changing behavior. (#1133)
23
+ - Renames values and type fields across release-kit's source so that each name ends with its kind, such as `applied` to `appliedWrites` and `TargetPrepareOptions.only` to `onlyTargetIds`. (#1134)
24
+ - Renames `release-kit`'s source functions and test helpers so that each name starts with a transitive verb that states its action, for example `releasePrepareTargets` to `prepareReleaseTargets` (with its module and test files), `publishWorkflow` to `renderPublishWorkflow`, and `toChangelogEntries` to `convertToChangelogEntries`, without changing the exports of `.` or `./config`. (#1137)
25
+ - Renames identifiers in `release-kit` so that each measurement ends with its unit, such as `timestamp` becoming `timestampSec` on `ReleaseWindow` and `SUBJECT_COLUMN_BUDGET` becoming `SUBJECT_BUDGET_COLUMNS`, and so that obscure abbreviations are spelled out, such as `pkg` becoming `packageJson` and `dep` becoming `dependency`. (#1138)
26
+
27
+ ### 🧪 Tests
28
+
29
+ - Renames the values declared in release-kit's `src/__tests__` files, from the first one alphabetically through `prepareCommand.unit.test.ts`, so that each name ends with its kind, such as `underlying` to `underlyingError` and `unreleased` to `unreleasedReading`. (#1135)
30
+ - Suffixes the values declared in release-kit's remaining tests with their kind, such as `known` to `knownFlags`, `silent` to `silencedConsole`, and `actual` to `actualModule`, in the `src/__tests__` files after `prepareCommand.unit.test.ts` and the tests under `src/init/__tests__` and `src/sync-labels/__tests__`. (#1136)
31
+
5
32
  ## 15.0.3 — 2026-10-27
6
33
 
7
34
  ### ♻️ Refactoring
package/README.md CHANGED
@@ -6,7 +6,15 @@ 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 --><!-- /section:release-notes -->
9
+ <!-- section:release-notes -->
10
+ ## Release notes — v16.1.0 (2026-11-03)
11
+
12
+ ### 🎉 Features
13
+
14
+ - Makes `prepare` and `publish` refuse a release, dry runs included, when a tarball packed from a clean checkout of HEAD lacks a file named by the package's `main`, `types`, `bin`, or `exports`, which keeps a package with missing build output from reaching the registry. (#1147)
15
+ - Adds the `devExportConditions` config key, which lists the export conditions that resolve only inside the repo and whose targets the tarball check does not require, in addition to `source`. (#1147)
16
+ - Adds a `content` label to the `common` label preset, for consumer-facing material such as copy, images, or displayed data rather than functionality. (#1150)
17
+ <!-- /section:release-notes -->
10
18
 
11
19
  ## Installation
12
20
 
@@ -65,8 +73,9 @@ Every command other than `--version` and `--help` first locates the repo root an
65
73
  1. **Workspace discovery**: Reads `pnpm-workspace.yaml` and resolves its `packages` patterns to find workspace directories, applying pnpm's semantics: A `!`-prefixed entry excludes what it matches, wherever it appears in the list. Each directory containing a `package.json` becomes a workspace. The repo is treated as a single-package project when it doesn't contain a workspace file, and when the workspace file doesn't declare a `packages` list: a file kept for `catalog:` or `overrides:` alone, which pnpm likewise resolves to the root package. A workspace file that declares patterns that don't resolve to any package is an error naming the condition that emptied it.
66
74
  2. **Config loading**: Loads `.config/release-kit.config.ts` (if present) from the repo root, or the file named by `--config` relative to the invocation directory, and merges it with discovered defaults.
67
75
  3. **Commit analysis**: For each workspace, reads the history once, builds the changelog items of the commits since the last version tag, and takes the bump as the highest level that those items call for.
68
- 4. **Version bump + changelog**: Bumps `package.json` versions, builds structured `ChangelogEntry[]` from the commits between each pair of release tags, applies any [editorial overrides](docs/editorial-overrides.md) from per-scope `.meta/changelog-overrides.json` files, and renders both `CHANGELOG.md` and `.meta/changelog.json` from that single source, so the two always agree.
69
- 5. **Release tags file**: Writes computed tags to `tmp/.release-tags` for the release workflow to read when tagging and pushing.
76
+ 4. **Tarball check**: Packs each publishable package that the release includes from a clean checkout of HEAD, and refuses the release when a tarball lacks a file that the package's manifest names; see [Packages whose tarball would lack a manifest file](docs/releasing.md#packages-whose-tarball-would-lack-a-manifest-file).
77
+ 5. **Version bump + changelog**: Bumps `package.json` versions, builds structured `ChangelogEntry[]` from the commits between each pair of release tags, applies any [editorial overrides](docs/editorial-overrides.md) from per-scope `.meta/changelog-overrides.json` files, and renders both `CHANGELOG.md` and `.meta/changelog.json` from that single source, so the two always agree.
78
+ 6. **Release tags file**: Writes computed tags to `tmp/.release-tags` for the release workflow to read when tagging and pushing.
70
79
 
71
80
  [Changelogs](docs/changelogs.md) covers what a published tarball includes, the `changelog.json` item schema, and which commits appear in a changelog.
72
81
 
@@ -55,10 +55,10 @@ export function resolveOverrideKey(overrideKey, positionsByHash) {
55
55
  if (hashes.length > 1) {
56
56
  return { kind: 'ambiguous', hashes };
57
57
  }
58
- const available = [...(positionsByHash.get(hash) ?? [])];
58
+ const availablePositions = [...(positionsByHash.get(hash) ?? [])];
59
59
  const positions = entry === undefined
60
- ? available.toSorted((left, right) => (left ?? 0) - (right ?? 0))
61
- : available.filter((position) => position === entry);
60
+ ? availablePositions.toSorted((left, right) => (left ?? 0) - (right ?? 0))
61
+ : availablePositions.filter((position) => position === entry);
62
62
  return positions.length === 0 ? { kind: 'none' } : { kind: 'items', hash, positions };
63
63
  }
64
64
  function applyOverridesToItems(items, itemIdToOverride) {
@@ -133,10 +133,10 @@ function resolveOverrideKeys(positionsByHash, overrides) {
133
133
  }
134
134
  const { hash, positions } = resolution;
135
135
  if (parseOverrideKey(overrideKey).entry === undefined && positions.length > 1) {
136
- const fieldsSet = ['description', 'body'].filter((field) => override[field] !== undefined);
137
- if (fieldsSet.length > 0) {
136
+ const setFields = ['description', 'body'].filter((field) => override[field] !== undefined);
137
+ if (setFields.length > 0) {
138
138
  const ordinalKeys = positions.map((position) => `${overrideKey}:${position}`).join(', ');
139
- errors.push(`Override key '${overrideKey}' sets ${fieldsSet.join(' and ')} on a commit with several items; use ${ordinalKeys}`);
139
+ errors.push(`Override key '${overrideKey}' sets ${setFields.join(' and ')} on a commit with several items; use ${ordinalKeys}`);
140
140
  continue;
141
141
  }
142
142
  }
@@ -4,8 +4,8 @@ import { isPublishableManifest } from "./isPublishableManifest.js";
4
4
  import { lookUpNpmPackage } from "./npmRegistry.js";
5
5
  import { isRecord } from "./typeGuards.js";
6
6
  export function assertReleasesOnNpm(workspacePaths) {
7
- const unpublished = [];
8
- const unverifiable = [];
7
+ const unpublishedCandidates = [];
8
+ const unverifiableCandidates = [];
9
9
  for (const workspacePath of workspacePaths) {
10
10
  const candidate = buildNpmCandidate(workspacePath);
11
11
  if (candidate === undefined) {
@@ -13,16 +13,16 @@ export function assertReleasesOnNpm(workspacePaths) {
13
13
  }
14
14
  const lookup = lookUpNpmPackage(candidate.name, candidate.registry);
15
15
  if (lookup.status === 'unpublished') {
16
- unpublished.push(candidate);
16
+ unpublishedCandidates.push(candidate);
17
17
  }
18
18
  else if (lookup.status === 'unverifiable') {
19
- unverifiable.push({ candidate, detail: lookup.detail });
19
+ unverifiableCandidates.push({ candidate, detail: lookup.detail });
20
20
  }
21
21
  }
22
- if (unpublished.length === 0 && unverifiable.length === 0) {
22
+ if (unpublishedCandidates.length === 0 && unverifiableCandidates.length === 0) {
23
23
  return;
24
24
  }
25
- throw new Error(describeRefusal(unpublished, unverifiable));
25
+ throw new Error(describeRefusal(unpublishedCandidates, unverifiableCandidates));
26
26
  }
27
27
  function buildNpmCandidate(workspacePath) {
28
28
  const manifest = JSON.parse(readFileSync(join(workspacePath, 'package.json'), 'utf8'));
@@ -37,19 +37,19 @@ function buildNpmCandidate(workspacePath) {
37
37
  ...(typeof registry === 'string' && { registry }),
38
38
  };
39
39
  }
40
- function describeRefusal(unpublished, unverifiable) {
40
+ function describeRefusal(unpublishedCandidates, unverifiableCandidates) {
41
41
  const sections = ['Cannot prepare the release: npm must already have every package that the release publishes.'];
42
- if (unpublished.length > 0) {
42
+ if (unpublishedCandidates.length > 0) {
43
43
  sections.push([
44
44
  'Not on npm yet:',
45
- ...unpublished.map(({ name, workspacePath }) => ` - ${name}: run "npm publish --access public" from ${workspacePath}`),
45
+ ...unpublishedCandidates.map(({ name, workspacePath }) => ` - ${name}: run "npm publish --access public" from ${workspacePath}`),
46
46
  'Then register each one as a trusted publisher; "rdy run --from npm:@williamthorsen/release-kit npm-auto-publish" prints the command.',
47
47
  ].join('\n'));
48
48
  }
49
- if (unverifiable.length > 0) {
49
+ if (unverifiableCandidates.length > 0) {
50
50
  sections.push([
51
51
  'Could not confirm on npm:',
52
- ...unverifiable.map(({ candidate, detail }) => ` - ${candidate.name}: ${detail}`),
52
+ ...unverifiableCandidates.map(({ candidate, detail }) => ` - ${candidate.name}: ${detail}`),
53
53
  ].join('\n'));
54
54
  }
55
55
  return sections.join('\n\n');
@@ -4,10 +4,10 @@ import { readChangelogEntries } from "./changelogJsonUtils.js";
4
4
  import { readCurrentVersion } from "./planVersionBump.js";
5
5
  import { readChangelogSections } from "./readChangelogSections.js";
6
6
  export function assertTaggedBaseline(findings) {
7
- const untagged = findings.filter((finding) => finding !== undefined);
8
- if (untagged.length === 0)
7
+ const untaggedFindings = findings.filter((finding) => finding !== undefined);
8
+ if (untaggedFindings.length === 0)
9
9
  return;
10
- const lines = untagged.map(({ label, version, tag }) => ` - ${label}: ${version} (create tag ${tag})`);
10
+ const lines = untaggedFindings.map(({ label, version, tag }) => ` - ${label}: ${version} (create tag ${tag})`);
11
11
  throw new Error([
12
12
  'The current version is recorded in the changelog, but its tag is not the previous tag reachable from HEAD:',
13
13
  ...lines,
@@ -31,9 +31,9 @@ export function findUntaggedBaseline(target, config) {
31
31
  function readRecordedVersions(changelogPaths, config) {
32
32
  const versions = new Set();
33
33
  for (const changelogPath of changelogPaths) {
34
- const { versioned } = readChangelogSections(join(changelogPath, 'CHANGELOG.md'));
34
+ const { versionedSections } = readChangelogSections(join(changelogPath, 'CHANGELOG.md'));
35
35
  const entries = readChangelogEntries(resolveChangelogJsonPath(config, changelogPath)) ?? [];
36
- for (const { version } of [...versioned, ...entries]) {
36
+ for (const { version } of [...versionedSections, ...entries]) {
37
37
  versions.add(version);
38
38
  }
39
39
  }
@@ -0,0 +1,9 @@
1
+ import { packFromCleanCheckout } from './packFromCleanCheckout.js';
2
+ /**
3
+ * Throws when the tarball of a publishable package among the given workspaces would lack a file that its manifest
4
+ * names, or when the packages cannot be packed from a clean checkout of HEAD.
5
+ *
6
+ * Each workspace path is repo-relative and is read from the current directory. A private package is not packed, and
7
+ * an empty list doesn't clone anything.
8
+ */
9
+ export declare function assertTarballsComplete(workspacePaths: readonly string[], devExportConditions: readonly string[], pack?: typeof packFromCleanCheckout): void;
@@ -0,0 +1,52 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { describeError } from '@williamthorsen/toolbelt.errors';
4
+ import { detectPackageManager } from "./detectPackageManager.js";
5
+ import { findMissingManifestPaths } from "./findMissingManifestPaths.js";
6
+ import { isPublishableManifest } from "./isPublishableManifest.js";
7
+ import { packFromCleanCheckout } from "./packFromCleanCheckout.js";
8
+ import { isRecord } from "./typeGuards.js";
9
+ export function assertTarballsComplete(workspacePaths, devExportConditions, pack = packFromCleanCheckout) {
10
+ const candidates = workspacePaths
11
+ .map((workspacePath) => ({ workspacePath, manifest: readManifest(workspacePath) }))
12
+ .filter(({ manifest }) => isPublishableManifest(manifest));
13
+ if (candidates.length === 0) {
14
+ return;
15
+ }
16
+ process.stderr.write(`Checking the tarballs of ${candidates.length} ${candidates.length === 1 ? 'package' : 'packages'} from a clean checkout of HEAD...\n`);
17
+ let filesByWorkspace;
18
+ try {
19
+ filesByWorkspace = pack(candidates.map(({ workspacePath }) => workspacePath), detectPackageManager());
20
+ }
21
+ catch (error) {
22
+ throw new Error(`Cannot release: The packages could not be packed from a clean checkout of HEAD.\n${describeError(error)}`, { cause: error });
23
+ }
24
+ const incompletePackages = candidates
25
+ .map(({ workspacePath, manifest }) => ({
26
+ workspacePath,
27
+ manifest,
28
+ missingPaths: findMissingManifestPaths(manifest, filesByWorkspace.get(workspacePath) ?? [], devExportConditions),
29
+ }))
30
+ .filter(({ missingPaths }) => missingPaths.length > 0);
31
+ if (incompletePackages.length === 0) {
32
+ return;
33
+ }
34
+ throw new Error(describeRefusal(incompletePackages));
35
+ }
36
+ function describeRefusal(incompletePackages) {
37
+ const sections = ['Cannot release: The tarball of every package must contain each file that its manifest names.'];
38
+ for (const { workspacePath, manifest, missingPaths } of incompletePackages) {
39
+ const name = typeof manifest['name'] === 'string' ? manifest['name'] : workspacePath;
40
+ sections.push([
41
+ `${name} (${workspacePath}) lacks:`,
42
+ ...missingPaths.map(({ location, path }) => ` - ${path} (${location})`),
43
+ ].join('\n'));
44
+ }
45
+ sections.push('Build these files in the package\'s publish lifecycle, such as in a "prepack" script. ' +
46
+ 'If an export condition is for development only, list it in the "devExportConditions" config key.');
47
+ return sections.join('\n\n');
48
+ }
49
+ function readManifest(workspacePath) {
50
+ const manifest = JSON.parse(readFileSync(join(workspacePath, 'package.json'), 'utf8'));
51
+ return isRecord(manifest) ? manifest : {};
52
+ }
@@ -127,12 +127,12 @@ if (command === 'init') {
127
127
  }
128
128
  enterRepoRootOrExit();
129
129
  const initFlagSchema = {
130
- dryRun: { long: '--dry-run', type: 'boolean' },
131
- force: { long: '--force', type: 'boolean' },
132
- withConfig: { long: '--with-config', type: 'boolean' },
130
+ isDryRun: { long: '--dry-run', type: 'boolean' },
131
+ shouldForce: { long: '--force', type: 'boolean' },
132
+ shouldScaffoldConfig: { long: '--with-config', type: 'boolean' },
133
133
  };
134
- const { dryRun, force, withConfig } = parseArgsOrExit(flags, initFlagSchema).flags;
135
- const exitCode = initCommand({ dryRun, force, styles, withConfig });
134
+ const { isDryRun, shouldForce, shouldScaffoldConfig } = parseArgsOrExit(flags, initFlagSchema).flags;
135
+ const exitCode = initCommand({ isDryRun, shouldForce, styles, shouldScaffoldConfig });
136
136
  process.exit(exitCode);
137
137
  }
138
138
  if (command === 'update-templates') {
@@ -142,10 +142,10 @@ if (command === 'update-templates') {
142
142
  }
143
143
  enterRepoRootOrExit();
144
144
  const updateTemplatesFlagSchema = {
145
- dryRun: { long: '--dry-run', type: 'boolean' },
145
+ isDryRun: { long: '--dry-run', type: 'boolean' },
146
146
  };
147
- const { dryRun } = parseArgsOrExit(flags, updateTemplatesFlagSchema).flags;
148
- const exitCode = updateTemplatesCommand({ dryRun, styles });
147
+ const { isDryRun } = parseArgsOrExit(flags, updateTemplatesFlagSchema).flags;
148
+ const exitCode = updateTemplatesCommand({ isDryRun, styles });
149
149
  process.exit(exitCode);
150
150
  }
151
151
  if (command === 'sync-labels') {
@@ -163,15 +163,15 @@ if (command === 'sync-labels') {
163
163
  const { invocationDir } = enterRepoRootOrExit();
164
164
  const syncLabelsInitFlagSchema = {
165
165
  ...configFlagSchema,
166
- dryRun: { long: '--dry-run', type: 'boolean' },
167
- force: { long: '--force', type: 'boolean' },
166
+ isDryRun: { long: '--dry-run', type: 'boolean' },
167
+ shouldForce: { long: '--force', type: 'boolean' },
168
168
  };
169
- const { config, dryRun, force } = parseArgsOrExit(subflags, syncLabelsInitFlagSchema).flags;
169
+ const { config, isDryRun, shouldForce } = parseArgsOrExit(subflags, syncLabelsInitFlagSchema).flags;
170
170
  const configPath = resolveConfigFlag(config, invocationDir);
171
171
  const exitCode = await syncLabelsInitCommand({
172
172
  ...(configPath !== undefined && { configPath }),
173
- dryRun,
174
- force,
173
+ isDryRun,
174
+ shouldForce,
175
175
  styles,
176
176
  });
177
177
  process.exit(exitCode);
@@ -184,11 +184,11 @@ if (command === 'sync-labels') {
184
184
  const { invocationDir } = enterRepoRootOrExit();
185
185
  const generateFlagSchema = {
186
186
  ...configFlagSchema,
187
- check: { long: '--check', type: 'boolean' },
187
+ shouldCheck: { long: '--check', type: 'boolean' },
188
188
  };
189
- const { check, config } = parseArgsOrExit(subflags, generateFlagSchema).flags;
189
+ const { shouldCheck, config } = parseArgsOrExit(subflags, generateFlagSchema).flags;
190
190
  const configPath = resolveConfigFlag(config, invocationDir);
191
- const exitCode = await generateCommand({ check, ...(configPath !== undefined && { configPath }), styles });
191
+ const exitCode = await generateCommand({ shouldCheck, ...(configPath !== undefined && { configPath }), styles });
192
192
  process.exit(exitCode);
193
193
  }
194
194
  if (subcommand === 'sync') {