@williamthorsen/release-kit 10.7.0 → 10.9.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 +71 -3
- package/.readyup/kits/default.js +3 -3
- package/.readyup/kits/npm-auto-publish.js +1 -1
- package/.readyup/manifest.json +9 -9
- package/CHANGELOG.md +49 -3
- package/README.md +21 -15
- package/dist/esm/bin/release-kit.js +20 -102
- package/dist/esm/buildChangelogEntries.d.ts +1 -1
- package/dist/esm/buildChangelogEntries.js +14 -5
- package/dist/esm/createGithubReleaseCommand.d.ts +2 -1
- package/dist/esm/createGithubReleaseCommand.js +4 -2
- package/dist/esm/createTags.d.ts +2 -0
- package/dist/esm/createTags.js +5 -4
- package/dist/esm/format.d.ts +5 -5
- package/dist/esm/format.js +5 -4
- package/dist/esm/glyphs.d.ts +3 -0
- package/dist/esm/glyphs.js +6 -0
- package/dist/esm/init/initCommand.d.ts +3 -1
- package/dist/esm/init/initCommand.js +15 -15
- package/dist/esm/loadValidatedConfig.d.ts +2 -1
- package/dist/esm/loadValidatedConfig.js +3 -3
- package/dist/esm/parseCommitMessage.d.ts +4 -5
- package/dist/esm/prepareCommand.d.ts +2 -1
- package/dist/esm/prepareCommand.js +17 -16
- package/dist/esm/publishCommand.d.ts +2 -1
- package/dist/esm/publishCommand.js +2 -2
- package/dist/esm/reportPrepare.d.ts +2 -0
- package/dist/esm/reportPrepare.js +44 -38
- package/dist/esm/resolveReleaseNotesConfig.d.ts +2 -1
- package/dist/esm/resolveReleaseNotesConfig.js +4 -4
- package/dist/esm/resolveStylesOrExit.d.ts +10 -0
- package/dist/esm/resolveStylesOrExit.js +10 -0
- package/dist/esm/showTagPrefixesCommand.d.ts +2 -1
- package/dist/esm/showTagPrefixesCommand.js +16 -10
- package/dist/esm/sync-labels/generateCommand.d.ts +4 -2
- package/dist/esm/sync-labels/generateCommand.js +4 -4
- package/dist/esm/sync-labels/initCommand.d.ts +3 -1
- package/dist/esm/sync-labels/initCommand.js +7 -7
- package/dist/esm/tagCommand.d.ts +2 -1
- package/dist/esm/tagCommand.js +2 -2
- package/dist/esm/types.d.ts +8 -7
- package/dist/esm/validateOverridesCommand.d.ts +4 -4
- package/dist/esm/validateOverridesCommand.js +6 -6
- package/dist/esm/workTypesData.d.ts +10 -9
- package/dist/esm/workTypesData.js +97 -11
- package/docs/changelogs.md +8 -8
- package/docs/configuration.md +23 -1
- package/docs/work-types.md +28 -30
- package/package.json +11 -11
- package/presets/labels/common.yaml +2 -2
- package/dist/esm/checkWorkTypesDrift.d.ts +0 -36
- package/dist/esm/checkWorkTypesDrift.js +0 -117
- package/dist/esm/syncWorkTypes.d.ts +0 -28
- package/dist/esm/syncWorkTypes.js +0 -94
- package/dist/esm/work-types.json +0 -133
- package/dist/esm/work-types.schema.json +0 -100
- package/dist/esm/workTypesUtils.d.ts +0 -22
- package/dist/esm/workTypesUtils.js +0 -16
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { formatGlyphLine, formatStatusLine, measureGlyphColumn, STATUS_GLYPHS, } from '@williamthorsen/nmr-core';
|
|
1
2
|
import { bold, dim, sectionHeader } from "./format.js";
|
|
3
|
+
import { RELEASE_GLYPHS } from "./glyphs.js";
|
|
2
4
|
import { GIT_CLIFF_NPX_ARGS } from "./runGitCliff.js";
|
|
3
5
|
const CLIFF_DRY_RUN_COMMAND = `npx ${GIT_CLIFF_NPX_ARGS.join(' ')} ...`;
|
|
4
6
|
export function reportPrepare(plan, options) {
|
|
@@ -10,6 +12,7 @@ export function reportPrepare(plan, options) {
|
|
|
10
12
|
}
|
|
11
13
|
function formatSingleWorkspace(result, options) {
|
|
12
14
|
const dryRun = !options.applied;
|
|
15
|
+
const { style } = options;
|
|
13
16
|
const lines = [];
|
|
14
17
|
const workspace = result.workspaces[0];
|
|
15
18
|
if (workspace === undefined) {
|
|
@@ -20,11 +23,11 @@ function formatSingleWorkspace(result, options) {
|
|
|
20
23
|
if (workspace.parsedCommitCount !== undefined) {
|
|
21
24
|
lines.push(dim(` Parsed ${workspace.parsedCommitCount} typed commits`));
|
|
22
25
|
}
|
|
23
|
-
formatUnparseableWarning(lines, workspace);
|
|
24
|
-
formatPolicyViolations(lines, workspace.policyViolations);
|
|
26
|
+
formatUnparseableWarning(lines, workspace, style);
|
|
27
|
+
formatPolicyViolations(lines, workspace.policyViolations, style);
|
|
25
28
|
if (workspace.status === 'skipped') {
|
|
26
|
-
lines.push(
|
|
27
|
-
formatWarnings(lines, result);
|
|
29
|
+
lines.push(formatStatusLine(style, 'skipped', workspace.skipReason));
|
|
30
|
+
formatWarnings(lines, result, style);
|
|
28
31
|
return lines.join('\n');
|
|
29
32
|
}
|
|
30
33
|
if (workspace.setVersion !== undefined) {
|
|
@@ -40,49 +43,50 @@ function formatSingleWorkspace(result, options) {
|
|
|
40
43
|
lines.push(dim(`Bumping versions (version override)...`));
|
|
41
44
|
}
|
|
42
45
|
if (workspace.setVersion !== undefined) {
|
|
43
|
-
lines.push(
|
|
46
|
+
lines.push(formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${workspace.currentVersion} → ${bold(workspace.newVersion)} (version override)`));
|
|
44
47
|
}
|
|
45
48
|
else if (workspace.releaseType !== undefined) {
|
|
46
|
-
lines.push(
|
|
49
|
+
lines.push(formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${workspace.currentVersion} → ${bold(workspace.newVersion)} (${workspace.releaseType})`));
|
|
47
50
|
}
|
|
48
51
|
formatBumpFiles(lines, workspace, dryRun);
|
|
49
52
|
lines.push(dim('Generating changelogs...'));
|
|
50
53
|
formatChangelogFiles(lines, workspace, dryRun);
|
|
51
54
|
formatPreviewFiles(lines, workspace, dryRun);
|
|
52
55
|
formatFormatCommand(lines, result, options);
|
|
53
|
-
formatWarnings(lines, result);
|
|
54
|
-
lines.push(
|
|
56
|
+
formatWarnings(lines, result, style);
|
|
57
|
+
lines.push(formatStatusLine(style, 'passed', 'Release preparation complete.'), ` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(workspace.tag))}`);
|
|
55
58
|
return lines.join('\n');
|
|
56
59
|
}
|
|
57
60
|
function formatMultiWorkspace(result, options) {
|
|
58
61
|
const dryRun = !options.applied;
|
|
62
|
+
const { style } = options;
|
|
59
63
|
const lines = [];
|
|
60
64
|
for (const workspace of result.workspaces) {
|
|
61
|
-
formatWorkspaceSection(lines, workspace, dryRun);
|
|
65
|
+
formatWorkspaceSection(lines, workspace, dryRun, style);
|
|
62
66
|
}
|
|
63
67
|
if (result.project !== undefined) {
|
|
64
|
-
formatProjectSection(lines, result.project, dryRun);
|
|
68
|
+
formatProjectSection(lines, result.project, dryRun, style);
|
|
65
69
|
}
|
|
66
70
|
formatFormatCommand(lines, result, options);
|
|
67
|
-
formatWarnings(lines, result);
|
|
71
|
+
formatWarnings(lines, result, style);
|
|
68
72
|
if (result.tags.length > 0) {
|
|
69
|
-
lines.push(`\n
|
|
73
|
+
lines.push(`\n${formatStatusLine(style, 'passed', 'Release preparation complete.')}`);
|
|
70
74
|
for (const tag of result.tags) {
|
|
71
|
-
lines.push(`
|
|
75
|
+
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(tag))}`);
|
|
72
76
|
}
|
|
73
77
|
}
|
|
74
78
|
else {
|
|
75
|
-
lines.push(`\n
|
|
79
|
+
lines.push(`\n${formatStatusLine(style, 'skipped', 'No workspaces had release-worthy changes.')}`);
|
|
76
80
|
}
|
|
77
81
|
return lines.join('\n');
|
|
78
82
|
}
|
|
79
|
-
function formatProjectSection(lines, project, dryRun) {
|
|
83
|
+
function formatProjectSection(lines, project, dryRun, style) {
|
|
80
84
|
lines.push(`\n${sectionHeader('project')}`);
|
|
81
85
|
const since = project.previousTag === undefined ? '(no previous release found)' : `since ${project.previousTag}`;
|
|
82
86
|
lines.push(dim(` Found ${project.commitCount} commits ${since}`));
|
|
83
|
-
formatPolicyViolations(lines, project.policyViolations, ' ');
|
|
87
|
+
formatPolicyViolations(lines, project.policyViolations, style, ' ');
|
|
84
88
|
if (project.status === 'skipped') {
|
|
85
|
-
lines.push(`
|
|
89
|
+
lines.push(` ${formatStatusLine(style, 'skipped', project.skipReason)}`);
|
|
86
90
|
return;
|
|
87
91
|
}
|
|
88
92
|
const { releaseType, currentVersion, newVersion, tag } = project;
|
|
@@ -92,8 +96,8 @@ function formatProjectSection(lines, project, dryRun) {
|
|
|
92
96
|
if (project.bumpOverride !== undefined) {
|
|
93
97
|
lines.push(` Using bump override: ${project.bumpOverride}`);
|
|
94
98
|
}
|
|
95
|
-
formatProjectUnparseable(lines, project);
|
|
96
|
-
lines.push(dim(` Bumping versions (${releaseType})...`), `
|
|
99
|
+
formatProjectUnparseable(lines, project, style);
|
|
100
|
+
lines.push(dim(` Bumping versions (${releaseType})...`), ` ${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${currentVersion} → ${bold(newVersion)} (${releaseType})`)}`);
|
|
97
101
|
for (const file of project.bumpedFiles) {
|
|
98
102
|
if (dryRun) {
|
|
99
103
|
lines.push(dim(` [dry-run] Would bump ${file}`));
|
|
@@ -112,9 +116,9 @@ function formatProjectSection(lines, project, dryRun) {
|
|
|
112
116
|
}
|
|
113
117
|
}
|
|
114
118
|
formatPreviewFiles(lines, project, dryRun, ' ');
|
|
115
|
-
lines.push(`
|
|
119
|
+
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(tag))}`);
|
|
116
120
|
}
|
|
117
|
-
function formatProjectUnparseable(lines, project) {
|
|
121
|
+
function formatProjectUnparseable(lines, project, style) {
|
|
118
122
|
const unparseable = project.unparseableCommits;
|
|
119
123
|
if (unparseable === undefined || unparseable.length === 0) {
|
|
120
124
|
return;
|
|
@@ -122,35 +126,35 @@ function formatProjectUnparseable(lines, project) {
|
|
|
122
126
|
const count = unparseable.length;
|
|
123
127
|
const isPatchFloor = project.parsedCommitCount === 0;
|
|
124
128
|
const suffix = isPatchFloor ? ' (defaulting to patch bump)' : '';
|
|
125
|
-
lines.push(`
|
|
129
|
+
lines.push(` ${formatStatusLine(style, 'warning', `${count} commit${count === 1 ? '' : 's'} could not be parsed${suffix}`)}`);
|
|
126
130
|
for (const commit of unparseable) {
|
|
127
131
|
const shortHash = commit.hash.slice(0, 7);
|
|
128
132
|
const truncatedMessage = commit.message.length > 72 ? `${commit.message.slice(0, 69)}...` : commit.message;
|
|
129
133
|
lines.push(` · ${shortHash} ${truncatedMessage}`);
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
|
-
function formatWorkspaceSection(lines, workspace, dryRun) {
|
|
136
|
+
function formatWorkspaceSection(lines, workspace, dryRun, style) {
|
|
133
137
|
if (workspace.name !== undefined) {
|
|
134
138
|
lines.push(`\n${sectionHeader(workspace.name)}`);
|
|
135
139
|
}
|
|
136
140
|
const since = workspace.previousTag === undefined ? '(no previous release found)' : `since ${workspace.previousTag}`;
|
|
137
141
|
lines.push(dim(` Found ${workspace.commitCount} commits ${since}`));
|
|
138
142
|
if (workspace.status === 'skipped') {
|
|
139
|
-
lines.push(`
|
|
143
|
+
lines.push(` ${formatStatusLine(style, 'skipped', workspace.skipReason)}`);
|
|
140
144
|
return;
|
|
141
145
|
}
|
|
142
146
|
const { propagatedFrom } = workspace;
|
|
143
147
|
const isPropagatedOnly = propagatedFrom !== undefined && workspace.commitCount === 0;
|
|
144
148
|
formatCommitSummary(lines, workspace, propagatedFrom, isPropagatedOnly);
|
|
145
|
-
formatUnparseableWarning(lines, workspace, ' ');
|
|
146
|
-
formatPolicyViolations(lines, workspace.policyViolations, ' ');
|
|
149
|
+
formatUnparseableWarning(lines, workspace, style, ' ');
|
|
150
|
+
formatPolicyViolations(lines, workspace.policyViolations, style, ' ');
|
|
147
151
|
formatBumpLabels(lines, workspace, isPropagatedOnly);
|
|
148
|
-
formatVersionLine(lines, workspace, propagatedFrom, isPropagatedOnly);
|
|
152
|
+
formatVersionLine(lines, workspace, propagatedFrom, isPropagatedOnly, style);
|
|
149
153
|
formatBumpFiles(lines, workspace, dryRun, ' ');
|
|
150
154
|
lines.push(dim(' Generating changelogs...'));
|
|
151
155
|
formatChangelogFiles(lines, workspace, dryRun, ' ');
|
|
152
156
|
formatPreviewFiles(lines, workspace, dryRun, ' ');
|
|
153
|
-
lines.push(`
|
|
157
|
+
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(workspace.tag))}`);
|
|
154
158
|
}
|
|
155
159
|
function formatCommitSummary(lines, workspace, propagatedFrom, isPropagatedOnly) {
|
|
156
160
|
if (isPropagatedOnly && propagatedFrom !== undefined) {
|
|
@@ -175,13 +179,14 @@ function formatBumpLabels(lines, workspace, isPropagatedOnly) {
|
|
|
175
179
|
lines.push(dim(` Bumping versions (version override)...`));
|
|
176
180
|
}
|
|
177
181
|
}
|
|
178
|
-
function formatVersionLine(lines, workspace, propagatedFrom, isPropagatedOnly) {
|
|
182
|
+
function formatVersionLine(lines, workspace, propagatedFrom, isPropagatedOnly, style) {
|
|
183
|
+
const versions = `${workspace.currentVersion} → ${bold(workspace.newVersion)}`;
|
|
179
184
|
if (workspace.setVersion !== undefined) {
|
|
180
|
-
lines.push(`
|
|
185
|
+
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${versions} (version override)`)}`);
|
|
181
186
|
}
|
|
182
187
|
else if (workspace.releaseType !== undefined) {
|
|
183
188
|
const suffix = isPropagatedOnly ? formatPropagationSuffix(propagatedFrom) : '';
|
|
184
|
-
lines.push(`
|
|
189
|
+
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${versions} (${workspace.releaseType}${suffix})`)}`);
|
|
185
190
|
}
|
|
186
191
|
}
|
|
187
192
|
function formatBumpFiles(lines, workspace, dryRun, indent = '') {
|
|
@@ -215,7 +220,7 @@ function formatPreviewFiles(lines, release, dryRun, indent = '') {
|
|
|
215
220
|
}
|
|
216
221
|
}
|
|
217
222
|
}
|
|
218
|
-
function formatUnparseableWarning(lines, workspace, indent = '') {
|
|
223
|
+
function formatUnparseableWarning(lines, workspace, style, indent = '') {
|
|
219
224
|
const unparseable = workspace.unparseableCommits;
|
|
220
225
|
if (unparseable === undefined || unparseable.length === 0) {
|
|
221
226
|
return;
|
|
@@ -223,19 +228,19 @@ function formatUnparseableWarning(lines, workspace, indent = '') {
|
|
|
223
228
|
const count = unparseable.length;
|
|
224
229
|
const isPatchFloor = workspace.parsedCommitCount === 0;
|
|
225
230
|
const suffix = isPatchFloor ? ' (defaulting to patch bump)' : '';
|
|
226
|
-
lines.push(`${indent}
|
|
231
|
+
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${count} commit${count === 1 ? '' : 's'} could not be parsed${suffix}`)}`);
|
|
227
232
|
for (const commit of unparseable) {
|
|
228
233
|
const shortHash = commit.hash.slice(0, 7);
|
|
229
234
|
const truncatedMessage = commit.message.length > 72 ? `${commit.message.slice(0, 69)}...` : commit.message;
|
|
230
235
|
lines.push(`${indent} · ${shortHash} ${truncatedMessage}`);
|
|
231
236
|
}
|
|
232
237
|
}
|
|
233
|
-
function formatPolicyViolations(lines, violations, indent = '') {
|
|
238
|
+
function formatPolicyViolations(lines, violations, style, indent = '') {
|
|
234
239
|
if (violations === undefined || violations.length === 0) {
|
|
235
240
|
return;
|
|
236
241
|
}
|
|
237
242
|
const count = violations.length;
|
|
238
|
-
lines.push(`${indent}
|
|
243
|
+
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${count} policy violation${count === 1 ? '' : 's'}:`)}`);
|
|
239
244
|
for (const violation of violations) {
|
|
240
245
|
const shortHash = violation.commitHash.slice(0, 7);
|
|
241
246
|
const subject = violation.commitSubject;
|
|
@@ -250,14 +255,14 @@ function formatPropagationSuffix(propagatedFrom) {
|
|
|
250
255
|
const names = propagatedFrom.map((p) => p.packageName).join(', ');
|
|
251
256
|
return `, dependency: ${names}`;
|
|
252
257
|
}
|
|
253
|
-
function formatWarnings(lines, result) {
|
|
258
|
+
function formatWarnings(lines, result, style) {
|
|
254
259
|
const { warnings } = result;
|
|
255
260
|
if (warnings === undefined || warnings.length === 0) {
|
|
256
261
|
return;
|
|
257
262
|
}
|
|
258
263
|
lines.push('');
|
|
259
264
|
for (const warning of warnings) {
|
|
260
|
-
lines.push(
|
|
265
|
+
lines.push(formatStatusLine(style, 'warning', warning));
|
|
261
266
|
}
|
|
262
267
|
}
|
|
263
268
|
function formatFormatCommand(lines, result, options) {
|
|
@@ -272,5 +277,6 @@ function formatFormatCommand(lines, result, options) {
|
|
|
272
277
|
lines.push(dim(`\n Running format command: ${result.formatCommand.command}`));
|
|
273
278
|
return;
|
|
274
279
|
}
|
|
275
|
-
|
|
280
|
+
const messageIndent = ' '.repeat(2 + measureGlyphColumn(STATUS_GLYPHS[options.style]) + 1);
|
|
281
|
+
lines.push(`\n ${formatStatusLine(options.style, 'warning', `Format command failed: ${result.formatCommand.command}`)}`, `${messageIndent}${options.formatError}`);
|
|
276
282
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type OutputStyle } from '@williamthorsen/nmr-core';
|
|
1
2
|
import type { ReleaseNotesConfig } from './types.js';
|
|
2
3
|
export interface ResolvedReleaseNotesConfig {
|
|
3
4
|
releaseNotes: ReleaseNotesConfig;
|
|
@@ -20,7 +21,7 @@ export interface ResolveReleaseNotesConfigOptions {
|
|
|
20
21
|
* By default, falls back to defaults when `loadConfig()` rejects (legacy publish behavior).
|
|
21
22
|
* When `strictLoad` is `true`, a load failure prints an error and calls `process.exit(1)`.
|
|
22
23
|
*/
|
|
23
|
-
export declare function resolveReleaseNotesConfig(options?: ResolveReleaseNotesConfigOptions): Promise<ResolvedReleaseNotesConfig>;
|
|
24
|
+
export declare function resolveReleaseNotesConfig(stderrStyle: OutputStyle, options?: ResolveReleaseNotesConfigOptions): Promise<ResolvedReleaseNotesConfig>;
|
|
24
25
|
/** Extract section headers in declaration order from a merged workTypes record. */
|
|
25
26
|
export declare function deriveSectionOrder(workTypes: Record<string, {
|
|
26
27
|
header: string;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { reportError } from '@williamthorsen/nmr-core';
|
|
1
|
+
import { formatStatusLine, printError, reportError } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
3
|
import { DEFAULT_CHANGELOG_JSON_CONFIG, DEFAULT_RELEASE_NOTES_CONFIG } from "./defaults.js";
|
|
4
4
|
import { loadConfig, resolveWorkTypes } from "./loadConfig.js";
|
|
5
5
|
import { validateConfig } from "./validateConfig.js";
|
|
6
|
-
export async function resolveReleaseNotesConfig(options = {}) {
|
|
6
|
+
export async function resolveReleaseNotesConfig(stderrStyle, options = {}) {
|
|
7
7
|
const { strictLoad = false } = options;
|
|
8
8
|
let rawConfig;
|
|
9
9
|
try {
|
|
@@ -28,12 +28,12 @@ export async function resolveReleaseNotesConfig(options = {}) {
|
|
|
28
28
|
if (errors.length > 0) {
|
|
29
29
|
process.stderr.write('Invalid config:\n');
|
|
30
30
|
for (const err of errors) {
|
|
31
|
-
|
|
31
|
+
printError(err, stderrStyle);
|
|
32
32
|
}
|
|
33
33
|
process.exit(1);
|
|
34
34
|
}
|
|
35
35
|
for (const warning of warnings) {
|
|
36
|
-
console.warn(`
|
|
36
|
+
console.warn(` ${formatStatusLine(stderrStyle, 'warning', warning)}`);
|
|
37
37
|
}
|
|
38
38
|
return {
|
|
39
39
|
releaseNotes: {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
2
|
+
/** The environment variable holding the caller's standing style preference: `auto`, `plain`, or `rich`. */
|
|
3
|
+
export declare const OUTPUT_STYLE_ENV_VAR = "RELEASE_KIT_OUTPUT_STYLE";
|
|
4
|
+
export interface ResolveStylesOptions {
|
|
5
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
6
|
+
readonly stderrIsTty: boolean;
|
|
7
|
+
readonly stdoutIsTty: boolean;
|
|
8
|
+
}
|
|
9
|
+
/** Resolves the style of each output stream, exiting with a usage error when the variable names no setting. */
|
|
10
|
+
export declare function resolveStylesOrExit(options: ResolveStylesOptions): StreamStyles;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { describeInvalidOutputStyle, reportError, resolveStreamStyles, } from '@williamthorsen/nmr-core';
|
|
2
|
+
export const OUTPUT_STYLE_ENV_VAR = 'RELEASE_KIT_OUTPUT_STYLE';
|
|
3
|
+
export function resolveStylesOrExit(options) {
|
|
4
|
+
const { invalid, styles } = resolveStreamStyles({ ...options, envVar: OUTPUT_STYLE_ENV_VAR });
|
|
5
|
+
if (invalid !== undefined) {
|
|
6
|
+
reportError(describeInvalidOutputStyle(invalid));
|
|
7
|
+
process.exit(1);
|
|
8
|
+
}
|
|
9
|
+
return styles;
|
|
10
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
1
2
|
/**
|
|
2
3
|
* Orchestrate the CLI `show-tag-prefixes` command.
|
|
3
4
|
*
|
|
@@ -9,4 +10,4 @@
|
|
|
9
10
|
*
|
|
10
11
|
* @returns The exit code the caller should use.
|
|
11
12
|
*/
|
|
12
|
-
export declare function showTagPrefixesCommand(): Promise<number>;
|
|
13
|
+
export declare function showTagPrefixesCommand(styles: StreamStyles): Promise<number>;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
+
import { formatStatusLine } from '@williamthorsen/nmr-core';
|
|
1
2
|
import { detectRepoType } from "./init/detectRepoType.js";
|
|
2
3
|
import { previewTagPrefixes } from "./previewTagPrefixes.js";
|
|
3
|
-
export async function showTagPrefixesCommand() {
|
|
4
|
+
export async function showTagPrefixesCommand(styles) {
|
|
4
5
|
if (detectRepoType() === 'single-package') {
|
|
5
6
|
process.stdout.write(renderSinglePackage());
|
|
6
7
|
return 0;
|
|
7
8
|
}
|
|
8
9
|
const preview = await previewTagPrefixes();
|
|
9
|
-
process.stdout.write(renderMonorepo(preview));
|
|
10
|
+
process.stdout.write(renderMonorepo(preview, styles.stdout));
|
|
10
11
|
return computeExitCode(preview);
|
|
11
12
|
}
|
|
12
13
|
function renderSinglePackage() {
|
|
@@ -17,13 +18,13 @@ function renderSinglePackage() {
|
|
|
17
18
|
];
|
|
18
19
|
return lines.join('\n');
|
|
19
20
|
}
|
|
20
|
-
function renderMonorepo(preview) {
|
|
21
|
+
function renderMonorepo(preview, style) {
|
|
21
22
|
const lines = ['Workspace tag prefixes:', ''];
|
|
22
23
|
for (const row of preview.workspaces) {
|
|
23
|
-
lines.push(...renderWorkspaceRow(row));
|
|
24
|
+
lines.push(...renderWorkspaceRow(row, style));
|
|
24
25
|
}
|
|
25
26
|
if (preview.collisions.length > 0) {
|
|
26
|
-
lines.push('', ...preview.collisions.map((collision) =>
|
|
27
|
+
lines.push('', ...preview.collisions.map((collision) => formatStatusLine(style, 'failed', `tag prefix collision: '${collision.tagPrefix}' used by ${collision.workspacePaths.join(', ')}`)));
|
|
27
28
|
}
|
|
28
29
|
if (preview.undeclaredCandidates.length > 0) {
|
|
29
30
|
lines.push('', 'Undeclared tag prefixes:', '', ...preview.undeclaredCandidates.map((candidate) => ` '${candidate.prefix}' — ${candidate.tagCount} tags (e.g., ${candidate.exampleTags.join(', ')})`), '', 'Suggested config snippet (adjust `dir` to match your workspace if the guess is wrong, and replace the `name` placeholder with the legacy npm name):', '', renderSuggestedSnippet(preview.undeclaredCandidates), '', "If the suggested `dir` does not match your workspace, adjust before pasting. Each legacy identity requires a `name` — replace the `TODO-fill-in-legacy-npm-name` placeholder with the package's prior npm name.");
|
|
@@ -31,20 +32,25 @@ function renderMonorepo(preview) {
|
|
|
31
32
|
lines.push('');
|
|
32
33
|
return lines.join('\n');
|
|
33
34
|
}
|
|
34
|
-
function renderWorkspaceRow(row) {
|
|
35
|
+
function renderWorkspaceRow(row, style) {
|
|
35
36
|
const lines = [];
|
|
36
37
|
if (row.derivedPrefix === null) {
|
|
37
|
-
|
|
38
|
+
const failure = formatStatusLine(style, 'failed', `derivation failed: ${row.derivationError ?? 'unknown error'}`);
|
|
39
|
+
lines.push(` ${row.workspacePath} — ${failure}`);
|
|
38
40
|
return lines;
|
|
39
41
|
}
|
|
40
|
-
const statusMarker = row.derivedTagCount > 0
|
|
42
|
+
const statusMarker = row.derivedTagCount > 0
|
|
43
|
+
? formatStatusLine(style, 'passed', `${row.derivedTagCount} tags`)
|
|
44
|
+
: formatStatusLine(style, 'warning', 'no existing tags');
|
|
41
45
|
lines.push(` ${row.workspacePath} — derived prefix '${row.derivedPrefix}', ${statusMarker}`);
|
|
42
46
|
for (const entry of row.legacyEntries) {
|
|
43
47
|
if (entry.tagCount > 0) {
|
|
44
|
-
|
|
48
|
+
const recognized = `${entry.tagCount} legacy tags with '${entry.prefix}' prefix (recognized)`;
|
|
49
|
+
lines.push(` ${formatStatusLine(style, 'passed', recognized)}`);
|
|
45
50
|
}
|
|
46
51
|
else {
|
|
47
|
-
|
|
52
|
+
const tagless = `recorded legacy prefix '${entry.prefix}' has no tags`;
|
|
53
|
+
lines.push(` ${formatStatusLine(style, 'warning', tagless)}`);
|
|
48
54
|
}
|
|
49
55
|
}
|
|
50
56
|
return lines;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type OutputStyle, type StreamStyles } from '@williamthorsen/nmr-core';
|
|
1
2
|
import type { RepoLabelsConfig } from '../types.js';
|
|
2
3
|
import type { LabelDefinition } from './types.js';
|
|
3
4
|
/** Output path for the generated labels file. */
|
|
@@ -10,11 +11,12 @@ export declare function formatLabelsYaml(labels: LabelDefinition[], presetHashes
|
|
|
10
11
|
* Reports an actionable error and returns `undefined` when the config file is missing,
|
|
11
12
|
* fails to load or validate, or carries no `repoLabels` block.
|
|
12
13
|
*/
|
|
13
|
-
export declare function loadRepoLabelsConfig(): Promise<RepoLabelsConfig | undefined>;
|
|
14
|
+
export declare function loadRepoLabelsConfig(stderrStyle: OutputStyle): Promise<RepoLabelsConfig | undefined>;
|
|
14
15
|
/** Options for the `sync-labels generate` subcommand. */
|
|
15
16
|
interface GenerateOptions {
|
|
16
17
|
/** Compare the regenerated content against the committed file instead of writing. */
|
|
17
18
|
check?: boolean;
|
|
19
|
+
styles: StreamStyles;
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* Runs the `sync-labels generate` command.
|
|
@@ -23,5 +25,5 @@ interface GenerateOptions {
|
|
|
23
25
|
* and writes `.github/labels.yaml` -- or, with `check`, regenerates in memory and reports whether the committed file
|
|
24
26
|
* is stale, writing nothing. Returns 0 on success, 1 on failure.
|
|
25
27
|
*/
|
|
26
|
-
export declare function generateCommand({ check }
|
|
28
|
+
export declare function generateCommand({ check, styles }: GenerateOptions): Promise<number>;
|
|
27
29
|
export {};
|
|
@@ -19,8 +19,8 @@ export function formatLabelsYaml(labels, presetHashes) {
|
|
|
19
19
|
const yamlBody = stringify(declared, { singleQuote: true, lineWidth: 0 });
|
|
20
20
|
return headerLines.join('\n') + '\n' + yamlBody;
|
|
21
21
|
}
|
|
22
|
-
export async function loadRepoLabelsConfig() {
|
|
23
|
-
const result = await loadValidatedConfig();
|
|
22
|
+
export async function loadRepoLabelsConfig(stderrStyle) {
|
|
23
|
+
const result = await loadValidatedConfig(stderrStyle);
|
|
24
24
|
if (result.status === 'missing') {
|
|
25
25
|
reportError(`No config file found at ${CONFIG_FILE_PATH}. Run \`release-kit sync-labels init\` first.`);
|
|
26
26
|
return undefined;
|
|
@@ -34,11 +34,11 @@ export async function loadRepoLabelsConfig() {
|
|
|
34
34
|
}
|
|
35
35
|
return result.config.repoLabels;
|
|
36
36
|
}
|
|
37
|
-
export async function generateCommand({ check = false
|
|
37
|
+
export async function generateCommand({ check = false, styles }) {
|
|
38
38
|
if (checkRetiredSyncLabelsConfig()) {
|
|
39
39
|
return 1;
|
|
40
40
|
}
|
|
41
|
-
const repoLabels = await loadRepoLabelsConfig();
|
|
41
|
+
const repoLabels = await loadRepoLabelsConfig(styles.stderr);
|
|
42
42
|
if (repoLabels === undefined) {
|
|
43
43
|
return 1;
|
|
44
44
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
1
2
|
/** Options for the `sync-labels init` subcommand. */
|
|
2
3
|
interface InitOptions {
|
|
3
4
|
dryRun: boolean;
|
|
4
5
|
force: boolean;
|
|
6
|
+
styles: StreamStyles;
|
|
5
7
|
}
|
|
6
8
|
/**
|
|
7
9
|
* Run the `sync-labels init` subcommand.
|
|
@@ -11,5 +13,5 @@ interface InitOptions {
|
|
|
11
13
|
* printed to stdout for manual paste when the file is already there — an existing,
|
|
12
14
|
* hand-authored config is never rewritten. Returns 0 on success, 1 on failure.
|
|
13
15
|
*/
|
|
14
|
-
export declare function syncLabelsInitCommand({ dryRun, force }: InitOptions): Promise<number>;
|
|
16
|
+
export declare function syncLabelsInitCommand({ dryRun, force, styles }: InitOptions): Promise<number>;
|
|
15
17
|
export {};
|
|
@@ -8,7 +8,7 @@ import { generateCommand, LABELS_OUTPUT_PATH } from "./generateCommand.js";
|
|
|
8
8
|
import { checkRetiredSyncLabelsConfig } from "./retiredConfig.js";
|
|
9
9
|
import { buildScopeLabels, renderRepoLabelsBlock, repoLabelsConfigScript, syncLabelsWorkflow } from "./templates.js";
|
|
10
10
|
const WORKFLOW_PATH = '.github/workflows/sync-labels.yaml';
|
|
11
|
-
export async function syncLabelsInitCommand({ dryRun, force }) {
|
|
11
|
+
export async function syncLabelsInitCommand({ dryRun, force, styles }) {
|
|
12
12
|
if (checkRetiredSyncLabelsConfig()) {
|
|
13
13
|
return 1;
|
|
14
14
|
}
|
|
@@ -32,14 +32,14 @@ export async function syncLabelsInitCommand({ dryRun, force }) {
|
|
|
32
32
|
console.info(` Found ${String(workspacePaths.length)} workspaces`);
|
|
33
33
|
}
|
|
34
34
|
const configExists = existsSync(CONFIG_FILE_PATH);
|
|
35
|
-
const retiredNames = configExists ? await loadRetiredPackageNames() : [];
|
|
35
|
+
const retiredNames = configExists ? await loadRetiredPackageNames(styles) : [];
|
|
36
36
|
if (retiredNames === undefined) {
|
|
37
37
|
return 1;
|
|
38
38
|
}
|
|
39
39
|
const scopeLabels = workspacePaths === undefined ? [] : buildScopeLabels(workspacePaths, retiredNames);
|
|
40
40
|
console.info('\n> Scaffolding files');
|
|
41
41
|
const workflowResult = writeFileWithCheck(WORKFLOW_PATH, syncLabelsWorkflow(), { dryRun, overwrite: force });
|
|
42
|
-
reportWriteResult(workflowResult, dryRun);
|
|
42
|
+
reportWriteResult(workflowResult, dryRun, styles);
|
|
43
43
|
if (configExists) {
|
|
44
44
|
console.info(`\n> ${CONFIG_FILE_PATH} already exists; add this block to the object passed to defineConfig:\n`);
|
|
45
45
|
console.info(renderRepoLabelsBlock(scopeLabels));
|
|
@@ -60,7 +60,7 @@ export async function syncLabelsInitCommand({ dryRun, force }) {
|
|
|
60
60
|
dryRun,
|
|
61
61
|
overwrite: force,
|
|
62
62
|
});
|
|
63
|
-
reportWriteResult(configResult, dryRun);
|
|
63
|
+
reportWriteResult(configResult, dryRun, styles);
|
|
64
64
|
if (workflowResult.outcome === 'failed' || configResult.outcome === 'failed') {
|
|
65
65
|
process.stderr.write('Failed to scaffold one or more files.\n');
|
|
66
66
|
return 1;
|
|
@@ -70,7 +70,7 @@ export async function syncLabelsInitCommand({ dryRun, force }) {
|
|
|
70
70
|
}
|
|
71
71
|
else {
|
|
72
72
|
console.info('\n> Generating labels');
|
|
73
|
-
const generateExitCode = await generateCommand();
|
|
73
|
+
const generateExitCode = await generateCommand({ styles });
|
|
74
74
|
if (generateExitCode !== 0) {
|
|
75
75
|
return generateExitCode;
|
|
76
76
|
}
|
|
@@ -87,8 +87,8 @@ export async function syncLabelsInitCommand({ dryRun, force }) {
|
|
|
87
87
|
`);
|
|
88
88
|
return 0;
|
|
89
89
|
}
|
|
90
|
-
async function loadRetiredPackageNames() {
|
|
91
|
-
const result = await loadValidatedConfig();
|
|
90
|
+
async function loadRetiredPackageNames(styles) {
|
|
91
|
+
const result = await loadValidatedConfig(styles.stderr);
|
|
92
92
|
if (result.status === 'invalid') {
|
|
93
93
|
return undefined;
|
|
94
94
|
}
|
package/dist/esm/tagCommand.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
1
2
|
/** Orchestrate the CLI `tag` command: parse flags and delegate to `createTags`. */
|
|
2
|
-
export declare function tagCommand(argv: string[]): void;
|
|
3
|
+
export declare function tagCommand(argv: string[], styles: StreamStyles): void;
|
package/dist/esm/tagCommand.js
CHANGED
|
@@ -5,10 +5,10 @@ const tagFlagSchema = {
|
|
|
5
5
|
dryRun: { long: '--dry-run', type: 'boolean' },
|
|
6
6
|
noGitChecks: { long: '--no-git-checks', type: 'boolean' },
|
|
7
7
|
};
|
|
8
|
-
export function tagCommand(argv) {
|
|
8
|
+
export function tagCommand(argv, styles) {
|
|
9
9
|
const { dryRun, noGitChecks } = parseArgsOrExit(argv, tagFlagSchema).flags;
|
|
10
10
|
try {
|
|
11
|
-
createTags({ dryRun, noGitChecks });
|
|
11
|
+
createTags({ dryRun, noGitChecks, style: styles.stdout });
|
|
12
12
|
}
|
|
13
13
|
catch (error) {
|
|
14
14
|
reportError(describeError(error));
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -11,19 +11,20 @@ export interface ChangelogItem {
|
|
|
11
11
|
/**
|
|
12
12
|
* Whether this item represents a breaking change.
|
|
13
13
|
*
|
|
14
|
-
* `true` when the commit subject carries the `!` prefix (e.g. `feat!:` or `drop(scope)!:`)
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* marker constructed from `WORK_TYPES_DATA.markers.breaking`
|
|
18
|
-
* with the canonical SSOT values) to surface them prominently
|
|
14
|
+
* `true` when the commit subject carries the `!` prefix (e.g. `feat!:` or `drop(scope)!:`) and
|
|
15
|
+
* the commit's work-type policy permits it, so the item agrees with the version bump.
|
|
16
|
+
* The `BREAKING CHANGE:` body footer is intentionally NOT considered here. Renderers prefix
|
|
17
|
+
* breaking-item bullets with the marker constructed from `WORK_TYPES_DATA.markers.breaking`
|
|
18
|
+
* (rendered as `🚨 **Breaking:** ` with the canonical SSOT values) to surface them prominently
|
|
19
|
+
* in release notes.
|
|
19
20
|
*/
|
|
20
21
|
breaking?: boolean;
|
|
21
22
|
/**
|
|
22
23
|
* Migration instruction for a consumer, taken from the `Migration:` paragraph of `body`.
|
|
23
24
|
*
|
|
24
25
|
* Derived from `body` wherever `body` is set, and absent where the body carries no labeled
|
|
25
|
-
* paragraph. Independent of `breaking`: a `
|
|
26
|
-
*
|
|
26
|
+
* paragraph. Independent of `breaking`: a `deprecate` commit cannot carry `!` under the default
|
|
27
|
+
* breaking policy and still calls for a migration. `body` keeps the paragraph, so
|
|
27
28
|
* `CHANGELOG.md` goes on rendering it in place.
|
|
28
29
|
*/
|
|
29
30
|
migration?: string;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { type OutputStyle, type StreamStyles } from '@williamthorsen/nmr-core';
|
|
1
2
|
import { type ValidateAllChangelogOverridesInputs, type ValidateAllChangelogOverridesResult } from './changelogOverrides.js';
|
|
2
3
|
import type { ChangelogEntry, ReleaseConfig } from './types.js';
|
|
3
4
|
/**
|
|
4
5
|
* Result of {@link validateOverridesCommand}: tiered exit code paired with a human-readable
|
|
5
|
-
* message
|
|
6
|
-
* uniformly thin.
|
|
6
|
+
* message, which keeps the CLI dispatch layer thin.
|
|
7
7
|
*
|
|
8
8
|
* Exit codes:
|
|
9
9
|
* - `0` — clean: no errors, no warnings.
|
|
@@ -36,9 +36,9 @@ export interface ValidateOverridesCommandDependencies {
|
|
|
36
36
|
* Single-package and monorepo modes are handled uniformly: single-package collapses to one
|
|
37
37
|
* project scope; monorepo expands to a project scope plus one scope per workspace.
|
|
38
38
|
*/
|
|
39
|
-
export declare function validateOverridesCommand(dependencies?: ValidateOverridesCommandDependencies): Promise<ValidateOverridesCommandResult>;
|
|
39
|
+
export declare function validateOverridesCommand(styles: StreamStyles, dependencies?: ValidateOverridesCommandDependencies): Promise<ValidateOverridesCommandResult>;
|
|
40
40
|
/**
|
|
41
41
|
* Pure formatter — take an aggregated validation result, return the tiered exit code and a
|
|
42
42
|
* rendered message. Exported for unit testing without going through the full discovery path.
|
|
43
43
|
*/
|
|
44
|
-
export declare function formatValidateOverridesResult(result: ValidateAllChangelogOverridesResult): ValidateOverridesCommandResult;
|
|
44
|
+
export declare function formatValidateOverridesResult(result: ValidateAllChangelogOverridesResult, style: OutputStyle): ValidateOverridesCommandResult;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatErrorLine } from '@williamthorsen/nmr-core';
|
|
1
|
+
import { formatErrorLine, formatStatusLine } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
3
|
import { buildChangelogEntries } from "./buildChangelogEntries.js";
|
|
4
4
|
import { resolveOverridePath, validateAllChangelogOverrides, } from "./changelogOverrides.js";
|
|
@@ -7,7 +7,7 @@ import { buildTagPattern, getAllTagPrefixes } from "./generateChangelogs.js";
|
|
|
7
7
|
import { loadConfig, mergeMonorepoConfig, mergeSinglePackageConfig, readRootPackageVersion } from "./loadConfig.js";
|
|
8
8
|
import { validateConfig } from "./validateConfig.js";
|
|
9
9
|
const SYNTHETIC_VALIDATE_TAG = 'validate-only';
|
|
10
|
-
export async function validateOverridesCommand(dependencies = {}) {
|
|
10
|
+
export async function validateOverridesCommand(styles, dependencies = {}) {
|
|
11
11
|
const discover = dependencies.discoverWorkspaces ?? discoverWorkspaces;
|
|
12
12
|
const load = dependencies.loadConfig ?? loadConfig;
|
|
13
13
|
const buildEntries = dependencies.buildEntries ?? defaultBuildEntries;
|
|
@@ -44,17 +44,17 @@ export async function validateOverridesCommand(dependencies = {}) {
|
|
|
44
44
|
return { exitCode: 2, message: formatErrorLine(`Failed to resolve overrides scope: ${describeError(error)}`) };
|
|
45
45
|
}
|
|
46
46
|
const result = validate(inputs);
|
|
47
|
-
return formatValidateOverridesResult(result);
|
|
47
|
+
return formatValidateOverridesResult(result, styles.stderr);
|
|
48
48
|
}
|
|
49
|
-
export function formatValidateOverridesResult(result) {
|
|
49
|
+
export function formatValidateOverridesResult(result, style) {
|
|
50
50
|
const { errors, warnings } = result;
|
|
51
51
|
if (errors.length === 0 && warnings.length === 0) {
|
|
52
52
|
return { exitCode: 0, message: 'All override files are valid (no errors, no stale keys).' };
|
|
53
53
|
}
|
|
54
54
|
const exitCode = errors.length > 0 ? 2 : 1;
|
|
55
55
|
const summary = formatSummaryLine(errors.length, warnings.length);
|
|
56
|
-
const errorLines = errors.map((message) => `
|
|
57
|
-
const warningLines = warnings.map((message) => `
|
|
56
|
+
const errorLines = errors.map((message) => ` ${formatStatusLine(style, 'failed', message)}`);
|
|
57
|
+
const warningLines = warnings.map((message) => ` ${formatStatusLine(style, 'warning', message)}`);
|
|
58
58
|
const message = [summary, '', ...errorLines, ...warningLines].join('\n');
|
|
59
59
|
return { exitCode, message };
|
|
60
60
|
}
|