@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.
Files changed (47) hide show
  1. package/CHANGELOG.md +43 -1
  2. package/README.md +202 -219
  3. package/dist/esm/.cache +1 -1
  4. package/dist/esm/bin/release-kit.d.ts +2 -0
  5. package/dist/esm/bin/release-kit.js +73 -0
  6. package/dist/esm/component.d.ts +2 -0
  7. package/dist/esm/component.js +15 -0
  8. package/dist/esm/defaults.d.ts +3 -2
  9. package/dist/esm/defaults.js +17 -12
  10. package/dist/esm/determineBumpType.d.ts +2 -2
  11. package/dist/esm/determineBumpType.js +11 -13
  12. package/dist/esm/discoverWorkspaces.d.ts +1 -0
  13. package/dist/esm/discoverWorkspaces.js +45 -0
  14. package/dist/esm/getCommitsSinceTarget.js +2 -1
  15. package/dist/esm/index.d.ts +5 -2
  16. package/dist/esm/index.js +11 -2
  17. package/dist/esm/init/checks.d.ts +9 -0
  18. package/dist/esm/init/checks.js +56 -0
  19. package/dist/esm/init/detectRepoType.d.ts +2 -0
  20. package/dist/esm/init/detectRepoType.js +19 -0
  21. package/dist/esm/init/initCommand.d.ts +5 -0
  22. package/dist/esm/init/initCommand.js +65 -0
  23. package/dist/esm/init/parseJsonRecord.d.ts +1 -0
  24. package/dist/esm/init/parseJsonRecord.js +15 -0
  25. package/dist/esm/init/prompt.d.ts +5 -0
  26. package/dist/esm/init/prompt.js +30 -0
  27. package/dist/esm/init/scaffold.d.ts +9 -0
  28. package/dist/esm/init/scaffold.js +65 -0
  29. package/dist/esm/init/templates.d.ts +3 -0
  30. package/dist/esm/init/templates.js +105 -0
  31. package/dist/esm/loadConfig.d.ts +5 -0
  32. package/dist/esm/loadConfig.js +91 -0
  33. package/dist/esm/parseCommitMessage.d.ts +1 -1
  34. package/dist/esm/parseCommitMessage.js +4 -4
  35. package/dist/esm/prepareCommand.d.ts +1 -0
  36. package/dist/esm/prepareCommand.js +77 -0
  37. package/dist/esm/releasePrepare.d.ts +1 -1
  38. package/dist/esm/releasePrepare.js +7 -3
  39. package/dist/esm/releasePrepareMono.d.ts +1 -1
  40. package/dist/esm/releasePrepareMono.js +16 -10
  41. package/dist/esm/runReleasePrepare.d.ts +9 -0
  42. package/dist/esm/runReleasePrepare.js +112 -0
  43. package/dist/esm/types.d.ts +22 -4
  44. package/dist/esm/validateConfig.d.ts +5 -0
  45. package/dist/esm/validateConfig.js +143 -0
  46. package/dist/tsconfig.generate-typings.tsbuildinfo +1 -1
  47. package/package.json +12 -4
