@williamthorsen/release-kit 14.2.2 → 14.2.3

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 (84) hide show
  1. package/.meta/changelog.json +50 -0
  2. package/.readyup/kits/default.js +135 -145
  3. package/.readyup/kits/npm-auto-publish.js +179 -180
  4. package/.readyup/manifest.json +31 -17
  5. package/CHANGELOG.md +16 -0
  6. package/README.md +7 -1
  7. package/dist/esm/applyChangelogOverrides.d.ts +50 -0
  8. package/dist/esm/applyChangelogOverrides.js +162 -0
  9. package/dist/esm/attachChangelogDiagnostics.d.ts +1 -1
  10. package/dist/esm/bin/release-kit.js +6 -238
  11. package/dist/esm/buildChangelogEntries.d.ts +20 -31
  12. package/dist/esm/buildChangelogEntries.js +71 -188
  13. package/dist/esm/buildChangelogItem.d.ts +56 -0
  14. package/dist/esm/buildChangelogItem.js +119 -0
  15. package/dist/esm/buildReleasedResult.d.ts +20 -0
  16. package/dist/esm/buildReleasedResult.js +78 -0
  17. package/dist/esm/changelogJsonUtils.d.ts +1 -1
  18. package/dist/esm/changelogOverrides.d.ts +29 -143
  19. package/dist/esm/changelogOverrides.js +35 -467
  20. package/dist/esm/configSchema.d.ts +225 -0
  21. package/dist/esm/configSchema.js +103 -0
  22. package/dist/esm/decideDirectReleases.d.ts +30 -0
  23. package/dist/esm/decideDirectReleases.js +66 -0
  24. package/dist/esm/defineConfig.d.ts +2 -2
  25. package/dist/esm/deriveReleaseNotesConfig.d.ts +2 -1
  26. package/dist/esm/deriveReleaseNotesConfig.js +1 -1
  27. package/dist/esm/formatPrepareFiles.d.ts +11 -0
  28. package/dist/esm/formatPrepareFiles.js +55 -0
  29. package/dist/esm/formatPrepareWarnings.d.ts +26 -0
  30. package/dist/esm/formatPrepareWarnings.js +72 -0
  31. package/dist/esm/help/initHelp.d.ts +4 -0
  32. package/dist/esm/help/initHelp.js +31 -0
  33. package/dist/esm/help/overridesHelp.d.ts +4 -0
  34. package/dist/esm/help/overridesHelp.js +32 -0
  35. package/dist/esm/help/releaseStepsHelp.d.ts +12 -0
  36. package/dist/esm/help/releaseStepsHelp.js +91 -0
  37. package/dist/esm/help/syncLabelsHelp.d.ts +8 -0
  38. package/dist/esm/help/syncLabelsHelp.js +55 -0
  39. package/dist/esm/help/usage.d.ts +2 -0
  40. package/dist/esm/help/usage.js +29 -0
  41. package/dist/esm/index.d.ts +1 -1
  42. package/dist/esm/index.js +1 -1
  43. package/dist/esm/loadChangelogOverrides.d.ts +31 -0
  44. package/dist/esm/loadChangelogOverrides.js +138 -0
  45. package/dist/esm/loadConfig.d.ts +0 -40
  46. package/dist/esm/loadConfig.js +0 -240
  47. package/dist/esm/loadValidatedConfig.d.ts +1 -1
  48. package/dist/esm/mergeConfig.d.ts +41 -0
  49. package/dist/esm/mergeConfig.js +118 -0
  50. package/dist/esm/monorepoConfigAssertions.d.ts +49 -0
  51. package/dist/esm/monorepoConfigAssertions.js +126 -0
  52. package/dist/esm/planChangelogFiles.d.ts +16 -0
  53. package/dist/esm/planChangelogFiles.js +41 -0
  54. package/dist/esm/prepareArgs.d.ts +55 -0
  55. package/dist/esm/prepareArgs.js +69 -0
  56. package/dist/esm/prepareCommand.d.ts +0 -48
  57. package/dist/esm/prepareCommand.js +6 -73
  58. package/dist/esm/prepareComposite.d.ts +16 -0
  59. package/dist/esm/prepareComposite.js +39 -0
  60. package/dist/esm/prepareRunContext.d.ts +38 -0
  61. package/dist/esm/prepareRunContext.js +39 -0
  62. package/dist/esm/prepareTarget.d.ts +2 -7
  63. package/dist/esm/prepareTarget.js +2 -118
  64. package/dist/esm/previewTagPrefixes.d.ts +1 -1
  65. package/dist/esm/releasePrepareTargets.js +13 -205
  66. package/dist/esm/reportPrepare.js +3 -125
  67. package/dist/esm/resolveCommandTags.d.ts +1 -1
  68. package/dist/esm/resolveCommandTags.js +2 -1
  69. package/dist/esm/resolveReleaseDeclaration.d.ts +2 -1
  70. package/dist/esm/resolveReleaseTargets.d.ts +2 -1
  71. package/dist/esm/resolveReleaseTargets.js +2 -1
  72. package/dist/esm/sync-labels/generateCommand.d.ts +1 -1
  73. package/dist/esm/sync-labels/resolveLabels.d.ts +1 -1
  74. package/dist/esm/topologicalSort.d.ts +12 -0
  75. package/dist/esm/topologicalSort.js +60 -0
  76. package/dist/esm/types.d.ts +1 -225
  77. package/dist/esm/types.js +1 -103
  78. package/dist/esm/validateAllChangelogOverrides.d.ts +56 -0
  79. package/dist/esm/validateAllChangelogOverrides.js +138 -0
  80. package/dist/esm/validateConfig.d.ts +1 -1
  81. package/dist/esm/validateConfig.js +7 -9
  82. package/dist/esm/validateOverridesCommand.d.ts +1 -1
  83. package/dist/esm/validateOverridesCommand.js +2 -1
  84. package/package.json +2 -2
