@williamthorsen/release-kit 4.8.0 → 5.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 (100) hide show
  1. package/CHANGELOG.md +134 -4
  2. package/README.md +404 -40
  3. package/cliff.toml.template +2 -1
  4. package/dist/esm/.cache +1 -1
  5. package/dist/esm/assertCleanWorkingTree.js +1 -1
  6. package/dist/esm/bin/release-kit.js +45 -14
  7. package/dist/esm/buildChangelogEntries.d.ts +3 -0
  8. package/dist/esm/{generateChangelogJson.js → buildChangelogEntries.js} +40 -77
  9. package/dist/esm/buildDependencyGraph.d.ts +4 -3
  10. package/dist/esm/buildDependencyGraph.js +18 -11
  11. package/dist/esm/buildReleaseSummary.js +12 -4
  12. package/dist/esm/buildSyntheticChangelogEntry.d.ts +5 -0
  13. package/dist/esm/buildSyntheticChangelogEntry.js +13 -0
  14. package/dist/esm/bumpAllVersions.d.ts +1 -0
  15. package/dist/esm/bumpAllVersions.js +16 -2
  16. package/dist/esm/bumpVersion.js +3 -0
  17. package/dist/esm/changelogJsonFile.d.ts +4 -0
  18. package/dist/esm/changelogJsonFile.js +68 -0
  19. package/dist/esm/commitCommand.js +1 -1
  20. package/dist/esm/compareVersions.d.ts +1 -0
  21. package/dist/esm/compareVersions.js +27 -0
  22. package/dist/esm/createGithubRelease.d.ts +6 -2
  23. package/dist/esm/createGithubRelease.js +17 -17
  24. package/dist/esm/createGithubReleaseCommand.d.ts +1 -0
  25. package/dist/esm/createGithubReleaseCommand.js +41 -0
  26. package/dist/esm/decideRelease.d.ts +25 -0
  27. package/dist/esm/decideRelease.js +28 -0
  28. package/dist/esm/defaults.d.ts +1 -0
  29. package/dist/esm/defaults.js +7 -3
  30. package/dist/esm/deriveWorkspaceConfig.d.ts +2 -0
  31. package/dist/esm/deriveWorkspaceConfig.js +37 -0
  32. package/dist/esm/detectUndeclaredTagPrefixes.d.ts +7 -0
  33. package/dist/esm/detectUndeclaredTagPrefixes.js +46 -0
  34. package/dist/esm/generateChangelogs.d.ts +1 -1
  35. package/dist/esm/generateChangelogs.js +14 -3
  36. package/dist/esm/getCommitsSinceTarget.d.ts +1 -1
  37. package/dist/esm/getCommitsSinceTarget.js +8 -4
  38. package/dist/esm/index.d.ts +2 -39
  39. package/dist/esm/index.js +0 -75
  40. package/dist/esm/init/initCommand.js +1 -1
  41. package/dist/esm/init/scaffold.d.ts +1 -1
  42. package/dist/esm/init/scaffold.js +8 -5
  43. package/dist/esm/init/templates.d.ts +1 -0
  44. package/dist/esm/init/templates.js +35 -5
  45. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +6 -1
  46. package/dist/esm/injectReleaseNotesIntoReadme.js +20 -7
  47. package/dist/esm/loadConfig.d.ts +12 -2
  48. package/dist/esm/loadConfig.js +161 -14
  49. package/dist/esm/parseRequestedTags.d.ts +1 -0
  50. package/dist/esm/parseRequestedTags.js +10 -0
  51. package/dist/esm/prepareCommand.d.ts +3 -1
  52. package/dist/esm/prepareCommand.js +121 -31
  53. package/dist/esm/previewTagPrefixes.d.ts +30 -0
  54. package/dist/esm/previewTagPrefixes.js +120 -0
  55. package/dist/esm/propagateBumps.d.ts +1 -0
  56. package/dist/esm/propagateBumps.js +1 -1
  57. package/dist/esm/publish.d.ts +0 -1
  58. package/dist/esm/publish.js +3 -3
  59. package/dist/esm/publishCommand.js +18 -14
  60. package/dist/esm/pushCommand.js +5 -4
  61. package/dist/esm/readCurrentVersion.d.ts +1 -0
  62. package/dist/esm/readCurrentVersion.js +21 -0
  63. package/dist/esm/releasePrepare.d.ts +2 -0
  64. package/dist/esm/releasePrepare.js +140 -54
  65. package/dist/esm/releasePrepareMono.js +312 -143
  66. package/dist/esm/releasePrepareProject.d.ts +9 -0
  67. package/dist/esm/releasePrepareProject.js +109 -0
  68. package/dist/esm/renderReleaseNotes.d.ts +1 -0
  69. package/dist/esm/renderReleaseNotes.js +29 -2
  70. package/dist/esm/reportPrepare.js +146 -73
  71. package/dist/esm/resolveCliffConfigPath.js +1 -1
  72. package/dist/esm/resolveCommandTags.d.ts +1 -1
  73. package/dist/esm/resolveCommandTags.js +17 -13
  74. package/dist/esm/resolveReleaseNotesConfig.d.ts +8 -1
  75. package/dist/esm/resolveReleaseNotesConfig.js +17 -7
  76. package/dist/esm/resolveReleaseTags.d.ts +2 -1
  77. package/dist/esm/resolveReleaseTags.js +19 -14
  78. package/dist/esm/showTagPrefixesCommand.d.ts +1 -0
  79. package/dist/esm/showTagPrefixesCommand.js +84 -0
  80. package/dist/esm/sync-labels/initCommand.js +1 -1
  81. package/dist/esm/sync-labels/presets.js +1 -1
  82. package/dist/esm/tagCommand.js +1 -1
  83. package/dist/esm/types.d.ts +77 -19
  84. package/dist/esm/validateConfig.js +205 -36
  85. package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -0
  86. package/dist/esm/validateOnlyExcludesStrandedDependents.js +109 -0
  87. package/dist/esm/version.d.ts +1 -1
  88. package/dist/esm/version.js +1 -1
  89. package/dist/esm/writeReleaseNotesPreviews.d.ts +18 -0
  90. package/dist/esm/writeReleaseNotesPreviews.js +65 -0
  91. package/package.json +5 -2
  92. package/presets/labels/common.yaml +9 -6
  93. package/schemas/label-map.json +24 -0
  94. package/dist/esm/component.d.ts +0 -2
  95. package/dist/esm/component.js +0 -14
  96. package/dist/esm/findPackageRoot.d.ts +0 -1
  97. package/dist/esm/findPackageRoot.js +0 -17
  98. package/dist/esm/generateChangelogJson.d.ts +0 -7
  99. package/dist/esm/githubReleaseCommand.d.ts +0 -1
  100. package/dist/esm/githubReleaseCommand.js +0 -35
