@williamthorsen/release-kit 16.1.0 → 16.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.meta/changelog.json +45 -0
- package/.readyup/kits/default.js +2 -2
- package/.readyup/kits/npm-auto-publish.js +1 -1
- package/.readyup/manifest.json +6 -6
- package/CHANGELOG.md +16 -0
- package/README.md +3 -5
- package/bin/release-kit.js +7 -3
- package/dist/esm/assertCleanWorkingTree.js +14 -7
- package/dist/esm/assertReleasesOnNpm.js +11 -2
- package/dist/esm/assertTaggedBaseline.js +2 -1
- package/dist/esm/assertTarballsComplete.js +15 -3
- package/dist/esm/buildChangelogEntries.js +2 -2
- package/dist/esm/buildDependencyGraph.js +5 -4
- package/dist/esm/bumpVersion.js +3 -2
- package/dist/esm/changelogOverrides.js +3 -2
- package/dist/esm/cli.js +11 -0
- package/dist/esm/commitCommand.js +11 -5
- package/dist/esm/compareVersions.js +2 -1
- package/dist/esm/createGithubRelease.js +7 -1
- package/dist/esm/createGithubReleaseCommand.js +3 -4
- package/dist/esm/createTags.js +6 -4
- package/dist/esm/deleteFileIfExists.js +2 -1
- package/dist/esm/deriveDirectoryConfig.js +7 -6
- package/dist/esm/deriveWorkspaceConfig.js +4 -3
- package/dist/esm/enterRepoRoot.js +2 -1
- package/dist/esm/enumerateReleaseWindows.js +2 -2
- package/dist/esm/init/initCommand.js +10 -1
- package/dist/esm/init/updateTemplatesCommand.js +10 -1
- package/dist/esm/loadConfig.js +6 -5
- package/dist/esm/mergeConfig.js +5 -4
- package/dist/esm/monorepoConfigAssertions.js +6 -5
- package/dist/esm/packFromCleanCheckout.js +12 -3
- package/dist/esm/planVersionBump.js +6 -5
- package/dist/esm/prepareCommand.js +12 -22
- package/dist/esm/prepareComposite.js +2 -1
- package/dist/esm/prepareRunContext.js +2 -2
- package/dist/esm/previewTagPrefixes.js +2 -2
- package/dist/esm/publish.js +7 -1
- package/dist/esm/publishCommand.js +4 -18
- package/dist/esm/pushCommand.js +5 -12
- package/dist/esm/pushRelease.js +18 -6
- package/dist/esm/releaseFiles.js +2 -2
- package/dist/esm/releasePlan.js +2 -2
- package/dist/esm/resolveCommandTags.d.ts +4 -4
- package/dist/esm/resolveCommandTags.js +3 -5
- package/dist/esm/resolveReleaseDeclaration.js +9 -8
- package/dist/esm/resolveReleaseTags.js +11 -4
- package/dist/esm/resolveReleaseTargets.js +2 -1
- package/dist/esm/runCli.d.ts +5 -0
- package/dist/esm/runCli.js +224 -0
- package/dist/esm/showTagPrefixesCommand.js +5 -3
- package/dist/esm/sync-labels/generateCommand.js +4 -1
- package/dist/esm/sync-labels/initCommand.js +7 -1
- package/dist/esm/sync-labels/presets.js +7 -7
- package/dist/esm/sync-labels/resolveLabels.js +4 -3
- package/dist/esm/sync-labels/templates.js +2 -2
- package/dist/esm/tagCommand.js +2 -9
- package/dist/esm/user-error-chaining.d.ts +13 -0
- package/dist/esm/user-error-chaining.js +11 -0
- package/dist/esm/validateOverridesCommand.js +7 -1
- package/package.json +6 -6
- package/dist/esm/bin/release-kit.js +0 -240
- /package/dist/esm/{bin/release-kit.d.ts → cli.d.ts} +0 -0
|
@@ -1,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 { 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
|
}
|
package/dist/esm/loadConfig.js
CHANGED
|
@@ -9,8 +9,9 @@ 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 {
|
|
12
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
13
13
|
import { isRecord } from "./typeGuards.js";
|
|
14
|
+
import { chainUserError } from "./user-error-chaining.js";
|
|
14
15
|
export const ROOT_PACKAGE_JSON_PATH = 'package.json';
|
|
15
16
|
export function readRootPackageVersion() {
|
|
16
17
|
const absolutePath = path.resolve(process.cwd(), ROOT_PACKAGE_JSON_PATH);
|
|
@@ -22,14 +23,14 @@ export function readRootPackageVersion() {
|
|
|
22
23
|
contents = readFileSync(absolutePath, 'utf8');
|
|
23
24
|
}
|
|
24
25
|
catch (error) {
|
|
25
|
-
throw
|
|
26
|
+
throw chainUserError(`Failed to read root ${ROOT_PACKAGE_JSON_PATH}`, error);
|
|
26
27
|
}
|
|
27
28
|
let parsedJson;
|
|
28
29
|
try {
|
|
29
30
|
parsedJson = JSON.parse(contents);
|
|
30
31
|
}
|
|
31
32
|
catch (error) {
|
|
32
|
-
throw
|
|
33
|
+
throw chainUserError(`Failed to parse root ${ROOT_PACKAGE_JSON_PATH}`, error);
|
|
33
34
|
}
|
|
34
35
|
if (!isRecord(parsedJson)) {
|
|
35
36
|
return { doesExist: true, version: undefined };
|
|
@@ -43,12 +44,12 @@ export async function loadConfig(configPath) {
|
|
|
43
44
|
if (configPath === undefined) {
|
|
44
45
|
return undefined;
|
|
45
46
|
}
|
|
46
|
-
throw new
|
|
47
|
+
throw new UserError(`Config file not found: ${absoluteConfigPath}`);
|
|
47
48
|
}
|
|
48
49
|
const importedModule = await import(__rewriteRelativeImportExtension(pathToFileURL(absoluteConfigPath).href));
|
|
49
50
|
const resolvedConfig = isRecord(importedModule) ? (importedModule['default'] ?? importedModule['config']) : undefined;
|
|
50
51
|
if (resolvedConfig === undefined) {
|
|
51
|
-
throw new
|
|
52
|
+
throw new UserError('Config file must have a default export or a named `config` export (e.g., `export default { ... }` or `export const config = { ... }`)');
|
|
52
53
|
}
|
|
53
54
|
return resolvedConfig;
|
|
54
55
|
}
|
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
|
}
|
|
@@ -2,8 +2,10 @@ import { spawnSync } from 'node:child_process';
|
|
|
2
2
|
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs';
|
|
3
3
|
import { tmpdir } from 'node:os';
|
|
4
4
|
import { join } from 'node:path';
|
|
5
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
5
6
|
import { resolveExecutable } from "./detectPackageManager.js";
|
|
6
7
|
import { isRecord } from "./typeGuards.js";
|
|
8
|
+
import { chainUserError } from "./user-error-chaining.js";
|
|
7
9
|
export function packFromCleanCheckout(workspacePaths, packageManager) {
|
|
8
10
|
const repoRoot = runStep('find the repository root', 'git', ['rev-parse', '--show-toplevel'], process.cwd()).trim();
|
|
9
11
|
const headSha = runStep('resolve HEAD', 'git', ['rev-parse', 'HEAD'], repoRoot).trim();
|
|
@@ -51,7 +53,14 @@ const PACKAGE_MANAGER_COMMANDS = {
|
|
|
51
53
|
},
|
|
52
54
|
};
|
|
53
55
|
function declaresPrepublishOnly(packageDir) {
|
|
54
|
-
const
|
|
56
|
+
const manifestPath = join(packageDir, 'package.json');
|
|
57
|
+
let manifest;
|
|
58
|
+
try {
|
|
59
|
+
manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw chainUserError(`Failed to parse ${manifestPath}`, error);
|
|
63
|
+
}
|
|
55
64
|
const scripts = isRecord(manifest) ? manifest['scripts'] : undefined;
|
|
56
65
|
return isRecord(scripts) && typeof scripts['prepublishOnly'] === 'string';
|
|
57
66
|
}
|
|
@@ -59,7 +68,7 @@ function findTarball(packDir, workspacePath) {
|
|
|
59
68
|
const tarballs = readdirSync(packDir).filter((name) => name.endsWith('.tgz'));
|
|
60
69
|
const [tarball] = tarballs;
|
|
61
70
|
if (tarballs.length !== 1 || tarball === undefined) {
|
|
62
|
-
throw new
|
|
71
|
+
throw new UserError(`Could not pack ${workspacePath}: expected one tarball, found ${tarballs.length}.`);
|
|
63
72
|
}
|
|
64
73
|
return join(packDir, tarball);
|
|
65
74
|
}
|
|
@@ -77,5 +86,5 @@ function runStep(step, command, args, cwd) {
|
|
|
77
86
|
}
|
|
78
87
|
const outcome = result.error === undefined ? `exited with status ${result.status}` : result.error.message;
|
|
79
88
|
const output = [result.stdout, result.stderr].filter((text) => text !== '').join('\n');
|
|
80
|
-
throw new
|
|
89
|
+
throw new UserError(`Could not ${step}: \`${[command, ...args].join(' ')}\` ${outcome}.${output === '' ? '' : `\n${output}`}`);
|
|
81
90
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { bumpVersion } from "./bumpVersion.js";
|
|
4
4
|
import { isForwardVersion } from "./compareVersions.js";
|
|
5
|
+
import { chainUserError } from "./user-error-chaining.js";
|
|
5
6
|
function isPackageJson(value) {
|
|
6
7
|
return typeof value === 'object' && value !== null && 'version' in value && typeof value.version === 'string';
|
|
7
8
|
}
|
|
@@ -19,7 +20,7 @@ export function planVersionSet(packageFiles, newVersion) {
|
|
|
19
20
|
const { firstFile, firstPackageJson } = readPrimaryPackage(packageFiles);
|
|
20
21
|
const currentVersion = firstPackageJson.version;
|
|
21
22
|
if (!isForwardVersion(currentVersion, newVersion)) {
|
|
22
|
-
throw new
|
|
23
|
+
throw new UserError(`--set-version ${newVersion} is not greater than current version ${currentVersion}`);
|
|
23
24
|
}
|
|
24
25
|
return {
|
|
25
26
|
currentVersion,
|
|
@@ -50,17 +51,17 @@ function readPackageJson(filePath) {
|
|
|
50
51
|
content = readFileSync(filePath, 'utf8');
|
|
51
52
|
}
|
|
52
53
|
catch (error) {
|
|
53
|
-
throw
|
|
54
|
+
throw chainUserError(`Failed to read ${filePath}`, error);
|
|
54
55
|
}
|
|
55
56
|
let parsedJson;
|
|
56
57
|
try {
|
|
57
58
|
parsedJson = JSON.parse(content);
|
|
58
59
|
}
|
|
59
60
|
catch (error) {
|
|
60
|
-
throw
|
|
61
|
+
throw chainUserError(`Failed to parse JSON in ${filePath}`, error);
|
|
61
62
|
}
|
|
62
63
|
if (!isPackageJson(parsedJson)) {
|
|
63
|
-
throw new
|
|
64
|
+
throw new UserError(`No valid 'version' field found in ${filePath}`);
|
|
64
65
|
}
|
|
65
66
|
return parsedJson;
|
|
66
67
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
|
-
import { formatGlyphLine, reportError } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { formatGlyphLine, isAnticipatedFailure, reportError, UserError, } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
4
|
import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
|
|
5
5
|
import { assertReleasesOnNpm } from "./assertReleasesOnNpm.js";
|
|
@@ -15,6 +15,8 @@ import { applyReleasePlan } from "./releasePlan.js";
|
|
|
15
15
|
import { reportPrepare } from "./reportPrepare.js";
|
|
16
16
|
import { resolveConfigFlag } from "./resolveConfigFlag.js";
|
|
17
17
|
import { isSinglePackageTargets, PROJECT_TARGET_ID, resolveReleaseTargets, SINGLE_PACKAGE_TARGET_ID, } from "./resolveReleaseTargets.js";
|
|
18
|
+
import { prefixUserError } from "./user-error-chaining.js";
|
|
19
|
+
const UNCHANGED_TREE_NOTE = 'The command did not write any files; the working tree is unchanged.';
|
|
18
20
|
export async function prepareCommand(argv, styles, invocationDir) {
|
|
19
21
|
const { configPath: configFlag, isDryRun, shouldForce, shouldSkipGitChecks, bumpOverride, onlyNames, setVersion, shouldIncludeReleaseNotes, } = parseArgs(argv);
|
|
20
22
|
const options = {
|
|
@@ -26,13 +28,7 @@ export async function prepareCommand(argv, styles, invocationDir) {
|
|
|
26
28
|
console.info(`\n${formatGlyphLine(RELEASE_GLYPHS, styles.stdout, 'dryRun', 'DRY RUN: release-kit will not modify any files')}\n`);
|
|
27
29
|
}
|
|
28
30
|
if (!isDryRun && !shouldSkipGitChecks) {
|
|
29
|
-
|
|
30
|
-
assertCleanWorkingTree();
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
reportError(describeError(error));
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
31
|
+
assertCleanWorkingTree();
|
|
36
32
|
}
|
|
37
33
|
const configResult = await loadValidatedConfig(resolveConfigFlag(configFlag, invocationDir));
|
|
38
34
|
if (configResult.status === 'invalid') {
|
|
@@ -49,8 +45,7 @@ export async function prepareCommand(argv, styles, invocationDir) {
|
|
|
49
45
|
workspaceDiscovery = discoverWorkspaces();
|
|
50
46
|
}
|
|
51
47
|
catch (error) {
|
|
52
|
-
|
|
53
|
-
process.exit(1);
|
|
48
|
+
throw prefixUserError('Failed to discover workspaces', error);
|
|
54
49
|
}
|
|
55
50
|
if (workspaceDiscovery.kind === 'empty') {
|
|
56
51
|
reportError(`No workspace package to release. ${describeEmptyWorkspace(workspaceDiscovery)}`);
|
|
@@ -91,8 +86,7 @@ function resolveTargetsOrExit(userConfig, discovery, onlyNames, setVersion) {
|
|
|
91
86
|
config = resolveReleaseTargets(userConfig, discovery);
|
|
92
87
|
}
|
|
93
88
|
catch (error) {
|
|
94
|
-
|
|
95
|
-
process.exit(1);
|
|
89
|
+
throw prefixUserError('Failed to resolve workspaces', error);
|
|
96
90
|
}
|
|
97
91
|
if (setVersion !== undefined && config.targets.some((target) => target.kind === 'composite')) {
|
|
98
92
|
reportError('--set-version cannot be combined with a project release. ' +
|
|
@@ -129,22 +123,18 @@ function runAndReport(computePlan, isDryRun, style) {
|
|
|
129
123
|
plan = computePlan();
|
|
130
124
|
}
|
|
131
125
|
catch (error) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
126
|
+
if (isAnticipatedFailure(error)) {
|
|
127
|
+
throw new UserError(`${error.message}\n${UNCHANGED_TREE_NOTE}`, { cause: error });
|
|
128
|
+
}
|
|
129
|
+
process.stderr.write(`${UNCHANGED_TREE_NOTE}\n`);
|
|
130
|
+
throw error;
|
|
135
131
|
}
|
|
136
132
|
if (isDryRun) {
|
|
137
133
|
process.stdout.write(reportPrepare(plan, { applied: false, style }) + '\n');
|
|
138
134
|
reportPlanFiles(plan, true);
|
|
139
135
|
return;
|
|
140
136
|
}
|
|
141
|
-
|
|
142
|
-
applyReleasePlan(plan);
|
|
143
|
-
}
|
|
144
|
-
catch (error) {
|
|
145
|
-
reportError(describeError(error));
|
|
146
|
-
process.exit(1);
|
|
147
|
-
}
|
|
137
|
+
applyReleasePlan(plan);
|
|
148
138
|
const formatError = runFormatCommand(plan.formatCommand);
|
|
149
139
|
process.stdout.write(reportPrepare(plan, { applied: true, style, ...(formatError !== undefined && { formatError }) }) + '\n');
|
|
150
140
|
reportPlanFiles(plan, false);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { UserError } from '@williamthorsen/nmr-core';
|
|
1
2
|
import { assertTaggedBaseline } from "./assertTaggedBaseline.js";
|
|
2
3
|
import { collect } from "./prepareRunContext.js";
|
|
3
4
|
import { buildSkippedTargetResult, decideTargetRelease, planTargetRelease, readTargetHistory, } from "./prepareTarget.js";
|
|
@@ -24,7 +25,7 @@ export function prepareComposite(compositeTarget, context, options, collectedRel
|
|
|
24
25
|
return buildSkippedTargetResult(compositeTarget, history, decision.skipReason);
|
|
25
26
|
}
|
|
26
27
|
if (decision.setVersion !== undefined) {
|
|
27
|
-
throw new
|
|
28
|
+
throw new UserError('A composite target cannot take --set-version');
|
|
28
29
|
}
|
|
29
30
|
const plannedRelease = planTargetRelease({
|
|
30
31
|
...context,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
2
1
|
import { isRepoRoot } from "./prepareTarget.js";
|
|
2
|
+
import { prefixUserError } from "./user-error-chaining.js";
|
|
3
3
|
export function collect(collectedReleases, plannedRelease) {
|
|
4
4
|
collectedReleases.tags.push(plannedRelease.tag);
|
|
5
5
|
collectedReleases.writes.push(...plannedRelease.writes);
|
|
@@ -34,6 +34,6 @@ export function runStage(stage, fn) {
|
|
|
34
34
|
return fn();
|
|
35
35
|
}
|
|
36
36
|
catch (error) {
|
|
37
|
-
throw
|
|
37
|
+
throw prefixUserError(stage, error);
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { basename } from 'node:path';
|
|
3
|
-
import { GIT_OUTPUT_LIMIT_BYTES } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { GIT_OUTPUT_LIMIT_BYTES, UserError } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
5
5
|
import { DEFAULT_PROJECT_TAG_PREFIX } from "./defaults.js";
|
|
6
6
|
import { deriveDirectoryConfig } from "./deriveDirectoryConfig.js";
|
|
@@ -12,7 +12,7 @@ import { normalizeWorkspacePath } from "./resolveReleaseDeclaration.js";
|
|
|
12
12
|
export function previewTagPrefixes(config) {
|
|
13
13
|
const workspace = discoverWorkspaces();
|
|
14
14
|
if (workspace.kind === 'empty') {
|
|
15
|
-
throw new
|
|
15
|
+
throw new UserError(`No workspace package to preview. ${describeEmptyWorkspace(workspace)}`);
|
|
16
16
|
}
|
|
17
17
|
const workspacePaths = workspace.kind === 'packages' ? workspace.packageDirs : [];
|
|
18
18
|
const overridesByPath = buildOverrideMap(config, workspacePaths);
|
package/dist/esm/publish.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { resolveExecutable } from "./detectPackageManager.js";
|
|
3
|
+
import { asUserError } from "./user-error-chaining.js";
|
|
3
4
|
export function publishPackage(resolvedTag, packageManager, options) {
|
|
4
5
|
const { isDryRun, shouldGenerateProvenance } = options;
|
|
5
6
|
const executable = resolveExecutable(packageManager);
|
|
6
7
|
const args = buildPublishArgs(packageManager, { isDryRun, shouldGenerateProvenance });
|
|
7
8
|
console.info(`\n${isDryRun ? '[dry-run] ' : ''}Running: ${executable} ${args.join(' ')} (cwd: ${resolvedTag.workspacePath})`);
|
|
8
|
-
|
|
9
|
+
try {
|
|
10
|
+
execFileSync(executable, args, { cwd: resolvedTag.workspacePath, stdio: 'inherit' });
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
throw asUserError(error);
|
|
14
|
+
}
|
|
9
15
|
}
|
|
10
16
|
function buildPublishArgs(packageManager, options) {
|
|
11
17
|
const args = packageManager === 'yarn-berry' ? ['npm', 'publish'] : ['publish'];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { parseArgsOrExit
|
|
4
|
-
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
|
+
import { parseArgsOrExit } from '@williamthorsen/nmr-core';
|
|
5
4
|
import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
|
|
6
5
|
import { assertTarballsComplete } from "./assertTarballsComplete.js";
|
|
7
6
|
import { configFlagSchema } from "./configFlagSchema.js";
|
|
@@ -29,13 +28,7 @@ export async function publishCommand(argv, styles, invocationDir) {
|
|
|
29
28
|
const { isDryRun, shouldSkipGitChecks, shouldGenerateProvenance } = parsedArgs.flags;
|
|
30
29
|
const userConfig = await loadUsableConfig(styles.stderr, configPath);
|
|
31
30
|
if (!isDryRun && !shouldSkipGitChecks) {
|
|
32
|
-
|
|
33
|
-
assertCleanWorkingTree();
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
reportError(describeError(error));
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
31
|
+
assertCleanWorkingTree();
|
|
39
32
|
}
|
|
40
33
|
const requestedTags = parseRequestedTags(parsedArgs.flags.tags);
|
|
41
34
|
const resolvedTags = resolveCommandTags(requestedTags, userConfig);
|
|
@@ -47,13 +40,7 @@ export async function publishCommand(argv, styles, invocationDir) {
|
|
|
47
40
|
console.info('Nothing to publish.');
|
|
48
41
|
return;
|
|
49
42
|
}
|
|
50
|
-
|
|
51
|
-
assertTarballsComplete(publishableTags.map(({ workspacePath }) => workspacePath), userConfig?.devExportConditions ?? []);
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
reportError(describeError(error));
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
43
|
+
assertTarballsComplete(publishableTags.map(({ workspacePath }) => workspacePath), userConfig?.devExportConditions ?? []);
|
|
57
44
|
const packageManager = detectPackageManager();
|
|
58
45
|
const { releaseNotes, changelogJsonOutputPath, sectionOrder } = deriveReleaseNotesConfig(userConfig);
|
|
59
46
|
const shouldInject = releaseNotes.shouldInjectIntoReadme;
|
|
@@ -90,8 +77,7 @@ export async function publishCommand(argv, styles, invocationDir) {
|
|
|
90
77
|
console.warn(` ${tag}`);
|
|
91
78
|
}
|
|
92
79
|
}
|
|
93
|
-
|
|
94
|
-
process.exit(1);
|
|
80
|
+
throw error;
|
|
95
81
|
}
|
|
96
82
|
}
|
|
97
83
|
function filterPublishableTags(resolvedTags, isExplicit) {
|
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
|