@williamthorsen/release-kit 12.0.0 → 13.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 (168) hide show
  1. package/.meta/changelog.json +229 -0
  2. package/.readyup/kits/default.js +18 -17
  3. package/.readyup/kits/npm-auto-publish.js +24 -23
  4. package/.readyup/manifest.json +16 -11
  5. package/CHANGELOG.md +64 -0
  6. package/README.md +27 -51
  7. package/bin/release-kit.js +1 -1
  8. package/dist/esm/assertCleanWorkingTree.d.ts +1 -5
  9. package/dist/esm/assertTaggedBaseline.d.ts +29 -0
  10. package/dist/esm/assertTaggedBaseline.js +41 -0
  11. package/dist/esm/attachChangelogDiagnostics.d.ts +1 -1
  12. package/dist/esm/attachChangelogDiagnostics.js +3 -0
  13. package/dist/esm/bin/release-kit.js +85 -22
  14. package/dist/esm/buildChangelogEntries.d.ts +23 -13
  15. package/dist/esm/buildChangelogEntries.js +36 -17
  16. package/dist/esm/buildDependencyGraph.d.ts +6 -6
  17. package/dist/esm/buildEmptyReleaseEntry.d.ts +1 -8
  18. package/dist/esm/buildReleaseSummary.d.ts +2 -8
  19. package/dist/esm/buildSyntheticChangelogEntry.d.ts +3 -5
  20. package/dist/esm/buildSyntheticChangelogEntry.js +2 -2
  21. package/dist/esm/bumpVersion.d.ts +1 -8
  22. package/dist/esm/changelogJsonFile.d.ts +5 -7
  23. package/dist/esm/changelogJsonFile.js +2 -17
  24. package/dist/esm/changelogJsonUtils.d.ts +7 -10
  25. package/dist/esm/changelogOverrides.d.ts +59 -98
  26. package/dist/esm/classifyChangelogCommit.d.ts +7 -9
  27. package/dist/esm/classifyChangelogCommit.js +3 -2
  28. package/dist/esm/commitCommand.d.ts +2 -4
  29. package/dist/esm/compareVersions.d.ts +10 -4
  30. package/dist/esm/compareVersions.js +16 -0
  31. package/dist/esm/configFlagSchema.d.ts +1 -1
  32. package/dist/esm/createGithubRelease.d.ts +7 -19
  33. package/dist/esm/createGithubReleaseCommand.d.ts +5 -5
  34. package/dist/esm/createGithubReleaseCommand.js +9 -9
  35. package/dist/esm/createTags.d.ts +2 -3
  36. package/dist/esm/decideRelease.d.ts +2 -6
  37. package/dist/esm/defaults.d.ts +5 -24
  38. package/dist/esm/defaults.js +4 -5
  39. package/dist/esm/defineConfig.d.ts +1 -1
  40. package/dist/esm/deleteFileIfExists.d.ts +1 -1
  41. package/dist/esm/deriveReleaseNotesConfig.d.ts +20 -0
  42. package/dist/esm/deriveReleaseNotesConfig.js +21 -0
  43. package/dist/esm/deriveWorkspaceConfig.d.ts +3 -5
  44. package/dist/esm/detectPackageManager.d.ts +2 -4
  45. package/dist/esm/detectUndeclaredTagPrefixes.d.ts +4 -6
  46. package/dist/esm/detectUndeclaredTagPrefixes.js +1 -1
  47. package/dist/esm/determineBumpType.d.ts +1 -1
  48. package/dist/esm/discoverWorkspaces.d.ts +14 -15
  49. package/dist/esm/discoverWorkspaces.js +12 -12
  50. package/dist/esm/enterRepoRoot.d.ts +12 -0
  51. package/dist/esm/enterRepoRoot.js +16 -0
  52. package/dist/esm/enumerateReleaseWindows.d.ts +8 -8
  53. package/dist/esm/extractMigration.d.ts +6 -8
  54. package/dist/esm/findUnroutedEntryScopes.d.ts +16 -0
  55. package/dist/esm/findUnroutedEntryScopes.js +57 -0
  56. package/dist/esm/formatExcludedSkip.d.ts +3 -0
  57. package/dist/esm/formatExcludedSkip.js +3 -0
  58. package/dist/esm/formatPrivateSkip.d.ts +1 -6
  59. package/dist/esm/generateChangelogs.d.ts +10 -5
  60. package/dist/esm/glyphs.d.ts +1 -1
  61. package/dist/esm/help/prepareHelp.d.ts +3 -4
  62. package/dist/esm/help/prepareHelp.js +4 -3
  63. package/dist/esm/init/checks.d.ts +3 -3
  64. package/dist/esm/init/checks.js +4 -1
  65. package/dist/esm/init/detectRepoType.d.ts +2 -3
  66. package/dist/esm/init/hasProvenance.d.ts +2 -0
  67. package/dist/esm/init/hasProvenance.js +3 -0
  68. package/dist/esm/init/initCommand.d.ts +3 -6
  69. package/dist/esm/init/initCommand.js +10 -10
  70. package/dist/esm/init/parseJsonRecord.d.ts +1 -4
  71. package/dist/esm/init/scaffold.d.ts +1 -1
  72. package/dist/esm/init/templates.d.ts +9 -11
  73. package/dist/esm/init/templates.js +12 -11
  74. package/dist/esm/init/updateTemplatesCommand.d.ts +13 -0
  75. package/dist/esm/init/updateTemplatesCommand.js +64 -0
  76. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +12 -17
  77. package/dist/esm/injectSection.d.ts +3 -3
  78. package/dist/esm/loadConfig.d.ts +16 -14
  79. package/dist/esm/loadConfig.js +2 -2
  80. package/dist/esm/loadValidatedConfig.d.ts +16 -19
  81. package/dist/esm/loadValidatedConfig.js +6 -1
  82. package/dist/esm/packsPath.d.ts +9 -9
  83. package/dist/esm/parseChangeRecordBlock.d.ts +2 -2
  84. package/dist/esm/parseCommitMessage.d.ts +17 -32
  85. package/dist/esm/parseCommitMessage.js +28 -37
  86. package/dist/esm/parseRequestedTags.d.ts +3 -3
  87. package/dist/esm/planReleaseNotesPreviews.d.ts +6 -6
  88. package/dist/esm/planVersionBump.d.ts +5 -3
  89. package/dist/esm/planVersionBump.js +3 -0
  90. package/dist/esm/prepareCommand.d.ts +4 -7
  91. package/dist/esm/prepareCommand.js +13 -6
  92. package/dist/esm/previewTagPrefixes.d.ts +8 -10
  93. package/dist/esm/propagateBumps.d.ts +6 -7
  94. package/dist/esm/publish.d.ts +1 -1
  95. package/dist/esm/publishCommand.d.ts +4 -3
  96. package/dist/esm/publishCommand.js +8 -8
  97. package/dist/esm/pushCommand.d.ts +4 -3
  98. package/dist/esm/pushCommand.js +8 -2
  99. package/dist/esm/pushRelease.d.ts +2 -2
  100. package/dist/esm/readChangelogSections.d.ts +33 -0
  101. package/dist/esm/readChangelogSections.js +80 -0
  102. package/dist/esm/releaseFiles.d.ts +5 -7
  103. package/dist/esm/releasePlan.d.ts +11 -13
  104. package/dist/esm/releasePrepare.d.ts +23 -22
  105. package/dist/esm/releasePrepare.js +31 -11
  106. package/dist/esm/releasePrepareMono.d.ts +9 -7
  107. package/dist/esm/releasePrepareMono.js +85 -33
  108. package/dist/esm/releasePrepareProject.d.ts +19 -29
  109. package/dist/esm/releasePrepareProject.js +31 -12
  110. package/dist/esm/renderChangelogMarkdown.d.ts +10 -4
  111. package/dist/esm/renderChangelogMarkdown.js +27 -2
  112. package/dist/esm/renderReleaseNotes.d.ts +3 -11
  113. package/dist/esm/renderReleaseNotes.js +2 -6
  114. package/dist/esm/reportPrepare.d.ts +4 -5
  115. package/dist/esm/reportPrepare.js +33 -19
  116. package/dist/esm/resolveCommandTags.d.ts +15 -11
  117. package/dist/esm/resolveCommandTags.js +35 -5
  118. package/dist/esm/resolveConfigFlag.d.ts +7 -0
  119. package/dist/esm/resolveConfigFlag.js +4 -0
  120. package/dist/esm/resolveReleaseTags.d.ts +6 -8
  121. package/dist/esm/resolveReleaseTags.js +1 -1
  122. package/dist/esm/resolveStylesOrExit.d.ts +2 -2
  123. package/dist/esm/showTagPrefixesCommand.d.ts +7 -10
  124. package/dist/esm/showTagPrefixesCommand.js +4 -4
  125. package/dist/esm/stripEmojiPrefix.d.ts +1 -0
  126. package/dist/esm/stripScope.d.ts +2 -2
  127. package/dist/esm/stripScope.js +3 -3
  128. package/dist/esm/sync-labels/generateCommand.d.ts +11 -10
  129. package/dist/esm/sync-labels/generateCommand.js +17 -9
  130. package/dist/esm/sync-labels/initCommand.d.ts +6 -7
  131. package/dist/esm/sync-labels/initCommand.js +2 -2
  132. package/dist/esm/sync-labels/presets.d.ts +5 -4
  133. package/dist/esm/sync-labels/presets.js +9 -9
  134. package/dist/esm/sync-labels/resolveLabels.d.ts +7 -7
  135. package/dist/esm/sync-labels/resolveLabels.js +2 -1
  136. package/dist/esm/sync-labels/retiredConfig.d.ts +4 -5
  137. package/dist/esm/sync-labels/syncCommand.d.ts +1 -4
  138. package/dist/esm/sync-labels/templates.d.ts +3 -3
  139. package/dist/esm/sync-labels/templates.js +3 -3
  140. package/dist/esm/sync-labels/typeLabels.d.ts +14 -0
  141. package/dist/esm/sync-labels/typeLabels.js +38 -0
  142. package/dist/esm/sync-labels/types.d.ts +4 -2
  143. package/dist/esm/tagCommand.d.ts +1 -1
  144. package/dist/esm/typeGuards.d.ts +2 -2
  145. package/dist/esm/types.d.ts +122 -93
  146. package/dist/esm/types.js +2 -1
  147. package/dist/esm/validateConfig.d.ts +2 -11
  148. package/dist/esm/validateConfig.js +2 -2
  149. package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -12
  150. package/dist/esm/validateOverridesCommand.d.ts +17 -20
  151. package/dist/esm/validateOverridesCommand.js +1 -1
  152. package/docs/api.md +6 -6
  153. package/docs/changelogs.md +63 -40
  154. package/docs/configuration.md +29 -27
  155. package/docs/editorial-overrides.md +23 -23
  156. package/docs/labels.md +37 -19
  157. package/docs/project-releases.md +13 -13
  158. package/docs/publishing.md +5 -5
  159. package/docs/readiness-checks.md +3 -3
  160. package/docs/releasing.md +50 -25
  161. package/docs/work-types.md +41 -57
  162. package/package.json +7 -6
  163. package/presets/labels/common.yaml +1 -41
  164. package/dist/esm/resolveReleaseNotesConfig.d.ts +0 -26
  165. package/dist/esm/resolveReleaseNotesConfig.js +0 -29
  166. package/dist/esm/workTypesData.d.ts +0 -50
  167. package/dist/esm/workTypesData.js +0 -145
  168. package/schemas/label-map.json +0 -24