@@ -1,35 +1,51 @@
1
1
  import { execSync } from "node:child_process";
2
- import { readFileSync } from "node:fs";
2
+ import { buildChangelogEntries } from "./buildChangelogEntries.js";
3
3
  import { buildDependencyGraph } from "./buildDependencyGraph.js";
4
- import { bumpAllVersions } from "./bumpAllVersions.js";
4
+ import { buildSyntheticChangelogEntry } from "./buildSyntheticChangelogEntry.js";
5
+ import { bumpAllVersions, setAllVersions } from "./bumpAllVersions.js";
6
+ import { resolveChangelogJsonPath, upsertChangelogJson } from "./changelogJsonFile.js";
7
+ import { isForwardVersion } from "./compareVersions.js";
8
+ import { decideRelease } from "./decideRelease.js";
5
9
  import { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
6
- import { determineBumpFromCommits } from "./determineBumpFromCommits.js";
7
- import { generateChangelogJson, generateSyntheticChangelogJson } from "./generateChangelogJson.js";
10
+ import { detectUndeclaredTagPrefixes } from "./detectUndeclaredTagPrefixes.js";
8
11
  import { buildTagPattern, generateChangelog } from "./generateChangelogs.js";
9
12
  import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
10
13
  import { hasPrettierConfig } from "./hasPrettierConfig.js";
14
+ import { resolveWorkTypes } from "./loadConfig.js";
11
15
  import { propagateBumps } from "./propagateBumps.js";
16
+ import { readCurrentVersion } from "./readCurrentVersion.js";
17
+ import { releasePrepareProject } from "./releasePrepareProject.js";
18
+ import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
19
+ import { writeReleaseNotesPreviews } from "./writeReleaseNotesPreviews.js";
12
20
  import { writeSyntheticChangelog } from "./writeSyntheticChangelog.js";
13
21
  function releasePrepareMono(config, options) {
14
- const { dryRun } = options;
22
+ const { dryRun, withReleaseNotes } = options;
23
+ if (withReleaseNotes === true && !config.changelogJson.enabled) {
24
+ console.warn("Warning: --with-release-notes requires changelogJson.enabled; skipping preview generation");
25
+ }
26
+ const sectionOrder = deriveSectionOrder(resolveWorkTypes(config.workTypes));
15
27
  const { directBumps, directResults, skippedResults, currentVersions } = determineDirectBumps(config, options);
16
28
  const previousTags = /* @__PURE__ */ new Map();
17
29
  for (const result of directResults.values()) {
18
- previousTags.set(result.component.dir, result.tag);
30
+ previousTags.set(result.workspace.dir, result.tag);
19
31
  }
20
32
  for (const skipped of skippedResults) {
21
- previousTags.set(skipped.component.dir, skipped.tag);
33
+ previousTags.set(skipped.workspace.dir, skipped.tag);
22
34
  }
23
- const graph = buildDependencyGraph(config.components);
35
+ const graph = buildDependencyGraph(config.workspaces);
24
36
  const fullReleaseSet = propagateBumps(directBumps, graph, currentVersions);
25
37
  const { sorted: sortedDirs, cyclicDirs } = topologicalSort(fullReleaseSet, graph);
26
38
  const warnings = [];
27
39
  if (cyclicDirs.length > 0) {
28
40
  warnings.push(
29
- `Circular workspace dependencies detected among: ${cyclicDirs.join(", ")}. Propagation metadata may be incomplete for these components.`
41
+ `Circular workspace dependencies detected among: ${cyclicDirs.join(", ")}. Propagation metadata may be incomplete for these workspaces.`
30
42
  );
31
43
  }
32
- const components = collectSkippedComponents(skippedResults, fullReleaseSet);
44
+ const workspaces = collectSkippedWorkspaces(skippedResults, fullReleaseSet);
45
+ const previewOptions = {
46
+ enabled: withReleaseNotes === true && config.changelogJson.enabled,
47
+ sectionOrder
48
+ };
33
49
  const { tags, modifiedFiles } = executeReleaseSet(
34
50
  sortedDirs,
35
51
  fullReleaseSet,
@@ -37,108 +53,148 @@ function releasePrepareMono(config, options) {
37
53
  directResults,
38
54
  previousTags,
39
55
  dryRun,
40
- components
56
+ workspaces,
57
+ previewOptions
41
58
  );
42
- const configOrder = new Map(config.components.map((c, i) => [c.dir, i]));
43
- components.sort((a, b) => {
59
+ const configOrder = new Map(config.workspaces.map((w, i) => [w.dir, i]));
60
+ workspaces.sort((a, b) => {
44
61
  const orderA = configOrder.get(a.name ?? "") ?? 0;
45
62
  const orderB = configOrder.get(b.name ?? "") ?? 0;
46
63
  return orderA - orderB;
47
64
  });
65
+ let project;
66
+ if (config.project !== void 0) {
67
+ project = tryStage("project release stage", () => releasePrepareProject({ config, options, modifiedFiles, tags }));
68
+ }
48
69
  const formatCommand = runFormatCommand(config, tags, modifiedFiles, dryRun);
49
70
  return {
50
- components,
71
+ workspaces,
51
72
  tags,
52
73
  formatCommand,
53
74
  dryRun,
54
- ...warnings.length > 0 ? { warnings } : {}
75
+ ...warnings.length > 0 ? { warnings } : {},
76
+ ...project === void 0 ? {} : { project }
55
77
  };
56
78
  }
57
79
  function determineDirectBumps(config, options) {
58
- const { force, bumpOverride } = options;
80
+ const { force, bumpOverride, setVersion } = options;
81
+ if (setVersion !== void 0 && config.workspaces.length !== 1) {
82
+ throw new Error(`--set-version requires exactly one workspace; received ${config.workspaces.length}`);
83
+ }
59
84
  const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
60
85
  const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
61
86
  const directBumps = /* @__PURE__ */ new Map();
62
87
  const directResults = /* @__PURE__ */ new Map();
63
88
  const skippedResults = [];
64
89
  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);
90
+ const hintState = { emitted: false };
91
+ const knownPrefixes = config.workspaces.flatMap(getAllTagPrefixes);
92
+ for (const workspace of config.workspaces) {
93
+ const name = workspace.dir;
94
+ const stageLabel = workspaceStageLabel(workspace.dir);
95
+ const { tag, commits } = tryStage(
96
+ stageLabel,
97
+ () => getCommitsSinceTarget(getAllTagPrefixes(workspace), workspace.paths)
98
+ );
68
99
  const since = tag === void 0 ? "(no previous release found)" : `since ${tag}`;
69
- const primaryPackageFile = component.packageFiles[0];
100
+ if (tag === void 0) {
101
+ maybeEmitBaselineHint(workspace, knownPrefixes, hintState);
102
+ }
103
+ const primaryPackageFile = workspace.packageFiles[0];
70
104
  if (primaryPackageFile !== void 0) {
71
- const currentVersion = readCurrentVersion(primaryPackageFile);
105
+ const currentVersion = tryStage(stageLabel, () => readCurrentVersion(primaryPackageFile));
72
106
  if (currentVersion !== void 0) {
73
- currentVersions.set(component.dir, currentVersion);
107
+ currentVersions.set(workspace.dir, currentVersion);
74
108
  }
75
109
  }
76
- if (commits.length === 0 && !force) {
77
- skippedResults.push({
78
- component,
110
+ if (setVersion !== void 0) {
111
+ const currentVersion = currentVersions.get(workspace.dir);
112
+ if (currentVersion === void 0) {
113
+ throw new Error(
114
+ `Cannot validate --set-version: failed to read current version from ${primaryPackageFile ?? "(no package file)"}`
115
+ );
116
+ }
117
+ if (!isForwardVersion(currentVersion, setVersion)) {
118
+ throw new Error(`--set-version ${setVersion} is not greater than current version ${currentVersion}`);
119
+ }
120
+ directBumps.set(workspace.dir, { releaseType: "patch", newVersionOverride: setVersion });
121
+ directResults.set(workspace.dir, {
122
+ workspace,
79
123
  tag,
80
- commitCount: 0,
124
+ commits,
125
+ releaseType: void 0,
81
126
  parsedCommitCount: void 0,
82
127
  unparseableCommits: void 0,
83
- skipReason: `No changes for ${name} ${since}. Skipping.`
128
+ bumpOverride: void 0,
129
+ setVersion
84
130
  });
85
131
  continue;
86
132
  }
87
- let releaseType;
88
- let parsedCommitCount;
89
- let unparseableCommits;
90
- if (bumpOverride === void 0) {
91
- const determination = determineBumpFromCommits(commits, workTypes, versionPatterns, config.scopeAliases);
92
- parsedCommitCount = determination.parsedCommitCount;
93
- unparseableCommits = determination.unparseableCommits;
94
- releaseType = determination.releaseType;
95
- } else {
96
- releaseType = bumpOverride;
97
- }
98
- if (releaseType === void 0) {
133
+ const decision = tryStage(
134
+ stageLabel,
135
+ () => decideRelease({
136
+ commits,
137
+ force,
138
+ bumpOverride,
139
+ workTypes,
140
+ versionPatterns,
141
+ scopeAliases: config.scopeAliases,
142
+ skipReasons: {
143
+ noCommits: `No commits for ${name} ${since}. Pass --force to release at patch. Skipping.`,
144
+ noBumpWorthy: `No bump-worthy commits for ${name} ${since}. Pass --force to release at patch (or --force --bump=X for a different level). Skipping.`
145
+ }
146
+ })
147
+ );
148
+ if (decision.outcome === "skip") {
99
149
  skippedResults.push({
100
- component,
150
+ workspace,
101
151
  tag,
102
152
  commitCount: commits.length,
103
- parsedCommitCount,
104
- unparseableCommits,
105
- skipReason: `No release-worthy changes for ${name} ${since}. Skipping.`
153
+ parsedCommitCount: decision.parsedCommitCount,
154
+ unparseableCommits: decision.unparseableCommits,
155
+ skipReason: decision.skipReason
106
156
  });
107
157
  continue;
108
158
  }
109
- directBumps.set(component.dir, { releaseType });
110
- directResults.set(component.dir, {
111
- component,
159
+ directBumps.set(workspace.dir, { releaseType: decision.releaseType });
160
+ directResults.set(workspace.dir, {
161
+ workspace,
112
162
  tag,
113
163
  commits,
114
- releaseType,
115
- parsedCommitCount,
116
- unparseableCommits
164
+ releaseType: decision.releaseType,
165
+ parsedCommitCount: decision.parsedCommitCount,
166
+ unparseableCommits: decision.unparseableCommits,
167
+ bumpOverride
117
168
  });
118
169
  }
119
170
  return { directBumps, directResults, skippedResults, currentVersions };
120
171
  }
121
- function collectSkippedComponents(skippedResults, fullReleaseSet) {
122
- const components = [];
172
+ function collectSkippedWorkspaces(skippedResults, fullReleaseSet) {
173
+ const workspaces = [];
123
174
  for (const skipped of skippedResults) {
124
- if (fullReleaseSet.has(skipped.component.dir)) {
175
+ if (fullReleaseSet.has(skipped.workspace.dir)) {
125
176
  continue;
126
177
  }
127
- components.push({
128
- name: skipped.component.dir,
178
+ const result = {
179
+ name: skipped.workspace.dir,
129
180
  status: "skipped",
130
- previousTag: skipped.tag,
131
181
  commitCount: skipped.commitCount,
132
- parsedCommitCount: skipped.parsedCommitCount,
133
- unparseableCommits: skipped.unparseableCommits,
134
- bumpedFiles: [],
135
- changelogFiles: [],
136
182
  skipReason: skipped.skipReason
137
- });
183
+ };
184
+ if (skipped.tag !== void 0) {
185
+ result.previousTag = skipped.tag;
186
+ }
187
+ if (skipped.parsedCommitCount !== void 0) {
188
+ result.parsedCommitCount = skipped.parsedCommitCount;
189
+ }
190
+ if (skipped.unparseableCommits !== void 0) {
191
+ result.unparseableCommits = skipped.unparseableCommits;
192
+ }
193
+ workspaces.push(result);
138
194
  }
139
- return components;
195
+ return workspaces;
140
196
  }
141
- function executeReleaseSet(sortedDirs, fullReleaseSet, config, directResults, previousTags, dryRun, components) {
197
+ function executeReleaseSet(sortedDirs, fullReleaseSet, config, directResults, previousTags, dryRun, workspaces, previewOptions) {
142
198
  const tags = [];
143
199
  const modifiedFiles = [];
144
200
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -147,79 +203,178 @@ function executeReleaseSet(sortedDirs, fullReleaseSet, config, directResults, pr
147
203
  if (releaseEntry === void 0) {
148
204
  continue;
149
205
  }
150
- const component = findComponent(config.components, dir);
151
- if (component === void 0) {
206
+ const workspace = findWorkspace(config.workspaces, dir);
207
+ if (workspace === void 0) {
152
208
  continue;
153
209
  }
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
- })
193
- );
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);
210
+ tryStage(
211
+ workspaceStageLabel(dir),
212
+ () => executeWorkspaceRelease({
213
+ dir,
214
+ workspace,
215
+ releaseEntry,
216
+ directResult: directResults.get(dir),
217
+ previousTags,
218
+ config,
219
+ dryRun,
220
+ today,
221
+ tags,
222
+ modifiedFiles,
223
+ workspaces,
224
+ previewOptions
225
+ })
226
+ );
227
+ }
228
+ return { tags, modifiedFiles };
229
+ }
230
+ function executeWorkspaceRelease(args) {
231
+ const {
232
+ dir,
233
+ workspace,
234
+ releaseEntry,
235
+ directResult,
236
+ previousTags,
237
+ config,
238
+ dryRun,
239
+ today,
240
+ tags,
241
+ modifiedFiles,
242
+ workspaces,
243
+ previewOptions
244
+ } = args;
245
+ const setVersionTarget = directResult?.setVersion;
246
+ const bump = setVersionTarget === void 0 ? bumpAllVersions(workspace.packageFiles, releaseEntry.releaseType, dryRun) : setAllVersions(workspace.packageFiles, setVersionTarget, dryRun);
247
+ const newTag = `${workspace.tagPrefix}${bump.newVersion}`;
248
+ tags.push(newTag);
249
+ modifiedFiles.push(...workspace.packageFiles, ...workspace.changelogPaths.map((p) => `${p}/CHANGELOG.md`));
250
+ const isPropagationOnly = directResult === void 0;
251
+ const changelogFiles = generateWorkspaceChangelogs({
252
+ workspace,
253
+ releaseEntry,
254
+ newTag,
255
+ newVersion: bump.newVersion,
256
+ isPropagationOnly,
257
+ config,
258
+ dryRun,
259
+ today,
260
+ modifiedFiles,
261
+ previewOptions
262
+ });
263
+ const released = {
264
+ name: dir,
265
+ status: "released",
266
+ commitCount: directResult?.commits.length ?? 0,
267
+ currentVersion: bump.currentVersion,
268
+ newVersion: bump.newVersion,
269
+ tag: newTag,
270
+ bumpedFiles: bump.files,
271
+ changelogFiles
272
+ };
273
+ const previousTag = directResult?.tag ?? previousTags.get(dir);
274
+ if (previousTag !== void 0) {
275
+ released.previousTag = previousTag;
276
+ }
277
+ if (directResult?.parsedCommitCount !== void 0) {
278
+ released.parsedCommitCount = directResult.parsedCommitCount;
279
+ }
280
+ if (setVersionTarget === void 0) {
281
+ released.releaseType = releaseEntry.releaseType;
282
+ }
283
+ if (directResult?.commits !== void 0) {
284
+ released.commits = directResult.commits;
285
+ }
286
+ if (directResult?.unparseableCommits !== void 0) {
287
+ released.unparseableCommits = directResult.unparseableCommits;
288
+ }
289
+ if (releaseEntry.propagatedFrom !== void 0) {
290
+ released.propagatedFrom = releaseEntry.propagatedFrom;
291
+ }
292
+ if (directResult?.bumpOverride !== void 0) {
293
+ released.bumpOverride = directResult.bumpOverride;
294
+ }
295
+ if (setVersionTarget !== void 0) {
296
+ released.setVersion = setVersionTarget;
297
+ }
298
+ workspaces.push(released);
299
+ }
300
+ function generateWorkspaceChangelogs(args) {
301
+ const {
302
+ workspace,
303
+ releaseEntry,
304
+ newTag,
305
+ newVersion,
306
+ isPropagationOnly,
307
+ config,
308
+ dryRun,
309
+ today,
310
+ modifiedFiles,
311
+ previewOptions
312
+ } = args;
313
+ const changelogFiles = [];
314
+ let firstChangelogJsonPath;
315
+ if (isPropagationOnly && releaseEntry.propagatedFrom !== void 0) {
316
+ for (const changelogPath of workspace.changelogPaths) {
317
+ changelogFiles.push(
318
+ writeSyntheticChangelog({
319
+ changelogPath,
320
+ newVersion,
321
+ date: today,
322
+ propagatedFrom: releaseEntry.propagatedFrom,
323
+ dryRun
324
+ })
325
+ );
326
+ }
327
+ if (config.changelogJson.enabled) {
328
+ const syntheticEntry = buildSyntheticChangelogEntry(releaseEntry.propagatedFrom, newVersion, today);
329
+ for (const changelogPath of workspace.changelogPaths) {
330
+ const jsonPath = resolveChangelogJsonPath(config, changelogPath);
331
+ if (!dryRun) {
332
+ upsertChangelogJson(jsonPath, [syntheticEntry]);
202
333
  }
334
+ modifiedFiles.push(jsonPath);
335
+ firstChangelogJsonPath ??= jsonPath;
203
336
  }
204
337
  }
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
338
+ maybeWritePreviews(workspace, newTag, firstChangelogJsonPath, previewOptions, dryRun);
339
+ return changelogFiles;
340
+ }
341
+ const tagPattern = buildTagPattern(getAllTagPrefixes(workspace));
342
+ for (const changelogPath of workspace.changelogPaths) {
343
+ changelogFiles.push(
344
+ ...generateChangelog(config, changelogPath, newTag, dryRun, {
345
+ tagPattern,
346
+ includePaths: workspace.paths
347
+ })
348
+ );
349
+ }
350
+ if (config.changelogJson.enabled) {
351
+ const entries = buildChangelogEntries(config, newTag, {
352
+ tagPattern,
353
+ includePaths: workspace.paths
220
354
  });
355
+ for (const changelogPath of workspace.changelogPaths) {
356
+ const jsonPath = resolveChangelogJsonPath(config, changelogPath);
357
+ if (!dryRun) {
358
+ upsertChangelogJson(jsonPath, entries);
359
+ }
360
+ modifiedFiles.push(jsonPath);
361
+ firstChangelogJsonPath ??= jsonPath;
362
+ }
221
363
  }
222
- return { tags, modifiedFiles };
364
+ maybeWritePreviews(workspace, newTag, firstChangelogJsonPath, previewOptions, dryRun);
365
+ return changelogFiles;
366
+ }
367
+ function maybeWritePreviews(workspace, newTag, changelogJsonPath, previewOptions, dryRun) {
368
+ if (!previewOptions.enabled || changelogJsonPath === void 0) {
369
+ return;
370
+ }
371
+ writeReleaseNotesPreviews({
372
+ workspacePath: workspace.workspacePath,
373
+ tag: newTag,
374
+ changelogJsonPath,
375
+ sectionOrder: previewOptions.sectionOrder,
376
+ dryRun
377
+ });
223
378
  }
224
379
  function runFormatCommand(config, tags, modifiedFiles, dryRun) {
225
380
  const formatCommandStr = config.formatCommand ?? (hasPrettierConfig() ? "npx prettier --write" : void 0);
@@ -233,28 +388,39 @@ function runFormatCommand(config, tags, modifiedFiles, dryRun) {
233
388
  try {
234
389
  execSync(fullCommand, { stdio: "inherit" });
235
390
  } catch (error) {
236
- throw new Error(
237
- `Format command failed ('${fullCommand}'): ${error instanceof Error ? error.message : String(error)}`
238
- );
391
+ const baseMessage = error instanceof Error ? error.message : String(error);
392
+ throw new Error(`format stage: ${baseMessage} (command: '${fullCommand}')`, { cause: error });
239
393
  }
240
394
  return { command: fullCommand, executed: true, files: modifiedFiles };
241
395
  }
242
- function findComponent(components, dir) {
243
- return components.find((c) => c.dir === dir);
396
+ function findWorkspace(workspaces, dir) {
397
+ return workspaces.find((w) => w.dir === dir);
244
398
  }
245
- function hasVersionField(value) {
246
- return typeof value === "object" && value !== null && "version" in value && typeof value.version === "string";
399
+ function wrapStageError(stageLabel, error) {
400
+ const message = error instanceof Error ? error.message : String(error);
401
+ return new Error(`${stageLabel}: ${message}`, { cause: error });
247
402
  }
248
- function readCurrentVersion(filePath) {
403
+ function tryStage(stageLabel, fn) {
249
404
  try {
250
- const content = readFileSync(filePath, "utf8");
251
- const parsed = JSON.parse(content);
252
- if (hasVersionField(parsed)) {
253
- return parsed.version;
254
- }
255
- } catch {
405
+ return fn();
406
+ } catch (error) {
407
+ throw wrapStageError(stageLabel, error);
256
408
  }
257
- return void 0;
409
+ }
410
+ function workspaceStageLabel(dir) {
411
+ return `workspace '${dir}' release stage`;
412
+ }
413
+ function maybeEmitBaselineHint(workspace, knownPrefixes, state) {
414
+ if (state.emitted) return;
415
+ if ((workspace.legacyIdentities?.length ?? 0) > 0) return;
416
+ const candidates = detectUndeclaredTagPrefixes(knownPrefixes);
417
+ if (candidates.length === 0) return;
418
+ const totalTags = candidates.reduce((sum, candidate) => sum + candidate.tagCount, 0);
419
+ const example = candidates[0]?.exampleTags[0] ?? `${candidates[0]?.prefix ?? ""}?`;
420
+ console.error(
421
+ `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.`
422
+ );
423
+ state.emitted = true;
258
424
  }
259
425
  function topologicalSort(releaseSet, graph) {
260
426
  const releaseDirs = new Set(releaseSet.keys());
@@ -314,6 +480,9 @@ function topologicalSort(releaseSet, graph) {
314
480
  }
315
481
  return { sorted, cyclicDirs };
316
482
  }
483
+ function getAllTagPrefixes(workspace) {
484
+ return [workspace.tagPrefix, ...workspace.legacyIdentities?.map((identity) => identity.tagPrefix) ?? []];
485
+ }
317
486
  export {
318
487
  releasePrepareMono
319
488
  };
@@ -0,0 +1,9 @@
1
+ import type { ReleasePrepareOptions } from './releasePrepare.ts';
2
+ import type { MonorepoReleaseConfig, ProjectPrepareResult } from './types.ts';
3
+ export interface ReleasePrepareProjectArgs {
4
+ config: MonorepoReleaseConfig;
5
+ options: ReleasePrepareOptions;
6
+ modifiedFiles: string[];
7
+ tags: string[];
8
+ }
9
+ export declare function releasePrepareProject(args: ReleasePrepareProjectArgs): ProjectPrepareResult;