@williamthorsen/release-kit 10.3.1 → 10.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/.readyup/kits/default.js +2 -2
- package/.readyup/kits/npm-auto-publish.js +1 -1
- package/.readyup/manifest.json +43 -4
- package/CHANGELOG.md +50 -0
- package/README.md +36 -9
- package/dist/esm/bin/release-kit.js +6 -5
- package/dist/esm/buildChangelogEntries.js +2 -1
- package/dist/esm/buildDependencyGraph.js +3 -6
- package/dist/esm/changelogJsonFile.js +2 -1
- package/dist/esm/changelogOverrides.js +3 -2
- package/dist/esm/checkWorkTypesDrift.js +5 -4
- package/dist/esm/commitCommand.js +2 -2
- package/dist/esm/createGithubReleaseCommand.js +2 -1
- package/dist/esm/createTags.js +2 -2
- package/dist/esm/deleteFileIfExists.js +2 -1
- package/dist/esm/deriveWorkspaceConfig.js +2 -2
- package/dist/esm/discoverWorkspaces.js +2 -1
- package/dist/esm/getCommitsSinceTarget.js +3 -5
- package/dist/esm/init/initCommand.js +4 -3
- package/dist/esm/init/scaffold.js +2 -1
- package/dist/esm/loadConfig.js +9 -5
- package/dist/esm/loadValidatedConfig.js +2 -1
- package/dist/esm/planReleaseNotesPreviews.js +2 -1
- package/dist/esm/planVersionBump.js +3 -6
- package/dist/esm/prepareCommand.js +8 -7
- package/dist/esm/previewTagPrefixes.js +2 -1
- package/dist/esm/publishCommand.js +3 -2
- package/dist/esm/pushCommand.js +2 -1
- package/dist/esm/readCurrentVersion.js +2 -1
- package/dist/esm/releaseFiles.js +4 -8
- package/dist/esm/releasePrepareMono.js +2 -5
- package/dist/esm/releasePrepareProject.js +1 -1
- package/dist/esm/resolveCommandTags.js +3 -2
- package/dist/esm/resolveReleaseNotesConfig.js +2 -1
- package/dist/esm/sync-labels/generateCommand.js +3 -2
- package/dist/esm/sync-labels/initCommand.js +2 -1
- package/dist/esm/sync-labels/presets.js +2 -2
- package/dist/esm/sync-labels/syncCommand.js +2 -1
- package/dist/esm/syncWorkTypes.js +5 -4
- package/dist/esm/tagCommand.js +2 -1
- package/dist/esm/types.d.ts +3 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/validateOverridesCommand.js +6 -9
- package/dist/esm/workTypesUtils.d.ts +0 -1
- package/dist/esm/workTypesUtils.js +0 -3
- package/package.json +16 -4
package/.readyup/kits/default.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** @noformat — @generated. Do not edit. Compiled by rdy. */
|
|
2
2
|
/* eslint-disable */
|
|
3
|
-
export const __readyupVersion = "0.
|
|
3
|
+
export const __readyupVersion = "0.28.0";
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
// .readyup/kits/default.ts
|
|
@@ -52,7 +52,7 @@ function detectRepoType() {
|
|
|
52
52
|
|
|
53
53
|
// .readyup/kits/default.ts
|
|
54
54
|
function getMinVersion() {
|
|
55
|
-
const picked = { "version": "10.
|
|
55
|
+
const picked = { "version": "10.4.0" };
|
|
56
56
|
if (typeof picked["version"] !== "string") {
|
|
57
57
|
throw new TypeError("release-kit/package.json: 'version' must be a string");
|
|
58
58
|
}
|
package/.readyup/manifest.json
CHANGED
|
@@ -5,24 +5,63 @@
|
|
|
5
5
|
"checklists": [
|
|
6
6
|
"release-kit"
|
|
7
7
|
],
|
|
8
|
+
"esbuildVersion": "0.28.1",
|
|
9
|
+
"inputs": [
|
|
10
|
+
{
|
|
11
|
+
"hash": "fe239d01",
|
|
12
|
+
"kind": "module",
|
|
13
|
+
"path": "kits/default.ts"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"hash": "07509c48",
|
|
17
|
+
"kind": "inline",
|
|
18
|
+
"path": "../package.json",
|
|
19
|
+
"paths": [
|
|
20
|
+
"version"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"hash": "850b703f",
|
|
25
|
+
"kind": "module",
|
|
26
|
+
"path": "../src/init/detectRepoType.ts"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"hash": "b6873aba",
|
|
30
|
+
"kind": "module",
|
|
31
|
+
"path": "../src/init/parseJsonRecord.ts"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"hash": "646fe51b",
|
|
35
|
+
"kind": "module",
|
|
36
|
+
"path": "../src/typeGuards.ts"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
8
39
|
"name": "default",
|
|
9
40
|
"path": "kits/default.js",
|
|
10
|
-
"readyupVersion": "0.
|
|
41
|
+
"readyupVersion": "0.28.0",
|
|
11
42
|
"source": "kits/default.ts",
|
|
12
43
|
"sourceHash": "fe239d01",
|
|
13
|
-
"targetHash": "
|
|
44
|
+
"targetHash": "5413984b"
|
|
14
45
|
},
|
|
15
46
|
{
|
|
16
47
|
"checklists": [
|
|
17
48
|
"repo",
|
|
18
49
|
"packages"
|
|
19
50
|
],
|
|
51
|
+
"esbuildVersion": "0.28.1",
|
|
52
|
+
"inputs": [
|
|
53
|
+
{
|
|
54
|
+
"hash": "ac484613",
|
|
55
|
+
"kind": "module",
|
|
56
|
+
"path": "kits/npm-auto-publish.ts"
|
|
57
|
+
}
|
|
58
|
+
],
|
|
20
59
|
"name": "npm-auto-publish",
|
|
21
60
|
"path": "kits/npm-auto-publish.js",
|
|
22
|
-
"readyupVersion": "0.
|
|
61
|
+
"readyupVersion": "0.28.0",
|
|
23
62
|
"source": "kits/npm-auto-publish.ts",
|
|
24
63
|
"sourceHash": "ac484613",
|
|
25
|
-
"targetHash": "
|
|
64
|
+
"targetHash": "0636c995"
|
|
26
65
|
}
|
|
27
66
|
]
|
|
28
67
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,56 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 10.4.0 — 2026-08-17
|
|
6
|
+
|
|
7
|
+
### 🎉 Features
|
|
8
|
+
|
|
9
|
+
- Let a project release cover repo-root paths (#685)
|
|
10
|
+
|
|
11
|
+
Extends `release-kit`'s project release to a repo whose content lives at the root. Such a repo declares `project: { paths: ['**'] }` and gets the root `CHANGELOG.md`, the root `.meta/changelog.json`, the `--with-release-notes` previews under root `docs/`, the root `package.json` bump, and the project tag that the stage produces.
|
|
12
|
+
|
|
13
|
+
`paths` scopes any project release's window, not only the whole-tree case, and the project tier of `release-kit validate-overrides` with it. A declared value replaces the workspace union rather than extending it; omitting `paths` resolves to that union, so existing consumers see no change.
|
|
14
|
+
|
|
15
|
+
### ♻️ Refactoring
|
|
16
|
+
|
|
17
|
+
- Adopt silenceConsole across the test suites (#677)
|
|
18
|
+
|
|
19
|
+
Adopts `silenceConsole` from `@williamthorsen/toolbelt.vitest` to replace all hand-rolled `vi.spyOn(console, …)` spies and reduce boilerplate across the repo's test suites. The function returns a disposable, allowing the caller to use `using` to enable restoration of the console method at the end of the test scope.
|
|
20
|
+
|
|
21
|
+
- Adopt `isError` and consolidate errno narrowing on a shared helper (#697)
|
|
22
|
+
|
|
23
|
+
Replaces every hand-rolled `instanceof Error` narrowing with `hasErrnoCode`, a new predicate exported from `@williamthorsen/nmr-core`, or with `isError` from `@williamthorsen/toolbelt.errors`. Previously the errno question was answered by inline copies alongside two private helpers of differing shape; both helpers are removed.
|
|
24
|
+
|
|
25
|
+
Separately, `eslint.config.ts` now bans `instanceof Error` in every position rather than in a ternary alone.
|
|
26
|
+
|
|
27
|
+
### 🧪 Tests
|
|
28
|
+
|
|
29
|
+
- Replace the stdio spies with captureStdio (#678)
|
|
30
|
+
|
|
31
|
+
Replaces every `process.stdout` and `process.stderr` spy in the test suites with `captureStdio` from `@williamthorsen/toolbelt.testing` and deletes the obviated local capture helpers. `captureStdio` buffers writes rather than recording calls, so assertions that previously used `toHaveBeenCalledWith` instead check `stderr`/`stdout` strings and their chunk arrays.
|
|
32
|
+
|
|
33
|
+
Separately, `unicorn/no-nonstandard-builtin-properties` is off for test files. Its hand-maintained symbol table omits `Symbol.dispose`, which a suite-scoped capture calls to restore the streams, and `schema: []` offers no way to extend the table.
|
|
34
|
+
|
|
35
|
+
- Adopt throwOnProcessExit and captureError across the test suites (#690)
|
|
36
|
+
|
|
37
|
+
Adopts `throwOnProcessExit` from `@williamthorsen/toolbelt.vitest`, replacing every hand-rolled `process.exit` mock in the `release-kit` and `nmr-core` test suites, and `captureError` from `@williamthorsen/toolbelt.testing`, replacing inline try/catch/`instanceof` blocks.
|
|
38
|
+
|
|
39
|
+
Separately, `@williamthorsen/toolbelt.vitest` is now listed in the ReadyUp config, so that its kit is run by `rdy run --packages`.
|
|
40
|
+
|
|
41
|
+
## 10.3.2 — 2026-08-13
|
|
42
|
+
|
|
43
|
+
### ♻️ Refactoring
|
|
44
|
+
|
|
45
|
+
- Adopt toolbelt.errors for error-message extraction and cause-chaining (#652)
|
|
46
|
+
|
|
47
|
+
Improves error reporting by adopting standardized ways of capturing and describing errors across the codebase.
|
|
48
|
+
|
|
49
|
+
### ⚙️ Tooling
|
|
50
|
+
|
|
51
|
+
- Upgrade the lint config and clear its violations (#671)
|
|
52
|
+
|
|
53
|
+
Upgrades `@williamthorsen/eslint-config-typescript`, along with other config packages, and clears lint violations surfaced by the newly activated rules.
|
|
54
|
+
|
|
5
55
|
## 10.3.0 — 2026-08-08
|
|
6
56
|
|
|
7
57
|
### 🎉 Features
|
package/README.md
CHANGED
|
@@ -4,7 +4,17 @@ Version-bumping and changelog-generation toolkit for release workflows.
|
|
|
4
4
|
|
|
5
5
|
Provides a self-contained CLI that auto-discovers workspaces from `pnpm-workspace.yaml`, parses conventional commits, determines version bumps, updates `package.json` files, and generates changelogs from `git-cliff --context` output rendered in-process (with optional [editorial overrides](#editorial-overrides)).
|
|
6
6
|
|
|
7
|
-
<!-- section:release-notes
|
|
7
|
+
<!-- section:release-notes -->
|
|
8
|
+
## Release notes — v10.4.0 (2026-08-17)
|
|
9
|
+
|
|
10
|
+
### 🎉 Features
|
|
11
|
+
|
|
12
|
+
- Let a project release cover repo-root paths (#685)
|
|
13
|
+
|
|
14
|
+
Extends `release-kit`'s project release to a repo whose content lives at the root. Such a repo declares `project: { paths: ['**'] }` and gets the root `CHANGELOG.md`, the root `.meta/changelog.json`, the `--with-release-notes` previews under root `docs/`, the root `package.json` bump, and the project tag that the stage produces.
|
|
15
|
+
|
|
16
|
+
`paths` scopes any project release's window, not only the whole-tree case, and the project tier of `release-kit validate-overrides` with it. A declared value replaces the workspace union rather than extending it; omitting `paths` resolves to that union, so existing consumers see no change.
|
|
17
|
+
<!-- /section:release-notes -->
|
|
8
18
|
|
|
9
19
|
## Installation
|
|
10
20
|
|
|
@@ -201,33 +211,50 @@ export default config;
|
|
|
201
211
|
|
|
202
212
|
When configured, each `release-kit prepare` run additionally:
|
|
203
213
|
|
|
204
|
-
- Computes commits since the last project tag (`<tagPrefix><version>`), filtered to the union of every contributing workspace's paths.
|
|
214
|
+
- Computes commits since the last project tag (`<tagPrefix><version>`), filtered to `paths` (by default, the union of every contributing workspace's paths).
|
|
205
215
|
- Bumps the root `package.json`'s `version` field using the same bump-derivation rules as workspaces (or the `--bump=...` override).
|
|
206
|
-
- Regenerates the root `./CHANGELOG.md` from the structured `ChangelogEntry[]` produced by `git-cliff --context` (scoped to the project's `tagPrefix` and
|
|
216
|
+
- Regenerates the root `./CHANGELOG.md` from the structured `ChangelogEntry[]` produced by `git-cliff --context` (scoped to the project's `tagPrefix` and the same `paths`) and any matching editorial overrides.
|
|
207
217
|
- Emits `./.meta/changelog.json` (when `changelogJson.enabled`).
|
|
208
218
|
- With `--with-release-notes`, additionally emits `./docs/RELEASE_NOTES.v<version>.md`.
|
|
209
219
|
- Appends the project tag to `tmp/.release-tags` so `release-kit commit` and `release-kit tag` pick it up alongside per-workspace tags.
|
|
210
220
|
|
|
211
|
-
If no
|
|
221
|
+
If no commit under `paths` has landed since the last project tag, the project release is silently skipped — same behavior as a per-workspace skip.
|
|
212
222
|
|
|
213
223
|
#### `ProjectConfig`
|
|
214
224
|
|
|
215
225
|
```typescript
|
|
216
226
|
interface ProjectConfig {
|
|
227
|
+
paths?: string[]; // Defaults to the union of every contributing workspace's paths
|
|
217
228
|
tagPrefix?: string; // Defaults to 'v'
|
|
218
229
|
}
|
|
219
230
|
```
|
|
220
231
|
|
|
221
|
-
| Field | Default
|
|
222
|
-
| ----------- |
|
|
223
|
-
| `
|
|
232
|
+
| Field | Default | Description |
|
|
233
|
+
| ----------- | ----------------------- | -------------------------------------------------------------------- |
|
|
234
|
+
| `paths` | Contributing-path union | Patterns selecting the commits the project release considers |
|
|
235
|
+
| `tagPrefix` | `'v'` | Prefix for project tags. The full tag is `${tagPrefix}${newVersion}` |
|
|
236
|
+
|
|
237
|
+
By default the contributing workspaces are implicit: every non-excluded discovered workspace contributes its `<dir>/**` glob, and the project release considers commits under their union.
|
|
238
|
+
|
|
239
|
+
Declare `paths` to choose the window yourself. Each entry reaches two matchers: `git log` as a pathspec, and `git-cliff --include-path` as a glob. A declared value **replaces** the workspace union rather than extending it: `paths: ['docs/**']` drops every workspace commit from the project release.
|
|
240
|
+
|
|
241
|
+
A repo whose content lives at the root — where no commit matches any `<dir>/**` glob, so the project release would skip every run — declares the whole tree:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
const config: ReleaseKitConfig = {
|
|
245
|
+
project: { paths: ['**'] },
|
|
246
|
+
};
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
`'**'` is the whole-tree pattern, matching root-level files, dotfiles, and nested paths alike.
|
|
224
250
|
|
|
225
|
-
|
|
251
|
+
Terminate a directory scope with `/**`. The two matchers agree on `'aws/**'` but diverge on a bare `'aws'`, which git reads as the whole subtree and git-cliff matches against nothing; `'.'` is the same trap for the repo root. A release under such an entry finds commits, bumps the version, and writes the tag, while its changelog gains no entry for them — and nothing reports an error.
|
|
226
252
|
|
|
227
253
|
Validation rules:
|
|
228
254
|
|
|
229
255
|
- The root `package.json` must exist and declare a `version` field. release-kit reports an error at config-load time if either is missing.
|
|
230
|
-
- The `project` block is rejected in single-package mode (the
|
|
256
|
+
- The `project` block is rejected in single-package mode (the package's own release already covers the whole repo, so a project tier would only duplicate it).
|
|
257
|
+
- `paths`, when declared, must hold at least one non-empty string. An empty array is rejected: it reaches git and git-cliff as no filter at all, which is the inverse of how it reads.
|
|
231
258
|
- Unknown fields inside `project` are rejected.
|
|
232
259
|
|
|
233
260
|
CLI flag interactions:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { parseArgsOrExit, readPackageVersion, reportError } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
4
|
import { checkWorkTypesDrift } from "../checkWorkTypesDrift.js";
|
|
4
5
|
import { commitCommand } from "../commitCommand.js";
|
|
5
6
|
import { createGithubReleaseCommand } from "../createGithubReleaseCommand.js";
|
|
@@ -258,7 +259,6 @@ Options:
|
|
|
258
259
|
}
|
|
259
260
|
const args = process.argv.slice(2);
|
|
260
261
|
const command = args[0];
|
|
261
|
-
const flags = args.slice(1);
|
|
262
262
|
if (command === '--version' || command === '-V') {
|
|
263
263
|
console.info(VERSION);
|
|
264
264
|
process.exit(0);
|
|
@@ -267,6 +267,7 @@ if (command === undefined || ['--help', '-h'].includes(command)) {
|
|
|
267
267
|
showUsage();
|
|
268
268
|
process.exit(0);
|
|
269
269
|
}
|
|
270
|
+
const flags = args.slice(1);
|
|
270
271
|
if (command === 'prepare') {
|
|
271
272
|
if (flags.some((f) => f === '--help' || f === '-h')) {
|
|
272
273
|
showPrepareHelp();
|
|
@@ -284,7 +285,7 @@ if (command === 'commit') {
|
|
|
284
285
|
commitCommand(flags);
|
|
285
286
|
}
|
|
286
287
|
catch (error) {
|
|
287
|
-
reportError(
|
|
288
|
+
reportError(describeError(error));
|
|
288
289
|
process.exit(1);
|
|
289
290
|
}
|
|
290
291
|
process.exit(0);
|
|
@@ -349,11 +350,11 @@ if (command === 'init') {
|
|
|
349
350
|
}
|
|
350
351
|
if (command === 'sync-labels') {
|
|
351
352
|
const subcommand = flags[0];
|
|
352
|
-
const subflags = flags.slice(1);
|
|
353
353
|
if (subcommand === undefined || ['--help', '-h'].includes(subcommand)) {
|
|
354
354
|
showSyncLabelsHelp();
|
|
355
355
|
process.exit(0);
|
|
356
356
|
}
|
|
357
|
+
const subflags = flags.slice(1);
|
|
357
358
|
if (subcommand === 'init') {
|
|
358
359
|
if (subflags.some((f) => f === '--help' || f === '-h')) {
|
|
359
360
|
showSyncLabelsInitHelp();
|
|
@@ -397,11 +398,11 @@ if (command === 'sync-labels') {
|
|
|
397
398
|
}
|
|
398
399
|
if (command === 'overrides') {
|
|
399
400
|
const subcommand = flags[0];
|
|
400
|
-
const subflags = flags.slice(1);
|
|
401
401
|
if (subcommand === undefined || ['--help', '-h'].includes(subcommand)) {
|
|
402
402
|
showOverridesHelp();
|
|
403
403
|
process.exit(0);
|
|
404
404
|
}
|
|
405
|
+
const subflags = flags.slice(1);
|
|
405
406
|
if (subcommand === 'validate') {
|
|
406
407
|
if (subflags.some((f) => f === '--help' || f === '-h')) {
|
|
407
408
|
showOverridesValidateHelp();
|
|
@@ -426,11 +427,11 @@ if (command === 'overrides') {
|
|
|
426
427
|
}
|
|
427
428
|
if (command === 'work-types') {
|
|
428
429
|
const subcommand = flags[0];
|
|
429
|
-
const subflags = flags.slice(1);
|
|
430
430
|
if (subcommand === undefined || ['--help', '-h'].includes(subcommand)) {
|
|
431
431
|
showWorkTypesHelp();
|
|
432
432
|
process.exit(0);
|
|
433
433
|
}
|
|
434
|
+
const subflags = flags.slice(1);
|
|
434
435
|
if (subcommand === 'check') {
|
|
435
436
|
if (subflags.some((f) => f === '--help' || f === '-h')) {
|
|
436
437
|
showWorkTypesCheckHelp();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
1
2
|
import { extractVersion } from "./changelogJsonUtils.js";
|
|
2
3
|
import { DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
3
4
|
import { COMMIT_PREPROCESSOR_PATTERNS, PIPE_SCOPE_SOURCE } from "./parseCommitMessage.js";
|
|
@@ -31,7 +32,7 @@ export function buildChangelogEntries(config, tag, options) {
|
|
|
31
32
|
return transformReleases(releases, devOnlySections);
|
|
32
33
|
}
|
|
33
34
|
catch (error) {
|
|
34
|
-
throw
|
|
35
|
+
throw chainError(`Failed to build changelog entries for tag ${tag}`, error);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
function parseCliffContext(json) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
2
3
|
function isPackageJsonSubset(value) {
|
|
3
4
|
return typeof value === 'object' && value !== null;
|
|
4
5
|
}
|
|
@@ -51,18 +52,14 @@ function readPackageJsonSubset(filePath) {
|
|
|
51
52
|
content = readFileSync(filePath, 'utf8');
|
|
52
53
|
}
|
|
53
54
|
catch (error) {
|
|
54
|
-
throw
|
|
55
|
-
cause: error,
|
|
56
|
-
});
|
|
55
|
+
throw chainError(`Failed to read ${filePath}`, error);
|
|
57
56
|
}
|
|
58
57
|
let parsed;
|
|
59
58
|
try {
|
|
60
59
|
parsed = JSON.parse(content);
|
|
61
60
|
}
|
|
62
61
|
catch (error) {
|
|
63
|
-
throw
|
|
64
|
-
cause: error,
|
|
65
|
-
});
|
|
62
|
+
throw chainError(`Failed to parse JSON in ${filePath}`, error);
|
|
66
63
|
}
|
|
67
64
|
if (!isPackageJsonSubset(parsed)) {
|
|
68
65
|
throw new Error(`Invalid package.json at ${filePath}`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
4
|
import stringify from 'json-stringify-pretty-compact';
|
|
4
5
|
import semver from 'semver';
|
|
5
6
|
import { isChangelogEntry } from "./changelogJsonUtils.js";
|
|
@@ -30,7 +31,7 @@ function readExistingEntries(filePath) {
|
|
|
30
31
|
return parsed.filter(isChangelogEntry);
|
|
31
32
|
}
|
|
32
33
|
catch (error) {
|
|
33
|
-
console.warn(`Warning: could not parse existing ${filePath}: ${
|
|
34
|
+
console.warn(`Warning: could not parse existing ${filePath}: ${describeError(error)}; treating as empty`);
|
|
34
35
|
return [];
|
|
35
36
|
}
|
|
36
37
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
4
|
import { isRecord } from "./typeGuards.js";
|
|
4
5
|
const OVERRIDES_FILENAME = '.meta/changelog-overrides.json';
|
|
5
6
|
const VALID_AUDIENCE_VALUES = new Set(['all', 'dev', 'skip']);
|
|
@@ -15,7 +16,7 @@ export function loadChangelogOverrides(path) {
|
|
|
15
16
|
}
|
|
16
17
|
catch (error) {
|
|
17
18
|
return {
|
|
18
|
-
errors: [`Failed to read override file ${path}: ${
|
|
19
|
+
errors: [`Failed to read override file ${path}: ${describeError(error)}`],
|
|
19
20
|
};
|
|
20
21
|
}
|
|
21
22
|
let parsed;
|
|
@@ -24,7 +25,7 @@ export function loadChangelogOverrides(path) {
|
|
|
24
25
|
}
|
|
25
26
|
catch (error) {
|
|
26
27
|
return {
|
|
27
|
-
errors: [`Failed to parse override file ${path}: ${
|
|
28
|
+
errors: [`Failed to parse override file ${path}: ${describeError(error)}`],
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
31
|
const result = validateChangelogOverrides(parsed);
|
|
@@ -2,7 +2,8 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import { dirname, resolve } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { formatErrorLine } from '@williamthorsen/nmr-core';
|
|
5
|
-
import {
|
|
5
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
6
|
+
import { buildFetchInit, hasExpectedTopLevelShape } from "./workTypesUtils.js";
|
|
6
7
|
export const UPSTREAM_WORK_TYPES_URL = 'https://raw.githubusercontent.com/williamthorsen/codeassembly/main/packages/agents/content/skills/_data/work-types.json';
|
|
7
8
|
function resolveDefaultLocalPath() {
|
|
8
9
|
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -20,7 +21,7 @@ export async function checkWorkTypesDrift(dependencies = {}) {
|
|
|
20
21
|
catch (error) {
|
|
21
22
|
return {
|
|
22
23
|
exitCode: 3,
|
|
23
|
-
message: `Local work-types.json is not valid JSON: ${
|
|
24
|
+
message: `Local work-types.json is not valid JSON: ${describeError(error)}`,
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
27
|
const fetchInit = buildFetchInit();
|
|
@@ -31,7 +32,7 @@ export async function checkWorkTypesDrift(dependencies = {}) {
|
|
|
31
32
|
catch (error) {
|
|
32
33
|
return {
|
|
33
34
|
exitCode: 2,
|
|
34
|
-
message: formatErrorLine(`Failed to fetch upstream work-types.json: ${
|
|
35
|
+
message: formatErrorLine(`Failed to fetch upstream work-types.json: ${describeError(error)}`),
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
if (response.status === 404) {
|
|
@@ -54,7 +55,7 @@ export async function checkWorkTypesDrift(dependencies = {}) {
|
|
|
54
55
|
catch (error) {
|
|
55
56
|
return {
|
|
56
57
|
exitCode: 3,
|
|
57
|
-
message: `Upstream work-types.json is not valid JSON: ${
|
|
58
|
+
message: `Upstream work-types.json is not valid JSON: ${describeError(error)}`,
|
|
58
59
|
};
|
|
59
60
|
}
|
|
60
61
|
if (!hasExpectedTopLevelShape(upstreamJson)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
|
-
import { GIT_OUTPUT_LIMIT, parseArgsOrExit } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { GIT_OUTPUT_LIMIT, hasErrnoCode, parseArgsOrExit } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { readReleaseTags, RELEASE_SUMMARY_FILE, resolveReleaseTagsPath } from "./releaseFiles.js";
|
|
5
5
|
const commitFlagSchema = {
|
|
6
6
|
dryRun: { long: '--dry-run', type: 'boolean' },
|
|
@@ -16,7 +16,7 @@ export function commitCommand(argv) {
|
|
|
16
16
|
summary = readFileSync(RELEASE_SUMMARY_FILE, 'utf8').trim();
|
|
17
17
|
}
|
|
18
18
|
catch (error) {
|
|
19
|
-
if (error
|
|
19
|
+
if (hasErrnoCode(error, 'ENOENT')) {
|
|
20
20
|
}
|
|
21
21
|
else {
|
|
22
22
|
throw error;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
2
3
|
import { createGithubReleases } from "./createGithubRelease.js";
|
|
3
4
|
import { formatPrivateSkip } from "./formatPrivateSkip.js";
|
|
4
5
|
import { parseRequestedTags } from "./parseRequestedTags.js";
|
|
@@ -28,7 +29,7 @@ export async function createGithubReleaseCommand(argv) {
|
|
|
28
29
|
outcome = createGithubReleases(publishableTags, changelogJsonOutputPath, dryRun, sectionOrder);
|
|
29
30
|
}
|
|
30
31
|
catch (error) {
|
|
31
|
-
reportError(`Failed to create GitHub Releases: ${
|
|
32
|
+
reportError(`Failed to create GitHub Releases: ${describeError(error)}`);
|
|
32
33
|
process.exit(1);
|
|
33
34
|
}
|
|
34
35
|
if (outcome.skipped.length > 0) {
|
package/dist/esm/createTags.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { GIT_OUTPUT_LIMIT } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { GIT_OUTPUT_LIMIT, hasErrnoCode } from '@williamthorsen/nmr-core';
|
|
3
3
|
import { deleteFileIfExists } from "./deleteFileIfExists.js";
|
|
4
4
|
import { readReleaseTags, RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
|
|
5
5
|
export function createTags(options) {
|
|
@@ -48,7 +48,7 @@ function assertCleanWorkingTree() {
|
|
|
48
48
|
execFileSync('git', ['diff', '--quiet', '--cached'], { maxBuffer: GIT_OUTPUT_LIMIT });
|
|
49
49
|
}
|
|
50
50
|
catch (error) {
|
|
51
|
-
if (error
|
|
51
|
+
if (hasErrnoCode(error, 'ENOENT')) {
|
|
52
52
|
throw error;
|
|
53
53
|
}
|
|
54
54
|
throw new Error('Working tree is dirty. Commit or stash changes before tagging, or use `--no-git-checks` to skip this check.', { cause: error });
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { unlinkSync } from 'node:fs';
|
|
2
|
+
import { hasErrnoCode } from '@williamthorsen/nmr-core';
|
|
2
3
|
export function deleteFileIfExists(filePath) {
|
|
3
4
|
try {
|
|
4
5
|
unlinkSync(filePath);
|
|
5
6
|
}
|
|
6
7
|
catch (error) {
|
|
7
|
-
if (error
|
|
8
|
+
if (hasErrnoCode(error, 'ENOENT')) {
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
10
11
|
throw error;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { basename } from 'node:path';
|
|
3
|
+
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
3
4
|
import { isRecord } from "./typeGuards.js";
|
|
4
5
|
export function deriveWorkspaceConfig(workspacePath) {
|
|
5
6
|
const dir = basename(workspacePath);
|
|
@@ -9,8 +10,7 @@ export function deriveWorkspaceConfig(workspacePath) {
|
|
|
9
10
|
parsed = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
10
11
|
}
|
|
11
12
|
catch (error) {
|
|
12
|
-
|
|
13
|
-
throw new Error(`Failed to read ${packageJsonPath}: ${message}`, { cause: error });
|
|
13
|
+
throw chainError(`Failed to read ${packageJsonPath}`, error);
|
|
14
14
|
}
|
|
15
15
|
const name = isRecord(parsed) ? parsed['name'] : undefined;
|
|
16
16
|
if (typeof name !== 'string' || name.length === 0) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
4
|
import { glob } from 'glob';
|
|
4
5
|
import { parse } from 'yaml';
|
|
5
6
|
import { isRecord } from "./typeGuards.js";
|
|
@@ -13,7 +14,7 @@ export async function discoverWorkspaces() {
|
|
|
13
14
|
content = readFileSync(workspaceFile, 'utf8');
|
|
14
15
|
}
|
|
15
16
|
catch (error) {
|
|
16
|
-
console.warn(`Warning: Failed to read ${workspaceFile}: ${
|
|
17
|
+
console.warn(`Warning: Failed to read ${workspaceFile}: ${describeError(error)}`);
|
|
17
18
|
return undefined;
|
|
18
19
|
}
|
|
19
20
|
const parsed = parse(content);
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { GIT_OUTPUT_LIMIT } from '@williamthorsen/nmr-core';
|
|
3
|
+
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
3
4
|
const FIELD_SEPARATOR = '\u{1F}';
|
|
4
5
|
function isNoTagError(err) {
|
|
5
6
|
return typeof err === 'object' && err !== null && 'status' in err && err.status === 128;
|
|
6
7
|
}
|
|
7
|
-
function errorMessage(err) {
|
|
8
|
-
return err instanceof Error ? err.message : String(err);
|
|
9
|
-
}
|
|
10
8
|
function findLatestTag(tagPrefixes) {
|
|
11
9
|
if (tagPrefixes.length === 0) {
|
|
12
10
|
throw new Error('findLatestTag: tagPrefixes must contain at least one entry');
|
|
@@ -24,7 +22,7 @@ function findLatestTag(tagPrefixes) {
|
|
|
24
22
|
if (isNoTagError(error)) {
|
|
25
23
|
return undefined;
|
|
26
24
|
}
|
|
27
|
-
throw
|
|
25
|
+
throw chainError(`Failed to run 'git describe'`, error);
|
|
28
26
|
}
|
|
29
27
|
}
|
|
30
28
|
const RELEASE_COMMIT_PREFIX = 'release:';
|
|
@@ -59,7 +57,7 @@ export function getCommitsSinceTarget(tagPrefixes, paths) {
|
|
|
59
57
|
}).trim();
|
|
60
58
|
}
|
|
61
59
|
catch (error) {
|
|
62
|
-
throw
|
|
60
|
+
throw chainError(`Failed to run 'git log' for range '${range}'`, error);
|
|
63
61
|
}
|
|
64
62
|
if (logOutput === '') {
|
|
65
63
|
return { tag, commits: [] };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { printError, printStep, printSuccess, reportWriteResult } from '@williamthorsen/nmr-core';
|
|
2
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
2
3
|
import { hasPackageJson, isGitRepo, usesPnpm } from "./checks.js";
|
|
3
4
|
import { detectRepoType } from "./detectRepoType.js";
|
|
4
5
|
import { scaffoldFiles } from "./scaffold.js";
|
|
@@ -29,7 +30,7 @@ export function initCommand({ dryRun, force, withConfig }) {
|
|
|
29
30
|
eligible = checkEligibility();
|
|
30
31
|
}
|
|
31
32
|
catch (error) {
|
|
32
|
-
printError(`Eligibility check failed: ${
|
|
33
|
+
printError(`Eligibility check failed: ${describeError(error)}`);
|
|
33
34
|
return 1;
|
|
34
35
|
}
|
|
35
36
|
if (!eligible)
|
|
@@ -40,7 +41,7 @@ export function initCommand({ dryRun, force, withConfig }) {
|
|
|
40
41
|
repoType = detectRepoType();
|
|
41
42
|
}
|
|
42
43
|
catch (error) {
|
|
43
|
-
printError(`Failed to detect repo type: ${
|
|
44
|
+
printError(`Failed to detect repo type: ${describeError(error)}`);
|
|
44
45
|
return 1;
|
|
45
46
|
}
|
|
46
47
|
printSuccess(`Detected: ${repoType}`);
|
|
@@ -50,7 +51,7 @@ export function initCommand({ dryRun, force, withConfig }) {
|
|
|
50
51
|
results = scaffoldFiles({ repoType, dryRun, overwrite: force, withConfig });
|
|
51
52
|
}
|
|
52
53
|
catch (error) {
|
|
53
|
-
printError(`Failed to scaffold files: ${
|
|
54
|
+
printError(`Failed to scaffold files: ${describeError(error)}`);
|
|
54
55
|
return 1;
|
|
55
56
|
}
|
|
56
57
|
for (const result of results) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { findPackageRoot, writeFileWithCheck } from '@williamthorsen/nmr-core';
|
|
4
|
+
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
5
|
import { createGithubReleaseWorkflow, publishWorkflow, releaseConfigScript, releaseWorkflow } from "./templates.js";
|
|
5
6
|
export function copyCliffTemplate(dryRun, overwrite) {
|
|
6
7
|
const destPath = '.config/git-cliff.toml';
|
|
@@ -14,7 +15,7 @@ export function copyCliffTemplate(dryRun, overwrite) {
|
|
|
14
15
|
content = readFileSync(templatePath, 'utf8');
|
|
15
16
|
}
|
|
16
17
|
catch (error) {
|
|
17
|
-
const message =
|
|
18
|
+
const message = describeError(error);
|
|
18
19
|
return { filePath: destPath, outcome: 'failed', error: `Failed to read template ${templatePath}: ${message}` };
|
|
19
20
|
}
|
|
20
21
|
return writeFileWithCheck(destPath, content, { dryRun, overwrite });
|