@williamthorsen/release-kit 0.2.2 → 1.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/CHANGELOG.md +43 -1
- package/README.md +202 -219
- package/dist/esm/.cache +1 -1
- package/dist/esm/bin/release-kit.d.ts +2 -0
- package/dist/esm/bin/release-kit.js +73 -0
- package/dist/esm/component.d.ts +2 -0
- package/dist/esm/component.js +15 -0
- package/dist/esm/defaults.d.ts +3 -2
- package/dist/esm/defaults.js +17 -12
- package/dist/esm/determineBumpType.d.ts +2 -2
- package/dist/esm/determineBumpType.js +11 -13
- package/dist/esm/discoverWorkspaces.d.ts +1 -0
- package/dist/esm/discoverWorkspaces.js +45 -0
- package/dist/esm/getCommitsSinceTarget.js +2 -1
- package/dist/esm/index.d.ts +5 -2
- package/dist/esm/index.js +11 -2
- package/dist/esm/init/checks.d.ts +9 -0
- package/dist/esm/init/checks.js +56 -0
- package/dist/esm/init/detectRepoType.d.ts +2 -0
- package/dist/esm/init/detectRepoType.js +19 -0
- package/dist/esm/init/initCommand.d.ts +5 -0
- package/dist/esm/init/initCommand.js +65 -0
- package/dist/esm/init/parseJsonRecord.d.ts +1 -0
- package/dist/esm/init/parseJsonRecord.js +15 -0
- package/dist/esm/init/prompt.d.ts +5 -0
- package/dist/esm/init/prompt.js +30 -0
- package/dist/esm/init/scaffold.d.ts +9 -0
- package/dist/esm/init/scaffold.js +65 -0
- package/dist/esm/init/templates.d.ts +3 -0
- package/dist/esm/init/templates.js +105 -0
- package/dist/esm/loadConfig.d.ts +5 -0
- package/dist/esm/loadConfig.js +91 -0
- package/dist/esm/parseCommitMessage.d.ts +1 -1
- package/dist/esm/parseCommitMessage.js +4 -4
- package/dist/esm/prepareCommand.d.ts +1 -0
- package/dist/esm/prepareCommand.js +77 -0
- package/dist/esm/releasePrepare.d.ts +1 -1
- package/dist/esm/releasePrepare.js +7 -3
- package/dist/esm/releasePrepareMono.d.ts +1 -1
- package/dist/esm/releasePrepareMono.js +16 -10
- package/dist/esm/runReleasePrepare.d.ts +9 -0
- package/dist/esm/runReleasePrepare.js +112 -0
- package/dist/esm/types.d.ts +22 -4
- package/dist/esm/validateConfig.d.ts +5 -0
- package/dist/esm/validateConfig.js +143 -0
- package/dist/tsconfig.generate-typings.tsbuildinfo +1 -1
- package/package.json +12 -4
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { component } from "./component.js";
|
|
3
|
+
import { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
4
|
+
const CONFIG_FILE_PATH = ".config/release-kit.config.ts";
|
|
5
|
+
async function loadConfig() {
|
|
6
|
+
if (!existsSync(CONFIG_FILE_PATH)) {
|
|
7
|
+
return void 0;
|
|
8
|
+
}
|
|
9
|
+
const { createJiti } = await import("jiti");
|
|
10
|
+
const jiti = createJiti(import.meta.url);
|
|
11
|
+
const imported = await jiti.import(CONFIG_FILE_PATH);
|
|
12
|
+
if (!isRecord(imported)) {
|
|
13
|
+
throw new Error(`Config file must export an object, got ${Array.isArray(imported) ? "array" : typeof imported}`);
|
|
14
|
+
}
|
|
15
|
+
const resolved = imported.default ?? imported.config;
|
|
16
|
+
if (resolved === void 0) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
"Config file must have a default export or a named `config` export (e.g., `export default { ... }` or `export const config = { ... }`)"
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
return resolved;
|
|
22
|
+
}
|
|
23
|
+
function isRecord(value) {
|
|
24
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
function mergeMonorepoConfig(discoveredPaths, userConfig) {
|
|
27
|
+
let components = discoveredPaths.map((workspacePath) => component(workspacePath));
|
|
28
|
+
if (userConfig?.components !== void 0) {
|
|
29
|
+
const overrides = new Map(userConfig.components.map((c) => [c.dir, c]));
|
|
30
|
+
components = components.filter((c) => {
|
|
31
|
+
const override = overrides.get(c.dir);
|
|
32
|
+
return override?.shouldExclude !== true;
|
|
33
|
+
}).map((c) => {
|
|
34
|
+
const override = overrides.get(c.dir);
|
|
35
|
+
if (override?.tagPrefix !== void 0) {
|
|
36
|
+
return { ...c, tagPrefix: override.tagPrefix };
|
|
37
|
+
}
|
|
38
|
+
return c;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
const workTypes = userConfig?.workTypes === void 0 ? { ...DEFAULT_WORK_TYPES } : { ...DEFAULT_WORK_TYPES, ...userConfig.workTypes };
|
|
42
|
+
const versionPatterns = userConfig?.versionPatterns === void 0 ? { ...DEFAULT_VERSION_PATTERNS } : { ...userConfig.versionPatterns };
|
|
43
|
+
const result = {
|
|
44
|
+
components,
|
|
45
|
+
workTypes,
|
|
46
|
+
versionPatterns
|
|
47
|
+
};
|
|
48
|
+
const formatCommand = userConfig?.formatCommand;
|
|
49
|
+
if (formatCommand !== void 0) {
|
|
50
|
+
result.formatCommand = formatCommand;
|
|
51
|
+
}
|
|
52
|
+
const cliffConfigPath = userConfig?.cliffConfigPath;
|
|
53
|
+
if (cliffConfigPath !== void 0) {
|
|
54
|
+
result.cliffConfigPath = cliffConfigPath;
|
|
55
|
+
}
|
|
56
|
+
const workspaceAliases = userConfig?.workspaceAliases;
|
|
57
|
+
if (workspaceAliases !== void 0) {
|
|
58
|
+
result.workspaceAliases = workspaceAliases;
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
function mergeSinglePackageConfig(userConfig) {
|
|
63
|
+
const workTypes = userConfig?.workTypes === void 0 ? { ...DEFAULT_WORK_TYPES } : { ...DEFAULT_WORK_TYPES, ...userConfig.workTypes };
|
|
64
|
+
const versionPatterns = userConfig?.versionPatterns === void 0 ? { ...DEFAULT_VERSION_PATTERNS } : { ...userConfig.versionPatterns };
|
|
65
|
+
const result = {
|
|
66
|
+
tagPrefix: "v",
|
|
67
|
+
packageFiles: ["package.json"],
|
|
68
|
+
changelogPaths: ["."],
|
|
69
|
+
workTypes,
|
|
70
|
+
versionPatterns
|
|
71
|
+
};
|
|
72
|
+
const formatCommand = userConfig?.formatCommand;
|
|
73
|
+
if (formatCommand !== void 0) {
|
|
74
|
+
result.formatCommand = formatCommand;
|
|
75
|
+
}
|
|
76
|
+
const cliffConfigPath = userConfig?.cliffConfigPath;
|
|
77
|
+
if (cliffConfigPath !== void 0) {
|
|
78
|
+
result.cliffConfigPath = cliffConfigPath;
|
|
79
|
+
}
|
|
80
|
+
const workspaceAliases = userConfig?.workspaceAliases;
|
|
81
|
+
if (workspaceAliases !== void 0) {
|
|
82
|
+
result.workspaceAliases = workspaceAliases;
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
export {
|
|
87
|
+
CONFIG_FILE_PATH,
|
|
88
|
+
loadConfig,
|
|
89
|
+
mergeMonorepoConfig,
|
|
90
|
+
mergeSinglePackageConfig
|
|
91
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { ParsedCommit, WorkTypeConfig } from './types.ts';
|
|
2
|
-
export declare function parseCommitMessage(message: string, hash: string, workTypes:
|
|
2
|
+
export declare function parseCommitMessage(message: string, hash: string, workTypes: Record<string, WorkTypeConfig>, workspaceAliases?: Record<string, string>): ParsedCommit | undefined;
|
|
@@ -27,14 +27,14 @@ function parseCommitMessage(message, hash, workTypes, workspaceAliases) {
|
|
|
27
27
|
}
|
|
28
28
|
function resolveType(rawType, workTypes) {
|
|
29
29
|
const lowered = rawType.toLowerCase();
|
|
30
|
-
for (const config of workTypes) {
|
|
31
|
-
if (
|
|
32
|
-
return
|
|
30
|
+
for (const [key, config] of Object.entries(workTypes)) {
|
|
31
|
+
if (key === lowered) {
|
|
32
|
+
return key;
|
|
33
33
|
}
|
|
34
34
|
if (config.aliases !== void 0) {
|
|
35
35
|
for (const alias of config.aliases) {
|
|
36
36
|
if (alias === lowered) {
|
|
37
|
-
return
|
|
37
|
+
return key;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function prepareCommand(argv: string[]): Promise<void>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
2
|
+
import { loadConfig, mergeMonorepoConfig, mergeSinglePackageConfig } from "./loadConfig.js";
|
|
3
|
+
import { releasePrepare } from "./releasePrepare.js";
|
|
4
|
+
import { releasePrepareMono } from "./releasePrepareMono.js";
|
|
5
|
+
import { parseArgs, RELEASE_TAGS_FILE, writeReleaseTags } from "./runReleasePrepare.js";
|
|
6
|
+
import { validateConfig } from "./validateConfig.js";
|
|
7
|
+
async function prepareCommand(argv) {
|
|
8
|
+
const { dryRun, bumpOverride, only } = parseArgs(argv);
|
|
9
|
+
const options = { dryRun, ...bumpOverride === void 0 ? {} : { bumpOverride } };
|
|
10
|
+
let rawConfig;
|
|
11
|
+
try {
|
|
12
|
+
rawConfig = await loadConfig();
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error(`Error loading config: ${error instanceof Error ? error.message : String(error)}`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
let userConfig;
|
|
18
|
+
if (rawConfig !== void 0) {
|
|
19
|
+
const { config, errors } = validateConfig(rawConfig);
|
|
20
|
+
if (errors.length > 0) {
|
|
21
|
+
console.error("Invalid config:");
|
|
22
|
+
for (const err of errors) {
|
|
23
|
+
console.error(` - ${err}`);
|
|
24
|
+
}
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
userConfig = config;
|
|
28
|
+
}
|
|
29
|
+
let discoveredPaths;
|
|
30
|
+
try {
|
|
31
|
+
discoveredPaths = await discoverWorkspaces();
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(`Error discovering workspaces: ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
let tags = [];
|
|
37
|
+
if (discoveredPaths === void 0) {
|
|
38
|
+
if (only !== void 0) {
|
|
39
|
+
console.error("Error: --only is only supported for monorepo configurations");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
const config = mergeSinglePackageConfig(userConfig);
|
|
43
|
+
try {
|
|
44
|
+
tags = releasePrepare(config, options);
|
|
45
|
+
writeReleaseTags(tags, dryRun);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error("Error preparing release:", error instanceof Error ? error.message : String(error));
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
const config = mergeMonorepoConfig(discoveredPaths, userConfig);
|
|
52
|
+
if (only !== void 0) {
|
|
53
|
+
const knownNames = config.components.map((c) => c.dir);
|
|
54
|
+
for (const name of only) {
|
|
55
|
+
if (!knownNames.includes(name)) {
|
|
56
|
+
console.error(`Error: Unknown component "${name}". Known components: ${knownNames.join(", ")}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
config.components = config.components.filter((c) => only.includes(c.dir));
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
tags = releasePrepareMono(config, options);
|
|
64
|
+
writeReleaseTags(tags, dryRun);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error("Error preparing release:", error instanceof Error ? error.message : String(error));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (tags.length > 0) {
|
|
71
|
+
console.info(`
|
|
72
|
+
Release tags file: ${RELEASE_TAGS_FILE}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
prepareCommand
|
|
77
|
+
};
|
|
@@ -3,4 +3,4 @@ export interface ReleasePrepareOptions {
|
|
|
3
3
|
dryRun: boolean;
|
|
4
4
|
bumpOverride?: ReleaseType;
|
|
5
5
|
}
|
|
6
|
-
export declare function releasePrepare(config: ReleaseConfig, options: ReleasePrepareOptions):
|
|
6
|
+
export declare function releasePrepare(config: ReleaseConfig, options: ReleasePrepareOptions): string[];
|
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import { bumpAllVersions } from "./bumpAllVersions.js";
|
|
3
|
+
import { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
3
4
|
import { determineBumpType } from "./determineBumpType.js";
|
|
4
5
|
import { generateChangelogs } from "./generateChangelogs.js";
|
|
5
6
|
import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
|
|
6
7
|
import { parseCommitMessage } from "./parseCommitMessage.js";
|
|
7
8
|
function releasePrepare(config, options) {
|
|
8
9
|
const { dryRun, bumpOverride } = options;
|
|
10
|
+
const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
|
|
11
|
+
const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
|
|
9
12
|
console.info("Finding commits since last release...");
|
|
10
13
|
const { tag, commits } = getCommitsSinceTarget(config.tagPrefix);
|
|
11
14
|
console.info(` Found ${commits.length} commits since ${tag ?? "the beginning"}`);
|
|
12
15
|
let releaseType;
|
|
13
16
|
if (bumpOverride === void 0) {
|
|
14
|
-
const parsedCommits = commits.map((c) => parseCommitMessage(c.message, c.hash,
|
|
17
|
+
const parsedCommits = commits.map((c) => parseCommitMessage(c.message, c.hash, workTypes, config.workspaceAliases)).filter((c) => c !== void 0);
|
|
15
18
|
console.info(` Parsed ${parsedCommits.length} typed commits`);
|
|
16
|
-
releaseType = determineBumpType(parsedCommits,
|
|
19
|
+
releaseType = determineBumpType(parsedCommits, workTypes, versionPatterns);
|
|
17
20
|
} else {
|
|
18
21
|
releaseType = bumpOverride;
|
|
19
22
|
console.info(` Using bump override: ${releaseType}`);
|
|
20
23
|
}
|
|
21
24
|
if (releaseType === void 0) {
|
|
22
25
|
console.info("No release-worthy changes found. Skipping.");
|
|
23
|
-
return;
|
|
26
|
+
return [];
|
|
24
27
|
}
|
|
25
28
|
console.info(`Bumping versions (${releaseType})...`);
|
|
26
29
|
const newVersion = bumpAllVersions(config.packageFiles, releaseType, dryRun);
|
|
@@ -42,6 +45,7 @@ function releasePrepare(config, options) {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
console.info(`Release preparation complete: ${newTag}`);
|
|
48
|
+
return [newTag];
|
|
45
49
|
}
|
|
46
50
|
export {
|
|
47
51
|
releasePrepare
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { ReleasePrepareOptions } from './releasePrepare.ts';
|
|
2
2
|
import type { MonorepoReleaseConfig } from './types.ts';
|
|
3
|
-
export declare function releasePrepareMono(config: MonorepoReleaseConfig, options: ReleasePrepareOptions):
|
|
3
|
+
export declare function releasePrepareMono(config: MonorepoReleaseConfig, options: ReleasePrepareOptions): string[];
|
|
@@ -1,46 +1,51 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import { bumpAllVersions } from "./bumpAllVersions.js";
|
|
3
|
+
import { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
3
4
|
import { determineBumpType } from "./determineBumpType.js";
|
|
4
5
|
import { generateChangelog } from "./generateChangelogs.js";
|
|
5
6
|
import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
|
|
6
7
|
import { parseCommitMessage } from "./parseCommitMessage.js";
|
|
7
8
|
function releasePrepareMono(config, options) {
|
|
8
9
|
const { dryRun, bumpOverride } = options;
|
|
9
|
-
|
|
10
|
+
const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
|
|
11
|
+
const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
|
|
12
|
+
const tags = [];
|
|
10
13
|
for (const component of config.components) {
|
|
14
|
+
const name = component.dir;
|
|
11
15
|
console.info(`
|
|
12
|
-
Processing component: ${
|
|
16
|
+
Processing component: ${name}`);
|
|
13
17
|
console.info(" Finding commits since last release...");
|
|
14
18
|
const { tag, commits } = getCommitsSinceTarget(component.tagPrefix, component.paths);
|
|
15
|
-
|
|
19
|
+
const since = tag === void 0 ? "(no previous release found)" : `since ${tag}`;
|
|
20
|
+
console.info(` Found ${commits.length} commits ${since}`);
|
|
16
21
|
if (commits.length === 0) {
|
|
17
|
-
console.info(` No changes for ${
|
|
22
|
+
console.info(` No changes for ${name} ${since}. Skipping.`);
|
|
18
23
|
continue;
|
|
19
24
|
}
|
|
20
25
|
let releaseType;
|
|
21
26
|
if (bumpOverride === void 0) {
|
|
22
|
-
const parsedCommits = commits.map((c) => parseCommitMessage(c.message, c.hash,
|
|
27
|
+
const parsedCommits = commits.map((c) => parseCommitMessage(c.message, c.hash, workTypes, config.workspaceAliases)).filter((c) => c !== void 0);
|
|
23
28
|
console.info(` Parsed ${parsedCommits.length} typed commits`);
|
|
24
|
-
releaseType = determineBumpType(parsedCommits,
|
|
29
|
+
releaseType = determineBumpType(parsedCommits, workTypes, versionPatterns);
|
|
25
30
|
} else {
|
|
26
31
|
releaseType = bumpOverride;
|
|
27
32
|
console.info(` Using bump override: ${releaseType}`);
|
|
28
33
|
}
|
|
29
34
|
if (releaseType === void 0) {
|
|
30
|
-
console.info(` No release-worthy changes for ${
|
|
35
|
+
console.info(` No release-worthy changes for ${name} ${since}. Skipping.`);
|
|
31
36
|
continue;
|
|
32
37
|
}
|
|
33
38
|
console.info(` Bumping versions (${releaseType})...`);
|
|
34
39
|
const newVersion = bumpAllVersions(component.packageFiles, releaseType, dryRun);
|
|
35
40
|
const newTag = `${component.tagPrefix}${newVersion}`;
|
|
36
|
-
|
|
41
|
+
tags.push(newTag);
|
|
37
42
|
console.info(" Generating changelogs...");
|
|
38
43
|
for (const changelogPath of component.changelogPaths) {
|
|
39
44
|
generateChangelog(config, changelogPath, newTag, dryRun, { includePaths: component.paths });
|
|
40
45
|
}
|
|
41
46
|
console.info(` Component release prepared: ${newTag}`);
|
|
42
47
|
}
|
|
43
|
-
if (
|
|
48
|
+
if (tags.length > 0 && config.formatCommand !== void 0) {
|
|
44
49
|
if (dryRun) {
|
|
45
50
|
console.info(`
|
|
46
51
|
[dry-run] Would run format command: ${config.formatCommand}`);
|
|
@@ -56,9 +61,10 @@ Processing component: ${component.tagPrefix}`);
|
|
|
56
61
|
}
|
|
57
62
|
}
|
|
58
63
|
}
|
|
59
|
-
const summary =
|
|
64
|
+
const summary = tags.length > 0 ? "Monorepo release preparation complete." : "No components had release-worthy changes.";
|
|
60
65
|
console.info(`
|
|
61
66
|
${summary}`);
|
|
67
|
+
return tags;
|
|
62
68
|
}
|
|
63
69
|
export {
|
|
64
70
|
releasePrepareMono
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MonorepoReleaseConfig, ReleaseConfig, ReleaseType } from './types.ts';
|
|
2
|
+
export declare const RELEASE_TAGS_FILE = "/tmp/release-kit/.release-tags";
|
|
3
|
+
export declare function parseArgs(argv: string[]): {
|
|
4
|
+
dryRun: boolean;
|
|
5
|
+
bumpOverride: ReleaseType | undefined;
|
|
6
|
+
only: string[] | undefined;
|
|
7
|
+
};
|
|
8
|
+
export declare function runReleasePrepare(config: MonorepoReleaseConfig | ReleaseConfig): void;
|
|
9
|
+
export declare function writeReleaseTags(tags: string[], dryRun: boolean): void;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { releasePrepare } from "./releasePrepare.js";
|
|
4
|
+
import { releasePrepareMono } from "./releasePrepareMono.js";
|
|
5
|
+
const RELEASE_TAGS_FILE = "/tmp/release-kit/.release-tags";
|
|
6
|
+
const VALID_BUMP_TYPES = ["major", "minor", "patch"];
|
|
7
|
+
function isReleaseType(value) {
|
|
8
|
+
return VALID_BUMP_TYPES.includes(value);
|
|
9
|
+
}
|
|
10
|
+
function isMonorepoConfig(config) {
|
|
11
|
+
return "components" in config;
|
|
12
|
+
}
|
|
13
|
+
function showHelp() {
|
|
14
|
+
console.info(`
|
|
15
|
+
Usage: runReleasePrepare [options]
|
|
16
|
+
|
|
17
|
+
Legacy entry point for release preparation. Prefer the CLI:
|
|
18
|
+
npx @williamthorsen/release-kit prepare
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
--dry-run Run without modifying any files
|
|
22
|
+
--bump=major|minor|patch Override the bump type for all components
|
|
23
|
+
--only=name1,name2 Only process the named components (comma-separated, monorepo only)
|
|
24
|
+
--help Show this help message
|
|
25
|
+
`);
|
|
26
|
+
}
|
|
27
|
+
function parseArgs(argv) {
|
|
28
|
+
let dryRun = false;
|
|
29
|
+
let bumpOverride;
|
|
30
|
+
let only;
|
|
31
|
+
for (const arg of argv) {
|
|
32
|
+
if (arg === "--dry-run") {
|
|
33
|
+
dryRun = true;
|
|
34
|
+
} else if (arg.startsWith("--bump=")) {
|
|
35
|
+
const value = arg.slice("--bump=".length);
|
|
36
|
+
if (!isReleaseType(value)) {
|
|
37
|
+
console.error(`Error: Invalid bump type "${value}". Must be one of: ${VALID_BUMP_TYPES.join(", ")}`);
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
bumpOverride = value;
|
|
41
|
+
} else if (arg.startsWith("--only=")) {
|
|
42
|
+
const value = arg.slice("--only=".length);
|
|
43
|
+
if (!value) {
|
|
44
|
+
console.error("Error: --only requires a comma-separated list of component names");
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
only = value.split(",");
|
|
48
|
+
} else if (arg === "--help" || arg === "-h") {
|
|
49
|
+
showHelp();
|
|
50
|
+
process.exit(0);
|
|
51
|
+
} else {
|
|
52
|
+
console.error(`Error: Unknown argument: ${arg}`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { dryRun, bumpOverride, only };
|
|
57
|
+
}
|
|
58
|
+
function runReleasePrepare(config) {
|
|
59
|
+
const { dryRun, bumpOverride, only } = parseArgs(process.argv.slice(2));
|
|
60
|
+
const options = { dryRun, ...bumpOverride === void 0 ? {} : { bumpOverride } };
|
|
61
|
+
if (!isMonorepoConfig(config)) {
|
|
62
|
+
if (only !== void 0) {
|
|
63
|
+
console.error("Error: --only is only supported for monorepo configurations");
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const tags = releasePrepare(config, options);
|
|
68
|
+
writeReleaseTags(tags, dryRun);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.error("Error preparing release:", error instanceof Error ? error.message : String(error));
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let effectiveConfig = config;
|
|
76
|
+
if (only !== void 0) {
|
|
77
|
+
const knownNames = config.components.map((c) => c.dir);
|
|
78
|
+
for (const name of only) {
|
|
79
|
+
if (!knownNames.includes(name)) {
|
|
80
|
+
console.error(`Error: Unknown component "${name}". Known components: ${knownNames.join(", ")}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const filtered = config.components.filter((c) => only.includes(c.dir));
|
|
85
|
+
effectiveConfig = { ...config, components: filtered };
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const tags = releasePrepareMono(effectiveConfig, options);
|
|
89
|
+
writeReleaseTags(tags, dryRun);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error("Error preparing release:", error instanceof Error ? error.message : String(error));
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function writeReleaseTags(tags, dryRun) {
|
|
96
|
+
if (tags.length === 0) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (dryRun) {
|
|
100
|
+
console.info(` [dry-run] Would write ${RELEASE_TAGS_FILE}: ${tags.join(" ")}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
mkdirSync(dirname(RELEASE_TAGS_FILE), { recursive: true });
|
|
104
|
+
writeFileSync(RELEASE_TAGS_FILE, tags.join("\n"), "utf8");
|
|
105
|
+
console.info(` Wrote ${RELEASE_TAGS_FILE}: ${tags.join(" ")}`);
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
RELEASE_TAGS_FILE,
|
|
109
|
+
parseArgs,
|
|
110
|
+
runReleasePrepare,
|
|
111
|
+
writeReleaseTags
|
|
112
|
+
};
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
export type ReleaseType = 'major' | 'minor' | 'patch';
|
|
2
2
|
export interface WorkTypeConfig {
|
|
3
|
-
type: string;
|
|
4
3
|
header: string;
|
|
5
|
-
bump: ReleaseType;
|
|
6
4
|
aliases?: string[];
|
|
7
5
|
}
|
|
6
|
+
export interface VersionPatterns {
|
|
7
|
+
major: string[];
|
|
8
|
+
minor: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface ReleaseKitConfig {
|
|
11
|
+
components?: ComponentOverride[];
|
|
12
|
+
versionPatterns?: VersionPatterns;
|
|
13
|
+
workTypes?: Record<string, WorkTypeConfig>;
|
|
14
|
+
formatCommand?: string;
|
|
15
|
+
cliffConfigPath?: string;
|
|
16
|
+
workspaceAliases?: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
export interface ComponentOverride {
|
|
19
|
+
dir: string;
|
|
20
|
+
tagPrefix?: string;
|
|
21
|
+
shouldExclude?: boolean;
|
|
22
|
+
}
|
|
8
23
|
export interface Commit {
|
|
9
24
|
message: string;
|
|
10
25
|
hash: string;
|
|
@@ -18,6 +33,7 @@ export interface ParsedCommit {
|
|
|
18
33
|
breaking: boolean;
|
|
19
34
|
}
|
|
20
35
|
export interface ComponentConfig {
|
|
36
|
+
dir: string;
|
|
21
37
|
tagPrefix: string;
|
|
22
38
|
packageFiles: string[];
|
|
23
39
|
changelogPaths: string[];
|
|
@@ -25,7 +41,8 @@ export interface ComponentConfig {
|
|
|
25
41
|
}
|
|
26
42
|
export interface MonorepoReleaseConfig {
|
|
27
43
|
components: ComponentConfig[];
|
|
28
|
-
workTypes
|
|
44
|
+
workTypes?: Record<string, WorkTypeConfig>;
|
|
45
|
+
versionPatterns?: VersionPatterns;
|
|
29
46
|
formatCommand?: string;
|
|
30
47
|
cliffConfigPath?: string;
|
|
31
48
|
workspaceAliases?: Record<string, string>;
|
|
@@ -34,7 +51,8 @@ export interface ReleaseConfig {
|
|
|
34
51
|
tagPrefix: string;
|
|
35
52
|
packageFiles: string[];
|
|
36
53
|
changelogPaths: string[];
|
|
37
|
-
workTypes
|
|
54
|
+
workTypes?: Record<string, WorkTypeConfig>;
|
|
55
|
+
versionPatterns?: VersionPatterns;
|
|
38
56
|
formatCommand?: string;
|
|
39
57
|
cliffConfigPath?: string;
|
|
40
58
|
workspaceAliases?: Record<string, string>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
function validateConfig(raw) {
|
|
2
|
+
const errors = [];
|
|
3
|
+
if (!isRecord(raw)) {
|
|
4
|
+
return { config: {}, errors: ["Config must be an object"] };
|
|
5
|
+
}
|
|
6
|
+
const config = {};
|
|
7
|
+
const knownFields = /* @__PURE__ */ new Set([
|
|
8
|
+
"components",
|
|
9
|
+
"versionPatterns",
|
|
10
|
+
"workTypes",
|
|
11
|
+
"formatCommand",
|
|
12
|
+
"cliffConfigPath",
|
|
13
|
+
"workspaceAliases"
|
|
14
|
+
]);
|
|
15
|
+
for (const key of Object.keys(raw)) {
|
|
16
|
+
if (!knownFields.has(key)) {
|
|
17
|
+
errors.push(`Unknown field: '${key}'`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
validateComponents(raw.components, config, errors);
|
|
21
|
+
validateVersionPatterns(raw.versionPatterns, config, errors);
|
|
22
|
+
validateWorkTypes(raw.workTypes, config, errors);
|
|
23
|
+
validateStringField("formatCommand", raw.formatCommand, config, errors);
|
|
24
|
+
validateStringField("cliffConfigPath", raw.cliffConfigPath, config, errors);
|
|
25
|
+
validateWorkspaceAliases(raw.workspaceAliases, config, errors);
|
|
26
|
+
return { config, errors };
|
|
27
|
+
}
|
|
28
|
+
function isRecord(value) {
|
|
29
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
30
|
+
}
|
|
31
|
+
function isStringArray(value) {
|
|
32
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
33
|
+
}
|
|
34
|
+
function validateComponents(value, config, errors) {
|
|
35
|
+
if (value === void 0) return;
|
|
36
|
+
if (!Array.isArray(value)) {
|
|
37
|
+
errors.push("'components' must be an array");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const components = [];
|
|
41
|
+
for (const [i, entry] of value.entries()) {
|
|
42
|
+
if (!isRecord(entry)) {
|
|
43
|
+
errors.push(`components[${i}]: must be an object`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (typeof entry.dir !== "string" || entry.dir === "") {
|
|
47
|
+
errors.push(`components[${i}]: 'dir' is required`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const component = { dir: entry.dir };
|
|
51
|
+
if (entry.tagPrefix !== void 0) {
|
|
52
|
+
if (typeof entry.tagPrefix === "string") {
|
|
53
|
+
component.tagPrefix = entry.tagPrefix;
|
|
54
|
+
} else {
|
|
55
|
+
errors.push(`components[${i}]: 'tagPrefix' must be a string`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (entry.shouldExclude !== void 0) {
|
|
59
|
+
if (typeof entry.shouldExclude === "boolean") {
|
|
60
|
+
component.shouldExclude = entry.shouldExclude;
|
|
61
|
+
} else {
|
|
62
|
+
errors.push(`components[${i}]: 'shouldExclude' must be a boolean`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
components.push(component);
|
|
66
|
+
}
|
|
67
|
+
config.components = components;
|
|
68
|
+
}
|
|
69
|
+
function validateVersionPatterns(value, config, errors) {
|
|
70
|
+
if (value === void 0) return;
|
|
71
|
+
if (!isRecord(value)) {
|
|
72
|
+
errors.push("'versionPatterns' must be an object");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!isStringArray(value.major)) {
|
|
76
|
+
errors.push("versionPatterns.major: expected string array");
|
|
77
|
+
}
|
|
78
|
+
if (!isStringArray(value.minor)) {
|
|
79
|
+
errors.push("versionPatterns.minor: expected string array");
|
|
80
|
+
}
|
|
81
|
+
if (isStringArray(value.major) && isStringArray(value.minor)) {
|
|
82
|
+
config.versionPatterns = { major: value.major, minor: value.minor };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function validateWorkTypes(value, config, errors) {
|
|
86
|
+
if (value === void 0) return;
|
|
87
|
+
if (!isRecord(value) || Array.isArray(value)) {
|
|
88
|
+
errors.push("'workTypes' must be a record (object)");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const workTypes = {};
|
|
92
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
93
|
+
if (!isRecord(entry)) {
|
|
94
|
+
errors.push(`workTypes.${key}: must be an object`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (typeof entry.header !== "string") {
|
|
98
|
+
errors.push(`workTypes.${key}: 'header' is required and must be a string`);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const wtEntry = { header: entry.header };
|
|
102
|
+
if (entry.aliases !== void 0) {
|
|
103
|
+
if (isStringArray(entry.aliases)) {
|
|
104
|
+
wtEntry.aliases = entry.aliases;
|
|
105
|
+
} else {
|
|
106
|
+
errors.push(`workTypes.${key}: 'aliases' must be a string array`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
workTypes[key] = wtEntry;
|
|
110
|
+
}
|
|
111
|
+
config.workTypes = workTypes;
|
|
112
|
+
}
|
|
113
|
+
function validateStringField(fieldName, value, config, errors) {
|
|
114
|
+
if (value === void 0) return;
|
|
115
|
+
if (typeof value !== "string") {
|
|
116
|
+
errors.push(`'${fieldName}' must be a string`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
config[fieldName] = value;
|
|
120
|
+
}
|
|
121
|
+
function validateWorkspaceAliases(value, config, errors) {
|
|
122
|
+
if (value === void 0) return;
|
|
123
|
+
if (!isRecord(value)) {
|
|
124
|
+
errors.push("'workspaceAliases' must be a record (object)");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const aliases = {};
|
|
128
|
+
let valid = true;
|
|
129
|
+
for (const [key, v] of Object.entries(value)) {
|
|
130
|
+
if (typeof v === "string") {
|
|
131
|
+
aliases[key] = v;
|
|
132
|
+
} else {
|
|
133
|
+
errors.push(`workspaceAliases.${key}: value must be a string`);
|
|
134
|
+
valid = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (valid) {
|
|
138
|
+
config.workspaceAliases = aliases;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export {
|
|
142
|
+
validateConfig
|
|
143
|
+
};
|