@@ -4,11 +4,14 @@ import { describeError } from '@williamthorsen/toolbelt.errors';
4
4
  import { commitCommand } from "../commitCommand.js";
5
5
  import { configFlagSchema } from "../configFlagSchema.js";
6
6
  import { createGithubReleaseCommand } from "../createGithubReleaseCommand.js";
7
+ import { enterRepoRoot } from "../enterRepoRoot.js";
7
8
  import { showPrepareHelp } from "../help/prepareHelp.js";
8
9
  import { initCommand } from "../init/initCommand.js";
10
+ import { updateTemplatesCommand } from "../init/updateTemplatesCommand.js";
9
11
  import { prepareCommand } from "../prepareCommand.js";
10
12
  import { publishCommand } from "../publishCommand.js";
11
13
  import { pushCommand } from "../pushCommand.js";
14
+ import { resolveConfigFlag } from "../resolveConfigFlag.js";
12
15
  import { OUTPUT_STYLE_ENV_VAR, resolveStylesOrExit } from "../resolveStylesOrExit.js";
13
16
  import { showTagPrefixesCommand } from "../showTagPrefixesCommand.js";
14
17
  import { generateCommand } from "../sync-labels/generateCommand.js";
@@ -30,6 +33,7 @@ Commands:
30
33
  create-github-release Create GitHub Releases from changelog.json for tags on HEAD
