@williamthorsen/release-kit 4.7.0 → 5.0.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 (87) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +310 -40
  3. package/cliff.toml.template +2 -1
  4. package/dist/esm/.cache +1 -1
  5. package/dist/esm/bin/release-kit.js +67 -12
  6. package/dist/esm/buildDependencyGraph.d.ts +3 -3
  7. package/dist/esm/buildDependencyGraph.js +10 -10
  8. package/dist/esm/buildReleaseSummary.js +4 -4
  9. package/dist/esm/bumpAllVersions.d.ts +1 -0
  10. package/dist/esm/bumpAllVersions.js +16 -2
  11. package/dist/esm/bumpVersion.js +3 -0
  12. package/dist/esm/commitCommand.js +1 -1
  13. package/dist/esm/compareVersions.d.ts +1 -0
  14. package/dist/esm/compareVersions.js +27 -0
  15. package/dist/esm/createGithubRelease.d.ts +6 -2
  16. package/dist/esm/createGithubRelease.js +17 -17
  17. package/dist/esm/createGithubReleaseCommand.d.ts +1 -0
  18. package/dist/esm/createGithubReleaseCommand.js +41 -0
  19. package/dist/esm/defaults.js +5 -3
  20. package/dist/esm/deriveWorkspaceConfig.d.ts +2 -0
  21. package/dist/esm/deriveWorkspaceConfig.js +37 -0
  22. package/dist/esm/detectUndeclaredTagPrefixes.d.ts +7 -0
  23. package/dist/esm/detectUndeclaredTagPrefixes.js +46 -0
  24. package/dist/esm/generateChangelogJson.js +37 -1
  25. package/dist/esm/generateChangelogs.d.ts +1 -1
  26. package/dist/esm/generateChangelogs.js +14 -3
  27. package/dist/esm/getCommitsSinceTarget.d.ts +1 -1
  28. package/dist/esm/getCommitsSinceTarget.js +8 -4
  29. package/dist/esm/index.d.ts +9 -3
  30. package/dist/esm/index.js +12 -3
  31. package/dist/esm/init/detectRepoType.js +1 -2
  32. package/dist/esm/init/initCommand.js +1 -1
  33. package/dist/esm/init/scaffold.d.ts +1 -1
  34. package/dist/esm/init/scaffold.js +8 -5
  35. package/dist/esm/init/templates.d.ts +1 -0
  36. package/dist/esm/init/templates.js +40 -10
  37. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +6 -1
  38. package/dist/esm/injectReleaseNotesIntoReadme.js +20 -7
  39. package/dist/esm/loadConfig.d.ts +2 -1
  40. package/dist/esm/loadConfig.js +65 -12
  41. package/dist/esm/parseRequestedTags.d.ts +1 -0
  42. package/dist/esm/parseRequestedTags.js +10 -0
  43. package/dist/esm/prepareCommand.d.ts +3 -1
  44. package/dist/esm/prepareCommand.js +75 -27
  45. package/dist/esm/previewTagPrefixes.d.ts +30 -0
  46. package/dist/esm/previewTagPrefixes.js +120 -0
  47. package/dist/esm/propagateBumps.d.ts +1 -0
  48. package/dist/esm/propagateBumps.js +1 -1
  49. package/dist/esm/publishCommand.js +8 -13
  50. package/dist/esm/pushCommand.d.ts +1 -0
  51. package/dist/esm/pushCommand.js +47 -0
  52. package/dist/esm/pushRelease.d.ts +11 -0
  53. package/dist/esm/pushRelease.js +26 -0
  54. package/dist/esm/readCurrentVersion.d.ts +1 -0
  55. package/dist/esm/readCurrentVersion.js +21 -0
  56. package/dist/esm/releasePrepare.d.ts +2 -0
  57. package/dist/esm/releasePrepare.js +72 -30
  58. package/dist/esm/releasePrepareMono.js +235 -112
  59. package/dist/esm/renderReleaseNotes.d.ts +1 -0
  60. package/dist/esm/renderReleaseNotes.js +29 -2
  61. package/dist/esm/reportPrepare.js +100 -73
  62. package/dist/esm/resolveCliffConfigPath.js +1 -1
  63. package/dist/esm/resolveCommandTags.d.ts +1 -1
  64. package/dist/esm/resolveCommandTags.js +17 -13
  65. package/dist/esm/resolveReleaseNotesConfig.d.ts +8 -1
  66. package/dist/esm/resolveReleaseNotesConfig.js +17 -7
  67. package/dist/esm/resolveReleaseTags.d.ts +2 -1
  68. package/dist/esm/resolveReleaseTags.js +19 -14
  69. package/dist/esm/showTagPrefixesCommand.d.ts +1 -0
  70. package/dist/esm/showTagPrefixesCommand.js +84 -0
  71. package/dist/esm/sync-labels/initCommand.js +1 -1
  72. package/dist/esm/sync-labels/presets.js +1 -1
  73. package/dist/esm/sync-labels/templates.js +1 -1
  74. package/dist/esm/tagCommand.js +1 -1
  75. package/dist/esm/types.d.ts +22 -7
  76. package/dist/esm/validateConfig.js +179 -36
  77. package/dist/esm/version.d.ts +1 -1
  78. package/dist/esm/version.js +1 -1
  79. package/dist/esm/writeReleaseNotesPreviews.d.ts +18 -0
  80. package/dist/esm/writeReleaseNotesPreviews.js +65 -0
  81. package/package.json +2 -2
  82. package/dist/esm/component.d.ts +0 -2
  83. package/dist/esm/component.js +0 -14
  84. package/dist/esm/findPackageRoot.d.ts +0 -1
  85. package/dist/esm/findPackageRoot.js +0 -17
  86. package/dist/esm/githubReleaseCommand.d.ts +0 -1
  87. package/dist/esm/githubReleaseCommand.js +0 -35
