@williamthorsen/release-kit 4.0.0 → 4.4.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,42 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [release-kit-v4.4.0] - 2026-04-04
6
+
7
+ ### Documentation
8
+
9
+ - #135 release-kit|docs: Refine README to match preflight documentation standard (#138)
10
+
11
+ Restructures the release-kit README to match the documentation standard established by the preflight README (#114). Reorders sections to follow the cross-package convention, converts CLI flag listings from code blocks to tables, adds representative `prepare --dry-run` output to the quick start, and condenses ~90 lines of inline workflow YAML into a summary with an inputs table and trigger examples. Fixes several accuracy gaps found by verifying documentation against source.
12
+
13
+ ### Features
14
+
15
+ - #142 feat: Add --version flag to nmr and release-kit (#143)
16
+
17
+ Adds `--version` / `-V` support to the `nmr` and `release-kit` CLIs, matching the existing `preflight` behavior. Moves the build-time version generation script to the shared `config/` directory so all three packages use a single `generateVersion.ts`.
18
+
19
+ - #150 feat: Detect and report missing build output in bin wrappers (#152)
20
+
21
+ Adds try/catch with `ERR_MODULE_NOT_FOUND` detection to all six bin wrappers across `nmr`, `preflight`, and `release-kit`. Previously, five of the six wrappers used bare `import()` calls that produced cryptic unhandled rejections when `dist/` was missing, and `preflight`'s existing try/catch gave no actionable guidance.
22
+
23
+ ### Refactoring
24
+
25
+ - #88 release-kit|refactor: Extract deleteFileIfExists helper (#136)
26
+
27
+ Replaces the duplicate `deleteTagsFile` and `deleteSummaryFile` functions in `createTags.ts` with a single parameterized `deleteFileIfExists(path)` utility. The new helper lives in its own module and is exported from the package barrel for reuse.
28
+
29
+ - #145 refactor: Extract shared CLI argument-parsing utility into core (#151)
30
+
31
+ Add a schema-driven `parseArgs` function to `@williamthorsen/node-monorepo-core` that handles boolean flags, string flags (both `--flag=value` and `--flag value`), short aliases, positional collection, the `--` delimiter, and unknown-flag errors. Migrate all CLI argument-parsing sites in preflight (3 sites) and release-kit (5 sites) to use it. A companion `translateParseError` helper normalizes internal error messages for consistent user-facing output.
32
+
33
+ ## [release-kit-v4.0.0] - 2026-04-02
34
+
35
+ ### Features
36
+
37
+ - #125 feat: Rename reusable workflows to .reusable.yaml convention (#129)
38
+
39
+ Renames all three reusable GitHub Actions workflow files from the inconsistent `-workflow.yaml`/bare `.yaml` convention to a uniform `.reusable.yaml` suffix. Updates all references across caller workflows, release-kit templates, tests, preflight collection, and documentation. Scaffolds the sync-labels caller workflow and labels file for this repo. Deletes superseded legacy files.
40
+
5
41
  ## [release-kit-v3.0.0] - 2026-03-29
6
42
 
7
43
  ### Bug fixes
@@ -48,6 +84,10 @@ Prevents `releasePrepareMono` and `releasePrepare` from silently skipping compon
48
84
 
49
85
  ### Features
50
86
 
87
+ - #8 feat: Add shared writeFileWithCheck utility and overwrite reporting (#66)
88
+
89
+ Extracts three duplicated `writeIfAbsent` implementations and two duplicated terminal helper sets into shared utilities in `@williamthorsen/node-monorepo-core`, then migrates all consumers (`release-kit init`, `preflight init`, `sync-labels`) to use them. All init commands now report which files were created, overwritten, skipped, or failed — including when `--force` replaces existing files.
90
+
51
91
  - #11 release-kit|feat: Separate tag-write errors from release preparation errors (#67)
52
92
 
53
93
  When tag-file writing fails, the error message now reads "Error writing release tags:" instead of the misleading "Error preparing release:", which only appeared because both operations shared a single try/catch.
@@ -112,8 +152,18 @@ Removes the ability to customize `tagPrefix` per component, enforcing the determ
112
152
 
113
153
  Adds ANSI formatting and emoji markers to the `release-kit prepare` command output. Progress chatter is dimmed, key results (version bumps, release tags, completion status) are highlighted with bold text and emoji, and monorepo components are separated by box-drawing section headers.
114
154
 
155
+ - #59 feat: Extract nmr CLI from core package (#61)
156
+
157
+ Extracts all nmr CLI code from `packages/core` into a new `packages/nmr` package (`@williamthorsen/nmr`). Core is reduced to an empty shared-library shell ready for cross-cutting utilities. All internal references are rewired and the full build/test pipeline passes.
158
+
159
+ Scopes: core, nmr
160
+
115
161
  ### Refactoring
116
162
 
163
+ - #43 refactor: Replace dist bin targets with thin wrapper scripts (#48)
164
+
165
+ The `bin` entries in `packages/core` and `packages/release-kit` pointed directly into `dist/esm/`, causing `pnpm install` to emit "Failed to create bin" warnings in fresh worktrees where `dist/` does not yet exist. Each bin entry now points to a committed wrapper script in `bin/` that dynamically imports the real entry point. The `files` field in both packages includes `bin` so the wrappers are published.
166
+
117
167
  - #53 release-kit|refactor: Separate presentation from logic in prepare workflow (#57)
118
168
 
119
169
  Extracts all `console.info` calls from the prepare workflow's logic functions (`bumpAllVersions`, `generateChangelogs`, `releasePrepare`, `releasePrepareMono`) into a dedicated `reportPrepare` formatter. Logic functions now return structured result types (`BumpResult`, `ComponentPrepareResult`, `PrepareResult`). The legacy `runReleasePrepare` entry point is retired, with its utilities absorbed into `prepareCommand`.
package/README.md CHANGED
@@ -20,6 +20,36 @@ npx @williamthorsen/release-kit init
20
20
  npx @williamthorsen/release-kit prepare --dry-run
21
21
  ```
22
22
 
23
+ Example output from `prepare --dry-run` in a monorepo:
24
+
25
+ ```
26
+ 🔍 DRY RUN — no files will be modified
27
+
28
+ ── arrays ──────────────────────────────────────
29
+ Found 4 commits since arrays-v1.2.0
30
+ Parsed 3 typed commits
31
+ Bumping versions (minor)...
32
+ 📦 1.2.0 → 1.3.0 (minor)
33
+ [dry-run] Would bump packages/arrays/package.json
34
+ Generating changelogs...
35
+ [dry-run] Would run: npx --yes git-cliff ... --output packages/arrays/CHANGELOG.md
36
+ 🏷️ arrays-v1.3.0
37
+
38
+ ── strings ─────────────────────────────────────
39
+ Found 2 commits since strings-v0.5.1
40
+ Parsed 2 typed commits
41
+ Bumping versions (patch)...
42
+ 📦 0.5.1 → 0.5.2 (patch)
43
+ [dry-run] Would bump packages/strings/package.json
44
+ Generating changelogs...
45
+ [dry-run] Would run: npx --yes git-cliff ... --output packages/strings/CHANGELOG.md
46
+ 🏷️ strings-v0.5.2
47
+
48
+ ✅ Release preparation complete.
49
+ 🏷️ arrays-v1.3.0
50
+ 🏷️ strings-v0.5.2
51
+ ```
52
+
23
53
  That's it for most repos. The CLI auto-discovers workspaces and applies sensible defaults. The bundled `cliff.toml.template` is used automatically — no need to copy it. Customize only what you need via `.config/release-kit.config.ts`.
24
54
 
25
55
  ## How it works
@@ -30,58 +60,20 @@ That's it for most repos. The CLI auto-discovers workspaces and applies sensible
30
60
  4. **Version bump + changelog**: bumps `package.json` versions and generates changelogs via `git-cliff`.
31
61
  5. **Release tags file**: writes computed tags to `tmp/.release-tags` for the release workflow to read when tagging and pushing.
32
62
 
33
- ## CLI reference
34
-
35
- ### `release-kit prepare`
36
-
37
- Run release preparation with automatic workspace discovery.
38
-
39
- ```
40
- Usage: release-kit prepare [options]
41
-
42
- Options:
43
- --dry-run Preview changes without writing files
44
- --bump=major|minor|patch Override the bump type for all components
45
- --only=name1,name2 Only process the named components (monorepo only)
46
- --help, -h Show help
47
- ```
48
-
49
- Component names for `--only` match the package directory name (e.g., `arrays`, `release-kit`).
50
-
51
- ### `release-kit init`
52
-
53
- Initialize release-kit in the current repository. By default, scaffolds only the GitHub Actions workflow file. Use `--with-config` to also scaffold configuration files.
54
-
55
- ```
56
- Usage: release-kit init [options]
57
-
58
- Options:
59
- --with-config Also scaffold .config/release-kit.config.ts and .config/git-cliff.toml
60
- --force Overwrite existing files instead of skipping them
61
- --dry-run Preview changes without writing files
62
- --help, -h Show help
63
- ```
64
-
65
- Scaffolded files:
66
-
67
- - `.github/workflows/release.yaml` — workflow that delegates to a reusable release workflow
68
- - `.config/release-kit.config.ts` — starter config with commented-out customization examples (with `--with-config`)
69
- - `.config/git-cliff.toml` — copied from the bundled template (with `--with-config`)
70
-
71
- ### `release-kit sync-labels`
63
+ ## Commit format
72
64
 
73
- Manage GitHub label definitions via config-driven YAML files.
65
+ release-kit parses commits in these formats:
74
66
 
75
67
  ```
76
- Usage: release-kit sync-labels <command> [options]
77
-
78
- Commands:
79
- init Scaffold sync-labels config and caller workflow
80
- generate Generate .github/labels.yaml from config
81
- sync Generate labels and push to GitHub (runs generate + gh label sync)
68
+ type: description # e.g., feat: add utility
69
+ scope|type: description # e.g., arrays|feat: add compact function
70
+ type(scope): description # e.g., feat(arrays): add compact function
71
+ type!: description # breaking change (triggers major bump)
72
+ scope|type!: description # scoped breaking change
73
+ type(scope)!: description # conventional scoped breaking change
82
74
  ```
83
75
 
84
- `init` scaffolds `.config/sync-labels.config.ts` with auto-detected workspace scope labels and a `.github/workflows/sync-labels.yaml` caller workflow. `generate` reads the config and writes `.github/labels.yaml`. `sync` runs `generate` and then applies labels to the GitHub repo.
76
+ The `scope|type:` format scopes a commit to a specific component in a monorepo. Use `scopeAliases` in your config to map shorthand names to canonical scope names.
85
77
 
86
78
  ## Configuration
87
79
 
@@ -144,7 +136,7 @@ interface VersionPatterns {
144
136
  }
145
137
  ```
146
138
 
147
- Default: `{ major: ['!'], minor: ['feat', 'feature'] }`
139
+ Default: `{ major: ['!'], minor: ['feat'] }`
148
140
 
149
141
  ### Default work types
150
142
 
@@ -163,151 +155,70 @@ Default: `{ major: ['!'], minor: ['feat', 'feature'] }`
163
155
 
164
156
  Work types from your config are merged with these defaults by key — your entries override or extend, they don't replace the full set.
165
157
 
166
- ## Commit format
158
+ ## CLI reference
167
159
 
168
- release-kit parses commits in these formats:
160
+ ### Global options
169
161
 
170
- ```
171
- type: description # e.g., feat: add utility
172
- scope|type: description # e.g., arrays|feat: add compact function
173
- type(scope): description # e.g., feat(arrays): add compact function
174
- type!: description # breaking change (triggers major bump)
175
- scope|type!: description # scoped breaking change
176
- type(scope)!: description # conventional scoped breaking change
177
- ```
162
+ | Flag | Description |
163
+ | ----------------- | ------------------- |
164
+ | `--help`, `-h` | Show help message |
165
+ | `--version`, `-V` | Show version number |
178
166
 
179
- The `scope|type:` format scopes a commit to a specific component in a monorepo. Use `scopeAliases` in your config to map shorthand names to canonical scope names.
167
+ ### `release-kit prepare`
180
168
 
181
- ## Using `component()` for manual configuration
169
+ Run release preparation with automatic workspace discovery.
182
170
 
183
- If you need to build a `MonorepoReleaseConfig` manually (e.g., for the legacy script-based approach), the exported `component()` helper creates a `ComponentConfig` from a workspace-relative path:
171
+ | Flag | Description |
172
+ | ---------------------------- | ---------------------------------------------------------------- |
173
+ | `--dry-run` | Preview changes without writing files |
174
+ | `--bump=major\|minor\|patch` | Override the bump type for all components |
175
+ | `--force` | Bypass the "no commits since last tag" check (requires `--bump`) |
176
+ | `--only=name1,name2` | Only process the named components (monorepo only) |
177
+ | `--help`, `-h` | Show help |
184
178
 
185
- ```typescript
186
- import { component } from '@williamthorsen/release-kit';
179
+ Component names for `--only` match the package directory name (e.g., `arrays`, `release-kit`).
187
180
 
188
- // Accepts the full workspace-relative path
189
- component('packages/arrays');
190
- // => {
191
- // dir: 'arrays',
192
- // tagPrefix: 'arrays-v',
193
- // packageFiles: ['packages/arrays/package.json'],
194
- // changelogPaths: ['packages/arrays'],
195
- // paths: ['packages/arrays/**'],
196
- // }
197
- ```
181
+ ### `release-kit init`
198
182
 
199
- The `dir` field is derived from `path.basename()`, so `packages/arrays` and `libs/arrays` both produce `dir: 'arrays'`. The `tagPrefix` is always `${dir}-v` it cannot be customized.
183
+ Initialize release-kit in the current repository. By default, scaffolds only the GitHub Actions workflow file. Use `--with-config` to also scaffold configuration files.
200
184
 
201
- ## GitHub Actions workflow
185
+ | Flag | Description |
186
+ | --------------- | -------------------------------------------------------------------------- |
187
+ | `--with-config` | Also scaffold `.config/release-kit.config.ts` and `.config/git-cliff.toml` |
188
+ | `--force` | Overwrite existing files instead of skipping them |
189
+ | `--dry-run` | Preview changes without writing files |
190
+ | `--help`, `-h` | Show help |
202
191
 
203
- The `init` command scaffolds a workflow that delegates to a reusable release workflow. For repos that need a self-contained workflow:
204
-
205
- ### Monorepo
206
-
207
- ```yaml
208
- name: Release
209
-
210
- on:
211
- workflow_dispatch:
212
- inputs:
213
- only:
214
- description: 'Components to release (comma-separated, leave empty for all)'
215
- required: false
216
- type: string
217
- bump:
218
- description: 'Override bump type (leave empty to auto-detect)'
219
- required: false
220
- type: choice
221
- options:
222
- - ''
223
- - patch
224
- - minor
225
- - major
226
-
227
- permissions:
228
- contents: write
229
-
230
- jobs:
231
- release:
232
- runs-on: ubuntu-latest
233
- steps:
234
- - uses: actions/checkout@v4
235
- with:
236
- fetch-depth: 0
237
- token: ${{ secrets.GITHUB_TOKEN }}
238
-
239
- - uses: actions/setup-node@v4
240
- with:
241
- node-version: '24'
242
-
243
- - name: Run release preparation
244
- run: |
245
- ARGS=""
246
- if [ -n "${{ inputs.only }}" ]; then
247
- ARGS="$ARGS --only=${{ inputs.only }}"
248
- fi
249
- if [ -n "${{ inputs.bump }}" ]; then
250
- ARGS="$ARGS --bump=${{ inputs.bump }}"
251
- fi
252
- npx @williamthorsen/release-kit prepare $ARGS
253
-
254
- - name: Check for changes
255
- id: check
256
- run: |
257
- if git diff --quiet; then
258
- echo "changed=false" >> "$GITHUB_OUTPUT"
259
- echo "No release-worthy changes found."
260
- else
261
- echo "changed=true" >> "$GITHUB_OUTPUT"
262
- fi
263
-
264
- - name: Read release tags
265
- if: steps.check.outputs.changed == 'true'
266
- id: tags
267
- run: |
268
- TAGS=$(cat tmp/.release-tags | tr '\n' ' ')
269
- echo "tags=$TAGS" >> "$GITHUB_OUTPUT"
270
- echo "Releasing: $TAGS"
271
-
272
- - name: Commit, tag, and push
273
- if: steps.check.outputs.changed == 'true'
274
- run: |
275
- git config user.name "github-actions[bot]"
276
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
277
- git add -A
278
- git commit -m "release: ${{ steps.tags.outputs.tags }}"
279
- for TAG in ${{ steps.tags.outputs.tags }}; do
280
- git tag "$TAG"
281
- done
282
- git push origin main ${{ steps.tags.outputs.tags }}
283
- ```
192
+ Scaffolded files:
284
193
 
285
- ### Single-package repo
194
+ - `.github/workflows/release.yaml` — workflow that delegates to a reusable release workflow
195
+ - `.config/release-kit.config.ts` — starter config with commented-out customization examples (with `--with-config`)
196
+ - `.config/git-cliff.toml` — copied from the bundled template (with `--with-config`)
286
197
 
287
- The same workflow without the `only` input. Replace the prepare step with:
198
+ ### `release-kit sync-labels`
288
199
 
289
- ```yaml
290
- - name: Run release preparation
291
- run: |
292
- ARGS=""
293
- if [ -n "${{ inputs.bump }}" ]; then
294
- ARGS="--bump=${{ inputs.bump }}"
295
- fi
296
- npx @williamthorsen/release-kit prepare $ARGS
297
- ```
200
+ Manage GitHub label definitions via config-driven YAML files.
298
201
 
299
- And the tag step with:
202
+ | Subcommand | Description | Flags |
203
+ | ---------- | -------------------------------------------------------------- | ---------------------- |
204
+ | `init` | Scaffold config, caller workflow, and generate labels | `--dry-run`, `--force` |
205
+ | `generate` | Regenerate `.github/labels.yaml` from config | — |
206
+ | `sync` | Trigger the `sync-labels` GitHub Actions workflow via `gh` CLI | — |
300
207
 
301
- ```yaml
302
- - name: Read release tag
303
- if: steps.check.outputs.changed == 'true'
304
- id: tags
305
- run: |
306
- TAG=$(cat tmp/.release-tags)
307
- echo "tag=$TAG" >> "$GITHUB_OUTPUT"
308
- ```
208
+ `init` scaffolds `.config/sync-labels.config.ts` with auto-detected workspace scope labels and a `.github/workflows/sync-labels.yaml` caller workflow, then generates `.github/labels.yaml`. `generate` reads the config and writes `.github/labels.yaml`. `sync` triggers the workflow remotely — it requires the `gh` CLI and an existing workflow file.
209
+
210
+ ## GitHub Actions workflow
211
+
212
+ The `init` command scaffolds a release workflow at `.github/workflows/release.yaml` that delegates to a reusable release workflow. The scaffolded workflow accepts these inputs:
213
+
214
+ | Input | Type | Description |
215
+ | ------ | ------ | ------------------------------------------------------------------- |
216
+ | `only` | string | Components to release (comma-separated, leave empty for all) |
217
+ | `bump` | choice | Override bump type: `patch`, `minor`, `major` (empty = auto-detect) |
218
+
219
+ For repos that need a self-contained workflow instead of the reusable one, the scaffolded file can be expanded. The key steps are: checkout with full history (`fetch-depth: 0`), run `release-kit prepare` with optional `--only` and `--bump` flags, check for changes, read tags from `tmp/.release-tags`, then commit, tag, and push.
309
220
 
310
- ## Triggering a release
221
+ ### Triggering a release
311
222
 
312
223
  ```sh
313
224
  # All components
@@ -322,12 +233,14 @@ Or use the GitHub UI: Actions > Release > Run workflow.
322
233
 
323
234
  ## cliff.toml setup
324
235
 
325
- The package includes a bundled `cliff.toml.template` that is used automatically when no custom config is found. The resolution order is:
236
+ The package includes a bundled `cliff.toml.template` that is used automatically when no custom config is found. The resolution order:
326
237
 
327
- 1. Explicit `cliffConfigPath` in `.config/release-kit.config.ts`
328
- 2. `.config/git-cliff.toml`
329
- 3. `cliff.toml` (repo root)
330
- 4. Bundled `cliff.toml.template` (automatic fallback)
238
+ | Priority | Path | Notes |
239
+ | -------- | ----------------------------- | ----------------------------------------------- |
240
+ | 1 | `cliffConfigPath` in config | Explicit path, returned without existence check |
241
+ | 2 | `.config/git-cliff.toml` | Project-level override |
242
+ | 3 | `cliff.toml` | Repo root fallback |
243
+ | 4 | Bundled `cliff.toml.template` | Automatic fallback |
331
244
 
332
245
  The bundled template provides a generic git-cliff configuration that:
333
246
 
@@ -344,6 +257,26 @@ This package shells out to two external tools:
344
257
  - **`git`** — must be available on `PATH`. Used to find tags and retrieve commit history.
345
258
  - **`git-cliff`** — automatically downloaded and cached via `npx` on first invocation. No need to install it as a dev dependency.
346
259
 
260
+ ## Using `component()` for manual configuration
261
+
262
+ If you need to build a `MonorepoReleaseConfig` manually (e.g., for the legacy script-based approach), the exported `component()` helper creates a `ComponentConfig` from a workspace-relative path:
263
+
264
+ ```typescript
265
+ import { component } from '@williamthorsen/release-kit';
266
+
267
+ // Accepts the full workspace-relative path
268
+ component('packages/arrays');
269
+ // => {
270
+ // dir: 'arrays',
271
+ // tagPrefix: 'arrays-v',
272
+ // packageFiles: ['packages/arrays/package.json'],
273
+ // changelogPaths: ['packages/arrays'],
274
+ // paths: ['packages/arrays/**'],
275
+ // }
276
+ ```
277
+
278
+ The `dir` field is derived from `path.basename()`, so `packages/arrays` and `libs/arrays` both produce `dir: 'arrays'`. The `tagPrefix` is always `${dir}-v` — it cannot be customized.
279
+
347
280
  ## Legacy script-based approach
348
281
 
349
282
  The CLI-driven approach is recommended for new setups. The script-based approach (using `runReleasePrepare` with a manually maintained config) is still supported for backward compatibility.
@@ -1,2 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import('../dist/esm/bin/release-kit.js');
2
+ try {
3
+ await import('../dist/esm/bin/release-kit.js');
4
+ } catch (error) {
5
+ if (error.code === 'ERR_MODULE_NOT_FOUND') {
6
+ process.stderr.write('release-kit: build output not found — run `pnpm run build` first\n');
7
+ } else {
8
+ process.stderr.write(`release-kit: failed to load: ${error.message}\n`);
9
+ }
10
+ process.exit(1);
11
+ }
package/dist/esm/.cache CHANGED
@@ -1 +1 @@
1
- eda8d9c3d3574c46e1afbeccbda04ffac0675bd9d30270ab8644fc48477ace14
1
+ 89fa3a396cfa2f4f2cf5ce8827dbe171e310519ab3faa7325268a87693116335
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ import { parseArgs, translateParseError } from "@williamthorsen/node-monorepo-core";
2
3
  import { commitCommand } from "../commitCommand.js";
3
4
  import { initCommand } from "../init/initCommand.js";
4
5
  import { prepareCommand } from "../prepareCommand.js";
@@ -7,6 +8,7 @@ import { generateCommand } from "../sync-labels/generateCommand.js";
7
8
  import { syncLabelsInitCommand } from "../sync-labels/initCommand.js";
8
9
  import { syncLabelsCommand } from "../sync-labels/syncCommand.js";
9
10
  import { tagCommand } from "../tagCommand.js";
11
+ import { VERSION } from "../version.js";
10
12
  function showUsage() {
11
13
  console.info(`
12
14
  Usage: release-kit <command> [options]
@@ -140,6 +142,10 @@ Options:
140
142
  const args = process.argv.slice(2);
141
143
  const command = args[0];
142
144
  const flags = args.slice(1);
145
+ if (command === "--version" || command === "-V") {
146
+ console.info(VERSION);
147
+ process.exit(0);
148
+ }
143
149
  if (command === "--help" || command === "-h" || command === void 0) {
144
150
  showUsage();
145
151
  process.exit(0);
@@ -186,15 +192,19 @@ if (command === "init") {
186
192
  showInitHelp();
187
193
  process.exit(0);
188
194
  }
189
- const knownInitFlags = /* @__PURE__ */ new Set(["--dry-run", "--force", "--with-config", "--help", "-h"]);
190
- const unknownFlags = flags.filter((f) => !knownInitFlags.has(f));
191
- if (unknownFlags.length > 0) {
192
- console.error(`Error: Unknown option: ${unknownFlags[0]}`);
195
+ const initFlagSchema = {
196
+ dryRun: { long: "--dry-run", type: "boolean" },
197
+ force: { long: "--force", type: "boolean" },
198
+ withConfig: { long: "--with-config", type: "boolean" }
199
+ };
200
+ let parsed;
201
+ try {
202
+ parsed = parseArgs(flags, initFlagSchema);
203
+ } catch (error) {
204
+ console.error(`Error: ${translateParseError(error)}`);
193
205
  process.exit(1);
194
206
  }
195
- const dryRun = flags.includes("--dry-run");
196
- const force = flags.includes("--force");
197
- const withConfig = flags.includes("--with-config");
207
+ const { dryRun, force, withConfig } = parsed.flags;
198
208
  const exitCode = initCommand({ dryRun, force, withConfig });
199
209
  process.exit(exitCode);
200
210
  }
@@ -210,14 +220,18 @@ if (command === "sync-labels") {
210
220
  showSyncLabelsInitHelp();
211
221
  process.exit(0);
212
222
  }
213
- const knownFlags = /* @__PURE__ */ new Set(["--dry-run", "--force", "--help", "-h"]);
214
- const unknownFlags = subflags.filter((f) => !knownFlags.has(f));
215
- if (unknownFlags.length > 0) {
216
- console.error(`Error: Unknown option: ${unknownFlags[0]}`);
223
+ const syncLabelsInitFlagSchema = {
224
+ dryRun: { long: "--dry-run", type: "boolean" },
225
+ force: { long: "--force", type: "boolean" }
226
+ };
227
+ let syncParsed;
228
+ try {
229
+ syncParsed = parseArgs(subflags, syncLabelsInitFlagSchema);
230
+ } catch (error) {
231
+ console.error(`Error: ${translateParseError(error)}`);
217
232
  process.exit(1);
218
233
  }
219
- const dryRun = subflags.includes("--dry-run");
220
- const force = subflags.includes("--force");
234
+ const { dryRun, force } = syncParsed.flags;
221
235
  const exitCode = await syncLabelsInitCommand({ dryRun, force });
222
236
  process.exit(exitCode);
223
237
  }
@@ -1,14 +1,19 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { readFileSync } from "node:fs";
3
+ import { parseArgs, translateParseError } from "@williamthorsen/node-monorepo-core";
3
4
  import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./prepareCommand.js";
5
+ const commitFlagSchema = {
6
+ dryRun: { long: "--dry-run", type: "boolean" }
7
+ };
4
8
  function commitCommand(argv) {
5
- const knownFlags = /* @__PURE__ */ new Set(["--dry-run"]);
6
- const unknownFlags = argv.filter((f) => !knownFlags.has(f));
7
- if (unknownFlags.length > 0) {
8
- console.error(`Error: Unknown option: ${unknownFlags[0]}`);
9
+ let parsed;
10
+ try {
11
+ parsed = parseArgs(argv, commitFlagSchema);
12
+ } catch (error) {
13
+ console.error(`Error: ${translateParseError(error)}`);
9
14
  process.exit(1);
10
15
  }
11
- const dryRun = argv.includes("--dry-run");
16
+ const dryRun = parsed.flags.dryRun;
12
17
  let tagsContent;
13
18
  try {
14
19
  tagsContent = readFileSync(RELEASE_TAGS_FILE, "utf8");
@@ -1,5 +1,6 @@
1
1
  import { execFileSync } from "node:child_process";
2
- import { readFileSync, unlinkSync } from "node:fs";
2
+ import { readFileSync } from "node:fs";
3
+ import { deleteFileIfExists } from "./deleteFileIfExists.js";
3
4
  import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./prepareCommand.js";
4
5
  function createTags(options) {
5
6
  const { dryRun, noGitChecks } = options;
@@ -42,30 +43,10 @@ function createTags(options) {
42
43
  for (const tag of tags) {
43
44
  console.info(`\u{1F3F7}\uFE0F ${tag}`);
44
45
  }
45
- deleteTagsFile();
46
- deleteSummaryFile();
46
+ deleteFileIfExists(RELEASE_TAGS_FILE);
47
+ deleteFileIfExists(RELEASE_SUMMARY_FILE);
47
48
  return tags;
48
49
  }
49
- function deleteTagsFile() {
50
- try {
51
- unlinkSync(RELEASE_TAGS_FILE);
52
- } catch (error) {
53
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
54
- return;
55
- }
56
- throw error;
57
- }
58
- }
59
- function deleteSummaryFile() {
60
- try {
61
- unlinkSync(RELEASE_SUMMARY_FILE);
62
- } catch (error) {
63
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
64
- return;
65
- }
66
- throw error;
67
- }
68
- }
69
50
  function assertCleanWorkingTree() {
70
51
  try {
71
52
  execFileSync("git", ["diff", "--quiet"]);
@@ -0,0 +1 @@
1
+ export declare function deleteFileIfExists(filePath: string): void;
@@ -0,0 +1,14 @@
1
+ import { unlinkSync } from "node:fs";
2
+ function deleteFileIfExists(filePath) {
3
+ try {
4
+ unlinkSync(filePath);
5
+ } catch (error) {
6
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
7
+ return;
8
+ }
9
+ throw error;
10
+ }
11
+ }
12
+ export {
13
+ deleteFileIfExists
14
+ };
@@ -13,6 +13,7 @@ export { bumpVersion } from './bumpVersion.ts';
13
13
  export { commitCommand } from './commitCommand.ts';
14
14
  export { component } from './component.ts';
15
15
  export { createTags } from './createTags.ts';
16
+ export { deleteFileIfExists } from './deleteFileIfExists.ts';
16
17
  export { detectPackageManager } from './detectPackageManager.ts';
17
18
  export { determineBumpType } from './determineBumpType.ts';
18
19
  export { discoverWorkspaces } from './discoverWorkspaces.ts';
package/dist/esm/index.js CHANGED
@@ -5,6 +5,7 @@ import { bumpVersion } from "./bumpVersion.js";
5
5
  import { commitCommand } from "./commitCommand.js";
6
6
  import { component } from "./component.js";
7
7
  import { createTags } from "./createTags.js";
8
+ import { deleteFileIfExists } from "./deleteFileIfExists.js";
8
9
  import { detectPackageManager } from "./detectPackageManager.js";
9
10
  import { determineBumpType } from "./determineBumpType.js";
10
11
  import { discoverWorkspaces } from "./discoverWorkspaces.js";
@@ -30,6 +31,7 @@ export {
30
31
  commitCommand,
31
32
  component,
32
33
  createTags,
34
+ deleteFileIfExists,
33
35
  detectPackageManager,
34
36
  determineBumpType,
35
37
  discoverWorkspaces,
@@ -1,4 +1,8 @@
1
- import { writeFileWithCheck } from "@williamthorsen/node-monorepo-core";
1
+ import {
2
+ parseArgs as coreParseArgs,
3
+ translateParseError,
4
+ writeFileWithCheck
5
+ } from "@williamthorsen/node-monorepo-core";
2
6
  import { buildReleaseSummary } from "./buildReleaseSummary.js";
3
7
  import { discoverWorkspaces } from "./discoverWorkspaces.js";
4
8
  import { dim } from "./format.js";
@@ -25,43 +29,40 @@ Options:
25
29
  --help Show this help message
26
30
  `);
27
31
  }
32
+ const prepareFlagSchema = {
33
+ dryRun: { long: "--dry-run", type: "boolean" },
34
+ force: { long: "--force", type: "boolean" },
35
+ bump: { long: "--bump", type: "string" },
36
+ only: { long: "--only", type: "string" },
37
+ help: { long: "--help", type: "boolean", short: "-h" }
38
+ };
28
39
  function parseArgs(argv) {
29
- let dryRun = false;
30
- let force = false;
40
+ let parsed;
41
+ try {
42
+ parsed = coreParseArgs(argv, prepareFlagSchema);
43
+ } catch (error) {
44
+ throw new Error(translateParseError(error));
45
+ }
46
+ const { flags } = parsed;
47
+ if (flags.help) {
48
+ showHelp();
49
+ process.exit(0);
50
+ }
31
51
  let bumpOverride;
32
- let only;
33
- for (const arg of argv) {
34
- if (arg === "--dry-run") {
35
- dryRun = true;
36
- } else if (arg === "--force") {
37
- force = true;
38
- } else if (arg.startsWith("--bump=")) {
39
- const value = arg.slice("--bump=".length);
40
- if (!isReleaseType(value)) {
41
- console.error(`Error: Invalid bump type "${value}". Must be one of: ${VALID_BUMP_TYPES.join(", ")}`);
42
- process.exit(1);
43
- }
44
- bumpOverride = value;
45
- } else if (arg.startsWith("--only=")) {
46
- const value = arg.slice("--only=".length);
47
- if (!value) {
48
- console.error("Error: --only requires a comma-separated list of component names");
49
- process.exit(1);
50
- }
51
- only = value.split(",");
52
- } else if (arg === "--help" || arg === "-h") {
53
- showHelp();
54
- process.exit(0);
55
- } else {
56
- console.error(`Error: Unknown argument: ${arg}`);
57
- process.exit(1);
52
+ if (flags.bump !== void 0) {
53
+ if (!isReleaseType(flags.bump)) {
54
+ throw new Error(`Invalid bump type "${flags.bump}". Must be one of: ${VALID_BUMP_TYPES.join(", ")}`);
58
55
  }
56
+ bumpOverride = flags.bump;
59
57
  }
60
- if (force && bumpOverride === void 0) {
61
- console.error("Error: --force requires --bump to specify the version bump type");
62
- process.exit(1);
58
+ let only;
59
+ if (flags.only !== void 0) {
60
+ only = flags.only.split(",");
61
+ }
62
+ if (flags.force && bumpOverride === void 0) {
63
+ throw new Error("--force requires --bump to specify the version bump type");
63
64
  }
64
- return { dryRun, force, bumpOverride, only };
65
+ return { dryRun: flags.dryRun, force: flags.force, bumpOverride, only };
65
66
  }
66
67
  function writeReleaseTags(tags, dryRun) {
67
68
  if (tags.length === 0) {
@@ -70,7 +71,16 @@ function writeReleaseTags(tags, dryRun) {
70
71
  return writeFileWithCheck(RELEASE_TAGS_FILE, tags.join("\n"), { dryRun, overwrite: true });
71
72
  }
72
73
  async function prepareCommand(argv) {
73
- const { dryRun, force, bumpOverride, only } = parseArgs(argv);
74
+ let dryRun;
75
+ let force;
76
+ let bumpOverride;
77
+ let only;
78
+ try {
79
+ ({ dryRun, force, bumpOverride, only } = parseArgs(argv));
80
+ } catch (error) {
81
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
82
+ process.exit(1);
83
+ }
74
84
  const options = {
75
85
  dryRun,
76
86
  force,
@@ -1,20 +1,25 @@
1
1
  import { basename } from "node:path";
2
+ import { parseArgs, translateParseError } from "@williamthorsen/node-monorepo-core";
2
3
  import { detectPackageManager } from "./detectPackageManager.js";
3
4
  import { discoverWorkspaces } from "./discoverWorkspaces.js";
4
5
  import { publish } from "./publish.js";
5
6
  import { resolveReleaseTags } from "./resolveReleaseTags.js";
7
+ const publishFlagSchema = {
8
+ dryRun: { long: "--dry-run", type: "boolean" },
9
+ noGitChecks: { long: "--no-git-checks", type: "boolean" },
10
+ provenance: { long: "--provenance", type: "boolean" },
11
+ only: { long: "--only", type: "string" }
12
+ };
6
13
  async function publishCommand(argv) {
7
- const knownFlags = /* @__PURE__ */ new Set(["--dry-run", "--no-git-checks", "--provenance"]);
8
- const unknownFlags = argv.filter((f) => !f.startsWith("--only=") && !knownFlags.has(f));
9
- if (unknownFlags.length > 0) {
10
- console.error(`Error: Unknown option: ${unknownFlags[0]}`);
14
+ let parsed;
15
+ try {
16
+ parsed = parseArgs(argv, publishFlagSchema);
17
+ } catch (error) {
18
+ console.error(`Error: ${translateParseError(error)}`);
11
19
  process.exit(1);
12
20
  }
13
- const dryRun = argv.includes("--dry-run");
14
- const noGitChecks = argv.includes("--no-git-checks");
15
- const provenance = argv.includes("--provenance");
16
- const onlyArg = argv.find((f) => f.startsWith("--only="));
17
- const only = onlyArg?.slice("--only=".length).split(",");
21
+ const { dryRun, noGitChecks, provenance } = parsed.flags;
22
+ const only = parsed.flags.only?.split(",");
18
23
  let discoveredPaths;
19
24
  try {
20
25
  discoveredPaths = await discoverWorkspaces();
@@ -1,13 +1,18 @@
1
+ import { parseArgs, translateParseError } from "@williamthorsen/node-monorepo-core";
1
2
  import { createTags } from "./createTags.js";
3
+ const tagFlagSchema = {
4
+ dryRun: { long: "--dry-run", type: "boolean" },
5
+ noGitChecks: { long: "--no-git-checks", type: "boolean" }
6
+ };
2
7
  function tagCommand(argv) {
3
- const knownFlags = /* @__PURE__ */ new Set(["--dry-run", "--no-git-checks"]);
4
- const unknownFlags = argv.filter((f) => !knownFlags.has(f));
5
- if (unknownFlags.length > 0) {
6
- console.error(`Error: Unknown option: ${unknownFlags[0]}`);
8
+ let parsed;
9
+ try {
10
+ parsed = parseArgs(argv, tagFlagSchema);
11
+ } catch (error) {
12
+ console.error(`Error: ${translateParseError(error)}`);
7
13
  process.exit(1);
8
14
  }
9
- const dryRun = argv.includes("--dry-run");
10
- const noGitChecks = argv.includes("--no-git-checks");
15
+ const { dryRun, noGitChecks } = parsed.flags;
11
16
  try {
12
17
  createTags({ dryRun, noGitChecks });
13
18
  } catch (error) {
@@ -0,0 +1 @@
1
+ export declare const VERSION = "4.4.0";
@@ -0,0 +1,4 @@
1
+ const VERSION = "4.4.0";
2
+ export {
3
+ VERSION
4
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamthorsen/release-kit",
3
- "version": "4.0.0",
3
+ "version": "4.4.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",
@@ -34,7 +34,7 @@
34
34
  "glob": "13.0.6",
35
35
  "jiti": "2.6.1",
36
36
  "js-yaml": "4.1.1",
37
- "@williamthorsen/node-monorepo-core": "0.2.1"
37
+ "@williamthorsen/node-monorepo-core": "0.2.5"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/js-yaml": "4.0.9",