@@ -0,0 +1,2 @@
1
+ import type { ComponentConfig } from './types.ts';
2
+ export declare function component(workspacePath: string, tagPrefix?: string): ComponentConfig;
@@ -0,0 +1,15 @@
1
+ import { basename } from "node:path";
2
+ function component(workspacePath, tagPrefix) {
3
+ const dir = basename(workspacePath);
4
+ const prefix = tagPrefix ?? `${dir}-v`;
5
+ return {
6
+ dir,
7
+ tagPrefix: prefix,
8
+ packageFiles: [`${workspacePath}/package.json`],
9
+ changelogPaths: [workspacePath],
10
+ paths: [`${workspacePath}/**`]
11
+ };
12
+ }
13
+ export {
14
+ component
15
+ };
@@ -1,2 +1,3 @@
1
- import type { WorkTypeConfig } from './types.ts';
2
- export declare const DEFAULT_WORK_TYPES: readonly WorkTypeConfig[];
1
+ import type { VersionPatterns, WorkTypeConfig } from './types.ts';
2
+ export declare const DEFAULT_WORK_TYPES: Record<string, WorkTypeConfig>;
3
+ export declare const DEFAULT_VERSION_PATTERNS: VersionPatterns;
@@ -1,15 +1,20 @@
1
- const DEFAULT_WORK_TYPES = [
2
- { type: "fix", header: "Bug fixes", bump: "patch", aliases: ["bugfix"] },
3
- { type: "feat", header: "Features", bump: "minor", aliases: ["feature"] },
4
- { type: "internal", header: "Internal", bump: "patch" },
5
- { type: "refactor", header: "Refactoring", bump: "patch" },
6
- { type: "tests", header: "Tests", bump: "patch", aliases: ["test"] },
7
- { type: "tooling", header: "Tooling", bump: "patch" },
8
- { type: "ci", header: "CI", bump: "patch" },
9
- { type: "deps", header: "Dependencies", bump: "patch", aliases: ["dep"] },
10
- { type: "docs", header: "Documentation", bump: "patch", aliases: ["doc"] },
11
- { type: "fmt", header: "Formatting", bump: "patch" }
12
- ];
1
+ const DEFAULT_WORK_TYPES = {
2
+ fix: { header: "Bug fixes", aliases: ["bugfix"] },
3
+ feat: { header: "Features", aliases: ["feature"] },
4
+ internal: { header: "Internal" },
5
+ refactor: { header: "Refactoring" },
6
+ tests: { header: "Tests", aliases: ["test"] },
7
+ tooling: { header: "Tooling" },
8
+ ci: { header: "CI" },
9
+ deps: { header: "Dependencies", aliases: ["dep"] },
10
+ docs: { header: "Documentation", aliases: ["doc"] },
11
+ fmt: { header: "Formatting" }
12
+ };
13
+ const DEFAULT_VERSION_PATTERNS = {
14
+ major: ["!"],
15
+ minor: ["feat", "feature"]
16
+ };
13
17
  export {
18
+ DEFAULT_VERSION_PATTERNS,
14
19
  DEFAULT_WORK_TYPES
15
20
  };
@@ -1,2 +1,2 @@
1
- import type { ParsedCommit, ReleaseType, WorkTypeConfig } from './types.ts';
2
- export declare function determineBumpType(commits: readonly ParsedCommit[], workTypes: readonly WorkTypeConfig[]): ReleaseType | undefined;
1
+ import type { ParsedCommit, ReleaseType, VersionPatterns, WorkTypeConfig } from './types.ts';
2
+ export declare function determineBumpType(commits: readonly ParsedCommit[], workTypes: Record<string, WorkTypeConfig>, versionPatterns: VersionPatterns): ReleaseType | undefined;
@@ -3,24 +3,25 @@ const RELEASE_PRIORITY = {
3
3
  minor: 2,
4
4
  patch: 1
5
5
  };
