@williamthorsen/release-kit 11.0.0 → 13.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 (189) hide show
  1. package/.meta/changelog.json +274 -2
  2. package/.readyup/kits/default.js +18 -33
  3. package/.readyup/kits/npm-auto-publish.js +24 -23
  4. package/.readyup/manifest.json +16 -11
  5. package/CHANGELOG.md +98 -0
  6. package/README.md +57 -69
  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 +4 -0
  12. package/dist/esm/attachChangelogDiagnostics.js +14 -0
  13. package/dist/esm/bin/release-kit.js +88 -24
  14. package/dist/esm/buildChangelogEntries.d.ts +69 -17
  15. package/dist/esm/buildChangelogEntries.js +222 -120
  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/buildReleaseSummary.js +2 -2
  20. package/dist/esm/buildSyntheticChangelogEntry.d.ts +3 -5
  21. package/dist/esm/buildSyntheticChangelogEntry.js +2 -2
  22. package/dist/esm/bumpVersion.d.ts +1 -8
  23. package/dist/esm/changelogJsonFile.d.ts +5 -7
  24. package/dist/esm/changelogJsonFile.js +2 -17
  25. package/dist/esm/changelogJsonUtils.d.ts +7 -10
  26. package/dist/esm/changelogOverrides.d.ts +79 -100
  27. package/dist/esm/changelogOverrides.js +138 -58
  28. package/dist/esm/classifyChangelogCommit.d.ts +36 -0
  29. package/dist/esm/classifyChangelogCommit.js +34 -0
  30. package/dist/esm/commitCommand.d.ts +2 -4
  31. package/dist/esm/compareVersions.d.ts +10 -4
  32. package/dist/esm/compareVersions.js +16 -0
  33. package/dist/esm/configFlagSchema.d.ts +1 -1
  34. package/dist/esm/createGithubRelease.d.ts +7 -19
  35. package/dist/esm/createGithubReleaseCommand.d.ts +5 -5
  36. package/dist/esm/createGithubReleaseCommand.js +9 -9
  37. package/dist/esm/createTags.d.ts +2 -3
  38. package/dist/esm/decideRelease.d.ts +11 -41
  39. package/dist/esm/decideRelease.js +5 -27
  40. package/dist/esm/defaults.d.ts +5 -24
  41. package/dist/esm/defaults.js +7 -5
  42. package/dist/esm/defineConfig.d.ts +1 -1
  43. package/dist/esm/deleteFileIfExists.d.ts +1 -1
  44. package/dist/esm/deriveReleaseNotesConfig.d.ts +20 -0
  45. package/dist/esm/deriveReleaseNotesConfig.js +21 -0
  46. package/dist/esm/deriveWorkspaceConfig.d.ts +3 -5
  47. package/dist/esm/detectPackageManager.d.ts +2 -4
  48. package/dist/esm/detectUndeclaredTagPrefixes.d.ts +4 -6
  49. package/dist/esm/detectUndeclaredTagPrefixes.js +1 -1
  50. package/dist/esm/determineBumpType.d.ts +11 -11
  51. package/dist/esm/determineBumpType.js +6 -7
  52. package/dist/esm/discoverWorkspaces.d.ts +14 -15
  53. package/dist/esm/discoverWorkspaces.js +12 -12
  54. package/dist/esm/enterRepoRoot.d.ts +12 -0
  55. package/dist/esm/enterRepoRoot.js +16 -0
  56. package/dist/esm/enumerateReleaseWindows.d.ts +50 -0
  57. package/dist/esm/enumerateReleaseWindows.js +124 -0
  58. package/dist/esm/extractMigration.d.ts +6 -8
  59. package/dist/esm/findUnroutedEntryScopes.d.ts +16 -0
  60. package/dist/esm/findUnroutedEntryScopes.js +57 -0
  61. package/dist/esm/formatExcludedSkip.d.ts +3 -0
  62. package/dist/esm/formatExcludedSkip.js +3 -0
  63. package/dist/esm/formatPrivateSkip.d.ts +1 -6
  64. package/dist/esm/generateChangelogs.d.ts +13 -26
  65. package/dist/esm/generateChangelogs.js +0 -14
  66. package/dist/esm/glyphs.d.ts +2 -2
  67. package/dist/esm/help/prepareHelp.d.ts +3 -4
  68. package/dist/esm/help/prepareHelp.js +7 -8
  69. package/dist/esm/index.d.ts +1 -1
  70. package/dist/esm/init/checks.d.ts +3 -3
  71. package/dist/esm/init/checks.js +4 -1
  72. package/dist/esm/init/detectRepoType.d.ts +2 -3
  73. package/dist/esm/init/hasProvenance.d.ts +2 -0
  74. package/dist/esm/init/hasProvenance.js +3 -0
  75. package/dist/esm/init/initCommand.d.ts +3 -6
  76. package/dist/esm/init/initCommand.js +11 -11
  77. package/dist/esm/init/parseJsonRecord.d.ts +1 -4
  78. package/dist/esm/init/scaffold.d.ts +1 -3
  79. package/dist/esm/init/scaffold.js +2 -30
  80. package/dist/esm/init/templates.d.ts +9 -11
  81. package/dist/esm/init/templates.js +12 -11
  82. package/dist/esm/init/updateTemplatesCommand.d.ts +13 -0
  83. package/dist/esm/init/updateTemplatesCommand.js +64 -0
  84. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +12 -17
  85. package/dist/esm/injectSection.d.ts +3 -3
  86. package/dist/esm/loadConfig.d.ts +16 -14
  87. package/dist/esm/loadConfig.js +2 -5
  88. package/dist/esm/loadValidatedConfig.d.ts +16 -19
  89. package/dist/esm/loadValidatedConfig.js +6 -1
  90. package/dist/esm/packsPath.d.ts +9 -9
  91. package/dist/esm/parseChangeRecordBlock.d.ts +37 -0
  92. package/dist/esm/parseChangeRecordBlock.js +134 -0
  93. package/dist/esm/parseCommitMessage.d.ts +42 -32
  94. package/dist/esm/parseCommitMessage.js +33 -41
  95. package/dist/esm/parseRequestedTags.d.ts +3 -3
  96. package/dist/esm/planReleaseNotesPreviews.d.ts +6 -6
  97. package/dist/esm/planVersionBump.d.ts +5 -3
  98. package/dist/esm/planVersionBump.js +3 -0
  99. package/dist/esm/prepareCommand.d.ts +4 -7
  100. package/dist/esm/prepareCommand.js +17 -18
  101. package/dist/esm/previewTagPrefixes.d.ts +8 -10
  102. package/dist/esm/propagateBumps.d.ts +6 -7
  103. package/dist/esm/publish.d.ts +1 -1
  104. package/dist/esm/publishCommand.d.ts +4 -3
  105. package/dist/esm/publishCommand.js +8 -8
  106. package/dist/esm/pushCommand.d.ts +4 -3
  107. package/dist/esm/pushCommand.js +8 -2
  108. package/dist/esm/pushRelease.d.ts +2 -2
  109. package/dist/esm/readChangelogSections.d.ts +33 -0
  110. package/dist/esm/readChangelogSections.js +80 -0
  111. package/dist/esm/releaseFiles.d.ts +5 -7
  112. package/dist/esm/releasePlan.d.ts +11 -13
  113. package/dist/esm/releasePrepare.d.ts +25 -25
  114. package/dist/esm/releasePrepare.js +80 -80
  115. package/dist/esm/releasePrepareMono.d.ts +9 -7
  116. package/dist/esm/releasePrepareMono.js +114 -99
  117. package/dist/esm/releasePrepareProject.d.ts +19 -29
  118. package/dist/esm/releasePrepareProject.js +46 -45
  119. package/dist/esm/renderChangelogMarkdown.d.ts +10 -4
  120. package/dist/esm/renderChangelogMarkdown.js +27 -2
  121. package/dist/esm/renderReleaseNotes.d.ts +3 -11
  122. package/dist/esm/renderReleaseNotes.js +2 -6
  123. package/dist/esm/reportPrepare.d.ts +4 -5
  124. package/dist/esm/reportPrepare.js +67 -42
  125. package/dist/esm/resolveCommandTags.d.ts +15 -11
  126. package/dist/esm/resolveCommandTags.js +35 -5
  127. package/dist/esm/resolveConfigFlag.d.ts +7 -0
  128. package/dist/esm/resolveConfigFlag.js +4 -0
  129. package/dist/esm/resolveReleaseTags.d.ts +6 -8
  130. package/dist/esm/resolveReleaseTags.js +1 -1
  131. package/dist/esm/resolveStylesOrExit.d.ts +2 -2
  132. package/dist/esm/showTagPrefixesCommand.d.ts +7 -10
  133. package/dist/esm/showTagPrefixesCommand.js +4 -4
  134. package/dist/esm/stripEmojiPrefix.d.ts +1 -0
  135. package/dist/esm/stripScope.d.ts +2 -2
  136. package/dist/esm/stripScope.js +3 -3
  137. package/dist/esm/sync-labels/generateCommand.d.ts +11 -10
  138. package/dist/esm/sync-labels/generateCommand.js +11 -8
  139. package/dist/esm/sync-labels/initCommand.d.ts +6 -7
  140. package/dist/esm/sync-labels/initCommand.js +2 -2
  141. package/dist/esm/sync-labels/presets.d.ts +5 -4
  142. package/dist/esm/sync-labels/presets.js +9 -9
  143. package/dist/esm/sync-labels/resolveLabels.d.ts +7 -7
  144. package/dist/esm/sync-labels/resolveLabels.js +1 -1
  145. package/dist/esm/sync-labels/retiredConfig.d.ts +4 -5
  146. package/dist/esm/sync-labels/syncCommand.d.ts +1 -4
  147. package/dist/esm/sync-labels/templates.d.ts +3 -3
  148. package/dist/esm/sync-labels/templates.js +3 -3
  149. package/dist/esm/sync-labels/typeLabels.d.ts +14 -0
  150. package/dist/esm/sync-labels/typeLabels.js +38 -0
  151. package/dist/esm/sync-labels/types.d.ts +1 -1
  152. package/dist/esm/tagCommand.d.ts +1 -1
  153. package/dist/esm/typeGuards.d.ts +2 -2
  154. package/dist/esm/types.d.ts +185 -116
  155. package/dist/esm/types.js +2 -2
  156. package/dist/esm/validateConfig.d.ts +2 -11
  157. package/dist/esm/validateConfig.js +5 -1
  158. package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -12
  159. package/dist/esm/validateOverridesCommand.d.ts +19 -21
  160. package/dist/esm/validateOverridesCommand.js +16 -23
  161. package/docs/api.md +6 -6
  162. package/docs/changelogs.md +90 -28
  163. package/docs/configuration.md +30 -29
  164. package/docs/editorial-overrides.md +90 -31
  165. package/docs/labels.md +21 -19
  166. package/docs/project-releases.md +14 -14
  167. package/docs/publishing.md +5 -5
  168. package/docs/readiness-checks.md +3 -3
  169. package/docs/releasing.md +54 -24
  170. package/docs/work-types.md +46 -58
  171. package/package.json +7 -7
  172. package/presets/labels/common.yaml +1 -41
  173. package/schemas/changelog-overrides.json +39 -0
  174. package/cliff.toml.template +0 -65
  175. package/dist/esm/collectPolicyViolations.d.ts +0 -15
  176. package/dist/esm/collectPolicyViolations.js +0 -12
  177. package/dist/esm/determineBumpFromCommits.d.ts +0 -17
  178. package/dist/esm/determineBumpFromCommits.js +0 -28
  179. package/dist/esm/getCommitsSinceTarget.d.ts +0 -21
  180. package/dist/esm/getCommitsSinceTarget.js +0 -66
  181. package/dist/esm/resolveCliffConfigPath.d.ts +0 -11
  182. package/dist/esm/resolveCliffConfigPath.js +0 -20
  183. package/dist/esm/resolveReleaseNotesConfig.d.ts +0 -26
  184. package/dist/esm/resolveReleaseNotesConfig.js +0 -29
  185. package/dist/esm/runGitCliff.d.ts +0 -38
  186. package/dist/esm/runGitCliff.js +0 -25
  187. package/dist/esm/workTypesData.d.ts +0 -50
  188. package/dist/esm/workTypesData.js +0 -145
  189. package/schemas/label-map.json +0 -24
