@williamthorsen/release-kit 14.2.2 → 14.2.4
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 +88 -0
- package/.readyup/kits/default.js +135 -145
- package/.readyup/kits/npm-auto-publish.js +179 -180
- package/.readyup/manifest.json +31 -17
- package/CHANGELOG.md +35 -0
- package/README.md +7 -1
- package/dist/esm/applyChangelogOverrides.d.ts +50 -0
- package/dist/esm/applyChangelogOverrides.js +162 -0
- package/dist/esm/attachChangelogDiagnostics.d.ts +1 -1
- package/dist/esm/bin/release-kit.js +6 -238
- package/dist/esm/buildChangelogEntries.d.ts +24 -35
- package/dist/esm/buildChangelogEntries.js +71 -188
- package/dist/esm/buildChangelogItem.d.ts +58 -0
- package/dist/esm/buildChangelogItem.js +122 -0
- package/dist/esm/buildReleasedResult.d.ts +20 -0
- package/dist/esm/buildReleasedResult.js +78 -0
- package/dist/esm/changelogJsonUtils.d.ts +1 -1
- package/dist/esm/changelogOverrides.d.ts +29 -143
- package/dist/esm/changelogOverrides.js +35 -467
- package/dist/esm/configSchema.d.ts +225 -0
- package/dist/esm/configSchema.js +103 -0
- package/dist/esm/decideDirectReleases.d.ts +30 -0
- package/dist/esm/decideDirectReleases.js +66 -0
- package/dist/esm/defineConfig.d.ts +2 -2
- package/dist/esm/deriveReleaseNotesConfig.d.ts +2 -1
- package/dist/esm/deriveReleaseNotesConfig.js +1 -1
- package/dist/esm/formatPrepareFiles.d.ts +11 -0
- package/dist/esm/formatPrepareFiles.js +55 -0
- package/dist/esm/formatPrepareWarnings.d.ts +26 -0
- package/dist/esm/formatPrepareWarnings.js +72 -0
- package/dist/esm/help/initHelp.d.ts +4 -0
- package/dist/esm/help/initHelp.js +31 -0
- package/dist/esm/help/overridesHelp.d.ts +4 -0
- package/dist/esm/help/overridesHelp.js +32 -0
- package/dist/esm/help/releaseStepsHelp.d.ts +12 -0
- package/dist/esm/help/releaseStepsHelp.js +91 -0
- package/dist/esm/help/syncLabelsHelp.d.ts +8 -0
- package/dist/esm/help/syncLabelsHelp.js +55 -0
- package/dist/esm/help/usage.d.ts +2 -0
- package/dist/esm/help/usage.js +29 -0
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/loadChangelogOverrides.d.ts +31 -0
- package/dist/esm/loadChangelogOverrides.js +138 -0
- package/dist/esm/loadConfig.d.ts +0 -40
- package/dist/esm/loadConfig.js +0 -240
- package/dist/esm/loadValidatedConfig.d.ts +1 -1
- package/dist/esm/mergeConfig.d.ts +41 -0
- package/dist/esm/mergeConfig.js +118 -0
- package/dist/esm/monorepoConfigAssertions.d.ts +49 -0
- package/dist/esm/monorepoConfigAssertions.js +126 -0
- package/dist/esm/planChangelogFiles.d.ts +16 -0
- package/dist/esm/planChangelogFiles.js +41 -0
- package/dist/esm/prepareArgs.d.ts +55 -0
- package/dist/esm/prepareArgs.js +69 -0
- package/dist/esm/prepareCommand.d.ts +0 -48
- package/dist/esm/prepareCommand.js +6 -73
- package/dist/esm/prepareComposite.d.ts +16 -0
- package/dist/esm/prepareComposite.js +39 -0
- package/dist/esm/prepareRunContext.d.ts +38 -0
- package/dist/esm/prepareRunContext.js +39 -0
- package/dist/esm/prepareTarget.d.ts +2 -7
- package/dist/esm/prepareTarget.js +2 -118
- package/dist/esm/previewTagPrefixes.d.ts +1 -1
- package/dist/esm/releasePrepareTargets.js +13 -205
- package/dist/esm/reportPrepare.js +3 -125
- package/dist/esm/resolveCommandTags.d.ts +1 -1
- package/dist/esm/resolveCommandTags.js +2 -1
- package/dist/esm/resolveReleaseDeclaration.d.ts +2 -1
- package/dist/esm/resolveReleaseTargets.d.ts +2 -1
- package/dist/esm/resolveReleaseTargets.js +2 -1
- package/dist/esm/sync-labels/generateCommand.d.ts +1 -1
- package/dist/esm/sync-labels/resolveLabels.d.ts +1 -1
- package/dist/esm/topologicalSort.d.ts +12 -0
- package/dist/esm/topologicalSort.js +60 -0
- package/dist/esm/types.d.ts +1 -225
- package/dist/esm/types.js +1 -103
- package/dist/esm/validateAllChangelogOverrides.d.ts +56 -0
- package/dist/esm/validateAllChangelogOverrides.js +138 -0
- package/dist/esm/validateConfig.d.ts +1 -1
- package/dist/esm/validateConfig.js +7 -9
- package/dist/esm/validateOverridesCommand.d.ts +1 -1
- package/dist/esm/validateOverridesCommand.js +2 -1
- package/docs/changelogs.md +6 -4
- package/package.json +4 -4
|
@@ -1,307 +1,52 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
1
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { isRecord } from "./typeGuards.js";
|
|
2
|
+
import { applyChangelogOverrides, formatStaleOverrideKeyWarning } from "./applyChangelogOverrides.js";
|
|
3
|
+
import { loadChangelogOverrides } from "./loadChangelogOverrides.js";
|
|
6
4
|
const OVERRIDES_FILENAME = '.meta/changelog-overrides.json';
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return { overrides: new Map() };
|
|
15
|
-
}
|
|
16
|
-
let content;
|
|
17
|
-
try {
|
|
18
|
-
content = readFileSync(path, 'utf8');
|
|
19
|
-
}
|
|
20
|
-
catch (error) {
|
|
21
|
-
return {
|
|
22
|
-
errors: [`Failed to read override file ${path}: ${describeError(error)}`],
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
let parsed;
|
|
26
|
-
try {
|
|
27
|
-
parsed = JSON.parse(content);
|
|
28
|
-
}
|
|
29
|
-
catch (error) {
|
|
30
|
-
return {
|
|
31
|
-
errors: [`Failed to parse override file ${path}: ${describeError(error)}`],
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
const result = validateChangelogOverrides(parsed);
|
|
35
|
-
if (result.errors.length > 0) {
|
|
36
|
-
return { errors: result.errors };
|
|
37
|
-
}
|
|
38
|
-
return { overrides: result.overrides };
|
|
39
|
-
}
|
|
40
|
-
export function validateChangelogOverrides(raw) {
|
|
41
|
-
const overrides = new Map();
|
|
42
|
-
const errors = [];
|
|
43
|
-
if (!isRecord(raw)) {
|
|
44
|
-
errors.push('Override file: top-level value must be an object keyed by commit hash');
|
|
45
|
-
return { overrides, errors };
|
|
46
|
-
}
|
|
47
|
-
for (const [key, rawEntry] of Object.entries(raw)) {
|
|
48
|
-
if (key === SCHEMA_KEY) {
|
|
49
|
-
if (typeof rawEntry !== 'string') {
|
|
50
|
-
errors.push(`Override file: '${SCHEMA_KEY}' must be a string`);
|
|
51
|
-
}
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
if (!OVERRIDE_KEY_PATTERN.test(key)) {
|
|
55
|
-
errors.push(`overrides['${key}']: key must be a lowercase hex commit hash or prefix, optionally followed by ':<n>'`);
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
const validated = validateSingleOverride(key, rawEntry, errors);
|
|
59
|
-
if (validated !== undefined) {
|
|
60
|
-
overrides.set(key, validated);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return { overrides, errors };
|
|
64
|
-
}
|
|
65
|
-
export function parseOverrideKey(key) {
|
|
66
|
-
const separatorIndex = key.indexOf(':');
|
|
67
|
-
if (separatorIndex === -1) {
|
|
68
|
-
return { hashPrefix: key };
|
|
69
|
-
}
|
|
70
|
-
return { hashPrefix: key.slice(0, separatorIndex), entry: Number(key.slice(separatorIndex + 1)) };
|
|
71
|
-
}
|
|
72
|
-
function validateSingleOverride(key, rawEntry, errors) {
|
|
73
|
-
if (!isRecord(rawEntry)) {
|
|
74
|
-
errors.push(`overrides['${key}']: must be an object`);
|
|
75
|
-
return undefined;
|
|
76
|
-
}
|
|
77
|
-
let entryValid = true;
|
|
78
|
-
for (const fieldName of Object.keys(rawEntry)) {
|
|
79
|
-
if (KNOWN_OVERRIDE_FIELDS.has(fieldName)) {
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
errors.push(`overrides['${key}']: unknown field '${fieldName}'`);
|
|
83
|
-
entryValid = false;
|
|
84
|
-
}
|
|
85
|
-
const result = {};
|
|
86
|
-
if (rawEntry['audience'] !== undefined) {
|
|
87
|
-
const audienceResult = validateAudience(key, rawEntry['audience'], errors);
|
|
88
|
-
if (audienceResult === undefined) {
|
|
89
|
-
entryValid = false;
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
result.audience = audienceResult;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (rawEntry['description'] !== undefined) {
|
|
96
|
-
if (typeof rawEntry['description'] !== 'string') {
|
|
97
|
-
errors.push(`overrides['${key}']: 'description' must be a string`);
|
|
98
|
-
entryValid = false;
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
result.description = rawEntry['description'];
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
if (rawEntry['body'] !== undefined) {
|
|
105
|
-
if (typeof rawEntry['body'] !== 'string') {
|
|
106
|
-
errors.push(`overrides['${key}']: 'body' must be a string`);
|
|
107
|
-
entryValid = false;
|
|
108
|
-
}
|
|
109
|
-
else {
|
|
110
|
-
result.body = rawEntry['body'];
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (rawEntry['breaking'] !== undefined) {
|
|
114
|
-
if (typeof rawEntry['breaking'] !== 'boolean') {
|
|
115
|
-
errors.push(`overrides['${key}']: 'breaking' must be a boolean`);
|
|
116
|
-
entryValid = false;
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
result.breaking = rawEntry['breaking'];
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
if (Object.keys(result).length === 0 && entryValid) {
|
|
123
|
-
errors.push(`overrides['${key}']: at least one override field must be set`);
|
|
124
|
-
return undefined;
|
|
125
|
-
}
|
|
126
|
-
if (!entryValid) {
|
|
127
|
-
return undefined;
|
|
128
|
-
}
|
|
129
|
-
return result;
|
|
130
|
-
}
|
|
131
|
-
function validateAudience(key, value, errors) {
|
|
132
|
-
if (typeof value !== 'string' || !VALID_AUDIENCE_VALUES.has(value)) {
|
|
133
|
-
errors.push(`overrides['${key}']: 'audience' must be one of 'all' | 'dev' | 'skip'`);
|
|
134
|
-
return undefined;
|
|
135
|
-
}
|
|
136
|
-
if (!V1_SUPPORTED_AUDIENCE_VALUES.has(value)) {
|
|
137
|
-
errors.push(`overrides['${key}']: audience '${value}' is not yet supported; only 'skip' is currently accepted`);
|
|
138
|
-
return undefined;
|
|
139
|
-
}
|
|
140
|
-
return 'skip';
|
|
141
|
-
}
|
|
142
|
-
export function formatStaleOverrideKeyWarning(key) {
|
|
143
|
-
return `Override key '${key}' did not match any item in the changelog (likely a stale reference)`;
|
|
144
|
-
}
|
|
145
|
-
export function applyChangelogOverrides(entries, overrides) {
|
|
146
|
-
const warnings = [];
|
|
147
|
-
if (overrides.size === 0) {
|
|
148
|
-
return { entries: entries.map(cloneEntry), warnings, errors: [], matchedKeys: [] };
|
|
149
|
-
}
|
|
150
|
-
const { errors, itemIdToKey } = resolveOverrideKeys(indexEntryPositions(entries), overrides);
|
|
151
|
-
const itemIdToOverride = new Map();
|
|
152
|
-
for (const [itemId, overrideKey] of itemIdToKey) {
|
|
153
|
-
const override = overrides.get(overrideKey);
|
|
154
|
-
if (override !== undefined) {
|
|
155
|
-
itemIdToOverride.set(itemId, override);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const matchedKeys = [...new Set(itemIdToKey.values())];
|
|
159
|
-
const transformedEntries = [];
|
|
160
|
-
for (const entry of entries) {
|
|
161
|
-
const transformedSections = [];
|
|
162
|
-
for (const section of entry.sections) {
|
|
163
|
-
const transformedItems = applyOverridesToItems(section.items, itemIdToOverride);
|
|
164
|
-
if (transformedItems.length === 0) {
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
transformedSections.push({ ...section, items: transformedItems });
|
|
168
|
-
}
|
|
169
|
-
transformedEntries.push({ ...entry, sections: transformedSections });
|
|
5
|
+
export function applyWorkspaceOverrides(newEntries, workspacePath, overrideContext) {
|
|
6
|
+
const { project, perWorkspace, overrideWarnings, globalMatchedRootKeys } = overrideContext;
|
|
7
|
+
const workspaceOverrides = workspacePath === undefined ? undefined : perWorkspace.get(workspacePath);
|
|
8
|
+
const composed = composeOverrides(project, workspaceOverrides);
|
|
9
|
+
const applied = applyChangelogOverrides(newEntries, composed);
|
|
10
|
+
if (applied.errors.length > 0) {
|
|
11
|
+
throw new Error(`Changelog override application failed:\n - ${applied.errors.join('\n - ')}`);
|
|
170
12
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
for (const [overrideKey, override] of overrides) {
|
|
178
|
-
const resolution = resolveOverrideKey(overrideKey, positionsByHash);
|
|
179
|
-
if (resolution.kind === 'ambiguous') {
|
|
180
|
-
errors.push(`Override key '${overrideKey}' is ambiguous: matches multiple commits (${resolution.hashes.join(', ')}). ` +
|
|
181
|
-
'Use a longer prefix or the full commit hash.');
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
if (resolution.kind === 'none') {
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
const { hash, positions } = resolution;
|
|
188
|
-
if (parseOverrideKey(overrideKey).entry === undefined && positions.length > 1) {
|
|
189
|
-
const fieldsSet = ['description', 'body'].filter((field) => override[field] !== undefined);
|
|
190
|
-
if (fieldsSet.length > 0) {
|
|
191
|
-
const ordinalKeys = positions.map((position) => `${overrideKey}:${position}`).join(', ');
|
|
192
|
-
errors.push(`Override key '${overrideKey}' sets ${fieldsSet.join(' and ')} on a commit with several items; use ${ordinalKeys}`);
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
for (const position of positions) {
|
|
197
|
-
const itemId = formatItemId(hash, position);
|
|
198
|
-
const claimingKey = itemIdToKey.get(itemId);
|
|
199
|
-
if (claimingKey === undefined) {
|
|
200
|
-
itemIdToKey.set(itemId, overrideKey);
|
|
201
|
-
continue;
|
|
13
|
+
overrideWarnings.push(...applied.warnings);
|
|
14
|
+
const matchedSet = new Set(applied.matchedKeys);
|
|
15
|
+
if (workspaceOverrides !== undefined) {
|
|
16
|
+
for (const key of workspaceOverrides.keys()) {
|
|
17
|
+
if (!matchedSet.has(key)) {
|
|
18
|
+
overrideWarnings.push(formatStaleOverrideKeyWarning(key));
|
|
202
19
|
}
|
|
203
|
-
const target = position === undefined ? `commit ${hash}` : `the item at ${hash}:${position}`;
|
|
204
|
-
errors.push(`Override keys '${claimingKey}' and '${overrideKey}' both match ${target}; keep one`);
|
|
205
|
-
failedKeys.add(claimingKey);
|
|
206
|
-
failedKeys.add(overrideKey);
|
|
207
20
|
}
|
|
208
21
|
}
|
|
209
|
-
for (const
|
|
210
|
-
if (
|
|
211
|
-
itemIdToKey.delete(itemId);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return { errors, itemIdToKey };
|
|
215
|
-
}
|
|
216
|
-
function resolveOverrideKey(overrideKey, positionsByHash) {
|
|
217
|
-
const { hashPrefix, entry } = parseOverrideKey(overrideKey);
|
|
218
|
-
const hashes = positionsByHash
|
|
219
|
-
.keys()
|
|
220
|
-
.filter((hash) => hash.startsWith(hashPrefix))
|
|
221
|
-
.toArray();
|
|
222
|
-
const [hash] = hashes;
|
|
223
|
-
if (hash === undefined) {
|
|
224
|
-
return { kind: 'none' };
|
|
225
|
-
}
|
|
226
|
-
if (hashes.length > 1) {
|
|
227
|
-
return { kind: 'ambiguous', hashes };
|
|
228
|
-
}
|
|
229
|
-
const available = [...(positionsByHash.get(hash) ?? [])];
|
|
230
|
-
const positions = entry === undefined
|
|
231
|
-
? available.toSorted((left, right) => (left ?? 0) - (right ?? 0))
|
|
232
|
-
: available.filter((position) => position === entry);
|
|
233
|
-
return positions.length === 0 ? { kind: 'none' } : { kind: 'items', hash, positions };
|
|
234
|
-
}
|
|
235
|
-
function indexEntryPositions(entries) {
|
|
236
|
-
return indexItemPositions(entries.flatMap((entry) => entry.sections.flatMap((section) => section.items)));
|
|
237
|
-
}
|
|
238
|
-
function indexItemPositions(items) {
|
|
239
|
-
const positionsByHash = new Map();
|
|
240
|
-
for (const item of items) {
|
|
241
|
-
if (item.hash === undefined)
|
|
22
|
+
for (const key of applied.matchedKeys) {
|
|
23
|
+
if (workspaceOverrides?.has(key))
|
|
242
24
|
continue;
|
|
243
|
-
|
|
244
|
-
positions.add(item.entry);
|
|
245
|
-
positionsByHash.set(item.hash, positions);
|
|
25
|
+
globalMatchedRootKeys.add(key);
|
|
246
26
|
}
|
|
247
|
-
return
|
|
248
|
-
}
|
|
249
|
-
function formatItemId(hash, entry) {
|
|
250
|
-
return entry === undefined ? hash : `${hash}:${entry}`;
|
|
27
|
+
return applied;
|
|
251
28
|
}
|
|
252
|
-
function
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
const override = itemIdToOverride.get(formatItemId(item.hash, item.entry));
|
|
260
|
-
if (override === undefined) {
|
|
261
|
-
result.push(cloneItem(item));
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
if (override.audience === 'skip') {
|
|
265
|
-
continue;
|
|
29
|
+
export function composeOverrides(rootEntries, workspaceEntries) {
|
|
30
|
+
const composed = new Map(rootEntries);
|
|
31
|
+
if (workspaceEntries !== undefined) {
|
|
32
|
+
for (const [key, value] of workspaceEntries) {
|
|
33
|
+
composed.set(key, value);
|
|
266
34
|
}
|
|
267
|
-
result.push(applyOverrideToItem(item, override));
|
|
268
35
|
}
|
|
269
|
-
return
|
|
36
|
+
return composed;
|
|
270
37
|
}
|
|
271
|
-
function
|
|
272
|
-
const result = { ...
|
|
273
|
-
if (
|
|
274
|
-
result.
|
|
275
|
-
}
|
|
276
|
-
if (override.body !== undefined) {
|
|
277
|
-
result.body = override.body;
|
|
278
|
-
}
|
|
279
|
-
if (override.body !== undefined && item.entry === undefined) {
|
|
280
|
-
const migration = extractMigration(override.body);
|
|
281
|
-
if (migration === undefined) {
|
|
282
|
-
delete result.migration;
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
result.migration = migration;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (override.breaking !== undefined) {
|
|
289
|
-
result.breaking = override.breaking;
|
|
38
|
+
export function createOverrideContext(workspacePaths) {
|
|
39
|
+
const result = loadOverridesForScopes({ project: '.', workspaces: [...workspacePaths] });
|
|
40
|
+
if (result.errors.length > 0) {
|
|
41
|
+
throw new Error(`Failed to load changelog overrides:\n - ${result.errors.join('\n - ')}`);
|
|
290
42
|
}
|
|
291
|
-
return result;
|
|
292
|
-
}
|
|
293
|
-
function cloneItem(item) {
|
|
294
|
-
return { ...item };
|
|
295
|
-
}
|
|
296
|
-
function cloneEntry(entry) {
|
|
297
43
|
return {
|
|
298
|
-
|
|
299
|
-
|
|
44
|
+
project: result.project,
|
|
45
|
+
perWorkspace: result.perWorkspace,
|
|
46
|
+
overrideWarnings: [],
|
|
47
|
+
globalMatchedRootKeys: new Set(),
|
|
300
48
|
};
|
|
301
49
|
}
|
|
302
|
-
export function resolveOverridePath(scopeRoot) {
|
|
303
|
-
return path.posix.join(scopeRoot, OVERRIDES_FILENAME);
|
|
304
|
-
}
|
|
305
50
|
export function loadOverridesForScopes(scopes) {
|
|
306
51
|
const errors = [];
|
|
307
52
|
let project = new Map();
|
|
@@ -327,183 +72,6 @@ export function loadOverridesForScopes(scopes) {
|
|
|
327
72
|
}
|
|
328
73
|
return { project, perWorkspace, errors };
|
|
329
74
|
}
|
|
330
|
-
export function
|
|
331
|
-
|
|
332
|
-
if (workspaceEntries !== undefined) {
|
|
333
|
-
for (const [key, value] of workspaceEntries) {
|
|
334
|
-
composed.set(key, value);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return composed;
|
|
338
|
-
}
|
|
339
|
-
export function createOverrideContext(workspacePaths) {
|
|
340
|
-
const result = loadOverridesForScopes({ project: '.', workspaces: [...workspacePaths] });
|
|
341
|
-
if (result.errors.length > 0) {
|
|
342
|
-
throw new Error(`Failed to load changelog overrides:\n - ${result.errors.join('\n - ')}`);
|
|
343
|
-
}
|
|
344
|
-
return {
|
|
345
|
-
project: result.project,
|
|
346
|
-
perWorkspace: result.perWorkspace,
|
|
347
|
-
overrideWarnings: [],
|
|
348
|
-
globalMatchedRootKeys: new Set(),
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
export function validateAllChangelogOverrides(inputs) {
|
|
352
|
-
const errors = [];
|
|
353
|
-
const warnings = [];
|
|
354
|
-
const projectFilePath = inputs.project?.filePath;
|
|
355
|
-
const projectMap = loadScopeMap(projectFilePath, errors);
|
|
356
|
-
const workspaceMaps = (inputs.workspaces ?? []).map((scope) => ({
|
|
357
|
-
filePath: scope.filePath,
|
|
358
|
-
items: scope.items,
|
|
359
|
-
map: loadScopeMap(scope.filePath, errors),
|
|
360
|
-
}));
|
|
361
|
-
const globalMatchedRootKeys = new Set();
|
|
362
|
-
for (const workspace of workspaceMaps) {
|
|
363
|
-
processWorkspaceScope({
|
|
364
|
-
workspace,
|
|
365
|
-
projectFilePath,
|
|
366
|
-
projectMap,
|
|
367
|
-
errors,
|
|
368
|
-
warnings,
|
|
369
|
-
globalMatchedRootKeys,
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
const projectItems = inputs.project?.items;
|
|
373
|
-
if (projectFilePath !== undefined && projectItems !== undefined) {
|
|
374
|
-
processProjectScope({ projectFilePath, projectMap, projectItems, errors, globalMatchedRootKeys });
|
|
375
|
-
}
|
|
376
|
-
if (projectFilePath !== undefined) {
|
|
377
|
-
collectRootStaleWarnings(projectFilePath, projectMap, globalMatchedRootKeys, warnings);
|
|
378
|
-
}
|
|
379
|
-
return { errors, warnings };
|
|
380
|
-
}
|
|
381
|
-
function processWorkspaceScope(args) {
|
|
382
|
-
const { workspace, projectFilePath, projectMap, errors, warnings, globalMatchedRootKeys } = args;
|
|
383
|
-
const { filePath, items, map } = workspace;
|
|
384
|
-
const validationEntries = makeValidationEntries(items);
|
|
385
|
-
const positionsByHash = indexItemPositions(items);
|
|
386
|
-
const workspaceApplied = applyChangelogOverrides(validationEntries, map);
|
|
387
|
-
for (const message of workspaceApplied.errors) {
|
|
388
|
-
errors.push(prefixWithFilePath(filePath, message));
|
|
389
|
-
}
|
|
390
|
-
if (projectFilePath !== undefined && projectMap.size > 0) {
|
|
391
|
-
const projectMinusShadowed = filterShadowedKeys(projectMap, map);
|
|
392
|
-
const projectApplied = applyChangelogOverrides(validationEntries, projectMinusShadowed);
|
|
393
|
-
for (const message of projectApplied.errors) {
|
|
394
|
-
errors.push(prefixWithFilePath(projectFilePath, message));
|
|
395
|
-
}
|
|
396
|
-
const perFileErrors = new Set([...workspaceApplied.errors, ...projectApplied.errors]);
|
|
397
|
-
const composedApplied = applyChangelogOverrides(validationEntries, composeOverrides(projectMap, map));
|
|
398
|
-
for (const message of composedApplied.errors) {
|
|
399
|
-
if (!perFileErrors.has(message)) {
|
|
400
|
-
errors.push(prefixWithFilePath(filePath, message));
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
for (const key of map.keys()) {
|
|
405
|
-
if (!hasAnyMatch(key, positionsByHash)) {
|
|
406
|
-
warnings.push(formatWorkspaceStaleWarning(filePath, key));
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
for (const key of projectMap.keys()) {
|
|
410
|
-
if (map.has(key))
|
|
411
|
-
continue;
|
|
412
|
-
if (hasAnyMatch(key, positionsByHash)) {
|
|
413
|
-
globalMatchedRootKeys.add(key);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
function processProjectScope(args) {
|
|
418
|
-
const { projectFilePath, projectMap, projectItems, errors, globalMatchedRootKeys } = args;
|
|
419
|
-
const applied = applyChangelogOverrides(makeValidationEntries(projectItems), projectMap);
|
|
420
|
-
for (const message of applied.errors) {
|
|
421
|
-
errors.push(prefixWithFilePath(projectFilePath, message));
|
|
422
|
-
}
|
|
423
|
-
const positionsByHash = indexItemPositions(projectItems);
|
|
424
|
-
for (const key of projectMap.keys()) {
|
|
425
|
-
if (hasAnyMatch(key, positionsByHash)) {
|
|
426
|
-
globalMatchedRootKeys.add(key);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
function collectRootStaleWarnings(projectFilePath, projectMap, globalMatchedRootKeys, warnings) {
|
|
431
|
-
for (const key of projectMap.keys()) {
|
|
432
|
-
if (!globalMatchedRootKeys.has(key)) {
|
|
433
|
-
warnings.push(formatRootStaleWarning(projectFilePath, key));
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
function hasAnyMatch(key, positionsByHash) {
|
|
438
|
-
return resolveOverrideKey(key, positionsByHash).kind !== 'none';
|
|
439
|
-
}
|
|
440
|
-
function filterShadowedKeys(projectMap, workspaceMap) {
|
|
441
|
-
const result = new Map();
|
|
442
|
-
for (const [key, value] of projectMap) {
|
|
443
|
-
if (workspaceMap.has(key))
|
|
444
|
-
continue;
|
|
445
|
-
result.set(key, value);
|
|
446
|
-
}
|
|
447
|
-
return result;
|
|
448
|
-
}
|
|
449
|
-
function loadScopeMap(filePath, errors) {
|
|
450
|
-
if (filePath === undefined) {
|
|
451
|
-
return new Map();
|
|
452
|
-
}
|
|
453
|
-
const result = loadChangelogOverrides(filePath);
|
|
454
|
-
if ('errors' in result) {
|
|
455
|
-
for (const message of result.errors) {
|
|
456
|
-
errors.push(prefixWithFilePath(filePath, message));
|
|
457
|
-
}
|
|
458
|
-
return new Map();
|
|
459
|
-
}
|
|
460
|
-
return result.overrides;
|
|
461
|
-
}
|
|
462
|
-
function makeValidationEntries(items) {
|
|
463
|
-
return [
|
|
464
|
-
{
|
|
465
|
-
version: '0.0.0',
|
|
466
|
-
date: '0000-00-00',
|
|
467
|
-
sections: [
|
|
468
|
-
{
|
|
469
|
-
title: 'Validation',
|
|
470
|
-
audience: 'all',
|
|
471
|
-
items: items.map((item) => ({ ...item, description: '' })),
|
|
472
|
-
},
|
|
473
|
-
],
|
|
474
|
-
},
|
|
475
|
-
];
|
|
476
|
-
}
|
|
477
|
-
function prefixWithFilePath(filePath, message) {
|
|
478
|
-
return `${filePath}: ${message}`;
|
|
479
|
-
}
|
|
480
|
-
function formatWorkspaceStaleWarning(filePath, key) {
|
|
481
|
-
return `${filePath}: Override key '${key}' did not match any item in this workspace's history (likely a stale reference)`;
|
|
482
|
-
}
|
|
483
|
-
function formatRootStaleWarning(filePath, key) {
|
|
484
|
-
return `${filePath}: Override key '${key}' did not match any item in any scope (likely a stale reference)`;
|
|
485
|
-
}
|
|
486
|
-
export function applyWorkspaceOverrides(newEntries, workspacePath, overrideContext) {
|
|
487
|
-
const { project, perWorkspace, overrideWarnings, globalMatchedRootKeys } = overrideContext;
|
|
488
|
-
const workspaceOverrides = workspacePath === undefined ? undefined : perWorkspace.get(workspacePath);
|
|
489
|
-
const composed = composeOverrides(project, workspaceOverrides);
|
|
490
|
-
const applied = applyChangelogOverrides(newEntries, composed);
|
|
491
|
-
if (applied.errors.length > 0) {
|
|
492
|
-
throw new Error(`Changelog override application failed:\n - ${applied.errors.join('\n - ')}`);
|
|
493
|
-
}
|
|
494
|
-
overrideWarnings.push(...applied.warnings);
|
|
495
|
-
const matchedSet = new Set(applied.matchedKeys);
|
|
496
|
-
if (workspaceOverrides !== undefined) {
|
|
497
|
-
for (const key of workspaceOverrides.keys()) {
|
|
498
|
-
if (!matchedSet.has(key)) {
|
|
499
|
-
overrideWarnings.push(formatStaleOverrideKeyWarning(key));
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
for (const key of applied.matchedKeys) {
|
|
504
|
-
if (workspaceOverrides?.has(key))
|
|
505
|
-
continue;
|
|
506
|
-
globalMatchedRootKeys.add(key);
|
|
507
|
-
}
|
|
508
|
-
return applied;
|
|
75
|
+
export function resolveOverridePath(scopeRoot) {
|
|
76
|
+
return path.posix.join(scopeRoot, OVERRIDES_FILENAME);
|
|
509
77
|
}
|