@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
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
|
-
import { GIT_OUTPUT_LIMIT_BYTES, hasErrnoCode,
|
|
3
|
+
import { GIT_OUTPUT_LIMIT_BYTES, hasErrnoCode, UserError } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { readReleaseTags, RELEASE_SUMMARY_FILE, resolveReleaseTagsPath } from "./releaseFiles.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
};
|
|
8
|
-
export function commitCommand(argv) {
|
|
9
|
-
const isDryRun = parseArgsOrExit(argv, commitFlagSchema).flags.isDryRun;
|
|
5
|
+
import { asUserError } from "./user-error-chaining.js";
|
|
6
|
+
export function commitCommand({ isDryRun }) {
|
|
10
7
|
const tags = readReleaseTags();
|
|
11
8
|
if (tags.length === 0) {
|
|
12
|
-
throw new
|
|
9
|
+
throw new UserError(`Tags file at ${resolveReleaseTagsPath()} is empty. Run \`release-kit prepare\` first.`);
|
|
13
10
|
}
|
|
14
11
|
let summary = '';
|
|
15
12
|
try {
|
|
@@ -19,7 +16,7 @@ export function commitCommand(argv) {
|
|
|
19
16
|
if (hasErrnoCode(error, 'ENOENT')) {
|
|
20
17
|
}
|
|
21
18
|
else {
|
|
22
|
-
throw error;
|
|
19
|
+
throw asUserError(error);
|
|
23
20
|
}
|
|
24
21
|
}
|
|
25
22
|
const title = `release: ${tags.join(' ')}`;
|
|
@@ -42,7 +39,12 @@ export function commitCommand(argv) {
|
|
|
42
39
|
}
|
|
43
40
|
return;
|
|
44
41
|
}
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
try {
|
|
43
|
+
execFileSync('git', ['add', '-A'], { maxBuffer: GIT_OUTPUT_LIMIT_BYTES });
|
|
44
|
+
execFileSync('git', ['commit', '-m', message], { maxBuffer: GIT_OUTPUT_LIMIT_BYTES });
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw asUserError(error);
|
|
48
|
+
}
|
|
47
49
|
console.info(`Created release commit: ${title}`);
|
|
48
50
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
1
2
|
import semver from 'semver';
|
|
2
3
|
const CANONICAL_SEMVER_PATTERN = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
3
4
|
function parseCanonicalSemver(version) {
|
|
4
5
|
const match = version.match(CANONICAL_SEMVER_PATTERN);
|
|
5
6
|
if (!match) {
|
|
6
|
-
throw new
|
|
7
|
+
throw new UserError(`Invalid semver version: '${version}'`);
|
|
7
8
|
}
|
|
8
9
|
const [, major = '', minor = '', patch = ''] = match;
|
|
9
10
|
return {
|
|
@@ -3,6 +3,7 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { extractVersion, readChangelogEntries } from "./changelogJsonUtils.js";
|
|
5
5
|
import { matchesAudience, renderReleaseNotesSingle } from "./renderReleaseNotes.js";
|
|
6
|
+
import { asUserError } from "./user-error-chaining.js";
|
|
6
7
|
export function createGithubRelease(options) {
|
|
7
8
|
const { tag, changelogJsonPath, isDryRun, sectionOrder } = options;
|
|
8
9
|
if (!existsSync(changelogJsonPath)) {
|
|
@@ -36,7 +37,12 @@ export function createGithubRelease(options) {
|
|
|
36
37
|
console.info(`[dry-run] Would run: gh ${args.join(' ')}`);
|
|
37
38
|
return { status: 'created' };
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
+
try {
|
|
41
|
+
execFileSync('gh', args, { stdio: 'inherit' });
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
throw asUserError(error);
|
|
45
|
+
}
|
|
40
46
|
return { status: 'created' };
|
|
41
47
|
}
|
|
42
48
|
export function createGithubReleases(tags, changelogJsonOutputPath, isDryRun, sectionOrder) {
|
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { StreamStyles } from '@williamthorsen/nmr-core';
|
|
2
|
+
export interface CreateGithubReleaseCommandOptions {
|
|
3
|
+
configPath: string | undefined;
|
|
4
|
+
isDryRun: boolean;
|
|
5
|
+
/** The tag names that `--tags` selects, or `undefined` for every release tag on HEAD. */
|
|
6
|
+
tags: string[] | undefined;
|
|
7
|
+
styles: StreamStyles;
|
|
8
|
+
}
|
|
2
9
|
/**
|
|
3
10
|
* Runs the CLI `create-github-release` command: creates GitHub Releases from `changelog.json` for the tags on HEAD, or
|
|
4
11
|
* a comma-separated `--tags` subset, without publishing to npm.
|
|
5
12
|
*
|
|
6
13
|
* A private workspace's tag is skipped with a warning and doesn't get a Release; an all-private tag set is a clean
|
|
7
|
-
* no-op. A project tag gets a Release whatever the root `package.json#private` says.
|
|
8
|
-
* against `invocationDir`.
|
|
14
|
+
* no-op. A project tag gets a Release whatever the root `package.json#private` says.
|
|
9
15
|
*/
|
|
10
|
-
export declare function createGithubReleaseCommand(
|
|
16
|
+
export declare function createGithubReleaseCommand({ configPath, isDryRun, tags, styles, }: CreateGithubReleaseCommandOptions): Promise<void>;
|
|
@@ -1,25 +1,12 @@
|
|
|
1
|
-
import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
2
|
-
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
|
-
import { configFlagSchema } from "./configFlagSchema.js";
|
|
4
1
|
import { createGithubReleases } from "./createGithubRelease.js";
|
|
5
2
|
import { deriveReleaseNotesConfig } from "./deriveReleaseNotesConfig.js";
|
|
6
3
|
import { formatPrivateSkip } from "./formatPrivateSkip.js";
|
|
7
4
|
import { loadUsableConfig } from "./loadValidatedConfig.js";
|
|
8
|
-
import { parseRequestedTags } from "./parseRequestedTags.js";
|
|
9
5
|
import { resolveCommandTags } from "./resolveCommandTags.js";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
...configFlagSchema,
|
|
13
|
-
isDryRun: { long: '--dry-run', type: 'boolean' },
|
|
14
|
-
tags: { long: '--tags', type: 'string' },
|
|
15
|
-
};
|
|
16
|
-
export async function createGithubReleaseCommand(argv, styles, invocationDir) {
|
|
17
|
-
const parsedArgs = parseArgsOrExit(argv, createGithubReleaseFlagSchema);
|
|
18
|
-
const configPath = resolveConfigFlag(parsedArgs.flags.config, invocationDir);
|
|
19
|
-
const { isDryRun } = parsedArgs.flags;
|
|
6
|
+
import { prefixUserError } from "./user-error-chaining.js";
|
|
7
|
+
export async function createGithubReleaseCommand({ configPath, isDryRun, tags, styles, }) {
|
|
20
8
|
const userConfig = await loadUsableConfig(styles.stderr, configPath);
|
|
21
|
-
const
|
|
22
|
-
const resolvedTags = resolveCommandTags(requestedTags, userConfig);
|
|
9
|
+
const resolvedTags = resolveCommandTags(tags, userConfig);
|
|
23
10
|
const releasableTags = resolvedTags.filter(isReleasable);
|
|
24
11
|
for (const resolvedTag of resolvedTags) {
|
|
25
12
|
if (!isReleasable(resolvedTag)) {
|
|
@@ -35,8 +22,7 @@ export async function createGithubReleaseCommand(argv, styles, invocationDir) {
|
|
|
35
22
|
outcome = createGithubReleases(releasableTags, changelogJsonOutputPath, isDryRun, sectionOrder);
|
|
36
23
|
}
|
|
37
24
|
catch (error) {
|
|
38
|
-
|
|
39
|
-
process.exit(1);
|
|
25
|
+
throw prefixUserError('Failed to create GitHub Releases', error);
|
|
40
26
|
}
|
|
41
27
|
if (outcome.skippedTags.length === 0) {
|
|
42
28
|
return;
|
package/dist/esm/createTags.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { formatGlyphLine, GIT_OUTPUT_LIMIT_BYTES, hasErrnoCode } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { formatGlyphLine, GIT_OUTPUT_LIMIT_BYTES, hasErrnoCode, UserError, } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { deleteFileIfExists } from "./deleteFileIfExists.js";
|
|
4
4
|
import { RELEASE_GLYPHS } from "./glyphs.js";
|
|
5
5
|
import { readReleaseTags, RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
|
|
6
|
+
import { asUserError } from "./user-error-chaining.js";
|
|
6
7
|
export function createTags(options) {
|
|
7
8
|
const { isDryRun, shouldSkipGitChecks, style } = options;
|
|
8
9
|
const tags = readReleaseTags();
|
|
@@ -26,13 +27,14 @@ export function createTags(options) {
|
|
|
26
27
|
createdTags.push(tag);
|
|
27
28
|
}
|
|
28
29
|
catch (error) {
|
|
30
|
+
const failure = asUserError(error);
|
|
29
31
|
if (createdTags.length > 0) {
|
|
30
32
|
console.warn('Tags created before failure:');
|
|
31
33
|
for (const tag of createdTags) {
|
|
32
34
|
console.warn(` ${tag}`);
|
|
33
35
|
}
|
|
34
36
|
}
|
|
35
|
-
throw
|
|
37
|
+
throw failure;
|
|
36
38
|
}
|
|
37
39
|
}
|
|
38
40
|
console.info('Created tags:');
|
|
@@ -50,8 +52,8 @@ function assertCleanWorkingTree() {
|
|
|
50
52
|
}
|
|
51
53
|
catch (error) {
|
|
52
54
|
if (hasErrnoCode(error, 'ENOENT')) {
|
|
53
|
-
throw error;
|
|
55
|
+
throw asUserError(error);
|
|
54
56
|
}
|
|
55
|
-
throw new
|
|
57
|
+
throw new UserError('Working tree is dirty. Commit or stash changes before tagging, or use `--no-git-checks` to skip this check.', { cause: error });
|
|
56
58
|
}
|
|
57
59
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { unlinkSync } from 'node:fs';
|
|
2
2
|
import { hasErrnoCode } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { asUserError } from "./user-error-chaining.js";
|
|
3
4
|
export function deleteFileIfExists(filePath) {
|
|
4
5
|
try {
|
|
5
6
|
unlinkSync(filePath);
|
|
@@ -8,6 +9,6 @@ export function deleteFileIfExists(filePath) {
|
|
|
8
9
|
if (hasErrnoCode(error, 'ENOENT')) {
|
|
9
10
|
return;
|
|
10
11
|
}
|
|
11
|
-
throw error;
|
|
12
|
+
throw asUserError(error);
|
|
12
13
|
}
|
|
13
14
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { isRecord, UserError } from '@williamthorsen/nmr-core';
|
|
4
|
+
import { chainUserError } from "./user-error-chaining.js";
|
|
5
5
|
export const DIRECTORY_VERSION_FILE = 'version.json';
|
|
6
6
|
const TAG_PREFIX_PATTERN = /^[\w.-]+$/;
|
|
7
7
|
export function deriveDirectoryConfig(declaredDirectory, root = process.cwd()) {
|
|
8
8
|
const label = `releases.${declaredDirectory.name}`;
|
|
9
9
|
if (statSync(path.resolve(root, declaredDirectory.path), { throwIfNoEntry: false })?.isDirectory() !== true) {
|
|
10
|
-
throw new
|
|
10
|
+
throw new UserError(`${label}: path '${declaredDirectory.path}' is not a directory; declare a directory that exists in the repo`);
|
|
11
11
|
}
|
|
12
12
|
const versionFile = resolveVersionFile(declaredDirectory.path, root);
|
|
13
13
|
assertVersionField(label, versionFile, root);
|
|
@@ -28,17 +28,17 @@ export function deriveDirectoryConfig(declaredDirectory, root = process.cwd()) {
|
|
|
28
28
|
function assertVersionField(label, versionFile, root) {
|
|
29
29
|
const absolutePath = path.resolve(root, versionFile);
|
|
30
30
|
if (!existsSync(absolutePath)) {
|
|
31
|
-
throw new
|
|
31
|
+
throw new UserError(`${label}: ${versionFile} not found; create it with the directory's current version, such as { "version": "0.0.0" }`);
|
|
32
32
|
}
|
|
33
33
|
let parsedJson;
|
|
34
34
|
try {
|
|
35
35
|
parsedJson = JSON.parse(readFileSync(absolutePath, 'utf8'));
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
|
-
throw
|
|
38
|
+
throw chainUserError(`${label}: failed to read ${versionFile}`, error);
|
|
39
39
|
}
|
|
40
40
|
if (!isRecord(parsedJson) || typeof parsedJson['version'] !== 'string') {
|
|
41
|
-
throw new
|
|
41
|
+
throw new UserError(`${label}: ${versionFile} does not contain a string 'version' field; add one`);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
function deriveTagPrefix(label, directoryPath) {
|
|
@@ -47,7 +47,7 @@ function deriveTagPrefix(label, directoryPath) {
|
|
|
47
47
|
}
|
|
48
48
|
const basename = path.posix.basename(directoryPath);
|
|
49
49
|
if (!TAG_PREFIX_PATTERN.test(basename)) {
|
|
50
|
-
throw new
|
|
50
|
+
throw new UserError(`${label}: the directory name '${basename}' cannot form a tag prefix; set tagPrefix, such as '${basename.replaceAll(/[^\w.-]+/g, '-')}-v'`);
|
|
51
51
|
}
|
|
52
52
|
return `${basename}-v`;
|
|
53
53
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { basename } from 'node:path';
|
|
3
|
-
import {
|
|
3
|
+
import { isRecord, UserError } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { isPublishableManifest } from "./isPublishableManifest.js";
|
|
5
|
-
import {
|
|
5
|
+
import { chainUserError } from "./user-error-chaining.js";
|
|
6
6
|
export function deriveWorkspaceConfig(workspacePath) {
|
|
7
7
|
const dir = basename(workspacePath);
|
|
8
8
|
const packageJsonPath = `${workspacePath}/package.json`;
|
|
@@ -11,11 +11,11 @@ export function deriveWorkspaceConfig(workspacePath) {
|
|
|
11
11
|
parsedJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
12
12
|
}
|
|
13
13
|
catch (error) {
|
|
14
|
-
throw
|
|
14
|
+
throw chainUserError(`Failed to read ${packageJsonPath}`, error);
|
|
15
15
|
}
|
|
16
16
|
const name = isRecord(parsedJson) ? parsedJson['name'] : undefined;
|
|
17
17
|
if (typeof name !== 'string' || name.length === 0) {
|
|
18
|
-
throw new
|
|
18
|
+
throw new UserError(`${packageJsonPath} is missing a 'name' field (required for tag derivation).`);
|
|
19
19
|
}
|
|
20
20
|
const unscopedName = stripNpmScope(name);
|
|
21
21
|
const isPublishable = isPublishableManifest(parsedJson);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { isRecord } from
|
|
3
|
+
import { isRecord } from '@williamthorsen/nmr-core';
|
|
4
4
|
export function detectPackageManager() {
|
|
5
5
|
const packageJsonPath = join(process.cwd(), 'package.json');
|
|
6
6
|
try {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
3
4
|
import { findMonorepoRoot } from '@williamthorsen/nmr-core/workspace';
|
|
4
5
|
export function enterRepoRoot(startDir = process.cwd()) {
|
|
5
6
|
const invocationDir = path.resolve(startDir);
|
|
6
7
|
const root = findMonorepoRoot(invocationDir) ?? findSinglePackageRoot(invocationDir);
|
|
7
8
|
if (root === undefined) {
|
|
8
|
-
throw new
|
|
9
|
+
throw new UserError(`No repo root found from ${invocationDir}: expected a pnpm-workspace.yaml in it or any parent directory, ` +
|
|
9
10
|
'or a package.json in it. Run release-kit from inside a monorepo or at the root of a single-package repo.');
|
|
10
11
|
}
|
|
11
12
|
process.chdir(root);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { GIT_OUTPUT_LIMIT_BYTES } from '@williamthorsen/nmr-core';
|
|
3
|
-
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
4
3
|
import { matchesTagPrefix } from "./matchesTagPrefix.js";
|
|
4
|
+
import { chainUserError } from "./user-error-chaining.js";
|
|
5
5
|
const FIELD_SEPARATOR = '\u{1F}';
|
|
6
6
|
const RECORD_SEPARATOR = '\u{1E}';
|
|
7
7
|
export function enumerateReleaseWindows(options) {
|
|
@@ -117,6 +117,6 @@ function runGit(args, description) {
|
|
|
117
117
|
}).trim();
|
|
118
118
|
}
|
|
119
119
|
catch (error) {
|
|
120
|
-
throw
|
|
120
|
+
throw chainUserError(`Failed to run ${description}`, error);
|
|
121
121
|
}
|
|
122
122
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isRecord } from
|
|
1
|
+
import { isRecord } from '@williamthorsen/nmr-core';
|
|
2
2
|
const BUILT_IN_DEV_CONDITION = 'source';
|
|
3
3
|
export function findMissingManifestPaths(manifest, packedFiles, devExportConditions) {
|
|
4
4
|
const packedSet = new Set(packedFiles);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { isRecord } from
|
|
3
|
+
import { isRecord } from '@williamthorsen/nmr-core';
|
|
4
4
|
export function hasPrettierConfig() {
|
|
5
5
|
const cwd = process.cwd();
|
|
6
6
|
for (const file of PRETTIER_CONFIG_FILES) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { printError, printSkip, printStep, printSuccess, reportWriteResult, } from '@williamthorsen/nmr-core';
|
|
1
|
+
import { isAnticipatedFailure, printError, printSkip, printStep, printSuccess, reportWriteResult, } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
3
|
import { hasPackageJson, isGitRepo, usesPnpm } from "./checks.js";
|
|
4
4
|
import { detectRepoType } from "./detectRepoType.js";
|
|
@@ -19,6 +19,9 @@ export function initCommand({ isDryRun, shouldForce, styles, shouldScaffoldConfi
|
|
|
19
19
|
eligible = checkEligibility(styles);
|
|
20
20
|
}
|
|
21
21
|
catch (error) {
|
|
22
|
+
if (!isAnticipatedFailure(error)) {
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
22
25
|
printError(`Eligibility check failed: ${describeError(error)}`, styles.stderr);
|
|
23
26
|
return 1;
|
|
24
27
|
}
|
|
@@ -30,6 +33,9 @@ export function initCommand({ isDryRun, shouldForce, styles, shouldScaffoldConfi
|
|
|
30
33
|
repoType = detectRepoType();
|
|
31
34
|
}
|
|
32
35
|
catch (error) {
|
|
36
|
+
if (!isAnticipatedFailure(error)) {
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
33
39
|
printError(`Failed to detect repo type: ${describeError(error)}`, styles.stderr);
|
|
34
40
|
return 1;
|
|
35
41
|
}
|
|
@@ -46,6 +52,9 @@ export function initCommand({ isDryRun, shouldForce, styles, shouldScaffoldConfi
|
|
|
46
52
|
results = scaffoldFiles({ repoType, isDryRun, shouldOverwrite: shouldForce, publishable, shouldScaffoldConfig });
|
|
47
53
|
}
|
|
48
54
|
catch (error) {
|
|
55
|
+
if (!isAnticipatedFailure(error)) {
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
49
58
|
printError(`Failed to scaffold files: ${describeError(error)}`, styles.stderr);
|
|
50
59
|
return 1;
|
|
51
60
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { printError, printSkip, printStep, printSuccess, reportTemplateUpdate, updateManagedFile, } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { isAnticipatedFailure, printError, printSkip, printStep, printSuccess, reportTemplateUpdate, updateManagedFile, } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
4
|
import { readSyncLabelsWorkflowTemplate } from "../sync-labels/templates.js";
|
|
5
5
|
import { detectRepoType } from "./detectRepoType.js";
|
|
@@ -18,6 +18,9 @@ export function updateTemplatesCommand({ isDryRun, styles }) {
|
|
|
18
18
|
eligible = checkEligibility(styles);
|
|
19
19
|
}
|
|
20
20
|
catch (error) {
|
|
21
|
+
if (!isAnticipatedFailure(error)) {
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
21
24
|
printError(`Eligibility check failed: ${describeError(error)}`, styles.stderr);
|
|
22
25
|
return 1;
|
|
23
26
|
}
|
|
@@ -29,6 +32,9 @@ export function updateTemplatesCommand({ isDryRun, styles }) {
|
|
|
29
32
|
repoType = detectRepoType();
|
|
30
33
|
}
|
|
31
34
|
catch (error) {
|
|
35
|
+
if (!isAnticipatedFailure(error)) {
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
32
38
|
printError(`Failed to detect repo type: ${describeError(error)}`, styles.stderr);
|
|
33
39
|
return 1;
|
|
34
40
|
}
|
|
@@ -43,6 +49,9 @@ export function updateTemplatesCommand({ isDryRun, styles }) {
|
|
|
43
49
|
results = files.map(([filePath, content]) => updateManagedFile(filePath, content, { isDryRun }));
|
|
44
50
|
}
|
|
45
51
|
catch (error) {
|
|
52
|
+
if (!isAnticipatedFailure(error)) {
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
46
55
|
printError(`Failed to render workflows: ${describeError(error)}`, styles.stderr);
|
|
47
56
|
return 1;
|
|
48
57
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isRecord } from
|
|
1
|
+
import { isRecord } from '@williamthorsen/nmr-core';
|
|
2
2
|
export function isPublishableManifest(manifest) {
|
|
3
3
|
const privateField = isRecord(manifest) ? manifest['private'] : undefined;
|
|
4
4
|
return privateField === undefined || privateField === false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { isRecord } from '@williamthorsen/nmr-core';
|
|
2
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
|
-
import { isRecord } from "./typeGuards.js";
|
|
4
4
|
const VALID_AUDIENCE_VALUES = new Set(['all', 'dev', 'skip']);
|
|
5
5
|
const V1_SUPPORTED_AUDIENCE_VALUES = new Set(['skip']);
|
|
6
6
|
const SCHEMA_KEY = '$schema';
|
package/dist/esm/loadConfig.js
CHANGED
|
@@ -9,8 +9,8 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
9
9
|
import { existsSync, readFileSync } from 'node:fs';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { pathToFileURL } from 'node:url';
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
12
|
+
import { isRecord, UserError } from '@williamthorsen/nmr-core';
|
|
13
|
+
import { chainUserError } from "./user-error-chaining.js";
|
|
14
14
|
export const ROOT_PACKAGE_JSON_PATH = 'package.json';
|
|
15
15
|
export function readRootPackageVersion() {
|
|
16
16
|
const absolutePath = path.resolve(process.cwd(), ROOT_PACKAGE_JSON_PATH);
|
|
@@ -22,14 +22,14 @@ export function readRootPackageVersion() {
|
|
|
22
22
|
contents = readFileSync(absolutePath, 'utf8');
|
|
23
23
|
}
|
|
24
24
|
catch (error) {
|
|
25
|
-
throw
|
|
25
|
+
throw chainUserError(`Failed to read root ${ROOT_PACKAGE_JSON_PATH}`, error);
|
|
26
26
|
}
|
|
27
27
|
let parsedJson;
|
|
28
28
|
try {
|
|
29
29
|
parsedJson = JSON.parse(contents);
|
|
30
30
|
}
|
|
31
31
|
catch (error) {
|
|
32
|
-
throw
|
|
32
|
+
throw chainUserError(`Failed to parse root ${ROOT_PACKAGE_JSON_PATH}`, error);
|
|
33
33
|
}
|
|
34
34
|
if (!isRecord(parsedJson)) {
|
|
35
35
|
return { doesExist: true, version: undefined };
|
|
@@ -43,12 +43,12 @@ export async function loadConfig(configPath) {
|
|
|
43
43
|
if (configPath === undefined) {
|
|
44
44
|
return undefined;
|
|
45
45
|
}
|
|
46
|
-
throw new
|
|
46
|
+
throw new UserError(`Config file not found: ${absoluteConfigPath}`);
|
|
47
47
|
}
|
|
48
48
|
const importedModule = await import(__rewriteRelativeImportExtension(pathToFileURL(absoluteConfigPath).href));
|
|
49
49
|
const resolvedConfig = isRecord(importedModule) ? (importedModule['default'] ?? importedModule['config']) : undefined;
|
|
50
50
|
if (resolvedConfig === undefined) {
|
|
51
|
-
throw new
|
|
51
|
+
throw new UserError('Config file must have a default export or a named `config` export (e.g., `export default { ... }` or `export const config = { ... }`)');
|
|
52
52
|
}
|
|
53
53
|
return resolvedConfig;
|
|
54
54
|
}
|
|
@@ -12,6 +12,11 @@ export type ConfigProblem = {
|
|
|
12
12
|
kind: 'validation';
|
|
13
13
|
errors: string[];
|
|
14
14
|
};
|
|
15
|
+
/** Reports a config file that a CLI command cannot use, carrying the problem for `reportConfigProblem` to render. */
|
|
16
|
+
export declare class ConfigProblemError extends Error {
|
|
17
|
+
readonly problem: ConfigProblem;
|
|
18
|
+
constructor(problem: ConfigProblem);
|
|
19
|
+
}
|
|
15
20
|
/**
|
|
16
21
|
* Outcome of loading and validating the consumer config file. `missing` is not an error here; the caller decides
|
|
17
22
|
* whether an absent config file is one.
|
|
@@ -47,7 +52,7 @@ export declare function loadValidatedConfig(configPath?: string): Promise<LoadVa
|
|
|
47
52
|
* Loads and validates the config for a CLI command, returning it, or `undefined` when the default config file does
|
|
48
53
|
* not exist.
|
|
49
54
|
*
|
|
50
|
-
*
|
|
55
|
+
* Throws a `ConfigProblemError` for an unusable config, whether the file failed to load or failed validation, and
|
|
51
56
|
* whether it was named by the caller or the default one. Validation warnings print to stderr.
|
|
52
57
|
*/
|
|
53
58
|
export declare function loadUsableConfig(stderrStyle: OutputStyle, configPath?: string): Promise<ReleaseKitConfig | undefined>;
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { formatStatusLine, printError, reportError } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
|
+
import { BIN } from "./bin.js";
|
|
3
4
|
import { CONFIG_FILE_PATH, loadConfig } from "./loadConfig.js";
|
|
4
5
|
import { validateConfig } from "./validateConfig.js";
|
|
6
|
+
export class ConfigProblemError extends Error {
|
|
7
|
+
problem;
|
|
8
|
+
constructor(problem) {
|
|
9
|
+
super('Invalid config');
|
|
10
|
+
this.name = 'ConfigProblemError';
|
|
11
|
+
this.problem = problem;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
5
14
|
export async function loadValidatedConfig(configPath) {
|
|
6
15
|
const configFilePath = configPath ?? CONFIG_FILE_PATH;
|
|
7
16
|
let rawConfig;
|
|
@@ -23,8 +32,7 @@ export async function loadValidatedConfig(configPath) {
|
|
|
23
32
|
export async function loadUsableConfig(stderrStyle, configPath) {
|
|
24
33
|
const result = await loadValidatedConfig(configPath);
|
|
25
34
|
if (result.status === 'invalid') {
|
|
26
|
-
|
|
27
|
-
process.exit(1);
|
|
35
|
+
throw new ConfigProblemError(result.problem);
|
|
28
36
|
}
|
|
29
37
|
if (result.status === 'missing') {
|
|
30
38
|
return undefined;
|
|
@@ -34,7 +42,7 @@ export async function loadUsableConfig(stderrStyle, configPath) {
|
|
|
34
42
|
}
|
|
35
43
|
export function reportConfigProblem(problem, stderrStyle) {
|
|
36
44
|
if (problem.kind === 'load') {
|
|
37
|
-
reportError(`Failed to load config: ${problem.message}`);
|
|
45
|
+
reportError(BIN, `Failed to load config: ${problem.message}`);
|
|
38
46
|
return;
|
|
39
47
|
}
|
|
40
48
|
process.stderr.write('Invalid config:\n');
|
package/dist/esm/mergeConfig.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
1
2
|
import { DEFAULT_CHANGELOG_JSON_CONFIG, DEFAULT_PROJECT_TAG_PREFIX, DEFAULT_RELEASE_NOTES_CONFIG, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES, } from "./defaults.js";
|
|
2
3
|
import { deriveDirectoryConfig } from "./deriveDirectoryConfig.js";
|
|
3
4
|
import { deriveWorkspaceConfig } from "./deriveWorkspaceConfig.js";
|
|
@@ -45,10 +46,10 @@ export function mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage) {
|
|
|
45
46
|
}
|
|
46
47
|
export function mergeSinglePackageConfig(userConfig) {
|
|
47
48
|
if (userConfig?.project !== undefined) {
|
|
48
|
-
throw new
|
|
49
|
+
throw new UserError('project block is not supported in single-package mode');
|
|
49
50
|
}
|
|
50
51
|
if (userConfig?.releases !== undefined) {
|
|
51
|
-
throw new
|
|
52
|
+
throw new UserError('releases block is not supported in single-package mode');
|
|
52
53
|
}
|
|
53
54
|
const workTypes = resolveWorkTypes(userConfig?.workTypes);
|
|
54
55
|
const versionPatterns = userConfig?.versionPatterns === undefined ? { ...DEFAULT_VERSION_PATTERNS } : { ...userConfig.versionPatterns };
|
|
@@ -104,10 +105,10 @@ function resolveProjectConfig(declaredComposite, rootPackage, workspaces) {
|
|
|
104
105
|
}
|
|
105
106
|
const subject = declaredComposite.name === undefined ? 'project block' : `composite release '${declaredComposite.name}'`;
|
|
106
107
|
if (rootPackage === undefined || !rootPackage.doesExist) {
|
|
107
|
-
throw new
|
|
108
|
+
throw new UserError(`${subject} requires a root ${ROOT_PACKAGE_JSON_PATH}; create one with a 'version' field at the repo root`);
|
|
108
109
|
}
|
|
109
110
|
if (rootPackage.version === undefined) {
|
|
110
|
-
throw new
|
|
111
|
+
throw new UserError(`${subject} requires root ${ROOT_PACKAGE_JSON_PATH} to have a 'version' field; add a 'version' field to the root package.json`);
|
|
111
112
|
}
|
|
112
113
|
return {
|
|
113
114
|
paths: declaredComposite.paths ?? workspaces.flatMap((workspace) => workspace.paths),
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
1
2
|
import { ROOT_PACKAGE_JSON_PATH } from "./loadConfig.js";
|
|
2
3
|
import { getTargetName, normalizeWorkspacePath } from "./resolveReleaseDeclaration.js";
|
|
3
4
|
export function assertLegacyIdentityDoesNotMatchCurrent(dir, currentName, currentTagPrefix, legacyIdentities) {
|
|
4
5
|
for (const identity of legacyIdentities) {
|
|
5
6
|
if (identity.name === currentName && identity.tagPrefix === currentTagPrefix) {
|
|
6
|
-
throw new
|
|
7
|
+
throw new UserError(`Workspace '${dir}': legacyIdentities must not match the current identity ` +
|
|
7
8
|
`(name='${currentName}', tagPrefix='${currentTagPrefix}'). ` +
|
|
8
9
|
'The current identity is always searched; listing it again is a no-op.');
|
|
9
10
|
}
|
|
@@ -31,7 +32,7 @@ export function assertNoSharedVersionFiles(workspaces, directories, project) {
|
|
|
31
32
|
for (const [index, directory] of directories.entries()) {
|
|
32
33
|
const peer = owners.slice(index + 1).find((owner) => owner.versionFiles.includes(directory.versionFile));
|
|
33
34
|
if (peer !== undefined) {
|
|
34
|
-
throw new
|
|
35
|
+
throw new UserError(`directory release '${directory.name}' and ${peer.label} share the version file '${directory.versionFile}', ` +
|
|
35
36
|
"so each release would overwrite the other's version and changelog; declare only one of them");
|
|
36
37
|
}
|
|
37
38
|
}
|
|
@@ -84,7 +85,7 @@ export function assertNoTagPrefixCollisions(workspaces, directories, retiredPack
|
|
|
84
85
|
if (a === undefined || b === undefined || a.owner === b.owner)
|
|
85
86
|
continue;
|
|
86
87
|
if (isPrefixCollision(a.prefix, b.prefix)) {
|
|
87
|
-
throw new
|
|
88
|
+
throw new UserError(`Tag prefix collision: '${a.prefix}' (${a.label}) and '${b.prefix}' (${b.label}). ` +
|
|
88
89
|
'One prefix is identical to or a strict prefix of the other; ' +
|
|
89
90
|
'a tag under the longer prefix can then match the shorter one and be counted among its releases.');
|
|
90
91
|
}
|
|
@@ -99,7 +100,7 @@ export function assertRetiredPackagesDoNotCollideWithActive(workspaces, retiredP
|
|
|
99
100
|
for (const retiredPackage of retiredPackages) {
|
|
100
101
|
const activeWorkspace = workspaceByDerivedPrefix.get(retiredPackage.tagPrefix);
|
|
101
102
|
if (activeWorkspace !== undefined) {
|
|
102
|
-
throw new
|
|
103
|
+
throw new UserError(`retiredPackages: tagPrefix '${retiredPackage.tagPrefix}' collides with active workspace '${activeWorkspace.dir}' ` +
|
|
103
104
|
`(derived prefix '${activeWorkspace.tagPrefix}'). A retired package's tagPrefix cannot belong to an active workspace.`);
|
|
104
105
|
}
|
|
105
106
|
}
|
|
@@ -117,7 +118,7 @@ export function assertUniqueTagPrefixes(workspaces) {
|
|
|
117
118
|
}
|
|
118
119
|
for (const [prefix, paths] of pathsByPrefix) {
|
|
119
120
|
if (paths.length > 1) {
|
|
120
|
-
throw new
|
|
121
|
+
throw new UserError(`Duplicate tag prefix '${prefix}' for workspaces: ${paths.join(', ')}`);
|
|
121
122
|
}
|
|
122
123
|
}
|
|
123
124
|
}
|
package/dist/esm/npmRegistry.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { isRecord } from
|
|
2
|
+
import { isRecord } from '@williamthorsen/nmr-core';
|
|
3
3
|
export const AUTH_ERROR_CODES = new Set(['E401', 'ENEEDAUTH']);
|
|
4
4
|
export const UNREACHABLE_ERROR_CODES = new Set([
|
|
5
5
|
'EAI_AGAIN',
|