@@ -1,12 +1,14 @@
1
1
  import type { ReleasePlan } from './releasePlan.js';
2
2
  import type { ReleasePrepareOptions } from './releasePrepare.js';
3
- import type { MonorepoReleaseConfig } from './types.js';
3
+ import type { MonorepoPrepareConfig } from './types.js';
4
4
  /**
5
- * Orchestrate release preparation for a monorepo with multiple workspaces.
5
+ * Orchestrates release preparation for a monorepo with multiple workspaces.
6
6
  *
7
- * Phase 1: Determine direct bumps from commits for each workspace.
8
- * Phase 2: Build the dependency graph and propagate bumps to dependents.
9
- * Phase 2b: Topologically sort the full release set.
10
- * Phase 3: Execute bumps and generate changelogs in dependency order.
7
+ * Phase 1: Determines direct bumps from commits for each workspace.
8
+ * Phase 2: Builds the dependency graph and propagates bumps to dependents.
9
+ * Phase 2b: Sorts the full release set topologically.
10
+ * Phase 3: Plans bumps and changelogs in dependency order.
11
+ * Phase 3b: Plans the project release.
12
+ * Phase 4: Renders the format command.
11
13
  */
12
- export declare function releasePrepareMono(config: MonorepoReleaseConfig, options: ReleasePrepareOptions): ReleasePlan;
14
+ export declare function releasePrepareMono(config: MonorepoPrepareConfig, options: ReleasePrepareOptions): ReleasePlan;
@@ -1,26 +1,26 @@
1
1
  import { join as joinPath } from 'node:path';
