@williamthorsen/release-kit 5.2.1 → 5.3.1
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/CHANGELOG.md +76 -26
- package/README.md +223 -31
- package/cliff.toml.template +4 -39
- package/dist/esm/.cache +1 -1
- package/dist/esm/bin/release-kit.js +59 -0
- package/dist/esm/buildChangelogEntries.js +6 -0
- package/dist/esm/buildEmptyReleaseEntry.d.ts +2 -0
- package/dist/esm/buildEmptyReleaseEntry.js +16 -0
- package/dist/esm/changelogJsonFile.d.ts +2 -0
- package/dist/esm/changelogJsonFile.js +11 -1
- package/dist/esm/changelogJsonUtils.d.ts +2 -1
- package/dist/esm/changelogJsonUtils.js +9 -0
- package/dist/esm/changelogOverrides.d.ts +53 -0
- package/dist/esm/changelogOverrides.js +424 -0
- package/dist/esm/checkWorkTypesDrift.js +3 -2
- package/dist/esm/defaults.js +1 -1
- package/dist/esm/generateChangelogs.d.ts +2 -3
- package/dist/esm/generateChangelogs.js +4 -34
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.js +8 -0
- package/dist/esm/loadConfig.d.ts +1 -1
- package/dist/esm/releasePrepare.js +68 -11
- package/dist/esm/releasePrepareMono.js +103 -59
- package/dist/esm/releasePrepareProject.d.ts +4 -1
- package/dist/esm/releasePrepareProject.js +74 -18
- package/dist/esm/renderChangelogMarkdown.d.ts +12 -0
- package/dist/esm/renderChangelogMarkdown.js +32 -0
- package/dist/esm/renderReleaseNotes.js +6 -1
- package/dist/esm/resolveReleaseNotesConfig.js +3 -1
- package/dist/esm/runGitCliff.d.ts +1 -0
- package/dist/esm/runGitCliff.js +7 -0
- package/dist/esm/syncWorkTypes.js +3 -2
- package/dist/esm/types.d.ts +98 -31
- package/dist/esm/types.js +60 -0
- package/dist/esm/validateConfig.js +84 -345
- package/dist/esm/validateOverridesCommand.d.ts +14 -0
- package/dist/esm/validateOverridesCommand.js +136 -0
- package/dist/esm/work-types.json +23 -17
- package/dist/esm/work-types.schema.json +28 -1
- package/dist/esm/workTypesData.d.ts +8 -0
- package/dist/esm/workTypesData.js +20 -17
- package/dist/esm/workTypesUtils.d.ts +1 -0
- package/dist/esm/workTypesUtils.js +8 -0
- package/package.json +5 -4
- package/dist/esm/writeSyntheticChangelog.d.ts +0 -9
- package/dist/esm/writeSyntheticChangelog.js +0 -27
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { isRecord } from "./typeGuards.js";
|
|
4
|
+
const OVERRIDES_FILENAME = ".meta/changelog-overrides.json";
|
|
5
|
+
const VALID_AUDIENCE_VALUES = /* @__PURE__ */ new Set(["all", "dev", "skip"]);
|
|
6
|
+
const V1_SUPPORTED_AUDIENCE_VALUES = /* @__PURE__ */ new Set(["skip"]);
|
|
7
|
+
const KNOWN_OVERRIDE_FIELDS = /* @__PURE__ */ new Set(["audience", "description", "body", "breaking"]);
|
|
8
|
+
function loadChangelogOverrides(path2) {
|
|
9
|
+
if (!existsSync(path2)) {
|
|
10
|
+
return { overrides: /* @__PURE__ */ new Map() };
|
|
11
|
+
}
|
|
12
|
+
let content;
|
|
13
|
+
try {
|
|
14
|
+
content = readFileSync(path2, "utf8");
|
|
15
|
+
} catch (error) {
|
|
16
|
+
return {
|
|
17
|
+
errors: [`Failed to read override file ${path2}: ${error instanceof Error ? error.message : String(error)}`]
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
let parsed;
|
|
21
|
+
try {
|
|
22
|
+
parsed = JSON.parse(content);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
return {
|
|
25
|
+
errors: [`Failed to parse override file ${path2}: ${error instanceof Error ? error.message : String(error)}`]
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const result = validateChangelogOverrides(parsed);
|
|
29
|
+
if (result.errors.length > 0) {
|
|
30
|
+
return { errors: result.errors };
|
|
31
|
+
}
|
|
32
|
+
return { overrides: result.overrides };
|
|
33
|
+
}
|
|
34
|
+
function validateChangelogOverrides(raw) {
|
|
35
|
+
const overrides = /* @__PURE__ */ new Map();
|
|
36
|
+
const errors = [];
|
|
37
|
+
if (!isRecord(raw)) {
|
|
38
|
+
errors.push("Override file: top-level value must be an object keyed by commit hash");
|
|
39
|
+
return { overrides, errors };
|
|
40
|
+
}
|
|
41
|
+
for (const [key, rawEntry] of Object.entries(raw)) {
|
|
42
|
+
if (key === "") {
|
|
43
|
+
errors.push("Override file: empty-string key is not a valid commit hash");
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
const validated = validateSingleOverride(key, rawEntry, errors);
|
|
47
|
+
if (validated !== void 0) {
|
|
48
|
+
overrides.set(key, validated);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { overrides, errors };
|
|
52
|
+
}
|
|
53
|
+
function validateSingleOverride(key, rawEntry, errors) {
|
|
54
|
+
if (!isRecord(rawEntry)) {
|
|
55
|
+
errors.push(`overrides['${key}']: must be an object`);
|
|
56
|
+
return void 0;
|
|
57
|
+
}
|
|
58
|
+
let entryValid = true;
|
|
59
|
+
for (const fieldName of Object.keys(rawEntry)) {
|
|
60
|
+
if (!KNOWN_OVERRIDE_FIELDS.has(fieldName)) {
|
|
61
|
+
errors.push(`overrides['${key}']: unknown field '${fieldName}'`);
|
|
62
|
+
entryValid = false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const result = {};
|
|
66
|
+
if (rawEntry.audience !== void 0) {
|
|
67
|
+
const audienceResult = validateAudience(key, rawEntry.audience, errors);
|
|
68
|
+
if (audienceResult === void 0) {
|
|
69
|
+
entryValid = false;
|
|
70
|
+
} else {
|
|
71
|
+
result.audience = audienceResult;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (rawEntry.description !== void 0) {
|
|
75
|
+
if (typeof rawEntry.description !== "string") {
|
|
76
|
+
errors.push(`overrides['${key}']: 'description' must be a string`);
|
|
77
|
+
entryValid = false;
|
|
78
|
+
} else {
|
|
79
|
+
result.description = rawEntry.description;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (rawEntry.body !== void 0) {
|
|
83
|
+
if (typeof rawEntry.body !== "string") {
|
|
84
|
+
errors.push(`overrides['${key}']: 'body' must be a string`);
|
|
85
|
+
entryValid = false;
|
|
86
|
+
} else {
|
|
87
|
+
result.body = rawEntry.body;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (rawEntry.breaking !== void 0) {
|
|
91
|
+
if (typeof rawEntry.breaking !== "boolean") {
|
|
92
|
+
errors.push(`overrides['${key}']: 'breaking' must be a boolean`);
|
|
93
|
+
entryValid = false;
|
|
94
|
+
} else {
|
|
95
|
+
result.breaking = rawEntry.breaking;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (Object.keys(result).length === 0 && entryValid) {
|
|
99
|
+
errors.push(`overrides['${key}']: at least one override field must be set`);
|
|
100
|
+
return void 0;
|
|
101
|
+
}
|
|
102
|
+
if (!entryValid) {
|
|
103
|
+
return void 0;
|
|
104
|
+
}
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
function validateAudience(key, value, errors) {
|
|
108
|
+
if (typeof value !== "string" || !VALID_AUDIENCE_VALUES.has(value)) {
|
|
109
|
+
errors.push(`overrides['${key}']: 'audience' must be one of 'all' | 'dev' | 'skip'`);
|
|
110
|
+
return void 0;
|
|
111
|
+
}
|
|
112
|
+
if (!V1_SUPPORTED_AUDIENCE_VALUES.has(value)) {
|
|
113
|
+
errors.push(`overrides['${key}']: audience '${value}' is not yet supported; only 'skip' is currently accepted`);
|
|
114
|
+
return void 0;
|
|
115
|
+
}
|
|
116
|
+
return "skip";
|
|
117
|
+
}
|
|
118
|
+
function formatStaleOverrideKeyWarning(key) {
|
|
119
|
+
return `Override key '${key}' did not match any commit hash in the changelog (likely a stale reference)`;
|
|
120
|
+
}
|
|
121
|
+
function applyChangelogOverrides(entries, overrides) {
|
|
122
|
+
const warnings = [];
|
|
123
|
+
const errors = [];
|
|
124
|
+
const matchedKeys = [];
|
|
125
|
+
if (overrides.size === 0) {
|
|
126
|
+
return { entries: entries.map(cloneEntry), warnings, errors, matchedKeys };
|
|
127
|
+
}
|
|
128
|
+
const allHashes = [];
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
for (const section of entry.sections) {
|
|
131
|
+
for (const item of section.items) {
|
|
132
|
+
if (item.hash !== void 0) {
|
|
133
|
+
allHashes.push(item.hash);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const keyToMatchedHashes = /* @__PURE__ */ new Map();
|
|
139
|
+
for (const overrideKey of overrides.keys()) {
|
|
140
|
+
const matches = allHashes.filter((hash) => hash.startsWith(overrideKey));
|
|
141
|
+
if (matches.length === 0) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (matches.length > 1) {
|
|
145
|
+
errors.push(
|
|
146
|
+
`Override key '${overrideKey}' is ambiguous: matches multiple commits (${matches.join(", ")}). Use a longer prefix or the full commit hash.`
|
|
147
|
+
);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
keyToMatchedHashes.set(overrideKey, matches);
|
|
151
|
+
matchedKeys.push(overrideKey);
|
|
152
|
+
}
|
|
153
|
+
const hashToOverride = /* @__PURE__ */ new Map();
|
|
154
|
+
for (const [overrideKey, matchedHashes] of keyToMatchedHashes) {
|
|
155
|
+
const override = overrides.get(overrideKey);
|
|
156
|
+
if (override === void 0) continue;
|
|
157
|
+
for (const hash of matchedHashes) {
|
|
158
|
+
hashToOverride.set(hash, override);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const transformedEntries = [];
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
const transformedSections = [];
|
|
164
|
+
for (const section of entry.sections) {
|
|
165
|
+
const transformedItems = applyOverridesToItems(section.items, hashToOverride);
|
|
166
|
+
if (transformedItems.length === 0) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
transformedSections.push({ ...section, items: transformedItems });
|
|
170
|
+
}
|
|
171
|
+
transformedEntries.push({ ...entry, sections: transformedSections });
|
|
172
|
+
}
|
|
173
|
+
return { entries: transformedEntries, warnings, errors, matchedKeys };
|
|
174
|
+
}
|
|
175
|
+
function applyOverridesToItems(items, hashToOverride) {
|
|
176
|
+
const result = [];
|
|
177
|
+
for (const item of items) {
|
|
178
|
+
if (item.hash === void 0) {
|
|
179
|
+
result.push(cloneItem(item));
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const override = hashToOverride.get(item.hash);
|
|
183
|
+
if (override === void 0) {
|
|
184
|
+
result.push(cloneItem(item));
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (override.audience === "skip") {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
result.push(applyOverrideToItem(item, override));
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
function applyOverrideToItem(item, override) {
|
|
195
|
+
const result = { ...item };
|
|
196
|
+
if (override.description !== void 0) {
|
|
197
|
+
result.description = override.description;
|
|
198
|
+
}
|
|
199
|
+
if (override.body !== void 0) {
|
|
200
|
+
result.body = override.body;
|
|
201
|
+
}
|
|
202
|
+
if (override.breaking !== void 0) {
|
|
203
|
+
result.breaking = override.breaking;
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
function cloneItem(item) {
|
|
208
|
+
return { ...item };
|
|
209
|
+
}
|
|
210
|
+
function cloneEntry(entry) {
|
|
211
|
+
return {
|
|
212
|
+
...entry,
|
|
213
|
+
sections: entry.sections.map((section) => ({ ...section, items: section.items.map(cloneItem) }))
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function resolveOverridePath(scopeRoot) {
|
|
217
|
+
return path.posix.join(scopeRoot, OVERRIDES_FILENAME);
|
|
218
|
+
}
|
|
219
|
+
function loadOverridesForScopes(scopes) {
|
|
220
|
+
const errors = [];
|
|
221
|
+
let project = /* @__PURE__ */ new Map();
|
|
222
|
+
const perWorkspace = /* @__PURE__ */ new Map();
|
|
223
|
+
if (scopes.project !== void 0) {
|
|
224
|
+
const result = loadChangelogOverrides(resolveOverridePath(scopes.project));
|
|
225
|
+
if ("errors" in result) {
|
|
226
|
+
errors.push(...result.errors);
|
|
227
|
+
} else {
|
|
228
|
+
project = result.overrides;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
for (const workspacePath of scopes.workspaces ?? []) {
|
|
232
|
+
const result = loadChangelogOverrides(resolveOverridePath(workspacePath));
|
|
233
|
+
if ("errors" in result) {
|
|
234
|
+
errors.push(...result.errors);
|
|
235
|
+
} else if (result.overrides.size > 0) {
|
|
236
|
+
perWorkspace.set(workspacePath, result.overrides);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return { project, perWorkspace, errors };
|
|
240
|
+
}
|
|
241
|
+
function composeOverrides(rootEntries, workspaceEntries) {
|
|
242
|
+
const composed = new Map(rootEntries);
|
|
243
|
+
if (workspaceEntries !== void 0) {
|
|
244
|
+
for (const [key, value] of workspaceEntries) {
|
|
245
|
+
composed.set(key, value);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return composed;
|
|
249
|
+
}
|
|
250
|
+
function createOverrideContext(workspaces) {
|
|
251
|
+
const result = loadOverridesForScopes({
|
|
252
|
+
project: ".",
|
|
253
|
+
workspaces: workspaces.map((workspace) => workspace.workspacePath)
|
|
254
|
+
});
|
|
255
|
+
if (result.errors.length > 0) {
|
|
256
|
+
throw new Error(`Failed to load changelog overrides:
|
|
257
|
+
- ${result.errors.join("\n - ")}`);
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
project: result.project,
|
|
261
|
+
perWorkspace: result.perWorkspace,
|
|
262
|
+
overrideWarnings: [],
|
|
263
|
+
globalMatchedRootKeys: /* @__PURE__ */ new Set()
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
function validateAllChangelogOverrides(inputs) {
|
|
267
|
+
const errors = [];
|
|
268
|
+
const warnings = [];
|
|
269
|
+
const projectFilePath = inputs.project?.filePath;
|
|
270
|
+
const projectMap = loadScopeMap(projectFilePath, errors);
|
|
271
|
+
const workspaceMaps = (inputs.workspaces ?? []).map((scope) => ({
|
|
272
|
+
filePath: scope.filePath,
|
|
273
|
+
hashes: scope.hashes,
|
|
274
|
+
map: loadScopeMap(scope.filePath, errors)
|
|
275
|
+
}));
|
|
276
|
+
const globalMatchedRootKeys = /* @__PURE__ */ new Set();
|
|
277
|
+
for (const workspace of workspaceMaps) {
|
|
278
|
+
processWorkspaceScope({
|
|
279
|
+
workspace,
|
|
280
|
+
projectFilePath,
|
|
281
|
+
projectMap,
|
|
282
|
+
errors,
|
|
283
|
+
warnings,
|
|
284
|
+
globalMatchedRootKeys
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
const projectHashes = inputs.project?.hashes;
|
|
288
|
+
if (projectFilePath !== void 0 && projectHashes !== void 0) {
|
|
289
|
+
processProjectScope({ projectFilePath, projectMap, projectHashes, errors, globalMatchedRootKeys });
|
|
290
|
+
}
|
|
291
|
+
if (projectFilePath !== void 0) {
|
|
292
|
+
collectRootStaleWarnings(projectFilePath, projectMap, globalMatchedRootKeys, warnings);
|
|
293
|
+
}
|
|
294
|
+
return { errors, warnings };
|
|
295
|
+
}
|
|
296
|
+
function processWorkspaceScope(args) {
|
|
297
|
+
const { workspace, projectFilePath, projectMap, errors, warnings, globalMatchedRootKeys } = args;
|
|
298
|
+
const { filePath, hashes, map } = workspace;
|
|
299
|
+
const workspaceApplied = applyChangelogOverrides(makeValidationEntries(hashes), map);
|
|
300
|
+
for (const message of workspaceApplied.errors) {
|
|
301
|
+
errors.push(prefixWithFilePath(filePath, message));
|
|
302
|
+
}
|
|
303
|
+
if (projectFilePath !== void 0 && projectMap.size > 0) {
|
|
304
|
+
const projectMinusShadowed = filterShadowedKeys(projectMap, map);
|
|
305
|
+
const projectApplied = applyChangelogOverrides(makeValidationEntries(hashes), projectMinusShadowed);
|
|
306
|
+
for (const message of projectApplied.errors) {
|
|
307
|
+
errors.push(prefixWithFilePath(projectFilePath, message));
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
for (const key of map.keys()) {
|
|
311
|
+
if (!hasAnyMatch(key, hashes)) {
|
|
312
|
+
warnings.push(formatWorkspaceStaleWarning(filePath, key));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
for (const key of projectMap.keys()) {
|
|
316
|
+
if (map.has(key)) continue;
|
|
317
|
+
if (hasAnyMatch(key, hashes)) {
|
|
318
|
+
globalMatchedRootKeys.add(key);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function processProjectScope(args) {
|
|
323
|
+
const { projectFilePath, projectMap, projectHashes, errors, globalMatchedRootKeys } = args;
|
|
324
|
+
const applied = applyChangelogOverrides(makeValidationEntries(projectHashes), projectMap);
|
|
325
|
+
for (const message of applied.errors) {
|
|
326
|
+
errors.push(prefixWithFilePath(projectFilePath, message));
|
|
327
|
+
}
|
|
328
|
+
for (const key of projectMap.keys()) {
|
|
329
|
+
if (hasAnyMatch(key, projectHashes)) {
|
|
330
|
+
globalMatchedRootKeys.add(key);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function collectRootStaleWarnings(projectFilePath, projectMap, globalMatchedRootKeys, warnings) {
|
|
335
|
+
for (const key of projectMap.keys()) {
|
|
336
|
+
if (!globalMatchedRootKeys.has(key)) {
|
|
337
|
+
warnings.push(formatRootStaleWarning(projectFilePath, key));
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function hasAnyMatch(key, hashes) {
|
|
342
|
+
return hashes.some((hash) => hash.startsWith(key));
|
|
343
|
+
}
|
|
344
|
+
function filterShadowedKeys(projectMap, workspaceMap) {
|
|
345
|
+
const result = /* @__PURE__ */ new Map();
|
|
346
|
+
for (const [key, value] of projectMap) {
|
|
347
|
+
if (workspaceMap.has(key)) continue;
|
|
348
|
+
result.set(key, value);
|
|
349
|
+
}
|
|
350
|
+
return result;
|
|
351
|
+
}
|
|
352
|
+
function loadScopeMap(filePath, errors) {
|
|
353
|
+
if (filePath === void 0) {
|
|
354
|
+
return /* @__PURE__ */ new Map();
|
|
355
|
+
}
|
|
356
|
+
const result = loadChangelogOverrides(filePath);
|
|
357
|
+
if ("errors" in result) {
|
|
358
|
+
for (const message of result.errors) {
|
|
359
|
+
errors.push(prefixWithFilePath(filePath, message));
|
|
360
|
+
}
|
|
361
|
+
return /* @__PURE__ */ new Map();
|
|
362
|
+
}
|
|
363
|
+
return result.overrides;
|
|
364
|
+
}
|
|
365
|
+
function makeValidationEntries(hashes) {
|
|
366
|
+
return [
|
|
367
|
+
{
|
|
368
|
+
version: "0.0.0",
|
|
369
|
+
date: "0000-00-00",
|
|
370
|
+
sections: [
|
|
371
|
+
{
|
|
372
|
+
title: "Validation",
|
|
373
|
+
audience: "all",
|
|
374
|
+
items: hashes.map((hash) => ({ description: "", hash }))
|
|
375
|
+
}
|
|
376
|
+
]
|
|
377
|
+
}
|
|
378
|
+
];
|
|
379
|
+
}
|
|
380
|
+
function prefixWithFilePath(filePath, message) {
|
|
381
|
+
return `${filePath}: ${message}`;
|
|
382
|
+
}
|
|
383
|
+
function formatWorkspaceStaleWarning(filePath, key) {
|
|
384
|
+
return `${filePath}: Override key '${key}' did not match any commit in this workspace's history (likely a stale reference)`;
|
|
385
|
+
}
|
|
386
|
+
function formatRootStaleWarning(filePath, key) {
|
|
387
|
+
return `${filePath}: Override key '${key}' did not match any commit in any scope (likely a stale reference)`;
|
|
388
|
+
}
|
|
389
|
+
function applyWorkspaceOverrides(newEntries, workspacePath, overrideContext) {
|
|
390
|
+
const { project, perWorkspace, overrideWarnings, globalMatchedRootKeys } = overrideContext;
|
|
391
|
+
const workspaceOverrides = perWorkspace.get(workspacePath);
|
|
392
|
+
const composed = composeOverrides(project, workspaceOverrides);
|
|
393
|
+
const applied = applyChangelogOverrides(newEntries, composed);
|
|
394
|
+
if (applied.errors.length > 0) {
|
|
395
|
+
throw new Error(`Changelog override application failed:
|
|
396
|
+
- ${applied.errors.join("\n - ")}`);
|
|
397
|
+
}
|
|
398
|
+
overrideWarnings.push(...applied.warnings);
|
|
399
|
+
const matchedSet = new Set(applied.matchedKeys);
|
|
400
|
+
if (workspaceOverrides !== void 0) {
|
|
401
|
+
for (const key of workspaceOverrides.keys()) {
|
|
402
|
+
if (!matchedSet.has(key)) {
|
|
403
|
+
overrideWarnings.push(formatStaleOverrideKeyWarning(key));
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
for (const key of applied.matchedKeys) {
|
|
408
|
+
if (workspaceOverrides?.has(key)) continue;
|
|
409
|
+
globalMatchedRootKeys.add(key);
|
|
410
|
+
}
|
|
411
|
+
return applied;
|
|
412
|
+
}
|
|
413
|
+
export {
|
|
414
|
+
applyChangelogOverrides,
|
|
415
|
+
applyWorkspaceOverrides,
|
|
416
|
+
composeOverrides,
|
|
417
|
+
createOverrideContext,
|
|
418
|
+
formatStaleOverrideKeyWarning,
|
|
419
|
+
loadChangelogOverrides,
|
|
420
|
+
loadOverridesForScopes,
|
|
421
|
+
resolveOverridePath,
|
|
422
|
+
validateAllChangelogOverrides,
|
|
423
|
+
validateChangelogOverrides
|
|
424
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { errorMessage, hasExpectedTopLevelShape } from "./workTypesUtils.js";
|
|
4
|
+
import { buildFetchInit, errorMessage, hasExpectedTopLevelShape } from "./workTypesUtils.js";
|
|
5
5
|
const UPSTREAM_WORK_TYPES_URL = "https://raw.githubusercontent.com/williamthorsen/codeassembly/main/packages/agents/content/skills/_data/work-types.json";
|
|
6
6
|
function resolveDefaultLocalPath() {
|
|
7
7
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -21,9 +21,10 @@ async function checkWorkTypesDrift(dependencies = {}) {
|
|
|
21
21
|
message: `Local work-types.json is not valid JSON: ${errorMessage(error)}`
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
+
const fetchInit = buildFetchInit();
|
|
24
25
|
let response;
|
|
25
26
|
try {
|
|
26
|
-
response = await fetcher(url);
|
|
27
|
+
response = fetchInit === void 0 ? await fetcher(url) : await fetcher(url, fetchInit);
|
|
27
28
|
} catch (error) {
|
|
28
29
|
return {
|
|
29
30
|
exitCode: 2,
|
package/dist/esm/defaults.js
CHANGED
|
@@ -3,7 +3,7 @@ import { WORK_TYPES_DATA as WORK_TYPES_DATA2 } from "./workTypesData.js";
|
|
|
3
3
|
function composeHeader(entry) {
|
|
4
4
|
return `${entry.emoji} ${entry.label}`;
|
|
5
5
|
}
|
|
6
|
-
const DEV_ONLY_TIERS = /* @__PURE__ */ new Set(["
|
|
6
|
+
const DEV_ONLY_TIERS = /* @__PURE__ */ new Set(["internal", "process"]);
|
|
7
7
|
function deriveDefaultWorkTypes() {
|
|
8
8
|
const result = {};
|
|
9
9
|
for (const entry of WORK_TYPES_DATA.types) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { WorkspaceConfig } from './types.ts';
|
|
2
2
|
export declare function buildTagPattern(tagPrefixes: readonly string[]): string;
|
|
3
|
+
export declare function getAllTagPrefixes(workspace: WorkspaceConfig): string[];
|
|
3
4
|
export interface GenerateChangelogOptions {
|
|
4
5
|
tagPattern?: string;
|
|
5
6
|
includePaths?: string[];
|
|
6
7
|
}
|
|
7
|
-
export declare function generateChangelog(config: Pick<ReleaseConfig, 'cliffConfigPath'>, changelogPath: string, tag: string, dryRun: boolean, options?: GenerateChangelogOptions): string[];
|
|
8
|
-
export declare function generateChangelogs(config: ReleaseConfig, tag: string, dryRun: boolean): string[];
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { resolveCliffConfigPath } from "./resolveCliffConfigPath.js";
|
|
2
|
-
import { runGitCliff } from "./runGitCliff.js";
|
|
3
1
|
function buildTagPattern(tagPrefixes) {
|
|
4
2
|
if (tagPrefixes.length === 0) {
|
|
5
3
|
throw new Error("buildTagPattern: tagPrefixes must contain at least one entry");
|
|
@@ -11,41 +9,13 @@ function buildTagPattern(tagPrefixes) {
|
|
|
11
9
|
const escaped = tagPrefixes.map(escapeRegex);
|
|
12
10
|
return `(${escaped.join("|")})[0-9].*`;
|
|
13
11
|
}
|
|
12
|
+
function getAllTagPrefixes(workspace) {
|
|
13
|
+
return [workspace.tagPrefix, ...workspace.legacyIdentities?.map((identity) => identity.tagPrefix) ?? []];
|
|
14
|
+
}
|
|
14
15
|
function escapeRegex(value) {
|
|
15
16
|
return value.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
16
17
|
}
|
|
17
|
-
function generateChangelog(config, changelogPath, tag, dryRun, options) {
|
|
18
|
-
const outputFile = `${changelogPath}/CHANGELOG.md`;
|
|
19
|
-
if (dryRun) {
|
|
20
|
-
return [outputFile];
|
|
21
|
-
}
|
|
22
|
-
const resolvedConfigPath = resolveCliffConfigPath(config.cliffConfigPath, import.meta.url);
|
|
23
|
-
const cliffArgs = ["--output", outputFile, "--tag", tag];
|
|
24
|
-
if (options?.tagPattern !== void 0) {
|
|
25
|
-
cliffArgs.push("--tag-pattern", options.tagPattern);
|
|
26
|
-
}
|
|
27
|
-
for (const includePath of options?.includePaths ?? []) {
|
|
28
|
-
cliffArgs.push("--include-path", includePath);
|
|
29
|
-
}
|
|
30
|
-
try {
|
|
31
|
-
runGitCliff(resolvedConfigPath, cliffArgs, "inherit");
|
|
32
|
-
} catch (error) {
|
|
33
|
-
throw new Error(
|
|
34
|
-
`Failed to generate changelog for ${outputFile}: ${error instanceof Error ? error.message : String(error)}`
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
return [outputFile];
|
|
38
|
-
}
|
|
39
|
-
function generateChangelogs(config, tag, dryRun) {
|
|
40
|
-
const tagPattern = buildTagPattern([config.tagPrefix]);
|
|
41
|
-
const results = [];
|
|
42
|
-
for (const changelogPath of config.changelogPaths) {
|
|
43
|
-
results.push(...generateChangelog(config, changelogPath, tag, dryRun, { tagPattern }));
|
|
44
|
-
}
|
|
45
|
-
return results;
|
|
46
|
-
}
|
|
47
18
|
export {
|
|
48
19
|
buildTagPattern,
|
|
49
|
-
|
|
50
|
-
generateChangelogs
|
|
20
|
+
getAllTagPrefixes
|
|
51
21
|
};
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
export { type ChangelogOverrideScope, validateAllChangelogOverrides, type ValidateAllChangelogOverridesInputs, type ValidateAllChangelogOverridesResult, } from './changelogOverrides.ts';
|
|
1
2
|
export type { SyncLabelsConfig } from './sync-labels/types.ts';
|
|
2
3
|
export type { ReleaseKitConfig } from './types.ts';
|
|
4
|
+
export { formatValidateOverridesResult, type ValidateOverridesCommandResult } from './validateOverridesCommand.ts';
|
package/dist/esm/index.js
CHANGED
package/dist/esm/loadConfig.d.ts
CHANGED
|
@@ -12,4 +12,4 @@ export interface RootPackageInfo {
|
|
|
12
12
|
}
|
|
13
13
|
export declare function mergeMonorepoConfig(discoveredPaths: string[], userConfig: ReleaseKitConfig | undefined, rootPackage?: RootPackageInfo): MonorepoReleaseConfig;
|
|
14
14
|
export declare function mergeSinglePackageConfig(userConfig: ReleaseKitConfig | undefined): ReleaseConfig;
|
|
15
|
-
export declare function resolveWorkTypes(userWorkTypes?:
|
|
15
|
+
export declare function resolveWorkTypes(userWorkTypes?: ReleaseKitConfig['workTypes']): Record<string, WorkTypeConfig>;
|
|
@@ -1,23 +1,40 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import { buildChangelogEntries } from "./buildChangelogEntries.js";
|
|
3
|
+
import { buildEmptyReleaseEntry } from "./buildEmptyReleaseEntry.js";
|
|
3
4
|
import { bumpAllVersions, setAllVersions } from "./bumpAllVersions.js";
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
mergeChangelogEntriesWithDisk,
|
|
7
|
+
resolveChangelogJsonPath,
|
|
8
|
+
upsertChangelogJsonAndReturn
|
|
9
|
+
} from "./changelogJsonFile.js";
|
|
10
|
+
import {
|
|
11
|
+
applyChangelogOverrides,
|
|
12
|
+
formatStaleOverrideKeyWarning,
|
|
13
|
+
loadOverridesForScopes
|
|
14
|
+
} from "./changelogOverrides.js";
|
|
5
15
|
import { createPolicyViolationCollector } from "./collectPolicyViolations.js";
|
|
6
16
|
import { isForwardVersion } from "./compareVersions.js";
|
|
7
17
|
import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
8
18
|
import { determineBumpFromCommits } from "./determineBumpFromCommits.js";
|
|
9
|
-
import { generateChangelogs } from "./generateChangelogs.js";
|
|
10
19
|
import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
|
|
11
20
|
import { hasPrettierConfig } from "./hasPrettierConfig.js";
|
|
12
21
|
import { resolveWorkTypes } from "./loadConfig.js";
|
|
13
22
|
import { readCurrentVersion } from "./readCurrentVersion.js";
|
|
23
|
+
import { writeChangelogMarkdown } from "./renderChangelogMarkdown.js";
|
|
14
24
|
import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
|
|
25
|
+
import { refreshGitCliffCache } from "./runGitCliff.js";
|
|
15
26
|
import { writeReleaseNotesPreviews } from "./writeReleaseNotesPreviews.js";
|
|
16
27
|
function releasePrepare(config, options) {
|
|
17
28
|
const { dryRun, bumpOverride, setVersion, withReleaseNotes } = options;
|
|
18
29
|
const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
|
|
19
30
|
const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
|
|
20
31
|
const breakingPolicies = config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES;
|
|
32
|
+
const overridesResult = loadOverridesForScopes({ project: "." });
|
|
33
|
+
if (overridesResult.errors.length > 0) {
|
|
34
|
+
throw new Error(`Failed to load changelog overrides:
|
|
35
|
+
- ${overridesResult.errors.join("\n - ")}`);
|
|
36
|
+
}
|
|
37
|
+
const overrides = overridesResult.project;
|
|
21
38
|
const { tag, commits } = getCommitsSinceTarget([config.tagPrefix]);
|
|
22
39
|
let releaseType;
|
|
23
40
|
let parsedCommitCount;
|
|
@@ -36,6 +53,7 @@ function releasePrepare(config, options) {
|
|
|
36
53
|
if (!isForwardVersion(currentVersion, setVersion)) {
|
|
37
54
|
throw new Error(`--set-version ${setVersion} is not greater than current version ${currentVersion}`);
|
|
38
55
|
}
|
|
56
|
+
refreshGitCliffCache();
|
|
39
57
|
bump = setAllVersions(config.packageFiles, setVersion, dryRun);
|
|
40
58
|
} else {
|
|
41
59
|
if (bumpOverride === void 0) {
|
|
@@ -63,11 +81,20 @@ function releasePrepare(config, options) {
|
|
|
63
81
|
dryRun
|
|
64
82
|
};
|
|
65
83
|
}
|
|
84
|
+
refreshGitCliffCache();
|
|
66
85
|
bump = bumpAllVersions(config.packageFiles, releaseType, dryRun);
|
|
67
86
|
}
|
|
68
87
|
const newTag = `${config.tagPrefix}${bump.newVersion}`;
|
|
69
|
-
const
|
|
70
|
-
const
|
|
88
|
+
const overrideWarnings = [];
|
|
89
|
+
const { changelogFiles, changelogJsonFiles } = writeSinglePackageChangelogs({
|
|
90
|
+
config,
|
|
91
|
+
commits,
|
|
92
|
+
newTag,
|
|
93
|
+
newVersion: bump.newVersion,
|
|
94
|
+
dryRun,
|
|
95
|
+
overrides,
|
|
96
|
+
overrideWarnings
|
|
97
|
+
});
|
|
71
98
|
maybeWriteSinglePackagePreviews(withReleaseNotes === true, config, newTag, changelogJsonFiles[0], dryRun);
|
|
72
99
|
const formatCommandStr = config.formatCommand ?? (hasPrettierConfig() ? "npx prettier --write" : void 0);
|
|
73
100
|
let formatCommand;
|
|
@@ -102,12 +129,16 @@ function releasePrepare(config, options) {
|
|
|
102
129
|
policyViolations: collector.violations.length > 0 ? collector.violations : void 0,
|
|
103
130
|
setVersion
|
|
104
131
|
});
|
|
105
|
-
|
|
132
|
+
const result = {
|
|
106
133
|
workspaces: [released],
|
|
107
134
|
tags: [newTag],
|
|
108
135
|
formatCommand,
|
|
109
136
|
dryRun
|
|
110
137
|
};
|
|
138
|
+
if (overrideWarnings.length > 0) {
|
|
139
|
+
result.warnings = overrideWarnings;
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
111
142
|
}
|
|
112
143
|
function buildSkippedSinglePackage(args) {
|
|
113
144
|
const skipped = {
|
|
@@ -172,17 +203,43 @@ function buildReleasedSinglePackage(args) {
|
|
|
172
203
|
}
|
|
173
204
|
return released;
|
|
174
205
|
}
|
|
175
|
-
function
|
|
206
|
+
function writeSinglePackageChangelogs(args) {
|
|
207
|
+
const { config, commits, newTag, newVersion, dryRun, overrides, overrideWarnings } = args;
|
|
208
|
+
const isEmptyRange = commits.length === 0;
|
|
209
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
210
|
+
const baseEntries = isEmptyRange ? [buildEmptyReleaseEntry(newVersion, today)] : buildChangelogEntries(config, newTag);
|
|
211
|
+
const applied = applyChangelogOverrides(baseEntries, overrides);
|
|
212
|
+
if (applied.errors.length > 0) {
|
|
213
|
+
throw new Error(`Changelog override application failed:
|
|
214
|
+
- ${applied.errors.join("\n - ")}`);
|
|
215
|
+
}
|
|
216
|
+
overrideWarnings.push(...applied.warnings);
|
|
217
|
+
const matchedSet = new Set(applied.matchedKeys);
|
|
218
|
+
for (const overrideKey of overrides.keys()) {
|
|
219
|
+
if (!matchedSet.has(overrideKey)) {
|
|
220
|
+
overrideWarnings.push(formatStaleOverrideKeyWarning(overrideKey));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const sectionOrder = deriveSectionOrder(resolveWorkTypes(config.workTypes));
|
|
224
|
+
const changelogFiles = [];
|
|
176
225
|
const changelogJsonFiles = [];
|
|
177
|
-
const entries = buildChangelogEntries(config, newTag);
|
|
178
226
|
for (const changelogPath of config.changelogPaths) {
|
|
179
227
|
const jsonPath = resolveChangelogJsonPath(config, changelogPath);
|
|
180
|
-
|
|
181
|
-
|
|
228
|
+
const shouldWriteJson = config.changelogJson.enabled && !dryRun;
|
|
229
|
+
const mergedEntries = shouldWriteJson ? upsertChangelogJsonAndReturn(jsonPath, applied.entries) : mergeChangelogEntriesWithDisk(jsonPath, applied.entries);
|
|
230
|
+
if (config.changelogJson.enabled) {
|
|
231
|
+
changelogJsonFiles.push(jsonPath);
|
|
182
232
|
}
|
|
183
|
-
|
|
233
|
+
changelogFiles.push(
|
|
234
|
+
writeChangelogMarkdown({
|
|
235
|
+
changelogPath,
|
|
236
|
+
entries: mergedEntries,
|
|
237
|
+
sectionOrder,
|
|
238
|
+
dryRun
|
|
239
|
+
})
|
|
240
|
+
);
|
|
184
241
|
}
|
|
185
|
-
return changelogJsonFiles;
|
|
242
|
+
return { changelogFiles, changelogJsonFiles };
|
|
186
243
|
}
|
|
187
244
|
function maybeWriteSinglePackagePreviews(withReleaseNotes, config, newTag, changelogJsonPath, dryRun) {
|
|
188
245
|
if (!withReleaseNotes) {
|