@williamthorsen/release-kit 4.7.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,20 @@
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
+
5
19
  ## [release-kit-v4.7.0] - 2026-04-16
6
20
 
7
21
  ### Features
@@ -227,6 +241,10 @@ All notable changes to this project will be documented in this file.
227
241
 
228
242
  ### Features
229
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
+
230
248
  - Slim down release workflow by removing unnecessary pnpm install (#21)
231
249
 
232
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.
@@ -249,12 +267,4 @@ All notable changes to this project will be documented in this file.
249
267
 
250
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.
251
269
 
252
- ## [release-kit-v1.0.1] - 2026-03-14
253
-
254
- ### Features
255
-
256
- - Migrate release-kit from toolbelt (#18)
257
-
258
- 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.
259
-
260
270
  <!-- generated by git-cliff -->
package/dist/esm/.cache CHANGED
@@ -1 +1 @@
1
- d3e4e5bacab96fdfcab4ae55b07b4830280060f0bc39bbada29fa8f08e2c9381
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();
@@ -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 }}
@@ -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.7.0";
1
+ export declare const VERSION = "4.8.0";
@@ -1,4 +1,4 @@
1
- const VERSION = "4.7.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.7.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",