31
34
  show-tag-prefixes Show derived and declared legacy tag prefixes per workspace
32
35
  init Initialize release-kit in the current repository
36
+ update-templates Update the scaffolded workflows to the current templates
33
37
  overrides Manage editorial changelog overrides
34
38
  sync-labels Manage GitHub label synchronization
35
39
 
@@ -69,7 +73,8 @@ retired packages, then generate .github/labels.yaml. When the config file alread
69
73
  exists, print the block for manual paste instead of rewriting the file.
70
74
 
71
75
  Options:
72
- --config <path> Path to config file (default: .config/release-kit.config.ts)
76
+ --config <path> Config file, relative to the current directory
77
+ (default: .config/release-kit.config.ts at the repo root)
73
78
  --dry-run Preview changes without writing files
74
79
  --force Overwrite existing files instead of skipping them
75
80
  --help, -h Show this help message
@@ -83,7 +88,8 @@ Regenerate .github/labels.yaml from the repoLabels block of .config/release-kit.
83
88
 
84
89
  Options:
85
90
  --check Report whether .github/labels.yaml is stale instead of writing it
86
- --config <path> Path to config file (default: .config/release-kit.config.ts)
91
+ --config <path> Config file, relative to the current directory
92
+ (default: .config/release-kit.config.ts at the repo root)
87
93
  --help, -h Show this help message
88
94
  `);
89
95
  }
@@ -111,6 +117,20 @@ Options:
111
117
  --help, -h Show this help message
112
118
  `);
113
119
  }
