@williamthorsen/release-kit 5.0.0 → 5.2.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 (73) hide show
  1. package/CHANGELOG.md +149 -49
  2. package/README.md +275 -78
  3. package/cliff.toml.template +26 -17
  4. package/dist/esm/.cache +1 -1
  5. package/dist/esm/assertCleanWorkingTree.js +1 -1
  6. package/dist/esm/bin/release-kit.js +97 -4
  7. package/dist/esm/buildChangelogEntries.d.ts +4 -0
  8. package/dist/esm/buildChangelogEntries.js +173 -0
  9. package/dist/esm/buildDependencyGraph.d.ts +1 -0
  10. package/dist/esm/buildDependencyGraph.js +8 -1
  11. package/dist/esm/buildReleaseSummary.js +9 -1
  12. package/dist/esm/buildSyntheticChangelogEntry.d.ts +5 -0
  13. package/dist/esm/buildSyntheticChangelogEntry.js +13 -0
  14. package/dist/esm/changelogJsonFile.d.ts +4 -0
  15. package/dist/esm/changelogJsonFile.js +68 -0
  16. package/dist/esm/checkWorkTypesDrift.d.ts +11 -0
  17. package/dist/esm/checkWorkTypesDrift.js +110 -0
  18. package/dist/esm/collectPolicyViolations.d.ts +6 -0
  19. package/dist/esm/collectPolicyViolations.js +15 -0
  20. package/dist/esm/createGithubRelease.d.ts +12 -2
  21. package/dist/esm/createGithubRelease.js +12 -8
  22. package/dist/esm/createGithubReleaseCommand.js +10 -6
  23. package/dist/esm/decideRelease.d.ts +28 -0
  24. package/dist/esm/decideRelease.js +44 -0
  25. package/dist/esm/defaults.d.ts +8 -0
  26. package/dist/esm/defaults.js +43 -20
  27. package/dist/esm/deriveWorkspaceConfig.js +3 -0
  28. package/dist/esm/determineBumpFromCommits.d.ts +6 -1
  29. package/dist/esm/determineBumpFromCommits.js +9 -3
  30. package/dist/esm/generateChangelogs.js +14 -29
  31. package/dist/esm/index.d.ts +2 -43
  32. package/dist/esm/index.js +0 -82
  33. package/dist/esm/init/templates.js +2 -2
  34. package/dist/esm/loadConfig.d.ts +10 -1
  35. package/dist/esm/loadConfig.js +110 -24
  36. package/dist/esm/parseCommitMessage.d.ts +8 -2
  37. package/dist/esm/parseCommitMessage.js +32 -3
  38. package/dist/esm/prepareCommand.js +51 -9
  39. package/dist/esm/publish.d.ts +0 -1
  40. package/dist/esm/publish.js +3 -3
  41. package/dist/esm/publishCommand.js +31 -3
  42. package/dist/esm/releasePrepare.js +109 -41
  43. package/dist/esm/releasePrepareMono.js +156 -87
  44. package/dist/esm/releasePrepareProject.d.ts +9 -0
  45. package/dist/esm/releasePrepareProject.js +121 -0
  46. package/dist/esm/renderReleaseNotes.js +2 -1
  47. package/dist/esm/reportPrepare.js +88 -24
  48. package/dist/esm/resolveCommandTags.js +16 -6
  49. package/dist/esm/resolveReleaseTags.d.ts +8 -1
  50. package/dist/esm/resolveReleaseTags.js +11 -7
  51. package/dist/esm/runGitCliff.d.ts +2 -0
  52. package/dist/esm/runGitCliff.js +27 -0
  53. package/dist/esm/stripEmojiPrefix.d.ts +1 -0
  54. package/dist/esm/stripEmojiPrefix.js +7 -0
  55. package/dist/esm/syncWorkTypes.d.ts +10 -0
  56. package/dist/esm/syncWorkTypes.js +90 -0
  57. package/dist/esm/types.d.ts +72 -14
  58. package/dist/esm/validateConfig.js +26 -0
  59. package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -0
  60. package/dist/esm/validateOnlyExcludesStrandedDependents.js +109 -0
  61. package/dist/esm/work-types.json +127 -0
  62. package/dist/esm/work-types.schema.json +73 -0
  63. package/dist/esm/workTypesData.d.ts +14 -0
  64. package/dist/esm/workTypesData.js +59 -0
  65. package/dist/esm/workTypesUtils.d.ts +5 -0
  66. package/dist/esm/workTypesUtils.js +16 -0
  67. package/package.json +9 -3
  68. package/presets/labels/common.yaml +9 -6
  69. package/schemas/label-map.json +24 -0
  70. package/dist/esm/generateChangelogJson.d.ts +0 -7
  71. package/dist/esm/generateChangelogJson.js +0 -232
  72. package/dist/esm/version.d.ts +0 -1
  73. package/dist/esm/version.js +0 -4
