@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,18 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { formatStaleOverrideKeyWarning } from "./applyChangelogOverrides.js";
|
|
2
2
|
import { assertTaggedBaseline } from "./assertTaggedBaseline.js";
|
|
3
3
|
import { buildDependencyGraph } from "./buildDependencyGraph.js";
|
|
4
4
|
import { buildReleaseSummary } from "./buildReleaseSummary.js";
|
|
5
|
-
import { createOverrideContext
|
|
5
|
+
import { createOverrideContext } from "./changelogOverrides.js";
|
|
6
|
+
import { decideDirectReleases, reportUnroutedEntryScopes } from "./decideDirectReleases.js";
|
|
6
7
|
import { deriveDependenciesHeader, deriveSectionOrder } from "./deriveReleaseNotesConfig.js";
|
|
7
|
-
import { detectUndeclaredTagPrefixes } from "./detectUndeclaredTagPrefixes.js";
|
|
8
8
|
import { findTransitiveDependents } from "./findTransitiveDependents.js";
|
|
9
|
-
import { findUnroutedEntryScopes } from "./findUnroutedEntryScopes.js";
|
|
10
|
-
import { getAllTagPrefixes } from "./generateChangelogs.js";
|
|
11
9
|
import { hasPrettierConfig } from "./hasPrettierConfig.js";
|
|
12
|
-
import { resolveWorkTypes } from "./
|
|
13
|
-
import {
|
|
10
|
+
import { resolveWorkTypes } from "./mergeConfig.js";
|
|
11
|
+
import { describeCompositeSkip, prepareComposite } from "./prepareComposite.js";
|
|
12
|
+
import { collect, describeStage, isRepoRootTarget, readHistory, runStage, } from "./prepareRunContext.js";
|
|
13
|
+
import { buildSkippedTargetResult, planTargetRelease } from "./prepareTarget.js";
|
|
14
14
|
import { propagateBumps } from "./propagateBumps.js";
|
|
15
|
-
import { isSinglePackageTargets
|
|
15
|
+
import { isSinglePackageTargets } from "./resolveReleaseTargets.js";
|
|
16
|
+
import { topologicalSort } from "./topologicalSort.js";
|
|
16
17
|
const PREVIEWS_REQUIRE_JSON_WARNING = '--with-release-notes requires changelogJson.enabled; skipping preview generation';
|
|
17
18
|
export function releasePrepareTargets(config, options) {
|
|
18
19
|
const { settings } = config;
|
|
@@ -107,12 +108,6 @@ export function releasePrepareTargets(config, options) {
|
|
|
107
108
|
...(allWarnings.length > 0 && { warnings: allWarnings }),
|
|
108
109
|
};
|
|
109
110
|
}
|
|
110
|
-
function collect(collected, planned) {
|
|
111
|
-
collected.tags.push(planned.tag);
|
|
112
|
-
collected.writes.push(...planned.writes);
|
|
113
|
-
collected.removals.push(...planned.removals);
|
|
114
|
-
collected.modifiedFiles.push(...planned.modifiedFiles);
|
|
115
|
-
}
|
|
116
111
|
function composeSinglePackageWarnings(collected, overrideContext, arePreviewsBlocked, previewWarnings) {
|
|
117
112
|
if (collected.tags.length === 0) {
|
|
118
113
|
return [];
|
|
@@ -123,73 +118,11 @@ function composeSinglePackageWarnings(collected, overrideContext, arePreviewsBlo
|
|
|
123
118
|
...previewWarnings,
|
|
124
119
|
];
|
|
125
120
|
}
|
|
126
|
-
function
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
readings: new Map(),
|
|
130
|
-
decisions: new Map(),
|
|
131
|
-
bumps: new Map(),
|
|
132
|
-
untaggedBaselines: new Map(),
|
|
133
|
-
};
|
|
134
|
-
const hintState = { emitted: false };
|
|
135
|
-
const knownPrefixes = planned.flatMap(getAllTagPrefixes);
|
|
136
|
-
for (const target of planned) {
|
|
137
|
-
const { history, untagged } = runStage(describeStage(target), () => readTargetHistory(target, settings));
|
|
138
|
-
direct.readings.set(target.id, history);
|
|
139
|
-
if (untagged !== undefined) {
|
|
140
|
-
direct.untaggedBaselines.set(target.id, untagged);
|
|
141
|
-
}
|
|
142
|
-
if (history.previousTag === undefined && target.kind === 'package' && target.isWorkspace) {
|
|
143
|
-
maybeEmitBaselineHint(target, knownPrefixes, hintState);
|
|
144
|
-
}
|
|
145
|
-
const setVersion = setVersions?.get(target.id);
|
|
146
|
-
const decision = decideTargetRelease(target, history, {
|
|
147
|
-
force: force === true && startIds.has(target.id),
|
|
148
|
-
bumpOverride,
|
|
149
|
-
setVersion,
|
|
150
|
-
});
|
|
151
|
-
direct.decisions.set(target.id, decision);
|
|
152
|
-
if (decision.outcome === 'skip') {
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
direct.bumps.set(target.id, decision.setVersion === undefined
|
|
156
|
-
? { releaseType: decision.releaseType }
|
|
157
|
-
: { releaseType: 'patch', newVersionOverride: decision.setVersion });
|
|
158
|
-
}
|
|
159
|
-
return direct;
|
|
160
|
-
}
|
|
161
|
-
function describeCompositeSkip(composite, only) {
|
|
162
|
-
if (only === undefined || only.includes(composite.id)) {
|
|
163
|
-
return undefined;
|
|
164
|
-
}
|
|
165
|
-
if (composite.id === PROJECT_TARGET_ID) {
|
|
166
|
-
return (`Project release skipped: --only narrows this run to ${only.join(', ')}. ` +
|
|
167
|
-
'Run `release-kit prepare` without --only to include the project release.');
|
|
168
|
-
}
|
|
169
|
-
return `Release '${composite.id}' skipped: --only does not name it. Add ${composite.id} to --only to include it.`;
|
|
170
|
-
}
|
|
171
|
-
function describeStage(target) {
|
|
172
|
-
switch (target.kind) {
|
|
173
|
-
case 'composite':
|
|
174
|
-
return 'project release stage';
|
|
175
|
-
case 'directory':
|
|
176
|
-
return `directory '${target.id}' release stage`;
|
|
177
|
-
case 'package':
|
|
178
|
-
return target.isWorkspace ? `workspace '${target.id}' release stage` : undefined;
|
|
121
|
+
function listOverrideDirectory(target) {
|
|
122
|
+
if (target.kind === 'directory') {
|
|
123
|
+
return isRepoRootTarget(target) ? [] : [target.directoryPath];
|
|
179
124
|
}
|
|
180
|
-
|
|
181
|
-
function maybeEmitBaselineHint(target, knownPrefixes, state) {
|
|
182
|
-
if (state.emitted || (target.legacyIdentities?.length ?? 0) > 0)
|
|
183
|
-
return;
|
|
184
|
-
const candidates = detectUndeclaredTagPrefixes(knownPrefixes);
|
|
185
|
-
if (candidates.length === 0)
|
|
186
|
-
return;
|
|
187
|
-
const totalTags = candidates.reduce((sum, candidate) => sum + candidate.tagCount, 0);
|
|
188
|
-
const example = candidates[0]?.exampleTags[0] ?? `${candidates[0]?.prefix ?? ''}?`;
|
|
189
|
-
process.stderr.write(`Hint: no baseline tag found for ${target.id} under '${target.tagPrefix}', but ` +
|
|
190
|
-
`${totalTags} candidate-shaped tags exist (e.g., ${example}). ` +
|
|
191
|
-
"Run 'release-kit show-tag-prefixes' to check for undeclared legacy prefixes.\n");
|
|
192
|
-
state.emitted = true;
|
|
125
|
+
return target.isWorkspace ? [target.packagePath] : [];
|
|
193
126
|
}
|
|
194
127
|
function planFormatCommand(configuredCommand, collected) {
|
|
195
128
|
const formatCommandStr = configuredCommand ?? (hasPrettierConfig() ? 'npx prettier --write' : undefined);
|
|
@@ -199,71 +132,6 @@ function planFormatCommand(configuredCommand, collected) {
|
|
|
199
132
|
const files = collected.modifiedFiles;
|
|
200
133
|
return { command: `${formatCommandStr} ${files.join(' ')}`, files };
|
|
201
134
|
}
|
|
202
|
-
function prepareComposite(composite, context, options, collected, warnings) {
|
|
203
|
-
const { history, untagged } = readTargetHistory(composite, context.settings);
|
|
204
|
-
assertTaggedBaseline([untagged]);
|
|
205
|
-
const decision = decideTargetRelease(composite, history, {
|
|
206
|
-
force: options.force === true,
|
|
207
|
-
bumpOverride: options.bumpOverride,
|
|
208
|
-
setVersion: undefined,
|
|
209
|
-
});
|
|
210
|
-
if (decision.outcome === 'skip') {
|
|
211
|
-
return buildSkippedTargetResult(composite, history, decision.skipReason);
|
|
212
|
-
}
|
|
213
|
-
if (decision.setVersion !== undefined) {
|
|
214
|
-
throw new Error('A composite target cannot take --set-version');
|
|
215
|
-
}
|
|
216
|
-
const planned = planTargetRelease({
|
|
217
|
-
...context,
|
|
218
|
-
target: composite,
|
|
219
|
-
release: { releaseType: decision.releaseType },
|
|
220
|
-
decision,
|
|
221
|
-
history,
|
|
222
|
-
});
|
|
223
|
-
collect(collected, planned);
|
|
224
|
-
warnings.push(...planned.warnings);
|
|
225
|
-
return planned.result;
|
|
226
|
-
}
|
|
227
|
-
function isRepoRootTarget(target) {
|
|
228
|
-
return target.kind === 'directory' && isRepoRoot(target);
|
|
229
|
-
}
|
|
230
|
-
function listOverrideDirectory(target) {
|
|
231
|
-
if (target.kind === 'directory') {
|
|
232
|
-
return isRepoRootTarget(target) ? [] : [target.directoryPath];
|
|
233
|
-
}
|
|
234
|
-
return target.isWorkspace ? [target.packagePath] : [];
|
|
235
|
-
}
|
|
236
|
-
function readHistory(readings, target) {
|
|
237
|
-
const history = readings.get(target.id);
|
|
238
|
-
if (history === undefined) {
|
|
239
|
-
throw new Error(`Target '${target.id}' was planned without a history`);
|
|
240
|
-
}
|
|
241
|
-
return history;
|
|
242
|
-
}
|
|
243
|
-
function reportUnroutedEntryScopes(planned, direct, settings, memberTargets) {
|
|
244
|
-
const releasing = planned.filter((target) => direct.decisions.get(target.id)?.outcome === 'release');
|
|
245
|
-
const windows = [...releasing, ...planned.filter((target) => !releasing.includes(target))].map((target) => ({
|
|
246
|
-
target,
|
|
247
|
-
history: readHistory(direct.readings, target),
|
|
248
|
-
}));
|
|
249
|
-
const findings = findUnroutedEntryScopes(windows.map(({ target, history }) => ({ dir: target.id, commits: history.unreleased.commits })), memberTargets.map((target) => target.id), settings.scopeAliases ?? {});
|
|
250
|
-
for (const { target, history } of windows) {
|
|
251
|
-
history.unreleased.diagnostics.unroutedEntryScopes = isRepoRootTarget(target)
|
|
252
|
-
? []
|
|
253
|
-
: (findings.get(target.id) ?? []);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
function runStage(stage, fn) {
|
|
257
|
-
if (stage === undefined) {
|
|
258
|
-
return fn();
|
|
259
|
-
}
|
|
260
|
-
try {
|
|
261
|
-
return fn();
|
|
262
|
-
}
|
|
263
|
-
catch (error) {
|
|
264
|
-
throw chainError(stage, error);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
135
|
function selectStartIds(memberTargets, options) {
|
|
268
136
|
const { only, setVersions } = options;
|
|
269
137
|
if (only === undefined) {
|
|
@@ -271,63 +139,3 @@ function selectStartIds(memberTargets, options) {
|
|
|
271
139
|
}
|
|
272
140
|
return new Set([...only, ...(setVersions?.keys() ?? [])]);
|
|
273
141
|
}
|
|
274
|
-
function topologicalSort(releaseSet, graph) {
|
|
275
|
-
const releaseIds = new Set(releaseSet.keys());
|
|
276
|
-
if (releaseIds.size === 0) {
|
|
277
|
-
return { sorted: [], cyclicIds: [] };
|
|
278
|
-
}
|
|
279
|
-
const inDegree = new Map();
|
|
280
|
-
const forwardEdges = new Map();
|
|
281
|
-
for (const id of releaseIds) {
|
|
282
|
-
inDegree.set(id, 0);
|
|
283
|
-
forwardEdges.set(id, []);
|
|
284
|
-
}
|
|
285
|
-
for (const [packageName, dependents] of graph.dependentsOf) {
|
|
286
|
-
const dependencyId = graph.packageNameToDir.get(packageName);
|
|
287
|
-
if (dependencyId === undefined || !releaseIds.has(dependencyId)) {
|
|
288
|
-
continue;
|
|
289
|
-
}
|
|
290
|
-
for (const dependent of dependents) {
|
|
291
|
-
if (!releaseIds.has(dependent.id)) {
|
|
292
|
-
continue;
|
|
293
|
-
}
|
|
294
|
-
const edges = forwardEdges.get(dependencyId);
|
|
295
|
-
if (edges !== undefined) {
|
|
296
|
-
edges.push(dependent.id);
|
|
297
|
-
}
|
|
298
|
-
inDegree.set(dependent.id, (inDegree.get(dependent.id) ?? 0) + 1);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
const queue = [];
|
|
302
|
-
for (const [id, degree] of inDegree) {
|
|
303
|
-
if (degree === 0) {
|
|
304
|
-
queue.push(id);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
const sorted = [];
|
|
308
|
-
while (queue.length > 0) {
|
|
309
|
-
const id = queue.shift();
|
|
310
|
-
if (id === undefined) {
|
|
311
|
-
break;
|
|
312
|
-
}
|
|
313
|
-
sorted.push(id);
|
|
314
|
-
const dependents = forwardEdges.get(id) ?? [];
|
|
315
|
-
for (const dependent of dependents) {
|
|
316
|
-
const newDegree = (inDegree.get(dependent) ?? 1) - 1;
|
|
317
|
-
inDegree.set(dependent, newDegree);
|
|
318
|
-
if (newDegree === 0) {
|
|
319
|
-
queue.push(dependent);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
const sortedSet = new Set(sorted);
|
|
324
|
-
const cyclicIds = [];
|
|
325
|
-
for (const id of releaseIds) {
|
|
326
|
-
if (sortedSet.has(id)) {
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
sorted.push(id);
|
|
330
|
-
cyclicIds.push(id);
|
|
331
|
-
}
|
|
332
|
-
return { sorted, cyclicIds };
|
|
333
|
-
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { formatGlyphLine, formatStatusLine, measureGlyphColumn, STATUS_GLYPHS,
|
|
1
|
+
import { formatGlyphLine, formatStatusLine, measureGlyphColumn, STATUS_GLYPHS, } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { bold, dim, sectionHeader } from "./format.js";
|
|
3
|
+
import { formatBumpFiles, formatChangelogFiles, formatPreviewFiles } from "./formatPrepareFiles.js";
|
|
4
|
+
import { formatChangeRecordWarnings, formatPolicyViolations, formatUnparseableWarning, formatWarnings, } from "./formatPrepareWarnings.js";
|
|
3
5
|
import { RELEASE_GLYPHS } from "./glyphs.js";
|
|
4
6
|
import { PROJECT_TARGET_ID } from "./resolveReleaseTargets.js";
|
|
5
|
-
const SUBJECT_COLUMN_BUDGET = 72;
|
|
6
7
|
export function reportPrepare(plan, options) {
|
|
7
8
|
const isMultiWorkspace = plan.targets.some((target) => target.kind === 'composite' || target.name !== undefined);
|
|
8
9
|
if (isMultiWorkspace) {
|
|
@@ -178,116 +179,6 @@ function formatVersionLine(lines, workspace, propagatedFrom, isPropagatedOnly, s
|
|
|
178
179
|
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${versions} (${workspace.releaseType}${suffix})`)}`);
|
|
179
180
|
}
|
|
180
181
|
}
|
|
181
|
-
function formatBumpFiles(lines, workspace, dryRun, indent = '') {
|
|
182
|
-
for (const file of workspace.bumpedFiles) {
|
|
183
|
-
if (dryRun) {
|
|
184
|
-
lines.push(dim(`${indent} [dry-run] Would bump ${file}`));
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
lines.push(dim(`${indent} Bumped ${file}`));
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
function formatChangelogFiles(lines, release, dryRun, style, indent = '') {
|
|
192
|
-
for (const file of release.changelogFiles) {
|
|
193
|
-
if (dryRun) {
|
|
194
|
-
lines.push(dim(`${indent} [dry-run] Would generate changelog: ${file}`));
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
lines.push(dim(`${indent} Generating changelog: ${file}`));
|
|
198
|
-
}
|
|
199
|
-
const preservation = release.changelogPreservation?.find((candidate) => candidate.file === file);
|
|
200
|
-
if (preservation === undefined) {
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
if (preservation.preservedVersions.length > 0) {
|
|
204
|
-
lines.push(dim(`${indent} Kept from the existing changelog: ${preservation.preservedVersions.join(', ')}`));
|
|
205
|
-
}
|
|
206
|
-
const dropped = preservation.droppedUnversionedHeadings;
|
|
207
|
-
if (dropped.length === 0) {
|
|
208
|
-
continue;
|
|
209
|
-
}
|
|
210
|
-
const message = `Dropped ${dropped.length} section${dropped.length === 1 ? '' : 's'} without a version: ${dropped.join(', ')}`;
|
|
211
|
-
lines.push(`${indent} ${formatStatusLine(style, 'warning', message)}`);
|
|
212
|
-
}
|
|
213
|
-
const removedFiles = release.removedChangelogFiles ?? [];
|
|
214
|
-
for (const file of removedFiles) {
|
|
215
|
-
if (dryRun) {
|
|
216
|
-
lines.push(dim(`${indent} [dry-run] Would remove changelog: ${file}`));
|
|
217
|
-
}
|
|
218
|
-
else {
|
|
219
|
-
lines.push(dim(`${indent} Removing changelog: ${file}`));
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
function formatPreviewFiles(lines, release, dryRun, indent = '') {
|
|
224
|
-
const previewFiles = release.previewFiles ?? [];
|
|
225
|
-
for (const file of previewFiles) {
|
|
226
|
-
if (dryRun) {
|
|
227
|
-
lines.push(dim(`${indent} [dry-run] Would write ${file}`));
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
lines.push(dim(`${indent} Wrote ${file}`));
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
function formatUnparseableWarning(lines, release, style, indent = '') {
|
|
235
|
-
const unparseable = release.unparseableCommits;
|
|
236
|
-
if (unparseable === undefined || unparseable.length === 0) {
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
const count = unparseable.length;
|
|
240
|
-
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${count} commit${count === 1 ? '' : 's'} could not be parsed`)}`);
|
|
241
|
-
for (const commit of unparseable) {
|
|
242
|
-
const shortHash = commit.hash.slice(0, 7);
|
|
243
|
-
const truncatedSubject = truncateSubject(commit.subject);
|
|
244
|
-
lines.push(`${indent} · ${shortHash} ${truncatedSubject}`);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
function formatPolicyViolations(lines, violations, style, indent = '') {
|
|
248
|
-
if (violations === undefined || violations.length === 0) {
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
const count = violations.length;
|
|
252
|
-
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${count} policy violation${count === 1 ? '' : 's'}:`)}`);
|
|
253
|
-
for (const violation of violations) {
|
|
254
|
-
const shortHash = violation.commitHash.slice(0, 7);
|
|
255
|
-
const subject = violation.commitSubject;
|
|
256
|
-
const truncatedSubject = truncateSubject(subject);
|
|
257
|
-
const location = violation.surface === 'entry' ? `entry ${violation.entryPosition ?? '?'}` : `${violation.surface} surface`;
|
|
258
|
-
lines.push(`${indent} · ${shortHash} '${truncatedSubject}': type '${violation.type}' at ${location}`);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
function formatChangeRecordWarnings(lines, release, style, indent = '') {
|
|
262
|
-
const { malformedBlocks = [], undeclaredEntryTypes = [], unroutedEntryScopes = [] } = release;
|
|
263
|
-
if (malformedBlocks.length > 0) {
|
|
264
|
-
const count = malformedBlocks.length;
|
|
265
|
-
const header = `${count} change-record block${count === 1 ? '' : 's'} could not be read (item taken from the title)`;
|
|
266
|
-
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
|
|
267
|
-
for (const block of malformedBlocks) {
|
|
268
|
-
lines.push(`${indent} · ${block.commitHash.slice(0, 7)} '${truncateSubject(block.commitSubject)}': ${block.reason}`);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
if (undeclaredEntryTypes.length > 0) {
|
|
272
|
-
const count = undeclaredEntryTypes.length;
|
|
273
|
-
const header = `${count} change-record entr${count === 1 ? 'y has an' : 'ies have'} undeclared type${count === 1 ? '' : 's'} (no item)`;
|
|
274
|
-
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
|
|
275
|
-
for (const entry of undeclaredEntryTypes) {
|
|
276
|
-
const shortHash = entry.commitHash.slice(0, 7);
|
|
277
|
-
lines.push(`${indent} · ${shortHash} '${truncateSubject(entry.commitSubject)}': type '${entry.type}' at entry ${entry.entryPosition}`);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
if (unroutedEntryScopes.length === 0) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
const count = unroutedEntryScopes.length;
|
|
284
|
-
const header = `${count} change-record entry scope${count === 1 ? ' does not match' : 's do not match'} any workspace in the commit's window (no item there)`;
|
|
285
|
-
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
|
|
286
|
-
for (const finding of unroutedEntryScopes) {
|
|
287
|
-
const shortHash = finding.commitHash.slice(0, 7);
|
|
288
|
-
lines.push(`${indent} · ${shortHash} '${truncateSubject(finding.commitSubject)}': scope '${finding.scope}' at entry ${finding.entryPosition}`);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
182
|
function formatPropagationSuffix(propagatedFrom) {
|
|
292
183
|
if (propagatedFrom === undefined || propagatedFrom.length === 0) {
|
|
293
184
|
return '';
|
|
@@ -295,16 +186,6 @@ function formatPropagationSuffix(propagatedFrom) {
|
|
|
295
186
|
const names = propagatedFrom.map((p) => p.packageName).join(', ');
|
|
296
187
|
return `, dependency: ${names}`;
|
|
297
188
|
}
|
|
298
|
-
function formatWarnings(lines, result, style) {
|
|
299
|
-
const { warnings } = result;
|
|
300
|
-
if (warnings === undefined || warnings.length === 0) {
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
303
|
-
lines.push('');
|
|
304
|
-
for (const warning of warnings) {
|
|
305
|
-
lines.push(formatStatusLine(style, 'warning', warning));
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
189
|
function formatFormatCommand(lines, result, options) {
|
|
309
190
|
if (result.formatCommand === undefined) {
|
|
310
191
|
return;
|
|
@@ -320,6 +201,3 @@ function formatFormatCommand(lines, result, options) {
|
|
|
320
201
|
const messageIndent = ' '.repeat(2 + measureGlyphColumn(STATUS_GLYPHS[options.style]) + 1);
|
|
321
202
|
lines.push(`\n ${formatStatusLine(options.style, 'warning', `Format command failed: ${result.formatCommand.command}`)}`, `${messageIndent}${options.formatError}`);
|
|
322
203
|
}
|
|
323
|
-
function truncateSubject(subject) {
|
|
324
|
-
return truncateToWidth(subject, { width: SUBJECT_COLUMN_BUDGET });
|
|
325
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { ReleaseKitConfig } from './configSchema.js';
|
|
1
2
|
import { type ResolvedTag } from './resolveReleaseTags.js';
|
|
2
|
-
import type { ReleaseKitConfig } from './types.js';
|
|
3
3
|
/**
|
|
4
4
|
* Discovers workspaces, resolves release tags from HEAD, validates `--tags` names against the full resolved tag
|
|
5
5
|
* names (e.g., `nmr-core-v1.3.0`), and returns the filtered tag list. Exits with an error message on any validation
|
|
@@ -3,7 +3,8 @@ import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
|
3
3
|
import { deriveWorkspaceConfig } from "./deriveWorkspaceConfig.js";
|
|
4
4
|
import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
5
5
|
import { formatExcludedSkip } from "./formatExcludedSkip.js";
|
|
6
|
-
import {
|
|
6
|
+
import { readRootPackageVersion } from "./loadConfig.js";
|
|
7
|
+
import { mergeMonorepoConfig } from "./mergeConfig.js";
|
|
7
8
|
import { resolveReleaseTags } from "./resolveReleaseTags.js";
|
|
8
9
|
export function resolveCommandTags(tags, userConfig) {
|
|
9
10
|
let workspace;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { ReleaseKitConfig
|
|
1
|
+
import type { ReleaseKitConfig } from './configSchema.js';
|
|
2
|
+
import type { WorkspaceConfig } from './types.js';
|
|
2
3
|
/** The composite that a config declares, before the defaults and the root `package.json` check of `loadConfig`. */
|
|
3
4
|
export interface DeclaredComposite {
|
|
4
5
|
/** Name that selects the composite; `undefined` for a `project` block. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { ReleaseKitConfig } from './configSchema.js';
|
|
1
2
|
import type { WorkspaceDiscovery } from './discoverWorkspaces.js';
|
|
2
|
-
import type { MonorepoReleaseConfig,
|
|
3
|
+
import type { MonorepoReleaseConfig, ReleaseSettings, ReleaseTarget } from './types.js';
|
|
3
4
|
/** Id of the composite target that a `project` block declares. A target name is never empty, so ids cannot clash. */
|
|
4
5
|
export declare const PROJECT_TARGET_ID = "";
|
|
5
6
|
/** Id of the package target at the root of a single-package repo. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { isPublishableManifest } from "./isPublishableManifest.js";
|
|
3
|
-
import {
|
|
3
|
+
import { readRootPackageVersion } from "./loadConfig.js";
|
|
4
|
+
import { mergeMonorepoConfig, mergeSinglePackageConfig } from "./mergeConfig.js";
|
|
4
5
|
import { getTargetName } from "./resolveReleaseDeclaration.js";
|
|
5
6
|
export const PROJECT_TARGET_ID = '';
|
|
6
7
|
export const SINGLE_PACKAGE_TARGET_ID = '.';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type OutputStyle, type StreamStyles } from '@williamthorsen/nmr-core';
|
|
2
|
-
import type { RepoLabelsConfig } from '../
|
|
2
|
+
import type { RepoLabelsConfig } from '../configSchema.js';
|
|
3
3
|
import type { LabelDefinition } from './types.js';
|
|
4
4
|
/** Output path for the generated labels file. */
|
|
5
5
|
export declare const LABELS_OUTPUT_PATH = ".github/labels.yaml";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DependencyGraph } from './buildDependencyGraph.js';
|
|
2
|
+
import type { ReleaseEntry } from './propagateBumps.js';
|
|
3
|
+
/**
|
|
4
|
+
* Sorts target ids topologically so that dependencies are processed before their dependents.
|
|
5
|
+
*
|
|
6
|
+
* Uses Kahn's algorithm. Targets not in the release set are excluded. If the graph has
|
|
7
|
+
* cycles, the remaining nodes are appended in arbitrary order and reported via `cyclicIds`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function topologicalSort(releaseSet: Map<string, ReleaseEntry>, graph: DependencyGraph): {
|
|
10
|
+
sorted: string[];
|
|
11
|
+
cyclicIds: string[];
|
|
12
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export function topologicalSort(releaseSet, graph) {
|
|
2
|
+
const releaseIds = new Set(releaseSet.keys());
|
|
3
|
+
if (releaseIds.size === 0) {
|
|
4
|
+
return { sorted: [], cyclicIds: [] };
|
|
5
|
+
}
|
|
6
|
+
const inDegree = new Map();
|
|
7
|
+
const forwardEdges = new Map();
|
|
8
|
+
for (const id of releaseIds) {
|
|
9
|
+
inDegree.set(id, 0);
|
|
10
|
+
forwardEdges.set(id, []);
|
|
11
|
+
}
|
|
12
|
+
for (const [packageName, dependents] of graph.dependentsOf) {
|
|
13
|
+
const dependencyId = graph.packageNameToDir.get(packageName);
|
|
14
|
+
if (dependencyId === undefined || !releaseIds.has(dependencyId)) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
for (const dependent of dependents) {
|
|
18
|
+
if (!releaseIds.has(dependent.id)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const edges = forwardEdges.get(dependencyId);
|
|
22
|
+
if (edges !== undefined) {
|
|
23
|
+
edges.push(dependent.id);
|
|
24
|
+
}
|
|
25
|
+
inDegree.set(dependent.id, (inDegree.get(dependent.id) ?? 0) + 1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const queue = [];
|
|
29
|
+
for (const [id, degree] of inDegree) {
|
|
30
|
+
if (degree === 0) {
|
|
31
|
+
queue.push(id);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const sorted = [];
|
|
35
|
+
while (queue.length > 0) {
|
|
36
|
+
const id = queue.shift();
|
|
37
|
+
if (id === undefined) {
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
sorted.push(id);
|
|
41
|
+
const dependents = forwardEdges.get(id) ?? [];
|
|
42
|
+
for (const dependent of dependents) {
|
|
43
|
+
const newDegree = (inDegree.get(dependent) ?? 1) - 1;
|
|
44
|
+
inDegree.set(dependent, newDegree);
|
|
45
|
+
if (newDegree === 0) {
|
|
46
|
+
queue.push(dependent);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const sortedSet = new Set(sorted);
|
|
51
|
+
const cyclicIds = [];
|
|
52
|
+
for (const id of releaseIds) {
|
|
53
|
+
if (sortedSet.has(id)) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
sorted.push(id);
|
|
57
|
+
cyclicIds.push(id);
|
|
58
|
+
}
|
|
59
|
+
return { sorted, cyclicIds };
|
|
60
|
+
}
|