120
+ function showUpdateTemplatesHelp() {
121
+ console.info(`
122
+ Usage: release-kit update-templates [options]
123
+
124
+ Update the GitHub Actions workflows that release-kit scaffolds to the templates
125
+ of the installed version, showing what changed in each file. Creates a missing
126
+ workflow that \`init\` would create, and refreshes sync-labels.yaml only when it
127
+ exists. Keeps publish.yaml's provenance setting. Never touches config files.
128
+
129
+ Options:
130
+ --dry-run Preview changes without writing files
131
+ --help, -h Show this help message
132
+ `);
133
+ }
114
134
  function showCommitHelp() {
115
135
  console.info(`
116
136
  Usage: release-kit commit [options]
@@ -139,10 +159,12 @@ function showPushHelp() {
139
159
  console.info(`
140
160
  Usage: release-kit push [options]
141
161
 
142
- Push the release commit and each tag individually, ensuring GitHub Actions
143
- fires a separate workflow run per tag.
162
+ Push the release commit and each tag individually, so that GitHub Actions
163
+ starts a separate workflow run for each tag.
144
164
 
145
165
  Options:
166
+ --config <path> Config file, relative to the current directory
167
+ (default: .config/release-kit.config.ts at the repo root)
146
168
  --dry-run Preview without pushing
147
169
  --tags=tag1,tag2 Only push the named tags (comma-separated, full tag names)
148
170
  --tags-only Skip the branch push (push tags only)
@@ -154,10 +176,12 @@ function showCreateGithubReleaseHelp() {
154
176
  Usage: release-kit create-github-release [options]
155
177
 
156
178
  Create GitHub Releases from changelog.json for tags on HEAD. Private
157
- (package.json#private) workspaces are skipped with a warning and get no Release.
179
+ (package.json#private) workspaces are skipped with a warning, and the command
180
+ doesn't create a Release for them.
158
181
 
159
182
  Options:
160
- --config <path> Path to config file (default: .config/release-kit.config.ts)
183
+ --config <path> Config file, relative to the current directory
184
+ (default: .config/release-kit.config.ts at the repo root)
161
185
  --dry-run Preview without creating releases
162
186
  --tags=tag1,tag2 Only create releases for the named tags (comma-separated, full tag names)
163
187
  --help, -h Show this help message
@@ -168,12 +192,13 @@ function showShowTagPrefixesHelp() {
168
192
  Usage: release-kit show-tag-prefixes [options]
169
193
 
170
194
  Print a per-workspace table of derived tag prefixes, tag counts, and declared
171
- legacy tag prefixes. Surfaces any release-shaped tags whose prefix is neither a
195
+ legacy tag prefixes. Reports any release-shaped tags whose prefix is neither a
172
196
  derived prefix nor declared in \`legacyIdentities\`, with a copy-pasteable
173
197
  config snippet.
174
198
 
175
199
  Options:
176
- --config <path> Path to config file (default: .config/release-kit.config.ts)
200
+ --config <path> Config file, relative to the current directory
201
+ (default: .config/release-kit.config.ts at the repo root)
177
202
  --help, -h Show this help message
178
203
  `);
179
204
  }
@@ -199,12 +224,13 @@ scopes. Reports schema/parse errors, ambiguous-prefix errors, conflicting-key er
199
224
  stale-key warnings.
200
225
 
201
226
  Exit codes:
202
- 0 Clean — no errors, no stale keys
227
+ 0 Clean: no errors, no stale keys
203
228
  1 Stale-key warnings only (no errors)
204
- 2 Schema/parse, ambiguous-prefix, or conflicting-key errors (errors dominate)
229
+ 2 Schema/parse, ambiguous-prefix, or conflicting-key errors (errors take precedence)
205
230
 
206
231
  Options:
207
- --config <path> Path to config file (default: .config/release-kit.config.ts)
232
+ --config <path> Config file, relative to the current directory
233
+ (default: .config/release-kit.config.ts at the repo root)
208
234
  --help, -h Show this help message
209
235
  `);
210
236
  }
@@ -212,13 +238,14 @@ function showPublishHelp() {
212
238
  console.info(`
213
239
  Usage: release-kit publish [options]
214
240
 
215
- Publish packages that have release tags on HEAD. Operates only on workspaces where
241
+ Publish packages that have release tags on HEAD. Operates only on workspaces whose
216
242
  package.json#private is absent or false. Without --tags, unpublishable workspaces are
217
243
  silently filtered out. With --tags, an unpublishable tag is skipped with a warning while
218
- any publishable tags still publish.
244
+ any publishable tags are still published.
219
245
 
220
246
  Options:
221
- --config <path> Path to config file (default: .config/release-kit.config.ts)
247
+ --config <path> Config file, relative to the current directory
248
+ (default: .config/release-kit.config.ts at the repo root)
222
249
  --dry-run Preview without publishing
223
250
  --no-git-checks Skip the clean-working-tree check
224
251
  --tags=tag1,tag2 Only publish the named tags (comma-separated, full tag names)
@@ -226,6 +253,15 @@ Options:
226
253
  --help, -h Show this help message
227
254
  `);
228
255
  }
256
+ function enterRepoRootOrExit() {
257
+ try {
258
+ return enterRepoRoot();
259
+ }
260
+ catch (error) {
261
+ reportError(describeError(error));
262
+ process.exit(1);
263
+ }
264
+ }
229
265
  const args = process.argv.slice(2);
230
266
  const command = args[0];
231
267
  if (command === '--version' || command === '-V') {
@@ -247,7 +283,8 @@ if (command === 'prepare') {
247
283
  showPrepareHelp();
248
284
  process.exit(0);
249
285
  }
250
- await prepareCommand(flags, styles);
286
+ const { invocationDir } = enterRepoRootOrExit();
287
+ await prepareCommand(flags, styles, invocationDir);
251
288
  process.exit(0);
252
289
  }
253
290
  if (command === 'commit') {
@@ -255,6 +292,7 @@ if (command === 'commit') {
255
292
  showCommitHelp();
256
293
  process.exit(0);
257
294
  }
295
+ enterRepoRootOrExit();
258
296
  try {
259
297
  commitCommand(flags);
260
298
  }
@@ -269,6 +307,7 @@ if (command === 'tag') {
269
307
  showTagHelp();
270
308
  process.exit(0);
271
309
  }
310
+ enterRepoRootOrExit();
272
311
  tagCommand(flags, styles);
273
312
  process.exit(0);
274
313
  }
@@ -277,7 +316,8 @@ if (command === 'push') {
277
316
  showPushHelp();
278
317
  process.exit(0);
279
318
  }
280
- pushCommand(flags);
319
+ const { invocationDir } = enterRepoRootOrExit();
320
+ await pushCommand(flags, styles, invocationDir);
281
321
  process.exit(0);
282
322
  }
283
323
  if (command === 'create-github-release') {
@@ -285,7 +325,8 @@ if (command === 'create-github-release') {
285
325
  showCreateGithubReleaseHelp();
286
326
  process.exit(0);
287
327
  }
288
- await createGithubReleaseCommand(flags, styles);
328
+ const { invocationDir } = enterRepoRootOrExit();
329
+ await createGithubReleaseCommand(flags, styles, invocationDir);
289
330
  process.exit(0);
290
331
  }
291
332
  if (command === 'publish') {
@@ -293,7 +334,8 @@ if (command === 'publish') {
293
334
  showPublishHelp();
294
335
  process.exit(0);
295
336
  }
296
- await publishCommand(flags, styles);
337
+ const { invocationDir } = enterRepoRootOrExit();
338
+ await publishCommand(flags, styles, invocationDir);
297
339
  process.exit(0);
298
340
  }
299
341
  if (command === 'show-tag-prefixes') {
@@ -301,8 +343,9 @@ if (command === 'show-tag-prefixes') {
301
343
  showShowTagPrefixesHelp();
302
344
  process.exit(0);
303
345
  }
346
+ const { invocationDir } = enterRepoRootOrExit();
304
347
  const { config } = parseArgsOrExit(flags, configFlagSchema).flags;
305
- const exitCode = await showTagPrefixesCommand(styles, config);
348
+ const exitCode = await showTagPrefixesCommand(styles, resolveConfigFlag(config, invocationDir));
306
349
  process.exit(exitCode);
307
350
  }
308
351
  if (command === 'init') {
@@ -310,6 +353,7 @@ if (command === 'init') {
310
353
  showInitHelp();
311
354
  process.exit(0);
312
355
  }
356
+ enterRepoRootOrExit();
313
357
  const initFlagSchema = {
314
358
  dryRun: { long: '--dry-run', type: 'boolean' },
315
359
  force: { long: '--force', type: 'boolean' },
@@ -319,6 +363,19 @@ if (command === 'init') {
319
363
  const exitCode = initCommand({ dryRun, force, styles, withConfig });
320
364
  process.exit(exitCode);
321
365
  }
366
+ if (command === 'update-templates') {
367
+ if (flags.some((f) => f === '--help' || f === '-h')) {
368
+ showUpdateTemplatesHelp();
369
+ process.exit(0);
370
+ }
371
+ enterRepoRootOrExit();
372
+ const updateTemplatesFlagSchema = {
373
+ dryRun: { long: '--dry-run', type: 'boolean' },
374
+ };
375
+ const { dryRun } = parseArgsOrExit(flags, updateTemplatesFlagSchema).flags;
376
+ const exitCode = updateTemplatesCommand({ dryRun, styles });
377
+ process.exit(exitCode);
378
+ }
322
379
  if (command === 'sync-labels') {
323
380
  const subcommand = flags[0];
324
381
  if (subcommand === undefined || ['--help', '-h'].includes(subcommand)) {
@@ -331,14 +388,16 @@ if (command === 'sync-labels') {
331
388
  showSyncLabelsInitHelp();
332
389
  process.exit(0);
333
390
  }
391
+ const { invocationDir } = enterRepoRootOrExit();
334
392
  const syncLabelsInitFlagSchema = {
335
393
  ...configFlagSchema,
336
394
  dryRun: { long: '--dry-run', type: 'boolean' },
337
395
  force: { long: '--force', type: 'boolean' },
338
396
  };
339
397
  const { config, dryRun, force } = parseArgsOrExit(subflags, syncLabelsInitFlagSchema).flags;
398
+ const configPath = resolveConfigFlag(config, invocationDir);
340
399
  const exitCode = await syncLabelsInitCommand({
341
- ...(config !== undefined && { configPath: config }),
400
+ ...(configPath !== undefined && { configPath }),
342
401
  dryRun,
343
402
  force,
344
403
  styles,
@@ -350,12 +409,14 @@ if (command === 'sync-labels') {
350
409
  showSyncLabelsGenerateHelp();
351
410
  process.exit(0);
352
411
  }
412
+ const { invocationDir } = enterRepoRootOrExit();
353
413
  const generateFlagSchema = {
354
414
  ...configFlagSchema,
355
415
  check: { long: '--check', type: 'boolean' },
356
416
  };
357
417
  const { check, config } = parseArgsOrExit(subflags, generateFlagSchema).flags;
358
- const exitCode = await generateCommand({ check, ...(config !== undefined && { configPath: config }), styles });
418
+ const configPath = resolveConfigFlag(config, invocationDir);
419
+ const exitCode = await generateCommand({ check, ...(configPath !== undefined && { configPath }), styles });
359
420
  process.exit(exitCode);
360
421
  }
361
422
  if (subcommand === 'sync') {
@@ -367,6 +428,7 @@ if (command === 'sync-labels') {
367
428
  reportError(`Unknown option: ${subflags[0]}`);
368
429
  process.exit(1);
369
430
  }
431
+ enterRepoRootOrExit();
370
432
  const exitCode = syncLabelsCommand();
371
433
  process.exit(exitCode);
372
434
  }
@@ -386,8 +448,9 @@ if (command === 'overrides') {
386
448
  showOverridesValidateHelp();
387
449
  process.exit(0);
388
450
  }
451
+ const { invocationDir } = enterRepoRootOrExit();
389
452
  const { config } = parseArgsOrExit(subflags, configFlagSchema).flags;
390
- const result = await validateOverridesCommand(styles, config);
453
+ const result = await validateOverridesCommand(styles, resolveConfigFlag(config, invocationDir));
391
454
  if (result.exitCode === 0) {
392
455
  console.info(result.message);
393
456
  }
@@ -1,10 +1,10 @@
1
1
  import { type RawCommit } from './enumerateReleaseWindows.js';
2
2
  import type { GenerateChangelogOptions } from './generateChangelogs.js';
3
- import type { ChangelogEntry, ChangelogSection, MalformedChangeRecordBlock, PolicyViolation, ReleaseConfig, ReleaseType, UndeclaredEntryType } from './types.js';
3
+ import type { ChangelogEntry, ChangelogSection, MalformedChangeRecordBlock, PolicyViolation, ReleaseConfig, ReleaseType, UndeclaredEntryType, UnroutedEntryScope } from './types.js';
4
4
  /**
5
5
  * Strips a section title down to its bare name.
6
6
  *
7
- * A default title carries a leading emoji (e.g. `"🐛 Bug fixes"`), while a consumer's
7
+ * A default title has a leading emoji (e.g. `"🐛 Bug fixes"`), while a consumer's
8
8
  * `devOnlySections` or `workTypes` override may be written as a bare name. Comparing both sides
9
9
  * through this helper lets the two forms match.
10
10
  */
@@ -12,8 +12,7 @@ export declare function stripGroupDecorations(group: string): string;
12
12
  /**
13
13
  * Builds structured changelog entries from the release windows of git history, naming the unreleased window `tag`.
14
14
  *
15
- * Composes `readReleaseHistory` and `toChangelogEntries` for a caller that knows the tag before it reads. Performs no
16
- * `changelog.json` I/O; callers persist the entries via `renderChangelogJson`.
15
+ * Composes `readReleaseHistory` and `toChangelogEntries` for a caller that knows the tag before it reads.
17
16
  */
18
17
  export declare function buildChangelogEntries(config: ReleaseHistoryConfig, tag: string, options: GenerateChangelogOptions): {
19
18
  entries: ChangelogEntry[];
@@ -25,30 +24,35 @@ export interface ChangelogDiagnostics {
25
24
  /** Breaking-policy violations of the window's titles and change-record entries. */
26
25
  policyViolations: PolicyViolation[];
27
26
  undeclaredEntryTypes: UndeclaredEntryType[];
27
+ /**
28
+ * Entry scopes that don't route to any workspace. A read leaves it empty: Only a pass over every workspace can fill
29
+ * it.
30
+ */
31
+ unroutedEntryScopes: UnroutedEntryScope[];
28
32
  }
29
33
  /**
30
34
  * Reads a scope's release windows once and returns the changelog items of every window, with what the unreleased
31
35
  * window decides: its commits, its bump, and its diagnostics.
32
36
  *
33
- * A commit whose last `change-record` block records entries yields one item per entry; any other commit is
34
- * classified by its title through `classifyChangelogCommit`. A `release:` or merge subject yields nothing either
35
- * way. Only the unreleased window records diagnostics, since the released windows were reported when they were
36
- * prepared.
37
+ * A commit whose last `change-record` block records entries yields one item per entry routed to
38
+ * `options.workspaceDir`; any other commit is classified by its title through `classifyChangelogCommit`. A `release:`
39
+ * or merge subject yields nothing either way. The read records diagnostics only for the unreleased window, since the
40
+ * released windows were reported when they were prepared.
37
41
  */
38
42
  export declare function readReleaseHistory(config: ReleaseHistoryConfig, options: GenerateChangelogOptions): ReleaseHistory;
39
43
  /** The configuration that `readReleaseHistory` reads. */
40
- export type ReleaseHistoryConfig = Pick<ReleaseConfig, 'breakingPolicies' | 'changelogJson' | 'versionPatterns' | 'workTypes'>;
44
+ export type ReleaseHistoryConfig = Pick<ReleaseConfig, 'breakingPolicies' | 'changelogJson' | 'scopeAliases' | 'versionPatterns' | 'workTypes'>;
41
45
  /** One read of a scope's release windows. */
42
46
  export interface ReleaseHistory {
43
47
  /** The newest matching tag that HEAD reaches; undefined when none does. */
44
48
  previousTag: string | undefined;
45
- /** Entries of the released windows, newest first; a window that yields no item contributes none. */
49
+ /** Entries of the released windows, newest first; a window that doesn't yield any item contributes none. */
46
50
  releasedEntries: ChangelogEntry[];
47
51
  unreleased: UnreleasedWindowReading;
48
52
  }
49
53
  /** The unreleased window: its changelog sections and commits, and the bump and diagnostics that they determine. */
50
54
  export interface UnreleasedWindowReading {
51
- /** The highest bump that the window's items call for; undefined when the window yields no item. */
55
+ /** The highest bump that the window's items call for; undefined when the window doesn't yield any item. */
52
56
  bump: ReleaseType | undefined;
53
57
  /** The window's commits, newest first, without release commits. */
54
58
  commits: RawCommit[];
@@ -59,10 +63,16 @@ export interface UnreleasedWindowReading {
59
63
  parsedCommitCount: number;
60
64
  sections: ChangelogSection[];
61
65
  /**
62
- * Commits that yield no item and that neither an exclusion nor a diagnostic accounts for, newest first; undefined
63
- * when none.
66
+ * Commits that don't yield any item and that neither an exclusion nor a diagnostic accounts for, newest first;
67
+ * undefined when none.
64
68
  */
65
69
  unparseableCommits: RawCommit[] | undefined;
66
70
  }
67
71
  /** Labels the unreleased window of `history` with `tag` and returns the entries of every window, newest first. */
68
72
  export declare function toChangelogEntries(history: ReleaseHistory, tag: string): ChangelogEntry[];
73
+ /**
74
+ * Returns the entries that a direct release tagged `tag` records: those of `toChangelogEntries`, or, when the
75
+ * unreleased window doesn't yield any item, the synthetic "Forced version bump." entry dated `today` ahead of the
76
+ * released entries.
77
+ */
78
+ export declare function toReleaseEntries(history: ReleaseHistory, tag: string, today: string): ChangelogEntry[];
@@ -1,4 +1,5 @@
1
1
  import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
2
+ import { buildEmptyReleaseEntry } from "./buildEmptyReleaseEntry.js";
2
3
  import { extractVersion } from "./changelogJsonUtils.js";
3
4
  import { classifyChangelogCommit, isNonChangeSubject, isReleaseSubject } from "./classifyChangelogCommit.js";
4
5
  import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
@@ -6,7 +7,7 @@ import { determineBumpType } from "./determineBumpType.js";
6
7
  import { enumerateReleaseWindows } from "./enumerateReleaseWindows.js";
7
8
  import { extractMigration } from "./extractMigration.js";
8
9
  import { parseChangeRecordBlock, stripChangeRecordBlocks } from "./parseChangeRecordBlock.js";
9
- import { COMMIT_PREPROCESSOR_PATTERNS, evaluateBreakingPolicy, PIPE_SCOPE_SOURCE, resolveType, } from "./parseCommitMessage.js";
10
+ import { evaluateBreakingPolicy, parseSubject, resolveType } from "./parseCommitMessage.js";
10
11
  import { stripEmojiPrefix } from "./stripEmojiPrefix.js";
11
12
  const UNRELEASED_TAG = 'unreleased';
12
13
  const CANONICAL_SECTION_ORDER = new Map(Object.values(DEFAULT_WORK_TYPES).map((config, index) => [stripGroupDecorations(config.header), index]));
@@ -31,6 +32,9 @@ export function readReleaseHistory(config, options) {
31
32
  const context = {
32
33
  breakingPolicies: config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES,
33
34
  devOnlySections: new Set(config.changelogJson.devOnlySections.map(stripGroupDecorations)),
35
+ ...(options.workspaceDir !== undefined && {
36
+ routing: { workspaceDir: options.workspaceDir, scopeAliases: config.scopeAliases ?? {} },
37
+ }),
34
38
  versionPatterns: config.versionPatterns ?? DEFAULT_VERSION_PATTERNS,
35
39
  workTypes: config.workTypes ?? DEFAULT_WORK_TYPES,
36
40
  };
@@ -50,9 +54,20 @@ export function toChangelogEntries(history, tag) {
50
54
  ...history.releasedEntries,
51
55
  ];
52
56
  }
57
+ export function toReleaseEntries(history, tag, today) {
58
+ if (history.unreleased.sections.length === 0) {
59
+ return [buildEmptyReleaseEntry(extractVersion(tag), today), ...history.releasedEntries];
60
+ }
61
+ return toChangelogEntries(history, tag);
62
+ }
53
63
  function transformReleases(windows, context) {
54
64
  const [unreleasedWindow, baselineWindow] = windows;
55
- const diagnostics = { malformedBlocks: [], policyViolations: [], undeclaredEntryTypes: [] };
65
+ const diagnostics = {
66
+ malformedBlocks: [],
67
+ policyViolations: [],
68
+ undeclaredEntryTypes: [],
69
+ unroutedEntryScopes: [],
70
+ };
56
71
  const signals = [];
57
72
  const unparseable = [];
58
73
  let parsedCommitCount = 0;
@@ -130,7 +145,7 @@ function readCommit(commit, context, diagnostics) {
130
145
  if (classification.kind !== 'header') {
131
146
  return { items: [], isUnparseable: classification.kind === 'unparseable' && reading.kind !== 'malformed' };
132
147
  }
133
- const item = buildTitleItem(commit, classification.breaking);
148
+ const item = buildTitleItem(commit, classification.breaking, context.workTypes);
134
149
  return {
135
150
  items: [
136
151
  { header: classification.header, item, signal: { type: classification.type, breaking: item.breaking === true } },
@@ -149,6 +164,15 @@ function buildSections(sectionMap, devOnlySections) {
149
164
  }
150
165
  return sections.toSorted((a, b) => canonicalSectionPriority(a.title) - canonicalSectionPriority(b.title));
151
166
  }
167
+ function isRoutedTo(scopes, routing) {
168
+ if (scopes.length === 0) {
169
+ return true;
170
+ }
171
+ return scopes.some((scope) => {
172
+ const resolved = routing.scopeAliases[scope] ?? scope;
173
+ return resolved === '*' || resolved === routing.workspaceDir;
174
+ });
175
+ }
152
176
  function formatDate(timestamp) {
153
177
  return new Date(timestamp * 1_000).toISOString().slice(0, 10);
154
178
  }
@@ -161,7 +185,10 @@ function appendItem(sectionMap, header, item) {
161
185
  items.push(item);
162
186
  }
163
187
  function buildEntryItem(commit, entry, position, prNumber, context, diagnostics) {
164
- const { breakingPolicies, workTypes } = context;
188
+ const { breakingPolicies, routing, workTypes } = context;
189
+ if (routing !== undefined && !isRoutedTo(entry.scopes, routing)) {
190
+ return undefined;
191
+ }
165
192
  const type = resolveType(entry.type, workTypes);
166
193
  if (type === undefined) {
167
194
  diagnostics?.undeclaredEntryTypes.push({
@@ -208,13 +235,13 @@ function buildEntryItem(commit, entry, position, prNumber, context, diagnostics)
208
235
  item.entry = position;
209
236
  return { header: config.header, item, signal: { type, breaking } };
210
237
  }
211
- function buildTitleItem(commit, isParsedBreaking) {
238
+ function buildTitleItem(commit, isParsedBreaking, workTypes) {
212
239
  const body = extractBody(stripChangeRecordBlocks(commit.message));
213
240
  const item = { description: extractDescription(commit.message) };
214
241
  if (body !== undefined) {
215
242
  item.body = body;
216
243
  }
217
- if (isParsedBreaking && subjectHasBreakingMarker(commit.message)) {
244
+ if (isParsedBreaking && subjectHasBreakingMarker(commit.message, workTypes)) {
218
245
  item.breaking = true;
219
246
  }
220
247
  const migration = extractMigration(body);
@@ -224,21 +251,13 @@ function buildTitleItem(commit, isParsedBreaking) {
224
251
  item.hash = commit.hash;
225
252
  return item;
226
253
  }
227
- const SUBJECT_BREAKING_MARKER_PATTERN = new RegExp(String.raw `^(?:${PIPE_SCOPE_SOURCE}\|)?\w+(?:\([^)]+\))?!:`);
228
- function subjectHasBreakingMarker(message) {
229
- let subject = message.split('\n', 1)[0] ?? '';
230
- for (const pattern of COMMIT_PREPROCESSOR_PATTERNS) {
231
- subject = subject.replace(pattern, '');
232
- }
233
- return SUBJECT_BREAKING_MARKER_PATTERN.test(subject);
254
+ function subjectHasBreakingMarker(message, workTypes) {
255
+ return parseSubject(message.split('\n', 1)[0] ?? '', workTypes)?.breaking === true;
234
256
  }
235
257
  function extractDescription(message) {
236
258
  const firstLine = message.split('\n', 1)[0] ?? message;
237
259
  const afterColon = firstLine.split(': ').slice(1).join(': ');
238
- if (afterColon.length > 0) {
239
- return afterColon.charAt(0).toUpperCase() + afterColon.slice(1);
240
- }
241
- return firstLine;
260
+ return afterColon.charAt(0).toUpperCase() + afterColon.slice(1);
242
261
  }
243
262
  const TRAILER_PATTERNS = [
244
263
  /^Change:/i,
@@ -1,17 +1,17 @@
1
1
  import type { WorkspaceConfig } from './types.js';
2
2
  /** Identities, current versions, and `workspace:` dependency edges of the workspaces' packages. */
3
3
  export interface DependencyGraph {
4
- /** Resolve a package name to its workspace `dir`. */
4
+ /** Maps a package name to its workspace `dir`. */
5
5
  packageNameToDir: Map<string, string>;
6
- /** Resolve a workspace `dir` to its package name (inverse of `packageNameToDir`). */
6
+ /** Maps a workspace `dir` to its package name (inverse of `packageNameToDir`). */
7
7
  dirToPackageName: Map<string, string>;
8
- /** Resolve a workspace `dir` to its current version; absent when its `package.json` has no string `version`. */
8
+ /** Maps a workspace `dir` to its current version; absent when its `package.json` doesn't have a string `version`. */
9
9
  dirToVersion: Map<string, string>;
10
- /** Map a package name to the workspaces that depend on it. */
10
+ /** Maps a package name to the workspaces that depend on it. */
11
11
  dependentsOf: Map<string, WorkspaceConfig[]>;
12
12
  /**
13
- * Forward adjacency: map a workspace `dir` to the set of `workspace:`-protocol package
14
- * names it declares in `dependencies` or `peerDependencies`. Complement of `dependentsOf`.
13
+ * Maps a workspace `dir` to the `workspace:`-protocol package names that it declares in `dependencies` or
14
+ * `peerDependencies` (the forward counterpart of `dependentsOf`).
15
15
  */
16
16
  dependenciesOf: Map<string, Set<string>>;
17
17
  }
@@ -1,10 +1,3 @@
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
+ /** Builds the changelog entry of a forced release whose unreleased window doesn't yield any changelog item. */
10
3
  export declare function buildEmptyReleaseEntry(version: string, date: string): ChangelogEntry;
@@ -1,12 +1,6 @@
1
1
  import type { PrepareResult } from './types.js';
2
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.
3
+ * Builds a release commit body from a prepare result: one section per released workspace or project that has commits,
4
+ * the project last, each headed by its tag and listing its scope-stripped commit subjects.
11
5
  */
12
6
  export declare function buildReleaseSummary(result: Pick<PrepareResult, 'project' | 'workspaces'>): string;
@@ -1,11 +1,9 @@
1
1
  import type { ChangelogEntry } from './types.js';
2
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.
3
+ * Builds the changelog entry of a propagation-only bump, with one item per dependency whose bump propagated, under
4
+ * `sectionTitle`, the resolved `deps` work type's header.
7
5
  */
8
6
  export declare function buildSyntheticChangelogEntry(propagatedFrom: ReadonlyArray<{
9
7
  packageName: string;
10
8
  newVersion: string;
11
- }>, version: string, date: string): ChangelogEntry;
9
+ }>, version: string, date: string, sectionTitle: string): ChangelogEntry;
@@ -1,10 +1,10 @@
1
- export function buildSyntheticChangelogEntry(propagatedFrom, version, date) {
1
+ export function buildSyntheticChangelogEntry(propagatedFrom, version, date, sectionTitle) {
2
2
  const items = propagatedFrom.map((dep) => ({
3
3
  description: `Bumped \`${dep.packageName}\` to ${dep.newVersion}`,
4
4
  }));