@@ -1,9 +1,11 @@
1
1
  import { execSync } from "node:child_process";
2
+ import { buildChangelogEntries } from "./buildChangelogEntries.js";
2
3
  import { bumpAllVersions, setAllVersions } from "./bumpAllVersions.js";
4
+ import { resolveChangelogJsonPath, upsertChangelogJson } from "./changelogJsonFile.js";
5
+ import { createPolicyViolationCollector } from "./collectPolicyViolations.js";
3
6
  import { isForwardVersion } from "./compareVersions.js";
4
- import { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
7
+ import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
5
8
  import { determineBumpFromCommits } from "./determineBumpFromCommits.js";
6
- import { generateChangelogJson } from "./generateChangelogJson.js";
7
9
  import { generateChangelogs } from "./generateChangelogs.js";
8
10
  import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
9
11
  import { hasPrettierConfig } from "./hasPrettierConfig.js";
@@ -15,10 +17,12 @@ function releasePrepare(config, options) {
15
17
  const { dryRun, bumpOverride, setVersion, withReleaseNotes } = options;
16
18
  const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
17
19
  const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
20
+ const breakingPolicies = config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES;
18
21
  const { tag, commits } = getCommitsSinceTarget([config.tagPrefix]);
19
22
  let releaseType;
20
23
  let parsedCommitCount;
21
24
  let unparseableCommits;
25
+ const collector = createPolicyViolationCollector();
22
26
  let bump;
23
27
  if (setVersion !== void 0) {
24
28
  const primaryPackageFile = config.packageFiles[0];
@@ -35,7 +39,10 @@ function releasePrepare(config, options) {
35
39
  bump = setAllVersions(config.packageFiles, setVersion, dryRun);
36
40
  } else {
37
41
  if (bumpOverride === void 0) {
38
- const determination = determineBumpFromCommits(commits, workTypes, versionPatterns, config.scopeAliases);
42
+ const determination = determineBumpFromCommits(commits, workTypes, versionPatterns, config.scopeAliases, {
43
+ breakingPolicies,
44
+ onPolicyViolation: collector.onPolicyViolation
45
+ });
39
46
  parsedCommitCount = determination.parsedCommitCount;
40
47
  unparseableCommits = determination.unparseableCommits;
41
48
  releaseType = determination.releaseType;
@@ -43,19 +50,15 @@ function releasePrepare(config, options) {
43
50
  releaseType = bumpOverride;
44
51
  }
45
52
  if (releaseType === void 0) {
53
+ const skipped = buildSkippedSinglePackage({
54
+ commitCount: commits.length,
55
+ previousTag: tag,
56
+ parsedCommitCount,
57
+ unparseableCommits,
58
+ policyViolations: collector.violations.length > 0 ? collector.violations : void 0
59
+ });
46
60
  return {
47
- workspaces: [
48
- {
49
- status: "skipped",
50
- previousTag: tag,
51
- commitCount: commits.length,
52
- parsedCommitCount,
53
- unparseableCommits,
54
- bumpedFiles: [],
55
- changelogFiles: [],
56
- skipReason: "No release-worthy changes found. Skipping."
57
- }
58
- ],
61
+ workspaces: [skipped],
59
62
  tags: [],
60
63
  dryRun
61
64
  };
@@ -64,12 +67,7 @@ function releasePrepare(config, options) {
64
67
  }
65
68
  const newTag = `${config.tagPrefix}${bump.newVersion}`;
66
69
  const changelogFiles = generateChangelogs(config, newTag, dryRun);
67
- const changelogJsonFiles = [];
68
- if (config.changelogJson.enabled) {
69
- for (const changelogPath of config.changelogPaths) {
70
- changelogJsonFiles.push(...generateChangelogJson(config, changelogPath, newTag, dryRun));
71
- }
72
- }
70
+ const changelogJsonFiles = config.changelogJson.enabled ? buildAndPersistChangelogJson(config, newTag, dryRun) : [];
73
71
  maybeWriteSinglePackagePreviews(withReleaseNotes === true, config, newTag, changelogJsonFiles[0], dryRun);
74
72
  const formatCommandStr = config.formatCommand ?? (hasPrettierConfig() ? "npx prettier --write" : void 0);
75
73
  let formatCommand;
@@ -86,36 +84,106 @@ function releasePrepare(config, options) {
86
84
  try {
87
85
  execSync(fullCommand, { stdio: "inherit" });
88
86
  } catch (error) {
89
- throw new Error(
90
- `Format command failed ('${fullCommand}'): ${error instanceof Error ? error.message : String(error)}`
91
- );
87
+ const baseMessage = error instanceof Error ? error.message : String(error);
88
+ throw new Error(`format stage: ${baseMessage} (command: '${fullCommand}')`, { cause: error });
92
89
  }
93
90
  formatCommand = { command: fullCommand, executed: true, files: modifiedFiles };
94
91
  }
95
92
  }
93
+ const released = buildReleasedSinglePackage({
94
+ commits,
95
+ bump,
96
+ newTag,
97
+ changelogFiles,
98
+ previousTag: tag,
99
+ parsedCommitCount,
100
+ releaseType,
101
+ unparseableCommits,
102
+ policyViolations: collector.violations.length > 0 ? collector.violations : void 0,
103
+ setVersion
104
+ });
96
105
  return {
97
- workspaces: [
98
- {
99
- status: "released",
100
- previousTag: tag,
101
- commitCount: commits.length,
102
- parsedCommitCount,
103
- releaseType,
104
- currentVersion: bump.currentVersion,
105
- newVersion: bump.newVersion,
106
- tag: newTag,
107
- bumpedFiles: bump.files,
108
- changelogFiles,
109
- commits,
110
- unparseableCommits,
111
- ...setVersion === void 0 ? {} : { setVersion }
112
- }
113
- ],
106
+ workspaces: [released],
114
107
  tags: [newTag],
115
108
  formatCommand,
116
109
  dryRun
117
110
  };
118
111
  }
112
+ function buildSkippedSinglePackage(args) {
113
+ const skipped = {
114
+ status: "skipped",
115
+ commitCount: args.commitCount,
116
+ skipReason: "No release-worthy changes found. Skipping."
117
+ };
118
+ if (args.previousTag !== void 0) {
119
+ skipped.previousTag = args.previousTag;
120
+ }
121
+ if (args.parsedCommitCount !== void 0) {
122
+ skipped.parsedCommitCount = args.parsedCommitCount;
123
+ }
124
+ if (args.unparseableCommits !== void 0) {
125
+ skipped.unparseableCommits = args.unparseableCommits;
126
+ }
127
+ if (args.policyViolations !== void 0) {
128
+ skipped.policyViolations = args.policyViolations;
129
+ }
130
+ return skipped;
131
+ }
132
+ function buildReleasedSinglePackage(args) {
133
+ const {
134
+ commits,
135
+ bump,
136
+ newTag,
137
+ changelogFiles,
138
+ previousTag,
139
+ parsedCommitCount,
140
+ releaseType,
141
+ unparseableCommits,
142
+ policyViolations,
143
+ setVersion
144
+ } = args;
145
+ const released = {
146
+ status: "released",
147
+ commitCount: commits.length,
148
+ currentVersion: bump.currentVersion,
149
+ newVersion: bump.newVersion,
150
+ tag: newTag,
151
+ bumpedFiles: bump.files,
152
+ changelogFiles,
153
+ commits
154
+ };
155
+ if (previousTag !== void 0) {
156
+ released.previousTag = previousTag;
157
+ }
158
+ if (parsedCommitCount !== void 0) {
159
+ released.parsedCommitCount = parsedCommitCount;
160
+ }
161
+ if (releaseType !== void 0) {
162
+ released.releaseType = releaseType;
163
+ }
164
+ if (unparseableCommits !== void 0) {
165
+ released.unparseableCommits = unparseableCommits;
166
+ }
167
+ if (policyViolations !== void 0) {
168
+ released.policyViolations = policyViolations;
169
+ }
170
+ if (setVersion !== void 0) {
171
+ released.setVersion = setVersion;
172
+ }
173
+ return released;
174
+ }
175
+ function buildAndPersistChangelogJson(config, newTag, dryRun) {
176
+ const changelogJsonFiles = [];
177
+ const entries = buildChangelogEntries(config, newTag);
178
+ for (const changelogPath of config.changelogPaths) {
179
+ const jsonPath = resolveChangelogJsonPath(config, changelogPath);
180
+ if (!dryRun) {
181
+ upsertChangelogJson(jsonPath, entries);
182
+ }
183
+ changelogJsonFiles.push(jsonPath);
184
+ }
185
+ return changelogJsonFiles;
186
+ }
119
187
  function maybeWriteSinglePackagePreviews(withReleaseNotes, config, newTag, changelogJsonPath, dryRun) {
120
188
  if (!withReleaseNotes) {
121
189
  return;
@@ -1,17 +1,21 @@
1
1
  import { execSync } from "node:child_process";
2
+ import { buildChangelogEntries } from "./buildChangelogEntries.js";
2
3
  import { buildDependencyGraph } from "./buildDependencyGraph.js";
4
+ import { buildSyntheticChangelogEntry } from "./buildSyntheticChangelogEntry.js";
3
5
  import { bumpAllVersions, setAllVersions } from "./bumpAllVersions.js";
6
+ import { resolveChangelogJsonPath, upsertChangelogJson } from "./changelogJsonFile.js";
7
+ import { createPolicyViolationCollector } from "./collectPolicyViolations.js";
4
8
  import { isForwardVersion } from "./compareVersions.js";
5
- import { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
9
+ import { decideRelease } from "./decideRelease.js";
10
+ import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
6
11
  import { detectUndeclaredTagPrefixes } from "./detectUndeclaredTagPrefixes.js";
7
- import { determineBumpFromCommits } from "./determineBumpFromCommits.js";
8
- import { generateChangelogJson, generateSyntheticChangelogJson } from "./generateChangelogJson.js";
9
12
  import { buildTagPattern, generateChangelog } from "./generateChangelogs.js";
10
13
  import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
11
14
  import { hasPrettierConfig } from "./hasPrettierConfig.js";
12
15
  import { resolveWorkTypes } from "./loadConfig.js";
13
16
  import { propagateBumps } from "./propagateBumps.js";
14
17
  import { readCurrentVersion } from "./readCurrentVersion.js";
18
+ import { releasePrepareProject } from "./releasePrepareProject.js";
15
19
  import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
16
20
  import { writeReleaseNotesPreviews } from "./writeReleaseNotesPreviews.js";
17
21
  import { writeSyntheticChangelog } from "./writeSyntheticChangelog.js";
@@ -59,13 +63,18 @@ function releasePrepareMono(config, options) {
59
63
  const orderB = configOrder.get(b.name ?? "") ?? 0;
60
64
  return orderA - orderB;
61
65
  });
66
+ let project;
67
+ if (config.project !== void 0) {
68
+ project = tryStage("project release stage", () => releasePrepareProject({ config, options, modifiedFiles, tags }));
69
+ }
62
70
  const formatCommand = runFormatCommand(config, tags, modifiedFiles, dryRun);
63
71
  return {
64
72
  workspaces,
65
73
  tags,
66
74
  formatCommand,
67
75
  dryRun,
68
- ...warnings.length > 0 ? { warnings } : {}
76
+ ...warnings.length > 0 ? { warnings } : {},
77
+ ...project === void 0 ? {} : { project }
69
78
  };
70
79
  }
71
80
  function determineDirectBumps(config, options) {
@@ -75,6 +84,7 @@ function determineDirectBumps(config, options) {
75
84
  }
76
85
  const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
77
86
  const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
87
+ const breakingPolicies = config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES;
78
88
  const directBumps = /* @__PURE__ */ new Map();
79
89
  const directResults = /* @__PURE__ */ new Map();
80
90
  const skippedResults = [];
@@ -83,14 +93,18 @@ function determineDirectBumps(config, options) {
83
93
  const knownPrefixes = config.workspaces.flatMap(getAllTagPrefixes);
84
94
  for (const workspace of config.workspaces) {
85
95
  const name = workspace.dir;
86
- const { tag, commits } = getCommitsSinceTarget(getAllTagPrefixes(workspace), workspace.paths);
96
+ const stageLabel = workspaceStageLabel(workspace.dir);
97
+ const { tag, commits } = tryStage(
98
+ stageLabel,
99
+ () => getCommitsSinceTarget(getAllTagPrefixes(workspace), workspace.paths)
100
+ );
87
101
  const since = tag === void 0 ? "(no previous release found)" : `since ${tag}`;
88
102
  if (tag === void 0) {
89
103
  maybeEmitBaselineHint(workspace, knownPrefixes, hintState);
90
104
  }
91
105
  const primaryPackageFile = workspace.packageFiles[0];
92
106
  if (primaryPackageFile !== void 0) {
93
- const currentVersion = readCurrentVersion(primaryPackageFile);
107
+ const currentVersion = tryStage(stageLabel, () => readCurrentVersion(primaryPackageFile));
94
108
  if (currentVersion !== void 0) {
95
109
  currentVersions.set(workspace.dir, currentVersion);
96
110
  }
@@ -113,51 +127,53 @@ function determineDirectBumps(config, options) {
113
127
  releaseType: void 0,
114
128
  parsedCommitCount: void 0,
115
129
  unparseableCommits: void 0,
130
+ policyViolations: void 0,
131
+ bumpOverride: void 0,
116
132
  setVersion
117
133
  });
118
134
  continue;
119
135
  }
120
- if (commits.length === 0 && !force) {
121
- skippedResults.push({
122
- workspace,
123
- tag,
124
- commitCount: 0,
125
- parsedCommitCount: void 0,
126
- unparseableCommits: void 0,
127
- skipReason: `No changes for ${name} ${since}. Skipping.`
128
- });
129
- continue;
130
- }
131
- let releaseType;
132
- let parsedCommitCount;
133
- let unparseableCommits;
134
- if (bumpOverride === void 0) {
135
- const determination = determineBumpFromCommits(commits, workTypes, versionPatterns, config.scopeAliases);
136
- parsedCommitCount = determination.parsedCommitCount;
137
- unparseableCommits = determination.unparseableCommits;
138
- releaseType = determination.releaseType;
139
- } else {
140
- releaseType = bumpOverride;
141
- }
142
- if (releaseType === void 0) {
136
+ const collector = createPolicyViolationCollector();
137
+ const decision = tryStage(
138
+ stageLabel,
139
+ () => decideRelease({
140
+ commits,
141
+ force,
142
+ bumpOverride,
143
+ workTypes,
144
+ versionPatterns,
145
+ scopeAliases: config.scopeAliases,
146
+ breakingPolicies,
147
+ onPolicyViolation: collector.onPolicyViolation,
148
+ skipReasons: {
149
+ noCommits: `No commits for ${name} ${since}. Pass --force to release at patch. Skipping.`,
150
+ noBumpWorthy: `No bump-worthy commits for ${name} ${since}. Pass --force to release at patch (or --force --bump=X for a different level). Skipping.`
151
+ }
152
+ })
153
+ );
154
+ const policyViolations = collector.violations.length > 0 ? collector.violations : void 0;
155
+ if (decision.outcome === "skip") {
143
156
  skippedResults.push({
144
157
  workspace,
145
158
  tag,
146
159
  commitCount: commits.length,
147
- parsedCommitCount,
148
- unparseableCommits,
149
- skipReason: `No release-worthy changes for ${name} ${since}. Skipping.`
160
+ parsedCommitCount: decision.parsedCommitCount,
161
+ unparseableCommits: decision.unparseableCommits,
162
+ policyViolations,
163
+ skipReason: decision.skipReason
150
164
  });
151
165
  continue;
152
166
  }
153
- directBumps.set(workspace.dir, { releaseType });
167
+ directBumps.set(workspace.dir, { releaseType: decision.releaseType });
154
168
  directResults.set(workspace.dir, {
155
169
  workspace,
156
170
  tag,
157
171
  commits,
158
- releaseType,
159
- parsedCommitCount,
160
- unparseableCommits
172
+ releaseType: decision.releaseType,
173
+ parsedCommitCount: decision.parsedCommitCount,
174
+ unparseableCommits: decision.unparseableCommits,
175
+ policyViolations,
176
+ bumpOverride
161
177
  });
162
178
  }
163
179
  return { directBumps, directResults, skippedResults, currentVersions };
@@ -168,17 +184,25 @@ function collectSkippedWorkspaces(skippedResults, fullReleaseSet) {
168
184
  if (fullReleaseSet.has(skipped.workspace.dir)) {
169
185
  continue;
170
186
  }
171
- workspaces.push({
187
+ const result = {
172
188
  name: skipped.workspace.dir,
173
189
  status: "skipped",
174
- previousTag: skipped.tag,
175
190
  commitCount: skipped.commitCount,
176
- parsedCommitCount: skipped.parsedCommitCount,
177
- unparseableCommits: skipped.unparseableCommits,
178
- bumpedFiles: [],
179
- changelogFiles: [],
180
191
  skipReason: skipped.skipReason
181
- });
192
+ };
193
+ if (skipped.tag !== void 0) {
194
+ result.previousTag = skipped.tag;
195
+ }
196
+ if (skipped.parsedCommitCount !== void 0) {
197
+ result.parsedCommitCount = skipped.parsedCommitCount;
198
+ }
199
+ if (skipped.unparseableCommits !== void 0) {
200
+ result.unparseableCommits = skipped.unparseableCommits;
201
+ }
202
+ if (skipped.policyViolations !== void 0) {
203
+ result.policyViolations = skipped.policyViolations;
204
+ }
205
+ workspaces.push(result);
182
206
  }
183
207
  return workspaces;
184
208
  }
@@ -195,20 +219,23 @@ function executeReleaseSet(sortedDirs, fullReleaseSet, config, directResults, pr
195
219
  if (workspace === void 0) {
196
220
  continue;
197
221
  }
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
- });
222
+ tryStage(
223
+ workspaceStageLabel(dir),
224
+ () => executeWorkspaceRelease({
225
+ dir,
226
+ workspace,
227
+ releaseEntry,
228
+ directResult: directResults.get(dir),
229
+ previousTags,
230
+ config,
231
+ dryRun,
232
+ today,
233
+ tags,
234
+ modifiedFiles,
235
+ workspaces,
236
+ previewOptions
237
+ })
238
+ );
212
239
  }
213
240
  return { tags, modifiedFiles };
214
241
  }
@@ -245,25 +272,53 @@ function executeWorkspaceRelease(args) {
245
272
  modifiedFiles,
246
273
  previewOptions
247
274
  });
248
- workspaces.push({
275
+ const released = {
249
276
  name: dir,
250
277
  status: "released",
251
- previousTag: directResult?.tag ?? previousTags.get(dir),
252
278
  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
279
  currentVersion: bump.currentVersion,
258
280
  newVersion: bump.newVersion,
259
281
  tag: newTag,
260
282
  bumpedFiles: bump.files,
261
- changelogFiles,
262
- commits: directResult?.commits,
263
- unparseableCommits: directResult?.unparseableCommits,
264
- propagatedFrom: releaseEntry.propagatedFrom,
265
- ...setVersionTarget === void 0 ? {} : { setVersion: setVersionTarget }
283
+ changelogFiles
284
+ };
285
+ attachReleasedWorkspaceOptionals(released, {
286
+ previousTag: directResult?.tag ?? previousTags.get(dir),
287
+ directResult,
288
+ releaseEntry,
289
+ setVersionTarget
266
290
  });
291
+ workspaces.push(released);
292
+ }
293
+ function attachReleasedWorkspaceOptionals(released, args) {
294
+ const { previousTag, directResult, releaseEntry, setVersionTarget } = args;
295
+ if (previousTag !== void 0) {
296
+ released.previousTag = previousTag;
297
+ }
298
+ if (directResult?.parsedCommitCount !== void 0) {
299
+ released.parsedCommitCount = directResult.parsedCommitCount;
300
+ }
301
+ if (setVersionTarget === void 0) {
302
+ released.releaseType = releaseEntry.releaseType;
303
+ }
304
+ if (directResult?.commits !== void 0) {
305
+ released.commits = directResult.commits;
306
+ }
307
+ if (directResult?.unparseableCommits !== void 0) {
308
+ released.unparseableCommits = directResult.unparseableCommits;
309
+ }
310
+ if (directResult?.policyViolations !== void 0) {
311
+ released.policyViolations = directResult.policyViolations;
312
+ }
313
+ if (releaseEntry.propagatedFrom !== void 0) {
314
+ released.propagatedFrom = releaseEntry.propagatedFrom;
315
+ }
316
+ if (directResult?.bumpOverride !== void 0) {
317
+ released.bumpOverride = directResult.bumpOverride;
318
+ }
319
+ if (setVersionTarget !== void 0) {
320
+ released.setVersion = setVersionTarget;
321
+ }
267
322
  }
268
323
  function generateWorkspaceChangelogs(args) {
269
324
  const {
@@ -293,17 +348,14 @@ function generateWorkspaceChangelogs(args) {
293
348
  );
294
349
  }
295
350
  if (config.changelogJson.enabled) {
351
+ const syntheticEntry = buildSyntheticChangelogEntry(releaseEntry.propagatedFrom, newVersion, today);
296
352
  for (const changelogPath of workspace.changelogPaths) {
297
- const jsonFiles = generateSyntheticChangelogJson(
298
- config,
299
- changelogPath,
300
- newVersion,
301
- today,
302
- releaseEntry.propagatedFrom,
303
- dryRun
304
- );
305
- modifiedFiles.push(...jsonFiles);
306
- firstChangelogJsonPath ??= jsonFiles[0];
353
+ const jsonPath = resolveChangelogJsonPath(config, changelogPath);
354
+ if (!dryRun) {
355
+ upsertChangelogJson(jsonPath, [syntheticEntry]);
356
+ }
357
+ modifiedFiles.push(jsonPath);
358
+ firstChangelogJsonPath ??= jsonPath;
307
359
  }
308
360
  }
309
361
  maybeWritePreviews(workspace, newTag, firstChangelogJsonPath, previewOptions, dryRun);
@@ -319,13 +371,17 @@ function generateWorkspaceChangelogs(args) {
319
371
  );
320
372
  }
321
373
  if (config.changelogJson.enabled) {
374
+ const entries = buildChangelogEntries(config, newTag, {
375
+ tagPattern,
376
+ includePaths: workspace.paths
377
+ });
322
378
  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];
379
+ const jsonPath = resolveChangelogJsonPath(config, changelogPath);
380
+ if (!dryRun) {
381
+ upsertChangelogJson(jsonPath, entries);
382
+ }
383
+ modifiedFiles.push(jsonPath);
384
+ firstChangelogJsonPath ??= jsonPath;
329
385
  }
330
386
  }
331
387
  maybeWritePreviews(workspace, newTag, firstChangelogJsonPath, previewOptions, dryRun);
@@ -355,15 +411,28 @@ function runFormatCommand(config, tags, modifiedFiles, dryRun) {
355
411
  try {
356
412
  execSync(fullCommand, { stdio: "inherit" });
357
413
  } catch (error) {
358
- throw new Error(
359
- `Format command failed ('${fullCommand}'): ${error instanceof Error ? error.message : String(error)}`
360
- );
414
+ const baseMessage = error instanceof Error ? error.message : String(error);
415
+ throw new Error(`format stage: ${baseMessage} (command: '${fullCommand}')`, { cause: error });
361
416
  }
362
417
  return { command: fullCommand, executed: true, files: modifiedFiles };
363
418
  }
364
419
  function findWorkspace(workspaces, dir) {
365
420
  return workspaces.find((w) => w.dir === dir);
366
421
  }
422
+ function wrapStageError(stageLabel, error) {
423
+ const message = error instanceof Error ? error.message : String(error);
424
+ return new Error(`${stageLabel}: ${message}`, { cause: error });
425
+ }
426
+ function tryStage(stageLabel, fn) {
427
+ try {
428
+ return fn();
429
+ } catch (error) {
430
+ throw wrapStageError(stageLabel, error);
431
+ }
432
+ }
433
+ function workspaceStageLabel(dir) {
434
+ return `workspace '${dir}' release stage`;
435
+ }
367
436
  function maybeEmitBaselineHint(workspace, knownPrefixes, state) {
368
437
  if (state.emitted) return;
369
438
  if ((workspace.legacyIdentities?.length ?? 0) > 0) return;
@@ -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;