@williamthorsen/release-kit 18.1.1 → 18.3.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 +150 -0
- package/.readyup/kits/default.js +596 -32
- package/.readyup/kits/npm-auto-publish.js +1 -1
- package/.readyup/manifest.json +55 -33
- package/CHANGELOG.md +44 -0
- package/README.md +19 -2
- package/dist/esm/assertCleanWorkingTree.d.ts +2 -1
- package/dist/esm/assertCleanWorkingTree.js +4 -8
- package/dist/esm/assertReleasesOnNpm.js +2 -9
- package/dist/esm/assertTarballsComplete.js +2 -9
- package/dist/esm/buildChangelogEntries.d.ts +5 -3
- package/dist/esm/buildChangelogEntries.js +9 -4
- package/dist/esm/buildChangelogItem.d.ts +1 -1
- package/dist/esm/buildDependencyGraph.js +2 -16
- package/dist/esm/buildReleasedResult.d.ts +15 -3
- package/dist/esm/buildReleasedResult.js +23 -2
- package/dist/esm/changelogJsonFile.d.ts +3 -2
- package/dist/esm/changelogJsonFile.js +14 -17
- package/dist/esm/changelogJsonUtils.d.ts +22 -2
- package/dist/esm/changelogJsonUtils.js +19 -10
- package/dist/esm/cli/maintenanceCommands.js +1 -1
- package/dist/esm/commitCommand.d.ts +2 -1
- package/dist/esm/commitCommand.js +8 -11
- package/dist/esm/configSchema.d.ts +3 -1
- package/dist/esm/configSchema.js +1 -0
- package/dist/esm/createGithubRelease.d.ts +13 -10
- package/dist/esm/createGithubRelease.js +9 -13
- package/dist/esm/createGithubReleaseCommand.d.ts +1 -1
- package/dist/esm/createGithubReleaseCommand.js +25 -3
- package/dist/esm/createTags.d.ts +2 -1
- package/dist/esm/createTags.js +12 -12
- package/dist/esm/decideDirectReleases.d.ts +2 -1
- package/dist/esm/decideDirectReleases.js +5 -5
- package/dist/esm/decideRelease.d.ts +1 -1
- package/dist/esm/defaults.d.ts +4 -2
- package/dist/esm/defaults.js +1 -0
- package/dist/esm/deriveDirectoryConfig.js +5 -4
- package/dist/esm/deriveWorkspaceConfig.js +2 -9
- package/dist/esm/detectUndeclaredTagPrefixes.d.ts +2 -1
- package/dist/esm/detectUndeclaredTagPrefixes.js +4 -13
- package/dist/esm/determineBumpType.d.ts +2 -1
- package/dist/esm/determineBumpType.js +5 -0
- package/dist/esm/enumerateReleaseWindows.d.ts +3 -11
- package/dist/esm/enumerateReleaseWindows.js +17 -60
- package/dist/esm/findUnroutedEntryScopes.d.ts +1 -1
- package/dist/esm/git.d.ts +61 -0
- package/dist/esm/git.js +101 -0
- package/dist/esm/init/checks.d.ts +2 -1
- package/dist/esm/init/checks.js +5 -10
- package/dist/esm/init/eligibility.d.ts +12 -0
- package/dist/esm/init/eligibility.js +50 -0
- package/dist/esm/init/initCommand.d.ts +0 -2
- package/dist/esm/init/initCommand.js +4 -45
- package/dist/esm/init/managed-workflows.d.ts +10 -0
- package/dist/esm/init/managed-workflows.js +18 -0
- package/dist/esm/init/templates.js +2 -2
- package/dist/esm/init/updateTemplatesCommand.js +9 -42
- package/dist/esm/injectReleaseNotesIntoReadme.d.ts +29 -7
- package/dist/esm/injectReleaseNotesIntoReadme.js +13 -21
- package/dist/esm/loadConfig.js +3 -16
- package/dist/esm/mergeConfig.d.ts +2 -1
- package/dist/esm/packFromCleanCheckout.js +3 -9
- package/dist/esm/planVersionBump.js +2 -16
- package/dist/esm/prepareCommand.js +2 -1
- package/dist/esm/prepareComposite.js +1 -1
- package/dist/esm/prepareReleaseTargets.d.ts +2 -1
- package/dist/esm/prepareReleaseTargets.js +3 -2
- package/dist/esm/prepareRunContext.d.ts +2 -0
- package/dist/esm/prepareTarget.d.ts +6 -4
- package/dist/esm/prepareTarget.js +9 -3
- package/dist/esm/previewTagPrefixes.d.ts +9 -2
- package/dist/esm/previewTagPrefixes.js +22 -22
- package/dist/esm/publishCommand.d.ts +1 -1
- package/dist/esm/publishCommand.js +27 -2
- package/dist/esm/pushRelease.d.ts +2 -1
- package/dist/esm/pushRelease.js +11 -11
- package/dist/esm/readJsonFile.d.ts +2 -0
- package/dist/esm/readJsonFile.js +17 -0
- package/dist/esm/reportPrepare.js +68 -114
- package/dist/esm/resolveReleaseTags.d.ts +2 -1
- package/dist/esm/resolveReleaseTags.js +4 -12
- package/dist/esm/showTagPrefixesCommand.d.ts +6 -6
- package/dist/esm/showTagPrefixesCommand.js +94 -39
- package/dist/esm/sync-labels/generateCommand.js +0 -4
- package/dist/esm/sync-labels/initCommand.js +0 -4
- package/dist/esm/sync-labels/syncCommand.js +0 -4
- package/dist/esm/types.d.ts +8 -2
- package/docs/configuration.md +15 -6
- package/docs/labels.md +2 -2
- package/docs/releasing.md +3 -2
- package/docs/work-types.md +2 -0
- package/package.json +7 -7
- package/presets/labels/common.yaml +14 -0
- package/dist/esm/sync-labels/retiredConfig.d.ts +0 -11
- package/dist/esm/sync-labels/retiredConfig.js +0 -12
|
@@ -19,42 +19,12 @@ function formatSingleWorkspace(result, options) {
|
|
|
19
19
|
if (workspace === undefined || workspace.kind === 'composite') {
|
|
20
20
|
return '';
|
|
21
21
|
}
|
|
22
|
-
const sinceLabel = workspace.previousTag
|
|
23
|
-
lines
|
|
24
|
-
if (workspace.parsedCommitCount !== undefined) {
|
|
25
|
-
lines.push(dim(` Parsed ${workspace.parsedCommitCount} typed commits`));
|
|
26
|
-
}
|
|
27
|
-
formatUnparseableWarning(lines, workspace, style);
|
|
28
|
-
formatPolicyViolations(lines, workspace.policyViolations, style);
|
|
22
|
+
const sinceLabel = `since ${workspace.previousTag ?? 'the beginning'}`;
|
|
23
|
+
formatTargetSection(lines, workspace, { indent: '', sinceLabel }, isDryRun, style);
|
|
29
24
|
if (workspace.status === 'skipped') {
|
|
30
|
-
formatChangeRecordWarnings(lines, workspace, style);
|
|
31
|
-
lines.push(formatStatusLine(style, 'skipped', workspace.skipReason));
|
|
32
25
|
formatWarnings(lines, result, style);
|
|
33
26
|
return lines.join('\n');
|
|
34
27
|
}
|
|
35
|
-
if (workspace.versionOverride !== undefined) {
|
|
36
|
-
lines.push(` Using version override: ${workspace.versionOverride}`);
|
|
37
|
-
}
|
|
38
|
-
else if (workspace.bumpOverride !== undefined) {
|
|
39
|
-
lines.push(` Using bump override: ${workspace.bumpOverride}`);
|
|
40
|
-
}
|
|
41
|
-
if (workspace.releaseType !== undefined) {
|
|
42
|
-
lines.push(dim(`Bumping versions (${workspace.releaseType})...`));
|
|
43
|
-
}
|
|
44
|
-
else if (workspace.versionOverride !== undefined) {
|
|
45
|
-
lines.push(dim(`Bumping versions (version override)...`));
|
|
46
|
-
}
|
|
47
|
-
if (workspace.versionOverride !== undefined) {
|
|
48
|
-
lines.push(formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${workspace.currentVersion} → ${bold(workspace.newVersion)} (version override)`));
|
|
49
|
-
}
|
|
50
|
-
else if (workspace.releaseType !== undefined) {
|
|
51
|
-
lines.push(formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${workspace.currentVersion} → ${bold(workspace.newVersion)} (${workspace.releaseType})`));
|
|
52
|
-
}
|
|
53
|
-
formatChangeRecordWarnings(lines, workspace, style);
|
|
54
|
-
formatBumpFiles(lines, workspace, isDryRun);
|
|
55
|
-
lines.push(dim('Generating changelogs...'));
|
|
56
|
-
formatChangelogFiles(lines, workspace, isDryRun, style);
|
|
57
|
-
formatPreviewFiles(lines, workspace, isDryRun);
|
|
58
28
|
formatFormatCommand(lines, result, options);
|
|
59
29
|
formatWarnings(lines, result, style);
|
|
60
30
|
lines.push(formatStatusLine(style, 'passed', 'Release preparation complete.'), ` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(workspace.tag))}`);
|
|
@@ -65,11 +35,14 @@ function formatMultiWorkspace(result, options) {
|
|
|
65
35
|
const { style } = options;
|
|
66
36
|
const lines = [];
|
|
67
37
|
for (const target of result.targets) {
|
|
68
|
-
|
|
69
|
-
|
|
38
|
+
const title = getSectionTitle(target);
|
|
39
|
+
if (title !== undefined) {
|
|
40
|
+
lines.push(`\n${renderSectionHeader(title)}`);
|
|
70
41
|
}
|
|
71
|
-
|
|
72
|
-
|
|
42
|
+
const sinceLabel = target.previousTag === undefined ? '(no previous release found)' : `since ${target.previousTag}`;
|
|
43
|
+
formatTargetSection(lines, target, { indent: ' ', sinceLabel }, isDryRun, style);
|
|
44
|
+
if (target.status === 'released') {
|
|
45
|
+
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(target.tag))}`);
|
|
73
46
|
}
|
|
74
47
|
}
|
|
75
48
|
formatFormatCommand(lines, result, options);
|
|
@@ -85,98 +58,79 @@ function formatMultiWorkspace(result, options) {
|
|
|
85
58
|
}
|
|
86
59
|
return lines.join('\n');
|
|
87
60
|
}
|
|
88
|
-
function
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
lines.push(dim(` Found ${project.commitCount} commits ${sinceLabel}`));
|
|
92
|
-
formatPolicyViolations(lines, project.policyViolations, style, ' ');
|
|
93
|
-
if (project.status === 'skipped') {
|
|
94
|
-
formatUnparseableWarning(lines, project, style, ' ');
|
|
95
|
-
formatChangeRecordWarnings(lines, project, style, ' ');
|
|
96
|
-
lines.push(` ${formatStatusLine(style, 'skipped', project.skipReason)}`);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const { releaseType, currentVersion, newVersion, tag } = project;
|
|
100
|
-
if (project.parsedCommitCount > 0) {
|
|
101
|
-
lines.push(dim(` Parsed ${project.parsedCommitCount} typed commits`));
|
|
61
|
+
function getSectionTitle(target) {
|
|
62
|
+
if (target.kind === 'composite') {
|
|
63
|
+
return target.targetId === PROJECT_TARGET_ID ? 'project' : target.targetId;
|
|
102
64
|
}
|
|
103
|
-
|
|
104
|
-
lines.push(` Using bump override: ${project.bumpOverride}`);
|
|
105
|
-
}
|
|
106
|
-
formatUnparseableWarning(lines, project, style, ' ');
|
|
107
|
-
formatChangeRecordWarnings(lines, project, style, ' ');
|
|
108
|
-
lines.push(dim(` Bumping versions (${releaseType})...`), ` ${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${currentVersion} → ${bold(newVersion)} (${releaseType})`)}`);
|
|
109
|
-
for (const file of project.bumpedFiles) {
|
|
110
|
-
if (isDryRun) {
|
|
111
|
-
lines.push(dim(` [dry-run] Would bump ${file}`));
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
lines.push(dim(` Bumped ${file}`));
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
lines.push(dim(' Generating changelogs...'));
|
|
118
|
-
formatChangelogFiles(lines, project, isDryRun, style, ' ');
|
|
119
|
-
formatPreviewFiles(lines, project, isDryRun, ' ');
|
|
120
|
-
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(tag))}`);
|
|
65
|
+
return target.name;
|
|
121
66
|
}
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (workspace.status === 'skipped') {
|
|
129
|
-
formatUnparseableWarning(lines, workspace, style, ' ');
|
|
130
|
-
formatPolicyViolations(lines, workspace.policyViolations, style, ' ');
|
|
131
|
-
formatChangeRecordWarnings(lines, workspace, style, ' ');
|
|
132
|
-
lines.push(` ${formatStatusLine(style, 'skipped', workspace.skipReason)}`);
|
|
67
|
+
function formatTargetSection(lines, target, layout, isDryRun, style) {
|
|
68
|
+
const { indent, sinceLabel } = layout;
|
|
69
|
+
lines.push(dim(`${indent}Found ${target.commitCount} commits ${sinceLabel}`));
|
|
70
|
+
if (target.status === 'skipped') {
|
|
71
|
+
formatTargetWarnings(lines, target, style, indent);
|
|
72
|
+
lines.push(`${indent}${formatStatusLine(style, 'skipped', target.skipReason)}`);
|
|
133
73
|
return;
|
|
134
74
|
}
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
lines.
|
|
75
|
+
const origin = describeBumpOrigin(target);
|
|
76
|
+
formatCommitSummary(lines, target, origin);
|
|
77
|
+
formatTargetWarnings(lines, target, style, indent);
|
|
78
|
+
formatBumpLabels(lines, target, origin, indent);
|
|
79
|
+
formatVersionLine(lines, target, origin, indent, style);
|
|
80
|
+
formatBumpFiles(lines, target, isDryRun, indent);
|
|
81
|
+
lines.push(dim(`${indent}Generating changelogs...`));
|
|
82
|
+
formatChangelogFiles(lines, target, isDryRun, style, indent);
|
|
83
|
+
formatPreviewFiles(lines, target, isDryRun, indent);
|
|
84
|
+
}
|
|
85
|
+
function formatTargetWarnings(lines, target, style, indent) {
|
|
86
|
+
formatUnparseableWarning(lines, target, style, indent);
|
|
87
|
+
formatPolicyViolations(lines, target.policyViolations, style, indent);
|
|
88
|
+
formatChangeRecordWarnings(lines, target, style, indent);
|
|
89
|
+
}
|
|
90
|
+
function describeBumpOrigin(target) {
|
|
91
|
+
if (target.kind === 'composite') {
|
|
92
|
+
return { propagatedFrom: undefined, isPropagatedOnly: false, versionOverride: undefined };
|
|
93
|
+
}
|
|
94
|
+
if (target.kind === 'directory') {
|
|
95
|
+
return { propagatedFrom: undefined, isPropagatedOnly: false, versionOverride: target.versionOverride };
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
propagatedFrom: target.propagatedFrom,
|
|
99
|
+
isPropagatedOnly: target.propagatedOnly === true,
|
|
100
|
+
versionOverride: target.versionOverride,
|
|
101
|
+
};
|
|
148
102
|
}
|
|
149
|
-
function formatCommitSummary(lines,
|
|
150
|
-
if (isPropagatedOnly && propagatedFrom !== undefined) {
|
|
151
|
-
const dependencyNames = propagatedFrom.map((p) => p.packageName).join(', ');
|
|
103
|
+
function formatCommitSummary(lines, target, origin) {
|
|
104
|
+
if (origin.isPropagatedOnly && origin.propagatedFrom !== undefined) {
|
|
105
|
+
const dependencyNames = origin.propagatedFrom.map((p) => p.packageName).join(', ');
|
|
152
106
|
lines.push(dim(` Bumped via dependency: ${dependencyNames}`));
|
|
153
107
|
}
|
|
154
|
-
else if (
|
|
155
|
-
lines.push(dim(` Parsed ${
|
|
108
|
+
else if (target.parsedCommitCount !== undefined && target.parsedCommitCount > 0) {
|
|
109
|
+
lines.push(dim(` Parsed ${target.parsedCommitCount} typed commits`));
|
|
156
110
|
}
|
|
157
111
|
}
|
|
158
|
-
function formatBumpLabels(lines,
|
|
159
|
-
if (
|
|
160
|
-
lines.push(` Using version override: ${
|
|
112
|
+
function formatBumpLabels(lines, target, origin, indent) {
|
|
113
|
+
if (origin.versionOverride !== undefined) {
|
|
114
|
+
lines.push(` Using version override: ${origin.versionOverride}`);
|
|
161
115
|
}
|
|
162
|
-
else if (
|
|
163
|
-
lines.push(` Using bump override: ${
|
|
116
|
+
else if (target.bumpOverride !== undefined && !origin.isPropagatedOnly) {
|
|
117
|
+
lines.push(` Using bump override: ${target.bumpOverride}`);
|
|
164
118
|
}
|
|
165
|
-
if (
|
|
166
|
-
lines.push(dim(
|
|
119
|
+
if (target.releaseType !== undefined) {
|
|
120
|
+
lines.push(dim(`${indent}Bumping versions (${target.releaseType})...`));
|
|
167
121
|
}
|
|
168
|
-
else if (
|
|
169
|
-
lines.push(dim(
|
|
122
|
+
else if (origin.versionOverride !== undefined) {
|
|
123
|
+
lines.push(dim(`${indent}Bumping versions (version override)...`));
|
|
170
124
|
}
|
|
171
125
|
}
|
|
172
|
-
function formatVersionLine(lines,
|
|
173
|
-
const versions = `${
|
|
174
|
-
if (
|
|
175
|
-
lines.push(
|
|
176
|
-
}
|
|
177
|
-
else if (
|
|
178
|
-
const suffix = isPropagatedOnly ? formatPropagationSuffix(propagatedFrom) : '';
|
|
179
|
-
lines.push(
|
|
126
|
+
function formatVersionLine(lines, target, origin, indent, style) {
|
|
127
|
+
const versions = `${target.currentVersion} → ${bold(target.newVersion)}`;
|
|
128
|
+
if (origin.versionOverride !== undefined) {
|
|
129
|
+
lines.push(`${indent}${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${versions} (version override)`)}`);
|
|
130
|
+
}
|
|
131
|
+
else if (target.releaseType !== undefined) {
|
|
132
|
+
const suffix = origin.isPropagatedOnly ? formatPropagationSuffix(origin.propagatedFrom) : '';
|
|
133
|
+
lines.push(`${indent}${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${versions} (${target.releaseType}${suffix})`)}`);
|
|
180
134
|
}
|
|
181
135
|
}
|
|
182
136
|
function formatPropagationSuffix(propagatedFrom) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Git } from './git.js';
|
|
1
2
|
import type { DirectoryConfig, ResolvedProjectConfig, WorkspaceConfig } from './types.js';
|
|
2
3
|
export interface ResolvedTag {
|
|
3
4
|
tag: string;
|
|
@@ -41,5 +42,5 @@ type ResolveReleaseTagsArgs = {
|
|
|
41
42
|
* matching without needing `isPublishable` propagation; resolved tags default to
|
|
42
43
|
* `isPublishable: true`. Production callers should pass one of the named forms.
|
|
43
44
|
*/
|
|
44
|
-
export declare function resolveReleaseTags(args?: ResolveReleaseTagsArgs): ResolvedTag[];
|
|
45
|
+
export declare function resolveReleaseTags(args?: ResolveReleaseTagsArgs, git?: Git): ResolvedTag[];
|
|
45
46
|
export {};
|
|
@@ -1,25 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { GIT_OUTPUT_LIMIT_BYTES } from '@williamthorsen/nmr-core';
|
|
1
|
+
import { realGit } from "./git.js";
|
|
3
2
|
import { getTargetName } from "./resolveReleaseDeclaration.js";
|
|
4
3
|
import { PROJECT_TARGET_ID } from "./resolveReleaseTargets.js";
|
|
5
4
|
import { asUserError } from "./user-error-chaining.js";
|
|
6
5
|
const VERSION_PATTERN = /^v\d+\.\d+\.\d+/;
|
|
7
6
|
const SEMVER_SUFFIX_PATTERN = /^\d+\.\d+\.\d+/;
|
|
8
|
-
export function resolveReleaseTags(args) {
|
|
9
|
-
let
|
|
7
|
+
export function resolveReleaseTags(args, git = realGit) {
|
|
8
|
+
let tags;
|
|
10
9
|
try {
|
|
11
|
-
|
|
12
|
-
encoding: 'utf8',
|
|
13
|
-
maxBuffer: GIT_OUTPUT_LIMIT_BYTES,
|
|
14
|
-
});
|
|
10
|
+
tags = git.listTagsAtHead();
|
|
15
11
|
}
|
|
16
12
|
catch (error) {
|
|
17
13
|
throw asUserError(error);
|
|
18
14
|
}
|
|
19
|
-
const tags = output
|
|
20
|
-
.split('\n')
|
|
21
|
-
.map((line) => line.trim())
|
|
22
|
-
.filter((line) => line.length > 0);
|
|
23
15
|
if (args === undefined) {
|
|
24
16
|
return resolveSinglePackageTags(tags);
|
|
25
17
|
}
|
|
@@ -2,11 +2,11 @@ import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
|
2
2
|
/**
|
|
3
3
|
* Runs the CLI `show-tag-prefixes` command and returns its exit code.
|
|
4
4
|
*
|
|
5
|
-
* Prints
|
|
6
|
-
*
|
|
7
|
-
* derivation failure or collision and `0` otherwise; undeclared
|
|
8
|
-
* exists and either fails to load or fails validation, the
|
|
9
|
-
* modes, because a default-config preview would understate
|
|
10
|
-
* absent, the command previews against derived defaults.
|
|
5
|
+
* Prints one block per target, with its derived prefix and declared legacy identities and their tag counts, then the
|
|
6
|
+
* declared retired packages, the candidate-shaped tags outside the known set, and a footer on creating the first tag
|
|
7
|
+
* when a target does not have any. Returns `1` on any derivation failure or collision and `0` otherwise; undeclared
|
|
8
|
+
* candidates do not affect the exit code. When a config exists and either fails to load or fails validation, the
|
|
9
|
+
* command reports it to stderr and returns `1` in both repo modes, because a default-config preview would understate
|
|
10
|
+
* the declared legacy prefixes. When the default config is absent, the command previews against derived defaults.
|
|
11
11
|
*/
|
|
12
12
|
export declare function showTagPrefixesCommand(styles: StreamStyles, configPath?: string): Promise<number>;
|
|
@@ -2,7 +2,7 @@ import { formatStatusLine, isAnticipatedFailure, reportError, } from '@williamth
|
|
|
2
2
|
import { BIN } from "./bin.js";
|
|
3
3
|
import { detectRepoType } from "./init/detectRepoType.js";
|
|
4
4
|
import { loadValidatedConfig, reportConfigProblem } from "./loadValidatedConfig.js";
|
|
5
|
-
import { previewTagPrefixes } from "./previewTagPrefixes.js";
|
|
5
|
+
import { previewTagPrefixes, } from "./previewTagPrefixes.js";
|
|
6
6
|
export async function showTagPrefixesCommand(styles, configPath) {
|
|
7
7
|
const result = await loadValidatedConfig(configPath);
|
|
8
8
|
if (result.status === 'invalid') {
|
|
@@ -27,59 +27,114 @@ export async function showTagPrefixesCommand(styles, configPath) {
|
|
|
27
27
|
process.stdout.write(renderMonorepo(preview, styles.stdout));
|
|
28
28
|
return computeExitCode(preview);
|
|
29
29
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
30
|
+
const LABEL_WIDTH = 'legacy identity'.length;
|
|
31
|
+
const FIRST_TAG_FOOTER = 'A workspace marked "no derived tags yet" has not been released under its derived prefix. `release-kit tag` creates its first tag from the tags file that `release-kit prepare` writes.';
|
|
32
|
+
function computeExitCode(preview) {
|
|
33
|
+
const hasDerivationFailure = preview.workspaces.some((row) => row.derivedPrefix === null);
|
|
34
|
+
const hasCollision = preview.collisions.length > 0;
|
|
35
|
+
return hasDerivationFailure || hasCollision ? 1 : 0;
|
|
36
|
+
}
|
|
37
|
+
function formatPrefixColumns(columns, prefix, tagCount, note) {
|
|
38
|
+
const cells = `${prefix.padEnd(columns.prefixWidth)} ${String(tagCount).padStart(columns.countDigits)} tags`;
|
|
39
|
+
return note === undefined ? cells : `${cells} ${note}`;
|
|
40
|
+
}
|
|
41
|
+
function formatRow(label, body) {
|
|
42
|
+
return ` ${label.padEnd(LABEL_WIDTH)} ${body}`;
|
|
43
|
+
}
|
|
44
|
+
function measureColumns(preview) {
|
|
45
|
+
const entries = preview.retiredPackages.map((entry) => ({
|
|
46
|
+
prefix: entry.tagPrefix,
|
|
47
|
+
tagCount: entry.tagCount,
|
|
48
|
+
}));
|
|
49
|
+
for (const row of preview.workspaces) {
|
|
50
|
+
if (row.derivedPrefix !== null)
|
|
51
|
+
entries.push({ prefix: row.derivedPrefix, tagCount: row.derivedTagCount });
|
|
52
|
+
entries.push(...row.legacyEntries);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
prefixWidth: Math.max(0, ...entries.map((entry) => entry.prefix.length)),
|
|
56
|
+
countDigits: Math.max(1, ...entries.map((entry) => String(entry.tagCount).length)),
|
|
57
|
+
};
|
|
37
58
|
}
|
|
38
59
|
function renderMonorepo(preview, style) {
|
|
39
|
-
const
|
|
60
|
+
const columns = measureColumns(preview);
|
|
61
|
+
const lines = ['Workspace tag prefixes'];
|
|
40
62
|
for (const row of preview.workspaces) {
|
|
41
|
-
lines.push(...
|
|
63
|
+
lines.push('', ...renderTargetBlock(row, preview.collisions, columns, style));
|
|
42
64
|
}
|
|
43
|
-
if (preview.
|
|
44
|
-
lines.push('',
|
|
65
|
+
if (preview.retiredPackages.length > 0) {
|
|
66
|
+
lines.push('', 'Retired packages', '', ...renderRetiredLines(preview.retiredPackages, columns, style));
|
|
45
67
|
}
|
|
46
|
-
|
|
47
|
-
|
|
68
|
+
lines.push('', 'Undeclared tag prefixes', '', ...renderUndeclaredSection(preview.undeclaredCandidates, style));
|
|
69
|
+
if (preview.workspaces.some((row) => row.derivedPrefix !== null && row.derivedTagCount === 0)) {
|
|
70
|
+
lines.push('', formatStatusLine(style, 'info', FIRST_TAG_FOOTER));
|
|
48
71
|
}
|
|
49
72
|
lines.push('');
|
|
50
73
|
return lines.join('\n');
|
|
51
74
|
}
|
|
52
|
-
function
|
|
53
|
-
|
|
75
|
+
function renderRetiredLines(retiredPackages, columns, style) {
|
|
76
|
+
return retiredPackages.map((entry) => {
|
|
77
|
+
const identity = entry.successor === undefined ? entry.name : `${entry.name}, successor: ${entry.successor}`;
|
|
78
|
+
const status = entry.tagCount > 0 ? 'passed' : 'warning';
|
|
79
|
+
return ` ${formatStatusLine(style, status, formatPrefixColumns(columns, entry.tagPrefix, entry.tagCount, identity))}`;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function renderSinglePackage() {
|
|
83
|
+
const lines = [
|
|
84
|
+
'Workspace tag prefixes',
|
|
85
|
+
'',
|
|
86
|
+
'. (single-package mode)',
|
|
87
|
+
formatRow('derived prefix', 'v'),
|
|
88
|
+
'',
|
|
89
|
+
];
|
|
90
|
+
return lines.join('\n');
|
|
91
|
+
}
|
|
92
|
+
function renderSuggestedSnippet(candidates) {
|
|
93
|
+
const entries = candidates
|
|
94
|
+
.map((candidate) => ` { dir: '${candidate.suggestedDir}', legacyIdentities: [{ name: 'TODO-fill-in-legacy-npm-name', tagPrefix: '${candidate.prefix}' }] },`)
|
|
95
|
+
.join('\n');
|
|
96
|
+
return ` workspaces: [\n${entries}\n ],`;
|
|
97
|
+
}
|
|
98
|
+
function renderTargetBlock(row, collisions, columns, style) {
|
|
99
|
+
const lines = [row.name === null ? row.workspacePath : `${row.workspacePath} (${row.name})`];
|
|
54
100
|
if (row.derivedPrefix === null) {
|
|
55
101
|
const failure = formatStatusLine(style, 'failed', `derivation failed: ${row.derivationError ?? 'unknown error'}`);
|
|
56
|
-
lines.push(
|
|
102
|
+
lines.push(formatRow('derived prefix', failure));
|
|
57
103
|
return lines;
|
|
58
104
|
}
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
105
|
+
const collidingPaths = collisions
|
|
106
|
+
.filter((collision) => collision.tagPrefix === row.derivedPrefix)
|
|
107
|
+
.flatMap((collision) => collision.workspacePaths)
|
|
108
|
+
.filter((path) => path !== row.workspacePath);
|
|
109
|
+
let derivedStatus = 'passed';
|
|
110
|
+
let derivedNote;
|
|
111
|
+
if (collidingPaths.length > 0) {
|
|
112
|
+
derivedStatus = 'failed';
|
|
113
|
+
derivedNote = `collides with ${collidingPaths.join(', ')}`;
|
|
114
|
+
}
|
|
115
|
+
else if (row.derivedTagCount === 0) {
|
|
116
|
+
derivedStatus = 'warning';
|
|
117
|
+
derivedNote = 'no derived tags yet';
|
|
118
|
+
}
|
|
119
|
+
const derivedCells = formatPrefixColumns(columns, row.derivedPrefix, row.derivedTagCount, derivedNote);
|
|
120
|
+
lines.push(formatRow('derived prefix', formatStatusLine(style, derivedStatus, derivedCells)));
|
|
63
121
|
for (const entry of row.legacyEntries) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
else {
|
|
69
|
-
const taglessMessage = `recorded legacy prefix '${entry.prefix}' does not have any tags`;
|
|
70
|
-
lines.push(` ${formatStatusLine(style, 'warning', taglessMessage)}`);
|
|
71
|
-
}
|
|
122
|
+
const status = entry.tagCount > 0 ? 'passed' : 'warning';
|
|
123
|
+
const legacyCells = formatPrefixColumns(columns, entry.prefix, entry.tagCount, entry.name);
|
|
124
|
+
lines.push(formatRow('legacy identity', formatStatusLine(style, status, legacyCells)));
|
|
72
125
|
}
|
|
73
126
|
return lines;
|
|
74
127
|
}
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
128
|
+
function renderUndeclaredSection(candidates, style) {
|
|
129
|
+
if (candidates.length === 0)
|
|
130
|
+
return [' (none)'];
|
|
131
|
+
return [
|
|
132
|
+
...candidates.map((candidate) => ` ${formatStatusLine(style, 'warning', `'${candidate.prefix}': ${candidate.tagCount} tags (e.g., ${candidate.exampleTags.join(', ')})`)}`),
|
|
133
|
+
'',
|
|
134
|
+
'Suggested config snippet (adjust `dir` to match the workspace if the guess is wrong, and replace the `name` placeholder with the legacy npm name):',
|
|
135
|
+
'',
|
|
136
|
+
renderSuggestedSnippet(candidates),
|
|
137
|
+
'',
|
|
138
|
+
"If the suggested `dir` does not match the workspace, adjust it before pasting. Each legacy identity requires a `name`: Replace the `TODO-fill-in-legacy-npm-name` placeholder with the package's prior npm name.",
|
|
139
|
+
];
|
|
85
140
|
}
|
|
@@ -8,7 +8,6 @@ import { CONFIG_FILE_PATH } from "../loadConfig.js";
|
|
|
8
8
|
import { loadValidatedConfig, reportConfigProblem } from "../loadValidatedConfig.js";
|
|
9
9
|
import { hashPreset } from "./presets.js";
|
|
10
10
|
import { resolveLabels } from "./resolveLabels.js";
|
|
11
|
-
import { checkRetiredSyncLabelsConfig } from "./retiredConfig.js";
|
|
12
11
|
export const LABELS_OUTPUT_PATH = '.github/labels.yaml';
|
|
13
12
|
export function formatLabelsYaml(labels, presetHashes, configFilePath = CONFIG_FILE_PATH) {
|
|
14
13
|
const headerLines = ['# Generated by release-kit sync-labels — do not edit.', `# Source: ${configFilePath}`];
|
|
@@ -43,9 +42,6 @@ export async function loadRepoLabelsConfig(stderrStyle, configPath) {
|
|
|
43
42
|
return result.config.repoLabels;
|
|
44
43
|
}
|
|
45
44
|
export async function generateCommand({ shouldCheck = false, configPath, styles }) {
|
|
46
|
-
if (checkRetiredSyncLabelsConfig(configPath)) {
|
|
47
|
-
return 1;
|
|
48
|
-
}
|
|
49
45
|
const repoLabels = await loadRepoLabelsConfig(styles.stderr, configPath);
|
|
50
46
|
if (repoLabels === undefined) {
|
|
51
47
|
return 1;
|
|
@@ -5,14 +5,10 @@ import { describeEmptyWorkspace, discoverWorkspaces } from "../discoverWorkspace
|
|
|
5
5
|
import { CONFIG_FILE_PATH } from "../loadConfig.js";
|
|
6
6
|
import { loadValidatedConfig, reportConfigProblem } from "../loadValidatedConfig.js";
|
|
7
7
|
import { generateCommand, LABELS_OUTPUT_PATH } from "./generateCommand.js";
|
|
8
|
-
import { checkRetiredSyncLabelsConfig } from "./retiredConfig.js";
|
|
9
8
|
import { buildScopeLabels, readSyncLabelsWorkflowTemplate, renderRepoLabelsBlock, renderRepoLabelsConfigScript, } from "./templates.js";
|
|
10
9
|
const WORKFLOW_PATH = '.github/workflows/sync-labels.yaml';
|
|
11
10
|
export async function syncLabelsInitCommand({ configPath, isDryRun, shouldForce, styles, }) {
|
|
12
11
|
const configFilePath = configPath ?? CONFIG_FILE_PATH;
|
|
13
|
-
if (checkRetiredSyncLabelsConfig(configPath)) {
|
|
14
|
-
return 1;
|
|
15
|
-
}
|
|
16
12
|
if (isDryRun) {
|
|
17
13
|
console.info('[dry-run mode]');
|
|
18
14
|
}
|
|
@@ -3,7 +3,6 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { GIT_OUTPUT_LIMIT_BYTES, reportError } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
5
5
|
import { BIN } from "../bin.js";
|
|
6
|
-
import { checkRetiredSyncLabelsConfig } from "./retiredConfig.js";
|
|
7
6
|
const WORKFLOW_FILE = '.github/workflows/sync-labels.yaml';
|
|
8
7
|
function checkGhAvailable() {
|
|
9
8
|
try {
|
|
@@ -15,9 +14,6 @@ function checkGhAvailable() {
|
|
|
15
14
|
}
|
|
16
15
|
}
|
|
17
16
|
export function syncLabelsCommand() {
|
|
18
|
-
if (checkRetiredSyncLabelsConfig()) {
|
|
19
|
-
return 1;
|
|
20
|
-
}
|
|
21
17
|
if (!checkGhAvailable()) {
|
|
22
18
|
reportError(BIN, 'The `gh` CLI is not installed or not in PATH. Install it from https://cli.github.com/');
|
|
23
19
|
return 1;
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -355,8 +355,9 @@ export interface WorkTypeConfig {
|
|
|
355
355
|
excludedFromChangelog?: boolean | undefined;
|
|
356
356
|
}
|
|
357
357
|
/**
|
|
358
|
-
* Defines which commit types trigger major or minor version bumps.
|
|
359
|
-
* Any recognized commit type not listed defaults to a
|
|
358
|
+
* Defines which commit types trigger major or minor version bumps, and which trigger none.
|
|
359
|
+
* Any recognized commit type not listed in `major`, `minor`, or `none` (the default list when omitted) defaults to a
|
|
360
|
+
* patch bump.
|
|
360
361
|
* The sentinel `'!'` in `major` means "any breaking commit triggers a major bump".
|
|
361
362
|
*/
|
|
362
363
|
export interface VersionPatterns {
|
|
@@ -364,6 +365,11 @@ export interface VersionPatterns {
|
|
|
364
365
|
major: string[];
|
|
365
366
|
/** Commit types that trigger a minor bump. */
|
|
366
367
|
minor: string[];
|
|
368
|
+
/**
|
|
369
|
+
* Commit types that do not trigger a bump unless breaking. Defaults to `DEFAULT_VERSION_PATTERNS.none` when omitted;
|
|
370
|
+
* `[]` gives every unlisted type a patch bump.
|
|
371
|
+
*/
|
|
372
|
+
none?: string[] | undefined;
|
|
367
373
|
}
|
|
368
374
|
/** A raw commit from the git log. */
|
|
369
375
|
export interface Commit {
|
package/docs/configuration.md
CHANGED
|
@@ -22,7 +22,7 @@ The two paths differ in what an absent file means. An absent default path means
|
|
|
22
22
|
| `workspaces` | `WorkspaceOverride[]` | Override or exclude discovered workspaces (matched by `dir`) |
|
|
23
23
|
| `devExportConditions` | `string[]` | Export conditions that resolve only inside the repo, such as one that points at unpublished source; the [tarball check](releasing.md#packages-whose-tarball-would-lack-a-manifest-file) does not require their targets in the tarball. `source` is exempt without being listed |
|
|
24
24
|
| `formatCommand` | `string` | Shell command to run after changelog generation; modified file paths are appended as arguments |
|
|
25
|
-
| `versionPatterns` | `VersionPatterns` | Rules for which commit types trigger major
|
|
25
|
+
| `versionPatterns` | `VersionPatterns` | Rules for which commit types trigger major, minor, or no bumps |
|
|
26
26
|
| `scopeAliases` | `Record<string, string>` | Maps shorthand scope names to canonical names in commit titles and in change-record entry `scopes`, which [route items to workspaces](changelogs.md#routing-to-workspaces). A renamed workspace directory or [release target](#releases) needs an entry from its old name to its new one |
|
|
27
27
|
| `workTypes` | `Record<string, WorkTypeConfig>` | Work type definitions, merged with defaults by key |
|
|
28
28
|
| `breakingPolicies` | `Record<string, 'forbidden' \| 'optional'>` | Per-type `!`-policy lookup. Defaults to `DEFAULT_BREAKING_POLICIES`. Replaces the default entirely when provided. Set to `{}` to disable enforcement |
|
|
@@ -143,7 +143,7 @@ Validation rules:
|
|
|
143
143
|
- Full-tuple `(name, tagPrefix)` duplicates within `retiredPackages` are rejected.
|
|
144
144
|
- Two entries sharing the same `tagPrefix` but different `name`s are accepted: Such a pair documents a package renamed within the repo before being retired.
|
|
145
145
|
|
|
146
|
-
`show-tag-prefixes`
|
|
146
|
+
`show-tag-prefixes` lists each retired entry under "Retired packages", with its tag count and successor.
|
|
147
147
|
|
|
148
148
|
## Tag prefix collisions
|
|
149
149
|
|
|
@@ -160,26 +160,35 @@ The rule is enforced at config load; the resulting error identifies both collidi
|
|
|
160
160
|
|
|
161
161
|
## `release-kit show-tag-prefixes`
|
|
162
162
|
|
|
163
|
-
Prints
|
|
163
|
+
Prints one block per workspace and directory target, titled with its path and its package name or `releases` key, with its derived tag prefix and each declared legacy identity, their tag counts, and a status. Prefixes and counts are aligned in columns across the whole output. A "Retired packages" section follows when the config declares `retiredPackages`. An "Undeclared tag prefixes" section always follows: It reports any release-shaped tag prefix in the repo that is neither derived nor declared, along with a copy-pasteable `workspaces: [...]` config snippet, or `(none)`. The snippet uses a `TODO-fill-in-legacy-npm-name` placeholder for each identity's `name`; replace it with the package's prior npm name before pasting. When a derived prefix does not have any tags yet, a closing note explains that `release-kit tag` creates the first tag from the tags file that `release-kit prepare` writes.
|
|
164
164
|
|
|
165
165
|
Exits `0` when every workspace derives a prefix and the workspaces' prefixes don't collide; exits `1` on any derivation failure or collision. Undeclared candidates do not affect the exit code; the `legacy tag prefixes are declared` readyup check reports them as a warning.
|
|
166
166
|
|
|
167
167
|
When a config file exists and either fails to load or fails schema validation, the command reports the failure on stderr and exits `1` without printing a preview, because a config that cannot be read leaves the declared-versus-undeclared verdict wrong in the one dimension on which the command exists to report. That holds on the default path and under `--config`, and in single-package mode as well as monorepo mode. An absent default config is a supported state and still previews against derived defaults.
|
|
168
168
|
|
|
169
|
-
In single-package mode, prints a single
|
|
169
|
+
In single-package mode, prints a single block for `.` with the derived prefix `v`; legacy entries and undeclared-candidate scanning are not applicable.
|
|
170
170
|
|
|
171
171
|
## `VersionPatterns`
|
|
172
172
|
|
|
173
|
-
Defines which work types trigger major or minor bumps. `prepare` applies it to each changelog item of the unreleased window and takes the highest level. An item of a type not listed bumps patch.
|
|
173
|
+
Defines which work types trigger major or minor bumps, and which trigger none. `prepare` applies it to each changelog item of the unreleased window and takes the highest level. An item of a type that `none` lists doesn't bump, and an item of any other type not listed bumps patch. A window whose items don't call for any bump doesn't release unless `--force` is passed, and its items wait in the unreleased window for the next release.
|
|
174
174
|
|
|
175
175
|
```typescript
|
|
176
176
|
interface VersionPatterns {
|
|
177
177
|
major: string[]; // Patterns triggering a major bump ('!' = any breaking change)
|
|
178
178
|
minor: string[]; // Commit types triggering a minor bump
|
|
179
|
+
none?: string[]; // Commit types triggering no bump unless breaking
|
|
179
180
|
}
|
|
180
181
|
```
|
|
181
182
|
|
|
182
|
-
Default: `{ major: ['!'], minor: ['feat'] }`
|
|
183
|
+
Default: `{ major: ['!'], minor: ['feat'], none: ['ai', 'ci', 'docs', 'tests', 'tooling'] }`
|
|
184
|
+
|
|
185
|
+
The default `none` types don't change the code that a consumer runs. `refactor`, `internal`, and `deps` bump patch, because they change shipped code or installed dependencies. A `docs` change to a file that the package publishes reaches the registry with the next release.
|
|
186
|
+
|
|
187
|
+
A config that sets `versionPatterns` replaces `major` and `minor`, but an omitted `none` keeps the default list. To give every type not listed in `major` or `minor` a patch bump, as release-kit did before `none` existed, set `none: []`:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
versionPatterns: { major: ['!'], minor: ['feat'], none: [] },
|
|
191
|
+
```
|
|
183
192
|
|
|
184
193
|
## Output style
|
|
185
194
|
|
package/docs/labels.md
CHANGED
|
@@ -10,8 +10,6 @@ Manages GitHub label definitions via the `repoLabels` block of `.config/release-
|
|
|
10
10
|
|
|
11
11
|
`init` and `generate` accept `--config <path>` to read a config elsewhere; `sync` reads none and rejects the flag. Under `--config`, `init` reads the named file and prints the block for manual paste, and fails when that file does not exist rather than scaffolding one, so its write target stays `.config/release-kit.config.ts`. See [Config file location](configuration.md#config-file-location).
|
|
12
12
|
|
|
13
|
-
Every `sync-labels` subcommand refuses to run while the retired `.config/sync-labels.config.ts` is present, so that custom labels cannot be silently dropped mid-migration, and the refusal names what to move and where.
|
|
14
|
-
|
|
15
13
|
### Label configuration
|
|
16
14
|
|
|
17
15
|
```typescript
|
|
@@ -40,6 +38,8 @@ The bundled file's `confidence:` family grades how advisable a recommendation is
|
|
|
40
38
|
|
|
41
39
|
The `urgency:` family states how soon an item needs attention, apart from how valuable it is, which the `value:` family states. Its labels replace the earlier `priority:` family and name the old labels in `aliases`, so a repository that has `priority:` labels has them renamed in place by the first sync after it regenerates `.github/labels.yaml` (see [Renaming a label](#renaming-a-label)).
|
|
42
40
|
|
|
41
|
+
The `effort:` family states an item's size in points, apart from its urgency and value: one label for each value of the scale 1, 2, 3, 5, 8, and 13, from `effort:1` to `effort:13`.
|
|
42
|
+
|
|
43
43
|
The `status:` family marks where an item stands in its work: `status:in-progress` once work on it has started, `status:blocked` while it waits on another item, and `status:on-hold` while it is deliberately paused.
|
|
44
44
|
|
|
45
45
|
Names match case-insensitively, as GitHub matches them: `Bug` in the `labels` record replaces a preset's `bug`, and `Bug: null` removes it. A label takes the spelling of the last layer to write it.
|