5
5
  return {
6
6
  version,
7
7
  date,
8
- sections: [{ title: 'Dependency updates', audience: 'dev', items }],
8
+ sections: [{ title: sectionTitle, audience: 'dev', items }],
9
9
  };
10
10
  }
@@ -1,10 +1,3 @@
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
+ /** Bumps a `major.minor.patch` version by the release type; throws on a version of any other form. */
10
3
  export declare function bumpVersion(version: string, releaseType: ReleaseType): string;
@@ -1,15 +1,13 @@
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
+ /** Resolves the path of the `changelog.json` file under a workspace's changelog directory. */
3
3
  export declare function resolveChangelogJsonPath(config: Pick<ReleaseConfig, 'changelogJson'>, changelogPath: string): string;
4
4
  /** Renders changelog entries as a `changelog.json` file's complete content, sorted newest-first. */
5
5
  export declare function renderChangelogJson(entries: ChangelogEntry[]): string;
6
6
  /**
7
- * Merges `entries` with the on-disk entries at `filePath` and returns the merged set in
8
- * newest-first order, without writing.
7
+ * Merges `entries` with the on-disk entries at `filePath` and returns the merged set in newest-first order, without
8
+ * writing.
9
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.
10
+ * Keeps an on-disk entry that `entries` lacks, which preserves synthetic entries across propagation runs. A missing or
11
+ * malformed file counts as empty, so that a malformed file does not abort the release.
14
12
  */
15
13
  export declare function mergeChangelogEntriesWithDisk(filePath: string, entries: ChangelogEntry[]): ChangelogEntry[];