@williamthorsen/release-kit 16.1.1 → 17.0.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 +79 -0
- package/.readyup/kits/default.js +2 -2
- package/.readyup/kits/npm-auto-publish.js +1 -1
- package/.readyup/manifest.json +204 -16
- package/CHANGELOG.md +28 -0
- package/README.md +7 -2
- package/bin/release-kit.js +1 -1
- package/dist/esm/assertCleanWorkingTree.js +14 -7
- package/dist/esm/assertReleasesOnNpm.js +11 -3
- package/dist/esm/assertTaggedBaseline.js +2 -1
- package/dist/esm/assertTarballsComplete.js +16 -5
- package/dist/esm/bin.d.ts +2 -0
- package/dist/esm/bin.js +1 -0
- package/dist/esm/buildChangelogEntries.js +2 -2
- package/dist/esm/buildDependencyGraph.js +5 -4
- package/dist/esm/bumpVersion.js +3 -2
- package/dist/esm/changelogJsonUtils.js +2 -1
- package/dist/esm/changelogOverrides.js +3 -2
- package/dist/esm/cli/commandKit.d.ts +18 -0
- package/dist/esm/cli/commandKit.js +27 -0
- package/dist/esm/cli/maintenanceCommands.d.ts +6 -0
- package/dist/esm/cli/maintenanceCommands.js +129 -0
- package/dist/esm/cli/releaseCommands.d.ts +7 -0
- package/dist/esm/cli/releaseCommands.js +156 -0
- package/dist/esm/cli.js +9 -237
- package/dist/esm/commitCommand.d.ts +4 -1
- package/dist/esm/commitCommand.js +12 -10
- package/dist/esm/compareVersions.js +2 -1
- package/dist/esm/createGithubRelease.js +7 -1
- package/dist/esm/createGithubReleaseCommand.d.ts +10 -4
- package/dist/esm/createGithubReleaseCommand.js +4 -18
- package/dist/esm/createTags.js +6 -4
- package/dist/esm/deleteFileIfExists.js +2 -1
- package/dist/esm/deriveDirectoryConfig.js +7 -7
- package/dist/esm/deriveWorkspaceConfig.js +4 -4
- package/dist/esm/detectPackageManager.js +1 -1
- package/dist/esm/enterRepoRoot.js +2 -1
- package/dist/esm/enumerateReleaseWindows.js +2 -2
- package/dist/esm/findMissingManifestPaths.js +1 -1
- package/dist/esm/hasPrettierConfig.js +1 -1
- package/dist/esm/init/initCommand.js +10 -1
- package/dist/esm/init/parseJsonRecord.js +1 -1
- package/dist/esm/init/updateTemplatesCommand.js +10 -1
- package/dist/esm/isPublishableManifest.js +1 -1
- package/dist/esm/loadChangelogOverrides.js +1 -1
- package/dist/esm/loadConfig.js +6 -6
- package/dist/esm/loadValidatedConfig.d.ts +6 -1
- package/dist/esm/loadValidatedConfig.js +11 -3
- package/dist/esm/mergeConfig.js +5 -4
- package/dist/esm/monorepoConfigAssertions.js +6 -5
- package/dist/esm/npmRegistry.js +1 -1
- package/dist/esm/packFromCleanCheckout.js +12 -4
- package/dist/esm/parseChangeRecordBlock.js +1 -1
- package/dist/esm/planVersionBump.js +6 -5
- package/dist/esm/prepareArgs.d.ts +1 -49
- package/dist/esm/prepareArgs.js +4 -51
- package/dist/esm/prepareCommand.d.ts +15 -3
- package/dist/esm/prepareCommand.js +26 -44
- 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.d.ts +12 -3
- package/dist/esm/publishCommand.js +4 -33
- package/dist/esm/pushCommand.d.ts +12 -4
- package/dist/esm/pushCommand.js +6 -27
- 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 +8 -12
- package/dist/esm/resolveReleaseDeclaration.js +9 -8
- package/dist/esm/resolveReleaseTags.js +11 -4
- package/dist/esm/resolveReleaseTargets.js +2 -1
- package/dist/esm/{resolveStylesOrExit.d.ts → resolveStyles.d.ts} +2 -2
- package/dist/esm/resolveStyles.js +10 -0
- package/dist/esm/route.d.ts +2 -0
- package/dist/esm/route.js +39 -0
- package/dist/esm/showTagPrefixesCommand.js +6 -3
- package/dist/esm/sync-labels/generateCommand.js +11 -7
- package/dist/esm/sync-labels/initCommand.js +7 -1
- package/dist/esm/sync-labels/presets.js +7 -8
- package/dist/esm/sync-labels/resolveLabels.js +4 -3
- package/dist/esm/sync-labels/retiredConfig.js +2 -1
- package/dist/esm/sync-labels/syncCommand.js +4 -3
- package/dist/esm/sync-labels/templates.js +2 -2
- package/dist/esm/tagCommand.d.ts +8 -3
- package/dist/esm/tagCommand.js +2 -15
- package/dist/esm/typeGuards.d.ts +0 -2
- package/dist/esm/typeGuards.js +0 -3
- package/dist/esm/user-error-chaining.d.ts +13 -0
- package/dist/esm/user-error-chaining.js +11 -0
- package/dist/esm/validateConfig.js +1 -1
- package/dist/esm/validateOverridesCommand.js +12 -5
- package/docs/configuration.md +1 -1
- package/docs/editorial-overrides.md +2 -0
- package/docs/labels.md +2 -0
- package/docs/releasing.md +1 -1
- package/package.json +3 -3
- package/dist/esm/configFlagSchema.d.ts +0 -12
- package/dist/esm/configFlagSchema.js +0 -3
- package/dist/esm/help/initHelp.d.ts +0 -4
- package/dist/esm/help/initHelp.js +0 -31
- package/dist/esm/help/overridesHelp.d.ts +0 -4
- package/dist/esm/help/overridesHelp.js +0 -32
- package/dist/esm/help/prepareHelp.d.ts +0 -7
- package/dist/esm/help/prepareHelp.js +0 -30
- package/dist/esm/help/releaseStepsHelp.d.ts +0 -12
- package/dist/esm/help/releaseStepsHelp.js +0 -91
- package/dist/esm/help/syncLabelsHelp.d.ts +0 -8
- package/dist/esm/help/syncLabelsHelp.js +0 -55
- package/dist/esm/help/usage.d.ts +0 -2
- package/dist/esm/help/usage.js +0 -29
- package/dist/esm/resolveConfigFlag.d.ts +0 -7
- package/dist/esm/resolveConfigFlag.js +0 -4
- package/dist/esm/resolveStylesOrExit.js +0 -10
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
|
-
*
|
|
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, when `mergeMonorepoConfig` rejects the
|
|
7
|
+
* config, when the workspace does not resolve to any package (which is not single-package mode), and when HEAD does
|
|
8
|
+
* not have any release tags; throws a `UsageError` for a `--tags` name that HEAD lacks.
|
|
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,23 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { UsageError } from '@williamthorsen/nmr-core/cli';
|
|
3
3
|
import { deriveWorkspaceConfig } from "./deriveWorkspaceConfig.js";
|
|
4
4
|
import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
5
5
|
import { formatExcludedSkip } from "./formatExcludedSkip.js";
|
|
6
6
|
import { readRootPackageVersion } from "./loadConfig.js";
|
|
7
7
|
import { mergeMonorepoConfig } from "./mergeConfig.js";
|
|
8
8
|
import { resolveReleaseTags } from "./resolveReleaseTags.js";
|
|
9
|
+
import { prefixUserError } from "./user-error-chaining.js";
|
|
9
10
|
export function resolveCommandTags(tags, userConfig) {
|
|
10
11
|
let workspace;
|
|
11
12
|
try {
|
|
12
13
|
workspace = discoverWorkspaces();
|
|
13
14
|
}
|
|
14
15
|
catch (error) {
|
|
15
|
-
|
|
16
|
-
process.exit(1);
|
|
16
|
+
throw prefixUserError('Failed to discover workspaces', error);
|
|
17
17
|
}
|
|
18
18
|
if (workspace.kind === 'empty') {
|
|
19
|
-
|
|
20
|
-
process.exit(1);
|
|
19
|
+
throw new UserError(`No workspace package to tag. ${describeEmptyWorkspace(workspace)}`);
|
|
21
20
|
}
|
|
22
21
|
let workspaces;
|
|
23
22
|
let directories = [];
|
|
@@ -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) {
|
|
@@ -52,8 +50,7 @@ export function resolveCommandTags(tags, userConfig) {
|
|
|
52
50
|
throw new Error('resolveCommandTags: invariant violated; neither workspaces nor singleWorkspace was derived');
|
|
53
51
|
}
|
|
54
52
|
if (resolvedTags.length === 0) {
|
|
55
|
-
|
|
56
|
-
process.exit(1);
|
|
53
|
+
throw new UserError('HEAD does not have any release tags. Create tags with `release-kit tag` first.');
|
|
57
54
|
}
|
|
58
55
|
if (tags !== undefined) {
|
|
59
56
|
const availableTagNames = resolvedTags.map((t) => t.tag);
|
|
@@ -61,8 +58,7 @@ export function resolveCommandTags(tags, userConfig) {
|
|
|
61
58
|
if (availableTagNames.includes(name)) {
|
|
62
59
|
continue;
|
|
63
60
|
}
|
|
64
|
-
|
|
65
|
-
process.exit(1);
|
|
61
|
+
throw new UsageError(`Unknown tag "${name}" in --tags. Available: ${availableTagNames.join(', ')}`);
|
|
66
62
|
}
|
|
67
63
|
resolvedTags = resolvedTags.filter((t) => tags.includes(t.tag));
|
|
68
64
|
}
|
|
@@ -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
|
}
|
|
@@ -6,5 +6,5 @@ export interface ResolveStylesOptions {
|
|
|
6
6
|
readonly stderrIsTty: boolean;
|
|
7
7
|
readonly stdoutIsTty: boolean;
|
|
8
8
|
}
|
|
9
|
-
/** Resolves the style of each output stream,
|
|
10
|
-
export declare function
|
|
9
|
+
/** Resolves the style of each output stream, throwing a `UsageError` when the variable doesn't name a setting. */
|
|
10
|
+
export declare function resolveStyles(options: ResolveStylesOptions): StreamStyles;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { describeInvalidOutputStyle, resolveStreamStyles } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { UsageError } from '@williamthorsen/nmr-core/cli';
|
|
3
|
+
export const OUTPUT_STYLE_ENV_VAR = 'RELEASE_KIT_OUTPUT_STYLE';
|
|
4
|
+
export function resolveStyles(options) {
|
|
5
|
+
const { invalid, styles } = resolveStreamStyles({ ...options, envVar: OUTPUT_STYLE_ENV_VAR });
|
|
6
|
+
if (invalid !== undefined) {
|
|
7
|
+
throw new UsageError(describeInvalidOutputStyle(invalid));
|
|
8
|
+
}
|
|
9
|
+
return styles;
|
|
10
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { resolveSelfVersion } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { createCli, runCli } from '@williamthorsen/nmr-core/cli';
|
|
4
|
+
import { BIN } from "./bin.js";
|
|
5
|
+
import { init, overrides, showTagPrefixes, syncLabels, updateTemplates } from "./cli/maintenanceCommands.js";
|
|
6
|
+
import { commit, createGithubRelease, prepare, publish, push, tag } from "./cli/releaseCommands.js";
|
|
7
|
+
import { OUTPUT_STYLE_ENV_VAR, resolveStyles } from "./resolveStyles.js";
|
|
8
|
+
const VERSION = resolveSelfVersion(import.meta.url);
|
|
9
|
+
export async function routeCommand(args) {
|
|
10
|
+
return runCli(args, ROOT, {
|
|
11
|
+
name: BIN,
|
|
12
|
+
context: { env: process.env, stderrIsTty: process.stderr.isTTY, stdoutIsTty: process.stdout.isTTY },
|
|
13
|
+
stdout: process.stdout,
|
|
14
|
+
stderr: process.stderr,
|
|
15
|
+
version: () => VERSION,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
const { defineGroup } = createCli();
|
|
19
|
+
const ROOT = defineGroup({
|
|
20
|
+
summary: 'Version-bumping and changelog-generation toolkit for release workflows',
|
|
21
|
+
deriveContext: (_flags, context) => ({ styles: resolveStyles(context) }),
|
|
22
|
+
commands: {
|
|
23
|
+
prepare,
|
|
24
|
+
commit,
|
|
25
|
+
tag,
|
|
26
|
+
push,
|
|
27
|
+
publish,
|
|
28
|
+
'create-github-release': createGithubRelease,
|
|
29
|
+
'show-tag-prefixes': showTagPrefixes,
|
|
30
|
+
init,
|
|
31
|
+
'update-templates': updateTemplates,
|
|
32
|
+
overrides,
|
|
33
|
+
'sync-labels': syncLabels,
|
|
34
|
+
},
|
|
35
|
+
epilog: `Environment:
|
|
36
|
+
${OUTPUT_STYLE_ENV_VAR}
|
|
37
|
+
Output style: auto (default), plain, or rich. auto prints plain, without emoji,
|
|
38
|
+
when CI is set or the stream is not a terminal.`,
|
|
39
|
+
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { formatStatusLine, reportError } from '@williamthorsen/nmr-core';
|
|
2
|
-
import {
|
|
1
|
+
import { formatStatusLine, isAnticipatedFailure, reportError, } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { BIN } from "./bin.js";
|
|
3
3
|
import { detectRepoType } from "./init/detectRepoType.js";
|
|
4
4
|
import { loadValidatedConfig, reportConfigProblem } from "./loadValidatedConfig.js";
|
|
5
5
|
import { previewTagPrefixes } from "./previewTagPrefixes.js";
|
|
@@ -18,7 +18,10 @@ export async function showTagPrefixesCommand(styles, configPath) {
|
|
|
18
18
|
preview = previewTagPrefixes(result.status === 'ok' ? result.config : undefined);
|
|
19
19
|
}
|
|
20
20
|
catch (error) {
|
|
21
|
-
|
|
21
|
+
if (!isAnticipatedFailure(error)) {
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
reportError(BIN, error.message);
|
|
22
25
|
return 1;
|
|
23
26
|
}
|
|
24
27
|
process.stdout.write(renderMonorepo(preview, styles.stdout));
|
|
@@ -1,8 +1,9 @@
|
|
|
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
|
+
import { BIN } from "../bin.js";
|
|
6
7
|
import { CONFIG_FILE_PATH } from "../loadConfig.js";
|
|
7
8
|
import { loadValidatedConfig, reportConfigProblem } from "../loadValidatedConfig.js";
|
|
8
9
|
import { hashPreset } from "./presets.js";
|
|
@@ -28,7 +29,7 @@ export function formatLabelsYaml(labels, presetHashes, configFilePath = CONFIG_F
|
|
|
28
29
|
export async function loadRepoLabelsConfig(stderrStyle, configPath) {
|
|
29
30
|
const result = await loadValidatedConfig(configPath);
|
|
30
31
|
if (result.status === 'missing') {
|
|
31
|
-
reportError(`Config file not found at ${result.configFilePath}. Run \`release-kit sync-labels init\` first.`);
|
|
32
|
+
reportError(BIN, `Config file not found at ${result.configFilePath}. Run \`release-kit sync-labels init\` first.`);
|
|
32
33
|
return undefined;
|
|
33
34
|
}
|
|
34
35
|
if (result.status === 'invalid') {
|
|
@@ -36,7 +37,7 @@ export async function loadRepoLabelsConfig(stderrStyle, configPath) {
|
|
|
36
37
|
return undefined;
|
|
37
38
|
}
|
|
38
39
|
if (result.config.repoLabels === undefined) {
|
|
39
|
-
reportError(`The \`repoLabels\` block is missing from ${result.configFilePath}. Run \`release-kit sync-labels init\` to seed one.`);
|
|
40
|
+
reportError(BIN, `The \`repoLabels\` block is missing from ${result.configFilePath}. Run \`release-kit sync-labels init\` to seed one.`);
|
|
40
41
|
return undefined;
|
|
41
42
|
}
|
|
42
43
|
return result.config.repoLabels;
|
|
@@ -54,8 +55,11 @@ export async function generateCommand({ shouldCheck = false, configPath, styles
|
|
|
54
55
|
labels = resolveLabels(repoLabels);
|
|
55
56
|
}
|
|
56
57
|
catch (error) {
|
|
58
|
+
if (!isAnticipatedFailure(error)) {
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
57
61
|
const message = describeError(error);
|
|
58
|
-
reportError(`Failed to resolve labels: ${message}`);
|
|
62
|
+
reportError(BIN, `Failed to resolve labels: ${message}`);
|
|
59
63
|
return 1;
|
|
60
64
|
}
|
|
61
65
|
const presetHashes = new Map();
|
|
@@ -73,7 +77,7 @@ export async function generateCommand({ shouldCheck = false, configPath, styles
|
|
|
73
77
|
}
|
|
74
78
|
catch (error) {
|
|
75
79
|
const message = describeError(error);
|
|
76
|
-
reportError(`Failed to write ${LABELS_OUTPUT_PATH}: ${message}`);
|
|
80
|
+
reportError(BIN, `Failed to write ${LABELS_OUTPUT_PATH}: ${message}`);
|
|
77
81
|
return 1;
|
|
78
82
|
}
|
|
79
83
|
console.info(`Generated ${LABELS_OUTPUT_PATH} with ${String(labels.length)} labels.`);
|
|
@@ -85,11 +89,11 @@ function compareAgainstCommittedFile(expectedContent) {
|
|
|
85
89
|
existingContent = readFileSync(LABELS_OUTPUT_PATH, 'utf8');
|
|
86
90
|
}
|
|
87
91
|
catch {
|
|
88
|
-
reportError(`${LABELS_OUTPUT_PATH} is missing. Run \`release-kit sync-labels generate\` and commit the result.`);
|
|
92
|
+
reportError(BIN, `${LABELS_OUTPUT_PATH} is missing. Run \`release-kit sync-labels generate\` and commit the result.`);
|
|
89
93
|
return 1;
|
|
90
94
|
}
|
|
91
95
|
if (existingContent !== expectedContent) {
|
|
92
|
-
reportError(`${LABELS_OUTPUT_PATH} is stale. Run \`release-kit sync-labels generate\` and commit the result.`);
|
|
96
|
+
reportError(BIN, `${LABELS_OUTPUT_PATH} is stale. Run \`release-kit sync-labels generate\` and commit the result.`);
|
|
93
97
|
return 1;
|
|
94
98
|
}
|
|
95
99
|
console.info(`${LABELS_OUTPUT_PATH} is up to date.`);
|
|
@@ -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,9 @@ 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, isRecord, UserError } from '@williamthorsen/nmr-core';
|
|
7
6
|
import { parse } from 'yaml';
|
|
8
|
-
import {
|
|
7
|
+
import { chainUserError } from "../user-error-chaining.js";
|
|
9
8
|
import { deriveTypeLabels } from "./typeLabels.js";
|
|
10
9
|
const TYPE_LABELS_PRESET = 'common';
|
|
11
10
|
export function hashPreset(presetName) {
|
|
@@ -16,29 +15,29 @@ export function hashPreset(presetName) {
|
|
|
16
15
|
export function loadPreset(presetName) {
|
|
17
16
|
const presetPath = resolvePresetPath(presetName);
|
|
18
17
|
if (!existsSync(presetPath)) {
|
|
19
|
-
throw new
|
|
18
|
+
throw new UserError(`Unknown preset "${presetName}". The file ${presetPath} does not exist`);
|
|
20
19
|
}
|
|
21
20
|
let content;
|
|
22
21
|
try {
|
|
23
22
|
content = readFileSync(presetPath, 'utf8');
|
|
24
23
|
}
|
|
25
24
|
catch (error) {
|
|
26
|
-
throw
|
|
25
|
+
throw chainUserError(`Failed to read preset "${presetName}"`, error);
|
|
27
26
|
}
|
|
28
27
|
const parsedYaml = parse(content);
|
|
29
28
|
if (!Array.isArray(parsedYaml)) {
|
|
30
|
-
throw new
|
|
29
|
+
throw new UserError(`Preset "${presetName}" must be a YAML array of label definitions`);
|
|
31
30
|
}
|
|
32
31
|
const labels = [];
|
|
33
32
|
for (const entry of parsedYaml) {
|
|
34
33
|
if (!isRecord(entry)) {
|
|
35
|
-
throw new
|
|
34
|
+
throw new UserError(`Preset "${presetName}" contains an invalid label entry: ${JSON.stringify(entry)}`);
|
|
36
35
|
}
|
|
37
36
|
const { color, description, name } = entry;
|
|
38
37
|
if (typeof name !== 'string' ||
|
|
39
38
|
typeof color !== 'string' ||
|
|
40
39
|
(description !== undefined && typeof description !== 'string')) {
|
|
41
|
-
throw new
|
|
40
|
+
throw new UserError(`Preset "${presetName}" contains a label with invalid fields: ${JSON.stringify(entry)}`);
|
|
42
41
|
}
|
|
43
42
|
labels.push({ name, color, ...(description !== undefined && { description }) });
|
|
44
43
|
}
|
|
@@ -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,11 +1,12 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { BIN } from "../bin.js";
|
|
3
4
|
import { CONFIG_FILE_PATH } from "../loadConfig.js";
|
|
4
5
|
export const RETIRED_SYNC_LABELS_CONFIG_PATH = '.config/sync-labels.config.ts';
|
|
5
6
|
export function checkRetiredSyncLabelsConfig(configPath = CONFIG_FILE_PATH) {
|
|
6
7
|
if (!existsSync(RETIRED_SYNC_LABELS_CONFIG_PATH)) {
|
|
7
8
|
return false;
|
|
8
9
|
}
|
|
9
|
-
reportError(`${RETIRED_SYNC_LABELS_CONFIG_PATH} is no longer read. Move its labels into the \`repoLabels\` block of ${configPath} (presets move to \`repoLabels.extends\`; each label becomes a \`'name': { color, description }\` entry under \`repoLabels.labels\`), then delete the file.`);
|
|
10
|
+
reportError(BIN, `${RETIRED_SYNC_LABELS_CONFIG_PATH} is no longer read. Move its labels into the \`repoLabels\` block of ${configPath} (presets move to \`repoLabels.extends\`; each label becomes a \`'name': { color, description }\` entry under \`repoLabels.labels\`), then delete the file.`);
|
|
10
11
|
return true;
|
|
11
12
|
}
|
|
@@ -2,6 +2,7 @@ import { execSync } from 'node:child_process';
|
|
|
2
2
|
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
|
+
import { BIN } from "../bin.js";
|
|
5
6
|
import { checkRetiredSyncLabelsConfig } from "./retiredConfig.js";
|
|
6
7
|
const WORKFLOW_FILE = '.github/workflows/sync-labels.yaml';
|
|
7
8
|
function checkGhAvailable() {
|
|
@@ -18,11 +19,11 @@ export function syncLabelsCommand() {
|
|
|
18
19
|
return 1;
|
|
19
20
|
}
|
|
20
21
|
if (!checkGhAvailable()) {
|
|
21
|
-
reportError('The `gh` CLI is not installed or not in PATH. Install it from https://cli.github.com/');
|
|
22
|
+
reportError(BIN, 'The `gh` CLI is not installed or not in PATH. Install it from https://cli.github.com/');
|
|
22
23
|
return 1;
|
|
23
24
|
}
|
|
24
25
|
if (!existsSync(WORKFLOW_FILE)) {
|
|
25
|
-
reportError(`${WORKFLOW_FILE} not found. Run \`release-kit sync-labels init\` first.`);
|
|
26
|
+
reportError(BIN, `${WORKFLOW_FILE} not found. Run \`release-kit sync-labels init\` first.`);
|
|
26
27
|
return 1;
|
|
27
28
|
}
|
|
28
29
|
try {
|
|
@@ -30,7 +31,7 @@ export function syncLabelsCommand() {
|
|
|
30
31
|
}
|
|
31
32
|
catch (error) {
|
|
32
33
|
const message = describeError(error);
|
|
33
|
-
reportError(`Failed to trigger workflow: ${message}`);
|
|
34
|
+
reportError(BIN, `Failed to trigger workflow: ${message}`);
|
|
34
35
|
return 1;
|
|
35
36
|
}
|
|
36
37
|
console.info('Workflow triggered successfully. View runs at: gh run list --workflow=sync-labels.yaml');
|
|
@@ -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.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { StreamStyles } from '@williamthorsen/nmr-core';
|
|
2
|
+
export interface TagCommandOptions {
|
|
3
|
+
isDryRun: boolean;
|
|
4
|
+
shouldSkipGitChecks: boolean;
|
|
5
|
+
styles: StreamStyles;
|
|
6
|
+
}
|
|
7
|
+
/** Runs the CLI `tag` command by delegating to `createTags`. */
|
|
8
|
+
export declare function tagCommand({ isDryRun, shouldSkipGitChecks, styles }: TagCommandOptions): void;
|
package/dist/esm/tagCommand.js
CHANGED
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
2
|
-
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
1
|
import { createTags } from "./createTags.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
shouldSkipGitChecks: { long: '--no-git-checks', type: 'boolean' },
|
|
7
|
-
};
|
|
8
|
-
export function tagCommand(argv, styles) {
|
|
9
|
-
const { isDryRun, shouldSkipGitChecks } = parseArgsOrExit(argv, tagFlagSchema).flags;
|
|
10
|
-
try {
|
|
11
|
-
createTags({ isDryRun, shouldSkipGitChecks, style: styles.stdout });
|
|
12
|
-
}
|
|
13
|
-
catch (error) {
|
|
14
|
-
reportError(describeError(error));
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
2
|
+
export function tagCommand({ isDryRun, shouldSkipGitChecks, styles }) {
|
|
3
|
+
createTags({ isDryRun, shouldSkipGitChecks, style: styles.stdout });
|
|
17
4
|
}
|
package/dist/esm/typeGuards.d.ts
CHANGED
|
@@ -1,4 +1,2 @@
|
|
|
1
|
-
/** Checks whether a value is a plain object (non-null, non-array). */
|
|
2
|
-
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
3
1
|
/** Narrows an `unknown` value to `unknown[]` via `Array.isArray`, avoiding `any[]`. */
|
|
4
2
|
export declare function isUnknownArray(value: unknown): value is unknown[];
|