@@ -1,23 +1,15 @@
1
1
  import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
2
+ import { buildEntryItem, buildTitleItem, } from "./buildChangelogItem.js";
2
3
  import { buildEmptyReleaseEntry } from "./buildEmptyReleaseEntry.js";
3
4
  import { extractVersion } from "./changelogJsonUtils.js";
4
5
  import { classifyChangelogCommit, isNonChangeSubject, isReleaseSubject } from "./classifyChangelogCommit.js";
5
6
  import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
6
7
  import { determineBumpType } from "./determineBumpType.js";
7
8
  import { enumerateReleaseWindows } from "./enumerateReleaseWindows.js";
8
- import { extractMigration } from "./extractMigration.js";
9
- import { parseChangeRecordBlock, stripChangeRecordBlocks } from "./parseChangeRecordBlock.js";
10
- import { evaluateBreakingPolicy, parseSubject, resolveType } from "./parseCommitMessage.js";
9
+ import { parseChangeRecordBlock } from "./parseChangeRecordBlock.js";
11
10
  import { stripEmojiPrefix } from "./stripEmojiPrefix.js";
12
11
  const UNRELEASED_TAG = 'unreleased';
13
12
  const CANONICAL_SECTION_ORDER = new Map(Object.values(DEFAULT_WORK_TYPES).map((config, index) => [stripGroupDecorations(config.header), index]));
14
- function canonicalSectionPriority(title) {
15
- const index = CANONICAL_SECTION_ORDER.get(stripGroupDecorations(title));
16
- return index ?? Infinity;
17
- }
18
- export function stripGroupDecorations(group) {
19
- return stripEmojiPrefix(group);
20
- }
21
13
  export function buildChangelogEntries(config, tag, options) {
22
14
  const history = readReleaseHistory(config, options);
23
15
  return { entries: toChangelogEntries(history, tag), diagnostics: history.unreleased.diagnostics };
@@ -44,6 +36,9 @@ export function readReleaseHistory(config, options) {
44
36
  throw chainError(`Failed to read the release history for ${options.tagPrefixes.join(', ')}`, error);
45
37
  }
46
38
  }
