@williamthorsen/release-kit 4.6.0 → 4.8.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 CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [release-kit-v4.8.0] - 2026-04-17
6
+
7
+ ### Bug fixes
8
+
9
+ - Replace broad catch with `existsSync` guard in `detectRepoType` (#229)
10
+
11
+ Fixes silent swallowing of unexpected filesystem errors in `detectRepoType`. Previously, errors like `EACCES` (permission denied) or `EMFILE` (too many open files) when reading `package.json` were caught and discarded, causing the function to silently return `'single-package'` instead of surfacing the problem.
12
+
13
+ ### Features
14
+
15
+ - Add `push` command for safe tag pushing (#243)
16
+
17
+ Adds a `release-kit push` command that safely pushes the release commit and each tag individually, ensuring GitHub Actions fires a separate workflow run per tag. The command performs a `1 + N` push sequence: one branch push followed by one `git push --no-follow-tags origin <tag>` per resolved tag. Supports `--dry-run` (preview without pushing), `--only` (filter tags by package name), and `--tags-only` (skip the branch push).
18
+
19
+ ## [release-kit-v4.7.0] - 2026-04-16
20
+
21
+ ### Features
22
+
23
+ - Support ## as synthetic ticket prefix in changelogs
24
+
25
+ Commits prefixed with `##` are now included in changelogs without requiring a ticket ID. This supports ad-hoc changes made during interactive sessions where creating a ticket and PR adds undesired overhead.
26
+
5
27
  ## [release-kit-v4.6.0] - 2026-04-15
6
28
 
7
29
  ### Features
@@ -219,6 +241,10 @@ All notable changes to this project will be documented in this file.
219
241
 
220
242
  ### Features
221
243
 
244
+ - Migrate release-kit from toolbelt (#18)
245
+
246
+ Migrates the complete `@williamthorsen/release-kit` package (v1.0.1) from `williamthorsen/toolbelt` into `packages/release-kit/`, adds shebang preservation to the shared esbuild plugin for CLI binaries, and sets up dogfooding infrastructure so this monorepo uses release-kit for its own releases.
247
+
222
248
  - Slim down release workflow by removing unnecessary pnpm install (#21)
223
249
 
224
250
  Make release-kit self-contained by invoking git-cliff via `npx --yes` instead of requiring it on PATH, and by appending modified file paths to the format command so lightweight formatters like `npx prettier --write` work without a full `pnpm install`. Update init templates, README, and consuming repo config/workflow to reference workflow v3.
@@ -241,12 +267,4 @@ All notable changes to this project will be documented in this file.
241
267
 
242
268
  Addresses five code quality issues and a test coverage gap identified during the release-kit migration (#5). Extracts a duplicated `isRecord` type guard into a shared module, eliminates a double-read in `bumpAllVersions`, improves error handling in `usesPnpm` by replacing a silent catch with a structured error boundary, removes an unreachable `'feature'` pattern from version defaults, and adds an integration test for scaffold template path resolution.
243
269
 
244
- ## [release-kit-v1.0.1] - 2026-03-14
245
-
246
- ### Features
247
-
248
- - Migrate release-kit from toolbelt (#18)
249
-
250
- Migrates the complete `@williamthorsen/release-kit` package (v1.0.1) from `williamthorsen/toolbelt` into `packages/release-kit/`, adds shebang preservation to the shared esbuild plugin for CLI binaries, and sets up dogfooding infrastructure so this monorepo uses release-kit for its own releases.
251
-
252
270
  <!-- generated by git-cliff -->
@@ -5,9 +5,11 @@
5
5
  # Examples:
6
6
  # #19 rdy|feat: Add utility functions for common check patterns (#26)
7
7
  # AFG-42 panel|fix: Fix button layout (#73)
8
+ # ## feat: Quick utility added during interactive session
8
9
  #
9
- # Only ticketed commits (leading #NN or PROJ-NN) are included.
10
+ # Only ticketed commits (leading #NN, PROJ-NN, or ##) are included.
10
11
  # Unticketed maintenance commits (deps upgrades, tooling tweaks) are skipped.
12
+ # Use ## as a synthetic ticket prefix for ad-hoc commits that belong in the changelog.
11
13
 
12
14
  [changelog]
13
15
  header = """
@@ -55,27 +57,27 @@ filter_unconventional = false
55
57
  split_commits = false
56
58
  commit_preprocessors = []
57
59
  # Require a ticket-ID prefix for inclusion.
58
- # Supported prefixes: "#123 ", "#123-1 ", "#123.1 ", "TOOL-123 "
60
+ # Supported prefixes: "#123 ", "#123-1 ", "#123.1 ", "TOOL-123 ", "## "
59
61
  commit_parsers = [
60
62
  { message = "^release:", skip = true },
61
63
  { message = "^Merge", skip = true },
62
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?bugfix(!)?:", group = "Bug fixes" },
63
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?ci(!)?:", group = "CI" },
64
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?deprecate(!)?:", group = "Deprecated" },
65
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?deps?(!)?:", group = "Dependencies" },
66
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?docs?(!)?:", group = "Documentation" },
67
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?feat(!)?:", group = "Features" },
68
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?feature(!)?:", group = "Features" },
69
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?fix(!)?:", group = "Bug fixes" },
70
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?fmt(!)?:", group = "Formatting" },
71
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?internal(!)?:", group = "Internal" },
72
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?perf(!)?:", group = "Performance" },
73
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?performance(!)?:", group = "Performance" },
74
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?refactor(!)?:", group = "Refactoring" },
75
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?sec(!)?:", group = "Security" },
76
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?security(!)?:", group = "Security" },
77
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?tests?(!)?:", group = "Tests" },
78
- { message = "^(\\#\\d+([.\\-]\\d+)?|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?tooling(!)?:", group = "Tooling" },
64
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?bugfix(!)?:", group = "Bug fixes" },
65
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?ci(!)?:", group = "CI" },
66
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?deprecate(!)?:", group = "Deprecated" },
67
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?deps?(!)?:", group = "Dependencies" },
68
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?docs?(!)?:", group = "Documentation" },
69
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?feat(!)?:", group = "Features" },
70
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?feature(!)?:", group = "Features" },
71
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?fix(!)?:", group = "Bug fixes" },
72
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?fmt(!)?:", group = "Formatting" },
73
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?internal(!)?:", group = "Internal" },
74
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?perf(!)?:", group = "Performance" },
75
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?performance(!)?:", group = "Performance" },
76
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?refactor(!)?:", group = "Refactoring" },
77
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?sec(!)?:", group = "Security" },
78
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?security(!)?:", group = "Security" },
79
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?tests?(!)?:", group = "Tests" },
80
+ { message = "^(\\#\\d+([.\\-]\\d+)?|\\#\\#|[A-Z]+-\\d+)\\s+([\\w-]+\\|)?tooling(!)?:", group = "Tooling" },
79
81
  # Skip unticketed commits (maintenance, deps, initial scaffolding, etc.)
80
82
  { message = ".*", skip = true },
81
83
  ]
package/dist/esm/.cache CHANGED
@@ -1 +1 @@
1
- 020cc360fffab3d5b52fc5e388bc432d37399fdfb25c30cff31a23d37f1c2c09
1
+ d6a6d71f34f00ba31c3dca3b32657832693ff9c12de118fafa96fc3554835ecc
@@ -5,6 +5,7 @@ import { githubReleaseCommand } from "../githubReleaseCommand.js";
5
5
  import { initCommand } from "../init/initCommand.js";
6
6
  import { prepareCommand } from "../prepareCommand.js";
7
7
  import { publishCommand } from "../publishCommand.js";
8
+ import { pushCommand } from "../pushCommand.js";
8
9
  import { generateCommand } from "../sync-labels/generateCommand.js";
9
10
  import { syncLabelsInitCommand } from "../sync-labels/initCommand.js";
10
11
  import { syncLabelsCommand } from "../sync-labels/syncCommand.js";
@@ -18,6 +19,7 @@ Commands:
18
19
  prepare Run release preparation (auto-discovers workspaces)
19
20
  commit Stage changes and create the release commit
20
21
  tag Create annotated git tags from the tags file
22
+ push Push release commit and tags (one push per tag)
21
23
  publish Publish packages with release tags on HEAD
22
24
  github-release Create GitHub Releases from changelog.json for tags on HEAD
23
25
  init Initialize release-kit in the current repository
@@ -128,6 +130,20 @@ Options:
128
130
  --help, -h Show this help message
129
131
  `);
130
132
  }
133
+ function showPushHelp() {
134
+ console.info(`
135
+ Usage: release-kit push [options]
136
+
137
+ Push the release commit and each tag individually, ensuring GitHub Actions
138
+ fires a separate workflow run per tag.
139
+
140
+ Options:
141
+ --dry-run Preview without pushing
142
+ --only=name1,name2 Only push tags for the named packages (comma-separated, monorepo only)
143
+ --tags-only Skip the branch push (push tags only)
144
+ --help, -h Show this help message
145
+ `);
146
+ }
131
147
  function showGithubReleaseHelp() {
132
148
  console.info(`
133
149
  Usage: release-kit github-release [options]
@@ -194,6 +210,14 @@ if (command === "tag") {
194
210
  tagCommand(flags);
195
211
  process.exit(0);
196
212
  }
213
+ if (command === "push") {
214
+ if (flags.some((f) => f === "--help" || f === "-h")) {
215
+ showPushHelp();
216
+ process.exit(0);
217
+ }
218
+ await pushCommand(flags);
219
+ process.exit(0);
220
+ }
197
221
  if (command === "github-release") {
198
222
  if (flags.some((f) => f === "--help" || f === "-h")) {
199
223
  showGithubReleaseHelp();
@@ -20,10 +20,16 @@ function createGithubRelease(options) {
20
20
  console.warn(`Warning: no changelog entry for version ${version}; skipping GitHub Release creation`);
21
21
  return false;
22
22
  }
23
+ if (!entry.sections.some(matchesAudience("all"))) {
24
+ return false;
25
+ }
23
26
  const body = renderReleaseNotesSingle(entry, {
24
27
  filter: matchesAudience("all"),
25
28
  includeHeading: false
26
29
  });
30
+ if (body.trim() === "") {
31
+ return false;
32
+ }
27
33
  const args = ["release", "create", tag, "--title", tag, "--notes", body];
28
34
  if (dryRun) {
29
35
  console.info(`[dry-run] Would run: gh ${args.join(" ")}`);
@@ -27,6 +27,8 @@ export { injectSection } from './injectSection.ts';
27
27
  export { COMMIT_PREPROCESSOR_PATTERNS, parseCommitMessage } from './parseCommitMessage.ts';
28
28
  export { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE, writeReleaseTags } from './prepareCommand.ts';
29
29
  export { publishPackage } from './publish.ts';
30
+ export type { PushReleaseOptions } from './pushRelease.ts';
31
+ export { pushRelease } from './pushRelease.ts';
30
32
  export { releasePrepare } from './releasePrepare.ts';
31
33
  export { releasePrepareMono } from './releasePrepareMono.ts';
32
34
  export type { RenderOptions } from './renderReleaseNotes.ts';
package/dist/esm/index.js CHANGED
@@ -23,6 +23,7 @@ import { injectSection } from "./injectSection.js";
23
23
  import { COMMIT_PREPROCESSOR_PATTERNS, parseCommitMessage } from "./parseCommitMessage.js";
24
24
  import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE, writeReleaseTags } from "./prepareCommand.js";
25
25
  import { publishPackage } from "./publish.js";
26
+ import { pushRelease } from "./pushRelease.js";
26
27
  import { releasePrepare } from "./releasePrepare.js";
27
28
  import { releasePrepareMono } from "./releasePrepareMono.js";
28
29
  import { matchesAudience, renderReleaseNotesMulti, renderReleaseNotesSingle } from "./renderReleaseNotes.js";
@@ -60,6 +61,7 @@ export {
60
61
  matchesAudience,
61
62
  parseCommitMessage,
62
63
  publishPackage,
64
+ pushRelease,
63
65
  releasePrepare,
64
66
  releasePrepareMono,
65
67
  renderReleaseNotesMulti,
@@ -4,13 +4,12 @@ function detectRepoType() {
4
4
  if (existsSync("pnpm-workspace.yaml")) {
5
5
  return "monorepo";
6
6
  }
7
- try {
7
+ if (existsSync("package.json")) {
8
8
  const raw = readFileSync("package.json", "utf8");
9
9
  const pkg = parseJsonRecord(raw);
10
10
  if (pkg !== void 0 && Array.isArray(pkg.workspaces)) {
11
11
  return "monorepo";
12
12
  }
13
- } catch {
14
13
  }
15
14
  return "single-package";
16
15
  }
@@ -36,7 +36,7 @@ export default config;
36
36
  `;
37
37
  }
38
38
  function publishWorkflow(repoType) {
39
- const tagPattern = repoType === "monorepo" ? "'*-v[0-9]*'" : "'v[0-9]*'";
39
+ const tagPattern = repoType === "monorepo" ? "'*-v[0-9]*.[0-9]*.[0-9]*'" : "'v[0-9]*.[0-9]*.[0-9]*'";
40
40
  return `# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
41
41
  name: Publish
42
42
 
@@ -51,9 +51,9 @@ permissions:
51
51
 
52
52
  jobs:
53
53
  publish:
54
- uses: williamthorsen/node-monorepo-tools/.github/workflows/publish.reusable.yaml@publish-workflow-v1
54
+ uses: williamthorsen/node-monorepo-tools/.github/workflows/publish.reusable.yaml@workflow/publish-v1
55
55
  with:
56
- provenance: false # Set to true for public repos to generate npm provenance attestations
56
+ provenance: true
57
57
  `;
58
58
  }
59
59
  function releaseWorkflow(repoType) {
@@ -78,7 +78,7 @@ on:
78
78
  - minor
79
79
  - major
80
80
  force:
81
- description: 'Force a version bump even when there are no release-worthy changes'
81
+ description: 'Force a release even when there are no commits since the last tag (requires --bump)'
82
82
  required: false
83
83
  type: boolean
84
84
  default: false
@@ -89,7 +89,7 @@ permissions:
89
89
 
90
90
  jobs:
91
91
  release:
92
- uses: williamthorsen/node-monorepo-tools/.github/workflows/release.reusable.yaml@release-workflow-v1
92
+ uses: williamthorsen/node-monorepo-tools/.github/workflows/release.reusable.yaml@workflow/release-v1
93
93
  with:
94
94
  only: \${{ inputs.only }}
95
95
  bump: \${{ inputs.bump }}
@@ -112,7 +112,7 @@ on:
112
112
  - minor
113
113
  - major
114
114
  force:
115
- description: 'Force a version bump even when there are no release-worthy changes'
115
+ description: 'Force a release even when there are no commits since the last tag (requires --bump)'
116
116
  required: false
117
117
  type: boolean
118
118
  default: false
@@ -123,7 +123,7 @@ permissions:
123
123
 
124
124
  jobs:
125
125
  release:
126
- uses: williamthorsen/node-monorepo-tools/.github/workflows/release.reusable.yaml@release-workflow-v1
126
+ uses: williamthorsen/node-monorepo-tools/.github/workflows/release.reusable.yaml@workflow/release-v1
127
127
  with:
128
128
  bump: \${{ inputs.bump }}
129
129
  force: \${{ inputs.force }}
@@ -1,4 +1,4 @@
1
- const COMMIT_PREPROCESSOR_PATTERNS = [/^#\d+([.-]\d+)?\s+/, /^[A-Z]+-\d+\s+/];
1
+ const COMMIT_PREPROCESSOR_PATTERNS = [/^##\s+/, /^#\d+([.-]\d+)?\s+/, /^[A-Z]+-\d+\s+/];
2
2
  function parseCommitMessage(message, hash, workTypes, scopeAliases) {
3
3
  const stripped = stripTicketPrefix(message);
4
4
  const match = stripped.match(/^(?:([^|]+)\|)?(\w+)(?:\(([^)]+)\))?(!)?:\s*(.*)$/);
@@ -25,7 +25,7 @@ Usage: npx @williamthorsen/release-kit prepare [options]
25
25
  Options:
26
26
  --dry-run Run without modifying any files
27
27
  --bump=major|minor|patch Override the bump type for all components
28
- --force Bypass the "no commits since last tag" check (monorepo only, requires --bump)
28
+ --force Force a release even when there are no commits since the last tag (requires --bump)
29
29
  --no-git-checks, -n Skip the clean-working-tree check
30
30
  --only=name1,name2 Only process the named components (comma-separated, monorepo only)
31
31
  --help Show this help message
@@ -0,0 +1 @@
1
+ export declare function pushCommand(argv: string[]): Promise<void>;
@@ -0,0 +1,46 @@
1
+ import { parseArgs, translateParseError } from "@williamthorsen/node-monorepo-core";
2
+ import { pushRelease } from "./pushRelease.js";
3
+ import { resolveCommandTags } from "./resolveCommandTags.js";
4
+ const pushFlagSchema = {
5
+ dryRun: { long: "--dry-run", type: "boolean" },
6
+ only: { long: "--only", type: "string" },
7
+ tagsOnly: { long: "--tags-only", type: "boolean" }
8
+ };
9
+ async function pushCommand(argv) {
10
+ let parsed;
11
+ try {
12
+ parsed = parseArgs(argv, pushFlagSchema);
13
+ } catch (error) {
14
+ console.error(`Error: ${translateParseError(error)}`);
15
+ process.exit(1);
16
+ }
17
+ const { dryRun, tagsOnly } = parsed.flags;
18
+ const only = parsed.flags.only?.split(",");
19
+ const resolvedTags = await resolveCommandTags(only);
20
+ if (resolvedTags.length === 0) {
21
+ return;
22
+ }
23
+ const prefix = dryRun ? "[dry-run] Would push" : "Pushing";
24
+ if (!tagsOnly) {
25
+ console.info(`${prefix} branch and ${resolvedTags.length} tag(s):`);
26
+ } else {
27
+ console.info(`${prefix} ${resolvedTags.length} tag(s):`);
28
+ }
29
+ for (const { tag } of resolvedTags) {
30
+ console.info(` ${tag}`);
31
+ }
32
+ try {
33
+ const steps = pushRelease(resolvedTags, { dryRun, tagsOnly });
34
+ if (dryRun) {
35
+ for (const step of steps) {
36
+ console.info(`[dry-run] ${step.command.join(" ")}`);
37
+ }
38
+ }
39
+ } catch (error) {
40
+ console.error(error instanceof Error ? error.message : String(error));
41
+ process.exit(1);
42
+ }
43
+ }
44
+ export {
45
+ pushCommand
46
+ };
@@ -0,0 +1,11 @@
1
+ import type { ResolvedTag } from './resolveReleaseTags.ts';
2
+ export interface PushReleaseOptions {
3
+ dryRun?: boolean;
4
+ tagsOnly?: boolean;
5
+ }
6
+ export interface PushStep {
7
+ type: 'branch' | 'tag';
8
+ ref: string;
9
+ command: readonly [string, ...string[]];
10
+ }
11
+ export declare function pushRelease(resolvedTags: ResolvedTag[], options?: PushReleaseOptions): PushStep[];
@@ -0,0 +1,26 @@
1
+ import { execFileSync } from "node:child_process";
2
+ function pushRelease(resolvedTags, options = {}) {
3
+ const { dryRun = false, tagsOnly = false } = options;
4
+ const steps = [];
5
+ if (!tagsOnly) {
6
+ const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
7
+ encoding: "utf8",
8
+ stdio: ["pipe", "pipe", "pipe"]
9
+ }).trim();
10
+ const command = ["git", "push", "origin", branch];
11
+ steps.push({ type: "branch", ref: branch, command });
12
+ }
13
+ for (const { tag } of resolvedTags) {
14
+ const command = ["git", "push", "--no-follow-tags", "origin", tag];
15
+ steps.push({ type: "tag", ref: tag, command });
16
+ }
17
+ if (!dryRun) {
18
+ for (const step of steps) {
19
+ execFileSync(step.command[0], step.command.slice(1), { stdio: "inherit" });
20
+ }
21
+ }
22
+ return steps;
23
+ }
24
+ export {
25
+ pushRelease
26
+ };
@@ -12,7 +12,7 @@ permissions:
12
12
 
13
13
  jobs:
14
14
  sync:
15
- uses: williamthorsen/node-monorepo-tools/.github/workflows/sync-labels.reusable.yaml@sync-labels-workflow-v1
15
+ uses: williamthorsen/node-monorepo-tools/.github/workflows/sync-labels.reusable.yaml@workflow/sync-labels-v1
16
16
  `;
17
17
  }
18
18
  function buildScopeLabels(workspacePaths) {
@@ -1 +1 @@
1
- export declare const VERSION = "4.6.0";
1
+ export declare const VERSION = "4.8.0";
@@ -1,4 +1,4 @@
1
- const VERSION = "4.6.0";
1
+ const VERSION = "4.8.0";
2
2
  export {
3
3
  VERSION
4
4
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/release-kit",
3
- "version": "4.6.0",
3
+ "version": "4.8.0",
4
4
  "description": "Version-bumping and changelog-generation toolkit for release workflows",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/williamthorsen/node-monorepo-tools/tree/main/packages/release-kit#readme",
@@ -35,7 +35,7 @@
35
35
  "jiti": "2.6.1",
36
36
  "js-yaml": "4.1.1",
37
37
  "json-stringify-pretty-compact": "4.0.0",
38
- "@williamthorsen/node-monorepo-core": "0.2.6"
38
+ "@williamthorsen/node-monorepo-core": "0.2.7"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/js-yaml": "4.0.9",
@@ -1,4 +0,0 @@
1
- export declare function printStep(message: string): void;
2
- export declare function printSuccess(message: string): void;
3
- export declare function printSkip(message: string): void;
4
- export declare function printError(message: string): void;
@@ -1,19 +0,0 @@
1
- function printStep(message) {
2
- console.info(`
3
- > ${message}`);
4
- }
5
- function printSuccess(message) {
6
- console.info(` \u2705 ${message}`);
7
- }
8
- function printSkip(message) {
9
- console.info(` \u26A0\uFE0F ${message}`);
10
- }
11
- function printError(message) {
12
- console.error(` \u274C ${message}`);
13
- }
14
- export {
15
- printError,
16
- printSkip,
17
- printStep,
18
- printSuccess
19
- };
@@ -1,10 +0,0 @@
1
- import type { MonorepoReleaseConfig, ReleaseConfig, ReleaseType } from './types.ts';
2
- export declare const RELEASE_TAGS_FILE = "tmp/.release-tags";
3
- export declare function parseArgs(argv: string[]): {
4
- dryRun: boolean;
5
- force: boolean;
6
- bumpOverride: ReleaseType | undefined;
7
- only: string[] | undefined;
8
- };
9
- export declare function runReleasePrepare(config: MonorepoReleaseConfig | ReleaseConfig): void;
10
- export declare function writeReleaseTags(tags: string[], dryRun: boolean): void;
@@ -1,125 +0,0 @@
1
- import { mkdirSync, writeFileSync } from "node:fs";
2
- import { dirname } from "node:path";
3
- import { dim } from "./format.js";
4
- import { releasePrepare } from "./releasePrepare.js";
5
- import { releasePrepareMono } from "./releasePrepareMono.js";
6
- const RELEASE_TAGS_FILE = "tmp/.release-tags";
7
- const VALID_BUMP_TYPES = ["major", "minor", "patch"];
8
- function isReleaseType(value) {
9
- return VALID_BUMP_TYPES.includes(value);
10
- }
11
- function isMonorepoConfig(config) {
12
- return "components" in config;
13
- }
14
- function showHelp() {
15
- console.info(`
16
- Usage: runReleasePrepare [options]
17
-
18
- Legacy entry point for release preparation. Prefer the CLI:
19
- npx @williamthorsen/release-kit prepare
20
-
21
- Options:
22
- --dry-run Run without modifying any files
23
- --bump=major|minor|patch Override the bump type for all components
24
- --force Bypass the "no commits since last tag" check (monorepo only, requires --bump)
25
- --only=name1,name2 Only process the named components (comma-separated, monorepo only)
26
- --help Show this help message
27
- `);
28
- }
29
- function parseArgs(argv) {
30
- let dryRun = false;
31
- let force = false;
32
- let bumpOverride;
33
- let only;
34
- for (const arg of argv) {
35
- if (arg === "--dry-run") {
36
- dryRun = true;
37
- } else if (arg === "--force") {
38
- force = true;
39
- } else if (arg.startsWith("--bump=")) {
40
- const value = arg.slice("--bump=".length);
41
- if (!isReleaseType(value)) {
42
- console.error(`Error: Invalid bump type "${value}". Must be one of: ${VALID_BUMP_TYPES.join(", ")}`);
43
- process.exit(1);
44
- }
45
- bumpOverride = value;
46
- } else if (arg.startsWith("--only=")) {
47
- const value = arg.slice("--only=".length);
48
- if (!value) {
49
- console.error("Error: --only requires a comma-separated list of component names");
50
- process.exit(1);
51
- }
52
- only = value.split(",");
53
- } else if (arg === "--help" || arg === "-h") {
54
- showHelp();
55
- process.exit(0);
56
- } else {
57
- console.error(`Error: Unknown argument: ${arg}`);
58
- process.exit(1);
59
- }
60
- }
61
- if (force && bumpOverride === void 0) {
62
- console.error("Error: --force requires --bump to specify the version bump type");
63
- process.exit(1);
64
- }
65
- return { dryRun, force, bumpOverride, only };
66
- }
67
- function runReleasePrepare(config) {
68
- const { dryRun, force, bumpOverride, only } = parseArgs(process.argv.slice(2));
69
- const options = {
70
- dryRun,
71
- force,
72
- ...bumpOverride === void 0 ? {} : { bumpOverride }
73
- };
74
- if (!isMonorepoConfig(config)) {
75
- if (only !== void 0) {
76
- console.error("Error: --only is only supported for monorepo configurations");
77
- process.exit(1);
78
- }
79
- try {
80
- const tags = releasePrepare(config, options);
81
- writeReleaseTags(tags, dryRun);
82
- } catch (error) {
83
- console.error("Error preparing release:", error instanceof Error ? error.message : String(error));
84
- process.exit(1);
85
- }
86
- return;
87
- }
88
- let effectiveConfig = config;
89
- if (only !== void 0) {
90
- const knownNames = config.components.map((c) => c.dir);
91
- for (const name of only) {
92
- if (!knownNames.includes(name)) {
93
- console.error(`Error: Unknown component "${name}". Known components: ${knownNames.join(", ")}`);
94
- process.exit(1);
95
- }
96
- }
97
- const filtered = config.components.filter((c) => only.includes(c.dir));
98
- effectiveConfig = { ...config, components: filtered };
99
- }
100
- try {
101
- const tags = releasePrepareMono(effectiveConfig, options);
102
- writeReleaseTags(tags, dryRun);
103
- } catch (error) {
104
- console.error("Error preparing release:", error instanceof Error ? error.message : String(error));
105
- process.exit(1);
106
- }
107
- }
108
- function writeReleaseTags(tags, dryRun) {
109
- if (tags.length === 0) {
110
- return;
111
- }
112
- if (dryRun) {
113
- console.info(dim(` [dry-run] Would write ${RELEASE_TAGS_FILE}: ${tags.join(" ")}`));
114
- return;
115
- }
116
- mkdirSync(dirname(RELEASE_TAGS_FILE), { recursive: true });
117
- writeFileSync(RELEASE_TAGS_FILE, tags.join("\n"), "utf8");
118
- console.info(dim(` Wrote ${RELEASE_TAGS_FILE}: ${tags.join(" ")}`));
119
- }
120
- export {
121
- RELEASE_TAGS_FILE,
122
- parseArgs,
123
- runReleasePrepare,
124
- writeReleaseTags
125
- };
@@ -1,6 +0,0 @@
1
- interface WriteResult {
2
- action: 'created' | 'skipped' | 'dry-run' | 'failed';
3
- filePath: string;
4
- }
5
- export declare function writeIfAbsent(filePath: string, content: string, dryRun: boolean, overwrite: boolean): WriteResult;
6
- export {};
@@ -1,25 +0,0 @@
1
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
2
- import { dirname } from "node:path";
3
- function writeIfAbsent(filePath, content, dryRun, overwrite) {
4
- if (existsSync(filePath) && !overwrite) {
5
- console.info(` Skipped ${filePath} (already exists)`);
6
- return { action: "skipped", filePath };
7
- }
8
- if (dryRun) {
9
- console.info(` [dry-run] Would create ${filePath}`);
10
- return { action: "dry-run", filePath };
11
- }
12
- try {
13
- mkdirSync(dirname(filePath), { recursive: true });
14
- writeFileSync(filePath, content, "utf8");
15
- } catch (error) {
16
- const message = error instanceof Error ? error.message : String(error);
17
- console.error(` Failed to write ${filePath}: ${message}`);
18
- return { action: "failed", filePath };
19
- }
20
- console.info(` Created ${filePath}`);
21
- return { action: "created", filePath };
22
- }
23
- export {
24
- writeIfAbsent
25
- };