@williamthorsen/release-kit 10.8.0 → 11.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 +100 -3
- package/.readyup/kits/default.js +3 -3
- package/.readyup/kits/npm-auto-publish.js +43 -23
- package/.readyup/manifest.json +9 -9
- package/CHANGELOG.md +82 -3
- package/README.md +46 -11
- package/dist/esm/bin/release-kit.js +46 -120
- package/dist/esm/buildChangelogEntries.js +1 -0
- package/dist/esm/configFlagSchema.d.ts +12 -0
- package/dist/esm/configFlagSchema.js +3 -0
- package/dist/esm/createGithubReleaseCommand.d.ts +2 -1
- package/dist/esm/createGithubReleaseCommand.js +9 -3
- package/dist/esm/createTags.d.ts +2 -0
- package/dist/esm/createTags.js +5 -4
- package/dist/esm/discoverWorkspaces.d.ts +53 -8
- package/dist/esm/discoverWorkspaces.js +57 -37
- package/dist/esm/format.d.ts +5 -5
- package/dist/esm/format.js +5 -4
- package/dist/esm/glyphs.d.ts +3 -0
- package/dist/esm/glyphs.js +6 -0
- package/dist/esm/help/prepareHelp.d.ts +1 -1
- package/dist/esm/help/prepareHelp.js +1 -0
- package/dist/esm/init/initCommand.d.ts +3 -1
- package/dist/esm/init/initCommand.js +15 -15
- package/dist/esm/loadConfig.d.ts +7 -4
- package/dist/esm/loadConfig.js +6 -3
- package/dist/esm/loadValidatedConfig.d.ts +47 -5
- package/dist/esm/loadValidatedConfig.js +32 -15
- package/dist/esm/prepareCommand.d.ts +7 -1
- package/dist/esm/prepareCommand.js +37 -45
- package/dist/esm/previewTagPrefixes.d.ts +9 -6
- package/dist/esm/previewTagPrefixes.js +12 -24
- package/dist/esm/publishCommand.d.ts +4 -3
- package/dist/esm/publishCommand.js +9 -3
- package/dist/esm/pushCommand.d.ts +1 -1
- package/dist/esm/pushCommand.js +2 -2
- package/dist/esm/reportPrepare.d.ts +2 -0
- package/dist/esm/reportPrepare.js +51 -41
- package/dist/esm/resolveCommandTags.d.ts +3 -2
- package/dist/esm/resolveCommandTags.js +10 -6
- package/dist/esm/resolveReleaseNotesConfig.d.ts +10 -11
- package/dist/esm/resolveReleaseNotesConfig.js +10 -29
- package/dist/esm/resolveStylesOrExit.d.ts +10 -0
- package/dist/esm/resolveStylesOrExit.js +10 -0
- package/dist/esm/showTagPrefixesCommand.d.ts +5 -2
- package/dist/esm/showTagPrefixesCommand.js +31 -11
- package/dist/esm/sync-labels/generateCommand.d.ts +18 -8
- package/dist/esm/sync-labels/generateCommand.js +12 -11
- package/dist/esm/sync-labels/initCommand.d.ts +11 -4
- package/dist/esm/sync-labels/initCommand.js +24 -18
- package/dist/esm/sync-labels/retiredConfig.d.ts +4 -2
- package/dist/esm/sync-labels/retiredConfig.js +2 -2
- package/dist/esm/tagCommand.d.ts +2 -1
- package/dist/esm/tagCommand.js +2 -2
- package/dist/esm/validateOverridesCommand.d.ts +13 -7
- package/dist/esm/validateOverridesCommand.js +28 -36
- package/docs/configuration.md +35 -1
- package/docs/labels.md +2 -0
- package/docs/work-types.md +11 -8
- package/package.json +13 -14
- package/presets/labels/common.yaml +2 -2
- package/dist/esm/checkWorkTypesDrift.d.ts +0 -35
- package/dist/esm/checkWorkTypesDrift.js +0 -117
- package/dist/esm/syncWorkTypes.d.ts +0 -28
- package/dist/esm/syncWorkTypes.js +0 -94
- package/dist/esm/work-types.json +0 -149
- package/dist/esm/work-types.schema.json +0 -110
- package/dist/esm/workTypesUtils.d.ts +0 -22
- package/dist/esm/workTypesUtils.js +0 -16
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
* dispatcher and asserted by the help drift guard test; kept free of import-time side
|
|
4
4
|
* effects so both can load it.
|
|
5
5
|
*/
|
|
6
|
-
export declare const prepareHelpText = "\nUsage: release-kit prepare [options]\n\nRun release preparation with automatic workspace discovery.\n\nOptions:\n --dry-run Run without modifying any files\n --bump=major|minor|patch Override the bump type for all workspaces\n --set-version=X.Y.Z Set an explicit version; bypasses commit-derived bumps. In monorepo\n mode it requires --only, and is rejected when a 'project' block is configured.\n --force Release even when no commits or no bump-worthy commits exist\n since the last tag. In monorepo and project mode, defaults to\n patch when --bump is not given; use --bump=X for a different\n level. In single-package mode, a bare --force is rejected \u2014\n pass --bump=major|minor|patch.\n --no-git-checks, -n Skip the clean-working-tree check\n --only=name1,name2 Only process the named workspaces (comma-separated, monorepo only).\n When a 'project' block is configured, the project release is skipped.\n --with-release-notes Also write per-workspace release-notes previews under <workspacePath>/docs/\n (docs/README.v<version>.md and docs/RELEASE_NOTES.v<version>.md).\n Recommended .gitignore entry: packages/*/docs/*.v*.md (or docs/*.v*.md).\n --help, -h Show this help message\n";
|
|
6
|
+
export declare const prepareHelpText = "\nUsage: release-kit prepare [options]\n\nRun release preparation with automatic workspace discovery.\n\nOptions:\n --config <path> Path to config file (default: .config/release-kit.config.ts)\n --dry-run Run without modifying any files\n --bump=major|minor|patch Override the bump type for all workspaces\n --set-version=X.Y.Z Set an explicit version; bypasses commit-derived bumps. In monorepo\n mode it requires --only, and is rejected when a 'project' block is configured.\n --force Release even when no commits or no bump-worthy commits exist\n since the last tag. In monorepo and project mode, defaults to\n patch when --bump is not given; use --bump=X for a different\n level. In single-package mode, a bare --force is rejected \u2014\n pass --bump=major|minor|patch.\n --no-git-checks, -n Skip the clean-working-tree check\n --only=name1,name2 Only process the named workspaces (comma-separated, monorepo only).\n When a 'project' block is configured, the project release is skipped.\n --with-release-notes Also write per-workspace release-notes previews under <workspacePath>/docs/\n (docs/README.v<version>.md and docs/RELEASE_NOTES.v<version>.md).\n Recommended .gitignore entry: packages/*/docs/*.v*.md (or docs/*.v*.md).\n --help, -h Show this help message\n";
|
|
7
7
|
/** Prints the prepare command's help text. */
|
|
8
8
|
export declare function showPrepareHelp(): void;
|
|
@@ -4,6 +4,7 @@ Usage: release-kit prepare [options]
|
|
|
4
4
|
Run release preparation with automatic workspace discovery.
|
|
5
5
|
|
|
6
6
|
Options:
|
|
7
|
+
--config <path> Path to config file (default: .config/release-kit.config.ts)
|
|
7
8
|
--dry-run Run without modifying any files
|
|
8
9
|
--bump=major|minor|patch Override the bump type for all workspaces
|
|
9
10
|
--set-version=X.Y.Z Set an explicit version; bypasses commit-derived bumps. In monorepo
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
1
2
|
interface InitOptions {
|
|
2
3
|
dryRun: boolean;
|
|
3
4
|
force: boolean;
|
|
5
|
+
styles: StreamStyles;
|
|
4
6
|
withConfig: boolean;
|
|
5
7
|
}
|
|
6
8
|
/**
|
|
@@ -9,5 +11,5 @@ interface InitOptions {
|
|
|
9
11
|
* Checks eligibility, detects repo type, scaffolds files, and prints next steps.
|
|
10
12
|
* Returns the process exit code (0 for success, 1 for failure).
|
|
11
13
|
*/
|
|
12
|
-
export declare function initCommand({ dryRun, force, withConfig }: InitOptions): number;
|
|
14
|
+
export declare function initCommand({ dryRun, force, styles, withConfig }: InitOptions): number;
|
|
13
15
|
export {};
|
|
@@ -1,36 +1,36 @@
|
|
|
1
|
-
import { printError, printStep, printSuccess, reportWriteResult } from '@williamthorsen/nmr-core';
|
|
1
|
+
import { printError, 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";
|
|
5
5
|
import { scaffoldFiles } from "./scaffold.js";
|
|
6
|
-
function runRequiredCheck(label, result) {
|
|
6
|
+
function runRequiredCheck(label, result, styles) {
|
|
7
7
|
if (result.ok) {
|
|
8
|
-
printSuccess(label);
|
|
8
|
+
printSuccess(label, styles.stdout);
|
|
9
9
|
return true;
|
|
10
10
|
}
|
|
11
|
-
printError(result.message ?? `${label} failed
|
|
11
|
+
printError(result.message ?? `${label} failed`, styles.stderr);
|
|
12
12
|
return false;
|
|
13
13
|
}
|
|
14
|
-
function checkEligibility() {
|
|
14
|
+
function checkEligibility(styles) {
|
|
15
15
|
printStep('Checking eligibility');
|
|
16
|
-
if (!runRequiredCheck('Git repository detected', isGitRepo()))
|
|
16
|
+
if (!runRequiredCheck('Git repository detected', isGitRepo(), styles))
|
|
17
17
|
return false;
|
|
18
|
-
if (!runRequiredCheck('package.json found', hasPackageJson()))
|
|
18
|
+
if (!runRequiredCheck('package.json found', hasPackageJson(), styles))
|
|
19
19
|
return false;
|
|
20
|
-
if (!runRequiredCheck('pnpm detected', usesPnpm()))
|
|
20
|
+
if (!runRequiredCheck('pnpm detected', usesPnpm(), styles))
|
|
21
21
|
return false;
|
|
22
22
|
return true;
|
|
23
23
|
}
|
|
24
|
-
export function initCommand({ dryRun, force, withConfig }) {
|
|
24
|
+
export function initCommand({ dryRun, force, styles, withConfig }) {
|
|
25
25
|
if (dryRun) {
|
|
26
26
|
console.info('[dry-run mode]');
|
|
27
27
|
}
|
|
28
28
|
let eligible;
|
|
29
29
|
try {
|
|
30
|
-
eligible = checkEligibility();
|
|
30
|
+
eligible = checkEligibility(styles);
|
|
31
31
|
}
|
|
32
32
|
catch (error) {
|
|
33
|
-
printError(`Eligibility check failed: ${describeError(error)}
|
|
33
|
+
printError(`Eligibility check failed: ${describeError(error)}`, styles.stderr);
|
|
34
34
|
return 1;
|
|
35
35
|
}
|
|
36
36
|
if (!eligible)
|
|
@@ -41,21 +41,21 @@ export function initCommand({ dryRun, force, withConfig }) {
|
|
|
41
41
|
repoType = detectRepoType();
|
|
42
42
|
}
|
|
43
43
|
catch (error) {
|
|
44
|
-
printError(`Failed to detect repo type: ${describeError(error)}
|
|
44
|
+
printError(`Failed to detect repo type: ${describeError(error)}`, styles.stderr);
|
|
45
45
|
return 1;
|
|
46
46
|
}
|
|
47
|
-
printSuccess(`Detected: ${repoType}
|
|
47
|
+
printSuccess(`Detected: ${repoType}`, styles.stdout);
|
|
48
48
|
printStep('Scaffolding files');
|
|
49
49
|
let results;
|
|
50
50
|
try {
|
|
51
51
|
results = scaffoldFiles({ repoType, dryRun, overwrite: force, withConfig });
|
|
52
52
|
}
|
|
53
53
|
catch (error) {
|
|
54
|
-
printError(`Failed to scaffold files: ${describeError(error)}
|
|
54
|
+
printError(`Failed to scaffold files: ${describeError(error)}`, styles.stderr);
|
|
55
55
|
return 1;
|
|
56
56
|
}
|
|
57
57
|
for (const result of results) {
|
|
58
|
-
reportWriteResult(result, dryRun);
|
|
58
|
+
reportWriteResult(result, dryRun, styles);
|
|
59
59
|
}
|
|
60
60
|
if (results.some((r) => r.outcome === 'failed')) {
|
|
61
61
|
return 1;
|
package/dist/esm/loadConfig.d.ts
CHANGED
|
@@ -15,11 +15,14 @@ export declare function readRootPackageVersion(): {
|
|
|
15
15
|
/** The path where the consumer-facing config file is expected. */
|
|
16
16
|
export declare const CONFIG_FILE_PATH = ".config/release-kit.config.ts";
|
|
17
17
|
/**
|
|
18
|
-
* Loads the config file
|
|
19
|
-
*
|
|
20
|
-
* `
|
|
18
|
+
* Loads the config file, returning the raw config object.
|
|
19
|
+
*
|
|
20
|
+
* `configPath` is resolved against the working directory and defaults to `CONFIG_FILE_PATH`. An absent default
|
|
21
|
+
* path returns `undefined`, because a repo that declares no config is a supported state; an absent named path
|
|
22
|
+
* throws, because the caller asked for that file by name. Also throws when the file exists but cannot be
|
|
23
|
+
* imported, or exports neither a default nor a named `config`.
|
|
21
24
|
*/
|
|
22
|
-
export declare function loadConfig(): Promise<unknown>;
|
|
25
|
+
export declare function loadConfig(configPath?: string): Promise<unknown>;
|
|
23
26
|
/**
|
|
24
27
|
* Information about the root `package.json` passed into `mergeMonorepoConfig` when a
|
|
25
28
|
* `project` block is configured. The async I/O lives in `readRootPackageVersion`; this
|
package/dist/esm/loadConfig.js
CHANGED
|
@@ -39,10 +39,13 @@ export function readRootPackageVersion() {
|
|
|
39
39
|
return { exists: true, version: typeof parsed['version'] === 'string' ? parsed['version'] : undefined };
|
|
40
40
|
}
|
|
41
41
|
export const CONFIG_FILE_PATH = '.config/release-kit.config.ts';
|
|
42
|
-
export async function loadConfig() {
|
|
43
|
-
const absoluteConfigPath = path.resolve(process.cwd(), CONFIG_FILE_PATH);
|
|
42
|
+
export async function loadConfig(configPath) {
|
|
43
|
+
const absoluteConfigPath = path.resolve(process.cwd(), configPath ?? CONFIG_FILE_PATH);
|
|
44
44
|
if (!existsSync(absoluteConfigPath)) {
|
|
45
|
-
|
|
45
|
+
if (configPath === undefined) {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
throw new Error(`Config file not found: ${absoluteConfigPath}`);
|
|
46
49
|
}
|
|
47
50
|
const imported = await import(__rewriteRelativeImportExtension(pathToFileURL(absoluteConfigPath).href));
|
|
48
51
|
const resolved = isRecord(imported) ? (imported['default'] ?? imported['config']) : undefined;
|
|
@@ -1,18 +1,60 @@
|
|
|
1
|
+
import { type OutputStyle } from '@williamthorsen/nmr-core';
|
|
1
2
|
import type { ReleaseKitConfig } from './types.js';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
4
|
+
* Why a config file could not be used: the import threw, or the contents failed the schema.
|
|
5
|
+
*
|
|
6
|
+
* The distinction exists so that a caller can render the two differently. Every caller aborts on either.
|
|
7
|
+
*/
|
|
8
|
+
export type ConfigProblem = {
|
|
9
|
+
kind: 'load';
|
|
10
|
+
message: string;
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'validation';
|
|
13
|
+
errors: string[];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Outcome of loading and validating the consumer config file. `missing` is not an error
|
|
17
|
+
* here — whether an absent config file is one is the caller's call.
|
|
18
|
+
*
|
|
19
|
+
* `configFilePath` names the file the run read: the caller's `configPath`, or `CONFIG_FILE_PATH` when none was
|
|
20
|
+
* given. It stays relative to the working directory, so a message or a generated artifact that splices it reads
|
|
21
|
+
* the same on every machine.
|
|
5
22
|
*/
|
|
6
23
|
export type LoadValidatedConfigResult = {
|
|
7
24
|
status: 'invalid';
|
|
25
|
+
configFilePath: string;
|
|
26
|
+
problem: ConfigProblem;
|
|
8
27
|
} | {
|
|
9
28
|
status: 'missing';
|
|
29
|
+
configFilePath: string;
|
|
10
30
|
} | {
|
|
11
31
|
status: 'ok';
|
|
12
32
|
config: ReleaseKitConfig;
|
|
33
|
+
configFilePath: string;
|
|
34
|
+
warnings: string[];
|
|
13
35
|
};
|
|
14
36
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
37
|
+
* Loads the consumer config file and validates it against the shared schema, returning the outcome.
|
|
38
|
+
*
|
|
39
|
+
* Writes nothing to any stream: the caller picks the sink, which is what lets a command that must stay silent
|
|
40
|
+
* reuse this loader. `reportConfigProblem` and `reportConfigWarnings` render the stderr form.
|
|
41
|
+
*
|
|
42
|
+
* `configPath` defaults to `CONFIG_FILE_PATH`. A named path that does not exist is a load failure, so it reports
|
|
43
|
+
* as `invalid` rather than `missing`.
|
|
44
|
+
*/
|
|
45
|
+
export declare function loadValidatedConfig(configPath?: string): Promise<LoadValidatedConfigResult>;
|
|
46
|
+
/**
|
|
47
|
+
* Loads and validates the config, reporting the problem and exiting 1 when it is unusable, so that a command
|
|
48
|
+
* whose later work may never reach a loader still fails on a config that does.
|
|
49
|
+
*
|
|
50
|
+
* An absent default config returns cleanly, which is what keeps a repo that declares no config a supported state.
|
|
51
|
+
* Reports problems but never warnings: the command's own later load stays the single place that emits those, so
|
|
52
|
+
* a warning is not printed twice.
|
|
53
|
+
*
|
|
54
|
+
* The second load costs nothing: `import()` caches by URL and `validateConfig` is pure.
|
|
17
55
|
*/
|
|
18
|
-
export declare function
|
|
56
|
+
export declare function assertConfigUsable(stderrStyle: OutputStyle, configPath?: string): Promise<void>;
|
|
57
|
+
/** Writes a config problem to stderr in the form every CLI command uses. */
|
|
58
|
+
export declare function reportConfigProblem(problem: ConfigProblem, stderrStyle: OutputStyle): void;
|
|
59
|
+
/** Writes validation warnings to stderr as indented status lines. */
|
|
60
|
+
export declare function reportConfigWarnings(warnings: readonly string[], stderrStyle: OutputStyle): void;
|
|
@@ -1,27 +1,44 @@
|
|
|
1
|
-
import { reportError } from '@williamthorsen/nmr-core';
|
|
1
|
+
import { formatStatusLine, printError, reportError } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
|
-
import { loadConfig } from "./loadConfig.js";
|
|
3
|
+
import { CONFIG_FILE_PATH, loadConfig } from "./loadConfig.js";
|
|
4
4
|
import { validateConfig } from "./validateConfig.js";
|
|
5
|
-
export async function loadValidatedConfig() {
|
|
5
|
+
export async function loadValidatedConfig(configPath) {
|
|
6
|
+
const configFilePath = configPath ?? CONFIG_FILE_PATH;
|
|
6
7
|
let raw;
|
|
7
8
|
try {
|
|
8
|
-
raw = await loadConfig();
|
|
9
|
+
raw = await loadConfig(configPath);
|
|
9
10
|
}
|
|
10
11
|
catch (error) {
|
|
11
|
-
|
|
12
|
-
reportError(`Failed to load config: ${message}`);
|
|
13
|
-
return { status: 'invalid' };
|
|
12
|
+
return { status: 'invalid', configFilePath, problem: { kind: 'load', message: describeError(error) } };
|
|
14
13
|
}
|
|
15
14
|
if (raw === undefined) {
|
|
16
|
-
return { status: 'missing' };
|
|
15
|
+
return { status: 'missing', configFilePath };
|
|
17
16
|
}
|
|
18
|
-
const { config, errors } = validateConfig(raw);
|
|
17
|
+
const { config, errors, warnings } = validateConfig(raw);
|
|
19
18
|
if (errors.length > 0) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
return { status: 'invalid', configFilePath, problem: { kind: 'validation', errors } };
|
|
20
|
+
}
|
|
21
|
+
return { status: 'ok', config, configFilePath, warnings };
|
|
22
|
+
}
|
|
23
|
+
export async function assertConfigUsable(stderrStyle, configPath) {
|
|
24
|
+
const result = await loadValidatedConfig(configPath);
|
|
25
|
+
if (result.status === 'invalid') {
|
|
26
|
+
reportConfigProblem(result.problem, stderrStyle);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function reportConfigProblem(problem, stderrStyle) {
|
|
31
|
+
if (problem.kind === 'load') {
|
|
32
|
+
reportError(`Failed to load config: ${problem.message}`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
process.stderr.write('Invalid config:\n');
|
|
36
|
+
for (const error of problem.errors) {
|
|
37
|
+
printError(error, stderrStyle);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function reportConfigWarnings(warnings, stderrStyle) {
|
|
41
|
+
for (const warning of warnings) {
|
|
42
|
+
console.warn(` ${formatStatusLine(stderrStyle, 'warning', warning)}`);
|
|
25
43
|
}
|
|
26
|
-
return { status: 'ok', config };
|
|
27
44
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
1
2
|
import type { ReleaseType } from './types.js';
|
|
2
3
|
export declare const prepareFlagSchema: {
|
|
3
4
|
dryRun: {
|
|
@@ -29,9 +30,14 @@ export declare const prepareFlagSchema: {
|
|
|
29
30
|
long: string;
|
|
30
31
|
type: "boolean";
|
|
31
32
|
};
|
|
33
|
+
config: {
|
|
34
|
+
long: string;
|
|
35
|
+
type: "string";
|
|
36
|
+
};
|
|
32
37
|
};
|
|
33
38
|
/** Parses CLI arguments into structured options. Prints a usage error and exits on invalid input. */
|
|
34
39
|
export declare function parseArgs(argv: string[]): {
|
|
40
|
+
configPath: string | undefined;
|
|
35
41
|
dryRun: boolean;
|
|
36
42
|
force: boolean;
|
|
37
43
|
noGitChecks: boolean;
|
|
@@ -49,4 +55,4 @@ export declare function parseArgs(argv: string[]): {
|
|
|
49
55
|
* 4. Delegates to `releasePrepare` or `releasePrepareMono` to compute the release plan.
|
|
50
56
|
* 5. Applies the plan, runs the format command, and prints the result via `reportPrepare`.
|
|
51
57
|
*/
|
|
52
|
-
export declare function prepareCommand(argv: string[]): Promise<void>;
|
|
58
|
+
export declare function prepareCommand(argv: string[], styles: StreamStyles): Promise<void>;
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
|
-
import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { formatGlyphLine, parseArgsOrExit, reportError, } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
4
|
import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
|
|
5
5
|
import { buildDependencyGraph } from "./buildDependencyGraph.js";
|
|
6
|
-
import {
|
|
6
|
+
import { configFlagSchema } from "./configFlagSchema.js";
|
|
7
|
+
import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
7
8
|
import { dim } from "./format.js";
|
|
8
9
|
import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
|
|
9
|
-
import {
|
|
10
|
+
import { RELEASE_GLYPHS } from "./glyphs.js";
|
|
11
|
+
import { mergeMonorepoConfig, mergeSinglePackageConfig, readRootPackageVersion } from "./loadConfig.js";
|
|
12
|
+
import { loadValidatedConfig, reportConfigProblem, reportConfigWarnings } from "./loadValidatedConfig.js";
|
|
10
13
|
import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
|
|
11
14
|
import { applyReleasePlan } from "./releasePlan.js";
|
|
12
15
|
import { releasePrepare } from "./releasePrepare.js";
|
|
13
16
|
import { releasePrepareMono } from "./releasePrepareMono.js";
|
|
14
17
|
import { reportPrepare } from "./reportPrepare.js";
|
|
15
|
-
import { validateConfig } from "./validateConfig.js";
|
|
16
18
|
import { validateOnlyExcludesStrandedDependents } from "./validateOnlyExcludesStrandedDependents.js";
|
|
17
19
|
const VALID_BUMP_TYPES = ['major', 'minor', 'patch'];
|
|
18
20
|
const CANONICAL_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
@@ -20,6 +22,7 @@ function isReleaseType(value) {
|
|
|
20
22
|
return VALID_BUMP_TYPES.includes(value);
|
|
21
23
|
}
|
|
22
24
|
export const prepareFlagSchema = {
|
|
25
|
+
...configFlagSchema,
|
|
23
26
|
dryRun: { long: '--dry-run', type: 'boolean' },
|
|
24
27
|
force: { long: '--force', type: 'boolean' },
|
|
25
28
|
noGitChecks: {
|
|
@@ -63,6 +66,7 @@ export function parseArgs(argv) {
|
|
|
63
66
|
process.exit(1);
|
|
64
67
|
}
|
|
65
68
|
return {
|
|
69
|
+
configPath: flags.config,
|
|
66
70
|
dryRun: flags.dryRun,
|
|
67
71
|
force: flags.force,
|
|
68
72
|
noGitChecks: flags.noGitChecks,
|
|
@@ -72,8 +76,8 @@ export function parseArgs(argv) {
|
|
|
72
76
|
withReleaseNotes: flags.withReleaseNotes,
|
|
73
77
|
};
|
|
74
78
|
}
|
|
75
|
-
export async function prepareCommand(argv) {
|
|
76
|
-
const { dryRun, force, noGitChecks, bumpOverride, only, setVersion, withReleaseNotes } = parseArgs(argv);
|
|
79
|
+
export async function prepareCommand(argv, styles) {
|
|
80
|
+
const { configPath, dryRun, force, noGitChecks, bumpOverride, only, setVersion, withReleaseNotes } = parseArgs(argv);
|
|
77
81
|
const options = {
|
|
78
82
|
force,
|
|
79
83
|
...(bumpOverride !== undefined && { bumpOverride }),
|
|
@@ -81,7 +85,7 @@ export async function prepareCommand(argv) {
|
|
|
81
85
|
...(withReleaseNotes && { withReleaseNotes: true }),
|
|
82
86
|
};
|
|
83
87
|
if (dryRun) {
|
|
84
|
-
console.info(
|
|
88
|
+
console.info(`\n${formatGlyphLine(RELEASE_GLYPHS, styles.stdout, 'dryRun', 'DRY RUN — no files will be modified')}\n`);
|
|
85
89
|
}
|
|
86
90
|
if (!dryRun && !noGitChecks) {
|
|
87
91
|
try {
|
|
@@ -92,23 +96,36 @@ export async function prepareCommand(argv) {
|
|
|
92
96
|
process.exit(1);
|
|
93
97
|
}
|
|
94
98
|
}
|
|
95
|
-
const
|
|
96
|
-
|
|
99
|
+
const configResult = await loadValidatedConfig(configPath);
|
|
100
|
+
if (configResult.status === 'invalid') {
|
|
101
|
+
reportConfigProblem(configResult.problem, styles.stderr);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
let userConfig;
|
|
105
|
+
if (configResult.status === 'ok') {
|
|
106
|
+
reportConfigWarnings(configResult.warnings, styles.stderr);
|
|
107
|
+
userConfig = configResult.config;
|
|
108
|
+
}
|
|
109
|
+
let workspace;
|
|
97
110
|
try {
|
|
98
|
-
|
|
111
|
+
workspace = discoverWorkspaces();
|
|
99
112
|
}
|
|
100
113
|
catch (error) {
|
|
101
114
|
reportError(`Failed to discover workspaces: ${describeError(error)}`);
|
|
102
115
|
process.exit(1);
|
|
103
116
|
}
|
|
104
|
-
if (
|
|
105
|
-
|
|
117
|
+
if (workspace.kind === 'empty') {
|
|
118
|
+
reportError(`No workspace package to release. ${describeEmptyWorkspace(workspace)}`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
if (workspace.kind === 'single-package') {
|
|
122
|
+
runSinglePackageMode(userConfig, options, only, dryRun, styles.stdout);
|
|
106
123
|
}
|
|
107
124
|
else {
|
|
108
|
-
runMonorepoMode(
|
|
125
|
+
runMonorepoMode(workspace.packageDirs, userConfig, options, only, setVersion, dryRun, styles.stdout);
|
|
109
126
|
}
|
|
110
127
|
}
|
|
111
|
-
function runSinglePackageMode(userConfig, options, only, dryRun) {
|
|
128
|
+
function runSinglePackageMode(userConfig, options, only, dryRun, style) {
|
|
112
129
|
if (only !== undefined) {
|
|
113
130
|
reportError('--only is only supported for monorepo configurations');
|
|
114
131
|
process.exit(1);
|
|
@@ -119,9 +136,9 @@ function runSinglePackageMode(userConfig, options, only, dryRun) {
|
|
|
119
136
|
process.exit(1);
|
|
120
137
|
}
|
|
121
138
|
const config = mergeSinglePackageConfig(userConfig);
|
|
122
|
-
runAndReport(() => releasePrepare(config, options), dryRun);
|
|
139
|
+
runAndReport(() => releasePrepare(config, options), dryRun, style);
|
|
123
140
|
}
|
|
124
|
-
function runMonorepoMode(discoveredPaths, userConfig, options, only, setVersion, dryRun) {
|
|
141
|
+
function runMonorepoMode(discoveredPaths, userConfig, options, only, setVersion, dryRun, style) {
|
|
125
142
|
let config;
|
|
126
143
|
try {
|
|
127
144
|
const rootPackage = readRootPackageVersion();
|
|
@@ -177,34 +194,9 @@ function runMonorepoMode(discoveredPaths, userConfig, options, only, setVersion,
|
|
|
177
194
|
process.exit(1);
|
|
178
195
|
}
|
|
179
196
|
}
|
|
180
|
-
runAndReport(() => releasePrepareMono(config, { ...options, ...(only !== undefined && { only }) }), dryRun);
|
|
181
|
-
}
|
|
182
|
-
async function loadAndValidateConfig() {
|
|
183
|
-
let rawConfig;
|
|
184
|
-
try {
|
|
185
|
-
rawConfig = await loadConfig();
|
|
186
|
-
}
|
|
187
|
-
catch (error) {
|
|
188
|
-
reportError(`Failed to load config: ${describeError(error)}`);
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
if (rawConfig === undefined) {
|
|
192
|
-
return undefined;
|
|
193
|
-
}
|
|
194
|
-
const { config, errors, warnings } = validateConfig(rawConfig);
|
|
195
|
-
if (errors.length > 0) {
|
|
196
|
-
process.stderr.write('Invalid config:\n');
|
|
197
|
-
for (const err of errors) {
|
|
198
|
-
process.stderr.write(` ❌ ${err}\n`);
|
|
199
|
-
}
|
|
200
|
-
process.exit(1);
|
|
201
|
-
}
|
|
202
|
-
for (const warning of warnings) {
|
|
203
|
-
console.warn(` ⚠️ ${warning}`);
|
|
204
|
-
}
|
|
205
|
-
return config;
|
|
197
|
+
runAndReport(() => releasePrepareMono(config, { ...options, ...(only !== undefined && { only }) }), dryRun, style);
|
|
206
198
|
}
|
|
207
|
-
function runAndReport(computePlan, dryRun) {
|
|
199
|
+
function runAndReport(computePlan, dryRun, style) {
|
|
208
200
|
let plan;
|
|
209
201
|
try {
|
|
210
202
|
plan = computePlan();
|
|
@@ -215,7 +207,7 @@ function runAndReport(computePlan, dryRun) {
|
|
|
215
207
|
process.exit(1);
|
|
216
208
|
}
|
|
217
209
|
if (dryRun) {
|
|
218
|
-
process.stdout.write(reportPrepare(plan, { applied: false }) + '\n');
|
|
210
|
+
process.stdout.write(reportPrepare(plan, { applied: false, style }) + '\n');
|
|
219
211
|
reportPlanFiles(plan, true);
|
|
220
212
|
return;
|
|
221
213
|
}
|
|
@@ -227,7 +219,7 @@ function runAndReport(computePlan, dryRun) {
|
|
|
227
219
|
process.exit(1);
|
|
228
220
|
}
|
|
229
221
|
const formatError = runFormatCommand(plan.formatCommand);
|
|
230
|
-
process.stdout.write(reportPrepare(plan, { applied: true, ...(formatError !== undefined && { formatError }) }) + '\n');
|
|
222
|
+
process.stdout.write(reportPrepare(plan, { applied: true, style, ...(formatError !== undefined && { formatError }) }) + '\n');
|
|
231
223
|
reportPlanFiles(plan, false);
|
|
232
224
|
if (formatError !== undefined) {
|
|
233
225
|
reportError(`The format command failed, but the release is prepared and ${RELEASE_TAGS_FILE} is written. ` +
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type UndeclaredTagPrefix } from './detectUndeclaredTagPrefixes.js';
|
|
2
|
+
import type { ReleaseKitConfig } from './types.js';
|
|
2
3
|
/** One workspace's preview row in the tag-prefix preview. */
|
|
3
4
|
export interface TagPrefixPreviewRow {
|
|
4
5
|
/** Workspace-relative directory path (e.g., `packages/core`). */
|
|
@@ -41,10 +42,12 @@ export interface TagPrefixPreview {
|
|
|
41
42
|
/**
|
|
42
43
|
* Build a structured preview of tag-prefix state for every discovered workspace.
|
|
43
44
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
45
|
+
* Derives each workspace's tag prefix via `deriveWorkspaceConfig()`, recording the derivation error on failure
|
|
46
|
+
* rather than aborting. A repo releasing as a single package previews no row; one declaring patterns that
|
|
47
|
+
* resolve to no package throws, because the manifest is then what needs repairing and a clean table would
|
|
48
|
+
* hide that.
|
|
49
|
+
* Reads the already-validated `config` to surface declared legacy prefixes per workspace, scans local
|
|
50
|
+
* git tags for undeclared candidate prefixes via `detectUndeclaredTagPrefixes`, and reports collisions
|
|
51
|
+
* across successfully-derived prefixes.
|
|
49
52
|
*/
|
|
50
|
-
export declare function previewTagPrefixes():
|
|
53
|
+
export declare function previewTagPrefixes(config?: ReleaseKitConfig): TagPrefixPreview;
|
|
@@ -4,38 +4,26 @@ import { GIT_OUTPUT_LIMIT } from '@williamthorsen/nmr-core';
|
|
|
4
4
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
5
5
|
import { deriveWorkspaceConfig } from "./deriveWorkspaceConfig.js";
|
|
6
6
|
import { detectUndeclaredTagPrefixes } from "./detectUndeclaredTagPrefixes.js";
|
|
7
|
-
import { discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
7
|
+
import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
8
|
+
export function previewTagPrefixes(config) {
|
|
9
|
+
const workspace = discoverWorkspaces();
|
|
10
|
+
if (workspace.kind === 'empty') {
|
|
11
|
+
throw new Error(`No workspace package to preview. ${describeEmptyWorkspace(workspace)}`);
|
|
12
|
+
}
|
|
13
|
+
const workspacePaths = workspace.kind === 'packages' ? workspace.packageDirs : [];
|
|
14
|
+
const overridesByDir = buildOverrideMap(config);
|
|
14
15
|
const workspaces = Array.from(workspacePaths, (workspacePath) => buildPreviewRow(workspacePath, overridesByDir));
|
|
15
|
-
const retiredPackages = buildRetiredPreviewEntries(
|
|
16
|
+
const retiredPackages = buildRetiredPreviewEntries(config?.retiredPackages ?? []);
|
|
16
17
|
const collisions = detectCollisions(workspaces);
|
|
17
18
|
const knownPrefixes = collectKnownPrefixes(workspaces, retiredPackages);
|
|
18
19
|
const undeclaredCandidates = detectUndeclaredTagPrefixes(knownPrefixes);
|
|
19
20
|
return { workspaces, collisions, undeclaredCandidates, retiredPackages };
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
let raw;
|
|
23
|
-
try {
|
|
24
|
-
raw = await loadConfig();
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
if (raw === undefined)
|
|
30
|
-
return undefined;
|
|
31
|
-
const { config, errors } = validateConfig(raw);
|
|
32
|
-
return errors.length === 0 ? config : undefined;
|
|
33
|
-
}
|
|
34
|
-
function buildOverrideMap(userConfig) {
|
|
22
|
+
function buildOverrideMap(config) {
|
|
35
23
|
const map = new Map();
|
|
36
|
-
if (
|
|
24
|
+
if (config?.workspaces === undefined)
|
|
37
25
|
return map;
|
|
38
|
-
for (const entry of
|
|
26
|
+
for (const entry of config.workspaces) {
|
|
39
27
|
if (entry.legacyIdentities !== undefined) {
|
|
40
28
|
map.set(entry.dir, entry.legacyIdentities);
|
|
41
29
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
1
2
|
/**
|
|
2
|
-
* Orchestrate the CLI `publish` command: parse flags, discover workspaces, resolve tags
|
|
3
|
-
* detect the package manager, validate `--tags`, and publish each tag with inject/restore lifecycle.
|
|
3
|
+
* Orchestrate the CLI `publish` command: parse flags, open a named config, discover workspaces, resolve tags
|
|
4
|
+
* from HEAD, detect the package manager, validate `--tags`, and publish each tag with inject/restore lifecycle.
|
|
4
5
|
*/
|
|
5
|
-
export declare function publishCommand(argv: string[]): Promise<void>;
|
|
6
|
+
export declare function publishCommand(argv: string[], styles: StreamStyles): Promise<void>;
|
|
@@ -3,22 +3,26 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
5
5
|
import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
|
|
6
|
+
import { configFlagSchema } from "./configFlagSchema.js";
|
|
6
7
|
import { detectPackageManager } from "./detectPackageManager.js";
|
|
7
8
|
import { formatPrivateSkip } from "./formatPrivateSkip.js";
|
|
8
9
|
import { injectReleaseNotesIntoReadme, resolveReadmePath } from "./injectReleaseNotesIntoReadme.js";
|
|
10
|
+
import { assertConfigUsable } from "./loadValidatedConfig.js";
|
|
9
11
|
import { parseRequestedTags } from "./parseRequestedTags.js";
|
|
10
12
|
import { publishPackage } from "./publish.js";
|
|
11
13
|
import { resolveCommandTags } from "./resolveCommandTags.js";
|
|
12
14
|
import { resolveReleaseNotesConfig } from "./resolveReleaseNotesConfig.js";
|
|
13
15
|
const publishFlagSchema = {
|
|
16
|
+
...configFlagSchema,
|
|
14
17
|
dryRun: { long: '--dry-run', type: 'boolean' },
|
|
15
18
|
noGitChecks: { long: '--no-git-checks', type: 'boolean' },
|
|
16
19
|
provenance: { long: '--provenance', type: 'boolean' },
|
|
17
20
|
tags: { long: '--tags', type: 'string' },
|
|
18
21
|
};
|
|
19
|
-
export async function publishCommand(argv) {
|
|
22
|
+
export async function publishCommand(argv, styles) {
|
|
20
23
|
const parsed = parseArgsOrExit(argv, publishFlagSchema);
|
|
21
24
|
const { dryRun, noGitChecks, provenance } = parsed.flags;
|
|
25
|
+
await assertConfigUsable(styles.stderr, parsed.flags.config);
|
|
22
26
|
if (!dryRun && !noGitChecks) {
|
|
23
27
|
try {
|
|
24
28
|
assertCleanWorkingTree();
|
|
@@ -29,7 +33,7 @@ export async function publishCommand(argv) {
|
|
|
29
33
|
}
|
|
30
34
|
}
|
|
31
35
|
const requestedTags = parseRequestedTags(parsed.flags.tags);
|
|
32
|
-
const resolvedTags =
|
|
36
|
+
const resolvedTags = resolveCommandTags(requestedTags);
|
|
33
37
|
if (resolvedTags.length === 0) {
|
|
34
38
|
return;
|
|
35
39
|
}
|
|
@@ -39,7 +43,9 @@ export async function publishCommand(argv) {
|
|
|
39
43
|
return;
|
|
40
44
|
}
|
|
41
45
|
const packageManager = detectPackageManager();
|
|
42
|
-
const { releaseNotes, changelogJsonOutputPath, sectionOrder } = await resolveReleaseNotesConfig(
|
|
46
|
+
const { releaseNotes, changelogJsonOutputPath, sectionOrder } = await resolveReleaseNotesConfig(styles.stderr, {
|
|
47
|
+
...(parsed.flags.config !== undefined && { configPath: parsed.flags.config }),
|
|
48
|
+
});
|
|
43
49
|
const shouldInject = releaseNotes.shouldInjectIntoReadme;
|
|
44
50
|
console.info(dryRun ? '[dry-run] Would publish:' : 'Publishing:');
|
|
45
51
|
for (const { tag, workspacePath } of publishableTags) {
|
package/dist/esm/pushCommand.js
CHANGED
|
@@ -8,11 +8,11 @@ const pushFlagSchema = {
|
|
|
8
8
|
tags: { long: '--tags', type: 'string' },
|
|
9
9
|
tagsOnly: { long: '--tags-only', type: 'boolean' },
|
|
10
10
|
};
|
|
11
|
-
export
|
|
11
|
+
export function pushCommand(argv) {
|
|
12
12
|
const parsed = parseArgsOrExit(argv, pushFlagSchema);
|
|
13
13
|
const { dryRun, tagsOnly } = parsed.flags;
|
|
14
14
|
const requestedTags = parseRequestedTags(parsed.flags.tags);
|
|
15
|
-
const resolvedTags =
|
|
15
|
+
const resolvedTags = resolveCommandTags(requestedTags);
|
|
16
16
|
if (resolvedTags.length === 0) {
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type OutputStyle } from '@williamthorsen/nmr-core';
|
|
1
2
|
import type { PrepareResult } from './types.js';
|
|
2
3
|
/** How the plan was carried out, which decides the tense the report is rendered in. */
|
|
3
4
|
export interface ReportPrepareOptions {
|
|
@@ -5,6 +6,7 @@ export interface ReportPrepareOptions {
|
|
|
5
6
|
applied: boolean;
|
|
6
7
|
/** The format command's failure message, when it ran and failed. */
|
|
7
8
|
formatError?: string;
|
|
9
|
+
style: OutputStyle;
|
|
8
10
|
}
|
|
9
11
|
/**
|
|
10
12
|
* Format a release plan into styled terminal output.
|