39
+ export function stripGroupDecorations(group) {
40
+ return stripEmojiPrefix(group);
41
+ }
47
42
  export function toChangelogEntries(history, tag) {
48
43
  const { unreleased } = history;
49
44
  if (unreleased.sections.length === 0) {
@@ -60,6 +55,72 @@ export function toReleaseEntries(history, tag, today) {
60
55
  }
61
56
  return toChangelogEntries(history, tag);
62
57
  }
58
+ function appendItem(sectionMap, header, item) {
59
+ let items = sectionMap.get(header);
60
+ if (items === undefined) {
61
+ items = [];
62
+ sectionMap.set(header, items);
63
+ }
64
+ items.push(item);
65
+ }
66
+ function buildSections(sectionMap, devOnlySections) {
67
+ const sections = [];
68
+ for (const [title, items] of sectionMap) {
69
+ sections.push({
70
+ title,
71
+ audience: devOnlySections.has(stripGroupDecorations(title)) ? 'dev' : 'all',
72
+ items,
73
+ });
74
+ }
75
+ return sections.toSorted((a, b) => canonicalSectionPriority(a.title) - canonicalSectionPriority(b.title));
76
+ }
77
+ function canonicalSectionPriority(title) {
78
+ const index = CANONICAL_SECTION_ORDER.get(stripGroupDecorations(title));
79
+ return index ?? Infinity;
80
+ }
81
+ function formatDate(timestamp) {
82
+ return new Date(timestamp * 1_000).toISOString().slice(0, 10);
83
+ }
84
+ function readCommit(commit, context, diagnostics) {
85
+ if (isNonChangeSubject(commit.subject)) {
86
+ return { items: [], isUnparseable: false };
87
+ }
88
+ const reading = parseChangeRecordBlock(commit.message);
89
+ if (reading.kind === 'malformed') {
90
+ diagnostics?.malformedBlocks.push({
91
+ commitHash: commit.hash,
92
+ commitSubject: commit.subject,
93
+ reason: reading.reason,
94
+ });
95
+ }
96
+ if (reading.kind === 'read' && reading.entries.length > 0) {
97
+ const items = reading.entries.flatMap((entry, entryIndex) => buildEntryItem(commit, entry, entryIndex + 1, reading.prNumber, context, diagnostics) ?? []);
98
+ return { items, isUnparseable: false };
99
+ }
100
+ const classification = classifyChangelogCommit(commit, context.workTypes, {
101
+ breakingPolicies: context.breakingPolicies,
102
+ ...(diagnostics !== undefined && {
103
+ onPolicyViolation: (violating, type, surface) => {
104
+ diagnostics.policyViolations.push({
105
+ commitHash: violating.hash,
106
+ commitSubject: violating.subject,
107
+ type,
108
+ surface,
109
+ });
110
+ },
111
+ }),
112
+ });
113
+ if (classification.kind !== 'header') {
114
+ return { items: [], isUnparseable: classification.kind === 'unparseable' && reading.kind !== 'malformed' };
115
+ }
116
+ const item = buildTitleItem(commit, classification.breaking, context.workTypes);
117
+ return {
118
+ items: [
119
+ { header: classification.header, item, signal: { type: classification.type, breaking: item.breaking === true } },
120
+ ],
121
+ isUnparseable: false,
122
+ };
123
+ }
63
124
  function transformReleases(windows, context) {
64
125
  const [unreleasedWindow, baselineWindow] = windows;
65
126
  const diagnostics = {
@@ -113,181 +174,3 @@ function transformReleases(windows, context) {
113
174
  },
114
175
  };
115
176
  }
116
- function readCommit(commit, context, diagnostics) {
117
- if (isNonChangeSubject(commit.subject)) {
118
- return { items: [], isUnparseable: false };
119
- }
120
- const reading = parseChangeRecordBlock(commit.message);
121
- if (reading.kind === 'malformed') {
122
- diagnostics?.malformedBlocks.push({
123
- commitHash: commit.hash,
124
- commitSubject: commit.subject,
125
- reason: reading.reason,
126
- });
127
- }
128
- if (reading.kind === 'read' && reading.entries.length > 0) {
129
- const items = reading.entries.flatMap((entry, entryIndex) => buildEntryItem(commit, entry, entryIndex + 1, reading.prNumber, context, diagnostics) ?? []);
130
- return { items, isUnparseable: false };
131
- }
132
- const classification = classifyChangelogCommit(commit, context.workTypes, {
133
- breakingPolicies: context.breakingPolicies,
134
- ...(diagnostics !== undefined && {
135
- onPolicyViolation: (violating, type, surface) => {
136
- diagnostics.policyViolations.push({
137
- commitHash: violating.hash,
138
- commitSubject: violating.subject,
139
- type,
140
- surface,
141
- });
142
- },
143
- }),
144
- });
145
- if (classification.kind !== 'header') {
146
- return { items: [], isUnparseable: classification.kind === 'unparseable' && reading.kind !== 'malformed' };
147
- }
148
- const item = buildTitleItem(commit, classification.breaking, context.workTypes);
149
- return {
150
- items: [
151
- { header: classification.header, item, signal: { type: classification.type, breaking: item.breaking === true } },
152
- ],
153
- isUnparseable: false,
154
- };
155
- }
156
- function buildSections(sectionMap, devOnlySections) {
157
- const sections = [];
158
- for (const [title, items] of sectionMap) {
159
- sections.push({
160
- title,
161
- audience: devOnlySections.has(stripGroupDecorations(title)) ? 'dev' : 'all',
162
- items,
163
- });
164
- }
165
- return sections.toSorted((a, b) => canonicalSectionPriority(a.title) - canonicalSectionPriority(b.title));
166
- }
167
- function isRoutedTo(scopes, routing) {
168
- if (scopes.length === 0) {
169
- return true;
170
- }
171
- return scopes.some((scope) => {
172
- const resolved = routing.scopeAliases[scope] ?? scope;
173
- return resolved === '*' || resolved === routing.scope;
174
- });
175
- }
176
- function formatDate(timestamp) {
177
- return new Date(timestamp * 1_000).toISOString().slice(0, 10);
178
- }
179
- function appendItem(sectionMap, header, item) {
180
- let items = sectionMap.get(header);
181
- if (items === undefined) {
182
- items = [];
183
- sectionMap.set(header, items);
184
- }
185
- items.push(item);
186
- }
187
- function buildEntryItem(commit, entry, position, prNumber, context, diagnostics) {
188
- const { breakingPolicies, routing, workTypes } = context;
189
- if (routing !== undefined && !isRoutedTo(entry.scopes, routing)) {
190
- return undefined;
191
- }
192
- const type = resolveType(entry.type, workTypes);
193
- if (type === undefined) {
194
- diagnostics?.undeclaredEntryTypes.push({
195
- commitHash: commit.hash,
196
- commitSubject: commit.subject,
197
- entryPosition: position,
198
- type: entry.type,
199
- });
200
- return undefined;
201
- }
202
- const config = workTypes[type];
203
- if (config === undefined || config.excludedFromChangelog === true) {
204
- return undefined;
205
- }
206
- const breaking = evaluateBreakingPolicy({
207
- commit: { message: commit.message, subject: commit.subject, hash: commit.hash },
208
- resolvedType: type,
209
- hasPrefixBreaking: entry.breaking,
210
- hasFooterBreaking: false,
211
- policy: breakingPolicies[type] ?? 'optional',
212
- prefixSurface: 'entry',
213
- onPolicyViolation: diagnostics === undefined
214
- ? undefined
215
- : (violating, violatedType, surface) => {
216
- diagnostics.policyViolations.push({
217
- commitHash: violating.hash,
218
- commitSubject: violating.subject,
219
- type: violatedType,
220
- surface,
221
- entryPosition: position,
222
- });
223
- },
224
- });
225
- const item = {
226
- description: prNumber === undefined ? entry.text : `${entry.text} (#${prNumber})`,
227
- };
228
- if (breaking) {
229
- item.breaking = true;
230
- }
231
- if (entry.migration !== undefined) {
232
- item.migration = entry.migration;
233
- }
234
- item.hash = commit.hash;
235
- item.entry = position;
236
- return { header: config.header, item, signal: { type, breaking } };
237
- }
238
- function buildTitleItem(commit, isParsedBreaking, workTypes) {
239
- const body = extractBody(stripChangeRecordBlocks(commit.message));
240
- const item = { description: extractDescription(commit.message) };
241
- if (body !== undefined) {
242
- item.body = body;
243
- }
244
- if (isParsedBreaking && subjectHasBreakingMarker(commit.message, workTypes)) {
245
- item.breaking = true;
246
- }
247
- const migration = extractMigration(body);
248
- if (migration !== undefined) {
249
- item.migration = migration;
250
- }
251
- item.hash = commit.hash;
252
- return item;
253
- }
254
- function subjectHasBreakingMarker(message, workTypes) {
255
- return parseSubject(message.split('\n', 1)[0] ?? '', workTypes)?.breaking === true;
256
- }
257
- function extractDescription(message) {
258
- const firstLine = message.split('\n', 1)[0] ?? message;
259
- const afterColon = firstLine.split(': ').slice(1).join(': ');
260
- return afterColon.charAt(0).toUpperCase() + afterColon.slice(1);
261
- }
262
- const TRAILER_PATTERNS = [
263
- /^Change:/i,
264
- /^Signed-off-by:/i,
265
- /^Co-authored-by:/i,
266
- /^(Closes|Fixes|Resolves)\s+#\d+\s*$/i,
267
- /^https?:\/\/\S+\/pull\/\d+\/?\s*$/,
268
- ];
269
- function extractBody(message) {
270
- const lines = message.split('\n').slice(1);
271
- let start = 0;
272
- while (start < lines.length && (lines[start] ?? '').trim() === '') {
273
- start += 1;
274
- }
275
- let end = lines.length;
276
- while (end > start) {
277
- const line = lines[end - 1] ?? '';
278
- const trimmed = line.trim();
279
- if (trimmed === '') {
280
- end -= 1;
281
- continue;
282
- }
283
- if (TRAILER_PATTERNS.some((pattern) => pattern.test(trimmed))) {
284
- end -= 1;
285
- continue;
286
- }
287
- break;
288
- }
289
- if (end <= start) {
290
- return undefined;
291
- }
292
- return lines.slice(start, end).join('\n').trim();
293
- }
@@ -0,0 +1,56 @@
1
+ import type { BumpSignal } from './determineBumpType.js';
2
+ import type { RawCommit } from './enumerateReleaseWindows.js';
3
+ import { type ChangeRecordEntry } from './parseChangeRecordBlock.js';
4
+ import type { ChangelogItem, MalformedChangeRecordBlock, PolicyViolation, ReleaseSettings, UndeclaredEntryType, UnroutedEntryScope, VersionPatterns, WorkTypeConfig } from './types.js';
5
+ /**
6
+ * Builds the item that one change-record entry yields, with the section header that its type declares.
7
+ *
8
+ * Returns `undefined` for an entry whose scopes route it away from the workspace being read, for one whose type is
9
+ * undeclared, which is recorded as a diagnostic, and for one whose type is excluded from the changelog. An entry
10
+ * whose `breaking` violates its type's policy is recorded as a policy violation and yields an item that is not
11
+ * breaking.
12
+ */
13
+ export declare function buildEntryItem(commit: RawCommit, entry: ChangeRecordEntry, position: number, prNumber: number | undefined, context: ReadContext, diagnostics: ChangelogDiagnostics | undefined): DerivedItem | undefined;
14
+ /**
15
+ * Builds the item that a commit without a usable change-record block yields from its title and body. The body leaves
16
+ * out any block, which records data rather than prose.
17
+ *
18
+ * `isParsedBreaking` is the parser's policy-evaluated flag. The item is breaking only when the subject also has a
19
+ * prefix `!`, because the parse also counts a `BREAKING CHANGE:` footer on an `optional`-policy type, which the
20
+ * changelog ignores.
21
+ */
22
+ export declare function buildTitleItem(commit: RawCommit, isParsedBreaking: boolean, workTypes: Record<string, WorkTypeConfig>): ChangelogItem;
23
+ /** What reading the unreleased window reports beyond its items. */
24
+ export interface ChangelogDiagnostics {
25
+ malformedBlocks: MalformedChangeRecordBlock[];
26
+ /** Breaking-policy violations of the window's titles and change-record entries. */
27
+ policyViolations: PolicyViolation[];
28
+ undeclaredEntryTypes: UndeclaredEntryType[];
29
+ /**
30
+ * Entry scopes that don't route to any workspace. A read leaves it empty: Only a pass over every workspace can fill
31
+ * it.
32
+ */
33
+ unroutedEntryScopes: UnroutedEntryScope[];
34
+ }
35
+ /** An item with the section header under which it is filed and the signal that it contributes to the bump. */
36
+ export interface DerivedItem {
37
+ header: string;
38
+ item: ChangelogItem;
39
+ signal: BumpSignal;
40
+ }
41
+ /** What every window's read shares. */
42
+ export interface ReadContext {
43
+ breakingPolicies: NonNullable<ReleaseSettings['breakingPolicies']>;
44
+ /** Dev-only section titles, stripped of decorations. */
45
+ devOnlySections: ReadonlySet<string>;
46
+ /** The workspace to which entries are routed; undefined for a read that takes every entry. */
47
+ routing?: EntryRouting;
48
+ versionPatterns: VersionPatterns;
49
+ workTypes: NonNullable<ReleaseSettings['workTypes']>;
50
+ }
51
+ /** The scope of the workspace to which a read routes change-record entries, and the aliases that resolve scopes. */
52
+ interface EntryRouting {
53
+ scope: string;
54
+ scopeAliases: Readonly<Record<string, string>>;
55
+ }
56
+ export {};
@@ -0,0 +1,119 @@
1
+ import { extractMigration } from "./extractMigration.js";
2
+ import { stripChangeRecordBlocks } from "./parseChangeRecordBlock.js";
3
+ import { evaluateBreakingPolicy, parseSubject, resolveType } from "./parseCommitMessage.js";
4
+ const TRAILER_PATTERNS = [
5
+ /^Change:/i,
6
+ /^Signed-off-by:/i,
7
+ /^Co-authored-by:/i,
8
+ /^(Closes|Fixes|Resolves)\s+#\d+\s*$/i,
9
+ /^https?:\/\/\S+\/pull\/\d+\/?\s*$/,
10
+ ];
11
+ export function buildEntryItem(commit, entry, position, prNumber, context, diagnostics) {
12
+ const { breakingPolicies, routing, workTypes } = context;
13
+ if (routing !== undefined && !isRoutedTo(entry.scopes, routing)) {
14
+ return undefined;
15
+ }
16
+ const type = resolveType(entry.type, workTypes);
17
+ if (type === undefined) {
18
+ diagnostics?.undeclaredEntryTypes.push({
19
+ commitHash: commit.hash,
20
+ commitSubject: commit.subject,
21
+ entryPosition: position,
22
+ type: entry.type,
23
+ });
24
+ return undefined;
25
+ }
26
+ const config = workTypes[type];
27
+ if (config === undefined || config.excludedFromChangelog === true) {
28
+ return undefined;
29
+ }
30
+ const breaking = evaluateBreakingPolicy({
31
+ commit: { message: commit.message, subject: commit.subject, hash: commit.hash },
32
+ resolvedType: type,
33
+ hasPrefixBreaking: entry.breaking,
34
+ hasFooterBreaking: false,
35
+ policy: breakingPolicies[type] ?? 'optional',
36
+ prefixSurface: 'entry',
37
+ onPolicyViolation: diagnostics === undefined
38
+ ? undefined
39
+ : (violating, violatedType, surface) => {
40
+ diagnostics.policyViolations.push({
41
+ commitHash: violating.hash,
42
+ commitSubject: violating.subject,
43
+ type: violatedType,
44
+ surface,
45
+ entryPosition: position,
46
+ });
47
+ },
48
+ });
49
+ const item = {
50
+ description: prNumber === undefined ? entry.text : `${entry.text} (#${prNumber})`,
51
+ };
52
+ if (breaking) {
53
+ item.breaking = true;
54
+ }
55
+ if (entry.migration !== undefined) {
56
+ item.migration = entry.migration;
57
+ }
58
+ item.hash = commit.hash;
59
+ item.entry = position;
60
+ return { header: config.header, item, signal: { type, breaking } };
61
+ }
62
+ export function buildTitleItem(commit, isParsedBreaking, workTypes) {
63
+ const body = extractBody(stripChangeRecordBlocks(commit.message));
64
+ const item = { description: extractDescription(commit.message) };
65
+ if (body !== undefined) {
66
+ item.body = body;
67
+ }
68
+ if (isParsedBreaking && subjectHasBreakingMarker(commit.message, workTypes)) {
69
+ item.breaking = true;
70
+ }
71
+ const migration = extractMigration(body);
72
+ if (migration !== undefined) {
73
+ item.migration = migration;
74
+ }
75
+ item.hash = commit.hash;
76
+ return item;
77
+ }
78
+ function extractBody(message) {
79
+ const lines = message.split('\n').slice(1);
80
+ let start = 0;
81
+ while (start < lines.length && (lines[start] ?? '').trim() === '') {
82
+ start += 1;
83
+ }
84
+ let end = lines.length;
85
+ while (end > start) {
86
+ const line = lines[end - 1] ?? '';
87
+ const trimmed = line.trim();
88
+ if (trimmed === '') {
89
+ end -= 1;
90
+ continue;
91
+ }
92
+ if (TRAILER_PATTERNS.some((pattern) => pattern.test(trimmed))) {
93
+ end -= 1;
94
+ continue;
95
+ }
96
+ break;
97
+ }
98
+ if (end <= start) {
99
+ return undefined;
100
+ }
101
+ return lines.slice(start, end).join('\n').trim();
102
+ }
103
+ function extractDescription(message) {
104
+ const firstLine = message.split('\n', 1)[0] ?? message;
105
+ const afterColon = firstLine.split(': ').slice(1).join(': ');
106
+ return afterColon.charAt(0).toUpperCase() + afterColon.slice(1);
107
+ }
108
+ function isRoutedTo(scopes, routing) {
109
+ if (scopes.length === 0) {
110
+ return true;
111
+ }
112
+ return scopes.some((scope) => {
113
+ const resolved = routing.scopeAliases[scope] ?? scope;
114
+ return resolved === '*' || resolved === routing.scope;
115
+ });
116
+ }
117
+ function subjectHasBreakingMarker(message, workTypes) {
118
+ return parseSubject(message.split('\n', 1)[0] ?? '', workTypes)?.breaking === true;
119
+ }
@@ -0,0 +1,20 @@
1
+ import type { ReleaseHistory } from './buildChangelogEntries.js';
2
+ import type { ChangelogEntry, PropagationSource, ReleasedCompositeResult, ReleasedTargetResult, ReleaseTarget, ReleaseType } from './types.js';
3
+ /** The release level that a target takes, whether its own commits or a dependency's bump decided it. */
4
+ export interface TargetRelease {
5
+ releaseType: ReleaseType;
6
+ /** Dependencies whose bumps this release records; present for a propagated or mixed release. */
7
+ propagatedFrom?: PropagationSource[];
8
+ }
9
+ /** The fields of a direct release decision that a released result records; undefined for a propagated release. */
10
+ interface ReleaseDecisionFields {
11
+ bumpOverride: ReleaseType | undefined;
12
+ setVersion: string | undefined;
13
+ }
14
+ /** The fields that a released result of either kind takes from the plan. */
15
+ type ReleaseResultFields = Omit<ReleasedCompositeResult, 'commits' | 'kind' | 'parsedCommitCount' | 'releaseType' | 'status'>;
16
+ /** Builds the one synthetic entry of a release that propagation alone set, under the dependencies header. */
17
+ export declare function buildPropagationEntries(target: ReleaseTarget, release: TargetRelease, newVersion: string, today: string, dependenciesHeader: string): ChangelogEntry[];
18
+ /** Builds the released result of a target from the plan's fields, by the target's kind. */
19
+ export declare function buildReleasedResult(target: ReleaseTarget, fields: ReleaseResultFields, release: TargetRelease, decision: ReleaseDecisionFields | undefined, history: ReleaseHistory): ReleasedTargetResult;
20
+ export {};
@@ -0,0 +1,78 @@
1
+ import { attachChangelogDiagnostics } from "./attachChangelogDiagnostics.js";
2
+ import { buildSyntheticChangelogEntry } from "./buildSyntheticChangelogEntry.js";
3
+ export function buildPropagationEntries(target, release, newVersion, today, dependenciesHeader) {
4
+ if (release.propagatedFrom === undefined) {
5
+ throw new Error(`Workspace '${target.id}' has neither a direct release nor a propagation source`);
6
+ }
7
+ return [buildSyntheticChangelogEntry(release.propagatedFrom, newVersion, today, dependenciesHeader)];
8
+ }
9
+ export function buildReleasedResult(target, fields, release, decision, history) {
10
+ switch (target.kind) {
11
+ case 'package':
12
+ return buildReleasedPackage(target, fields, release, decision, history);
13
+ case 'directory':
14
+ return buildReleasedDirectory(target, fields, release, decision, history);
15
+ case 'composite':
16
+ return buildReleasedComposite(fields, release, decision, history);
17
+ }
18
+ }
19
+ function buildReleasedComposite(fields, release, decision, history) {
20
+ const { unreleased } = history;
21
+ const result = {
22
+ ...fields,
23
+ status: 'released',
24
+ kind: 'composite',
25
+ parsedCommitCount: unreleased.parsedCommitCount,
26
+ releaseType: release.releaseType,
27
+ commits: unreleased.commits,
28
+ ...(unreleased.unparseableCommits !== undefined && { unparseableCommits: unreleased.unparseableCommits }),
29
+ ...(decision?.bumpOverride !== undefined && { bumpOverride: decision.bumpOverride }),
30
+ };
31
+ attachChangelogDiagnostics(result, unreleased.diagnostics);
32
+ return result;
33
+ }
34
+ function buildReleasedDirectory(target, fields, release, decision, history) {
35
+ const { unreleased } = history;
36
+ const setVersion = decision?.setVersion;
37
+ const result = {
38
+ ...fields,
39
+ status: 'released',
40
+ kind: 'directory',
41
+ name: target.id,
42
+ commits: unreleased.commits,
43
+ ...(setVersion === undefined && {
44
+ releaseType: release.releaseType,
45
+ parsedCommitCount: unreleased.parsedCommitCount,
46
+ }),
47
+ ...(decision?.bumpOverride !== undefined && { bumpOverride: decision.bumpOverride }),
48
+ ...(setVersion !== undefined && { setVersion }),
49
+ };
50
+ if (setVersion === undefined && unreleased.unparseableCommits !== undefined) {
51
+ result.unparseableCommits = unreleased.unparseableCommits;
52
+ }
53
+ attachChangelogDiagnostics(result, unreleased.diagnostics);
54
+ return result;
55
+ }
56
+ function buildReleasedPackage(target, fields, release, decision, history) {
57
+ const { unreleased } = history;
58
+ const setVersion = decision?.setVersion;
59
+ const hasParseCounts = decision !== undefined && setVersion === undefined;
60
+ const result = {
61
+ ...fields,
62
+ status: 'released',
63
+ kind: 'package',
64
+ commits: unreleased.commits,
65
+ ...(target.isWorkspace && { name: target.id }),
66
+ ...(setVersion === undefined && { releaseType: release.releaseType }),
67
+ ...(hasParseCounts && { parsedCommitCount: unreleased.parsedCommitCount }),
68
+ ...(decision === undefined && { propagatedOnly: true }),
69
+ ...(release.propagatedFrom !== undefined && { propagatedFrom: release.propagatedFrom }),
70
+ ...(decision?.bumpOverride !== undefined && { bumpOverride: decision.bumpOverride }),
71
+ ...(setVersion !== undefined && { setVersion }),
72
+ };
73
+ if (setVersion === undefined && unreleased.unparseableCommits !== undefined) {
74
+ result.unparseableCommits = unreleased.unparseableCommits;
75
+ }
76
+ attachChangelogDiagnostics(result, unreleased.diagnostics);
77
+ return result;
78
+ }
@@ -4,7 +4,7 @@ import type { ChangelogEntry } from './types.js';
4
4
  *
5
5
  * Checks the entry's top level only: `sections` must be an array, and its contents pass through unvalidated.
6
6
  * `.meta/changelog.json` is written by release-kit and read tolerantly; strict validation applies to the
7
- * human-authored `.meta/changelog-overrides.json`, in `changelogOverrides.ts`.
7
+ * human-authored `.meta/changelog-overrides.json`, in `loadChangelogOverrides.ts`.
8
8
  */
9
9
  export declare function isChangelogEntry(value: unknown): value is ChangelogEntry;
10
10
  /** Extracts the version from a tag: the text from its first `N.N.N` onward, or the whole tag when it contains none. */