@williamthorsen/release-kit 11.0.0 → 12.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.
- package/.meta/changelog.json +62 -2
- package/.readyup/kits/default.js +1 -17
- package/.readyup/manifest.json +4 -4
- package/CHANGELOG.md +40 -0
- package/README.md +25 -44
- package/dist/esm/attachChangelogDiagnostics.d.ts +4 -0
- package/dist/esm/attachChangelogDiagnostics.js +11 -0
- package/dist/esm/bin/release-kit.js +4 -3
- package/dist/esm/buildChangelogEntries.d.ts +59 -17
- package/dist/esm/buildChangelogEntries.js +194 -111
- package/dist/esm/buildReleaseSummary.js +2 -2
- package/dist/esm/changelogOverrides.d.ts +31 -13
- package/dist/esm/changelogOverrides.js +138 -58
- package/dist/esm/classifyChangelogCommit.d.ts +38 -0
- package/dist/esm/classifyChangelogCommit.js +33 -0
- package/dist/esm/decideRelease.d.ts +11 -37
- package/dist/esm/decideRelease.js +5 -27
- package/dist/esm/defaults.d.ts +1 -1
- package/dist/esm/defaults.js +3 -0
- package/dist/esm/determineBumpType.d.ts +11 -11
- package/dist/esm/determineBumpType.js +6 -7
- package/dist/esm/enumerateReleaseWindows.d.ts +50 -0
- package/dist/esm/enumerateReleaseWindows.js +124 -0
- package/dist/esm/generateChangelogs.d.ts +5 -23
- package/dist/esm/generateChangelogs.js +0 -14
- package/dist/esm/glyphs.d.ts +1 -1
- package/dist/esm/help/prepareHelp.d.ts +1 -1
- package/dist/esm/help/prepareHelp.js +4 -6
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/init/initCommand.js +1 -1
- package/dist/esm/init/scaffold.d.ts +0 -2
- package/dist/esm/init/scaffold.js +2 -30
- package/dist/esm/loadConfig.d.ts +1 -1
- package/dist/esm/loadConfig.js +1 -4
- package/dist/esm/parseChangeRecordBlock.d.ts +37 -0
- package/dist/esm/parseChangeRecordBlock.js +134 -0
- package/dist/esm/parseCommitMessage.d.ts +31 -6
- package/dist/esm/parseCommitMessage.js +6 -5
- package/dist/esm/prepareCommand.js +4 -12
- package/dist/esm/releasePrepare.d.ts +7 -8
- package/dist/esm/releasePrepare.js +53 -73
- package/dist/esm/releasePrepareMono.js +48 -85
- package/dist/esm/releasePrepareProject.d.ts +1 -1
- package/dist/esm/releasePrepareProject.js +19 -37
- package/dist/esm/reportPrepare.js +39 -28
- package/dist/esm/types.d.ts +99 -55
- package/dist/esm/types.js +1 -1
- package/dist/esm/validateConfig.js +4 -0
- package/dist/esm/validateOverridesCommand.d.ts +6 -5
- package/dist/esm/validateOverridesCommand.js +16 -23
- package/docs/changelogs.md +64 -25
- package/docs/configuration.md +2 -3
- package/docs/editorial-overrides.md +75 -16
- package/docs/project-releases.md +4 -4
- package/docs/releasing.md +13 -8
- package/docs/work-types.md +13 -9
- package/package.json +1 -2
- package/schemas/changelog-overrides.json +39 -0
- package/cliff.toml.template +0 -65
- package/dist/esm/collectPolicyViolations.d.ts +0 -15
- package/dist/esm/collectPolicyViolations.js +0 -12
- package/dist/esm/determineBumpFromCommits.d.ts +0 -17
- package/dist/esm/determineBumpFromCommits.js +0 -28
- package/dist/esm/getCommitsSinceTarget.d.ts +0 -21
- package/dist/esm/getCommitsSinceTarget.js +0 -66
- package/dist/esm/resolveCliffConfigPath.d.ts +0 -11
- package/dist/esm/resolveCliffConfigPath.js +0 -20
- package/dist/esm/runGitCliff.d.ts +0 -38
- package/dist/esm/runGitCliff.js +0 -25
|
@@ -1,145 +1,228 @@
|
|
|
1
1
|
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
2
2
|
import { extractVersion } from "./changelogJsonUtils.js";
|
|
3
|
-
import {
|
|
3
|
+
import { classifyChangelogCommit, isNonChangeSubject, isReleaseSubject } from "./classifyChangelogCommit.js";
|
|
4
|
+
import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
5
|
+
import { determineBumpType } from "./determineBumpType.js";
|
|
6
|
+
import { enumerateReleaseWindows } from "./enumerateReleaseWindows.js";
|
|
4
7
|
import { extractMigration } from "./extractMigration.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { runGitCliff } from "./runGitCliff.js";
|
|
8
|
+
import { parseChangeRecordBlock, stripChangeRecordBlocks } from "./parseChangeRecordBlock.js";
|
|
9
|
+
import { COMMIT_PREPROCESSOR_PATTERNS, evaluateBreakingPolicy, PIPE_SCOPE_SOURCE, resolveType, } from "./parseCommitMessage.js";
|
|
8
10
|
import { stripEmojiPrefix } from "./stripEmojiPrefix.js";
|
|
9
|
-
|
|
10
|
-
const HTML_COMMENT_PREFIX_PATTERN = /^<!--[^>]*-->/;
|
|
11
|
+
const UNRELEASED_TAG = 'unreleased';
|
|
11
12
|
const CANONICAL_SECTION_ORDER = new Map(Object.values(DEFAULT_WORK_TYPES).map((config, index) => [stripGroupDecorations(config.header), index]));
|
|
12
13
|
function canonicalSectionPriority(title) {
|
|
13
14
|
const index = CANONICAL_SECTION_ORDER.get(stripGroupDecorations(title));
|
|
14
15
|
return index ?? Infinity;
|
|
15
16
|
}
|
|
16
17
|
export function stripGroupDecorations(group) {
|
|
17
|
-
return stripEmojiPrefix(group
|
|
18
|
+
return stripEmojiPrefix(group);
|
|
18
19
|
}
|
|
19
20
|
export function buildChangelogEntries(config, tag, options) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
const includePaths = options?.includePaths ?? [];
|
|
26
|
-
for (const includePath of includePaths) {
|
|
27
|
-
cliffArgs.push('--include-path', includePath);
|
|
28
|
-
}
|
|
21
|
+
const history = readReleaseHistory(config, options);
|
|
22
|
+
return { entries: toChangelogEntries(history, tag), diagnostics: history.unreleased.diagnostics };
|
|
23
|
+
}
|
|
24
|
+
export function readReleaseHistory(config, options) {
|
|
29
25
|
try {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
26
|
+
const windows = enumerateReleaseWindows({
|
|
27
|
+
...(options.paths !== undefined && { paths: options.paths }),
|
|
28
|
+
tagPrefixes: options.tagPrefixes,
|
|
29
|
+
unreleasedTag: UNRELEASED_TAG,
|
|
30
|
+
});
|
|
31
|
+
const context = {
|
|
32
|
+
breakingPolicies: config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES,
|
|
33
|
+
devOnlySections: new Set(config.changelogJson.devOnlySections.map(stripGroupDecorations)),
|
|
34
|
+
versionPatterns: config.versionPatterns ?? DEFAULT_VERSION_PATTERNS,
|
|
35
|
+
workTypes: config.workTypes ?? DEFAULT_WORK_TYPES,
|
|
36
|
+
};
|
|
37
|
+
return transformReleases(windows, context);
|
|
36
38
|
}
|
|
37
39
|
catch (error) {
|
|
38
|
-
throw chainError(`Failed to
|
|
40
|
+
throw chainError(`Failed to read the release history for ${options.tagPrefixes.join(', ')}`, error);
|
|
39
41
|
}
|
|
40
42
|
}
|
|
41
|
-
function
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
|
|
43
|
+
export function toChangelogEntries(history, tag) {
|
|
44
|
+
const { unreleased } = history;
|
|
45
|
+
if (unreleased.sections.length === 0) {
|
|
46
|
+
return [...history.releasedEntries];
|
|
45
47
|
}
|
|
46
|
-
return
|
|
48
|
+
return [
|
|
49
|
+
{ version: extractVersion(tag), date: unreleased.date, sections: unreleased.sections },
|
|
50
|
+
...history.releasedEntries,
|
|
51
|
+
];
|
|
47
52
|
}
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
function transformReleases(windows, context) {
|
|
54
|
+
const [unreleasedWindow, baselineWindow] = windows;
|
|
55
|
+
const diagnostics = { malformedBlocks: [], policyViolations: [], undeclaredEntryTypes: [] };
|
|
56
|
+
const signals = [];
|
|
57
|
+
const unparseable = [];
|
|
58
|
+
let parsedCommitCount = 0;
|
|
59
|
+
const sectionMap = new Map();
|
|
60
|
+
const unreleasedCommits = unreleasedWindow?.commits ?? [];
|
|
61
|
+
for (const commit of unreleasedCommits) {
|
|
62
|
+
const reading = readCommit(commit, context, diagnostics);
|
|
63
|
+
for (const derived of reading.items) {
|
|
64
|
+
appendItem(sectionMap, derived.header, derived.item);
|
|
65
|
+
signals.push(derived.signal);
|
|
66
|
+
}
|
|
67
|
+
if (reading.items.length > 0) {
|
|
68
|
+
parsedCommitCount += 1;
|
|
69
|
+
}
|
|
70
|
+
else if (reading.isUnparseable) {
|
|
71
|
+
unparseable.push(commit);
|
|
72
|
+
}
|
|
51
73
|
}
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
74
|
+
const releasedEntries = [];
|
|
75
|
+
for (const release of windows.slice(1)) {
|
|
76
|
+
const releaseSections = new Map();
|
|
77
|
+
for (const commit of release.commits) {
|
|
78
|
+
for (const derived of readCommit(commit, context, undefined).items) {
|
|
79
|
+
appendItem(releaseSections, derived.header, derived.item);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const sections = buildSections(releaseSections, context.devOnlySections);
|
|
83
|
+
if (sections.length > 0) {
|
|
84
|
+
releasedEntries.push({ version: extractVersion(release.version), date: formatDate(release.timestamp), sections });
|
|
85
|
+
}
|
|
55
86
|
}
|
|
56
|
-
|
|
57
|
-
|
|
87
|
+
return {
|
|
88
|
+
previousTag: baselineWindow?.version,
|
|
89
|
+
releasedEntries,
|
|
90
|
+
unreleased: {
|
|
91
|
+
bump: determineBumpType(signals, context.workTypes, context.versionPatterns),
|
|
92
|
+
commits: unreleasedCommits.filter((commit) => !isReleaseSubject(commit.subject)).toReversed(),
|
|
93
|
+
date: formatDate(unreleasedWindow?.timestamp ?? Math.floor(Date.now() / 1_000)),
|
|
94
|
+
diagnostics,
|
|
95
|
+
parsedCommitCount,
|
|
96
|
+
sections: buildSections(sectionMap, context.devOnlySections),
|
|
97
|
+
unparseableCommits: unparseable.length > 0 ? unparseable.toReversed() : undefined,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function readCommit(commit, context, diagnostics) {
|
|
102
|
+
if (isNonChangeSubject(commit.subject)) {
|
|
103
|
+
return { items: [], isUnparseable: false };
|
|
58
104
|
}
|
|
59
|
-
|
|
60
|
-
|
|
105
|
+
const reading = parseChangeRecordBlock(commit.message);
|
|
106
|
+
if (reading.kind === 'malformed') {
|
|
107
|
+
diagnostics?.malformedBlocks.push({
|
|
108
|
+
commitHash: commit.hash,
|
|
109
|
+
commitSubject: commit.subject,
|
|
110
|
+
reason: reading.reason,
|
|
111
|
+
});
|
|
61
112
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (!isRecord(value)) {
|
|
66
|
-
return { message: '' };
|
|
113
|
+
if (reading.kind === 'read' && reading.entries.length > 0) {
|
|
114
|
+
const items = reading.entries.flatMap((entry, entryIndex) => buildEntryItem(commit, entry, entryIndex + 1, reading.prNumber, context, diagnostics) ?? []);
|
|
115
|
+
return { items, isUnparseable: false };
|
|
67
116
|
}
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
117
|
+
const classification = classifyChangelogCommit(commit, context.workTypes, {
|
|
118
|
+
breakingPolicies: context.breakingPolicies,
|
|
119
|
+
...(diagnostics !== undefined && {
|
|
120
|
+
onPolicyViolation: (violating, type, surface) => {
|
|
121
|
+
diagnostics.policyViolations.push({
|
|
122
|
+
commitHash: violating.hash,
|
|
123
|
+
commitSubject: violating.subject,
|
|
124
|
+
type,
|
|
125
|
+
surface,
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
}),
|
|
129
|
+
});
|
|
130
|
+
if (classification.kind !== 'header') {
|
|
131
|
+
return { items: [], isUnparseable: classification.kind === 'unparseable' && reading.kind !== 'malformed' };
|
|
73
132
|
}
|
|
74
|
-
|
|
75
|
-
|
|
133
|
+
const item = buildTitleItem(commit, classification.breaking);
|
|
134
|
+
return {
|
|
135
|
+
items: [
|
|
136
|
+
{ header: classification.header, item, signal: { type: classification.type, breaking: item.breaking === true } },
|
|
137
|
+
],
|
|
138
|
+
isUnparseable: false,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function buildSections(sectionMap, devOnlySections) {
|
|
142
|
+
const sections = [];
|
|
143
|
+
for (const [title, items] of sectionMap) {
|
|
144
|
+
sections.push({
|
|
145
|
+
title,
|
|
146
|
+
audience: devOnlySections.has(stripGroupDecorations(title)) ? 'dev' : 'all',
|
|
147
|
+
items,
|
|
148
|
+
});
|
|
76
149
|
}
|
|
77
|
-
return
|
|
150
|
+
return sections.toSorted((a, b) => canonicalSectionPriority(a.title) - canonicalSectionPriority(b.title));
|
|
78
151
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const date = release.timestamp !== undefined ? new Date(release.timestamp * 1_000).toISOString().slice(0, 10) : 'unreleased';
|
|
88
|
-
const sectionMap = new Map();
|
|
89
|
-
const commits = release.commits ?? [];
|
|
90
|
-
for (const commit of commits) {
|
|
91
|
-
const group = stripCommentPrefix(commit.group ?? 'Other');
|
|
92
|
-
const description = extractDescription(commit.message);
|
|
93
|
-
const body = extractBody(commit.message);
|
|
94
|
-
const breaking = isBreakingUnderPolicy(commit, workTypes, breakingPolicies);
|
|
95
|
-
let items = sectionMap.get(group);
|
|
96
|
-
if (items === undefined) {
|
|
97
|
-
items = [];
|
|
98
|
-
sectionMap.set(group, items);
|
|
99
|
-
}
|
|
100
|
-
const item = { description };
|
|
101
|
-
if (body !== undefined) {
|
|
102
|
-
item.body = body;
|
|
103
|
-
}
|
|
104
|
-
if (breaking) {
|
|
105
|
-
item.breaking = true;
|
|
106
|
-
}
|
|
107
|
-
const migration = extractMigration(body);
|
|
108
|
-
if (migration !== undefined) {
|
|
109
|
-
item.migration = migration;
|
|
110
|
-
}
|
|
111
|
-
if (commit.id !== undefined && commit.id !== '') {
|
|
112
|
-
item.hash = commit.id;
|
|
113
|
-
}
|
|
114
|
-
items.push(item);
|
|
115
|
-
}
|
|
116
|
-
const sections = [];
|
|
117
|
-
for (const [title, items] of sectionMap) {
|
|
118
|
-
if (items.length === 0) {
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
sections.push({
|
|
122
|
-
title,
|
|
123
|
-
audience: devOnlyNormalised.has(stripGroupDecorations(title)) ? 'dev' : 'all',
|
|
124
|
-
items,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
sections.sort((a, b) => canonicalSectionPriority(a.title) - canonicalSectionPriority(b.title));
|
|
128
|
-
if (sections.length > 0) {
|
|
129
|
-
entries.push({ version, date, sections });
|
|
130
|
-
}
|
|
152
|
+
function formatDate(timestamp) {
|
|
153
|
+
return new Date(timestamp * 1_000).toISOString().slice(0, 10);
|
|
154
|
+
}
|
|
155
|
+
function appendItem(sectionMap, header, item) {
|
|
156
|
+
let items = sectionMap.get(header);
|
|
157
|
+
if (items === undefined) {
|
|
158
|
+
items = [];
|
|
159
|
+
sectionMap.set(header, items);
|
|
131
160
|
}
|
|
132
|
-
|
|
161
|
+
items.push(item);
|
|
133
162
|
}
|
|
134
|
-
function
|
|
135
|
-
|
|
163
|
+
function buildEntryItem(commit, entry, position, prNumber, context, diagnostics) {
|
|
164
|
+
const { breakingPolicies, workTypes } = context;
|
|
165
|
+
const type = resolveType(entry.type, workTypes);
|
|
166
|
+
if (type === undefined) {
|
|
167
|
+
diagnostics?.undeclaredEntryTypes.push({
|
|
168
|
+
commitHash: commit.hash,
|
|
169
|
+
commitSubject: commit.subject,
|
|
170
|
+
entryPosition: position,
|
|
171
|
+
type: entry.type,
|
|
172
|
+
});
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
const config = workTypes[type];
|
|
176
|
+
if (config === undefined || config.excludedFromChangelog === true) {
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
const breaking = evaluateBreakingPolicy({
|
|
180
|
+
commit: { message: commit.message, subject: commit.subject, hash: commit.hash },
|
|
181
|
+
resolvedType: type,
|
|
182
|
+
hasPrefixBreaking: entry.breaking,
|
|
183
|
+
hasFooterBreaking: false,
|
|
184
|
+
policy: breakingPolicies[type] ?? 'optional',
|
|
185
|
+
prefixSurface: 'entry',
|
|
186
|
+
onPolicyViolation: diagnostics === undefined
|
|
187
|
+
? undefined
|
|
188
|
+
: (violating, violatedType, surface) => {
|
|
189
|
+
diagnostics.policyViolations.push({
|
|
190
|
+
commitHash: violating.hash,
|
|
191
|
+
commitSubject: violating.subject,
|
|
192
|
+
type: violatedType,
|
|
193
|
+
surface,
|
|
194
|
+
entryPosition: position,
|
|
195
|
+
});
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
const item = {
|
|
199
|
+
description: prNumber === undefined ? entry.text : `${entry.text} (#${prNumber})`,
|
|
200
|
+
};
|
|
201
|
+
if (breaking) {
|
|
202
|
+
item.breaking = true;
|
|
203
|
+
}
|
|
204
|
+
if (entry.migration !== undefined) {
|
|
205
|
+
item.migration = entry.migration;
|
|
206
|
+
}
|
|
207
|
+
item.hash = commit.hash;
|
|
208
|
+
item.entry = position;
|
|
209
|
+
return { header: config.header, item, signal: { type, breaking } };
|
|
136
210
|
}
|
|
137
|
-
function
|
|
138
|
-
|
|
139
|
-
|
|
211
|
+
function buildTitleItem(commit, isParsedBreaking) {
|
|
212
|
+
const body = extractBody(stripChangeRecordBlocks(commit.message));
|
|
213
|
+
const item = { description: extractDescription(commit.message) };
|
|
214
|
+
if (body !== undefined) {
|
|
215
|
+
item.body = body;
|
|
216
|
+
}
|
|
217
|
+
if (isParsedBreaking && subjectHasBreakingMarker(commit.message)) {
|
|
218
|
+
item.breaking = true;
|
|
219
|
+
}
|
|
220
|
+
const migration = extractMigration(body);
|
|
221
|
+
if (migration !== undefined) {
|
|
222
|
+
item.migration = migration;
|
|
140
223
|
}
|
|
141
|
-
|
|
142
|
-
return
|
|
224
|
+
item.hash = commit.hash;
|
|
225
|
+
return item;
|
|
143
226
|
}
|
|
144
227
|
const SUBJECT_BREAKING_MARKER_PATTERN = new RegExp(String.raw `^(?:${PIPE_SCOPE_SOURCE}\|)?\w+(?:\([^)]+\))?!:`);
|
|
145
228
|
function subjectHasBreakingMarker(message) {
|
|
@@ -11,7 +11,7 @@ export function buildReleaseSummary(result) {
|
|
|
11
11
|
}
|
|
12
12
|
const lines = [workspace.tag];
|
|
13
13
|
for (const commit of commits) {
|
|
14
|
-
lines.push(`- ${stripScope(commit.
|
|
14
|
+
lines.push(`- ${stripScope(commit.subject)}`);
|
|
15
15
|
}
|
|
16
16
|
sections.push(lines.join('\n'));
|
|
17
17
|
}
|
|
@@ -19,7 +19,7 @@ export function buildReleaseSummary(result) {
|
|
|
19
19
|
if (project !== undefined && project.status === 'released' && project.commits.length > 0) {
|
|
20
20
|
const lines = [project.tag];
|
|
21
21
|
for (const commit of project.commits) {
|
|
22
|
-
lines.push(`- ${stripScope(commit.
|
|
22
|
+
lines.push(`- ${stripScope(commit.subject)}`);
|
|
23
23
|
}
|
|
24
24
|
sections.push(lines.join('\n'));
|
|
25
25
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { ChangelogEntry, ChangelogOverride, WorkspaceConfig } from './types.js';
|
|
2
|
+
/** Known fields on a single override entry; presence of any other field is a validation error. */
|
|
3
|
+
export declare const KNOWN_OVERRIDE_FIELDS: Set<string>;
|
|
4
|
+
/** Form of an override key: a lowercase hex commit hash or prefix, optionally followed by `:<n>`, a 1-based entry position. */
|
|
5
|
+
export declare const OVERRIDE_KEY_PATTERN: RegExp;
|
|
2
6
|
/** Result of loading an override file: either parsed overrides or a list of structured errors. */
|
|
3
7
|
export type LoadChangelogOverridesResult = {
|
|
4
8
|
overrides: Map<string, ChangelogOverride>;
|
|
@@ -26,6 +30,11 @@ export declare function validateChangelogOverrides(raw: unknown): {
|
|
|
26
30
|
overrides: Map<string, ChangelogOverride>;
|
|
27
31
|
errors: string[];
|
|
28
32
|
};
|
|
33
|
+
/** Split a valid override key into its hash prefix and, for an ordinal key, its 1-based entry position. */
|
|
34
|
+
export declare function parseOverrideKey(key: string): {
|
|
35
|
+
hashPrefix: string;
|
|
36
|
+
entry?: number;
|
|
37
|
+
};
|
|
29
38
|
/**
|
|
30
39
|
* Format the standard "stale override key" warning. Callers compute the set of stale keys
|
|
31
40
|
* (keys that didn't match anywhere) and use this helper to surface them uniformly. Centralized
|
|
@@ -35,12 +44,16 @@ export declare function formatStaleOverrideKeyWarning(key: string): string;
|
|
|
35
44
|
/**
|
|
36
45
|
* Apply overrides to a `ChangelogEntry[]`, returning a new array. Pure: no mutation, no I/O.
|
|
37
46
|
*
|
|
38
|
-
* Match algorithm: each
|
|
47
|
+
* Match algorithm: each key's hash prefix is matched against the distinct `ChangelogItem.hash` values.
|
|
39
48
|
* - 0 matches → key is recorded as unmatched in this batch (no warning emitted here).
|
|
40
|
-
* - 1 match →
|
|
49
|
+
* - 1 match → a bare key resolves to every item of that commit, and an ordinal key `<hash>:<n>` to the items whose
|
|
50
|
+
* `entry` is `n`. A key that resolves to no item is unmatched, so an ordinal key on a title-derived item goes stale.
|
|
41
51
|
* - 2+ matches → error (ambiguous prefix).
|
|
42
52
|
*
|
|
43
|
-
*
|
|
53
|
+
* Two more conditions are errors, and the keys involved apply nowhere: a bare key that sets `description` or `body` on a
|
|
54
|
+
* commit with several items, and two keys that resolve to the same item.
|
|
55
|
+
*
|
|
56
|
+
* `matchedKeys` lists the override keys that resolved to at least one item without error in this batch.
|
|
44
57
|
* Callers are responsible for computing stale-key warnings: in a monorepo run, an override
|
|
45
58
|
* may target a commit that lives in another workspace, so the per-batch zero-match signal is
|
|
46
59
|
* insufficient on its own. Single-package and monorepo orchestrators format warnings using
|
|
@@ -98,8 +111,8 @@ export declare function loadOverridesForScopes(scopes: {
|
|
|
98
111
|
*
|
|
99
112
|
* Byte-equal-key shadowing: when a workspace key string-equals a root key, the workspace
|
|
100
113
|
* entry wins entirely (no field-level merge) and supplants the root entry in the result.
|
|
101
|
-
*
|
|
102
|
-
*
|
|
114
|
+
* Non-identical keys that resolve to the same item do NOT shadow here: {@link applyChangelogOverrides}
|
|
115
|
+
* reports them as overlapping.
|
|
103
116
|
*
|
|
104
117
|
* Pure: never mutates inputs.
|
|
105
118
|
*/
|
|
@@ -127,12 +140,17 @@ export interface OverrideContext {
|
|
|
127
140
|
* is a bug, and we want the prepare run to fail before any workspace begins writing.
|
|
128
141
|
*/
|
|
129
142
|
export declare function createOverrideContext(workspaces: WorkspaceConfig[]): OverrideContext;
|
|
143
|
+
/** A changelog item as override matching sees it. `entry` is absent for a title-derived item. */
|
|
144
|
+
export interface OverrideTargetItem {
|
|
145
|
+
hash: string;
|
|
146
|
+
entry?: number;
|
|
147
|
+
}
|
|
130
148
|
/** Per-scope input to {@link validateAllChangelogOverrides}. */
|
|
131
149
|
export interface ChangelogOverrideScope {
|
|
132
150
|
/** Path to the override file (relative to the repo root). Used to load the file and to attribute findings. */
|
|
133
151
|
filePath: string;
|
|
134
|
-
/**
|
|
135
|
-
|
|
152
|
+
/** Items in this scope's history window. Each override key is matched against these. */
|
|
153
|
+
items: readonly OverrideTargetItem[];
|
|
136
154
|
}
|
|
137
155
|
/** Inputs to {@link validateAllChangelogOverrides}. */
|
|
138
156
|
export interface ValidateAllChangelogOverridesInputs {
|
|
@@ -141,16 +159,16 @@ export interface ValidateAllChangelogOverridesInputs {
|
|
|
141
159
|
*
|
|
142
160
|
* The file at `filePath` is loaded once and used in two ways:
|
|
143
161
|
* - Its overrides are composed into every workspace's apply (root-tier overrides apply globally).
|
|
144
|
-
* - When `
|
|
162
|
+
* - When `items` is provided, the project map is also applied directly to those items
|
|
145
163
|
* (project release in monorepo mode, or the package's history in single-package mode).
|
|
146
164
|
*
|
|
147
165
|
* Omit when no project file exists (rare — most repos have a root file even if empty).
|
|
148
166
|
*/
|
|
149
167
|
project?: {
|
|
150
168
|
filePath: string;
|
|
151
|
-
|
|
169
|
+
items?: readonly OverrideTargetItem[];
|
|
152
170
|
};
|
|
153
|
-
/** Per-workspace scopes. Each workspace's file applies only to its own
|
|
171
|
+
/** Per-workspace scopes. Each workspace's file applies only to its own items. */
|
|
154
172
|
workspaces?: readonly ChangelogOverrideScope[];
|
|
155
173
|
}
|
|
156
174
|
/** Result of {@link validateAllChangelogOverrides}: aggregated errors and warnings, each prefixed with the file path it pertains to. */
|
|
@@ -160,16 +178,16 @@ export interface ValidateAllChangelogOverridesResult {
|
|
|
160
178
|
}
|
|
161
179
|
/**
|
|
162
180
|
* End-to-end health check across every override file and scope. Pure: takes already-collected
|
|
163
|
-
*
|
|
181
|
+
* item universes and returns aggregated findings. The CLI command and any other consumer
|
|
164
182
|
* (programmatic library callers, future composite checks) wrap this with discovery and I/O.
|
|
165
183
|
*
|
|
166
184
|
* When invoked via the standard `validateOverridesCommand` entry point, each scope's
|
|
167
|
-
* `
|
|
185
|
+
* `items` is built by `buildChangelogEntries` — the same path `release-kit prepare` walks —
|
|
168
186
|
* so the match-set is byte-equal to what `prepare` would compute. The tier asymmetry is part
|
|
169
187
|
* of that contract: workspace-tier keys are stale if they don't match in their own workspace;
|
|
170
188
|
* root-tier keys are stale only if they don't match in any scope (no workspace AND not the
|
|
171
189
|
* project release window). Library callers that construct `inputs` directly are responsible
|
|
172
|
-
* for supplying the same
|
|
190
|
+
* for supplying the same item universes if they want this guarantee.
|
|
173
191
|
*
|
|
174
192
|
* Every returned string is prefixed with the relative override-file path it pertains to so
|
|
175
193
|
* consumers can locate the offending file without further structuring.
|