@williamthorsen/release-kit 16.1.0 → 16.1.2
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 +45 -0
- package/.readyup/kits/default.js +2 -2
- package/.readyup/kits/npm-auto-publish.js +1 -1
- package/.readyup/manifest.json +6 -6
- package/CHANGELOG.md +16 -0
- package/README.md +3 -5
- package/bin/release-kit.js +7 -3
- package/dist/esm/assertCleanWorkingTree.js +14 -7
- package/dist/esm/assertReleasesOnNpm.js +11 -2
- package/dist/esm/assertTaggedBaseline.js +2 -1
- package/dist/esm/assertTarballsComplete.js +15 -3
- package/dist/esm/buildChangelogEntries.js +2 -2
- package/dist/esm/buildDependencyGraph.js +5 -4
- package/dist/esm/bumpVersion.js +3 -2
- package/dist/esm/changelogOverrides.js +3 -2
- package/dist/esm/cli.js +11 -0
- package/dist/esm/commitCommand.js +11 -5
- package/dist/esm/compareVersions.js +2 -1
- package/dist/esm/createGithubRelease.js +7 -1
- package/dist/esm/createGithubReleaseCommand.js +3 -4
- package/dist/esm/createTags.js +6 -4
- package/dist/esm/deleteFileIfExists.js +2 -1
- package/dist/esm/deriveDirectoryConfig.js +7 -6
- package/dist/esm/deriveWorkspaceConfig.js +4 -3
- package/dist/esm/enterRepoRoot.js +2 -1
- package/dist/esm/enumerateReleaseWindows.js +2 -2
- package/dist/esm/init/initCommand.js +10 -1
- package/dist/esm/init/updateTemplatesCommand.js +10 -1
- package/dist/esm/loadConfig.js +6 -5
- package/dist/esm/mergeConfig.js +5 -4
- package/dist/esm/monorepoConfigAssertions.js +6 -5
- package/dist/esm/packFromCleanCheckout.js +12 -3
- package/dist/esm/planVersionBump.js +6 -5
- package/dist/esm/prepareCommand.js +12 -22
- package/dist/esm/prepareComposite.js +2 -1
- package/dist/esm/prepareRunContext.js +2 -2
- package/dist/esm/previewTagPrefixes.js +2 -2
- package/dist/esm/publish.js +7 -1
- package/dist/esm/publishCommand.js +4 -18
- package/dist/esm/pushCommand.js +5 -12
- package/dist/esm/pushRelease.js +18 -6
- package/dist/esm/releaseFiles.js +2 -2
- package/dist/esm/releasePlan.js +2 -2
- package/dist/esm/resolveCommandTags.d.ts +4 -4
- package/dist/esm/resolveCommandTags.js +3 -5
- package/dist/esm/resolveReleaseDeclaration.js +9 -8
- package/dist/esm/resolveReleaseTags.js +11 -4
- package/dist/esm/resolveReleaseTargets.js +2 -1
- package/dist/esm/runCli.d.ts +5 -0
- package/dist/esm/runCli.js +224 -0
- package/dist/esm/showTagPrefixesCommand.js +5 -3
- package/dist/esm/sync-labels/generateCommand.js +4 -1
- package/dist/esm/sync-labels/initCommand.js +7 -1
- package/dist/esm/sync-labels/presets.js +7 -7
- package/dist/esm/sync-labels/resolveLabels.js +4 -3
- package/dist/esm/sync-labels/templates.js +2 -2
- package/dist/esm/tagCommand.js +2 -9
- package/dist/esm/user-error-chaining.d.ts +13 -0
- package/dist/esm/user-error-chaining.js +11 -0
- package/dist/esm/validateOverridesCommand.js +7 -1
- package/package.json +6 -6
- package/dist/esm/bin/release-kit.js +0 -240
- /package/dist/esm/{bin/release-kit.d.ts → cli.d.ts} +0 -0
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
2
|
-
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
2
|
import { deriveWorkspaceConfig } from "./deriveWorkspaceConfig.js";
|
|
4
3
|
import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
5
4
|
import { formatExcludedSkip } from "./formatExcludedSkip.js";
|
|
6
5
|
import { readRootPackageVersion } from "./loadConfig.js";
|
|
7
6
|
import { mergeMonorepoConfig } from "./mergeConfig.js";
|
|
8
7
|
import { resolveReleaseTags } from "./resolveReleaseTags.js";
|
|
8
|
+
import { prefixUserError } from "./user-error-chaining.js";
|
|
9
9
|
export function resolveCommandTags(tags, userConfig) {
|
|
10
10
|
let workspace;
|
|
11
11
|
try {
|
|
12
12
|
workspace = discoverWorkspaces();
|
|
13
13
|
}
|
|
14
14
|
catch (error) {
|
|
15
|
-
|
|
16
|
-
process.exit(1);
|
|
15
|
+
throw prefixUserError('Failed to discover workspaces', error);
|
|
17
16
|
}
|
|
18
17
|
if (workspace.kind === 'empty') {
|
|
19
18
|
reportError(`No workspace package to tag. ${describeEmptyWorkspace(workspace)}`);
|
|
@@ -38,8 +37,7 @@ export function resolveCommandTags(tags, userConfig) {
|
|
|
38
37
|
}
|
|
39
38
|
}
|
|
40
39
|
catch (error) {
|
|
41
|
-
|
|
42
|
-
process.exit(1);
|
|
40
|
+
throw prefixUserError('Failed to resolve workspaces', error);
|
|
43
41
|
}
|
|
44
42
|
let resolvedTags;
|
|
45
43
|
if (workspaces !== undefined) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
2
3
|
export function getTargetName(workspace) {
|
|
3
4
|
return workspace.targetName ?? workspace.dir;
|
|
4
5
|
}
|
|
@@ -21,13 +22,13 @@ function applyLegacyIdentities(workspace, legacyIdentities) {
|
|
|
21
22
|
function claimPath(entryByPath, name, claimedPath, kind) {
|
|
22
23
|
const claimingEntryName = entryByPath.get(claimedPath);
|
|
23
24
|
if (claimingEntryName !== undefined) {
|
|
24
|
-
throw new
|
|
25
|
+
throw new UserError(`releases.${name}: ${kind} '${claimedPath}' is already declared by releases.${claimingEntryName}; declare each ${kind} once`);
|
|
25
26
|
}
|
|
26
27
|
entryByPath.set(claimedPath, name);
|
|
27
28
|
}
|
|
28
29
|
function declareDirectory(name, entry, normalizedPath) {
|
|
29
30
|
if (path.posix.isAbsolute(normalizedPath) || normalizedPath === '..' || normalizedPath.startsWith('../')) {
|
|
30
|
-
throw new
|
|
31
|
+
throw new UserError(`releases.${name}: path '${entry.path}' is outside the repo; declare a repo-relative directory`);
|
|
31
32
|
}
|
|
32
33
|
const isRoot = normalizedPath === '.' || normalizedPath === '';
|
|
33
34
|
return {
|
|
@@ -52,12 +53,12 @@ function resolveCompositeMembers(composite, retainedWorkspaces, excludedWorkspac
|
|
|
52
53
|
}
|
|
53
54
|
const label = `releases.${composite.name}.includes: '${memberName}'`;
|
|
54
55
|
if (memberName === composite.name) {
|
|
55
|
-
throw new
|
|
56
|
+
throw new UserError(`${label} is the composite itself; a composite includes package and directory targets only`);
|
|
56
57
|
}
|
|
57
58
|
if (excludedWorkspaces.some((workspace) => workspace.dir === memberName)) {
|
|
58
|
-
throw new
|
|
59
|
+
throw new UserError(`${label} is excluded, so the composite cannot include it`);
|
|
59
60
|
}
|
|
60
|
-
throw new
|
|
61
|
+
throw new UserError(`${label} is not a release target. Known targets: ${membersByName.keys().toArray().join(', ')}`);
|
|
61
62
|
});
|
|
62
63
|
}
|
|
63
64
|
function resolveLegacyBlocks(workspaces, overrides, project) {
|
|
@@ -93,7 +94,7 @@ function resolveReleasesBlock(workspaces, releases) {
|
|
|
93
94
|
for (const [name, entry] of Object.entries(releases)) {
|
|
94
95
|
if (entry !== false && 'includes' in entry) {
|
|
95
96
|
if (composite !== undefined) {
|
|
96
|
-
throw new
|
|
97
|
+
throw new UserError(`releases declares more than one composite ('${composite.name}', '${name}'); ` +
|
|
97
98
|
'a composite is versioned by the root package.json, which only one composite can own');
|
|
98
99
|
}
|
|
99
100
|
composite = {
|
|
@@ -106,7 +107,7 @@ function resolveReleasesBlock(workspaces, releases) {
|
|
|
106
107
|
if (entry === false) {
|
|
107
108
|
const workspace = workspaceByDir.get(name);
|
|
108
109
|
if (workspace === undefined) {
|
|
109
|
-
throw new
|
|
110
|
+
throw new UserError(`releases.${name}: false excludes a discovered workspace by its directory name, and no workspace is named '${name}'. ` +
|
|
110
111
|
`Known workspaces: ${workspaceByDir.keys().toArray().join(', ')}`);
|
|
111
112
|
}
|
|
112
113
|
claimPath(entryByPath, name, workspace.workspacePath, 'workspace');
|
|
@@ -122,7 +123,7 @@ function resolveReleasesBlock(workspaces, releases) {
|
|
|
122
123
|
continue;
|
|
123
124
|
}
|
|
124
125
|
if (entry.tagPrefix !== undefined) {
|
|
125
|
-
throw new
|
|
126
|
+
throw new UserError(`releases.${name}: tagPrefix applies only to a directory target, and '${workspace.workspacePath}' is a workspace, ` +
|
|
126
127
|
"whose tag prefix derives from its package name; declare the workspace's earlier prefixes in legacyIdentities");
|
|
127
128
|
}
|
|
128
129
|
claimPath(entryByPath, name, workspace.workspacePath, 'workspace');
|
|
@@ -2,13 +2,20 @@ import { execFileSync } from 'node:child_process';
|
|
|
2
2
|
import { GIT_OUTPUT_LIMIT_BYTES } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { getTargetName } from "./resolveReleaseDeclaration.js";
|
|
4
4
|
import { PROJECT_TARGET_ID } from "./resolveReleaseTargets.js";
|
|
5
|
+
import { asUserError } from "./user-error-chaining.js";
|
|
5
6
|
const VERSION_PATTERN = /^v\d+\.\d+\.\d+/;
|
|
6
7
|
const SEMVER_SUFFIX_PATTERN = /^\d+\.\d+\.\d+/;
|
|
7
8
|
export function resolveReleaseTags(args) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
let output;
|
|
10
|
+
try {
|
|
11
|
+
output = execFileSync('git', ['tag', '--points-at', 'HEAD'], {
|
|
12
|
+
encoding: 'utf8',
|
|
13
|
+
maxBuffer: GIT_OUTPUT_LIMIT_BYTES,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
throw asUserError(error);
|
|
18
|
+
}
|
|
12
19
|
const tags = output
|
|
13
20
|
.split('\n')
|
|
14
21
|
.map((line) => line.trim())
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
2
3
|
import { isPublishableManifest } from "./isPublishableManifest.js";
|
|
3
4
|
import { readRootPackageVersion } from "./loadConfig.js";
|
|
4
5
|
import { mergeMonorepoConfig, mergeSinglePackageConfig } from "./mergeConfig.js";
|
|
@@ -35,7 +36,7 @@ function assertUniqueTargetIds(targets) {
|
|
|
35
36
|
}
|
|
36
37
|
for (const [id, paths] of pathsById) {
|
|
37
38
|
if (paths.length > 1) {
|
|
38
|
-
throw new
|
|
39
|
+
throw new UserError(`Duplicate release target id '${id}' for workspaces: ${paths.join(', ')}`);
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { parseArgsOrExit, reportError, resolveSelfVersion } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { commitCommand } from "./commitCommand.js";
|
|
3
|
+
import { configFlagSchema } from "./configFlagSchema.js";
|
|
4
|
+
import { createGithubReleaseCommand } from "./createGithubReleaseCommand.js";
|
|
5
|
+
import { enterRepoRoot } from "./enterRepoRoot.js";
|
|
6
|
+
import { showInitHelp, showUpdateTemplatesHelp } from "./help/initHelp.js";
|
|
7
|
+
import { showOverridesHelp, showOverridesValidateHelp } from "./help/overridesHelp.js";
|
|
8
|
+
import { showPrepareHelp } from "./help/prepareHelp.js";
|
|
9
|
+
import { showCommitHelp, showCreateGithubReleaseHelp, showPublishHelp, showPushHelp, showShowTagPrefixesHelp, showTagHelp, } from "./help/releaseStepsHelp.js";
|
|
10
|
+
import { showSyncLabelsGenerateHelp, showSyncLabelsHelp, showSyncLabelsInitHelp, showSyncLabelsSyncHelp, } from "./help/syncLabelsHelp.js";
|
|
11
|
+
import { showUsage } from "./help/usage.js";
|
|
12
|
+
import { initCommand } from "./init/initCommand.js";
|
|
13
|
+
import { updateTemplatesCommand } from "./init/updateTemplatesCommand.js";
|
|
14
|
+
import { prepareCommand } from "./prepareCommand.js";
|
|
15
|
+
import { publishCommand } from "./publishCommand.js";
|
|
16
|
+
import { pushCommand } from "./pushCommand.js";
|
|
17
|
+
import { resolveConfigFlag } from "./resolveConfigFlag.js";
|
|
18
|
+
import { resolveStylesOrExit } from "./resolveStylesOrExit.js";
|
|
19
|
+
import { showTagPrefixesCommand } from "./showTagPrefixesCommand.js";
|
|
20
|
+
import { generateCommand } from "./sync-labels/generateCommand.js";
|
|
21
|
+
import { syncLabelsInitCommand } from "./sync-labels/initCommand.js";
|
|
22
|
+
import { syncLabelsCommand } from "./sync-labels/syncCommand.js";
|
|
23
|
+
import { tagCommand } from "./tagCommand.js";
|
|
24
|
+
import { validateOverridesCommand } from "./validateOverridesCommand.js";
|
|
25
|
+
const VERSION = resolveSelfVersion(import.meta.url);
|
|
26
|
+
export async function runCli(args) {
|
|
27
|
+
const command = args[0];
|
|
28
|
+
if (command === '--version' || command === '-V') {
|
|
29
|
+
console.info(VERSION);
|
|
30
|
+
process.exit(0);
|
|
31
|
+
}
|
|
32
|
+
if (command === undefined || ['--help', '-h'].includes(command)) {
|
|
33
|
+
showUsage();
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
const flags = args.slice(1);
|
|
37
|
+
const styles = resolveStylesOrExit({
|
|
38
|
+
env: process.env,
|
|
39
|
+
stderrIsTty: process.stderr.isTTY,
|
|
40
|
+
stdoutIsTty: process.stdout.isTTY,
|
|
41
|
+
});
|
|
42
|
+
if (command === 'prepare') {
|
|
43
|
+
if (flags.some((f) => f === '--help' || f === '-h')) {
|
|
44
|
+
showPrepareHelp();
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
const { invocationDir } = enterRepoRoot();
|
|
48
|
+
await prepareCommand(flags, styles, invocationDir);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
if (command === 'commit') {
|
|
52
|
+
if (flags.some((f) => f === '--help' || f === '-h')) {
|
|
53
|
+
showCommitHelp();
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
enterRepoRoot();
|
|
57
|
+
commitCommand(flags);
|
|
58
|
+
process.exit(0);
|
|
59
|
+
}
|
|
60
|
+
if (command === 'tag') {
|
|
61
|
+
if (flags.some((f) => f === '--help' || f === '-h')) {
|
|
62
|
+
showTagHelp();
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
enterRepoRoot();
|
|
66
|
+
tagCommand(flags, styles);
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
if (command === 'push') {
|
|
70
|
+
if (flags.some((f) => f === '--help' || f === '-h')) {
|
|
71
|
+
showPushHelp();
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
const { invocationDir } = enterRepoRoot();
|
|
75
|
+
await pushCommand(flags, styles, invocationDir);
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
if (command === 'create-github-release') {
|
|
79
|
+
if (flags.some((f) => f === '--help' || f === '-h')) {
|
|
80
|
+
showCreateGithubReleaseHelp();
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
const { invocationDir } = enterRepoRoot();
|
|
84
|
+
await createGithubReleaseCommand(flags, styles, invocationDir);
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
if (command === 'publish') {
|
|
88
|
+
if (flags.some((f) => f === '--help' || f === '-h')) {
|
|
89
|
+
showPublishHelp();
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
const { invocationDir } = enterRepoRoot();
|
|
93
|
+
await publishCommand(flags, styles, invocationDir);
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
if (command === 'show-tag-prefixes') {
|
|
97
|
+
if (flags.some((f) => f === '--help' || f === '-h')) {
|
|
98
|
+
showShowTagPrefixesHelp();
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
const { invocationDir } = enterRepoRoot();
|
|
102
|
+
const { config } = parseArgsOrExit(flags, configFlagSchema).flags;
|
|
103
|
+
const exitCode = await showTagPrefixesCommand(styles, resolveConfigFlag(config, invocationDir));
|
|
104
|
+
process.exit(exitCode);
|
|
105
|
+
}
|
|
106
|
+
if (command === 'init') {
|
|
107
|
+
if (flags.some((f) => f === '--help' || f === '-h')) {
|
|
108
|
+
showInitHelp();
|
|
109
|
+
process.exit(0);
|
|
110
|
+
}
|
|
111
|
+
enterRepoRoot();
|
|
112
|
+
const initFlagSchema = {
|
|
113
|
+
isDryRun: { long: '--dry-run', type: 'boolean' },
|
|
114
|
+
shouldForce: { long: '--force', type: 'boolean' },
|
|
115
|
+
shouldScaffoldConfig: { long: '--with-config', type: 'boolean' },
|
|
116
|
+
};
|
|
117
|
+
const { isDryRun, shouldForce, shouldScaffoldConfig } = parseArgsOrExit(flags, initFlagSchema).flags;
|
|
118
|
+
const exitCode = initCommand({ isDryRun, shouldForce, styles, shouldScaffoldConfig });
|
|
119
|
+
process.exit(exitCode);
|
|
120
|
+
}
|
|
121
|
+
if (command === 'update-templates') {
|
|
122
|
+
if (flags.some((f) => f === '--help' || f === '-h')) {
|
|
123
|
+
showUpdateTemplatesHelp();
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
enterRepoRoot();
|
|
127
|
+
const updateTemplatesFlagSchema = {
|
|
128
|
+
isDryRun: { long: '--dry-run', type: 'boolean' },
|
|
129
|
+
};
|
|
130
|
+
const { isDryRun } = parseArgsOrExit(flags, updateTemplatesFlagSchema).flags;
|
|
131
|
+
const exitCode = updateTemplatesCommand({ isDryRun, styles });
|
|
132
|
+
process.exit(exitCode);
|
|
133
|
+
}
|
|
134
|
+
if (command === 'sync-labels') {
|
|
135
|
+
const subcommand = flags[0];
|
|
136
|
+
if (subcommand === undefined || ['--help', '-h'].includes(subcommand)) {
|
|
137
|
+
showSyncLabelsHelp();
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}
|
|
140
|
+
const subflags = flags.slice(1);
|
|
141
|
+
if (subcommand === 'init') {
|
|
142
|
+
if (subflags.some((f) => f === '--help' || f === '-h')) {
|
|
143
|
+
showSyncLabelsInitHelp();
|
|
144
|
+
process.exit(0);
|
|
145
|
+
}
|
|
146
|
+
const { invocationDir } = enterRepoRoot();
|
|
147
|
+
const syncLabelsInitFlagSchema = {
|
|
148
|
+
...configFlagSchema,
|
|
149
|
+
isDryRun: { long: '--dry-run', type: 'boolean' },
|
|
150
|
+
shouldForce: { long: '--force', type: 'boolean' },
|
|
151
|
+
};
|
|
152
|
+
const { config, isDryRun, shouldForce } = parseArgsOrExit(subflags, syncLabelsInitFlagSchema).flags;
|
|
153
|
+
const configPath = resolveConfigFlag(config, invocationDir);
|
|
154
|
+
const exitCode = await syncLabelsInitCommand({
|
|
155
|
+
...(configPath !== undefined && { configPath }),
|
|
156
|
+
isDryRun,
|
|
157
|
+
shouldForce,
|
|
158
|
+
styles,
|
|
159
|
+
});
|
|
160
|
+
process.exit(exitCode);
|
|
161
|
+
}
|
|
162
|
+
if (subcommand === 'generate') {
|
|
163
|
+
if (subflags.some((f) => f === '--help' || f === '-h')) {
|
|
164
|
+
showSyncLabelsGenerateHelp();
|
|
165
|
+
process.exit(0);
|
|
166
|
+
}
|
|
167
|
+
const { invocationDir } = enterRepoRoot();
|
|
168
|
+
const generateFlagSchema = {
|
|
169
|
+
...configFlagSchema,
|
|
170
|
+
shouldCheck: { long: '--check', type: 'boolean' },
|
|
171
|
+
};
|
|
172
|
+
const { shouldCheck, config } = parseArgsOrExit(subflags, generateFlagSchema).flags;
|
|
173
|
+
const configPath = resolveConfigFlag(config, invocationDir);
|
|
174
|
+
const exitCode = await generateCommand({ shouldCheck, ...(configPath !== undefined && { configPath }), styles });
|
|
175
|
+
process.exit(exitCode);
|
|
176
|
+
}
|
|
177
|
+
if (subcommand === 'sync') {
|
|
178
|
+
if (subflags.some((f) => f === '--help' || f === '-h')) {
|
|
179
|
+
showSyncLabelsSyncHelp();
|
|
180
|
+
process.exit(0);
|
|
181
|
+
}
|
|
182
|
+
if (subflags.length > 0) {
|
|
183
|
+
reportError(`Unknown option: ${subflags[0]}`);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
enterRepoRoot();
|
|
187
|
+
const exitCode = syncLabelsCommand();
|
|
188
|
+
process.exit(exitCode);
|
|
189
|
+
}
|
|
190
|
+
reportError(`Unknown subcommand: ${subcommand}`);
|
|
191
|
+
showSyncLabelsHelp();
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
if (command === 'overrides') {
|
|
195
|
+
const subcommand = flags[0];
|
|
196
|
+
if (subcommand === undefined || ['--help', '-h'].includes(subcommand)) {
|
|
197
|
+
showOverridesHelp();
|
|
198
|
+
process.exit(0);
|
|
199
|
+
}
|
|
200
|
+
const subflags = flags.slice(1);
|
|
201
|
+
if (subcommand === 'validate') {
|
|
202
|
+
if (subflags.some((f) => f === '--help' || f === '-h')) {
|
|
203
|
+
showOverridesValidateHelp();
|
|
204
|
+
process.exit(0);
|
|
205
|
+
}
|
|
206
|
+
const { invocationDir } = enterRepoRoot();
|
|
207
|
+
const { config } = parseArgsOrExit(subflags, configFlagSchema).flags;
|
|
208
|
+
const result = await validateOverridesCommand(styles, resolveConfigFlag(config, invocationDir));
|
|
209
|
+
if (result.exitCode === 0) {
|
|
210
|
+
console.info(result.message);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
process.stderr.write(`${result.message}\n`);
|
|
214
|
+
}
|
|
215
|
+
process.exit(result.exitCode);
|
|
216
|
+
}
|
|
217
|
+
reportError(`Unknown subcommand: ${subcommand}`);
|
|
218
|
+
showOverridesHelp();
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
reportError(`Unknown command: ${command}`);
|
|
222
|
+
showUsage();
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { formatStatusLine, reportError } from '@williamthorsen/nmr-core';
|
|
2
|
-
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
1
|
+
import { formatStatusLine, isAnticipatedFailure, reportError, } from '@williamthorsen/nmr-core';
|
|
3
2
|
import { detectRepoType } from "./init/detectRepoType.js";
|
|
4
3
|
import { loadValidatedConfig, reportConfigProblem } from "./loadValidatedConfig.js";
|
|
5
4
|
import { previewTagPrefixes } from "./previewTagPrefixes.js";
|
|
@@ -18,7 +17,10 @@ export async function showTagPrefixesCommand(styles, configPath) {
|
|
|
18
17
|
preview = previewTagPrefixes(result.status === 'ok' ? result.config : undefined);
|
|
19
18
|
}
|
|
20
19
|
catch (error) {
|
|
21
|
-
|
|
20
|
+
if (!isAnticipatedFailure(error)) {
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
reportError(error.message);
|
|
22
24
|
return 1;
|
|
23
25
|
}
|
|
24
26
|
process.stdout.write(renderMonorepo(preview, styles.stdout));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { reportError } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { isAnticipatedFailure, reportError } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
5
5
|
import { stringify } from 'yaml';
|
|
6
6
|
import { CONFIG_FILE_PATH } from "../loadConfig.js";
|
|
@@ -54,6 +54,9 @@ export async function generateCommand({ shouldCheck = false, configPath, styles
|
|
|
54
54
|
labels = resolveLabels(repoLabels);
|
|
55
55
|
}
|
|
56
56
|
catch (error) {
|
|
57
|
+
if (!isAnticipatedFailure(error)) {
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
57
60
|
const message = describeError(error);
|
|
58
61
|
reportError(`Failed to resolve labels: ${message}`);
|
|
59
62
|
return 1;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
|
-
import { reportWriteResult, writeFileWithCheck } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { isAnticipatedFailure, reportWriteResult, writeFileWithCheck, } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
4
|
import { describeEmptyWorkspace, discoverWorkspaces } from "../discoverWorkspaces.js";
|
|
5
5
|
import { CONFIG_FILE_PATH } from "../loadConfig.js";
|
|
@@ -22,6 +22,9 @@ export async function syncLabelsInitCommand({ configPath, isDryRun, shouldForce,
|
|
|
22
22
|
workspace = discoverWorkspaces();
|
|
23
23
|
}
|
|
24
24
|
catch (error) {
|
|
25
|
+
if (!isAnticipatedFailure(error)) {
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
25
28
|
const message = describeError(error);
|
|
26
29
|
process.stderr.write(` Failed to discover workspaces: ${message}\n`);
|
|
27
30
|
return 1;
|
|
@@ -48,6 +51,9 @@ export async function syncLabelsInitCommand({ configPath, isDryRun, shouldForce,
|
|
|
48
51
|
workflow = readSyncLabelsWorkflowTemplate();
|
|
49
52
|
}
|
|
50
53
|
catch (error) {
|
|
54
|
+
if (!isAnticipatedFailure(error)) {
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
51
57
|
process.stderr.write(` ${describeError(error)}\n`);
|
|
52
58
|
return 1;
|
|
53
59
|
}
|
|
@@ -2,10 +2,10 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { CANONICAL_TAXONOMY } from '@williamthorsen/change-grammar';
|
|
5
|
-
import { findPackageRoot } from '@williamthorsen/nmr-core';
|
|
6
|
-
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
5
|
+
import { findPackageRoot, UserError } from '@williamthorsen/nmr-core';
|
|
7
6
|
import { parse } from 'yaml';
|
|
8
7
|
import { isRecord } from "../typeGuards.js";
|
|
8
|
+
import { chainUserError } from "../user-error-chaining.js";
|
|
9
9
|
import { deriveTypeLabels } from "./typeLabels.js";
|
|
10
10
|
const TYPE_LABELS_PRESET = 'common';
|
|
11
11
|
export function hashPreset(presetName) {
|
|
@@ -16,29 +16,29 @@ export function hashPreset(presetName) {
|
|
|
16
16
|
export function loadPreset(presetName) {
|
|
17
17
|
const presetPath = resolvePresetPath(presetName);
|
|
18
18
|
if (!existsSync(presetPath)) {
|
|
19
|
-
throw new
|
|
19
|
+
throw new UserError(`Unknown preset "${presetName}". The file ${presetPath} does not exist`);
|
|
20
20
|
}
|
|
21
21
|
let content;
|
|
22
22
|
try {
|
|
23
23
|
content = readFileSync(presetPath, 'utf8');
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
|
-
throw
|
|
26
|
+
throw chainUserError(`Failed to read preset "${presetName}"`, error);
|
|
27
27
|
}
|
|
28
28
|
const parsedYaml = parse(content);
|
|
29
29
|
if (!Array.isArray(parsedYaml)) {
|
|
30
|
-
throw new
|
|
30
|
+
throw new UserError(`Preset "${presetName}" must be a YAML array of label definitions`);
|
|
31
31
|
}
|
|
32
32
|
const labels = [];
|
|
33
33
|
for (const entry of parsedYaml) {
|
|
34
34
|
if (!isRecord(entry)) {
|
|
35
|
-
throw new
|
|
35
|
+
throw new UserError(`Preset "${presetName}" contains an invalid label entry: ${JSON.stringify(entry)}`);
|
|
36
36
|
}
|
|
37
37
|
const { color, description, name } = entry;
|
|
38
38
|
if (typeof name !== 'string' ||
|
|
39
39
|
typeof color !== 'string' ||
|
|
40
40
|
(description !== undefined && typeof description !== 'string')) {
|
|
41
|
-
throw new
|
|
41
|
+
throw new UserError(`Preset "${presetName}" contains a label with invalid fields: ${JSON.stringify(entry)}`);
|
|
42
42
|
}
|
|
43
43
|
labels.push({ name, color, ...(description !== undefined && { description }) });
|
|
44
44
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
1
2
|
import { loadPreset } from "./presets.js";
|
|
2
3
|
export function resolveLabels(config) {
|
|
3
4
|
const labelEntries = Object.entries(config.labels ?? {});
|
|
4
5
|
const caseVariantGroups = findCaseVariantGroups(labelEntries.map(([name]) => name));
|
|
5
6
|
if (caseVariantGroups.length > 0) {
|
|
6
7
|
const groupList = caseVariantGroups.map((group) => group.map((name) => `'${name}'`).join(' and ')).join('; ');
|
|
7
|
-
throw new
|
|
8
|
+
throw new UserError(`Keys in 'labels' differ only in case, which GitHub treats as the same label: ${groupList}. Keep one entry per label.`);
|
|
8
9
|
}
|
|
9
10
|
const resolvedLabels = new Map();
|
|
10
11
|
const presetNames = config.extends ?? [];
|
|
@@ -17,7 +18,7 @@ export function resolveLabels(config) {
|
|
|
17
18
|
const key = getLabelKey(name);
|
|
18
19
|
if (spec === null) {
|
|
19
20
|
if (!resolvedLabels.has(key)) {
|
|
20
|
-
throw new
|
|
21
|
+
throw new UserError(`Label '${name}' is set to null (remove), but the presets in 'extends' don't define it. Fix the name or delete the entry.`);
|
|
21
22
|
}
|
|
22
23
|
resolvedLabels.delete(key);
|
|
23
24
|
}
|
|
@@ -34,7 +35,7 @@ export function resolveLabels(config) {
|
|
|
34
35
|
const labels = sortLabels(resolvedLabels.values().toArray());
|
|
35
36
|
const aliasCollisions = findAliasCollisions(labels);
|
|
36
37
|
if (aliasCollisions.length > 0) {
|
|
37
|
-
throw new
|
|
38
|
+
throw new UserError(`Aliases must name labels that are not declared and must not repeat, or the sync merges one label into another and deletes it: ${aliasCollisions.join('; ')}.`);
|
|
38
39
|
}
|
|
39
40
|
return labels;
|
|
40
41
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { basename, resolve } from 'node:path';
|
|
3
3
|
import { findPackageRoot } from '@williamthorsen/nmr-core';
|
|
4
|
-
import {
|
|
4
|
+
import { chainUserError } from "../user-error-chaining.js";
|
|
5
5
|
export function readSyncLabelsWorkflowTemplate() {
|
|
6
6
|
const templatePath = resolve(findPackageRoot(import.meta.url), 'templates', 'sync-labels.yaml.template');
|
|
7
7
|
try {
|
|
8
8
|
return readFileSync(templatePath, 'utf8');
|
|
9
9
|
}
|
|
10
10
|
catch (error) {
|
|
11
|
-
throw
|
|
11
|
+
throw chainUserError(`Failed to read bundled template at ${templatePath}`, error);
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
14
|
export function buildScopeLabels(workspacePaths, retiredNames = []) {
|
package/dist/esm/tagCommand.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { parseArgsOrExit
|
|
2
|
-
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
1
|
+
import { parseArgsOrExit } from '@williamthorsen/nmr-core';
|
|
3
2
|
import { createTags } from "./createTags.js";
|
|
4
3
|
const tagFlagSchema = {
|
|
5
4
|
isDryRun: { long: '--dry-run', type: 'boolean' },
|
|
@@ -7,11 +6,5 @@ const tagFlagSchema = {
|
|
|
7
6
|
};
|
|
8
7
|
export function tagCommand(argv, styles) {
|
|
9
8
|
const { isDryRun, shouldSkipGitChecks } = parseArgsOrExit(argv, tagFlagSchema).flags;
|
|
10
|
-
|
|
11
|
-
createTags({ isDryRun, shouldSkipGitChecks, style: styles.stdout });
|
|
12
|
-
}
|
|
13
|
-
catch (error) {
|
|
14
|
-
reportError(describeError(error));
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
9
|
+
createTags({ isDryRun, shouldSkipGitChecks, style: styles.stdout });
|
|
17
10
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
2
|
+
/**
|
|
3
|
+
* Returns `error` as a `UserError` with the same message, for a failure that the tool anticipates, such as a failed
|
|
4
|
+
* external command.
|
|
5
|
+
*/
|
|
6
|
+
export declare function asUserError(error: unknown): UserError;
|
|
7
|
+
/** Wraps an anticipated failure as a `UserError` that reads `<message>: <cause's message>`. */
|
|
8
|
+
export declare function chainUserError(message: string, cause: unknown): UserError;
|
|
9
|
+
/**
|
|
10
|
+
* Prefixes an anticipated failure (see `isAnticipatedFailure`) with `<message>: ` as a `UserError`, and returns any
|
|
11
|
+
* other value unchanged, so that a crash keeps its own stack when rethrown.
|
|
12
|
+
*/
|
|
13
|
+
export declare function prefixUserError(message: string, error: unknown): unknown;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { isAnticipatedFailure, UserError } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
|
+
export function asUserError(error) {
|
|
4
|
+
return error instanceof UserError ? error : new UserError(describeError(error), { cause: error });
|
|
5
|
+
}
|
|
6
|
+
export function chainUserError(message, cause) {
|
|
7
|
+
return new UserError(`${message}: ${describeError(cause)}`, { cause });
|
|
8
|
+
}
|
|
9
|
+
export function prefixUserError(message, error) {
|
|
10
|
+
return isAnticipatedFailure(error) ? chainUserError(message, error) : error;
|
|
11
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { formatErrorLine, formatStatusLine } from '@williamthorsen/nmr-core';
|
|
1
|
+
import { formatErrorLine, formatStatusLine, isAnticipatedFailure, } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
3
|
import { buildChangelogEntries } from "./buildChangelogEntries.js";
|
|
4
4
|
import { resolveOverridePath } from "./changelogOverrides.js";
|
|
@@ -23,6 +23,9 @@ export async function validateOverridesCommand(styles, configPath, dependencies
|
|
|
23
23
|
workspace = discover();
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
|
+
if (!isAnticipatedFailure(error)) {
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
26
29
|
return { exitCode: 2, message: formatErrorLine(`Failed to discover workspaces: ${describeError(error)}`) };
|
|
27
30
|
}
|
|
28
31
|
if (workspace.kind === 'empty') {
|
|
@@ -36,6 +39,9 @@ export async function validateOverridesCommand(styles, configPath, dependencies
|
|
|
36
39
|
inputs = buildValidationInputs(resolveReleaseTargets(userConfig, workspace), buildEntries);
|
|
37
40
|
}
|
|
38
41
|
catch (error) {
|
|
42
|
+
if (!isAnticipatedFailure(error)) {
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
39
45
|
return { exitCode: 2, message: formatErrorLine(`Failed to resolve overrides scope: ${describeError(error)}`) };
|
|
40
46
|
}
|
|
41
47
|
const result = validate(inputs);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/release-kit",
|
|
3
|
-
"version": "16.1.
|
|
3
|
+
"version": "16.1.2",
|
|
4
4
|
"description": "Version-bumping and changelog-generation toolkit for release workflows",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"changelog",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"license": "ISC",
|
|
22
22
|
"author": "William Thorsen <william@thorsen.dev> (https://github.com/williamthorsen)",
|
|
23
23
|
"sideEffects": [
|
|
24
|
-
"./dist/esm/
|
|
24
|
+
"./dist/esm/cli.js"
|
|
25
25
|
],
|
|
26
26
|
"type": "module",
|
|
27
27
|
"exports": {
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
],
|
|
53
53
|
"dependencies": {
|
|
54
54
|
"@williamthorsen/change-grammar": "0.3.0",
|
|
55
|
-
"@williamthorsen/nmr-core": "0.
|
|
55
|
+
"@williamthorsen/nmr-core": "0.15.1",
|
|
56
56
|
"@williamthorsen/toolbelt.errors": "0.9.0",
|
|
57
57
|
"json-stringify-pretty-compact": "4.0.0",
|
|
58
|
-
"semver": "7.8.
|
|
58
|
+
"semver": "7.8.6",
|
|
59
59
|
"yaml": "2.9.1",
|
|
60
60
|
"zod": "4.6.7"
|
|
61
61
|
},
|
|
@@ -66,9 +66,9 @@
|
|
|
66
66
|
"@williamthorsen/toolbelt.vitest": "0.13.1",
|
|
67
67
|
"actionlint": "2.0.6",
|
|
68
68
|
"esbuild": "0.28.2",
|
|
69
|
-
"readyup": "0.
|
|
69
|
+
"readyup": "0.45.0",
|
|
70
70
|
"smol-toml": "1.9.1",
|
|
71
|
-
"vitest": "5.0
|
|
71
|
+
"vitest": "5.1.0"
|
|
72
72
|
},
|
|
73
73
|
"engines": {
|
|
74
74
|
"node": ">=24.16.0"
|