@williamthorsen/release-kit 0.2.3 → 1.0.1
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 +45 -1
- package/README.md +204 -215
- package/dist/esm/.cache +1 -1
- package/dist/esm/bin/release-kit.js +24 -1
- 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 -3
- package/dist/esm/index.js +10 -3
- package/dist/esm/init/checks.js +2 -2
- package/dist/esm/init/initCommand.js +3 -6
- package/dist/esm/init/scaffold.js +2 -51
- package/dist/esm/init/templates.d.ts +0 -1
- package/dist/esm/init/templates.js +26 -42
- package/dist/esm/loadConfig.d.ts +5 -0
- package/dist/esm/loadConfig.js +93 -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.js +5 -2
- package/dist/esm/releasePrepareMono.js +11 -6
- package/dist/esm/runReleasePrepare.d.ts +8 -2
- package/dist/esm/runReleasePrepare.js +13 -18
- 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 +9 -4
|
@@ -1,19 +1,22 @@
|
|
|
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}`);
|
|
@@ -1,33 +1,38 @@
|
|
|
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;
|
|
10
|
+
const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
|
|
11
|
+
const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
|
|
9
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})...`);
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
import type { MonorepoReleaseConfig, ReleaseConfig } from './types.ts';
|
|
2
|
-
export declare const RELEASE_TAGS_FILE = "
|
|
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
|
+
};
|
|
3
8
|
export declare function runReleasePrepare(config: MonorepoReleaseConfig | ReleaseConfig): void;
|
|
9
|
+
export declare function writeReleaseTags(tags: string[], dryRun: boolean): void;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
2
3
|
import { releasePrepare } from "./releasePrepare.js";
|
|
3
4
|
import { releasePrepareMono } from "./releasePrepareMono.js";
|
|
4
|
-
const RELEASE_TAGS_FILE = "
|
|
5
|
+
const RELEASE_TAGS_FILE = "/tmp/release-kit/.release-tags";
|
|
5
6
|
const VALID_BUMP_TYPES = ["major", "minor", "patch"];
|
|
6
7
|
function isReleaseType(value) {
|
|
7
8
|
return VALID_BUMP_TYPES.includes(value);
|
|
@@ -13,18 +14,14 @@ function showHelp() {
|
|
|
13
14
|
console.info(`
|
|
14
15
|
Usage: runReleasePrepare [options]
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
Legacy entry point for release preparation. Prefer the CLI:
|
|
18
|
+
npx @williamthorsen/release-kit prepare
|
|
17
19
|
|
|
18
20
|
Options:
|
|
19
21
|
--dry-run Run without modifying any files
|
|
20
22
|
--bump=major|minor|patch Override the bump type for all components
|
|
21
23
|
--only=name1,name2 Only process the named components (comma-separated, monorepo only)
|
|
22
24
|
--help Show this help message
|
|
23
|
-
|
|
24
|
-
Examples:
|
|
25
|
-
tsx .github/scripts/release-prepare.ts --dry-run
|
|
26
|
-
tsx .github/scripts/release-prepare.ts --bump=minor
|
|
27
|
-
tsx .github/scripts/release-prepare.ts --only=arrays,strings --dry-run
|
|
28
25
|
`);
|
|
29
26
|
}
|
|
30
27
|
function parseArgs(argv) {
|
|
@@ -77,19 +74,14 @@ function runReleasePrepare(config) {
|
|
|
77
74
|
}
|
|
78
75
|
let effectiveConfig = config;
|
|
79
76
|
if (only !== void 0) {
|
|
80
|
-
const
|
|
81
|
-
const filtered = config.components.filter((c) => {
|
|
82
|
-
const name = c.tagPrefix.replace(/-v$/, "");
|
|
83
|
-
return only.includes(name);
|
|
84
|
-
});
|
|
77
|
+
const knownNames = config.components.map((c) => c.dir);
|
|
85
78
|
for (const name of only) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const knownNames = knownPrefixes.map((p) => p.replace(/-v$/, "")).join(", ");
|
|
89
|
-
console.error(`Error: Unknown component "${name}". Known components: ${knownNames}`);
|
|
79
|
+
if (!knownNames.includes(name)) {
|
|
80
|
+
console.error(`Error: Unknown component "${name}". Known components: ${knownNames.join(", ")}`);
|
|
90
81
|
process.exit(1);
|
|
91
82
|
}
|
|
92
83
|
}
|
|
84
|
+
const filtered = config.components.filter((c) => only.includes(c.dir));
|
|
93
85
|
effectiveConfig = { ...config, components: filtered };
|
|
94
86
|
}
|
|
95
87
|
try {
|
|
@@ -108,10 +100,13 @@ function writeReleaseTags(tags, dryRun) {
|
|
|
108
100
|
console.info(` [dry-run] Would write ${RELEASE_TAGS_FILE}: ${tags.join(" ")}`);
|
|
109
101
|
return;
|
|
110
102
|
}
|
|
103
|
+
mkdirSync(dirname(RELEASE_TAGS_FILE), { recursive: true });
|
|
111
104
|
writeFileSync(RELEASE_TAGS_FILE, tags.join("\n"), "utf8");
|
|
112
105
|
console.info(` Wrote ${RELEASE_TAGS_FILE}: ${tags.join(" ")}`);
|
|
113
106
|
}
|
|
114
107
|
export {
|
|
115
108
|
RELEASE_TAGS_FILE,
|
|
116
|
-
|
|
109
|
+
parseArgs,
|
|
110
|
+
runReleasePrepare,
|
|
111
|
+
writeReleaseTags
|
|
117
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
|
+
};
|