2
2
  import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
3
- import { buildChangelogEntries } from "./buildChangelogEntries.js";
3
+ import { assertTaggedBaseline, findUntaggedBaseline } from "./assertTaggedBaseline.js";
4
+ import { attachChangelogDiagnostics } from "./attachChangelogDiagnostics.js";
5
+ import { readReleaseHistory, toReleaseEntries } from "./buildChangelogEntries.js";
4
6
  import { buildDependencyGraph } from "./buildDependencyGraph.js";
5
- import { buildEmptyReleaseEntry } from "./buildEmptyReleaseEntry.js";
6
7
  import { buildReleaseSummary } from "./buildReleaseSummary.js";
7
8
  import { buildSyntheticChangelogEntry } from "./buildSyntheticChangelogEntry.js";
8
9
  import { mergeChangelogEntriesWithDisk, renderChangelogJson, resolveChangelogJsonPath } from "./changelogJsonFile.js";
9
10
  import { applyWorkspaceOverrides, createOverrideContext, formatStaleOverrideKeyWarning, } from "./changelogOverrides.js";
10
- import { createPolicyViolationCollector } from "./collectPolicyViolations.js";
11
11
  import { decideRelease } from "./decideRelease.js";
12
- import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
12
+ import { deriveDependenciesHeader, deriveSectionOrder } from "./deriveReleaseNotesConfig.js";
13
13
  import { detectUndeclaredTagPrefixes } from "./detectUndeclaredTagPrefixes.js";
