@williamthorsen/release-kit 5.3.2 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +18 -10
- package/dist/esm/.cache +1 -1
- package/dist/esm/bin/release-kit.js +21 -49
- package/dist/esm/checkWorkTypesDrift.js +5 -2
- package/dist/esm/commitCommand.js +2 -9
- package/dist/esm/createGithubReleaseCommand.js +3 -9
- package/dist/esm/help/prepareHelp.d.ts +2 -0
- package/dist/esm/help/prepareHelp.js +30 -0
- package/dist/esm/prepareCommand.d.ts +31 -0
- package/dist/esm/prepareCommand.js +43 -79
- package/dist/esm/publishCommand.js +5 -11
- package/dist/esm/pushCommand.js +3 -9
- package/dist/esm/releasePrepareMono.js +3 -2
- package/dist/esm/resolveCommandTags.js +5 -4
- package/dist/esm/resolveReleaseNotesConfig.js +5 -3
- package/dist/esm/sync-labels/generateCommand.js +5 -4
- package/dist/esm/sync-labels/initCommand.js +3 -2
- package/dist/esm/sync-labels/syncCommand.js +4 -3
- package/dist/esm/syncWorkTypes.js +6 -3
- package/dist/esm/tagCommand.js +3 -10
- package/dist/esm/validateOverridesCommand.js +11 -10
- package/dist/esm/writeReleaseNotesPreviews.js +2 -2
- package/package.json +4 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 6.0.0 — 2026-06-30
|
|
6
|
+
|
|
7
|
+
### 🎉 Features
|
|
8
|
+
|
|
9
|
+
- 🚨 **Breaking:** Auto-activate integration test variant from config presence (#448)
|
|
10
|
+
|
|
11
|
+
A package can now separate its integration tests from its standalone suite simply by including a `vitest.integration.config.ts` (alongside a `vitest.standalone.config.ts`). The `--int-test` flag that previously enabled this is removed — that config-file pairing is now the only way to activate the separation. In such a package, `test` and `test:coverage` run only the standalone suite and skip integration tests, while a new `test:all` runs both suites together. The separation now holds even when tests run across every package at once, so a full-workspace `test` run still keeps integration tests out of the default suite. Packages that previously hand-copied these test scripts no longer need to.
|
|
12
|
+
|
|
13
|
+
### ♻️ Refactoring
|
|
14
|
+
|
|
15
|
+
- Consolidate prepare parsing onto parseArgsOrExit (#441)
|
|
16
|
+
|
|
17
|
+
Makes `release-kit`'s `prepare` command report invalid arguments the same way its other commands already do. The error message and exit code a user sees for a bad argument are unchanged.
|
|
18
|
+
|
|
19
|
+
- Unify prepare help text into a testable module (#443)
|
|
20
|
+
|
|
21
|
+
Running `release-kit prepare --help` now lists the complete set of options. The `--force` flag and the restriction that `--set-version` and `--only` cannot be used when a project block is configured were previously missing from the help and are now shown there; the same `--set-version` restriction was added to the package README's `prepare` reference.
|
|
22
|
+
|
|
23
|
+
### 📚 Documentation
|
|
24
|
+
|
|
25
|
+
- Document single-package --force rejection in release-kit prepare (#450)
|
|
26
|
+
|
|
27
|
+
The `release-kit prepare` `--force` documentation now correctly describes per-mode behavior: a bare `--force` is rejected in a single-package repo, where an explicit `--bump` level must be passed, while the "defaults to patch" shortcut applies only in monorepo and project mode.
|
|
28
|
+
|
|
29
|
+
## 5.4.0 — 2026-06-27
|
|
30
|
+
|
|
31
|
+
### 🎉 Features
|
|
32
|
+
|
|
33
|
+
- Centralize the per-package build as an nmr-compile bin (#419)
|
|
34
|
+
|
|
35
|
+
Introduces `nmr-compile`, a single command shipped with `@williamthorsen/nmr` that compiles each workspace package and now backs the default build. Consuming repos can delete their own per-package build script and pick up future build fixes just by upgrading nmr. Repeated builds with unchanged source now reliably skip recompiling instead of occasionally rebuilding for no reason, and import aliases now resolve correctly in symlinked checkouts.
|
|
36
|
+
|
|
5
37
|
## 5.3.2 — 2026-06-16
|
|
6
38
|
|
|
7
39
|
### ♻️ Refactoring
|
package/README.md
CHANGED
|
@@ -4,7 +4,15 @@ 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 — v6.0.0 (2026-06-30)
|
|
9
|
+
|
|
10
|
+
### 🎉 Features
|
|
11
|
+
|
|
12
|
+
- 🚨 **Breaking:** Auto-activate integration test variant from config presence (#448)
|
|
13
|
+
|
|
14
|
+
A package can now separate its integration tests from its standalone suite simply by including a `vitest.integration.config.ts` (alongside a `vitest.standalone.config.ts`). The `--int-test` flag that previously enabled this is removed — that config-file pairing is now the only way to activate the separation. In such a package, `test` and `test:coverage` run only the standalone suite and skip integration tests, while a new `test:all` runs both suites together. The separation now holds even when tests run across every package at once, so a full-workspace `test` run still keeps integration tests out of the default suite. Packages that previously hand-copied these test scripts no longer need to.
|
|
15
|
+
<!-- /section:release-notes -->
|
|
8
16
|
|
|
9
17
|
## Installation
|
|
10
18
|
|
|
@@ -549,15 +557,15 @@ The first release that ships under the new renderer will produce a one-time nois
|
|
|
549
557
|
|
|
550
558
|
Run release preparation with automatic workspace discovery.
|
|
551
559
|
|
|
552
|
-
| Flag | Description
|
|
553
|
-
| ---------------------------- |
|
|
554
|
-
| `--dry-run` | Preview changes without writing files
|
|
555
|
-
| `--bump=major\|minor\|patch` | Override the bump type for all workspaces
|
|
556
|
-
| `--set-version=X.Y.Z` | Set an explicit canonical semver version; bypasses commit-derived bumps. Requires `--only` in monorepo mode.
|
|
557
|
-
| `--force` | Release even when no commits or no bump-worthy commits exist since the last tag
|
|
558
|
-
| `--only=name1,name2` | Only process the named workspaces (monorepo only; rejected when a `project` block is configured)
|
|
559
|
-
| `--with-release-notes` | Write per-workspace release-notes previews under `{workspacePath}/docs/`
|
|
560
|
-
| `--help`, `-h` | Show help
|
|
560
|
+
| Flag | Description |
|
|
561
|
+
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
562
|
+
| `--dry-run` | Preview changes without writing files |
|
|
563
|
+
| `--bump=major\|minor\|patch` | Override the bump type for all workspaces |
|
|
564
|
+
| `--set-version=X.Y.Z` | Set an explicit canonical semver version; bypasses commit-derived bumps. Requires `--only` in monorepo mode (rejected when a `project` block is configured). |
|
|
565
|
+
| `--force` | Release even when no commits or no bump-worthy commits exist since the last tag. In monorepo/project mode, defaults to patch (combine with `--bump=X` for a different level); in single-package mode, a bare `--force` is rejected — pass `--bump=major\|minor\|patch`. |
|
|
566
|
+
| `--only=name1,name2` | Only process the named workspaces (monorepo only; rejected when a `project` block is configured) |
|
|
567
|
+
| `--with-release-notes` | Write per-workspace release-notes previews under `{workspacePath}/docs/` |
|
|
568
|
+
| `--help`, `-h` | Show help |
|
|
561
569
|
|
|
562
570
|
Workspace names for `--only` match the package directory name (e.g., `arrays`, `release-kit`).
|
|
563
571
|
|
package/dist/esm/.cache
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
5ed41ffb0267b5678fc3b3273d6104a1c1c120fea20f92d48e173aee3ae9e2f5
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { parseArgsOrExit, readPackageVersion, reportError } from "@williamthorsen/nmr-core";
|
|
3
3
|
import { checkWorkTypesDrift } from "../checkWorkTypesDrift.js";
|
|
4
4
|
import { commitCommand } from "../commitCommand.js";
|
|
5
5
|
import { createGithubReleaseCommand } from "../createGithubReleaseCommand.js";
|
|
6
|
+
import { showPrepareHelp } from "../help/prepareHelp.js";
|
|
6
7
|
import { initCommand } from "../init/initCommand.js";
|
|
7
8
|
import { prepareCommand } from "../prepareCommand.js";
|
|
8
9
|
import { publishCommand } from "../publishCommand.js";
|
|
@@ -99,24 +100,6 @@ Options:
|
|
|
99
100
|
--help, -h Show this help message
|
|
100
101
|
`);
|
|
101
102
|
}
|
|
102
|
-
function showPrepareHelp() {
|
|
103
|
-
console.info(`
|
|
104
|
-
Usage: release-kit prepare [options]
|
|
105
|
-
|
|
106
|
-
Run release preparation with automatic workspace discovery.
|
|
107
|
-
|
|
108
|
-
Options:
|
|
109
|
-
--dry-run Run without modifying any files
|
|
110
|
-
--bump=major|minor|patch Override the bump type for all workspaces
|
|
111
|
-
--set-version=X.Y.Z Set an explicit version; bypasses commit-derived bumps. Requires --only in monorepo mode.
|
|
112
|
-
--no-git-checks, -n Skip the clean-working-tree check
|
|
113
|
-
--only=name1,name2 Only process the named workspaces (comma-separated, monorepo only)
|
|
114
|
-
--with-release-notes Also write per-workspace release-notes previews under {workspacePath}/docs/
|
|
115
|
-
(docs/README.v{version}.md and docs/RELEASE_NOTES.v{version}.md).
|
|
116
|
-
Recommended .gitignore entry: packages/*/docs/*.v*.md (or docs/*.v*.md).
|
|
117
|
-
--help, -h Show this help message
|
|
118
|
-
`);
|
|
119
|
-
}
|
|
120
103
|
function showCommitHelp() {
|
|
121
104
|
console.info(`
|
|
122
105
|
Usage: release-kit commit [options]
|
|
@@ -295,7 +278,7 @@ if (command === "commit") {
|
|
|
295
278
|
try {
|
|
296
279
|
commitCommand(flags);
|
|
297
280
|
} catch (error) {
|
|
298
|
-
|
|
281
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
299
282
|
process.exit(1);
|
|
300
283
|
}
|
|
301
284
|
process.exit(0);
|
|
@@ -338,7 +321,7 @@ if (command === "show-tag-prefixes") {
|
|
|
338
321
|
process.exit(0);
|
|
339
322
|
}
|
|
340
323
|
if (flags.length > 0) {
|
|
341
|
-
|
|
324
|
+
reportError(`Unknown option: ${flags[0]}`);
|
|
342
325
|
process.exit(1);
|
|
343
326
|
}
|
|
344
327
|
const exitCode = await showTagPrefixesCommand();
|
|
@@ -354,14 +337,7 @@ if (command === "init") {
|
|
|
354
337
|
force: { long: "--force", type: "boolean" },
|
|
355
338
|
withConfig: { long: "--with-config", type: "boolean" }
|
|
356
339
|
};
|
|
357
|
-
|
|
358
|
-
try {
|
|
359
|
-
parsed = parseArgs(flags, initFlagSchema);
|
|
360
|
-
} catch (error) {
|
|
361
|
-
console.error(`Error: ${translateParseError(error)}`);
|
|
362
|
-
process.exit(1);
|
|
363
|
-
}
|
|
364
|
-
const { dryRun, force, withConfig } = parsed.flags;
|
|
340
|
+
const { dryRun, force, withConfig } = parseArgsOrExit(flags, initFlagSchema).flags;
|
|
365
341
|
const exitCode = initCommand({ dryRun, force, withConfig });
|
|
366
342
|
process.exit(exitCode);
|
|
367
343
|
}
|
|
@@ -381,14 +357,7 @@ if (command === "sync-labels") {
|
|
|
381
357
|
dryRun: { long: "--dry-run", type: "boolean" },
|
|
382
358
|
force: { long: "--force", type: "boolean" }
|
|
383
359
|
};
|
|
384
|
-
|
|
385
|
-
try {
|
|
386
|
-
syncParsed = parseArgs(subflags, syncLabelsInitFlagSchema);
|
|
387
|
-
} catch (error) {
|
|
388
|
-
console.error(`Error: ${translateParseError(error)}`);
|
|
389
|
-
process.exit(1);
|
|
390
|
-
}
|
|
391
|
-
const { dryRun, force } = syncParsed.flags;
|
|
360
|
+
const { dryRun, force } = parseArgsOrExit(subflags, syncLabelsInitFlagSchema).flags;
|
|
392
361
|
const exitCode = await syncLabelsInitCommand({ dryRun, force });
|
|
393
362
|
process.exit(exitCode);
|
|
394
363
|
}
|
|
@@ -398,7 +367,7 @@ if (command === "sync-labels") {
|
|
|
398
367
|
process.exit(0);
|
|
399
368
|
}
|
|
400
369
|
if (subflags.length > 0) {
|
|
401
|
-
|
|
370
|
+
reportError(`Unknown option: ${subflags[0]}`);
|
|
402
371
|
process.exit(1);
|
|
403
372
|
}
|
|
404
373
|
const exitCode = await generateCommand();
|
|
@@ -410,13 +379,13 @@ if (command === "sync-labels") {
|
|
|
410
379
|
process.exit(0);
|
|
411
380
|
}
|
|
412
381
|
if (subflags.length > 0) {
|
|
413
|
-
|
|
382
|
+
reportError(`Unknown option: ${subflags[0]}`);
|
|
414
383
|
process.exit(1);
|
|
415
384
|
}
|
|
416
385
|
const exitCode = syncLabelsCommand();
|
|
417
386
|
process.exit(exitCode);
|
|
418
387
|
}
|
|
419
|
-
|
|
388
|
+
reportError(`Unknown subcommand: ${subcommand}`);
|
|
420
389
|
showSyncLabelsHelp();
|
|
421
390
|
process.exit(1);
|
|
422
391
|
}
|
|
@@ -433,18 +402,19 @@ if (command === "overrides") {
|
|
|
433
402
|
process.exit(0);
|
|
434
403
|
}
|
|
435
404
|
if (subflags.length > 0) {
|
|
436
|
-
|
|
405
|
+
reportError(`Unknown option: ${subflags[0]}`);
|
|
437
406
|
process.exit(1);
|
|
438
407
|
}
|
|
439
408
|
const result = await validateOverridesCommand();
|
|
440
409
|
if (result.exitCode === 0) {
|
|
441
410
|
console.info(result.message);
|
|
442
411
|
} else {
|
|
443
|
-
|
|
412
|
+
process.stderr.write(`${result.message}
|
|
413
|
+
`);
|
|
444
414
|
}
|
|
445
415
|
process.exit(result.exitCode);
|
|
446
416
|
}
|
|
447
|
-
|
|
417
|
+
reportError(`Unknown subcommand: ${subcommand}`);
|
|
448
418
|
showOverridesHelp();
|
|
449
419
|
process.exit(1);
|
|
450
420
|
}
|
|
@@ -461,14 +431,15 @@ if (command === "work-types") {
|
|
|
461
431
|
process.exit(0);
|
|
462
432
|
}
|
|
463
433
|
if (subflags.length > 0) {
|
|
464
|
-
|
|
434
|
+
reportError(`Unknown option: ${subflags[0]}`);
|
|
465
435
|
process.exit(1);
|
|
466
436
|
}
|
|
467
437
|
const result = await checkWorkTypesDrift();
|
|
468
438
|
if (result.exitCode === 0) {
|
|
469
439
|
console.info(result.message);
|
|
470
440
|
} else {
|
|
471
|
-
|
|
441
|
+
process.stderr.write(`${result.message}
|
|
442
|
+
`);
|
|
472
443
|
}
|
|
473
444
|
process.exit(result.exitCode);
|
|
474
445
|
}
|
|
@@ -478,21 +449,22 @@ if (command === "work-types") {
|
|
|
478
449
|
process.exit(0);
|
|
479
450
|
}
|
|
480
451
|
if (subflags.length > 0) {
|
|
481
|
-
|
|
452
|
+
reportError(`Unknown option: ${subflags[0]}`);
|
|
482
453
|
process.exit(1);
|
|
483
454
|
}
|
|
484
455
|
const result = await syncWorkTypes();
|
|
485
456
|
if (result.exitCode === 0) {
|
|
486
457
|
console.info(result.message);
|
|
487
458
|
} else {
|
|
488
|
-
|
|
459
|
+
process.stderr.write(`${result.message}
|
|
460
|
+
`);
|
|
489
461
|
}
|
|
490
462
|
process.exit(result.exitCode);
|
|
491
463
|
}
|
|
492
|
-
|
|
464
|
+
reportError(`Unknown subcommand: ${subcommand}`);
|
|
493
465
|
showWorkTypesHelp();
|
|
494
466
|
process.exit(1);
|
|
495
467
|
}
|
|
496
|
-
|
|
468
|
+
reportError(`Unknown command: ${command}`);
|
|
497
469
|
showUsage();
|
|
498
470
|
process.exit(1);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { formatErrorLine } from "@williamthorsen/nmr-core";
|
|
4
5
|
import { buildFetchInit, errorMessage, hasExpectedTopLevelShape } from "./workTypesUtils.js";
|
|
5
6
|
const UPSTREAM_WORK_TYPES_URL = "https://raw.githubusercontent.com/williamthorsen/codeassembly/main/packages/agents/content/skills/_data/work-types.json";
|
|
6
7
|
function resolveDefaultLocalPath() {
|
|
@@ -28,7 +29,7 @@ async function checkWorkTypesDrift(dependencies = {}) {
|
|
|
28
29
|
} catch (error) {
|
|
29
30
|
return {
|
|
30
31
|
exitCode: 2,
|
|
31
|
-
message: `
|
|
32
|
+
message: formatErrorLine(`Failed to fetch upstream work-types.json: ${errorMessage(error)}`)
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
if (response.status === 404) {
|
|
@@ -40,7 +41,9 @@ async function checkWorkTypesDrift(dependencies = {}) {
|
|
|
40
41
|
if (!response.ok) {
|
|
41
42
|
return {
|
|
42
43
|
exitCode: 2,
|
|
43
|
-
message:
|
|
44
|
+
message: formatErrorLine(
|
|
45
|
+
`Failed to fetch upstream work-types.json: HTTP ${response.status} ${response.statusText}`
|
|
46
|
+
)
|
|
44
47
|
};
|
|
45
48
|
}
|
|
46
49
|
const upstreamText = await response.text();
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
-
import {
|
|
3
|
+
import { parseArgsOrExit } from "@williamthorsen/nmr-core";
|
|
4
4
|
import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./prepareCommand.js";
|
|
5
5
|
const commitFlagSchema = {
|
|
6
6
|
dryRun: { long: "--dry-run", type: "boolean" }
|
|
7
7
|
};
|
|
8
8
|
function commitCommand(argv) {
|
|
9
|
-
|
|
10
|
-
try {
|
|
11
|
-
parsed = parseArgs(argv, commitFlagSchema);
|
|
12
|
-
} catch (error) {
|
|
13
|
-
console.error(`Error: ${translateParseError(error)}`);
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
16
|
-
const dryRun = parsed.flags.dryRun;
|
|
9
|
+
const dryRun = parseArgsOrExit(argv, commitFlagSchema).flags.dryRun;
|
|
17
10
|
let tagsContent;
|
|
18
11
|
try {
|
|
19
12
|
tagsContent = readFileSync(RELEASE_TAGS_FILE, "utf8");
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseArgsOrExit, reportError } from "@williamthorsen/nmr-core";
|
|
2
2
|
import { createGithubReleases } from "./createGithubRelease.js";
|
|
3
3
|
import { parseRequestedTags } from "./parseRequestedTags.js";
|
|
4
4
|
import { resolveCommandTags } from "./resolveCommandTags.js";
|
|
@@ -8,13 +8,7 @@ const createGithubReleaseFlagSchema = {
|
|
|
8
8
|
tags: { long: "--tags", type: "string" }
|
|
9
9
|
};
|
|
10
10
|
async function createGithubReleaseCommand(argv) {
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
parsed = parseArgs(argv, createGithubReleaseFlagSchema);
|
|
14
|
-
} catch (error) {
|
|
15
|
-
console.error(`Error: ${translateParseError(error)}`);
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
11
|
+
const parsed = parseArgsOrExit(argv, createGithubReleaseFlagSchema);
|
|
18
12
|
const { dryRun } = parsed.flags;
|
|
19
13
|
const requestedTags = parseRequestedTags(parsed.flags.tags);
|
|
20
14
|
const resolvedTags = await resolveCommandTags(requestedTags);
|
|
@@ -23,7 +17,7 @@ async function createGithubReleaseCommand(argv) {
|
|
|
23
17
|
try {
|
|
24
18
|
outcome = createGithubReleases(resolvedTags, changelogJsonOutputPath, dryRun, sectionOrder);
|
|
25
19
|
} catch (error) {
|
|
26
|
-
|
|
20
|
+
reportError(`Failed to create GitHub Releases: ${error instanceof Error ? error.message : String(error)}`);
|
|
27
21
|
process.exit(1);
|
|
28
22
|
}
|
|
29
23
|
if (outcome.skipped.length > 0) {
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const prepareHelpText = "\nUsage: release-kit prepare [options]\n\nRun release preparation with automatic workspace discovery.\n\nOptions:\n --dry-run Run without modifying any files\n --bump=major|minor|patch Override the bump type for all workspaces\n --set-version=X.Y.Z Set an explicit version; bypasses commit-derived bumps.\n Requires --only in monorepo mode (rejected when a 'project' block is configured).\n --force Release even when no commits or no bump-worthy commits exist\n since the last tag. In monorepo and project mode, defaults to\n patch when --bump is not given; use --bump=X for a different\n level. In single-package mode, a bare --force is rejected \u2014\n pass --bump=major|minor|patch.\n --no-git-checks, -n Skip the clean-working-tree check\n --only=name1,name2 Only process the named workspaces (comma-separated, monorepo only;\n rejected when a 'project' block is configured)\n --with-release-notes Also write per-workspace release-notes previews under {workspacePath}/docs/\n (docs/README.v{version}.md and docs/RELEASE_NOTES.v{version}.md).\n Recommended .gitignore entry: packages/*/docs/*.v*.md (or docs/*.v*.md).\n --help, -h Show this help message\n";
|
|
2
|
+
export declare function showPrepareHelp(): void;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const prepareHelpText = `
|
|
2
|
+
Usage: release-kit prepare [options]
|
|
3
|
+
|
|
4
|
+
Run release preparation with automatic workspace discovery.
|
|
5
|
+
|
|
6
|
+
Options:
|
|
7
|
+
--dry-run Run without modifying any files
|
|
8
|
+
--bump=major|minor|patch Override the bump type for all workspaces
|
|
9
|
+
--set-version=X.Y.Z Set an explicit version; bypasses commit-derived bumps.
|
|
10
|
+
Requires --only in monorepo mode (rejected when a 'project' block is configured).
|
|
11
|
+
--force Release even when no commits or no bump-worthy commits exist
|
|
12
|
+
since the last tag. In monorepo and project mode, defaults to
|
|
13
|
+
patch when --bump is not given; use --bump=X for a different
|
|
14
|
+
level. In single-package mode, a bare --force is rejected \u2014
|
|
15
|
+
pass --bump=major|minor|patch.
|
|
16
|
+
--no-git-checks, -n Skip the clean-working-tree check
|
|
17
|
+
--only=name1,name2 Only process the named workspaces (comma-separated, monorepo only;
|
|
18
|
+
rejected when a 'project' block is configured)
|
|
19
|
+
--with-release-notes Also write per-workspace release-notes previews under {workspacePath}/docs/
|
|
20
|
+
(docs/README.v{version}.md and docs/RELEASE_NOTES.v{version}.md).
|
|
21
|
+
Recommended .gitignore entry: packages/*/docs/*.v*.md (or docs/*.v*.md).
|
|
22
|
+
--help, -h Show this help message
|
|
23
|
+
`;
|
|
24
|
+
function showPrepareHelp() {
|
|
25
|
+
console.info(prepareHelpText);
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
prepareHelpText,
|
|
29
|
+
showPrepareHelp
|
|
30
|
+
};
|
|
@@ -2,6 +2,37 @@ import type { WriteResult } from '@williamthorsen/nmr-core';
|
|
|
2
2
|
import type { ReleaseType } from './types.ts';
|
|
3
3
|
export declare const RELEASE_TAGS_FILE = "tmp/.release-tags";
|
|
4
4
|
export declare const RELEASE_SUMMARY_FILE = "tmp/.release-summary";
|
|
5
|
+
export declare const prepareFlagSchema: {
|
|
6
|
+
dryRun: {
|
|
7
|
+
long: string;
|
|
8
|
+
type: "boolean";
|
|
9
|
+
};
|
|
10
|
+
force: {
|
|
11
|
+
long: string;
|
|
12
|
+
type: "boolean";
|
|
13
|
+
};
|
|
14
|
+
noGitChecks: {
|
|
15
|
+
long: string;
|
|
16
|
+
type: "boolean";
|
|
17
|
+
short: string;
|
|
18
|
+
};
|
|
19
|
+
bump: {
|
|
20
|
+
long: string;
|
|
21
|
+
type: "string";
|
|
22
|
+
};
|
|
23
|
+
setVersion: {
|
|
24
|
+
long: string;
|
|
25
|
+
type: "string";
|
|
26
|
+
};
|
|
27
|
+
only: {
|
|
28
|
+
long: string;
|
|
29
|
+
type: "string";
|
|
30
|
+
};
|
|
31
|
+
withReleaseNotes: {
|
|
32
|
+
long: string;
|
|
33
|
+
type: "boolean";
|
|
34
|
+
};
|
|
35
|
+
};
|
|
5
36
|
export declare function parseArgs(argv: string[]): {
|
|
6
37
|
dryRun: boolean;
|
|
7
38
|
force: boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseArgsOrExit, reportError, writeFileWithCheck } from "@williamthorsen/nmr-core";
|
|
2
2
|
import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
|
|
3
3
|
import { buildDependencyGraph } from "./buildDependencyGraph.js";
|
|
4
4
|
import { buildReleaseSummary } from "./buildReleaseSummary.js";
|
|
@@ -18,27 +18,6 @@ const CANONICAL_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
|
18
18
|
function isReleaseType(value) {
|
|
19
19
|
return VALID_BUMP_TYPES.includes(value);
|
|
20
20
|
}
|
|
21
|
-
function showHelp() {
|
|
22
|
-
console.info(`
|
|
23
|
-
Usage: npx @williamthorsen/release-kit prepare [options]
|
|
24
|
-
|
|
25
|
-
Options:
|
|
26
|
-
--dry-run Run without modifying any files
|
|
27
|
-
--bump=major|minor|patch Override the bump type for all workspaces
|
|
28
|
-
--set-version=X.Y.Z Set an explicit version; bypasses commit-derived bumps.
|
|
29
|
-
Requires --only in monorepo mode (rejected when a 'project' block is configured).
|
|
30
|
-
--force Release even when no commits or no bump-worthy commits exist
|
|
31
|
-
since the last tag. Defaults to patch when --bump is not given;
|
|
32
|
-
use --bump=X to release at a different level.
|
|
33
|
-
--no-git-checks, -n Skip the clean-working-tree check
|
|
34
|
-
--only=name1,name2 Only process the named workspaces (comma-separated, monorepo only;
|
|
35
|
-
rejected when a 'project' block is configured)
|
|
36
|
-
--with-release-notes Also write per-workspace release-notes previews under {workspacePath}/docs/
|
|
37
|
-
(docs/README.v{version}.md and docs/RELEASE_NOTES.v{version}.md).
|
|
38
|
-
Recommended .gitignore entry: packages/*/docs/*.v*.md (or docs/*.v*.md).
|
|
39
|
-
--help Show this help message
|
|
40
|
-
`);
|
|
41
|
-
}
|
|
42
21
|
const prepareFlagSchema = {
|
|
43
22
|
dryRun: { long: "--dry-run", type: "boolean" },
|
|
44
23
|
force: { long: "--force", type: "boolean" },
|
|
@@ -50,34 +29,25 @@ const prepareFlagSchema = {
|
|
|
50
29
|
bump: { long: "--bump", type: "string" },
|
|
51
30
|
setVersion: { long: "--set-version", type: "string" },
|
|
52
31
|
only: { long: "--only", type: "string" },
|
|
53
|
-
withReleaseNotes: { long: "--with-release-notes", type: "boolean" }
|
|
54
|
-
help: { long: "--help", type: "boolean", short: "-h" }
|
|
32
|
+
withReleaseNotes: { long: "--with-release-notes", type: "boolean" }
|
|
55
33
|
};
|
|
56
34
|
function parseArgs(argv) {
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
parsed = coreParseArgs(argv, prepareFlagSchema);
|
|
60
|
-
} catch (error) {
|
|
61
|
-
throw new Error(translateParseError(error));
|
|
62
|
-
}
|
|
63
|
-
const { flags } = parsed;
|
|
64
|
-
if (flags.help) {
|
|
65
|
-
showHelp();
|
|
66
|
-
process.exit(0);
|
|
67
|
-
}
|
|
35
|
+
const { flags } = parseArgsOrExit(argv, prepareFlagSchema);
|
|
68
36
|
let bumpOverride;
|
|
69
37
|
if (flags.bump !== void 0) {
|
|
70
38
|
if (!isReleaseType(flags.bump)) {
|
|
71
|
-
|
|
39
|
+
reportError(`Invalid bump type "${flags.bump}". Must be one of: ${VALID_BUMP_TYPES.join(", ")}`);
|
|
40
|
+
process.exit(1);
|
|
72
41
|
}
|
|
73
42
|
bumpOverride = flags.bump;
|
|
74
43
|
}
|
|
75
44
|
let setVersion;
|
|
76
45
|
if (flags.setVersion !== void 0) {
|
|
77
46
|
if (!CANONICAL_SEMVER_PATTERN.test(flags.setVersion)) {
|
|
78
|
-
|
|
47
|
+
reportError(
|
|
79
48
|
`Invalid --set-version value "${flags.setVersion}". Must be canonical semver (N.N.N, no pre-release suffix).`
|
|
80
49
|
);
|
|
50
|
+
process.exit(1);
|
|
81
51
|
}
|
|
82
52
|
setVersion = flags.setVersion;
|
|
83
53
|
}
|
|
@@ -86,10 +56,12 @@ function parseArgs(argv) {
|
|
|
86
56
|
only = flags.only.split(",");
|
|
87
57
|
}
|
|
88
58
|
if (setVersion !== void 0 && bumpOverride !== void 0) {
|
|
89
|
-
|
|
59
|
+
reportError("--set-version cannot be combined with --bump");
|
|
60
|
+
process.exit(1);
|
|
90
61
|
}
|
|
91
62
|
if (setVersion !== void 0 && flags.force) {
|
|
92
|
-
|
|
63
|
+
reportError("--set-version cannot be combined with --force");
|
|
64
|
+
process.exit(1);
|
|
93
65
|
}
|
|
94
66
|
return {
|
|
95
67
|
dryRun: flags.dryRun,
|
|
@@ -111,19 +83,7 @@ function writeReleaseTags(tags, dryRun) {
|
|
|
111
83
|
});
|
|
112
84
|
}
|
|
113
85
|
async function prepareCommand(argv) {
|
|
114
|
-
|
|
115
|
-
let force;
|
|
116
|
-
let noGitChecks;
|
|
117
|
-
let bumpOverride;
|
|
118
|
-
let only;
|
|
119
|
-
let setVersion;
|
|
120
|
-
let withReleaseNotes;
|
|
121
|
-
try {
|
|
122
|
-
({ dryRun, force, noGitChecks, bumpOverride, only, setVersion, withReleaseNotes } = parseArgs(argv));
|
|
123
|
-
} catch (error) {
|
|
124
|
-
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
86
|
+
const { dryRun, force, noGitChecks, bumpOverride, only, setVersion, withReleaseNotes } = parseArgs(argv);
|
|
127
87
|
const options = {
|
|
128
88
|
dryRun,
|
|
129
89
|
force,
|
|
@@ -138,7 +98,7 @@ async function prepareCommand(argv) {
|
|
|
138
98
|
try {
|
|
139
99
|
assertCleanWorkingTree();
|
|
140
100
|
} catch (error) {
|
|
141
|
-
|
|
101
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
142
102
|
process.exit(1);
|
|
143
103
|
}
|
|
144
104
|
}
|
|
@@ -147,7 +107,7 @@ async function prepareCommand(argv) {
|
|
|
147
107
|
try {
|
|
148
108
|
discoveredPaths = await discoverWorkspaces();
|
|
149
109
|
} catch (error) {
|
|
150
|
-
|
|
110
|
+
reportError(`Failed to discover workspaces: ${error instanceof Error ? error.message : String(error)}`);
|
|
151
111
|
process.exit(1);
|
|
152
112
|
}
|
|
153
113
|
if (discoveredPaths === void 0) {
|
|
@@ -158,12 +118,12 @@ async function prepareCommand(argv) {
|
|
|
158
118
|
}
|
|
159
119
|
function runSinglePackageMode(userConfig, options, only, dryRun) {
|
|
160
120
|
if (only !== void 0) {
|
|
161
|
-
|
|
121
|
+
reportError("--only is only supported for monorepo configurations");
|
|
162
122
|
process.exit(1);
|
|
163
123
|
}
|
|
164
124
|
if (options.force && options.bumpOverride === void 0) {
|
|
165
|
-
|
|
166
|
-
"
|
|
125
|
+
reportError(
|
|
126
|
+
"--force without --bump is only supported for monorepo configurations. Use --bump=major|minor|patch to set the level for a single-package release."
|
|
167
127
|
);
|
|
168
128
|
process.exit(1);
|
|
169
129
|
}
|
|
@@ -176,18 +136,18 @@ function runMonorepoMode(discoveredPaths, userConfig, options, only, setVersion,
|
|
|
176
136
|
const rootPackage = readRootPackageVersion();
|
|
177
137
|
config = mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage);
|
|
178
138
|
} catch (error) {
|
|
179
|
-
|
|
139
|
+
reportError(`Failed to resolve workspaces: ${error instanceof Error ? error.message : String(error)}`);
|
|
180
140
|
process.exit(1);
|
|
181
141
|
}
|
|
182
142
|
if (setVersion !== void 0 && config.project !== void 0) {
|
|
183
|
-
|
|
184
|
-
"
|
|
143
|
+
reportError(
|
|
144
|
+
"--set-version cannot be combined with a project release. --set-version operates on a single workspace; a project release rolls up every contributing workspace. To use --set-version, run on a config without a `project` block."
|
|
185
145
|
);
|
|
186
146
|
process.exit(1);
|
|
187
147
|
}
|
|
188
148
|
if (only !== void 0 && config.project !== void 0) {
|
|
189
|
-
|
|
190
|
-
"
|
|
149
|
+
reportError(
|
|
150
|
+
"--only cannot be combined with a project release. To release a single workspace, use a config without a `project` block, or run a full `prepare` (no --only) to include the project release."
|
|
191
151
|
);
|
|
192
152
|
process.exit(1);
|
|
193
153
|
}
|
|
@@ -195,7 +155,7 @@ function runMonorepoMode(discoveredPaths, userConfig, options, only, setVersion,
|
|
|
195
155
|
const knownNames = config.workspaces.map((w) => w.dir);
|
|
196
156
|
for (const name of only) {
|
|
197
157
|
if (!knownNames.includes(name)) {
|
|
198
|
-
|
|
158
|
+
reportError(`Unknown workspace "${name}". Known workspaces: ${knownNames.join(", ")}`);
|
|
199
159
|
process.exit(1);
|
|
200
160
|
}
|
|
201
161
|
}
|
|
@@ -209,26 +169,27 @@ function runMonorepoMode(discoveredPaths, userConfig, options, only, setVersion,
|
|
|
209
169
|
return { has: result.commits.length > 0, tag: result.tag };
|
|
210
170
|
});
|
|
211
171
|
if (violations !== void 0) {
|
|
212
|
-
|
|
213
|
-
|
|
172
|
+
reportError("--only excludes packages with changes that would be stranded by the release.");
|
|
173
|
+
process.stderr.write("The following packages must be added to --only or have their dependencies removed:\n");
|
|
214
174
|
for (const violation of violations) {
|
|
215
175
|
const since = violation.tag ?? "the beginning";
|
|
216
|
-
|
|
176
|
+
process.stderr.write(
|
|
177
|
+
` - ${violation.dir} (downstream of ${violation.downstreamOf}; has commits since ${since})
|
|
178
|
+
`
|
|
179
|
+
);
|
|
217
180
|
}
|
|
218
|
-
|
|
181
|
+
process.stderr.write("Alternatively, run `release-kit prepare` without --only to release everything.\n");
|
|
219
182
|
process.exit(1);
|
|
220
183
|
}
|
|
221
184
|
config.workspaces = config.workspaces.filter((w) => only.includes(w.dir));
|
|
222
185
|
}
|
|
223
186
|
if (setVersion !== void 0) {
|
|
224
187
|
if (only === void 0) {
|
|
225
|
-
|
|
188
|
+
reportError("--set-version requires --only in monorepo mode");
|
|
226
189
|
process.exit(1);
|
|
227
190
|
}
|
|
228
191
|
if (config.workspaces.length !== 1) {
|
|
229
|
-
|
|
230
|
-
`Error: --set-version requires --only to match exactly one workspace; matched ${config.workspaces.length}`
|
|
231
|
-
);
|
|
192
|
+
reportError(`--set-version requires --only to match exactly one workspace; matched ${config.workspaces.length}`);
|
|
232
193
|
process.exit(1);
|
|
233
194
|
}
|
|
234
195
|
}
|
|
@@ -239,7 +200,7 @@ async function loadAndValidateConfig() {
|
|
|
239
200
|
try {
|
|
240
201
|
rawConfig = await loadConfig();
|
|
241
202
|
} catch (error) {
|
|
242
|
-
|
|
203
|
+
reportError(`Failed to load config: ${error instanceof Error ? error.message : String(error)}`);
|
|
243
204
|
process.exit(1);
|
|
244
205
|
}
|
|
245
206
|
if (rawConfig === void 0) {
|
|
@@ -247,9 +208,10 @@ async function loadAndValidateConfig() {
|
|
|
247
208
|
}
|
|
248
209
|
const { config, errors, warnings } = validateConfig(rawConfig);
|
|
249
210
|
if (errors.length > 0) {
|
|
250
|
-
|
|
211
|
+
process.stderr.write("Invalid config:\n");
|
|
251
212
|
for (const err of errors) {
|
|
252
|
-
|
|
213
|
+
process.stderr.write(` \u274C ${err}
|
|
214
|
+
`);
|
|
253
215
|
}
|
|
254
216
|
process.exit(1);
|
|
255
217
|
}
|
|
@@ -263,13 +225,13 @@ function runAndReport(execute, dryRun) {
|
|
|
263
225
|
try {
|
|
264
226
|
result = execute();
|
|
265
227
|
} catch (error) {
|
|
266
|
-
|
|
228
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
267
229
|
process.exit(1);
|
|
268
230
|
}
|
|
269
231
|
process.stdout.write(reportPrepare(result) + "\n");
|
|
270
232
|
const writeResult = writeReleaseTags(result.tags, dryRun);
|
|
271
233
|
if (writeResult?.outcome === "failed") {
|
|
272
|
-
|
|
234
|
+
reportError(`Failed to write release tags: ${writeResult.error ?? "unknown error"}`);
|
|
273
235
|
process.exit(1);
|
|
274
236
|
}
|
|
275
237
|
if (writeResult) {
|
|
@@ -288,7 +250,7 @@ function runAndReport(execute, dryRun) {
|
|
|
288
250
|
overwrite: true
|
|
289
251
|
});
|
|
290
252
|
if (summaryResult.outcome === "failed") {
|
|
291
|
-
|
|
253
|
+
reportError(`Failed to write release summary: ${summaryResult.error ?? "unknown error"}`);
|
|
292
254
|
process.exit(1);
|
|
293
255
|
}
|
|
294
256
|
if (dryRun) {
|
|
@@ -298,8 +260,9 @@ function runAndReport(execute, dryRun) {
|
|
|
298
260
|
}
|
|
299
261
|
}
|
|
300
262
|
if (writeResult && !dryRun) {
|
|
301
|
-
|
|
302
|
-
Run 'release-kit commit' to create the release commit
|
|
263
|
+
process.stderr.write(`
|
|
264
|
+
Run 'release-kit commit' to create the release commit.
|
|
265
|
+
`);
|
|
303
266
|
}
|
|
304
267
|
}
|
|
305
268
|
export {
|
|
@@ -307,5 +270,6 @@ export {
|
|
|
307
270
|
RELEASE_TAGS_FILE,
|
|
308
271
|
parseArgs,
|
|
309
272
|
prepareCommand,
|
|
273
|
+
prepareFlagSchema,
|
|
310
274
|
writeReleaseTags
|
|
311
275
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { parseArgsOrExit, reportError } from "@williamthorsen/nmr-core";
|
|
4
4
|
import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
|
|
5
5
|
import { detectPackageManager } from "./detectPackageManager.js";
|
|
6
6
|
import { injectReleaseNotesIntoReadme, resolveReadmePath } from "./injectReleaseNotesIntoReadme.js";
|
|
@@ -15,19 +15,13 @@ const publishFlagSchema = {
|
|
|
15
15
|
tags: { long: "--tags", type: "string" }
|
|
16
16
|
};
|
|
17
17
|
async function publishCommand(argv) {
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
parsed = parseArgs(argv, publishFlagSchema);
|
|
21
|
-
} catch (error) {
|
|
22
|
-
console.error(`Error: ${translateParseError(error)}`);
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
18
|
+
const parsed = parseArgsOrExit(argv, publishFlagSchema);
|
|
25
19
|
const { dryRun, noGitChecks, provenance } = parsed.flags;
|
|
26
20
|
if (!dryRun && !noGitChecks) {
|
|
27
21
|
try {
|
|
28
22
|
assertCleanWorkingTree();
|
|
29
23
|
} catch (error) {
|
|
30
|
-
|
|
24
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
31
25
|
process.exit(1);
|
|
32
26
|
}
|
|
33
27
|
}
|
|
@@ -80,7 +74,7 @@ async function publishCommand(argv) {
|
|
|
80
74
|
console.warn(` ${t}`);
|
|
81
75
|
}
|
|
82
76
|
}
|
|
83
|
-
|
|
77
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
84
78
|
process.exit(1);
|
|
85
79
|
}
|
|
86
80
|
}
|
|
@@ -92,7 +86,7 @@ function filterPublishableTags(resolvedTags, isExplicit) {
|
|
|
92
86
|
}
|
|
93
87
|
if (isExplicit && unpublishable.length > 0) {
|
|
94
88
|
for (const { tag, workspacePath } of unpublishable) {
|
|
95
|
-
|
|
89
|
+
reportError(`${tag} (${workspacePath}) cannot be published: package.json#private is true.`);
|
|
96
90
|
}
|
|
97
91
|
process.exit(1);
|
|
98
92
|
}
|
package/dist/esm/pushCommand.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseArgsOrExit, reportError } from "@williamthorsen/nmr-core";
|
|
2
2
|
import { parseRequestedTags } from "./parseRequestedTags.js";
|
|
3
3
|
import { pushRelease } from "./pushRelease.js";
|
|
4
4
|
import { resolveCommandTags } from "./resolveCommandTags.js";
|
|
@@ -8,13 +8,7 @@ const pushFlagSchema = {
|
|
|
8
8
|
tagsOnly: { long: "--tags-only", type: "boolean" }
|
|
9
9
|
};
|
|
10
10
|
async function pushCommand(argv) {
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
parsed = parseArgs(argv, pushFlagSchema);
|
|
14
|
-
} catch (error) {
|
|
15
|
-
console.error(`Error: ${translateParseError(error)}`);
|
|
16
|
-
process.exit(1);
|
|
17
|
-
}
|
|
11
|
+
const parsed = parseArgsOrExit(argv, pushFlagSchema);
|
|
18
12
|
const { dryRun, tagsOnly } = parsed.flags;
|
|
19
13
|
const requestedTags = parseRequestedTags(parsed.flags.tags);
|
|
20
14
|
const resolvedTags = await resolveCommandTags(requestedTags);
|
|
@@ -38,7 +32,7 @@ async function pushCommand(argv) {
|
|
|
38
32
|
}
|
|
39
33
|
}
|
|
40
34
|
} catch (error) {
|
|
41
|
-
|
|
35
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
42
36
|
process.exit(1);
|
|
43
37
|
}
|
|
44
38
|
}
|
|
@@ -487,8 +487,9 @@ function maybeEmitBaselineHint(workspace, knownPrefixes, state) {
|
|
|
487
487
|
if (candidates.length === 0) return;
|
|
488
488
|
const totalTags = candidates.reduce((sum, candidate) => sum + candidate.tagCount, 0);
|
|
489
489
|
const example = candidates[0]?.exampleTags[0] ?? `${candidates[0]?.prefix ?? ""}?`;
|
|
490
|
-
|
|
491
|
-
`Hint: no baseline tag found for ${workspace.dir} under '${workspace.tagPrefix}', but ${totalTags} candidate-shaped tags exist (e.g., ${example}). Run 'release-kit show-tag-prefixes' to check for undeclared legacy prefixes
|
|
490
|
+
process.stderr.write(
|
|
491
|
+
`Hint: no baseline tag found for ${workspace.dir} under '${workspace.tagPrefix}', but ${totalTags} candidate-shaped tags exist (e.g., ${example}). Run 'release-kit show-tag-prefixes' to check for undeclared legacy prefixes.
|
|
492
|
+
`
|
|
492
493
|
);
|
|
493
494
|
state.emitted = true;
|
|
494
495
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reportError } from "@williamthorsen/nmr-core";
|
|
1
2
|
import { deriveWorkspaceConfig } from "./deriveWorkspaceConfig.js";
|
|
2
3
|
import { discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
3
4
|
import { resolveReleaseTags } from "./resolveReleaseTags.js";
|
|
@@ -6,7 +7,7 @@ async function resolveCommandTags(tags) {
|
|
|
6
7
|
try {
|
|
7
8
|
discoveredPaths = await discoverWorkspaces();
|
|
8
9
|
} catch (error) {
|
|
9
|
-
|
|
10
|
+
reportError(`Failed to discover workspaces: ${error instanceof Error ? error.message : String(error)}`);
|
|
10
11
|
process.exit(1);
|
|
11
12
|
}
|
|
12
13
|
let workspaces;
|
|
@@ -18,7 +19,7 @@ async function resolveCommandTags(tags) {
|
|
|
18
19
|
workspaces = discoveredPaths.map((workspacePath) => deriveWorkspaceConfig(workspacePath));
|
|
19
20
|
}
|
|
20
21
|
} catch (error) {
|
|
21
|
-
|
|
22
|
+
reportError(`Failed to resolve workspaces: ${error instanceof Error ? error.message : String(error)}`);
|
|
22
23
|
process.exit(1);
|
|
23
24
|
}
|
|
24
25
|
let resolvedTags;
|
|
@@ -30,14 +31,14 @@ async function resolveCommandTags(tags) {
|
|
|
30
31
|
throw new Error("resolveCommandTags: invariant violated \u2014 neither workspaces nor singleWorkspace was derived");
|
|
31
32
|
}
|
|
32
33
|
if (resolvedTags.length === 0) {
|
|
33
|
-
|
|
34
|
+
reportError("No release tags found on HEAD. Create tags with `release-kit tag` first.");
|
|
34
35
|
process.exit(1);
|
|
35
36
|
}
|
|
36
37
|
if (tags !== void 0) {
|
|
37
38
|
const availableTagNames = resolvedTags.map((t) => t.tag);
|
|
38
39
|
for (const name of tags) {
|
|
39
40
|
if (!availableTagNames.includes(name)) {
|
|
40
|
-
|
|
41
|
+
reportError(`Unknown tag "${name}" in --tags. Available: ${availableTagNames.join(", ")}`);
|
|
41
42
|
process.exit(1);
|
|
42
43
|
}
|
|
43
44
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reportError } from "@williamthorsen/nmr-core";
|
|
1
2
|
import { DEFAULT_CHANGELOG_JSON_CONFIG, DEFAULT_RELEASE_NOTES_CONFIG } from "./defaults.js";
|
|
2
3
|
import { loadConfig, resolveWorkTypes } from "./loadConfig.js";
|
|
3
4
|
import { validateConfig } from "./validateConfig.js";
|
|
@@ -9,7 +10,7 @@ async function resolveReleaseNotesConfig(options = {}) {
|
|
|
9
10
|
} catch (error) {
|
|
10
11
|
const message = error instanceof Error ? error.message : String(error);
|
|
11
12
|
if (strictLoad) {
|
|
12
|
-
|
|
13
|
+
reportError(`Failed to load config: ${message}`);
|
|
13
14
|
process.exit(1);
|
|
14
15
|
}
|
|
15
16
|
console.warn(`Warning: failed to load config; using defaults: ${message}`);
|
|
@@ -23,9 +24,10 @@ async function resolveReleaseNotesConfig(options = {}) {
|
|
|
23
24
|
}
|
|
24
25
|
const { config, errors, warnings } = validateConfig(rawConfig);
|
|
25
26
|
if (errors.length > 0) {
|
|
26
|
-
|
|
27
|
+
process.stderr.write("Invalid config:\n");
|
|
27
28
|
for (const err of errors) {
|
|
28
|
-
|
|
29
|
+
process.stderr.write(` \u274C ${err}
|
|
30
|
+
`);
|
|
29
31
|
}
|
|
30
32
|
process.exit(1);
|
|
31
33
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
+
import { reportError } from "@williamthorsen/nmr-core";
|
|
3
4
|
import { stringify } from "yaml";
|
|
4
5
|
import { loadSyncLabelsConfig, SYNC_LABELS_CONFIG_PATH } from "./loadSyncLabelsConfig.js";
|
|
5
6
|
import { hashPresetFile } from "./presets.js";
|
|
@@ -19,11 +20,11 @@ async function generateCommand() {
|
|
|
19
20
|
config = await loadSyncLabelsConfig();
|
|
20
21
|
} catch (error) {
|
|
21
22
|
const message = error instanceof Error ? error.message : String(error);
|
|
22
|
-
|
|
23
|
+
reportError(`Failed to load config: ${message}`);
|
|
23
24
|
return 1;
|
|
24
25
|
}
|
|
25
26
|
if (config === void 0) {
|
|
26
|
-
|
|
27
|
+
reportError(`No config file found at ${SYNC_LABELS_CONFIG_PATH}. Run \`release-kit sync-labels init\` first.`);
|
|
27
28
|
return 1;
|
|
28
29
|
}
|
|
29
30
|
let labels;
|
|
@@ -31,7 +32,7 @@ async function generateCommand() {
|
|
|
31
32
|
labels = resolveLabels(config);
|
|
32
33
|
} catch (error) {
|
|
33
34
|
const message = error instanceof Error ? error.message : String(error);
|
|
34
|
-
|
|
35
|
+
reportError(`Failed to resolve labels: ${message}`);
|
|
35
36
|
return 1;
|
|
36
37
|
}
|
|
37
38
|
const presetHashes = /* @__PURE__ */ new Map();
|
|
@@ -44,7 +45,7 @@ async function generateCommand() {
|
|
|
44
45
|
writeFileSync(LABELS_OUTPUT_PATH, content, "utf8");
|
|
45
46
|
} catch (error) {
|
|
46
47
|
const message = error instanceof Error ? error.message : String(error);
|
|
47
|
-
|
|
48
|
+
reportError(`Failed to write ${LABELS_OUTPUT_PATH}: ${message}`);
|
|
48
49
|
return 1;
|
|
49
50
|
}
|
|
50
51
|
console.info(`Generated ${LABELS_OUTPUT_PATH} with ${String(labels.length)} labels.`);
|
|
@@ -14,7 +14,8 @@ async function syncLabelsInitCommand({ dryRun, force }) {
|
|
|
14
14
|
workspacePaths = await discoverWorkspaces();
|
|
15
15
|
} catch (error) {
|
|
16
16
|
const message = error instanceof Error ? error.message : String(error);
|
|
17
|
-
|
|
17
|
+
process.stderr.write(` Failed to discover workspaces: ${message}
|
|
18
|
+
`);
|
|
18
19
|
return 1;
|
|
19
20
|
}
|
|
20
21
|
const scopeLabels = workspacePaths === void 0 ? [] : buildScopeLabels(workspacePaths);
|
|
@@ -32,7 +33,7 @@ async function syncLabelsInitCommand({ dryRun, force }) {
|
|
|
32
33
|
reportWriteResult(workflowResult, dryRun);
|
|
33
34
|
reportWriteResult(configResult, dryRun);
|
|
34
35
|
if (workflowResult.outcome === "failed" || configResult.outcome === "failed") {
|
|
35
|
-
|
|
36
|
+
process.stderr.write("Failed to scaffold one or more files.\n");
|
|
36
37
|
return 1;
|
|
37
38
|
}
|
|
38
39
|
if (dryRun) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
|
+
import { reportError } from "@williamthorsen/nmr-core";
|
|
3
4
|
const WORKFLOW_FILE = ".github/workflows/sync-labels.yaml";
|
|
4
5
|
function checkGhAvailable() {
|
|
5
6
|
try {
|
|
@@ -11,18 +12,18 @@ function checkGhAvailable() {
|
|
|
11
12
|
}
|
|
12
13
|
function syncLabelsCommand() {
|
|
13
14
|
if (!checkGhAvailable()) {
|
|
14
|
-
|
|
15
|
+
reportError("The `gh` CLI is not installed or not in PATH. Install it from https://cli.github.com/");
|
|
15
16
|
return 1;
|
|
16
17
|
}
|
|
17
18
|
if (!existsSync(WORKFLOW_FILE)) {
|
|
18
|
-
|
|
19
|
+
reportError(`${WORKFLOW_FILE} not found. Run \`release-kit sync-labels init\` first.`);
|
|
19
20
|
return 1;
|
|
20
21
|
}
|
|
21
22
|
try {
|
|
22
23
|
execSync("gh workflow run sync-labels.yaml", { stdio: "inherit" });
|
|
23
24
|
} catch (error) {
|
|
24
25
|
const message = error instanceof Error ? error.message : String(error);
|
|
25
|
-
|
|
26
|
+
reportError(`Failed to trigger workflow: ${message}`);
|
|
26
27
|
return 1;
|
|
27
28
|
}
|
|
28
29
|
console.info("Workflow triggered successfully. View runs at: gh run list --workflow=sync-labels.yaml");
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { formatErrorLine } from "@williamthorsen/nmr-core";
|
|
4
5
|
import { UPSTREAM_WORK_TYPES_URL } from "./checkWorkTypesDrift.js";
|
|
5
6
|
import { isRecord } from "./typeGuards.js";
|
|
6
7
|
import { buildFetchInit, errorMessage, hasExpectedTopLevelShape } from "./workTypesUtils.js";
|
|
@@ -32,13 +33,15 @@ async function syncWorkTypes(dependencies = {}) {
|
|
|
32
33
|
} catch (error) {
|
|
33
34
|
return {
|
|
34
35
|
exitCode: 2,
|
|
35
|
-
message: `
|
|
36
|
+
message: formatErrorLine(`Failed to fetch upstream work-types.json: ${errorMessage(error)}`)
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
if (!response.ok) {
|
|
39
40
|
return {
|
|
40
41
|
exitCode: 2,
|
|
41
|
-
message:
|
|
42
|
+
message: formatErrorLine(
|
|
43
|
+
`Failed to fetch upstream work-types.json: HTTP ${response.status} ${response.statusText}`
|
|
44
|
+
)
|
|
42
45
|
};
|
|
43
46
|
}
|
|
44
47
|
const upstreamText = await response.text();
|
|
@@ -78,7 +81,7 @@ async function syncWorkTypes(dependencies = {}) {
|
|
|
78
81
|
} catch (error) {
|
|
79
82
|
return {
|
|
80
83
|
exitCode: 2,
|
|
81
|
-
message: `Failed to write ${localPath}: ${errorMessage(error)}`
|
|
84
|
+
message: formatErrorLine(`Failed to write ${localPath}: ${errorMessage(error)}`)
|
|
82
85
|
};
|
|
83
86
|
}
|
|
84
87
|
return {
|
package/dist/esm/tagCommand.js
CHANGED
|
@@ -1,22 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { parseArgsOrExit, reportError } from "@williamthorsen/nmr-core";
|
|
2
2
|
import { createTags } from "./createTags.js";
|
|
3
3
|
const tagFlagSchema = {
|
|
4
4
|
dryRun: { long: "--dry-run", type: "boolean" },
|
|
5
5
|
noGitChecks: { long: "--no-git-checks", type: "boolean" }
|
|
6
6
|
};
|
|
7
7
|
function tagCommand(argv) {
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
parsed = parseArgs(argv, tagFlagSchema);
|
|
11
|
-
} catch (error) {
|
|
12
|
-
console.error(`Error: ${translateParseError(error)}`);
|
|
13
|
-
process.exit(1);
|
|
14
|
-
}
|
|
15
|
-
const { dryRun, noGitChecks } = parsed.flags;
|
|
8
|
+
const { dryRun, noGitChecks } = parseArgsOrExit(argv, tagFlagSchema).flags;
|
|
16
9
|
try {
|
|
17
10
|
createTags({ dryRun, noGitChecks });
|
|
18
11
|
} catch (error) {
|
|
19
|
-
|
|
12
|
+
reportError(error instanceof Error ? error.message : String(error));
|
|
20
13
|
process.exit(1);
|
|
21
14
|
}
|
|
22
15
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { formatErrorLine } from "@williamthorsen/nmr-core";
|
|
1
2
|
import { buildChangelogEntries } from "./buildChangelogEntries.js";
|
|
2
3
|
import {
|
|
3
4
|
resolveOverridePath,
|
|
@@ -13,9 +14,15 @@ async function validateOverridesCommand(dependencies = {}) {
|
|
|
13
14
|
const load = dependencies.loadConfig ?? loadConfig;
|
|
14
15
|
const buildEntries = dependencies.buildEntries ?? defaultBuildEntries;
|
|
15
16
|
const validate = dependencies.validate ?? validateAllChangelogOverrides;
|
|
17
|
+
let rawConfig;
|
|
18
|
+
try {
|
|
19
|
+
rawConfig = await load();
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return { exitCode: 2, message: formatErrorLine(`Failed to load config: ${errorMessage(error)}`) };
|
|
22
|
+
}
|
|
16
23
|
let userConfig;
|
|
17
24
|
try {
|
|
18
|
-
userConfig =
|
|
25
|
+
userConfig = validateLoadedConfig(rawConfig);
|
|
19
26
|
} catch (error) {
|
|
20
27
|
return { exitCode: 2, message: errorMessage(error) };
|
|
21
28
|
}
|
|
@@ -23,13 +30,13 @@ async function validateOverridesCommand(dependencies = {}) {
|
|
|
23
30
|
try {
|
|
24
31
|
discoveredPaths = await discover();
|
|
25
32
|
} catch (error) {
|
|
26
|
-
return { exitCode: 2, message: `
|
|
33
|
+
return { exitCode: 2, message: formatErrorLine(`Failed to discover workspaces: ${errorMessage(error)}`) };
|
|
27
34
|
}
|
|
28
35
|
let inputs;
|
|
29
36
|
try {
|
|
30
37
|
inputs = discoveredPaths === void 0 ? buildSinglePackageInputs(userConfig, buildEntries) : buildMonorepoInputs(discoveredPaths, userConfig, buildEntries);
|
|
31
38
|
} catch (error) {
|
|
32
|
-
return { exitCode: 2, message: `
|
|
39
|
+
return { exitCode: 2, message: formatErrorLine(`Failed to resolve overrides scope: ${errorMessage(error)}`) };
|
|
33
40
|
}
|
|
34
41
|
const result = validate(inputs);
|
|
35
42
|
return formatValidateOverridesResult(result);
|
|
@@ -85,13 +92,7 @@ function flattenEntriesToHashes(entries) {
|
|
|
85
92
|
}
|
|
86
93
|
return hashes;
|
|
87
94
|
}
|
|
88
|
-
|
|
89
|
-
let rawConfig;
|
|
90
|
-
try {
|
|
91
|
-
rawConfig = await load();
|
|
92
|
-
} catch (error) {
|
|
93
|
-
throw new Error(`Error loading config: ${errorMessage(error)}`);
|
|
94
|
-
}
|
|
95
|
+
function validateLoadedConfig(rawConfig) {
|
|
95
96
|
if (rawConfig === void 0) {
|
|
96
97
|
return void 0;
|
|
97
98
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { writeFileWithCheck } from "@williamthorsen/nmr-core";
|
|
3
|
+
import { reportError, writeFileWithCheck } from "@williamthorsen/nmr-core";
|
|
4
4
|
import { extractVersion } from "./changelogJsonUtils.js";
|
|
5
5
|
import { dim } from "./format.js";
|
|
6
6
|
import { renderInjectedReadme } from "./injectReleaseNotesIntoReadme.js";
|
|
@@ -51,7 +51,7 @@ function writePreviewFile(filePath, content, dryRun) {
|
|
|
51
51
|
}
|
|
52
52
|
const result = writeFileWithCheck(filePath, content, { dryRun: false, overwrite: true });
|
|
53
53
|
if (result.outcome === "failed") {
|
|
54
|
-
|
|
54
|
+
reportError(`Failed to write ${filePath}: ${result.error ?? "unknown error"}`);
|
|
55
55
|
return { filePath, outcome: "failed", ...result.error === void 0 ? {} : { error: result.error } };
|
|
56
56
|
}
|
|
57
57
|
if (result.outcome === "created" || result.outcome === "overwritten") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/release-kit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.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,14 +35,14 @@
|
|
|
35
35
|
"glob": "13.0.6",
|
|
36
36
|
"jiti": "2.7.0",
|
|
37
37
|
"json-stringify-pretty-compact": "4.0.0",
|
|
38
|
-
"semver": "7.8.
|
|
38
|
+
"semver": "7.8.5",
|
|
39
39
|
"yaml": "2.9.0",
|
|
40
40
|
"zod": "4.4.3",
|
|
41
|
-
"@williamthorsen/nmr-core": "0.
|
|
41
|
+
"@williamthorsen/nmr-core": "0.5.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/semver": "7.7.1",
|
|
45
|
-
"smol-toml": "1.
|
|
45
|
+
"smol-toml": "1.7.0"
|
|
46
46
|
},
|
|
47
47
|
"engines": {
|
|
48
48
|
"node": ">=18.17.0"
|
|
@@ -53,10 +53,6 @@
|
|
|
53
53
|
},
|
|
54
54
|
"scripts": {
|
|
55
55
|
"build:post": "node scripts/copy-work-types.js",
|
|
56
|
-
"test": "pnpm exec vitest --config=vitest.standalone.config.ts",
|
|
57
|
-
"test:coverage": "pnpm exec vitest --config=vitest.standalone.config.ts --coverage",
|
|
58
|
-
"test:integration": "pnpm exec vitest --config=vitest.integration.config.ts",
|
|
59
|
-
"test:watch": "pnpm exec vitest --config=vitest.standalone.config.ts --watch",
|
|
60
56
|
"work-types:check": "pnpm exec release-kit work-types check",
|
|
61
57
|
"work-types:sync": "pnpm exec release-kit work-types sync"
|
|
62
58
|
}
|