@williamthorsen/release-kit 10.2.1 → 10.3.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 +78 -55
- package/.readyup/kits/npm-auto-publish.js +169 -57
- package/.readyup/manifest.json +6 -6
- package/CHANGELOG.md +54 -0
- package/README.md +18 -6
- package/bin/release-kit.js +1 -1
- package/dist/esm/bin/release-kit.js +4 -4
- package/dist/esm/buildChangelogEntries.js +17 -15
- package/dist/esm/buildDependencyGraph.js +6 -2
- package/dist/esm/buildReleaseSummary.d.ts +1 -1
- package/dist/esm/changelogJsonFile.d.ts +1 -3
- package/dist/esm/changelogJsonFile.js +4 -18
- package/dist/esm/changelogJsonUtils.js +7 -7
- package/dist/esm/changelogOverrides.js +13 -12
- package/dist/esm/checkWorkTypesDrift.js +1 -1
- package/dist/esm/commitCommand.js +3 -13
- package/dist/esm/createTags.js +3 -14
- package/dist/esm/deriveWorkspaceConfig.js +3 -3
- package/dist/esm/detectPackageManager.js +2 -2
- package/dist/esm/detectUndeclaredTagPrefixes.js +1 -1
- package/dist/esm/discoverWorkspaces.js +1 -1
- package/dist/esm/getCommitsSinceTarget.js +2 -2
- package/dist/esm/help/prepareHelp.d.ts +1 -1
- package/dist/esm/help/prepareHelp.js +2 -2
- package/dist/esm/init/checks.js +1 -1
- package/dist/esm/init/detectRepoType.js +1 -1
- package/dist/esm/injectReleaseNotesIntoReadme.d.ts +2 -0
- package/dist/esm/injectReleaseNotesIntoReadme.js +13 -10
- package/dist/esm/loadConfig.js +8 -6
- package/dist/esm/planReleaseNotesPreviews.d.ts +13 -0
- package/dist/esm/planReleaseNotesPreviews.js +43 -0
- package/dist/esm/planVersionBump.d.ts +9 -0
- package/dist/esm/planVersionBump.js +54 -0
- package/dist/esm/prepareCommand.d.ts +0 -4
- package/dist/esm/prepareCommand.js +51 -45
- package/dist/esm/propagateBumps.js +1 -1
- package/dist/esm/releaseFiles.d.ts +4 -0
- package/dist/esm/releaseFiles.js +34 -0
- package/dist/esm/releasePlan.d.ts +10 -0
- package/dist/esm/releasePlan.js +53 -0
- package/dist/esm/releasePrepare.d.ts +3 -3
- package/dist/esm/releasePrepare.js +53 -54
- package/dist/esm/releasePrepareMono.d.ts +3 -2
- package/dist/esm/releasePrepareMono.js +50 -53
- package/dist/esm/releasePrepareProject.d.ts +3 -0
- package/dist/esm/releasePrepareProject.js +33 -37
- package/dist/esm/renderChangelogMarkdown.d.ts +0 -7
- package/dist/esm/renderChangelogMarkdown.js +0 -13
- package/dist/esm/reportPrepare.d.ts +5 -1
- package/dist/esm/reportPrepare.js +37 -17
- package/dist/esm/sync-labels/generateCommand.js +4 -2
- package/dist/esm/sync-labels/presets.js +5 -5
- package/dist/esm/sync-labels/resolveLabels.js +5 -3
- package/dist/esm/syncWorkTypes.js +1 -1
- package/dist/esm/types.d.ts +4 -9
- package/dist/esm/validateConfig.js +11 -11
- package/dist/esm/workTypesData.d.ts +1 -1
- package/dist/esm/workTypesUtils.js +1 -1
- package/package.json +3 -3
- package/dist/esm/bumpAllVersions.d.ts +0 -3
- package/dist/esm/bumpAllVersions.js +0 -61
- package/dist/esm/writeReleaseNotesPreviews.d.ts +0 -18
- package/dist/esm/writeReleaseNotesPreviews.js +0 -63
package/README.md
CHANGED
|
@@ -5,13 +5,23 @@ Version-bumping and changelog-generation toolkit for release workflows.
|
|
|
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
7
|
<!-- section:release-notes -->
|
|
8
|
-
## Release notes — v10.
|
|
8
|
+
## Release notes — v10.3.0 (2026-08-08)
|
|
9
|
+
|
|
10
|
+
### 🎉 Features
|
|
11
|
+
|
|
12
|
+
- Stop reporting absence checks that pass (#610)
|
|
13
|
+
|
|
14
|
+
A ReadyUp check that checks for a problem (such as an unwanted file or a stale config reference) now stays silent unless it finds one. The `nmr`, `release-kit`, and `v11y-check` ReadyUp kits previously printed a line for every such check on every run.
|
|
15
|
+
|
|
16
|
+
- Stop reporting non-issues in the readyup kits (#613)
|
|
17
|
+
|
|
18
|
+
Modifies the `release-kit` and `nmr` ReadyUp kits to avoid verbose reporting when an initial check determines that further checks aren't applicable. In particular, a repo with no `release-kit` config file gets a single line in the `release-kit` report, and a repo that publishes nothing now bypasses the `npm-auto-publish` kit instead of failing it.
|
|
9
19
|
|
|
10
20
|
### 🐛 Bug fixes
|
|
11
21
|
|
|
12
|
-
-
|
|
22
|
+
- Publish build output atomically so a failed build cannot destroy it (#631)
|
|
13
23
|
|
|
14
|
-
Fixes an issue where
|
|
24
|
+
Fixes an issue where a failed or still-running `nmr build` could leave a package with no build output at all, breaking the tooling that a subsequent build depends on. Separately, the error reported when a package's build output is missing now names the correct recovery command.
|
|
15
25
|
<!-- /section:release-notes -->
|
|
16
26
|
|
|
17
27
|
## Installation
|
|
@@ -565,7 +575,7 @@ Run release preparation with automatic workspace discovery.
|
|
|
565
575
|
| `--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). |
|
|
566
576
|
| `--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`. |
|
|
567
577
|
| `--only=name1,name2` | Only process the named workspaces (monorepo only; rejected when a `project` block is configured) |
|
|
568
|
-
| `--with-release-notes` | Write per-workspace release-notes previews under
|
|
578
|
+
| `--with-release-notes` | Write per-workspace release-notes previews under `<workspacePath>/docs/` |
|
|
569
579
|
| `--help`, `-h` | Show help |
|
|
570
580
|
|
|
571
581
|
Workspace names for `--only` match the package directory name (e.g., `arrays`, `release-kit`).
|
|
@@ -574,8 +584,8 @@ Workspace names for `--only` match the package directory name (e.g., `arrays`, `
|
|
|
574
584
|
|
|
575
585
|
`--with-release-notes` writes two versioned files per workspace after each workspace's `changelog.json` is produced:
|
|
576
586
|
|
|
577
|
-
-
|
|
578
|
-
-
|
|
587
|
+
- `<workspacePath>/docs/README.v<version>.md` — the workspace `README.md` with release notes injected at the `<!-- section:release-notes -->` marker.
|
|
588
|
+
- `<workspacePath>/docs/RELEASE_NOTES.v<version>.md` — the standalone release notes for this version.
|
|
579
589
|
|
|
580
590
|
The publish-time inject-and-revert lifecycle is unchanged; previews are additive, deterministic, and safe to regenerate. When `changelogJson.enabled` is `false`, the flag logs a warning and skips preview generation. In dry-run mode, planned writes are logged and no files are created.
|
|
581
591
|
|
|
@@ -890,6 +900,8 @@ rdy list --from npm:@williamthorsen/release-kit # what release-
|
|
|
890
900
|
|
|
891
901
|
`--packages` is the form that survives release-kit publishing further kits. Every form needs `readyup` 0.23 or later, and `@williamthorsen/release-kit` as a _direct_ devDependency: a strict pnpm layout links nothing else into the project, so a transitive copy is unreachable.
|
|
892
902
|
|
|
903
|
+
`npm-auto-publish` queries the npm registry for each package's trusted publisher, so it needs an authenticated npm session and is meant to be invoked deliberately rather than swept up by an unattended run. Without a login it reports one error naming the missing login and blocks the rest of the `packages` checklist, rather than reporting a trusted-publisher failure for every package.
|
|
904
|
+
|
|
893
905
|
These kits are no longer reachable through `rdy run --from github:williamthorsen/node-monorepo-tools`. Repos still using that form should switch to one of the invocations above.
|
|
894
906
|
|
|
895
907
|
## External dependencies
|
package/bin/release-kit.js
CHANGED
|
@@ -3,7 +3,7 @@ try {
|
|
|
3
3
|
await import('../dist/esm/bin/release-kit.js');
|
|
4
4
|
} catch (error) {
|
|
5
5
|
if (error.code === 'ERR_MODULE_NOT_FOUND') {
|
|
6
|
-
process.stderr.write('release-kit: build output not found — run `
|
|
6
|
+
process.stderr.write('release-kit: build output not found — run `nmr build` first\n');
|
|
7
7
|
} else {
|
|
8
8
|
process.stderr.write(`release-kit: failed to load: ${error.message}\n`);
|
|
9
9
|
}
|
|
@@ -263,7 +263,7 @@ if (command === '--version' || command === '-V') {
|
|
|
263
263
|
console.info(VERSION);
|
|
264
264
|
process.exit(0);
|
|
265
265
|
}
|
|
266
|
-
if (command === '--help'
|
|
266
|
+
if (command === undefined || ['--help', '-h'].includes(command)) {
|
|
267
267
|
showUsage();
|
|
268
268
|
process.exit(0);
|
|
269
269
|
}
|
|
@@ -350,7 +350,7 @@ if (command === 'init') {
|
|
|
350
350
|
if (command === 'sync-labels') {
|
|
351
351
|
const subcommand = flags[0];
|
|
352
352
|
const subflags = flags.slice(1);
|
|
353
|
-
if (subcommand === '--help'
|
|
353
|
+
if (subcommand === undefined || ['--help', '-h'].includes(subcommand)) {
|
|
354
354
|
showSyncLabelsHelp();
|
|
355
355
|
process.exit(0);
|
|
356
356
|
}
|
|
@@ -398,7 +398,7 @@ if (command === 'sync-labels') {
|
|
|
398
398
|
if (command === 'overrides') {
|
|
399
399
|
const subcommand = flags[0];
|
|
400
400
|
const subflags = flags.slice(1);
|
|
401
|
-
if (subcommand === '--help'
|
|
401
|
+
if (subcommand === undefined || ['--help', '-h'].includes(subcommand)) {
|
|
402
402
|
showOverridesHelp();
|
|
403
403
|
process.exit(0);
|
|
404
404
|
}
|
|
@@ -427,7 +427,7 @@ if (command === 'overrides') {
|
|
|
427
427
|
if (command === 'work-types') {
|
|
428
428
|
const subcommand = flags[0];
|
|
429
429
|
const subflags = flags.slice(1);
|
|
430
|
-
if (subcommand === '--help'
|
|
430
|
+
if (subcommand === undefined || ['--help', '-h'].includes(subcommand)) {
|
|
431
431
|
showWorkTypesHelp();
|
|
432
432
|
process.exit(0);
|
|
433
433
|
}
|
|
@@ -20,7 +20,8 @@ export function buildChangelogEntries(config, tag, options) {
|
|
|
20
20
|
if (options?.tagPattern !== undefined) {
|
|
21
21
|
cliffArgs.push('--tag-pattern', options.tagPattern);
|
|
22
22
|
}
|
|
23
|
-
|
|
23
|
+
const includePaths = options?.includePaths ?? [];
|
|
24
|
+
for (const includePath of includePaths) {
|
|
24
25
|
cliffArgs.push('--include-path', includePath);
|
|
25
26
|
}
|
|
26
27
|
try {
|
|
@@ -30,7 +31,7 @@ export function buildChangelogEntries(config, tag, options) {
|
|
|
30
31
|
return transformReleases(releases, devOnlySections);
|
|
31
32
|
}
|
|
32
33
|
catch (error) {
|
|
33
|
-
throw new Error(`Failed to build changelog entries for tag ${tag}: ${error instanceof Error ? error.message : String(error)}
|
|
34
|
+
throw new Error(`Failed to build changelog entries for tag ${tag}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
37
|
function parseCliffContext(json) {
|
|
@@ -45,14 +46,14 @@ function toCliffContextRelease(value) {
|
|
|
45
46
|
return {};
|
|
46
47
|
}
|
|
47
48
|
const release = {};
|
|
48
|
-
if (typeof value
|
|
49
|
-
release.version = value
|
|
49
|
+
if (typeof value['version'] === 'string') {
|
|
50
|
+
release.version = value['version'];
|
|
50
51
|
}
|
|
51
|
-
if (typeof value
|
|
52
|
-
release.timestamp = value
|
|
52
|
+
if (typeof value['timestamp'] === 'number') {
|
|
53
|
+
release.timestamp = value['timestamp'];
|
|
53
54
|
}
|
|
54
|
-
if (isUnknownArray(value
|
|
55
|
-
release.commits = value
|
|
55
|
+
if (isUnknownArray(value['commits'])) {
|
|
56
|
+
release.commits = value['commits'].map(toCliffContextCommit);
|
|
56
57
|
}
|
|
57
58
|
return release;
|
|
58
59
|
}
|
|
@@ -61,13 +62,13 @@ function toCliffContextCommit(value) {
|
|
|
61
62
|
return { message: '' };
|
|
62
63
|
}
|
|
63
64
|
const commit = {
|
|
64
|
-
message: typeof value
|
|
65
|
+
message: typeof value['message'] === 'string' ? value['message'] : '',
|
|
65
66
|
};
|
|
66
|
-
if (typeof value
|
|
67
|
-
commit.group = value
|
|
67
|
+
if (typeof value['group'] === 'string') {
|
|
68
|
+
commit.group = value['group'];
|
|
68
69
|
}
|
|
69
|
-
if (typeof value
|
|
70
|
-
commit.id = value
|
|
70
|
+
if (typeof value['id'] === 'string') {
|
|
71
|
+
commit.id = value['id'];
|
|
71
72
|
}
|
|
72
73
|
return commit;
|
|
73
74
|
}
|
|
@@ -79,9 +80,10 @@ function transformReleases(releases, devOnlySections) {
|
|
|
79
80
|
continue;
|
|
80
81
|
}
|
|
81
82
|
const version = extractVersion(release.version);
|
|
82
|
-
const date = release.timestamp !== undefined ? new Date(release.timestamp *
|
|
83
|
+
const date = release.timestamp !== undefined ? new Date(release.timestamp * 1_000).toISOString().slice(0, 10) : 'unreleased';
|
|
83
84
|
const sectionMap = new Map();
|
|
84
|
-
|
|
85
|
+
const commits = release.commits ?? [];
|
|
86
|
+
for (const commit of commits) {
|
|
85
87
|
const group = stripCommentPrefix(commit.group ?? 'Other');
|
|
86
88
|
const description = extractDescription(commit.message);
|
|
87
89
|
const body = extractBody(commit.message);
|
|
@@ -51,14 +51,18 @@ function readPackageJsonSubset(filePath) {
|
|
|
51
51
|
content = readFileSync(filePath, 'utf8');
|
|
52
52
|
}
|
|
53
53
|
catch (error) {
|
|
54
|
-
throw new Error(`Failed to read ${filePath}: ${error instanceof Error ? error.message : String(error)}
|
|
54
|
+
throw new Error(`Failed to read ${filePath}: ${error instanceof Error ? error.message : String(error)}`, {
|
|
55
|
+
cause: error,
|
|
56
|
+
});
|
|
55
57
|
}
|
|
56
58
|
let parsed;
|
|
57
59
|
try {
|
|
58
60
|
parsed = JSON.parse(content);
|
|
59
61
|
}
|
|
60
62
|
catch (error) {
|
|
61
|
-
throw new Error(`Failed to parse JSON in ${filePath}: ${error instanceof Error ? error.message : String(error)}
|
|
63
|
+
throw new Error(`Failed to parse JSON in ${filePath}: ${error instanceof Error ? error.message : String(error)}`, {
|
|
64
|
+
cause: error,
|
|
65
|
+
});
|
|
62
66
|
}
|
|
63
67
|
if (!isPackageJsonSubset(parsed)) {
|
|
64
68
|
throw new Error(`Invalid package.json at ${filePath}`);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { PrepareResult } from './types.js';
|
|
2
|
-
export declare function buildReleaseSummary(result: PrepareResult): string;
|
|
2
|
+
export declare function buildReleaseSummary(result: Pick<PrepareResult, 'project' | 'workspaces'>): string;
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import type { ChangelogEntry, ReleaseConfig } from './types.js';
|
|
2
2
|
export declare function resolveChangelogJsonPath(config: Pick<ReleaseConfig, 'changelogJson'>, changelogPath: string): string;
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function upsertChangelogJson(filePath: string, entries: ChangelogEntry[]): string;
|
|
5
|
-
export declare function upsertChangelogJsonAndReturn(filePath: string, entries: ChangelogEntry[]): ChangelogEntry[];
|
|
3
|
+
export declare function renderChangelogJson(entries: ChangelogEntry[]): string;
|
|
6
4
|
export declare function mergeChangelogEntriesWithDisk(filePath: string, entries: ChangelogEntry[]): ChangelogEntry[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
3
|
import stringify from 'json-stringify-pretty-compact';
|
|
4
4
|
import semver from 'semver';
|
|
5
5
|
import { isChangelogEntry } from "./changelogJsonUtils.js";
|
|
@@ -7,22 +7,8 @@ import { isUnknownArray } from "./typeGuards.js";
|
|
|
7
7
|
export function resolveChangelogJsonPath(config, changelogPath) {
|
|
8
8
|
return join(changelogPath, config.changelogJson.outputPath);
|
|
9
9
|
}
|
|
10
|
-
export function
|
|
11
|
-
|
|
12
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
13
|
-
writeFileSync(filePath, stringify(sorted, { maxLength: 100 }) + '\n', 'utf8');
|
|
14
|
-
return filePath;
|
|
15
|
-
}
|
|
16
|
-
export function upsertChangelogJson(filePath, entries) {
|
|
17
|
-
upsertChangelogJsonAndReturn(filePath, entries);
|
|
18
|
-
return filePath;
|
|
19
|
-
}
|
|
20
|
-
export function upsertChangelogJsonAndReturn(filePath, entries) {
|
|
21
|
-
const existing = readExistingEntries(filePath);
|
|
22
|
-
const merged = mergeEntries(entries, existing);
|
|
23
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
24
|
-
writeFileSync(filePath, stringify(merged, { maxLength: 100 }) + '\n', 'utf8');
|
|
25
|
-
return merged;
|
|
10
|
+
export function renderChangelogJson(entries) {
|
|
11
|
+
return stringify(sortNewestFirst(entries), { maxLength: 100 }) + '\n';
|
|
26
12
|
}
|
|
27
13
|
export function mergeChangelogEntriesWithDisk(filePath, entries) {
|
|
28
14
|
const existing = readExistingEntries(filePath);
|
|
@@ -3,21 +3,21 @@ import { isRecord, isUnknownArray } from "./typeGuards.js";
|
|
|
3
3
|
export function isChangelogItem(value) {
|
|
4
4
|
if (!isRecord(value))
|
|
5
5
|
return false;
|
|
6
|
-
if (typeof value
|
|
6
|
+
if (typeof value['description'] !== 'string')
|
|
7
7
|
return false;
|
|
8
|
-
if (value
|
|
8
|
+
if (value['body'] !== undefined && typeof value['body'] !== 'string')
|
|
9
9
|
return false;
|
|
10
|
-
if (value
|
|
10
|
+
if (value['breaking'] !== undefined && typeof value['breaking'] !== 'boolean')
|
|
11
11
|
return false;
|
|
12
|
-
if (value
|
|
12
|
+
if (value['hash'] !== undefined && typeof value['hash'] !== 'string')
|
|
13
13
|
return false;
|
|
14
14
|
return true;
|
|
15
15
|
}
|
|
16
16
|
export function isChangelogEntry(value) {
|
|
17
17
|
return (isRecord(value) &&
|
|
18
|
-
typeof value
|
|
19
|
-
typeof value
|
|
20
|
-
isUnknownArray(value
|
|
18
|
+
typeof value['version'] === 'string' &&
|
|
19
|
+
typeof value['date'] === 'string' &&
|
|
20
|
+
isUnknownArray(value['sections']));
|
|
21
21
|
}
|
|
22
22
|
export function extractVersion(tag) {
|
|
23
23
|
const match = /(\d+\.\d+\.\d+.*)$/.exec(tag);
|
|
@@ -66,8 +66,8 @@ function validateSingleOverride(key, rawEntry, errors) {
|
|
|
66
66
|
entryValid = false;
|
|
67
67
|
}
|
|
68
68
|
const result = {};
|
|
69
|
-
if (rawEntry
|
|
70
|
-
const audienceResult = validateAudience(key, rawEntry
|
|
69
|
+
if (rawEntry['audience'] !== undefined) {
|
|
70
|
+
const audienceResult = validateAudience(key, rawEntry['audience'], errors);
|
|
71
71
|
if (audienceResult === undefined) {
|
|
72
72
|
entryValid = false;
|
|
73
73
|
}
|
|
@@ -75,31 +75,31 @@ function validateSingleOverride(key, rawEntry, errors) {
|
|
|
75
75
|
result.audience = audienceResult;
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
|
-
if (rawEntry
|
|
79
|
-
if (typeof rawEntry
|
|
78
|
+
if (rawEntry['description'] !== undefined) {
|
|
79
|
+
if (typeof rawEntry['description'] !== 'string') {
|
|
80
80
|
errors.push(`overrides['${key}']: 'description' must be a string`);
|
|
81
81
|
entryValid = false;
|
|
82
82
|
}
|
|
83
83
|
else {
|
|
84
|
-
result.description = rawEntry
|
|
84
|
+
result.description = rawEntry['description'];
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
if (rawEntry
|
|
88
|
-
if (typeof rawEntry
|
|
87
|
+
if (rawEntry['body'] !== undefined) {
|
|
88
|
+
if (typeof rawEntry['body'] !== 'string') {
|
|
89
89
|
errors.push(`overrides['${key}']: 'body' must be a string`);
|
|
90
90
|
entryValid = false;
|
|
91
91
|
}
|
|
92
92
|
else {
|
|
93
|
-
result.body = rawEntry
|
|
93
|
+
result.body = rawEntry['body'];
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
-
if (rawEntry
|
|
97
|
-
if (typeof rawEntry
|
|
96
|
+
if (rawEntry['breaking'] !== undefined) {
|
|
97
|
+
if (typeof rawEntry['breaking'] !== 'boolean') {
|
|
98
98
|
errors.push(`overrides['${key}']: 'breaking' must be a boolean`);
|
|
99
99
|
entryValid = false;
|
|
100
100
|
}
|
|
101
101
|
else {
|
|
102
|
-
result.breaking = rawEntry
|
|
102
|
+
result.breaking = rawEntry['breaking'];
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
if (Object.keys(result).length === 0 && entryValid) {
|
|
@@ -236,7 +236,8 @@ export function loadOverridesForScopes(scopes) {
|
|
|
236
236
|
project = result.overrides;
|
|
237
237
|
}
|
|
238
238
|
}
|
|
239
|
-
|
|
239
|
+
const workspacePaths = scopes.workspaces ?? [];
|
|
240
|
+
for (const workspacePath of workspacePaths) {
|
|
240
241
|
const result = loadChangelogOverrides(resolveOverridePath(workspacePath));
|
|
241
242
|
if ('errors' in result) {
|
|
242
243
|
errors.push(...result.errors);
|
|
@@ -1,25 +1,15 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { GIT_OUTPUT_LIMIT, parseArgsOrExit } from '@williamthorsen/nmr-core';
|
|
4
|
-
import { RELEASE_SUMMARY_FILE,
|
|
4
|
+
import { readReleaseTags, RELEASE_SUMMARY_FILE, resolveReleaseTagsPath } from "./releaseFiles.js";
|
|
5
5
|
const commitFlagSchema = {
|
|
6
6
|
dryRun: { long: '--dry-run', type: 'boolean' },
|
|
7
7
|
};
|
|
8
8
|
export function commitCommand(argv) {
|
|
9
9
|
const dryRun = parseArgsOrExit(argv, commitFlagSchema).flags.dryRun;
|
|
10
|
-
|
|
11
|
-
try {
|
|
12
|
-
tagsContent = readFileSync(RELEASE_TAGS_FILE, 'utf8');
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
throw new Error('No tags file found. Run `release-kit prepare` first.');
|
|
16
|
-
}
|
|
17
|
-
const tags = tagsContent
|
|
18
|
-
.split('\n')
|
|
19
|
-
.map((line) => line.trim())
|
|
20
|
-
.filter((line) => line.length > 0);
|
|
10
|
+
const tags = readReleaseTags();
|
|
21
11
|
if (tags.length === 0) {
|
|
22
|
-
throw new Error(
|
|
12
|
+
throw new Error(`Tags file at ${resolveReleaseTagsPath()} is empty. Run \`release-kit prepare\` first.`);
|
|
23
13
|
}
|
|
24
14
|
let summary = '';
|
|
25
15
|
try {
|
package/dist/esm/createTags.js
CHANGED
|
@@ -1,21 +1,10 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
3
2
|
import { GIT_OUTPUT_LIMIT } from '@williamthorsen/nmr-core';
|
|
4
3
|
import { deleteFileIfExists } from "./deleteFileIfExists.js";
|
|
5
|
-
import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./
|
|
4
|
+
import { readReleaseTags, RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
|
|
6
5
|
export function createTags(options) {
|
|
7
6
|
const { dryRun, noGitChecks } = options;
|
|
8
|
-
|
|
9
|
-
try {
|
|
10
|
-
content = readFileSync(RELEASE_TAGS_FILE, 'utf8');
|
|
11
|
-
}
|
|
12
|
-
catch {
|
|
13
|
-
throw new Error('No tags file found. Run `release-kit prepare` first.');
|
|
14
|
-
}
|
|
15
|
-
const tags = content
|
|
16
|
-
.split('\n')
|
|
17
|
-
.map((line) => line.trim())
|
|
18
|
-
.filter((line) => line.length > 0);
|
|
7
|
+
const tags = readReleaseTags();
|
|
19
8
|
if (tags.length === 0) {
|
|
20
9
|
return [];
|
|
21
10
|
}
|
|
@@ -62,6 +51,6 @@ function assertCleanWorkingTree() {
|
|
|
62
51
|
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
63
52
|
throw error;
|
|
64
53
|
}
|
|
65
|
-
throw new Error('Working tree is dirty. Commit or stash changes before tagging, or use `--no-git-checks` to skip this check.');
|
|
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 });
|
|
66
55
|
}
|
|
67
56
|
}
|
|
@@ -10,14 +10,14 @@ export function deriveWorkspaceConfig(workspacePath) {
|
|
|
10
10
|
}
|
|
11
11
|
catch (error) {
|
|
12
12
|
const message = error instanceof Error ? error.message : String(error);
|
|
13
|
-
throw new Error(`Failed to read ${packageJsonPath}: ${message}
|
|
13
|
+
throw new Error(`Failed to read ${packageJsonPath}: ${message}`, { cause: error });
|
|
14
14
|
}
|
|
15
|
-
const name = isRecord(parsed) ? parsed
|
|
15
|
+
const name = isRecord(parsed) ? parsed['name'] : undefined;
|
|
16
16
|
if (typeof name !== 'string' || name.length === 0) {
|
|
17
17
|
throw new Error(`${packageJsonPath} is missing a 'name' field (required for tag derivation).`);
|
|
18
18
|
}
|
|
19
19
|
const unscopedName = stripNpmScope(name);
|
|
20
|
-
const privateField = isRecord(parsed) ? parsed
|
|
20
|
+
const privateField = isRecord(parsed) ? parsed['private'] : undefined;
|
|
21
21
|
const isPublishable = privateField === undefined || privateField === false;
|
|
22
22
|
return {
|
|
23
23
|
dir,
|
|
@@ -6,8 +6,8 @@ export function detectPackageManager() {
|
|
|
6
6
|
try {
|
|
7
7
|
const content = readFileSync(packageJsonPath, 'utf8');
|
|
8
8
|
const parsed = JSON.parse(content);
|
|
9
|
-
if (isRecord(parsed) && typeof parsed
|
|
10
|
-
const [name, version] = parsed
|
|
9
|
+
if (isRecord(parsed) && typeof parsed['packageManager'] === 'string') {
|
|
10
|
+
const [name, version] = parsed['packageManager'].split('@', 2);
|
|
11
11
|
if (name === 'pnpm' || name === 'npm') {
|
|
12
12
|
return name;
|
|
13
13
|
}
|
|
@@ -23,7 +23,7 @@ export function detectUndeclaredTagPrefixes(knownPrefixes) {
|
|
|
23
23
|
const match = CANDIDATE_TAG_PATTERN.exec(tag);
|
|
24
24
|
if (match === null)
|
|
25
25
|
continue;
|
|
26
|
-
const prefix = match.groups?.prefix ?? '';
|
|
26
|
+
const prefix = match.groups?.['prefix'] ?? '';
|
|
27
27
|
if (prefix === '' || known.has(prefix))
|
|
28
28
|
continue;
|
|
29
29
|
let tags = grouped.get(prefix);
|
|
@@ -24,7 +24,7 @@ function findLatestTag(tagPrefixes) {
|
|
|
24
24
|
if (isNoTagError(error)) {
|
|
25
25
|
return undefined;
|
|
26
26
|
}
|
|
27
|
-
throw new Error(`Failed to run 'git describe': ${errorMessage(error)}
|
|
27
|
+
throw new Error(`Failed to run 'git describe': ${errorMessage(error)}`, { cause: error });
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
const RELEASE_COMMIT_PREFIX = 'release:';
|
|
@@ -59,7 +59,7 @@ export function getCommitsSinceTarget(tagPrefixes, paths) {
|
|
|
59
59
|
}).trim();
|
|
60
60
|
}
|
|
61
61
|
catch (error) {
|
|
62
|
-
throw new Error(`Failed to run 'git log' for range '${range}': ${errorMessage(error)}
|
|
62
|
+
throw new Error(`Failed to run 'git log' for range '${range}': ${errorMessage(error)}`, { cause: error });
|
|
63
63
|
}
|
|
64
64
|
if (logOutput === '') {
|
|
65
65
|
return { tag, commits: [] };
|
|
@@ -1,2 +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
|
|
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
2
|
export declare function showPrepareHelp(): void;
|
|
@@ -16,8 +16,8 @@ Options:
|
|
|
16
16
|
--no-git-checks, -n Skip the clean-working-tree check
|
|
17
17
|
--only=name1,name2 Only process the named workspaces (comma-separated, monorepo only;
|
|
18
18
|
rejected when a 'project' block is configured)
|
|
19
|
-
--with-release-notes Also write per-workspace release-notes previews under
|
|
20
|
-
(docs/README.v
|
|
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
21
|
Recommended .gitignore entry: packages/*/docs/*.v*.md (or docs/*.v*.md).
|
|
22
22
|
--help, -h Show this help message
|
|
23
23
|
`;
|
package/dist/esm/init/checks.js
CHANGED
|
@@ -23,7 +23,7 @@ export function usesPnpm() {
|
|
|
23
23
|
}
|
|
24
24
|
const raw = readFileSync('package.json', 'utf8');
|
|
25
25
|
const pkg = parseJsonRecord(raw);
|
|
26
|
-
if (pkg !== undefined && typeof pkg
|
|
26
|
+
if (pkg !== undefined && typeof pkg['packageManager'] === 'string' && pkg['packageManager'].startsWith('pnpm')) {
|
|
27
27
|
return { ok: true };
|
|
28
28
|
}
|
|
29
29
|
return {
|
|
@@ -7,7 +7,7 @@ export function detectRepoType() {
|
|
|
7
7
|
if (existsSync('package.json')) {
|
|
8
8
|
const raw = readFileSync('package.json', 'utf8');
|
|
9
9
|
const pkg = parseJsonRecord(raw);
|
|
10
|
-
if (pkg !== undefined && Array.isArray(pkg
|
|
10
|
+
if (pkg !== undefined && Array.isArray(pkg['workspaces'])) {
|
|
11
11
|
return 'monorepo';
|
|
12
12
|
}
|
|
13
13
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { ChangelogEntry } from './types.js';
|
|
1
2
|
export interface RenderedInjectedReadme {
|
|
2
3
|
injectedReadme: string;
|
|
3
4
|
releaseNotesMarkdown: string;
|
|
4
5
|
}
|
|
6
|
+
export declare function renderInjectedReadmeFromEntries(readme: string, entries: readonly ChangelogEntry[], tag: string, sectionOrder?: string[]): RenderedInjectedReadme | undefined;
|
|
5
7
|
export declare function renderInjectedReadme(readme: string, changelogJsonPath: string, tag: string, sectionOrder?: string[]): RenderedInjectedReadme | undefined;
|
|
6
8
|
export declare function injectReleaseNotesIntoReadme(readmePath: string, changelogJsonPath: string, tag: string, sectionOrder?: string[]): string | undefined;
|
|
7
9
|
export declare function resolveReadmePath(workspacePath: string): string | undefined;
|
|
@@ -3,17 +3,8 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { extractVersion, readChangelogEntries } from "./changelogJsonUtils.js";
|
|
4
4
|
import { injectSection } from "./injectSection.js";
|
|
5
5
|
import { matchesAudience, renderReleaseNotesSingle } from "./renderReleaseNotes.js";
|
|
6
|
-
export function
|
|
7
|
-
if (!existsSync(changelogJsonPath)) {
|
|
8
|
-
console.warn(`Warning: ${changelogJsonPath} not found; skipping README injection`);
|
|
9
|
-
return undefined;
|
|
10
|
-
}
|
|
6
|
+
export function renderInjectedReadmeFromEntries(readme, entries, tag, sectionOrder) {
|
|
11
7
|
const version = extractVersion(tag);
|
|
12
|
-
const entries = readChangelogEntries(changelogJsonPath);
|
|
13
|
-
if (entries === undefined) {
|
|
14
|
-
console.warn(`Warning: could not parse ${changelogJsonPath}; skipping README injection`);
|
|
15
|
-
return undefined;
|
|
16
|
-
}
|
|
17
8
|
const entry = entries.find((e) => e.version === version);
|
|
18
9
|
if (entry === undefined) {
|
|
19
10
|
console.warn(`Warning: no changelog entry for version ${version}; skipping README injection`);
|
|
@@ -33,6 +24,18 @@ export function renderInjectedReadme(readme, changelogJsonPath, tag, sectionOrde
|
|
|
33
24
|
const injectedReadme = injectSection(readme, 'release-notes', releaseNotesMarkdown);
|
|
34
25
|
return { injectedReadme, releaseNotesMarkdown };
|
|
35
26
|
}
|
|
27
|
+
export function renderInjectedReadme(readme, changelogJsonPath, tag, sectionOrder) {
|
|
28
|
+
if (!existsSync(changelogJsonPath)) {
|
|
29
|
+
console.warn(`Warning: ${changelogJsonPath} not found; skipping README injection`);
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const entries = readChangelogEntries(changelogJsonPath);
|
|
33
|
+
if (entries === undefined) {
|
|
34
|
+
console.warn(`Warning: could not parse ${changelogJsonPath}; skipping README injection`);
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return renderInjectedReadmeFromEntries(readme, entries, tag, sectionOrder);
|
|
38
|
+
}
|
|
36
39
|
export function injectReleaseNotesIntoReadme(readmePath, changelogJsonPath, tag, sectionOrder) {
|
|
37
40
|
const originalReadme = readFileSync(readmePath, 'utf8');
|
|
38
41
|
const rendered = renderInjectedReadme(originalReadme, changelogJsonPath, tag, sectionOrder);
|
package/dist/esm/loadConfig.js
CHANGED
|
@@ -23,19 +23,19 @@ export function readRootPackageVersion() {
|
|
|
23
23
|
contents = readFileSync(absolutePath, 'utf8');
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
|
-
throw new Error(`Failed to read root ${ROOT_PACKAGE_JSON_PATH}: ${error instanceof Error ? error.message : String(error)}
|
|
26
|
+
throw new Error(`Failed to read root ${ROOT_PACKAGE_JSON_PATH}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
27
27
|
}
|
|
28
28
|
let parsed;
|
|
29
29
|
try {
|
|
30
30
|
parsed = JSON.parse(contents);
|
|
31
31
|
}
|
|
32
32
|
catch (error) {
|
|
33
|
-
throw new Error(`Failed to parse root ${ROOT_PACKAGE_JSON_PATH}: ${error instanceof Error ? error.message : String(error)}
|
|
33
|
+
throw new Error(`Failed to parse root ${ROOT_PACKAGE_JSON_PATH}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
34
34
|
}
|
|
35
35
|
if (!isRecord(parsed)) {
|
|
36
36
|
return { exists: true, version: undefined };
|
|
37
37
|
}
|
|
38
|
-
return { exists: true, version: typeof parsed
|
|
38
|
+
return { exists: true, version: typeof parsed['version'] === 'string' ? parsed['version'] : undefined };
|
|
39
39
|
}
|
|
40
40
|
export const CONFIG_FILE_PATH = '.config/release-kit.config.ts';
|
|
41
41
|
export async function loadConfig() {
|
|
@@ -44,7 +44,7 @@ export async function loadConfig() {
|
|
|
44
44
|
return undefined;
|
|
45
45
|
}
|
|
46
46
|
const imported = await import(__rewriteRelativeImportExtension(pathToFileURL(absoluteConfigPath).href));
|
|
47
|
-
const resolved = isRecord(imported) ? (imported
|
|
47
|
+
const resolved = isRecord(imported) ? (imported['default'] ?? imported['config']) : undefined;
|
|
48
48
|
if (resolved === undefined) {
|
|
49
49
|
throw new Error('Config file must have a default export or a named `config` export (e.g., `export default { ... }` or `export const config = { ... }`)');
|
|
50
50
|
}
|
|
@@ -177,7 +177,8 @@ function assertNoTagPrefixCollisions(workspaces, retiredPackages, project) {
|
|
|
177
177
|
for (const workspace of workspaces) {
|
|
178
178
|
const owner = `ws:${workspace.dir}`;
|
|
179
179
|
sources.push({ prefix: workspace.tagPrefix, label: `workspace '${workspace.dir}'`, owner });
|
|
180
|
-
|
|
180
|
+
const legacyIdentities = workspace.legacyIdentities ?? [];
|
|
181
|
+
for (const identity of legacyIdentities) {
|
|
181
182
|
sources.push({
|
|
182
183
|
prefix: identity.tagPrefix,
|
|
183
184
|
label: `workspace '${workspace.dir}' legacyIdentities entry (name='${identity.name}')`,
|
|
@@ -185,7 +186,8 @@ function assertNoTagPrefixCollisions(workspaces, retiredPackages, project) {
|
|
|
185
186
|
});
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
|
-
|
|
189
|
+
const retiredEntries = (retiredPackages ?? []).entries();
|
|
190
|
+
for (const [index, retired] of retiredEntries) {
|
|
189
191
|
sources.push({
|
|
190
192
|
prefix: retired.tagPrefix,
|
|
191
193
|
label: `retiredPackages entry (name='${retired.name}')`,
|