6
- function determineBumpType(commits, workTypes) {
7
- const typeToBump = {};
8
- for (const config of workTypes) {
9
- typeToBump[config.type] = config.bump;
10
- }
6
+ function determineBumpType(commits, workTypes, versionPatterns) {
7
+ const knownTypes = new Set(Object.keys(workTypes));
11
8
  let highestPriority = 0;
12
9
  let result;
13
10
  for (const commit of commits) {
14
- if (commit.breaking) {
11
+ if (commit.breaking && versionPatterns.major.includes("!")) {
15
12
  return "major";
16
13
  }
17
14
  const commitType = commit.type;
18
- if (!isKeyOf(commitType, typeToBump)) {
15
+ if (!knownTypes.has(commitType)) {
19
16
  continue;
20
17
  }
21
- const bump = typeToBump[commitType];
22
- if (bump === void 0) {
23
- continue;
18
+ let bump;
19
+ if (versionPatterns.major.includes(commitType)) {
20
+ bump = "major";
21
+ } else if (versionPatterns.minor.includes(commitType)) {
22
+ bump = "minor";
23
+ } else {
24
+ bump = "patch";
24
25
  }
25
26
  const priority = RELEASE_PRIORITY[bump];
26
27
  if (priority > highestPriority) {
@@ -30,9 +31,6 @@ function determineBumpType(commits, workTypes) {
30
31
  }
31
32
  return result;
32
33
  }
33
- function isKeyOf(key, obj) {
34
- return Object.hasOwn(obj, key);
35
- }
36
34
  export {
37
35
  determineBumpType
38
36
  };
@@ -0,0 +1 @@
1
+ export declare function discoverWorkspaces(): Promise<string[] | undefined>;
@@ -0,0 +1,45 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { glob } from "glob";
4
+ import { load } from "js-yaml";
5
+ async function discoverWorkspaces() {
6
+ const workspaceFile = "pnpm-workspace.yaml";
7
+ if (!existsSync(workspaceFile)) {
8
+ return void 0;
9
+ }
10
+ let content;
11
+ try {
12
+ content = readFileSync(workspaceFile, "utf8");
13
+ } catch (error) {
14
+ console.warn(`Warning: Failed to read ${workspaceFile}: ${error instanceof Error ? error.message : String(error)}`);
15
+ return void 0;
16
+ }
17
+ const parsed = load(content);
18
+ if (!isRecord(parsed)) {
19
+ return void 0;
20
+ }
21
+ const packagesField = parsed.packages;
22
+ if (!Array.isArray(packagesField)) {
23
+ return void 0;
24
+ }
25
+ const patterns = packagesField.filter((p) => typeof p === "string");
26
+ if (patterns.length === 0) {
27
+ return void 0;
28
+ }
29
+ const directories = [];
30
+ for (const pattern of patterns) {
31
+ const matches = await glob(pattern, { posix: true });
32
+ for (const match of matches) {
33
+ if (existsSync(join(match, "package.json"))) {
34
+ directories.push(match);
35
+ }
36
+ }
37
+ }
38
+ return directories.length > 0 ? [...directories].sort() : void 0;
39
+ }
40
+ function isRecord(value) {
41
+ return typeof value === "object" && value !== null && !Array.isArray(value);
42
+ }
43
+ export {
44
+ discoverWorkspaces
45
+ };
@@ -20,6 +20,7 @@ function findLatestTag(tagPrefix) {
20
20
  throw new Error(`Failed to run 'git describe': ${errorMessage(error)}`);
21
21
  }
22
22
  }
23
+ const RELEASE_COMMIT_PREFIX = "release:";
23
24
  function parseLogOutput(logOutput) {
24
25
  const commits = [];
25
26
  for (const line of logOutput.split("\n")) {
@@ -28,7 +29,7 @@ function parseLogOutput(logOutput) {
28
29
  continue;
29
30
  }
30
31
  const [message, hash] = trimmedLine.split(FIELD_SEPARATOR);
31
- if (message !== void 0 && hash !== void 0) {
32
+ if (message !== void 0 && hash !== void 0 && !message.startsWith(RELEASE_COMMIT_PREFIX)) {
32
33
  commits.push({ message, hash });
33
34
  }
34
35
  }
@@ -1,12 +1,15 @@
1
1
  export type { GenerateChangelogOptions } from './generateChangelogs.ts';
2
2
  export type { ReleasePrepareOptions } from './releasePrepare.ts';
3
- export type { Commit, ComponentConfig, MonorepoReleaseConfig, ParsedCommit, ReleaseConfig, ReleaseType, WorkTypeConfig, } from './types.ts';
4
- export { DEFAULT_WORK_TYPES } from './defaults.ts';
3
+ export type { Commit, ComponentConfig, ComponentOverride, MonorepoReleaseConfig, ParsedCommit, ReleaseConfig, ReleaseKitConfig, ReleaseType, VersionPatterns, WorkTypeConfig, } from './types.ts';
4
+ export { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from './defaults.ts';
5
5
  export { bumpAllVersions } from './bumpAllVersions.ts';
6
6
  export { bumpVersion } from './bumpVersion.ts';
7
+ export { component } from './component.ts';
7
8
  export { determineBumpType } from './determineBumpType.ts';
9
+ export { discoverWorkspaces } from './discoverWorkspaces.ts';
8
10
  export { generateChangelog, generateChangelogs } from './generateChangelogs.ts';
9
11
  export { getCommitsSinceTarget } from './getCommitsSinceTarget.ts';
10
12
  export { parseCommitMessage } from './parseCommitMessage.ts';
11
13
  export { releasePrepare } from './releasePrepare.ts';
12
14
  export { releasePrepareMono } from './releasePrepareMono.ts';
15
+ export { RELEASE_TAGS_FILE, runReleasePrepare, writeReleaseTags } from './runReleasePrepare.ts';
package/dist/esm/index.js CHANGED
@@ -1,21 +1,30 @@
1
- import { DEFAULT_WORK_TYPES } from "./defaults.js";
1
+ import { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
2
2
  import { bumpAllVersions } from "./bumpAllVersions.js";
3
3
  import { bumpVersion } from "./bumpVersion.js";
4
+ import { component } from "./component.js";
4
5
  import { determineBumpType } from "./determineBumpType.js";
6
+ import { discoverWorkspaces } from "./discoverWorkspaces.js";
5
7
  import { generateChangelog, generateChangelogs } from "./generateChangelogs.js";
6
8
  import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
7
9
  import { parseCommitMessage } from "./parseCommitMessage.js";
8
10
  import { releasePrepare } from "./releasePrepare.js";
9
11
  import { releasePrepareMono } from "./releasePrepareMono.js";
12
+ import { RELEASE_TAGS_FILE, runReleasePrepare, writeReleaseTags } from "./runReleasePrepare.js";
10
13
  export {
14
+ DEFAULT_VERSION_PATTERNS,
11
15
  DEFAULT_WORK_TYPES,
16
+ RELEASE_TAGS_FILE,
12
17
  bumpAllVersions,
13
18
  bumpVersion,
19
+ component,
14
20
  determineBumpType,
21
+ discoverWorkspaces,
15
22
  generateChangelog,
16
23
  generateChangelogs,
17
24
  getCommitsSinceTarget,
18
25
  parseCommitMessage,
19
26
  releasePrepare,
20
- releasePrepareMono
27
+ releasePrepareMono,
28
+ runReleasePrepare,
29
+ writeReleaseTags
21
30
  };
@@ -0,0 +1,9 @@
1
+ export interface CheckResult {
2
+ ok: boolean;
3
+ message?: string;
4
+ }
5
+ export declare function isGitRepo(): CheckResult;
6
+ export declare function hasPackageJson(): CheckResult;
7
+ export declare function usesPnpm(): CheckResult;
8
+ export declare function hasCliffToml(): CheckResult;
9
+ export declare function notAlreadyInitialized(): CheckResult;
@@ -0,0 +1,56 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { parseJsonRecord } from "./parseJsonRecord.js";
4
+ function isGitRepo() {
5
+ try {
6
+ execSync("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
7
+ return { ok: true };
8
+ } catch {
9
+ return { ok: false, message: "Not inside a git repository. Run `git init` first." };
10
+ }
11
+ }
12
+ function hasPackageJson() {
13
+ if (existsSync("package.json")) {
14
+ return { ok: true };
15
+ }
16
+ return { ok: false, message: "No package.json found. Run `npm init` or `pnpm init` first." };
17
+ }
18
+ function usesPnpm() {
19
+ if (existsSync("pnpm-lock.yaml")) {
20
+ return { ok: true };
21
+ }
22
+ try {
23
+ const raw = readFileSync("package.json", "utf8");
24
+ const pkg = parseJsonRecord(raw);
25
+ if (pkg !== void 0 && typeof pkg.packageManager === "string" && pkg.packageManager.startsWith("pnpm")) {
26
+ return { ok: true };
27
+ }
28
+ } catch {
29
+ }
30
+ return {
31
+ ok: false,
32
+ message: "This project does not appear to use pnpm. A pnpm-lock.yaml or packageManager field is required."
33
+ };
34
+ }
35
+ function hasCliffToml() {
36
+ if (existsSync("cliff.toml")) {
37
+ return { ok: true };
38
+ }
39
+ return { ok: false, message: "No cliff.toml found. This file is required for changelog generation." };
40
+ }
41
+ function notAlreadyInitialized() {
42
+ if (!existsSync(".config/release-kit.config.ts") && !existsSync(".github/scripts/release.config.ts")) {
43
+ return { ok: true };
44
+ }
45
+ return {
46
+ ok: false,
47
+ message: "release-kit appears to be already initialized."
48
+ };
49
+ }
50
+ export {
51
+ hasCliffToml,
52
+ hasPackageJson,
53
+ isGitRepo,
54
+ notAlreadyInitialized,
55
+ usesPnpm
56
+ };
@@ -0,0 +1,2 @@
1
+ export type RepoType = 'monorepo' | 'single-package';
2
+ export declare function detectRepoType(): RepoType;
@@ -0,0 +1,19 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { parseJsonRecord } from "./parseJsonRecord.js";
3
+ function detectRepoType() {
4
+ if (existsSync("pnpm-workspace.yaml")) {
5
+ return "monorepo";
6
+ }
7
+ try {
8
+ const raw = readFileSync("package.json", "utf8");
9
+ const pkg = parseJsonRecord(raw);
10
+ if (pkg !== void 0 && Array.isArray(pkg.workspaces)) {
11
+ return "monorepo";
12
+ }
13
+ } catch {
14
+ }
15
+ return "single-package";
16
+ }
17
+ export {
18
+ detectRepoType
19
+ };
@@ -0,0 +1,5 @@
1
+ interface InitOptions {
2
+ dryRun: boolean;
3
+ }
4
+ export declare function initCommand({ dryRun }: InitOptions): Promise<number>;
5
+ export {};
@@ -0,0 +1,65 @@
1
+ import { hasCliffToml, hasPackageJson, isGitRepo, notAlreadyInitialized, usesPnpm } from "./checks.js";
2
+ import { detectRepoType } from "./detectRepoType.js";
3
+ import { confirm, printError, printStep, printSuccess } from "./prompt.js";
4
+ import { copyCliffTemplate, scaffoldFiles } from "./scaffold.js";
5
+ function runRequiredCheck(label, result) {
6
+ if (result.ok) {
7
+ printSuccess(label);
8
+ return true;
9
+ }
10
+ printError(result.message ?? `${label} failed`);
11
+ return false;
12
+ }
13
+ async function checkEligibility(dryRun) {
14
+ printStep("Checking eligibility");
15
+ if (!runRequiredCheck("Git repository detected", isGitRepo())) return { status: "fail", overwrite: false };
16
+ if (!runRequiredCheck("package.json found", hasPackageJson())) return { status: "fail", overwrite: false };
17
+ if (!runRequiredCheck("pnpm detected", usesPnpm())) return { status: "fail", overwrite: false };
18
+ const cliffCheck = hasCliffToml();
19
+ if (cliffCheck.ok) {
20
+ printSuccess("cliff.toml found");
21
+ } else {
22
+ console.info("");
23
+ const shouldCreate = await confirm("No cliff.toml found. Create one from the bundled template?");
24
+ if (shouldCreate) {
25
+ copyCliffTemplate(dryRun);
26
+ } else {
27
+ printError("cliff.toml is required for changelog generation. Aborting.");
28
+ return { status: "fail", overwrite: false };
29
+ }
30
+ }
31
+ const initCheck = notAlreadyInitialized();
32
+ if (!initCheck.ok) {
33
+ console.info("");
34
+ const shouldOverwrite = await confirm("release-kit appears to be already initialized. Overwrite existing files?");
35
+ if (!shouldOverwrite) {
36
+ console.info("Aborting.");
37
+ return { status: "abort", overwrite: false };
38
+ }
39
+ return { status: "pass", overwrite: true };
40
+ }
41
+ return { status: "pass", overwrite: false };
42
+ }
43
+ async function initCommand({ dryRun }) {
44
+ if (dryRun) {
45
+ console.info("[dry-run mode]");
46
+ }
47
+ const eligibility = await checkEligibility(dryRun);
48
+ if (eligibility.status === "fail") return 1;
49
+ if (eligibility.status === "abort") return 0;
50
+ printStep("Detecting repo type");
51
+ const repoType = detectRepoType();
52
+ printSuccess(`Detected: ${repoType}`);
53
+ printStep("Scaffolding files");
54
+ scaffoldFiles({ repoType, dryRun, overwrite: eligibility.overwrite });
55
+ printStep("Next steps");
56
+ console.info(`
57
+ 1. (Optional) Customize .config/release-kit.config.ts to exclude components, override version patterns, add custom work types, etc.
58
+ 2. Test by running: npx @williamthorsen/release-kit prepare --dry-run
59
+ 3. Commit the generated workflow file (and config file if created).
60
+ `);
61
+ return 0;
62
+ }
63
+ export {
64
+ initCommand
65
+ };
@@ -0,0 +1 @@
1
+ export declare function parseJsonRecord(raw: string): Record<string, unknown> | undefined;
@@ -0,0 +1,15 @@
1
+ function isRecord(value) {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value);
3
+ }
4
+ function parseJsonRecord(raw) {
5
+ let parsed;
6
+ try {
7
+ parsed = JSON.parse(raw);
8
+ } catch {
9
+ return void 0;
10
+ }
11
+ return isRecord(parsed) ? parsed : void 0;
12
+ }
13
+ export {
14
+ parseJsonRecord
15
+ };
@@ -0,0 +1,5 @@
1
+ export declare function confirm(question: string): Promise<boolean>;
2
+ export declare function printStep(message: string): void;
3
+ export declare function printSuccess(message: string): void;
4
+ export declare function printSkip(message: string): void;
5
+ export declare function printError(message: string): void;
@@ -0,0 +1,30 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ async function confirm(question) {
3
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
4
+ try {
5
+ const answer = await rl.question(`${question} (y/n) `);
6
+ return answer.trim().toLowerCase().startsWith("y");
7
+ } finally {
8
+ rl.close();
9
+ }
10
+ }
11
+ function printStep(message) {
12
+ console.info(`
13
+ > ${message}`);
14
+ }
15
+ function printSuccess(message) {
16
+ console.info(` [ok] ${message}`);
17
+ }
18
+ function printSkip(message) {
19
+ console.info(` [skip] ${message}`);
20
+ }
21
+ function printError(message) {
22
+ console.error(` [error] ${message}`);
23
+ }
24
+ export {
25
+ confirm,
26
+ printError,
27
+ printSkip,
28
+ printStep,
29
+ printSuccess
30
+ };
@@ -0,0 +1,9 @@
1
+ import type { RepoType } from './detectRepoType.ts';
2
+ interface ScaffoldOptions {
3
+ repoType: RepoType;
4
+ dryRun: boolean;
5
+ overwrite: boolean;
6
+ }
7
+ export declare function copyCliffTemplate(dryRun: boolean): void;
8
+ export declare function scaffoldFiles({ repoType, dryRun, overwrite }: ScaffoldOptions): void;
9
+ export {};
@@ -0,0 +1,65 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { dirname, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { printError, printSkip, printSuccess } from "./prompt.js";
5
+ import { releaseConfigScript, releaseWorkflow } from "./templates.js";
6
+ function tryWriteFile(filePath, content) {
7
+ try {
8
+ writeFileSync(filePath, content, "utf8");
9
+ return true;
10
+ } catch (error) {
11
+ const message = error instanceof Error ? error.message : String(error);
12
+ printError(`Failed to write ${filePath}: ${message}`);
13
+ return false;
14
+ }
15
+ }
16
+ function writeIfAbsent(filePath, content, dryRun, overwrite) {
17
+ if (existsSync(filePath) && !overwrite) {
18
+ printSkip(`${filePath} (already exists)`);
19
+ return;
20
+ }
21
+ if (dryRun) {
22
+ printSuccess(`[dry-run] Would create ${filePath}`);
23
+ return;
24
+ }
25
+ try {
26
+ mkdirSync(dirname(filePath), { recursive: true });
27
+ } catch (error) {
28
+ const message = error instanceof Error ? error.message : String(error);
29
+ printError(`Failed to create directory for ${filePath}: ${message}`);
30
+ return;
31
+ }
32
+ if (tryWriteFile(filePath, content)) {
33
+ printSuccess(`Created ${filePath}`);
34
+ }
35
+ }
36
+ function copyCliffTemplate(dryRun) {
37
+ const thisDir = dirname(fileURLToPath(import.meta.url));
38
+ const templatePath = resolve(thisDir, "..", "..", "..", "cliff.toml.template");
39
+ if (!existsSync(templatePath)) {
40
+ printError(`Could not find cliff.toml.template at ${templatePath}`);
41
+ return;
42
+ }
43
+ let content;
44
+ try {
45
+ content = readFileSync(templatePath, "utf8");
46
+ } catch (error) {
47
+ const message = error instanceof Error ? error.message : String(error);
48
+ printError(`Failed to read cliff.toml.template: ${message}`);
49
+ return;
50
+ }
51
+ writeIfAbsent("cliff.toml", content, dryRun, false);
52
+ }
53
+ function scaffoldFiles({ repoType, dryRun, overwrite }) {
54
+ const files = [
55
+ { path: ".config/release-kit.config.ts", content: releaseConfigScript(repoType) },
56
+ { path: ".github/workflows/release.yaml", content: releaseWorkflow(repoType) }
57
+ ];
58
+ for (const file of files) {
59
+ writeIfAbsent(file.path, file.content, dryRun, overwrite);
60
+ }
61
+ }
62
+ export {
63
+ copyCliffTemplate,
64
+ scaffoldFiles
65
+ };
@@ -0,0 +1,3 @@
1
+ import type { RepoType } from './detectRepoType.ts';
2
+ export declare function releaseConfigScript(repoType: RepoType): string;
3
+ export declare function releaseWorkflow(repoType: RepoType): string;
@@ -0,0 +1,105 @@
1
+ function releaseConfigScript(repoType) {
2
+ if (repoType === "monorepo") {
3
+ return `import type { ReleaseKitConfig } from '@williamthorsen/release-kit';
4
+
5
+ const config: ReleaseKitConfig = {
6
+ // Uncomment to exclude components from release processing:
7
+ // components: [
8
+ // { dir: 'my-package', shouldExclude: true },
9
+ // ],
10
+ // Uncomment to override the default version patterns:
11
+ // versionPatterns: { major: ['!'], minor: ['feat', 'feature'] },
12
+ // Uncomment to add custom work types (merged with defaults):
13
+ // workTypes: { perf: { header: 'Performance' } },
14
+ // TODO: Uncomment and adjust if you have a format command
15
+ // formatCommand: 'pnpm run fmt',
16
+ };
17
+
18
+ export default config;
19
+ `;
20
+ }
21
+ return `import type { ReleaseKitConfig } from '@williamthorsen/release-kit';
22
+
23
+ const config: ReleaseKitConfig = {
24
+ // Uncomment to override the default version patterns:
25
+ // versionPatterns: { major: ['!'], minor: ['feat', 'feature'] },
26
+ // Uncomment to add custom work types (merged with defaults):
27
+ // workTypes: { perf: { header: 'Performance' } },
28
+ // TODO: Uncomment and adjust if you have a format command
29
+ // formatCommand: 'pnpm run fmt',
30
+ };
31
+
32
+ export default config;
33
+ `;
34
+ }
35
+ function releaseWorkflow(repoType) {
36
+ if (repoType === "monorepo") {
37
+ return `# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
38
+ name: Release
39
+
40
+ on:
41
+ workflow_dispatch:
42
+ inputs:
43
+ only:
44
+ description: 'Components to release (comma-separated, leave empty for all)'
45
+ required: false
46
+ type: string
47
+ bump:
48
+ description: 'Override version bump type (leave empty to auto-detect from commits)'
49
+ required: false
50
+ type: choice
51
+ options:
52
+ - ''
53
+ - patch
54
+ - minor
55
+ - major
56
+
57
+ permissions:
58
+ contents: write
59
+ packages: read
60
+
61
+ jobs:
62
+ release:
63
+ uses: williamthorsen/.github/.github/workflows/release-pnpm.yaml@v2
64
+ with:
65
+ # TODO: Set the Node.js and pnpm versions for your project
66
+ node-version: '24'
67
+ pnpm-version: '10.32.1'
68
+ only: \${{ inputs.only }}
69
+ bump: \${{ inputs.bump }}
70
+ `;
71
+ }
72
+ return `# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
73
+ name: Release
74
+
75
+ on:
76
+ workflow_dispatch:
77
+ inputs:
78
+ bump:
79
+ description: 'Override version bump type (leave empty to auto-detect from commits)'
80
+ required: false
81
+ type: choice
82
+ options:
83
+ - ''
84
+ - patch
85
+ - minor
86
+ - major
87
+
88
+ permissions:
89
+ contents: write
90
+ packages: read
91
+
92
+ jobs:
93
+ release:
94
+ uses: williamthorsen/.github/.github/workflows/release-pnpm.yaml@v2
95
+ with:
96
+ # TODO: Set the Node.js and pnpm versions for your project
97
+ node-version: '24'
98
+ pnpm-version: '10.32.1'
99
+ bump: \${{ inputs.bump }}
100
+ `;
101
+ }
102
+ export {
103
+ releaseConfigScript,
104
+ releaseWorkflow
105
+ };
@@ -0,0 +1,5 @@
1
+ import type { MonorepoReleaseConfig, ReleaseConfig, ReleaseKitConfig } from './types.ts';
2
+ export declare const CONFIG_FILE_PATH = ".config/release-kit.config.ts";
3
+ export declare function loadConfig(): Promise<unknown>;
4
+ export declare function mergeMonorepoConfig(discoveredPaths: string[], userConfig: ReleaseKitConfig | undefined): MonorepoReleaseConfig;
5
+ export declare function mergeSinglePackageConfig(userConfig: ReleaseKitConfig | undefined): ReleaseConfig;