@williamthorsen/release-kit 16.1.1 → 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 +17 -0
- package/.readyup/kits/default.js +1 -1
- package/.readyup/manifest.json +3 -3
- package/CHANGELOG.md +6 -0
- package/README.md +2 -2
- 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 +8 -237
- 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 +2 -2
package/dist/esm/pushCommand.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 { configFlagSchema } from "./configFlagSchema.js";
|
|
4
3
|
import { loadUsableConfig } from "./loadValidatedConfig.js";
|
|
5
4
|
import { parseRequestedTags } from "./parseRequestedTags.js";
|
|
@@ -32,16 +31,10 @@ export async function pushCommand(argv, styles, invocationDir) {
|
|
|
32
31
|
for (const { tag } of resolvedTags) {
|
|
33
32
|
console.info(` ${tag}`);
|
|
34
33
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
console.info(`[dry-run] ${step.command.join(' ')}`);
|
|
40
|
-
}
|
|
34
|
+
const steps = pushRelease(resolvedTags, { isDryRun, tagsOnly });
|
|
35
|
+
if (isDryRun) {
|
|
36
|
+
for (const step of steps) {
|
|
37
|
+
console.info(`[dry-run] ${step.command.join(' ')}`);
|
|
41
38
|
}
|
|
42
39
|
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
reportError(describeError(error));
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
40
|
}
|
package/dist/esm/pushRelease.js
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { GIT_OUTPUT_LIMIT_BYTES } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { asUserError } from "./user-error-chaining.js";
|
|
3
4
|
export function pushRelease(resolvedTags, options = {}) {
|
|
4
5
|
const { isDryRun = false, tagsOnly = false } = options;
|
|
5
6
|
const steps = [];
|
|
6
7
|
if (!tagsOnly) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
let branch;
|
|
9
|
+
try {
|
|
10
|
+
branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
11
|
+
encoding: 'utf8',
|
|
12
|
+
maxBuffer: GIT_OUTPUT_LIMIT_BYTES,
|
|
13
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
14
|
+
}).trim();
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
throw asUserError(error);
|
|
18
|
+
}
|
|
12
19
|
const command = ['git', 'push', 'origin', branch];
|
|
13
20
|
steps.push({ type: 'branch', ref: branch, command });
|
|
14
21
|
}
|
|
@@ -18,7 +25,12 @@ export function pushRelease(resolvedTags, options = {}) {
|
|
|
18
25
|
}
|
|
19
26
|
if (!isDryRun) {
|
|
20
27
|
for (const step of steps) {
|
|
21
|
-
|
|
28
|
+
try {
|
|
29
|
+
execFileSync(step.command[0], step.command.slice(1), { stdio: 'inherit' });
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw asUserError(error);
|
|
33
|
+
}
|
|
22
34
|
}
|
|
23
35
|
}
|
|
24
36
|
return steps;
|
package/dist/esm/releaseFiles.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import { hasErrnoCode } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { hasErrnoCode, UserError } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
5
5
|
export const RELEASE_TAGS_FILE = 'tmp/.release-tags';
|
|
6
6
|
export const RELEASE_SUMMARY_FILE = 'tmp/.release-summary';
|
|
@@ -13,7 +13,7 @@ export function readReleaseTags() {
|
|
|
13
13
|
content = readFileSync(RELEASE_TAGS_FILE, 'utf8');
|
|
14
14
|
}
|
|
15
15
|
catch (error) {
|
|
16
|
-
throw new
|
|
16
|
+
throw new UserError(describeTagsReadFailure(error), { cause: error });
|
|
17
17
|
}
|
|
18
18
|
return content
|
|
19
19
|
.split('\n')
|
package/dist/esm/releasePlan.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeFileWithCheck } from '@williamthorsen/nmr-core';
|
|
1
|
+
import { UserError, writeFileWithCheck } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
3
|
import { deleteFileIfExists } from "./deleteFileIfExists.js";
|
|
4
4
|
import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
|
|
@@ -10,7 +10,7 @@ export function applyReleasePlan(plan) {
|
|
|
10
10
|
const outcome = applyStep(step);
|
|
11
11
|
if (outcome.outcome === 'failed') {
|
|
12
12
|
const unwrittenPaths = orderedSteps.slice(index).map((pending) => pending.path);
|
|
13
|
-
throw new
|
|
13
|
+
throw new UserError(describeApplyFailure({ failedStep: step, error: outcome.error, appliedWrites, unwrittenPaths }));
|
|
14
14
|
}
|
|
15
15
|
appliedWrites.push({ path: step.path, created: outcome.outcome === 'created' });
|
|
16
16
|
}
|
|
@@ -2,10 +2,10 @@ import type { ReleaseKitConfig } from './configSchema.js';
|
|
|
2
2
|
import { type ResolvedTag } from './resolveReleaseTags.js';
|
|
3
3
|
/**
|
|
4
4
|
* Discovers workspaces, resolves release tags from HEAD, validates `--tags` names against the full resolved tag
|
|
5
|
-
* names (e.g., `nmr-core-v1.3.0`), and returns the filtered tag list.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* mode.
|
|
5
|
+
* names (e.g., `nmr-core-v1.3.0`), and returns the filtered tag list. Throws a `UserError` when workspace discovery
|
|
6
|
+
* fails, when a workspace's `package.json` does not have a `name` field, and when `mergeMonorepoConfig` rejects the
|
|
7
|
+
* config. Exits with an error message on any other validation failure, including a workspace that does not resolve to
|
|
8
|
+
* any package, which is not single-package mode.
|
|
9
9
|
*
|
|
10
10
|
* In monorepo mode the workspace list is `userConfig` merged over the discovered workspaces, as `prepare` builds it,
|
|
11
11
|
* and each directory target and a declared composite (a `project` block or a `releases` composite) contribute their
|
|
@@ -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 = []) {
|