@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
|
@@ -1,15 +1,22 @@
|
|
|
1
|
+
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
1
2
|
/** Options for the `sync-labels init` subcommand. */
|
|
2
3
|
interface InitOptions {
|
|
4
|
+
/** Config file to read, relative to the working directory. Defaults to `CONFIG_FILE_PATH`. */
|
|
5
|
+
configPath?: string;
|
|
3
6
|
dryRun: boolean;
|
|
4
7
|
force: boolean;
|
|
8
|
+
styles: StreamStyles;
|
|
5
9
|
}
|
|
6
10
|
/**
|
|
7
11
|
* Run the `sync-labels init` subcommand.
|
|
8
12
|
*
|
|
9
13
|
* Discovers workspaces and retired packages, scaffolds the caller workflow, then seeds the
|
|
10
|
-
* `repoLabels` block: into a new
|
|
11
|
-
*
|
|
12
|
-
*
|
|
14
|
+
* `repoLabels` block: into a new config file when none exists, or printed to stdout for manual
|
|
15
|
+
* paste when the file is already there — an existing, hand-authored config is never rewritten.
|
|
16
|
+
* Returns 0 on success, 1 on failure.
|
|
17
|
+
*
|
|
18
|
+
* `configPath` names the config to read. Naming one that does not exist fails the command, so the
|
|
19
|
+
* scaffolding branch is reached only under the default path and always writes to `CONFIG_FILE_PATH`.
|
|
13
20
|
*/
|
|
14
|
-
export declare function syncLabelsInitCommand({ dryRun, force }: InitOptions): Promise<number>;
|
|
21
|
+
export declare function syncLabelsInitCommand({ configPath, dryRun, force, styles }: InitOptions): Promise<number>;
|
|
15
22
|
export {};
|
|
@@ -1,47 +1,52 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { reportWriteResult, writeFileWithCheck } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
|
-
import { discoverWorkspaces } from "../discoverWorkspaces.js";
|
|
4
|
+
import { describeEmptyWorkspace, discoverWorkspaces } from "../discoverWorkspaces.js";
|
|
5
5
|
import { CONFIG_FILE_PATH } from "../loadConfig.js";
|
|
6
|
-
import { loadValidatedConfig } from "../loadValidatedConfig.js";
|
|
6
|
+
import { loadValidatedConfig, reportConfigProblem } from "../loadValidatedConfig.js";
|
|
7
7
|
import { generateCommand, LABELS_OUTPUT_PATH } from "./generateCommand.js";
|
|
8
8
|
import { checkRetiredSyncLabelsConfig } from "./retiredConfig.js";
|
|
9
9
|
import { buildScopeLabels, renderRepoLabelsBlock, repoLabelsConfigScript, syncLabelsWorkflow } from "./templates.js";
|
|
10
10
|
const WORKFLOW_PATH = '.github/workflows/sync-labels.yaml';
|
|
11
|
-
export async function syncLabelsInitCommand({ dryRun, force }) {
|
|
12
|
-
|
|
11
|
+
export async function syncLabelsInitCommand({ configPath, dryRun, force, styles }) {
|
|
12
|
+
const configFilePath = configPath ?? CONFIG_FILE_PATH;
|
|
13
|
+
if (checkRetiredSyncLabelsConfig(configPath)) {
|
|
13
14
|
return 1;
|
|
14
15
|
}
|
|
15
16
|
if (dryRun) {
|
|
16
17
|
console.info('[dry-run mode]');
|
|
17
18
|
}
|
|
18
19
|
console.info('\n> Discovering workspaces');
|
|
19
|
-
let
|
|
20
|
+
let workspace;
|
|
20
21
|
try {
|
|
21
|
-
|
|
22
|
+
workspace = discoverWorkspaces();
|
|
22
23
|
}
|
|
23
24
|
catch (error) {
|
|
24
25
|
const message = describeError(error);
|
|
25
26
|
process.stderr.write(` Failed to discover workspaces: ${message}\n`);
|
|
26
27
|
return 1;
|
|
27
28
|
}
|
|
28
|
-
if (
|
|
29
|
+
if (workspace.kind === 'empty') {
|
|
30
|
+
process.stderr.write(` No workspace package to label. ${describeEmptyWorkspace(workspace)}\n`);
|
|
31
|
+
return 1;
|
|
32
|
+
}
|
|
33
|
+
if (workspace.kind === 'single-package') {
|
|
29
34
|
console.info(' No pnpm workspaces found (single-package repo)');
|
|
30
35
|
}
|
|
31
36
|
else {
|
|
32
|
-
console.info(` Found ${String(
|
|
37
|
+
console.info(` Found ${String(workspace.packageDirs.length)} workspaces`);
|
|
33
38
|
}
|
|
34
|
-
const configExists = existsSync(CONFIG_FILE_PATH);
|
|
35
|
-
const retiredNames = configExists ? await loadRetiredPackageNames() : [];
|
|
39
|
+
const configExists = configPath !== undefined || existsSync(CONFIG_FILE_PATH);
|
|
40
|
+
const retiredNames = configExists ? await loadRetiredPackageNames(styles, configPath) : [];
|
|
36
41
|
if (retiredNames === undefined) {
|
|
37
42
|
return 1;
|
|
38
43
|
}
|
|
39
|
-
const scopeLabels =
|
|
44
|
+
const scopeLabels = workspace.kind === 'single-package' ? [] : buildScopeLabels(workspace.packageDirs, retiredNames);
|
|
40
45
|
console.info('\n> Scaffolding files');
|
|
41
46
|
const workflowResult = writeFileWithCheck(WORKFLOW_PATH, syncLabelsWorkflow(), { dryRun, overwrite: force });
|
|
42
|
-
reportWriteResult(workflowResult, dryRun);
|
|
47
|
+
reportWriteResult(workflowResult, dryRun, styles);
|
|
43
48
|
if (configExists) {
|
|
44
|
-
console.info(`\n> ${
|
|
49
|
+
console.info(`\n> ${configFilePath} already exists; add this block to the object passed to defineConfig:\n`);
|
|
45
50
|
console.info(renderRepoLabelsBlock(scopeLabels));
|
|
46
51
|
if (workflowResult.outcome === 'failed') {
|
|
47
52
|
process.stderr.write('Failed to scaffold one or more files.\n');
|
|
@@ -49,7 +54,7 @@ export async function syncLabelsInitCommand({ dryRun, force }) {
|
|
|
49
54
|
}
|
|
50
55
|
console.info(`
|
|
51
56
|
> Next steps
|
|
52
|
-
1. Paste the block above into ${
|
|
57
|
+
1. Paste the block above into ${configFilePath}.
|
|
53
58
|
2. Run \`release-kit sync-labels generate\` to produce ${LABELS_OUTPUT_PATH}.
|
|
54
59
|
3. Commit the changes.
|
|
55
60
|
4. Run \`release-kit sync-labels sync\` to apply labels to your GitHub repo.
|
|
@@ -60,7 +65,7 @@ export async function syncLabelsInitCommand({ dryRun, force }) {
|
|
|
60
65
|
dryRun,
|
|
61
66
|
overwrite: force,
|
|
62
67
|
});
|
|
63
|
-
reportWriteResult(configResult, dryRun);
|
|
68
|
+
reportWriteResult(configResult, dryRun, styles);
|
|
64
69
|
if (workflowResult.outcome === 'failed' || configResult.outcome === 'failed') {
|
|
65
70
|
process.stderr.write('Failed to scaffold one or more files.\n');
|
|
66
71
|
return 1;
|
|
@@ -70,7 +75,7 @@ export async function syncLabelsInitCommand({ dryRun, force }) {
|
|
|
70
75
|
}
|
|
71
76
|
else {
|
|
72
77
|
console.info('\n> Generating labels');
|
|
73
|
-
const generateExitCode = await generateCommand();
|
|
78
|
+
const generateExitCode = await generateCommand({ styles });
|
|
74
79
|
if (generateExitCode !== 0) {
|
|
75
80
|
return generateExitCode;
|
|
76
81
|
}
|
|
@@ -87,9 +92,10 @@ export async function syncLabelsInitCommand({ dryRun, force }) {
|
|
|
87
92
|
`);
|
|
88
93
|
return 0;
|
|
89
94
|
}
|
|
90
|
-
async function loadRetiredPackageNames() {
|
|
91
|
-
const result = await loadValidatedConfig();
|
|
95
|
+
async function loadRetiredPackageNames(styles, configPath) {
|
|
96
|
+
const result = await loadValidatedConfig(configPath);
|
|
92
97
|
if (result.status === 'invalid') {
|
|
98
|
+
reportConfigProblem(result.problem, styles.stderr);
|
|
93
99
|
return undefined;
|
|
94
100
|
}
|
|
95
101
|
if (result.status === 'missing') {
|
|
@@ -5,6 +5,8 @@ export declare const RETIRED_SYNC_LABELS_CONFIG_PATH = ".config/sync-labels.conf
|
|
|
5
5
|
*
|
|
6
6
|
* Returns `true` when the file exists, in which case the caller must abort. The check is
|
|
7
7
|
* unconditional on config content: file presence alone triggers it, so a half-migrated
|
|
8
|
-
* repo cannot silently lose its custom labels.
|
|
8
|
+
* repo cannot silently lose its custom labels. `configPath` names the config to move the labels
|
|
9
|
+
* into; it defaults to `CONFIG_FILE_PATH`, which `sync-labels sync` relies on because it takes
|
|
10
|
+
* no `--config`.
|
|
9
11
|
*/
|
|
10
|
-
export declare function checkRetiredSyncLabelsConfig(): boolean;
|
|
12
|
+
export declare function checkRetiredSyncLabelsConfig(configPath?: string): boolean;
|
|
@@ -2,10 +2,10 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { CONFIG_FILE_PATH } from "../loadConfig.js";
|
|
4
4
|
export const RETIRED_SYNC_LABELS_CONFIG_PATH = '.config/sync-labels.config.ts';
|
|
5
|
-
export function checkRetiredSyncLabelsConfig() {
|
|
5
|
+
export function checkRetiredSyncLabelsConfig(configPath = CONFIG_FILE_PATH) {
|
|
6
6
|
if (!existsSync(RETIRED_SYNC_LABELS_CONFIG_PATH)) {
|
|
7
7
|
return false;
|
|
8
8
|
}
|
|
9
|
-
reportError(`${RETIRED_SYNC_LABELS_CONFIG_PATH} is no longer read. Move its labels into the \`repoLabels\` block of ${
|
|
9
|
+
reportError(`${RETIRED_SYNC_LABELS_CONFIG_PATH} is no longer read. Move its labels into the \`repoLabels\` block of ${configPath} (presets move to \`repoLabels.extends\`; each label becomes a \`'name': { color, description }\` entry under \`repoLabels.labels\`), then delete the file.`);
|
|
10
10
|
return true;
|
|
11
11
|
}
|
package/dist/esm/tagCommand.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
1
2
|
/** Orchestrate the CLI `tag` command: parse flags and delegate to `createTags`. */
|
|
2
|
-
export declare function tagCommand(argv: string[]): void;
|
|
3
|
+
export declare function tagCommand(argv: string[], styles: StreamStyles): void;
|
package/dist/esm/tagCommand.js
CHANGED
|
@@ -5,10 +5,10 @@ const tagFlagSchema = {
|
|
|
5
5
|
dryRun: { long: '--dry-run', type: 'boolean' },
|
|
6
6
|
noGitChecks: { long: '--no-git-checks', type: 'boolean' },
|
|
7
7
|
};
|
|
8
|
-
export function tagCommand(argv) {
|
|
8
|
+
export function tagCommand(argv, styles) {
|
|
9
9
|
const { dryRun, noGitChecks } = parseArgsOrExit(argv, tagFlagSchema).flags;
|
|
10
10
|
try {
|
|
11
|
-
createTags({ dryRun, noGitChecks });
|
|
11
|
+
createTags({ dryRun, noGitChecks, style: styles.stdout });
|
|
12
12
|
}
|
|
13
13
|
catch (error) {
|
|
14
14
|
reportError(describeError(error));
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { type OutputStyle, type StreamStyles } from '@williamthorsen/nmr-core';
|
|
1
2
|
import { type ValidateAllChangelogOverridesInputs, type ValidateAllChangelogOverridesResult } from './changelogOverrides.js';
|
|
3
|
+
import { type WorkspaceDiscovery } from './discoverWorkspaces.js';
|
|
4
|
+
import { type LoadValidatedConfigResult } from './loadValidatedConfig.js';
|
|
2
5
|
import type { ChangelogEntry, ReleaseConfig } from './types.js';
|
|
3
6
|
/**
|
|
4
7
|
* Result of {@link validateOverridesCommand}: tiered exit code paired with a human-readable
|
|
5
|
-
* message
|
|
6
|
-
* uniformly thin.
|
|
8
|
+
* message, which keeps the CLI dispatch layer thin.
|
|
7
9
|
*
|
|
8
10
|
* Exit codes:
|
|
9
11
|
* - `0` — clean: no errors, no warnings.
|
|
@@ -16,8 +18,8 @@ export interface ValidateOverridesCommandResult {
|
|
|
16
18
|
}
|
|
17
19
|
/** Injection seams for unit testing. Production callers leave defaults; tests substitute deterministic fakes. */
|
|
18
20
|
export interface ValidateOverridesCommandDependencies {
|
|
19
|
-
discoverWorkspaces?: () =>
|
|
20
|
-
|
|
21
|
+
discoverWorkspaces?: () => WorkspaceDiscovery;
|
|
22
|
+
loadValidatedConfig?: () => Promise<LoadValidatedConfigResult>;
|
|
21
23
|
/**
|
|
22
24
|
* Build changelog entries for a scope. Defaults to `buildChangelogEntries`, the same path
|
|
23
25
|
* `release-kit prepare` uses — anchoring `validate`'s hash universe to `prepare`'s by
|
|
@@ -34,11 +36,15 @@ export interface ValidateOverridesCommandDependencies {
|
|
|
34
36
|
* {@link validateAllChangelogOverrides}.
|
|
35
37
|
*
|
|
36
38
|
* Single-package and monorepo modes are handled uniformly: single-package collapses to one
|
|
37
|
-
* project scope; monorepo expands to a project scope plus one scope per workspace.
|
|
39
|
+
* project scope; monorepo expands to a project scope plus one scope per workspace. A workspace declaring
|
|
40
|
+
* patterns that resolve to no package is neither, and exits `2`.
|
|
41
|
+
*
|
|
42
|
+
* `configPath` names the config file to read, relative to the working directory; it defaults to
|
|
43
|
+
* `CONFIG_FILE_PATH`.
|
|
38
44
|
*/
|
|
39
|
-
export declare function validateOverridesCommand(dependencies?: ValidateOverridesCommandDependencies): Promise<ValidateOverridesCommandResult>;
|
|
45
|
+
export declare function validateOverridesCommand(styles: StreamStyles, configPath?: string, dependencies?: ValidateOverridesCommandDependencies): Promise<ValidateOverridesCommandResult>;
|
|
40
46
|
/**
|
|
41
47
|
* Pure formatter — take an aggregated validation result, return the tiered exit code and a
|
|
42
48
|
* rendered message. Exported for unit testing without going through the full discovery path.
|
|
43
49
|
*/
|
|
44
|
-
export declare function formatValidateOverridesResult(result: ValidateAllChangelogOverridesResult): ValidateOverridesCommandResult;
|
|
50
|
+
export declare function formatValidateOverridesResult(result: ValidateAllChangelogOverridesResult, style: OutputStyle): ValidateOverridesCommandResult;
|
|
@@ -1,60 +1,57 @@
|
|
|
1
|
-
import { formatErrorLine } from '@williamthorsen/nmr-core';
|
|
1
|
+
import { formatErrorLine, formatStatusLine } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
3
|
import { buildChangelogEntries } from "./buildChangelogEntries.js";
|
|
4
4
|
import { resolveOverridePath, validateAllChangelogOverrides, } from "./changelogOverrides.js";
|
|
5
|
-
import { discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
5
|
+
import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
6
6
|
import { buildTagPattern, getAllTagPrefixes } from "./generateChangelogs.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { mergeMonorepoConfig, mergeSinglePackageConfig, readRootPackageVersion } from "./loadConfig.js";
|
|
8
|
+
import { loadValidatedConfig } from "./loadValidatedConfig.js";
|
|
9
9
|
const SYNTHETIC_VALIDATE_TAG = 'validate-only';
|
|
10
|
-
export async function validateOverridesCommand(dependencies = {}) {
|
|
10
|
+
export async function validateOverridesCommand(styles, configPath, dependencies = {}) {
|
|
11
11
|
const discover = dependencies.discoverWorkspaces ?? discoverWorkspaces;
|
|
12
|
-
const load = dependencies.
|
|
12
|
+
const load = dependencies.loadValidatedConfig ?? (() => loadValidatedConfig(configPath));
|
|
13
13
|
const buildEntries = dependencies.buildEntries ?? defaultBuildEntries;
|
|
14
14
|
const validate = dependencies.validate ?? validateAllChangelogOverrides;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
catch (error) {
|
|
20
|
-
return { exitCode: 2, message: formatErrorLine(`Failed to load config: ${describeError(error)}`) };
|
|
21
|
-
}
|
|
22
|
-
let userConfig;
|
|
23
|
-
try {
|
|
24
|
-
userConfig = validateLoadedConfig(rawConfig);
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
27
|
-
return { exitCode: 2, message: describeError(error) };
|
|
15
|
+
const configResult = await load();
|
|
16
|
+
if (configResult.status === 'invalid') {
|
|
17
|
+
return { exitCode: 2, message: formatConfigProblem(configResult.problem) };
|
|
28
18
|
}
|
|
29
|
-
|
|
19
|
+
const userConfig = configResult.status === 'ok' ? configResult.config : undefined;
|
|
20
|
+
let workspace;
|
|
30
21
|
try {
|
|
31
|
-
|
|
22
|
+
workspace = discover();
|
|
32
23
|
}
|
|
33
24
|
catch (error) {
|
|
34
25
|
return { exitCode: 2, message: formatErrorLine(`Failed to discover workspaces: ${describeError(error)}`) };
|
|
35
26
|
}
|
|
27
|
+
if (workspace.kind === 'empty') {
|
|
28
|
+
return {
|
|
29
|
+
exitCode: 2,
|
|
30
|
+
message: formatErrorLine(`No workspace package to validate. ${describeEmptyWorkspace(workspace)}`),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
36
33
|
let inputs;
|
|
37
34
|
try {
|
|
38
35
|
inputs =
|
|
39
|
-
|
|
36
|
+
workspace.kind === 'single-package'
|
|
40
37
|
? buildSinglePackageInputs(userConfig, buildEntries)
|
|
41
|
-
: buildMonorepoInputs(
|
|
38
|
+
: buildMonorepoInputs(workspace.packageDirs, userConfig, buildEntries);
|
|
42
39
|
}
|
|
43
40
|
catch (error) {
|
|
44
41
|
return { exitCode: 2, message: formatErrorLine(`Failed to resolve overrides scope: ${describeError(error)}`) };
|
|
45
42
|
}
|
|
46
43
|
const result = validate(inputs);
|
|
47
|
-
return formatValidateOverridesResult(result);
|
|
44
|
+
return formatValidateOverridesResult(result, styles.stderr);
|
|
48
45
|
}
|
|
49
|
-
export function formatValidateOverridesResult(result) {
|
|
46
|
+
export function formatValidateOverridesResult(result, style) {
|
|
50
47
|
const { errors, warnings } = result;
|
|
51
48
|
if (errors.length === 0 && warnings.length === 0) {
|
|
52
49
|
return { exitCode: 0, message: 'All override files are valid (no errors, no stale keys).' };
|
|
53
50
|
}
|
|
54
51
|
const exitCode = errors.length > 0 ? 2 : 1;
|
|
55
52
|
const summary = formatSummaryLine(errors.length, warnings.length);
|
|
56
|
-
const errorLines = errors.map((message) => `
|
|
57
|
-
const warningLines = warnings.map((message) => `
|
|
53
|
+
const errorLines = errors.map((message) => ` ${formatStatusLine(style, 'failed', message)}`);
|
|
54
|
+
const warningLines = warnings.map((message) => ` ${formatStatusLine(style, 'warning', message)}`);
|
|
58
55
|
const message = [summary, '', ...errorLines, ...warningLines].join('\n');
|
|
59
56
|
return { exitCode, message };
|
|
60
57
|
}
|
|
@@ -94,15 +91,10 @@ function flattenEntriesToHashes(entries) {
|
|
|
94
91
|
}
|
|
95
92
|
return hashes;
|
|
96
93
|
}
|
|
97
|
-
function
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const { config, errors } = validateConfig(rawConfig);
|
|
102
|
-
if (errors.length > 0) {
|
|
103
|
-
throw new Error(`Invalid config:\n - ${errors.join('\n - ')}`);
|
|
104
|
-
}
|
|
105
|
-
return config;
|
|
94
|
+
function formatConfigProblem(problem) {
|
|
95
|
+
return problem.kind === 'load'
|
|
96
|
+
? formatErrorLine(`Failed to load config: ${problem.message}`)
|
|
97
|
+
: `Invalid config:\n - ${problem.errors.join('\n - ')}`;
|
|
106
98
|
}
|
|
107
99
|
function buildSinglePackageInputs(userConfig, buildEntries) {
|
|
108
100
|
const config = mergeSinglePackageConfig(userConfig);
|
package/docs/configuration.md
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
# Configuration
|
|
2
2
|
|
|
3
|
-
The fields of `.config/release-kit.config.ts`, how a workspace declares its prior identities or a retired package, the rule against colliding tag prefixes,
|
|
3
|
+
The fields of `.config/release-kit.config.ts`, how a workspace declares its prior identities or a retired package, the rule against colliding tag prefixes, the patterns that decide a bump, and the environment variable that sets the output style.
|
|
4
4
|
|
|
5
5
|
A minimal config file is in the [README](../README.md#configuration).
|
|
6
6
|
|
|
7
|
+
## Config file location
|
|
8
|
+
|
|
9
|
+
release-kit reads `.config/release-kit.config.ts`, resolved against the working directory. A run reads a different file when the subcommand is given `--config <path>`, which is also resolved against the working directory.
|
|
10
|
+
|
|
11
|
+
The flag is accepted by every subcommand that reads a config: `prepare`, `publish`, `create-github-release`, `show-tag-prefixes`, `overrides validate`, `sync-labels init`, and `sync-labels generate`. `commit`, `tag`, `push`, and `sync-labels sync` read no config and reject it.
|
|
12
|
+
|
|
13
|
+
The two paths differ in what an absent file means. An absent default path means the repo declares no config, and the run proceeds on discovered defaults. An absent `--config` path fails the command with an error naming the resolved path, because a caller who names a file expects that file to be read. A file that exists and fails validation is an error on both paths alike, and so is one that exists and fails to load.
|
|
14
|
+
|
|
15
|
+
`--config` names a file to read, never a write target. `release-kit init --with-config` scaffolds to `.config/release-kit.config.ts` and takes no `--config`, and `sync-labels init` writes its seeded `repoLabels` block to that same path.
|
|
16
|
+
|
|
7
17
|
## `ReleaseKitConfig` reference
|
|
8
18
|
|
|
9
19
|
| Field | Type | Description |
|
|
@@ -89,6 +99,8 @@ Print a per-workspace table of derived tag prefixes, tag counts, and declared le
|
|
|
89
99
|
|
|
90
100
|
Exits `0` when every workspace derives a prefix and there are no cross-workspace collisions; exits `1` on any derivation failure or collision. Undeclared candidates do not affect the exit code — they surface as a warning via the `legacy tag prefixes are declared` readyup check.
|
|
91
101
|
|
|
102
|
+
A config file that exists and either fails to load or fails schema validation reports the failure on stderr and exits `1`, printing no preview, because a config that cannot be read leaves the declared-versus-undeclared verdict wrong in the one dimension the command exists to report on. That holds on the default path and under `--config`, and in single-package mode as well as monorepo mode. An absent default config is a supported state and still previews against derived defaults.
|
|
103
|
+
|
|
92
104
|
In single-package mode, prints a single row with `workspacePath = .` and `derivedPrefix = v`; legacy entries and undeclared-candidate scanning are not applicable.
|
|
93
105
|
|
|
94
106
|
## `VersionPatterns`
|
|
@@ -103,3 +115,25 @@ interface VersionPatterns {
|
|
|
103
115
|
```
|
|
104
116
|
|
|
105
117
|
Default: `{ major: ['!'], minor: ['feat'] }`
|
|
118
|
+
|
|
119
|
+
## Output style
|
|
120
|
+
|
|
121
|
+
release-kit prints in one of two styles. `rich` marks each status with an emoji, such as ✅ or 🟠. `plain` replaces each emoji with a word, such as `PASS` or `WARN`, which a reader of a log can search for. The style is decided separately for stdout and stderr, so redirecting one stream to a file leaves the other rich at a terminal.
|
|
122
|
+
|
|
123
|
+
The `RELEASE_KIT_OUTPUT_STYLE` environment variable sets the style for every command, `init` and `sync-labels init` included:
|
|
124
|
+
|
|
125
|
+
| Value | Effect |
|
|
126
|
+
| ------- | ------------------------------------------------------------------------ |
|
|
127
|
+
| `auto` | Detect the style of each stream. The same as leaving the variable unset. |
|
|
128
|
+
| `plain` | Print plain on both streams. |
|
|
129
|
+
| `rich` | Print rich on both streams, even in a pipe or under CI. |
|
|
130
|
+
|
|
131
|
+
Any other value is a usage error: release-kit reports it and exits with code 1.
|
|
132
|
+
|
|
133
|
+
The variable outranks detection. Under `auto`, release-kit defers to detection, which reads `CI`, then the stream, then `TERM`. A stream is plain when any of these holds, and rich otherwise:
|
|
134
|
+
|
|
135
|
+
1. `CI` is set to anything other than an empty string or `false`.
|
|
136
|
+
2. The stream is not a terminal, as when its output is piped or redirected.
|
|
137
|
+
3. `TERM` is `linux`, the Linux virtual console, which draws no emoji.
|
|
138
|
+
|
|
139
|
+
The style governs emoji alone. Changelog section emoji are file content rather than terminal output, and they are unaffected.
|
package/docs/labels.md
CHANGED
|
@@ -8,6 +8,8 @@ Manage GitHub label definitions via the `repoLabels` block of `.config/release-k
|
|
|
8
8
|
|
|
9
9
|
`init` scaffolds the `.github/workflows/sync-labels.yaml` caller workflow and seeds a `repoLabels` block with scope labels discovered from workspaces and declared `retiredPackages`. When `.config/release-kit.config.ts` does not exist, `init` writes it; when it does, `init` prints the block for manual paste — a hand-authored config is never rewritten. `generate` resolves the block and writes `.github/labels.yaml`; with `--check` it regenerates in memory and exits non-zero if the committed file is stale or missing, writing nothing. `sync` triggers the workflow remotely — it requires the `gh` CLI and an existing workflow file.
|
|
10
10
|
|
|
11
|
+
`init` and `generate` accept `--config <path>` to read a config elsewhere; `sync` reads none and rejects the flag. Under `--config`, `init` reads the named file and prints the block for manual paste, and fails when that file does not exist rather than scaffolding one, so its write target stays `.config/release-kit.config.ts`. See [Config file location](configuration.md#config-file-location).
|
|
12
|
+
|
|
11
13
|
Every `sync-labels` subcommand refuses to run while the retired `.config/sync-labels.config.ts` is present, so custom labels cannot be silently dropped mid-migration, and the refusal names what to move and where.
|
|
12
14
|
|
|
13
15
|
### Label configuration
|
package/docs/work-types.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Work types and tiers
|
|
2
2
|
|
|
3
|
-
The taxonomy against which release-kit parses commits: its tiers, the breaking-change policy, section markers, customization, and the
|
|
3
|
+
The taxonomy against which release-kit parses commits: its tiers, the breaking-change policy, section markers, customization, and the maintainer scripts that keep release-kit's copy level with its upstream.
|
|
4
4
|
|
|
5
|
-
release-kit bundles a copy of the codeassembly canonical taxonomy in `packages/release-kit/src/work-types.json`, kept level by
|
|
5
|
+
release-kit bundles a copy of the codeassembly canonical taxonomy in `packages/release-kit/src/work-types.json`, kept level by the [maintainer scripts](#maintaining-the-bundled-taxonomy). The taxonomy is split into three tiers that drive section rendering and audience classification.
|
|
6
6
|
|
|
7
7
|
| Tier | Key | Header | Aliases | `!` policy |
|
|
8
8
|
| -------- | ----------- | ------------------------- | ------------- | ------------ |
|
|
@@ -53,7 +53,7 @@ The release-prepare orchestrators (`releasePrepare`, `releasePrepareMono`, `rele
|
|
|
53
53
|
```
|
|
54
54
|
arrays
|
|
55
55
|
Found 1 commits since arrays-v1.0.0
|
|
56
|
-
|
|
56
|
+
🟠 1 policy violation:
|
|
57
57
|
· def5678 'internal!: refactor cache' — type 'internal' at prefix surface
|
|
58
58
|
Bumping versions (patch)...
|
|
59
59
|
📦 1.0.0 → 1.0.1 (patch)
|
|
@@ -103,14 +103,17 @@ Work types from your config are merged with these defaults by key — your entri
|
|
|
103
103
|
|
|
104
104
|
The default `devOnlySections` (excluded from public release notes but still written to `CHANGELOG.md`) are derived from the `internal` and `process` tiers (excluding `fmt`). Override via `changelogJson.devOnlySections` in your config; matching is decorator-tolerant, so a bare-name override like `['Internal features']` keeps working against the emoji-prefixed and prefix-decorated default titles.
|
|
105
105
|
|
|
106
|
-
##
|
|
106
|
+
## Maintaining the bundled taxonomy
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
`src/workTypesData.ts` mirrors `src/work-types.json` for runtime use. Two maintainer scripts of the release-kit package compare that JSON with its codeassembly upstream and update it. They exist only in a checkout of this repository and are not part of the published `release-kit` CLI.
|
|
109
109
|
|
|
110
|
-
`check`
|
|
110
|
+
- `nmr work-types:check` compares the content of `src/work-types.json` with the upstream, ignoring the local `$schema` hint.
|
|
111
|
+
- `nmr work-types:sync` overwrites `src/work-types.json` with the upstream when their content differs.
|
|
112
|
+
|
|
113
|
+
Both run from `packages/release-kit`, or from anywhere in the repository as `nmr -F @williamthorsen/release-kit work-types:check` and `nmr -F @williamthorsen/release-kit work-types:sync`. After `sync` writes the file, update `src/workTypesData.ts` to match; `workTypesData.unit.test.ts` fails until the two agree.
|
|
111
114
|
|
|
112
|
-
|
|
115
|
+
`check` exits 0 on a match, 1 on drift, 2 when the upstream fetch fails, and 3 when either file is not valid JSON or the upstream fails the shape check. When the upstream URL returns 404, `check` exits 0 with a warning.
|
|
113
116
|
|
|
114
117
|
### Authenticated fetches
|
|
115
118
|
|
|
116
|
-
The upstream codeassembly repo is public, so `check` and `sync` need no token. When `GITHUB_TOKEN` is set in the environment, both
|
|
119
|
+
The upstream codeassembly repo is public, so `check` and `sync` need no token. When `GITHUB_TOKEN` is set in the environment, both scripts send it as `Authorization: Bearer <token>`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/release-kit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.0.0",
|
|
4
4
|
"description": "Version-bumping and changelog-generation toolkit for release workflows",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"changelog",
|
|
@@ -51,34 +51,33 @@
|
|
|
51
51
|
"schemas/**"
|
|
52
52
|
],
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@williamthorsen/nmr-core": "0.
|
|
55
|
-
"@williamthorsen/toolbelt.errors": "0.
|
|
56
|
-
"glob": "13.0.6",
|
|
54
|
+
"@williamthorsen/nmr-core": "0.13.0",
|
|
55
|
+
"@williamthorsen/toolbelt.errors": "0.8.1",
|
|
57
56
|
"json-stringify-pretty-compact": "4.0.0",
|
|
58
57
|
"semver": "7.8.5",
|
|
59
|
-
"yaml": "2.9.
|
|
60
|
-
"zod": "4.5
|
|
58
|
+
"yaml": "2.9.1",
|
|
59
|
+
"zod": "4.6.5"
|
|
61
60
|
},
|
|
62
61
|
"devDependencies": {
|
|
63
62
|
"@types/semver": "7.8.0",
|
|
64
|
-
"@williamthorsen/toolbelt.filesystem": "0.
|
|
65
|
-
"@williamthorsen/toolbelt.testing": "0.
|
|
66
|
-
"@williamthorsen/toolbelt.vitest": "0.
|
|
67
|
-
"readyup": "0.
|
|
63
|
+
"@williamthorsen/toolbelt.filesystem": "0.9.1",
|
|
64
|
+
"@williamthorsen/toolbelt.testing": "0.6.1",
|
|
65
|
+
"@williamthorsen/toolbelt.vitest": "0.10.1",
|
|
66
|
+
"readyup": "0.38.0",
|
|
68
67
|
"smol-toml": "1.8.0",
|
|
69
68
|
"vitest": "4.1.11"
|
|
70
69
|
},
|
|
71
70
|
"engines": {
|
|
72
|
-
"node": ">=24"
|
|
71
|
+
"node": ">=24.16.0"
|
|
73
72
|
},
|
|
74
73
|
"publishConfig": {
|
|
75
74
|
"access": "public",
|
|
76
75
|
"registry": "https://registry.npmjs.org"
|
|
77
76
|
},
|
|
78
77
|
"scripts": {
|
|
79
|
-
"build:post": "
|
|
78
|
+
"build:post": "rdy compile",
|
|
80
79
|
"build:pre": "rdy verify",
|
|
81
|
-
"work-types:check": "
|
|
82
|
-
"work-types:sync": "
|
|
80
|
+
"work-types:check": "node scripts/check-work-types.ts",
|
|
81
|
+
"work-types:sync": "node scripts/sync-work-types.ts"
|
|
83
82
|
}
|
|
84
83
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Main categories
|
|
5
5
|
- name: ai
|
|
6
6
|
color: a2eeef
|
|
7
|
-
description:
|
|
7
|
+
description: Guidance on working in this repo, such as its AGENTS.md or a repo-local skill, or its agent config
|
|
8
8
|
- name: breaking
|
|
9
9
|
color: b60205
|
|
10
10
|
description: Breaking API change or removal of functionality
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
description: Marks functionality for future removal
|
|
17
17
|
- name: documentation
|
|
18
18
|
color: a2eeef
|
|
19
|
-
description:
|
|
19
|
+
description: Documentation for human readers, such as a README, a guide, or a code comment
|
|
20
20
|
- name: feature
|
|
21
21
|
color: '0075ca'
|
|
22
22
|
description: Added or improved external functionality
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/** URL of the upstream canonical `work-types.json` published by codeassembly. */
|
|
2
|
-
export declare const UPSTREAM_WORK_TYPES_URL = "https://raw.githubusercontent.com/williamthorsen/codeassembly/main/packages/agents/content/skills/_data/work-types.json";
|
|
3
|
-
/** Outcome of a drift check, paired with a human-readable message. */
|
|
4
|
-
export interface DriftCheckResult {
|
|
5
|
-
/**
|
|
6
|
-
* Process exit code semantics:
|
|
7
|
-
* - `0` — match (or upstream not found, with a warning).
|
|
8
|
-
* - `1` — drift detected.
|
|
9
|
-
* - `2` — network error.
|
|
10
|
-
* - `3` — schema mismatch (upstream JSON does not parse or fails the schema invariants).
|
|
11
|
-
*/
|
|
12
|
-
exitCode: 0 | 1 | 2 | 3;
|
|
13
|
-
message: string;
|
|
14
|
-
}
|
|
15
|
-
/** Minimal injection seam so unit tests can substitute a deterministic fetcher. */
|
|
16
|
-
export interface CheckWorkTypesDriftDependencies {
|
|
17
|
-
/** Absolute path of the local `work-types.json`. Defaults to the bundled file. */
|
|
18
|
-
localPath?: string;
|
|
19
|
-
/** HTTP fetcher. Defaults to global `fetch` (Node 18+). */
|
|
20
|
-
fetch?: typeof globalThis.fetch;
|
|
21
|
-
/** Override the upstream URL (used by tests; production callers should leave default). */
|
|
22
|
-
upstreamUrl?: string;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* Compare the bundled `work-types.json` against the upstream codeassembly canonical and
|
|
26
|
-
* report drift.
|
|
27
|
-
*
|
|
28
|
-
* Failure modes:
|
|
29
|
-
* - Network error → exit 2 with a diagnostic.
|
|
30
|
-
* - Upstream 404 → exit 0 with a warning.
|
|
31
|
-
* - Schema mismatch → exit 3 with a diagnostic.
|
|
32
|
-
* - Drift → exit 1 with a unified-diff-style message.
|
|
33
|
-
* - Match → exit 0.
|
|
34
|
-
*/
|
|
35
|
-
export declare function checkWorkTypesDrift(dependencies?: CheckWorkTypesDriftDependencies): Promise<DriftCheckResult>;
|