14
- import { buildTagPattern, getAllTagPrefixes } from "./generateChangelogs.js";
15
- import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
14
+ import { findUnroutedEntryScopes } from "./findUnroutedEntryScopes.js";
15
+ import { getAllTagPrefixes } from "./generateChangelogs.js";
16
16
  import { hasPrettierConfig } from "./hasPrettierConfig.js";
17
17
  import { resolveWorkTypes } from "./loadConfig.js";
18
18
  import { planReleaseNotesPreviews } from "./planReleaseNotesPreviews.js";
19
19
  import { planVersionBump, planVersionSet } from "./planVersionBump.js";
20
20
  import { propagateBumps } from "./propagateBumps.js";
21
+ import { planPreservedSections } from "./readChangelogSections.js";
21
22
  import { releasePrepareProject } from "./releasePrepareProject.js";
22
23
  import { renderChangelogMarkdown } from "./renderChangelogMarkdown.js";
23
- import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
24
24
  export function releasePrepareMono(config, options) {
25
25
  const { only, withReleaseNotes } = options;
26
26
  const writes = [];
@@ -28,18 +28,18 @@ export function releasePrepareMono(config, options) {
28
28
  if (withReleaseNotes === true && !config.changelogJson.enabled) {
29
29
  warnings.push('--with-release-notes requires changelogJson.enabled; skipping preview generation');
30
30
  }
31
- const sectionOrder = deriveSectionOrder(resolveWorkTypes(config.workTypes));
31
+ const workTypes = resolveWorkTypes(config.workTypes);
32
+ const sectionOrder = deriveSectionOrder(workTypes);
33
+ const dependenciesHeader = deriveDependenciesHeader(workTypes);
32
34
  const overrideContext = createOverrideContext(config.workspaces);
33
- const { directBumps, directResults, skippedResults } = determineDirectBumps(config, options);
34
- const previousTags = new Map();
35
- for (const result of directResults.values()) {
36
- previousTags.set(result.workspace.dir, result.tag);
37
- }
38
- for (const skipped of skippedResults) {
39
- previousTags.set(skipped.workspace.dir, skipped.tag);
40
- }
35
+ const { directBumps, directResults, skippedResults, untaggedBaselines } = determineDirectBumps(config, options);
36
+ reportUnroutedEntryScopes([...directResults.values(), ...skippedResults], config, options);
37
+ const skippedHistories = new Map(skippedResults.map((skipped) => [skipped.workspace.dir, skipped.history]));
41
38
  const graph = buildDependencyGraph(config.workspaces);
42
39
  const fullReleaseSet = propagateBumps(directBumps, graph);
40
+ assertTaggedBaseline([...untaggedBaselines]
41
+ .filter(([dir]) => directBumps.has(dir) || !fullReleaseSet.has(dir))
42
+ .map(([, untagged]) => untagged));
43
43
  const { sorted: sortedDirs, cyclicDirs } = topologicalSort(fullReleaseSet, graph);
44
44
  if (cyclicDirs.length > 0) {
45
45
  warnings.push(`Circular workspace dependencies detected among: ${cyclicDirs.join(', ')}. ` +
@@ -55,13 +55,14 @@ export function releasePrepareMono(config, options) {
55
55
  fullReleaseSet,
56
56
  config,
57
57
  directResults,
58
- previousTags,
58
+ skippedHistories,
59
59
  writes,
60
60
  warnings,
61
61
  workspaces,
62
62
  previewOptions,
63
63
  overrideContext,
64
64
  sectionOrder,
65
+ dependenciesHeader,
65
66
  });
66
67
  const configOrder = new Map(config.workspaces.map((w, i) => [w.dir, i]));
67
68
  workspaces.sort((a, b) => {
@@ -111,18 +112,28 @@ function determineDirectBumps(config, options) {
111
112
  if (setVersion !== undefined && config.workspaces.length !== 1) {
112
113
  throw new Error(`--set-version requires exactly one workspace; received ${config.workspaces.length}`);
113
114
  }
114
- const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
115
- const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
116
- const breakingPolicies = config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES;
117
115
  const directBumps = new Map();
118
116
  const directResults = new Map();
119
117
  const skippedResults = [];
118
+ const untaggedBaselines = new Map();
120
119
  const hintState = { emitted: false };
121
120
  const knownPrefixes = config.workspaces.flatMap(getAllTagPrefixes);
122
121
  for (const workspace of config.workspaces) {
123
122
  const name = workspace.dir;
124
123
  const stageLabel = workspaceStageLabel(workspace.dir);
125
- const { tag, commits } = tryStage(stageLabel, () => getCommitsSinceTarget(getAllTagPrefixes(workspace), workspace.paths));
124
+ const tagPrefixes = getAllTagPrefixes(workspace);
125
+ const history = tryStage(stageLabel, () => readReleaseHistory(config, { tagPrefixes, paths: workspace.paths, workspaceDir: workspace.dir }));
126
+ const untagged = tryStage(stageLabel, () => findUntaggedBaseline({
127
+ label: `workspace '${workspace.dir}'`,
128
+ packageFiles: workspace.packageFiles,
129
+ changelogPaths: workspace.changelogPaths,
130
+ tagPrefixes,
131
+ previousTag: history.previousTag,
132
+ }, config));
133
+ if (untagged !== undefined) {
134
+ untaggedBaselines.set(workspace.dir, untagged);
135
+ }
136
+ const tag = history.previousTag;
126
137
  const since = tag === undefined ? '(no previous release found)' : `since ${tag}`;
127
138
  if (tag === undefined) {
128
139
  maybeEmitBaselineHint(workspace, knownPrefixes, hintState);
@@ -131,58 +142,42 @@ function determineDirectBumps(config, options) {
131
142
  directBumps.set(workspace.dir, { releaseType: 'patch', newVersionOverride: setVersion });
132
143
  directResults.set(workspace.dir, {
133
144
  workspace,
134
- tag,
135
- commits,
145
+ history,
136
146
  releaseType: undefined,
137
- parsedCommitCount: undefined,
138
- unparseableCommits: undefined,
139
- policyViolations: undefined,
140
147
  bumpOverride: undefined,
141
148
  setVersion,
142
149
  });
143
150
  continue;
144
151
  }
145
- const collector = createPolicyViolationCollector();
146
- const decision = tryStage(stageLabel, () => decideRelease({
147
- commits,
152
+ const decision = decideRelease({
153
+ naturalBump: history.unreleased.bump,
154
+ commitCount: history.unreleased.commits.length,
148
155
  force,
149
156
  bumpOverride,
150
- workTypes,
151
- versionPatterns,
152
- scopeAliases: config.scopeAliases,
153
- breakingPolicies,
154
- onPolicyViolation: collector.onPolicyViolation,
155
157
  skipReasons: {
156
158
  noCommits: `No commits for ${name} ${since}. Pass --force to release at patch. Skipping.`,
157
159
  noBumpWorthy: `No bump-worthy commits for ${name} ${since}. Pass --force to release at patch (or --force --bump=X for a different level). Skipping.`,
158
160
  },
159
- }));
160
- const policyViolations = collector.violations.length > 0 ? collector.violations : undefined;
161
+ });
161
162
  if (decision.outcome === 'skip') {
162
- skippedResults.push({
163
- workspace,
164
- tag,
165
- commitCount: commits.length,
166
- parsedCommitCount: decision.parsedCommitCount,
167
- unparseableCommits: decision.unparseableCommits,
168
- policyViolations,
169
- skipReason: decision.skipReason,
170
- });
163
+ skippedResults.push({ workspace, history, skipReason: decision.skipReason });
171
164
  continue;
172
165
  }
173
166
  directBumps.set(workspace.dir, { releaseType: decision.releaseType });
174
167
  directResults.set(workspace.dir, {
175
168
  workspace,
176
- tag,
177
- commits,
169
+ history,
178
170
  releaseType: decision.releaseType,
179
- parsedCommitCount: decision.parsedCommitCount,
180
- unparseableCommits: decision.unparseableCommits,
181
- policyViolations,
182
171
  bumpOverride,
183
172
  });
184
173
  }
185
- return { directBumps, directResults, skippedResults };
174
+ return { directBumps, directResults, skippedResults, untaggedBaselines };
175
+ }
176
+ function reportUnroutedEntryScopes(results, config, options) {
177
+ const findings = findUnroutedEntryScopes(results.map(({ workspace, history }) => ({ dir: workspace.dir, commits: history.unreleased.commits })), options.configuredWorkspaceDirs ?? config.workspaces.map((workspace) => workspace.dir), config.scopeAliases ?? {});
178
+ for (const { workspace, history } of results) {
179
+ history.unreleased.diagnostics.unroutedEntryScopes = findings.get(workspace.dir) ?? [];
180
+ }
186
181
  }
187
182
  function collectSkippedWorkspaces(skippedResults, fullReleaseSet) {
188
183
  const workspaces = [];
@@ -190,30 +185,27 @@ function collectSkippedWorkspaces(skippedResults, fullReleaseSet) {
190
185
  if (fullReleaseSet.has(skipped.workspace.dir)) {
191
186
  continue;
192
187
  }
188
+ const { previousTag, unreleased } = skipped.history;
193
189
  const result = {
194
190
  name: skipped.workspace.dir,
195
191
  status: 'skipped',
196
- commitCount: skipped.commitCount,
192
+ commitCount: unreleased.commits.length,
193
+ parsedCommitCount: unreleased.parsedCommitCount,
197
194
  skipReason: skipped.skipReason,
198
195
  };
199
- if (skipped.tag !== undefined) {
200
- result.previousTag = skipped.tag;
196
+ if (previousTag !== undefined) {
197
+ result.previousTag = previousTag;
201
198
  }
202
- if (skipped.parsedCommitCount !== undefined) {
203
- result.parsedCommitCount = skipped.parsedCommitCount;
204
- }
205
- if (skipped.unparseableCommits !== undefined) {
206
- result.unparseableCommits = skipped.unparseableCommits;
207
- }
208
- if (skipped.policyViolations !== undefined) {
209
- result.policyViolations = skipped.policyViolations;
199
+ if (unreleased.unparseableCommits !== undefined) {
200
+ result.unparseableCommits = unreleased.unparseableCommits;
210
201
  }
202
+ attachChangelogDiagnostics(result, unreleased.diagnostics);
211
203
  workspaces.push(result);
212
204
  }
213
205
  return workspaces;
214
206
  }
215
207
  function executeReleaseSet(args) {
216
- const { sortedDirs, fullReleaseSet, config, directResults, previousTags, writes, warnings, workspaces, previewOptions, overrideContext, sectionOrder, } = args;
208
+ const { sortedDirs, fullReleaseSet, config, directResults, skippedHistories, writes, warnings, workspaces, previewOptions, overrideContext, sectionOrder, dependenciesHeader, } = args;
217
209
  const tags = [];
218
210
  const modifiedFiles = [];
219
211
  const today = new Date().toISOString().slice(0, 10);
@@ -231,7 +223,7 @@ function executeReleaseSet(args) {
231
223
  workspace,
232
224
  releaseEntry,
233
225
  directResult: directResults.get(dir),
234
- previousTags,
226
+ skippedHistory: skippedHistories.get(dir),
235
227
  config,
236
228
  today,
237
229
  tags,
@@ -242,12 +234,13 @@ function executeReleaseSet(args) {
242
234
  previewOptions,
243
235
  overrideContext,
244
236
  sectionOrder,
237
+ dependenciesHeader,
245
238
  }));
246
239
  }
247
240
  return { tags, modifiedFiles };
248
241
  }
249
242
  function executeWorkspaceRelease(args) {
250
- const { dir, workspace, releaseEntry, directResult, previousTags, config, today, tags, modifiedFiles, writes, warnings, workspaces, previewOptions, overrideContext, sectionOrder, } = args;
243
+ const { dir, workspace, releaseEntry, directResult, skippedHistory, config, today, tags, modifiedFiles, writes, warnings, workspaces, previewOptions, overrideContext, sectionOrder, dependenciesHeader, } = args;
251
244
  const setVersionTarget = directResult?.setVersion;
252
245
  const bump = setVersionTarget === undefined
253
246
  ? planVersionBump(workspace.packageFiles, releaseEntry.releaseType)
@@ -256,15 +249,13 @@ function executeWorkspaceRelease(args) {
256
249
  tags.push(newTag);
257
250
  writes.push(...bump.writes);
258
251
  modifiedFiles.push(...workspace.packageFiles, ...workspace.changelogPaths.map((changelogPath) => joinPath(changelogPath, 'CHANGELOG.md')));
259
- const isPropagationOnly = directResult === undefined;
260
- const isEmptyRange = directResult !== undefined && directResult.commits.length === 0;
261
- const { changelogFiles, previewFiles } = generateWorkspaceChangelogs({
252
+ const phase1History = directResult?.history ?? skippedHistory;
253
+ const { changelogFiles, changelogPreservation, previewFiles } = generateWorkspaceChangelogs({
262
254
  workspace,
263
255
  releaseEntry,
264
256
  newTag,
265
257
  newVersion: bump.newVersion,
266
- isPropagationOnly,
267
- isEmptyRange,
258
+ history: directResult?.history,
268
259
  config,
269
260
  today,
270
261
  modifiedFiles,
@@ -273,45 +264,42 @@ function executeWorkspaceRelease(args) {
273
264
  previewOptions,
274
265
  overrideContext,
275
266
  sectionOrder,
267
+ dependenciesHeader,
276
268
  });
277
269
  const released = {
278
270
  name: dir,
279
271
  status: 'released',
280
- commitCount: directResult?.commits.length ?? 0,
272
+ commitCount: phase1History?.unreleased.commits.length ?? 0,
281
273
  currentVersion: bump.currentVersion,
282
274
  newVersion: bump.newVersion,
283
275
  tag: newTag,
284
276
  bumpedFiles: bump.writes.map((write) => write.path),
285
277
  changelogFiles,
286
278
  ...(previewFiles.length > 0 && { previewFiles }),
279
+ ...(changelogPreservation.length > 0 && { changelogPreservation }),
287
280
  };
288
281
  attachReleasedWorkspaceOptionals(released, {
289
- previousTag: directResult?.tag ?? previousTags.get(dir),
282
+ previousTag: phase1History?.previousTag,
290
283
  directResult,
284
+ skippedHistory,
291
285
  releaseEntry,
292
286
  setVersionTarget,
293
287
  });
294
288
  workspaces.push(released);
295
289
  }
296
290
  function attachReleasedWorkspaceOptionals(released, args) {
297
- const { previousTag, directResult, releaseEntry, setVersionTarget } = args;
291
+ const { previousTag, directResult, skippedHistory, releaseEntry, setVersionTarget } = args;
298
292
  if (previousTag !== undefined) {
299
293
  released.previousTag = previousTag;
300
294
  }
301
- if (directResult?.parsedCommitCount !== undefined) {
302
- released.parsedCommitCount = directResult.parsedCommitCount;
303
- }
304
295
  if (setVersionTarget === undefined) {
305
296
  released.releaseType = releaseEntry.releaseType;
306
297
  }
307
- if (directResult?.commits !== undefined) {
308
- released.commits = directResult.commits;
298
+ if (directResult !== undefined) {
299
+ attachDirectHistory(released, directResult.history.unreleased, setVersionTarget === undefined);
309
300
  }
310
- if (directResult?.unparseableCommits !== undefined) {
311
- released.unparseableCommits = directResult.unparseableCommits;
312
- }
313
- if (directResult?.policyViolations !== undefined) {
314
- released.policyViolations = directResult.policyViolations;
301
+ else {
302
+ attachPropagatedHistory(released, skippedHistory?.unreleased);
315
303
  }
316
304
  if (releaseEntry.propagatedFrom !== undefined) {
317
305
  released.propagatedFrom = releaseEntry.propagatedFrom;
@@ -323,20 +311,41 @@ function attachReleasedWorkspaceOptionals(released, args) {
323
311
  released.setVersion = setVersionTarget;
324
312
  }
325
313
  }
314
+ function attachDirectHistory(released, unreleased, hasDecidedBump) {
315
+ released.commits = unreleased.commits;
316
+ if (hasDecidedBump) {
317
+ released.parsedCommitCount = unreleased.parsedCommitCount;
318
+ if (unreleased.unparseableCommits !== undefined) {
319
+ released.unparseableCommits = unreleased.unparseableCommits;
320
+ }
321
+ }
322
+ attachChangelogDiagnostics(released, unreleased.diagnostics);
323
+ }
324
+ function attachPropagatedHistory(released, unreleased) {
325
+ released.propagatedOnly = true;
326
+ if (unreleased === undefined) {
327
+ return;
328
+ }
329
+ released.commits = unreleased.commits;
330
+ if (unreleased.unparseableCommits !== undefined) {
331
+ released.unparseableCommits = unreleased.unparseableCommits;
332
+ }
333
+ attachChangelogDiagnostics(released, unreleased.diagnostics);
334
+ }
326
335
  function generateWorkspaceChangelogs(args) {
327
- const { workspace, releaseEntry, newTag, newVersion, isPropagationOnly, isEmptyRange, config, today, modifiedFiles, writes, warnings, previewOptions, overrideContext, sectionOrder, } = args;
328
- const newEntries = buildWorkspaceEntries({
336
+ const { workspace, releaseEntry, newTag, newVersion, history, config, today, modifiedFiles, writes, warnings, previewOptions, overrideContext, sectionOrder, dependenciesHeader, } = args;
337
+ const entries = buildWorkspaceEntries({
329
338
  workspace,
330
339
  releaseEntry,
331
340
  newTag,
332
341
  newVersion,
333
- isPropagationOnly,
334
- isEmptyRange,
335
- config,
342
+ history,
336
343
  today,
344
+ dependenciesHeader,
337
345
  });
338
- const applied = applyWorkspaceOverrides(newEntries, workspace.workspacePath, overrideContext);
346
+ const applied = applyWorkspaceOverrides(entries, workspace.workspacePath, overrideContext);
339
347
  const changelogFiles = [];
348
+ const changelogPreservation = [];
340
349
  let firstMergedEntries;
341
350
  for (const changelogPath of workspace.changelogPaths) {
342
351
  const jsonPath = resolveChangelogJsonPath(config, changelogPath);
@@ -347,23 +356,29 @@ function generateWorkspaceChangelogs(args) {
347
356
  firstMergedEntries ??= mergedEntries;
348
357
  }
349
358
  const changelogFile = joinPath(changelogPath, 'CHANGELOG.md');
350
- writes.push({ path: changelogFile, content: renderChangelogMarkdown(mergedEntries, { sectionOrder }) });
359
+ const preserved = planPreservedSections(changelogFile, mergedEntries);
360
+ writes.push({
361
+ path: changelogFile,
362
+ content: renderChangelogMarkdown(mergedEntries, { sectionOrder, preservedSections: preserved.sections }),
363
+ });
351
364
  changelogFiles.push(changelogFile);
365
+ if (preserved.preservation !== undefined) {
366
+ changelogPreservation.push(preserved.preservation);
367
+ }
352
368
  }
353
369
  const previews = planPreviews(workspace, newTag, firstMergedEntries, previewOptions, warnings);
354
370
  writes.push(...previews);
355
- return { changelogFiles, previewFiles: previews.map((write) => write.path) };
371
+ return { changelogFiles, changelogPreservation, previewFiles: previews.map((write) => write.path) };
356
372
  }
357
373
  function buildWorkspaceEntries(args) {
358
- const { workspace, releaseEntry, newTag, newVersion, isPropagationOnly, isEmptyRange, config, today } = args;
359
- if (isPropagationOnly && releaseEntry.propagatedFrom !== undefined) {
360
- return [buildSyntheticChangelogEntry(releaseEntry.propagatedFrom, newVersion, today)];
361
- }
362
- if (isEmptyRange) {
363
- return [buildEmptyReleaseEntry(newVersion, today)];
374
+ const { workspace, releaseEntry, newTag, newVersion, history, today, dependenciesHeader } = args;
375
+ if (history === undefined) {
376
+ if (releaseEntry.propagatedFrom === undefined) {
377
+ throw new Error(`Workspace '${workspace.dir}' has neither a direct release nor a propagation source`);
378
+ }
379
+ return [buildSyntheticChangelogEntry(releaseEntry.propagatedFrom, newVersion, today, dependenciesHeader)];
364
380
  }
365
- const tagPattern = buildTagPattern(getAllTagPrefixes(workspace));
366
- return buildChangelogEntries(config, newTag, { tagPattern, includePaths: workspace.paths });
381
+ return toReleaseEntries(history, newTag, today);
367
382
  }
368
383
  function planPreviews(workspace, newTag, entries, previewOptions, warnings) {
369
384
  if (!previewOptions.enabled || entries === undefined) {
@@ -1,57 +1,47 @@
1
1
  import type { PlannedWrite } from './releasePlan.js';
2
2
  import type { ReleasePrepareOptions } from './releasePrepare.js';
3
- import type { ChangelogOverride, MonorepoReleaseConfig, ProjectPrepareResult } from './types.js';
3
+ import type { ChangelogOverride, MonorepoPrepareConfig, ProjectPrepareResult } from './types.js';
4
4
  /** Inputs to the project-release stage. */
5
5
  export interface ReleasePrepareProjectArgs {
6
6
  /** Resolved monorepo config; `config.project` must be defined when this function is called. */
7
- config: MonorepoReleaseConfig;
7
+ config: MonorepoPrepareConfig;
8
8
  options: ReleasePrepareOptions;
9
9
  /** Mutated in-place to append project-level files (root package.json, root CHANGELOG.md, root changelog.json). */
10
10
  modifiedFiles: string[];
11
- /** Mutated in-place to append every file this stage intends to write. */
11
+ /** Mutated in-place to append every file that this stage intends to write. */
12
12
  writes: PlannedWrite[];
13
13
  /** Mutated in-place to append the project tag. */
14
14
  tags: string[];
15
15
  /**
16
- * Mutated in-place to surface warnings this stage raises that are not override-specific
17
- * (currently the release-notes previews' skip reasons). Defaults to a discardable sink.
16
+ * Mutated in-place to receive the warnings that this stage raises and that are not override-specific.
17
+ * Defaults to a discardable sink.
18
18
  */
19
19
  warnings?: string[];
20
20
  /**
21
- * Root-tier editorial overrides loaded once at the top of the prepare run. Defaults to an
22
- * empty map when omitted (no overrides applied). The project changelog applies only the
23
- * root-tier file — per-workspace files describe per-workspace editorial intent and have no
24
- * meaning at the aggregated project tier.
21
+ * Root-tier editorial overrides. Defaults to an empty map. The project changelog applies only the
22
+ * root-tier file: Per-workspace files describe per-workspace editorial intent and are
23
+ * meaningless at the aggregated project tier.
25
24
  */
26
25
  rootOverrides?: Map<string, ChangelogOverride>;
27
26
  /**
28
- * Mutated in-place to surface override warnings (currently empty by design — stale-key
29
- * warnings are emitted by the orchestrator after aggregating across batches). Defaults to
30
- * a discardable sink when omitted.
27
+ * Mutated in-place to receive override-application warnings. Stale-key warnings are not among them: Those
28
+ * depend on the matches of every batch, which `globalMatchedRootKeys` collects. Defaults to a discardable sink.
31
29
  */
32
30
  overrideWarnings?: string[];
33
31
  /**
34
- * Mutated in-place: every root-tier override key matched in this stage is added so the
35
- * orchestrator can dedupe stale-key warnings across the run. Defaults to a discardable
36
- * sink when omitted.
32
+ * Mutated in-place to receive every root-tier override key that this stage matches, so that stale-key
33
+ * warnings can cover the whole run. Defaults to a discardable sink.
37
34
  */
38
35
  globalMatchedRootKeys?: Set<string>;
39
36
  }
40
37
  /**
41
- * Run the project-level release stage.
38
+ * Runs the project-level release stage: reads the history, decides the bump, and plans the root `package.json`
39
+ * bump, the root `CHANGELOG.md`, and optionally `changelog.json` and release-notes previews. Contributing paths
40
+ * come from the resolved `project.paths`, which defaults to the union of every (already-filtered) workspace's
41
+ * `paths`.
42
42
  *
43
- * Mirrors the per-workspace pipeline shape — find baseline tag → derive bump → bump version →
44
- * regenerate CHANGELOG → optionally emit changelog.json and release-notes previews — but
45
- * targets the root `package.json` and the root `CHANGELOG.md`. Contributing paths come from
46
- * the resolved `project.paths`, which defaults to the union of every (already-filtered)
47
- * workspace's `paths`.
48
- *
49
- * Returns a structured `{ status: 'skipped', skipReason, ... }` result when neither
50
- * commits nor `--force` provide a release signal. The caller should attach the returned
51
- * result to `PrepareResult.project`. `undefined` is returned only when there is no
52
- * configured `project` block — handled at the call site, not here.
53
- *
54
- * Caller contract: `prepareCommand` rejects `--only` upstream when a project block is
55
- * configured, so this orchestrator never has to reason about workspace-narrowing flags.
43
+ * Returns a `skipped` result when neither commits nor `--force` provide a release signal, and throws when
44
+ * `config.project` is undefined. A run narrowed by `--only` does not reach this stage, so it doesn't
45
+ * read any narrowing flag.
56
46
  */
57
47
  export declare function releasePrepareProject(args: ReleasePrepareProjectArgs): ProjectPrepareResult;