@@ -1,35 +1,48 @@
1
1
  import { execSync } from "node:child_process";
2
- import { readFileSync } from "node:fs";
3
2
  import { buildDependencyGraph } from "./buildDependencyGraph.js";
4
- import { bumpAllVersions } from "./bumpAllVersions.js";
3
+ import { bumpAllVersions, setAllVersions } from "./bumpAllVersions.js";
4
+ import { isForwardVersion } from "./compareVersions.js";
5
5
  import { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
6
+ import { detectUndeclaredTagPrefixes } from "./detectUndeclaredTagPrefixes.js";
6
7
  import { determineBumpFromCommits } from "./determineBumpFromCommits.js";
7
8
  import { generateChangelogJson, generateSyntheticChangelogJson } from "./generateChangelogJson.js";
8
9
  import { buildTagPattern, generateChangelog } from "./generateChangelogs.js";
9
10
  import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
10
11
  import { hasPrettierConfig } from "./hasPrettierConfig.js";
12
+ import { resolveWorkTypes } from "./loadConfig.js";
11
13
  import { propagateBumps } from "./propagateBumps.js";
14
+ import { readCurrentVersion } from "./readCurrentVersion.js";
15
+ import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
16
+ import { writeReleaseNotesPreviews } from "./writeReleaseNotesPreviews.js";
12
17
  import { writeSyntheticChangelog } from "./writeSyntheticChangelog.js";
13
18
  function releasePrepareMono(config, options) {
14
- const { dryRun } = options;
19
+ const { dryRun, withReleaseNotes } = options;
20
+ if (withReleaseNotes === true && !config.changelogJson.enabled) {
21
+ console.warn("Warning: --with-release-notes requires changelogJson.enabled; skipping preview generation");
22
+ }
23
+ const sectionOrder = deriveSectionOrder(resolveWorkTypes(config.workTypes));
15
24
  const { directBumps, directResults, skippedResults, currentVersions } = determineDirectBumps(config, options);
16
25
  const previousTags = /* @__PURE__ */ new Map();
17
26
  for (const result of directResults.values()) {
18
- previousTags.set(result.component.dir, result.tag);
27
+ previousTags.set(result.workspace.dir, result.tag);
19
28
  }
20
29
  for (const skipped of skippedResults) {
21
- previousTags.set(skipped.component.dir, skipped.tag);
30
+ previousTags.set(skipped.workspace.dir, skipped.tag);
22
31
  }
23
- const graph = buildDependencyGraph(config.components);
32
+ const graph = buildDependencyGraph(config.workspaces);
24
33
  const fullReleaseSet = propagateBumps(directBumps, graph, currentVersions);
25
34
  const { sorted: sortedDirs, cyclicDirs } = topologicalSort(fullReleaseSet, graph);
26
35
  const warnings = [];
27
36
  if (cyclicDirs.length > 0) {
28
37
  warnings.push(
29
- `Circular workspace dependencies detected among: ${cyclicDirs.join(", ")}. Propagation metadata may be incomplete for these components.`
38
+ `Circular workspace dependencies detected among: ${cyclicDirs.join(", ")}. Propagation metadata may be incomplete for these workspaces.`
30
39
  );
31
40
  }
32
- const components = collectSkippedComponents(skippedResults, fullReleaseSet);
41
+ const workspaces = collectSkippedWorkspaces(skippedResults, fullReleaseSet);
42
+ const previewOptions = {
43
+ enabled: withReleaseNotes === true && config.changelogJson.enabled,
44
+ sectionOrder
45
+ };
33
46
  const { tags, modifiedFiles } = executeReleaseSet(
34
47
  sortedDirs,
35
48
  fullReleaseSet,
@@ -37,17 +50,18 @@ function releasePrepareMono(config, options) {
37
50
  directResults,
38
51
  previousTags,
39
52
  dryRun,
40
- components
53
+ workspaces,
54
+ previewOptions
41
55
  );
42
- const configOrder = new Map(config.components.map((c, i) => [c.dir, i]));
43
- components.sort((a, b) => {
56
+ const configOrder = new Map(config.workspaces.map((w, i) => [w.dir, i]));
57
+ workspaces.sort((a, b) => {
44
58
  const orderA = configOrder.get(a.name ?? "") ?? 0;
45
59
  const orderB = configOrder.get(b.name ?? "") ?? 0;
46
60
  return orderA - orderB;
47
61
  });
48
62
  const formatCommand = runFormatCommand(config, tags, modifiedFiles, dryRun);
49
63
  return {
50
- components,
64
+ workspaces,
51
65
  tags,
52
66
  formatCommand,
53
67
  dryRun,
@@ -55,27 +69,57 @@ function releasePrepareMono(config, options) {
55
69
  };
56
70
  }
57
71
  function determineDirectBumps(config, options) {
58
- const { force, bumpOverride } = options;
72
+ const { force, bumpOverride, setVersion } = options;
73
+ if (setVersion !== void 0 && config.workspaces.length !== 1) {
74
+ throw new Error(`--set-version requires exactly one workspace; received ${config.workspaces.length}`);
75
+ }
59
76
  const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
60
77
  const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
61
78
  const directBumps = /* @__PURE__ */ new Map();
62
79
  const directResults = /* @__PURE__ */ new Map();
63
80
  const skippedResults = [];
64
81
  const currentVersions = /* @__PURE__ */ new Map();
65
- for (const component of config.components) {
66
- const name = component.dir;
67
- const { tag, commits } = getCommitsSinceTarget(component.tagPrefix, component.paths);
82
+ const hintState = { emitted: false };
83
+ const knownPrefixes = config.workspaces.flatMap(getAllTagPrefixes);
84
+ for (const workspace of config.workspaces) {
85
+ const name = workspace.dir;
86
+ const { tag, commits } = getCommitsSinceTarget(getAllTagPrefixes(workspace), workspace.paths);
68
87
  const since = tag === void 0 ? "(no previous release found)" : `since ${tag}`;
69
- const primaryPackageFile = component.packageFiles[0];
88
+ if (tag === void 0) {
89
+ maybeEmitBaselineHint(workspace, knownPrefixes, hintState);
90
+ }
91
+ const primaryPackageFile = workspace.packageFiles[0];
70
92
  if (primaryPackageFile !== void 0) {
71
93
  const currentVersion = readCurrentVersion(primaryPackageFile);
72
94
  if (currentVersion !== void 0) {
73
- currentVersions.set(component.dir, currentVersion);
95
+ currentVersions.set(workspace.dir, currentVersion);
96
+ }
97
+ }
98
+ if (setVersion !== void 0) {
99
+ const currentVersion = currentVersions.get(workspace.dir);
100
+ if (currentVersion === void 0) {
101
+ throw new Error(
102
+ `Cannot validate --set-version: failed to read current version from ${primaryPackageFile ?? "(no package file)"}`
103
+ );
74
104
  }
105
+ if (!isForwardVersion(currentVersion, setVersion)) {
106
+ throw new Error(`--set-version ${setVersion} is not greater than current version ${currentVersion}`);
107
+ }
108
+ directBumps.set(workspace.dir, { releaseType: "patch", newVersionOverride: setVersion });
109
+ directResults.set(workspace.dir, {
110
+ workspace,
111
+ tag,
112
+ commits,
113
+ releaseType: void 0,
114
+ parsedCommitCount: void 0,
115
+ unparseableCommits: void 0,
116
+ setVersion
117
+ });
118
+ continue;
75
119
  }
76
120
  if (commits.length === 0 && !force) {
77
121
  skippedResults.push({
78
- component,
122
+ workspace,
79
123
  tag,
80
124
  commitCount: 0,
81
125
  parsedCommitCount: void 0,
@@ -97,7 +141,7 @@ function determineDirectBumps(config, options) {
97
141
  }
98
142
  if (releaseType === void 0) {
99
143
  skippedResults.push({
100
- component,
144
+ workspace,
101
145
  tag,
102
146
  commitCount: commits.length,
103
147
  parsedCommitCount,
@@ -106,9 +150,9 @@ function determineDirectBumps(config, options) {
106
150
  });
107
151
  continue;
108
152
  }
109
- directBumps.set(component.dir, { releaseType });
110
- directResults.set(component.dir, {
111
- component,
153
+ directBumps.set(workspace.dir, { releaseType });
154
+ directResults.set(workspace.dir, {
155
+ workspace,
112
156
  tag,
113
157
  commits,
114
158
  releaseType,
@@ -118,14 +162,14 @@ function determineDirectBumps(config, options) {
118
162
  }
119
163
  return { directBumps, directResults, skippedResults, currentVersions };
120
164
  }
121
- function collectSkippedComponents(skippedResults, fullReleaseSet) {
122
- const components = [];
165
+ function collectSkippedWorkspaces(skippedResults, fullReleaseSet) {
166
+ const workspaces = [];
123
167
  for (const skipped of skippedResults) {
124
- if (fullReleaseSet.has(skipped.component.dir)) {
168
+ if (fullReleaseSet.has(skipped.workspace.dir)) {
125
169
  continue;
126
170
  }
127
- components.push({
128
- name: skipped.component.dir,
171
+ workspaces.push({
172
+ name: skipped.workspace.dir,
129
173
  status: "skipped",
130
174
  previousTag: skipped.tag,
131
175
  commitCount: skipped.commitCount,
@@ -136,9 +180,9 @@ function collectSkippedComponents(skippedResults, fullReleaseSet) {
136
180
  skipReason: skipped.skipReason
137
181
  });
138
182
  }
139
- return components;
183
+ return workspaces;
140
184
  }
141
- function executeReleaseSet(sortedDirs, fullReleaseSet, config, directResults, previousTags, dryRun, components) {
185
+ function executeReleaseSet(sortedDirs, fullReleaseSet, config, directResults, previousTags, dryRun, workspaces, previewOptions) {
142
186
  const tags = [];
143
187
  const modifiedFiles = [];
144
188
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -147,79 +191,157 @@ function executeReleaseSet(sortedDirs, fullReleaseSet, config, directResults, pr
147
191
  if (releaseEntry === void 0) {
148
192
  continue;
149
193
  }
150
- const component = findComponent(config.components, dir);
151
- if (component === void 0) {
194
+ const workspace = findWorkspace(config.workspaces, dir);
195
+ if (workspace === void 0) {
152
196
  continue;
153
197
  }
154
- const bump = bumpAllVersions(component.packageFiles, releaseEntry.releaseType, dryRun);
155
- const newTag = `${component.tagPrefix}${bump.newVersion}`;
156
- tags.push(newTag);
157
- modifiedFiles.push(...component.packageFiles, ...component.changelogPaths.map((p) => `${p}/CHANGELOG.md`));
158
- const changelogFiles = [];
159
- const directResult = directResults.get(dir);
160
- const isPropagationOnly = directResult === void 0;
161
- if (isPropagationOnly && releaseEntry.propagatedFrom !== void 0) {
162
- for (const changelogPath of component.changelogPaths) {
163
- changelogFiles.push(
164
- writeSyntheticChangelog({
165
- changelogPath,
166
- newVersion: bump.newVersion,
167
- date: today,
168
- propagatedFrom: releaseEntry.propagatedFrom,
169
- dryRun
170
- })
171
- );
172
- }
173
- if (config.changelogJson.enabled) {
174
- for (const changelogPath of component.changelogPaths) {
175
- const jsonFiles = generateSyntheticChangelogJson(
176
- config,
177
- changelogPath,
178
- bump.newVersion,
179
- today,
180
- releaseEntry.propagatedFrom,
181
- dryRun
182
- );
183
- modifiedFiles.push(...jsonFiles);
184
- }
185
- }
186
- } else {
187
- for (const changelogPath of component.changelogPaths) {
188
- changelogFiles.push(
189
- ...generateChangelog(config, changelogPath, newTag, dryRun, {
190
- tagPattern: buildTagPattern(component.tagPrefix),
191
- includePaths: component.paths
192
- })
198
+ executeWorkspaceRelease({
199
+ dir,
200
+ workspace,
201
+ releaseEntry,
202
+ directResult: directResults.get(dir),
203
+ previousTags,
204
+ config,
205
+ dryRun,
206
+ today,
207
+ tags,
208
+ modifiedFiles,
209
+ workspaces,
210
+ previewOptions
211
+ });
212
+ }
213
+ return { tags, modifiedFiles };
214
+ }
215
+ function executeWorkspaceRelease(args) {
216
+ const {
217
+ dir,
218
+ workspace,
219
+ releaseEntry,
220
+ directResult,
221
+ previousTags,
222
+ config,
223
+ dryRun,
224
+ today,
225
+ tags,
226
+ modifiedFiles,
227
+ workspaces,
228
+ previewOptions
229
+ } = args;
230
+ const setVersionTarget = directResult?.setVersion;
231
+ const bump = setVersionTarget === void 0 ? bumpAllVersions(workspace.packageFiles, releaseEntry.releaseType, dryRun) : setAllVersions(workspace.packageFiles, setVersionTarget, dryRun);
232
+ const newTag = `${workspace.tagPrefix}${bump.newVersion}`;
233
+ tags.push(newTag);
234
+ modifiedFiles.push(...workspace.packageFiles, ...workspace.changelogPaths.map((p) => `${p}/CHANGELOG.md`));
235
+ const isPropagationOnly = directResult === void 0;
236
+ const changelogFiles = generateWorkspaceChangelogs({
237
+ workspace,
238
+ releaseEntry,
239
+ newTag,
240
+ newVersion: bump.newVersion,
241
+ isPropagationOnly,
242
+ config,
243
+ dryRun,
244
+ today,
245
+ modifiedFiles,
246
+ previewOptions
247
+ });
248
+ workspaces.push({
249
+ name: dir,
250
+ status: "released",
251
+ previousTag: directResult?.tag ?? previousTags.get(dir),
252
+ commitCount: directResult?.commits.length ?? 0,
253
+ parsedCommitCount: directResult?.parsedCommitCount,
254
+ // For --set-version workspaces releaseType is left undefined so reporting can branch
255
+ // on the override case without conflating it with a bump type.
256
+ releaseType: setVersionTarget === void 0 ? releaseEntry.releaseType : void 0,
257
+ currentVersion: bump.currentVersion,
258
+ newVersion: bump.newVersion,
259
+ tag: newTag,
260
+ bumpedFiles: bump.files,
261
+ changelogFiles,
262
+ commits: directResult?.commits,
263
+ unparseableCommits: directResult?.unparseableCommits,
264
+ propagatedFrom: releaseEntry.propagatedFrom,
265
+ ...setVersionTarget === void 0 ? {} : { setVersion: setVersionTarget }
266
+ });
267
+ }
268
+ function generateWorkspaceChangelogs(args) {
269
+ const {
270
+ workspace,
271
+ releaseEntry,
272
+ newTag,
273
+ newVersion,
274
+ isPropagationOnly,
275
+ config,
276
+ dryRun,
277
+ today,
278
+ modifiedFiles,
279
+ previewOptions
280
+ } = args;
281
+ const changelogFiles = [];
282
+ let firstChangelogJsonPath;
283
+ if (isPropagationOnly && releaseEntry.propagatedFrom !== void 0) {
284
+ for (const changelogPath of workspace.changelogPaths) {
285
+ changelogFiles.push(
286
+ writeSyntheticChangelog({
287
+ changelogPath,
288
+ newVersion,
289
+ date: today,
290
+ propagatedFrom: releaseEntry.propagatedFrom,
291
+ dryRun
292
+ })
293
+ );
294
+ }
295
+ if (config.changelogJson.enabled) {
296
+ for (const changelogPath of workspace.changelogPaths) {
297
+ const jsonFiles = generateSyntheticChangelogJson(
298
+ config,
299
+ changelogPath,
300
+ newVersion,
301
+ today,
302
+ releaseEntry.propagatedFrom,
303
+ dryRun
193
304
  );
194
- }
195
- if (config.changelogJson.enabled) {
196
- for (const changelogPath of component.changelogPaths) {
197
- const jsonFiles = generateChangelogJson(config, changelogPath, newTag, dryRun, {
198
- tagPattern: buildTagPattern(component.tagPrefix),
199
- includePaths: component.paths
200
- });
201
- modifiedFiles.push(...jsonFiles);
202
- }
305
+ modifiedFiles.push(...jsonFiles);
306
+ firstChangelogJsonPath ??= jsonFiles[0];
203
307
  }
204
308
  }
205
- components.push({
206
- name: dir,
207
- status: "released",
208
- previousTag: directResult?.tag ?? previousTags.get(dir),
209
- commitCount: directResult?.commits.length ?? 0,
210
- parsedCommitCount: directResult?.parsedCommitCount,
211
- releaseType: releaseEntry.releaseType,
212
- currentVersion: bump.currentVersion,
213
- newVersion: bump.newVersion,
214
- tag: newTag,
215
- bumpedFiles: bump.files,
216
- changelogFiles,
217
- commits: directResult?.commits,
218
- unparseableCommits: directResult?.unparseableCommits,
219
- propagatedFrom: releaseEntry.propagatedFrom
220
- });
309
+ maybeWritePreviews(workspace, newTag, firstChangelogJsonPath, previewOptions, dryRun);
310
+ return changelogFiles;
221
311
  }
222
- return { tags, modifiedFiles };
312
+ const tagPattern = buildTagPattern(getAllTagPrefixes(workspace));
313
+ for (const changelogPath of workspace.changelogPaths) {
314
+ changelogFiles.push(
315
+ ...generateChangelog(config, changelogPath, newTag, dryRun, {
316
+ tagPattern,
317
+ includePaths: workspace.paths
318
+ })
319
+ );
320
+ }
321
+ if (config.changelogJson.enabled) {
322
+ for (const changelogPath of workspace.changelogPaths) {
323
+ const jsonFiles = generateChangelogJson(config, changelogPath, newTag, dryRun, {
324
+ tagPattern,
325
+ includePaths: workspace.paths
326
+ });
327
+ modifiedFiles.push(...jsonFiles);
328
+ firstChangelogJsonPath ??= jsonFiles[0];
329
+ }
330
+ }
331
+ maybeWritePreviews(workspace, newTag, firstChangelogJsonPath, previewOptions, dryRun);
332
+ return changelogFiles;
333
+ }
334
+ function maybeWritePreviews(workspace, newTag, changelogJsonPath, previewOptions, dryRun) {
335
+ if (!previewOptions.enabled || changelogJsonPath === void 0) {
336
+ return;
337
+ }
338
+ writeReleaseNotesPreviews({
339
+ workspacePath: workspace.workspacePath,
340
+ tag: newTag,
341
+ changelogJsonPath,
342
+ sectionOrder: previewOptions.sectionOrder,
343
+ dryRun
344
+ });
223
345
  }
224
346
  function runFormatCommand(config, tags, modifiedFiles, dryRun) {
225
347
  const formatCommandStr = config.formatCommand ?? (hasPrettierConfig() ? "npx prettier --write" : void 0);
@@ -239,22 +361,20 @@ function runFormatCommand(config, tags, modifiedFiles, dryRun) {
239
361
  }
240
362
  return { command: fullCommand, executed: true, files: modifiedFiles };
241
363
  }
242
- function findComponent(components, dir) {
243
- return components.find((c) => c.dir === dir);
364
+ function findWorkspace(workspaces, dir) {
365
+ return workspaces.find((w) => w.dir === dir);
244
366
  }
245
- function hasVersionField(value) {
246
- return typeof value === "object" && value !== null && "version" in value && typeof value.version === "string";
247
- }
248
- function readCurrentVersion(filePath) {
249
- try {
250
- const content = readFileSync(filePath, "utf8");
251
- const parsed = JSON.parse(content);
252
- if (hasVersionField(parsed)) {
253
- return parsed.version;
254
- }
255
- } catch {
256
- }
257
- return void 0;
367
+ function maybeEmitBaselineHint(workspace, knownPrefixes, state) {
368
+ if (state.emitted) return;
369
+ if ((workspace.legacyIdentities?.length ?? 0) > 0) return;
370
+ const candidates = detectUndeclaredTagPrefixes(knownPrefixes);
371
+ if (candidates.length === 0) return;
372
+ const totalTags = candidates.reduce((sum, candidate) => sum + candidate.tagCount, 0);
373
+ const example = candidates[0]?.exampleTags[0] ?? `${candidates[0]?.prefix ?? ""}?`;
374
+ console.error(
375
+ `Hint: no baseline tag found for ${workspace.dir} under '${workspace.tagPrefix}', but ${totalTags} candidate-shaped tags exist (e.g., ${example}). Run 'release-kit show-tag-prefixes' to check for undeclared legacy prefixes.`
376
+ );
377
+ state.emitted = true;
258
378
  }
259
379
  function topologicalSort(releaseSet, graph) {
260
380
  const releaseDirs = new Set(releaseSet.keys());
@@ -314,6 +434,9 @@ function topologicalSort(releaseSet, graph) {
314
434
  }
315
435
  return { sorted, cyclicDirs };
316
436
  }
437
+ function getAllTagPrefixes(workspace) {
438
+ return [workspace.tagPrefix, ...workspace.legacyIdentities?.map((identity) => identity.tagPrefix) ?? []];
439
+ }
317
440
  export {
318
441
  releasePrepareMono
319
442
  };
@@ -2,6 +2,7 @@ import type { ChangelogAudience, ChangelogEntry, ChangelogSection } from './type
2
2
  export interface RenderOptions {
3
3
  filter?: (section: ChangelogSection) => boolean;
4
4
  includeHeading?: boolean;
5
+ sectionOrder?: string[];
5
6
  }
6
7
  export declare function matchesAudience(audience: ChangelogAudience): (section: ChangelogSection) => boolean;
7
8
  export declare function renderReleaseNotesSingle(entry: ChangelogEntry, options?: RenderOptions): string;
@@ -10,7 +10,9 @@ function matchesAudience(audience) {
10
10
  function renderReleaseNotesSingle(entry, options) {
11
11
  const filter = options?.filter;
12
12
  const includeHeading = options?.includeHeading ?? true;
13
- const sections = filter !== void 0 ? entry.sections.filter(filter) : entry.sections;
13
+ const sectionOrder = options?.sectionOrder;
14
+ const filtered = filter !== void 0 ? entry.sections.filter(filter) : entry.sections;
15
+ const sections = sectionOrder !== void 0 ? sortSectionsByOrder(filtered, sectionOrder) : filtered;
14
16
  if (sections.length === 0) {
15
17
  return "";
16
18
  }
@@ -23,12 +25,37 @@ function renderReleaseNotesSingle(entry, options) {
23
25
  lines.push("");
24
26
  }
25
27
  lines.push(`### ${section.title}`, "");
26
- for (const item of section.items) {
28
+ for (const [index, item] of section.items.entries()) {
27
29
  lines.push(`- ${item.description}`);
30
+ if (item.body !== void 0 && item.body.length > 0) {
31
+ lines.push("", ...indentBodyLines(item.body));
32
+ if (index < section.items.length - 1) {
33
+ lines.push("");
34
+ }
35
+ }
28
36
  }
29
37
  }
30
38
  return lines.join("\n") + "\n";
31
39
  }
40
+ function sortSectionsByOrder(sections, order) {
41
+ const priority = /* @__PURE__ */ new Map();
42
+ for (const [index, title] of order.entries()) {
43
+ priority.set(title, index);
44
+ }
45
+ const indexed = sections.map((section, index) => ({ section, index }));
46
+ indexed.sort((a, b) => {
47
+ const priorityA = priority.get(a.section.title) ?? Number.POSITIVE_INFINITY;
48
+ const priorityB = priority.get(b.section.title) ?? Number.POSITIVE_INFINITY;
49
+ if (priorityA !== priorityB) {
50
+ return priorityA - priorityB;
51
+ }
52
+ return a.index - b.index;
53
+ });
54
+ return indexed.map(({ section }) => section);
55
+ }
56
+ function indentBodyLines(body) {
57
+ return body.split("\n").map((line) => line.length === 0 ? "" : ` ${line}`);
58
+ }
32
59
  function renderReleaseNotesMulti(entries, options) {
33
60
  const parts = entries.map((entry) => renderReleaseNotesSingle(entry, options)).filter((part) => part.length > 0);
34
61
  return parts.join("\n");