@williamthorsen/release-kit 10.2.1 → 10.2.2
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 +1 -1
- package/.readyup/kits/npm-auto-publish.js +135 -23
- package/.readyup/manifest.json +3 -3
- package/CHANGELOG.md +22 -0
- package/README.md +13 -3
- package/dist/esm/buildChangelogEntries.js +1 -1
- 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/commitCommand.js +3 -13
- package/dist/esm/createTags.js +2 -13
- package/dist/esm/injectReleaseNotesIntoReadme.d.ts +2 -0
- package/dist/esm/injectReleaseNotesIntoReadme.js +13 -10
- 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/{bumpAllVersions.js → planVersionBump.js} +15 -26
- package/dist/esm/prepareCommand.d.ts +0 -4
- package/dist/esm/prepareCommand.js +51 -45
- 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 +48 -52
- 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 +36 -17
- package/dist/esm/types.d.ts +4 -9
- package/dist/esm/workTypesData.d.ts +1 -1
- package/package.json +2 -2
- package/dist/esm/bumpAllVersions.d.ts +0 -3
- package/dist/esm/writeReleaseNotesPreviews.d.ts +0 -18
- package/dist/esm/writeReleaseNotesPreviews.js +0 -63
package/.readyup/kits/default.js
CHANGED
|
@@ -52,7 +52,7 @@ function detectRepoType() {
|
|
|
52
52
|
|
|
53
53
|
// .readyup/kits/default.ts
|
|
54
54
|
function getMinVersion() {
|
|
55
|
-
const picked = { "version": "10.2.
|
|
55
|
+
const picked = { "version": "10.2.2" };
|
|
56
56
|
if (typeof picked.version !== "string") {
|
|
57
57
|
throw new TypeError("release-kit/package.json: 'version' must be a string");
|
|
58
58
|
}
|
|
@@ -7,7 +7,11 @@ export const __readyupVersion = "0.23.0";
|
|
|
7
7
|
import { execSync } from "node:child_process";
|
|
8
8
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
9
9
|
import path from "node:path";
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
defineRdyChecklist,
|
|
12
|
+
defineRdyKit,
|
|
13
|
+
defineRdyStagedChecklist
|
|
14
|
+
} from "readyup";
|
|
11
15
|
import {
|
|
12
16
|
discoverWorkspaces,
|
|
13
17
|
fileContains,
|
|
@@ -17,7 +21,17 @@ import {
|
|
|
17
21
|
readFile,
|
|
18
22
|
readJsonFile
|
|
19
23
|
} from "readyup/check-utils";
|
|
24
|
+
var AUTH_ERROR_CODES = /* @__PURE__ */ new Set(["E401", "ENEEDAUTH"]);
|
|
20
25
|
var PUBLISH_WORKFLOW_FILE = "publish.yaml";
|
|
26
|
+
var UNREACHABLE_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
27
|
+
"EAI_AGAIN",
|
|
28
|
+
"ECONNREFUSED",
|
|
29
|
+
"ECONNRESET",
|
|
30
|
+
"ENETUNREACH",
|
|
31
|
+
"ENOTFOUND",
|
|
32
|
+
"ERR_SOCKET_TIMEOUT",
|
|
33
|
+
"ETIMEDOUT"
|
|
34
|
+
]);
|
|
21
35
|
var repoChecklist = defineRdyStagedChecklist({
|
|
22
36
|
name: "repo",
|
|
23
37
|
preconditions: [
|
|
@@ -62,6 +76,16 @@ var packagesChecklist = defineRdyChecklist({
|
|
|
62
76
|
name: "At least one workspace discovered",
|
|
63
77
|
check: () => discoverWorkspaces().length > 0,
|
|
64
78
|
fix: "Ensure pnpm-workspace.yaml lists package globs, or that a root package.json exists"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "npm session is usable",
|
|
82
|
+
check: () => {
|
|
83
|
+
const auth = getCachedNpmAuthStatus();
|
|
84
|
+
return auth.status === "authenticated" ? { ok: true } : { ok: false, detail: auth.detail };
|
|
85
|
+
},
|
|
86
|
+
get fix() {
|
|
87
|
+
return getCachedNpmAuthStatus().status === "unreachable" ? "Restore access to the npm registry, then re-run; the trusted-publisher check queries it directly" : "Log in to npm: npm login";
|
|
88
|
+
}
|
|
65
89
|
}
|
|
66
90
|
],
|
|
67
91
|
get checks() {
|
|
@@ -100,7 +124,7 @@ function buildWorkspaceCheck(workspace) {
|
|
|
100
124
|
checks: [
|
|
101
125
|
{
|
|
102
126
|
name: "trusted publisher configured",
|
|
103
|
-
check: () =>
|
|
127
|
+
check: () => checkTrustedPublisher(displayName),
|
|
104
128
|
get fix() {
|
|
105
129
|
return `Run: npm trust github ${displayName} --repo ${getCachedOwnerRepo()} --file ${PUBLISH_WORKFLOW_FILE}`;
|
|
106
130
|
}
|
|
@@ -148,6 +172,76 @@ function checkProvenanceMatchesVisibility() {
|
|
|
148
172
|
}
|
|
149
173
|
return { ok: true };
|
|
150
174
|
}
|
|
175
|
+
function checkTrustedPublisher(packageName) {
|
|
176
|
+
const result = classifyTrustQuery(
|
|
177
|
+
runNpmJson(`npm trust list ${packageName} --json`),
|
|
178
|
+
getCachedOwnerRepo(),
|
|
179
|
+
PUBLISH_WORKFLOW_FILE
|
|
180
|
+
);
|
|
181
|
+
switch (result.status) {
|
|
182
|
+
case "configured":
|
|
183
|
+
return { ok: true };
|
|
184
|
+
case "not-configured":
|
|
185
|
+
return { ok: false, detail: "No trusted publisher is configured for this package" };
|
|
186
|
+
default:
|
|
187
|
+
return { ok: false, detail: result.detail };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function classifyNpmAuth(result) {
|
|
191
|
+
if (result.exitOk) {
|
|
192
|
+
return { status: "authenticated" };
|
|
193
|
+
}
|
|
194
|
+
const error = readNpmError(result.stdout);
|
|
195
|
+
if (error === void 0) {
|
|
196
|
+
return { status: "unreachable", detail: "The npm registry query failed without a readable error payload" };
|
|
197
|
+
}
|
|
198
|
+
if (AUTH_ERROR_CODES.has(error.code)) {
|
|
199
|
+
return { status: "unauthenticated", detail: `The npm registry rejected the session (${error.code})` };
|
|
200
|
+
}
|
|
201
|
+
if (UNREACHABLE_ERROR_CODES.has(error.code)) {
|
|
202
|
+
return { status: "unreachable", detail: `Cannot reach the npm registry (${error.code})` };
|
|
203
|
+
}
|
|
204
|
+
return { status: "unreachable", detail: `The npm registry query failed (${error.code}): ${error.summary}` };
|
|
205
|
+
}
|
|
206
|
+
function classifyTrustQuery(result, expectedRepo, expectedFile) {
|
|
207
|
+
if (!result.exitOk) {
|
|
208
|
+
const error = readNpmError(result.stdout);
|
|
209
|
+
if (error === void 0) {
|
|
210
|
+
return { status: "error", detail: "The npm trust query failed without a readable error payload" };
|
|
211
|
+
}
|
|
212
|
+
if (error.code === "E404") {
|
|
213
|
+
return { status: "not-configured" };
|
|
214
|
+
}
|
|
215
|
+
return { status: "error", detail: `The npm trust query failed (${error.code}): ${error.summary}` };
|
|
216
|
+
}
|
|
217
|
+
const relationships = readTrustRelationships(result.stdout);
|
|
218
|
+
if (relationships === void 0) {
|
|
219
|
+
return { status: "error", detail: "The npm trust query returned a payload this check cannot read" };
|
|
220
|
+
}
|
|
221
|
+
if (relationships.length === 0) {
|
|
222
|
+
return { status: "not-configured" };
|
|
223
|
+
}
|
|
224
|
+
const isMatch = relationships.some(
|
|
225
|
+
(relationship) => relationship.type === "github" && relationship.repository === expectedRepo && relationship.file === expectedFile
|
|
226
|
+
);
|
|
227
|
+
return isMatch ? { status: "configured" } : {
|
|
228
|
+
status: "mismatched",
|
|
229
|
+
detail: `Expected github ${expectedRepo} (${expectedFile}); found ${describeTrustRelationships(relationships)}`
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function describeTrustRelationships(relationships) {
|
|
233
|
+
return relationships.map((relationship) => {
|
|
234
|
+
const type = typeof relationship.type === "string" ? relationship.type : "(unknown)";
|
|
235
|
+
const repository = typeof relationship.repository === "string" ? relationship.repository : "(unknown)";
|
|
236
|
+
const file = typeof relationship.file === "string" ? relationship.file : "(unknown)";
|
|
237
|
+
return `${type} ${repository} (${file})`;
|
|
238
|
+
}).join(", ");
|
|
239
|
+
}
|
|
240
|
+
var cachedNpmAuthStatus;
|
|
241
|
+
function getCachedNpmAuthStatus() {
|
|
242
|
+
cachedNpmAuthStatus ??= classifyNpmAuth(runNpmJson("npm whoami --json"));
|
|
243
|
+
return cachedNpmAuthStatus;
|
|
244
|
+
}
|
|
151
245
|
var cachedOwnerRepo;
|
|
152
246
|
function getCachedOwnerRepo() {
|
|
153
247
|
if (cachedOwnerRepo === void 0) {
|
|
@@ -183,27 +277,6 @@ function hasTokenReferences() {
|
|
|
183
277
|
}
|
|
184
278
|
return false;
|
|
185
279
|
}
|
|
186
|
-
function hasTrustedPublisher(packageName, expectedRepo, expectedFile) {
|
|
187
|
-
let output;
|
|
188
|
-
try {
|
|
189
|
-
output = execSync(`npm trust list ${packageName} --json`, {
|
|
190
|
-
encoding: "utf8",
|
|
191
|
-
stdio: "pipe"
|
|
192
|
-
});
|
|
193
|
-
} catch {
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
let parsed;
|
|
197
|
-
try {
|
|
198
|
-
parsed = JSON.parse(output);
|
|
199
|
-
} catch {
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
if (!isRecord(parsed)) {
|
|
203
|
-
return false;
|
|
204
|
-
}
|
|
205
|
-
return parsed.type === "github" && parsed.repository === expectedRepo && parsed.file === expectedFile;
|
|
206
|
-
}
|
|
207
280
|
function isPublishedToNpm(packageName) {
|
|
208
281
|
try {
|
|
209
282
|
execSync(`npm view ${packageName} version`, {
|
|
@@ -225,11 +298,50 @@ function isRepoPrivate() {
|
|
|
225
298
|
function parseProvenanceSetting(workflowContent) {
|
|
226
299
|
return /^[^#]*provenance:\s*['"]?true['"]?/im.test(workflowContent);
|
|
227
300
|
}
|
|
301
|
+
function readNpmError(stdout) {
|
|
302
|
+
let parsed;
|
|
303
|
+
try {
|
|
304
|
+
parsed = JSON.parse(stdout);
|
|
305
|
+
} catch {
|
|
306
|
+
return void 0;
|
|
307
|
+
}
|
|
308
|
+
if (!isRecord(parsed) || !isRecord(parsed.error) || typeof parsed.error.code !== "string") {
|
|
309
|
+
return void 0;
|
|
310
|
+
}
|
|
311
|
+
const { code, summary } = parsed.error;
|
|
312
|
+
return { code, summary: typeof summary === "string" ? summary : "" };
|
|
313
|
+
}
|
|
314
|
+
function readTrustRelationships(stdout) {
|
|
315
|
+
let parsed;
|
|
316
|
+
try {
|
|
317
|
+
parsed = JSON.parse(stdout);
|
|
318
|
+
} catch {
|
|
319
|
+
return void 0;
|
|
320
|
+
}
|
|
321
|
+
if (Array.isArray(parsed)) {
|
|
322
|
+
return parsed.filter((entry) => isRecord(entry) && "type" in entry);
|
|
323
|
+
}
|
|
324
|
+
if (!isRecord(parsed)) {
|
|
325
|
+
return void 0;
|
|
326
|
+
}
|
|
327
|
+
return "type" in parsed ? [parsed] : [];
|
|
328
|
+
}
|
|
329
|
+
function runNpmJson(command) {
|
|
330
|
+
try {
|
|
331
|
+
return { exitOk: true, stdout: execSync(command, { encoding: "utf8", stdio: "pipe" }) };
|
|
332
|
+
} catch (error) {
|
|
333
|
+
const stdout = isRecord(error) && typeof error.stdout === "string" ? error.stdout : "";
|
|
334
|
+
return { exitOk: false, stdout };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
228
337
|
function skipIfNotPublishable(workspace) {
|
|
229
338
|
return workspace.isPackage ? false : "package.json#private is true";
|
|
230
339
|
}
|
|
231
340
|
export {
|
|
232
341
|
buildWorkspaceCheck,
|
|
342
|
+
classifyNpmAuth,
|
|
343
|
+
classifyTrustQuery,
|
|
233
344
|
npm_auto_publish_default as default,
|
|
345
|
+
packagesChecklist,
|
|
234
346
|
skipIfNotPublishable
|
|
235
347
|
};
|
package/.readyup/manifest.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"readyupVersion": "0.23.0",
|
|
11
11
|
"source": "kits/default.ts",
|
|
12
12
|
"sourceHash": "afe26920",
|
|
13
|
-
"targetHash": "
|
|
13
|
+
"targetHash": "fecaa9b1"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"checklists": [
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"path": "kits/npm-auto-publish.js",
|
|
22
22
|
"readyupVersion": "0.23.0",
|
|
23
23
|
"source": "kits/npm-auto-publish.ts",
|
|
24
|
-
"sourceHash": "
|
|
25
|
-
"targetHash": "
|
|
24
|
+
"sourceHash": "5b58afac",
|
|
25
|
+
"targetHash": "610ed8a7"
|
|
26
26
|
}
|
|
27
27
|
]
|
|
28
28
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 10.2.2 — 2026-08-05
|
|
6
|
+
|
|
7
|
+
### 🐛 Bug fixes
|
|
8
|
+
|
|
9
|
+
- Report the missing npm login once, not per package (#598)
|
|
10
|
+
|
|
11
|
+
Fixes an issue where running release-kit's publish-readiness checks without an npm login reported a trusted-publisher failure for every published package. The missing login is now reported once, with `npm login` as the fix. An unreachable registry now has its own check. The trusted publisher check produces more informative failure messages.
|
|
12
|
+
|
|
13
|
+
- Make prepare all-or-nothing and report the tags file by path (#601)
|
|
14
|
+
|
|
15
|
+
Fixes an issue where a failed `prepare` could leave the release flow in a broken intermediate state. A release now lands completely or not at all, and a formatting failure no longer discards one that already landed. When a write fails partway, the error names every file that reached disk, every file that did not, and the commands that undo them.
|
|
16
|
+
|
|
17
|
+
`commit` and `tag` now report the full path of the tags file they looked for, and distinguish a missing file from one that could not be read, so that running from the wrong directory or worktree is no longer reported as a release that never happened.
|
|
18
|
+
|
|
19
|
+
Runs with `--with-release-notes` now list the preview files they write, and `--dry-run` names the ones a real run would write.
|
|
20
|
+
|
|
21
|
+
### 🧪 Tests
|
|
22
|
+
|
|
23
|
+
- Clear the deferred vitest test rules and enforce them (#602)
|
|
24
|
+
|
|
25
|
+
Fixes a subset of previously deferred lint violations and restores the severity of Vitest lint rules to "error" for purposes strict linting.
|
|
26
|
+
|
|
5
27
|
## 10.2.1 — 2026-08-04
|
|
6
28
|
|
|
7
29
|
### 🐛 Bug fixes
|
package/README.md
CHANGED
|
@@ -5,13 +5,21 @@ 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.2.
|
|
8
|
+
## Release notes — v10.2.2 (2026-08-05)
|
|
9
9
|
|
|
10
10
|
### 🐛 Bug fixes
|
|
11
11
|
|
|
12
|
-
-
|
|
12
|
+
- Report the missing npm login once, not per package (#598)
|
|
13
13
|
|
|
14
|
-
Fixes an issue where
|
|
14
|
+
Fixes an issue where running release-kit's publish-readiness checks without an npm login reported a trusted-publisher failure for every published package. The missing login is now reported once, with `npm login` as the fix. An unreachable registry now has its own check. The trusted publisher check produces more informative failure messages.
|
|
15
|
+
|
|
16
|
+
- Make prepare all-or-nothing and report the tags file by path (#601)
|
|
17
|
+
|
|
18
|
+
Fixes an issue where a failed `prepare` could leave the release flow in a broken intermediate state. A release now lands completely or not at all, and a formatting failure no longer discards one that already landed. When a write fails partway, the error names every file that reached disk, every file that did not, and the commands that undo them.
|
|
19
|
+
|
|
20
|
+
`commit` and `tag` now report the full path of the tags file they looked for, and distinguish a missing file from one that could not be read, so that running from the wrong directory or worktree is no longer reported as a release that never happened.
|
|
21
|
+
|
|
22
|
+
Runs with `--with-release-notes` now list the preview files they write, and `--dry-run` names the ones a real run would write.
|
|
15
23
|
<!-- /section:release-notes -->
|
|
16
24
|
|
|
17
25
|
## Installation
|
|
@@ -890,6 +898,8 @@ rdy list --from npm:@williamthorsen/release-kit # what release-
|
|
|
890
898
|
|
|
891
899
|
`--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
900
|
|
|
901
|
+
`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.
|
|
902
|
+
|
|
893
903
|
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
904
|
|
|
895
905
|
## External dependencies
|
|
@@ -79,7 +79,7 @@ function transformReleases(releases, devOnlySections) {
|
|
|
79
79
|
continue;
|
|
80
80
|
}
|
|
81
81
|
const version = extractVersion(release.version);
|
|
82
|
-
const date = release.timestamp !== undefined ? new Date(release.timestamp *
|
|
82
|
+
const date = release.timestamp !== undefined ? new Date(release.timestamp * 1_000).toISOString().slice(0, 10) : 'unreleased';
|
|
83
83
|
const sectionMap = new Map();
|
|
84
84
|
for (const commit of release.commits ?? []) {
|
|
85
85
|
const group = stripCommentPrefix(commit.group ?? 'Other');
|
|
@@ -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);
|
|
@@ -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
|
}
|
|
@@ -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);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PlannedWrite } from './releasePlan.js';
|
|
2
|
+
import type { ChangelogEntry } from './types.js';
|
|
3
|
+
export interface PlanReleaseNotesPreviewsOptions {
|
|
4
|
+
workspacePath: string;
|
|
5
|
+
tag: string;
|
|
6
|
+
entries: readonly ChangelogEntry[];
|
|
7
|
+
sectionOrder: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface ReleaseNotesPreviewsPlan {
|
|
10
|
+
writes: PlannedWrite[];
|
|
11
|
+
warnings: string[];
|
|
12
|
+
}
|
|
13
|
+
export declare function planReleaseNotesPreviews(options: PlanReleaseNotesPreviewsOptions): ReleaseNotesPreviewsPlan;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { extractVersion } from "./changelogJsonUtils.js";
|
|
4
|
+
import { renderInjectedReadmeFromEntries } from "./injectReleaseNotesIntoReadme.js";
|
|
5
|
+
export function planReleaseNotesPreviews(options) {
|
|
6
|
+
const { workspacePath, tag, entries, sectionOrder } = options;
|
|
7
|
+
const warnings = [];
|
|
8
|
+
const readmePath = path.join(workspacePath, 'README.md');
|
|
9
|
+
const readme = readWorkspaceReadme(readmePath, warnings);
|
|
10
|
+
const rendered = renderInjectedReadmeFromEntries(readme.content, entries, tag, sectionOrder);
|
|
11
|
+
if (rendered === undefined) {
|
|
12
|
+
return { writes: [], warnings };
|
|
13
|
+
}
|
|
14
|
+
if (!readme.exists && !readme.unreadable) {
|
|
15
|
+
warnings.push(`${readmePath} not found; skipping injected-README preview but still writing standalone release notes`);
|
|
16
|
+
}
|
|
17
|
+
const version = extractVersion(tag);
|
|
18
|
+
const docsDir = path.join(workspacePath, 'docs');
|
|
19
|
+
const writes = [];
|
|
20
|
+
if (readme.exists) {
|
|
21
|
+
writes.push({ path: path.join(docsDir, `README.v${version}.md`), content: rendered.injectedReadme });
|
|
22
|
+
}
|
|
23
|
+
writes.push({
|
|
24
|
+
path: path.join(docsDir, `RELEASE_NOTES.v${version}.md`),
|
|
25
|
+
content: rendered.releaseNotesMarkdown.endsWith('\n')
|
|
26
|
+
? rendered.releaseNotesMarkdown
|
|
27
|
+
: `${rendered.releaseNotesMarkdown}\n`,
|
|
28
|
+
});
|
|
29
|
+
return { writes, warnings };
|
|
30
|
+
}
|
|
31
|
+
function readWorkspaceReadme(readmePath, warnings) {
|
|
32
|
+
if (!existsSync(readmePath)) {
|
|
33
|
+
return { content: '', exists: false, unreadable: false };
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return { content: readFileSync(readmePath, 'utf8'), exists: true, unreadable: false };
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
40
|
+
warnings.push(`failed to read ${readmePath}: ${message}; skipping injected-README preview`);
|
|
41
|
+
return { content: '', exists: false, unreadable: true };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PlannedWrite } from './releasePlan.js';
|
|
2
|
+
import type { ReleaseType } from './types.js';
|
|
3
|
+
export interface VersionBumpPlan {
|
|
4
|
+
currentVersion: string;
|
|
5
|
+
newVersion: string;
|
|
6
|
+
writes: PlannedWrite[];
|
|
7
|
+
}
|
|
8
|
+
export declare function planVersionBump(packageFiles: readonly string[], releaseType: ReleaseType): VersionBumpPlan;
|
|
9
|
+
export declare function planVersionSet(packageFiles: readonly string[], newVersion: string): VersionBumpPlan;
|
|
@@ -1,43 +1,32 @@
|
|
|
1
|
-
import { readFileSync
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { bumpVersion } from "./bumpVersion.js";
|
|
3
3
|
function isPackageJson(value) {
|
|
4
4
|
return typeof value === 'object' && value !== null && 'version' in value && typeof value.version === 'string';
|
|
5
5
|
}
|
|
6
|
-
export function
|
|
7
|
-
const firstFile = packageFiles
|
|
8
|
-
if (firstFile === undefined) {
|
|
9
|
-
throw new Error('No package files specified');
|
|
10
|
-
}
|
|
11
|
-
const firstPkg = readPackageJson(firstFile);
|
|
6
|
+
export function planVersionBump(packageFiles, releaseType) {
|
|
7
|
+
const { firstFile, firstPkg } = readPrimaryPackage(packageFiles);
|
|
12
8
|
const currentVersion = firstPkg.version;
|
|
13
9
|
const newVersion = bumpVersion(currentVersion, releaseType);
|
|
14
|
-
|
|
15
|
-
return { currentVersion, newVersion, files: [...packageFiles] };
|
|
10
|
+
return { currentVersion, newVersion, writes: renderVersionWrites(packageFiles, firstFile, firstPkg, newVersion) };
|
|
16
11
|
}
|
|
17
|
-
export function
|
|
12
|
+
export function planVersionSet(packageFiles, newVersion) {
|
|
13
|
+
const { firstFile, firstPkg } = readPrimaryPackage(packageFiles);
|
|
14
|
+
const currentVersion = firstPkg.version;
|
|
15
|
+
return { currentVersion, newVersion, writes: renderVersionWrites(packageFiles, firstFile, firstPkg, newVersion) };
|
|
16
|
+
}
|
|
17
|
+
function readPrimaryPackage(packageFiles) {
|
|
18
18
|
const firstFile = packageFiles[0];
|
|
19
19
|
if (firstFile === undefined) {
|
|
20
20
|
throw new Error('No package files specified');
|
|
21
21
|
}
|
|
22
|
-
|
|
23
|
-
const currentVersion = firstPkg.version;
|
|
24
|
-
writeVersionToAllFiles(packageFiles, firstFile, firstPkg, newVersion, dryRun);
|
|
25
|
-
return { currentVersion, newVersion, files: [...packageFiles] };
|
|
22
|
+
return { firstFile, firstPkg: readPackageJson(firstFile) };
|
|
26
23
|
}
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
if (dryRun) {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
24
|
+
function renderVersionWrites(packageFiles, firstFile, firstPkg, newVersion) {
|
|
25
|
+
return packageFiles.map((filePath) => {
|
|
32
26
|
const pkg = filePath === firstFile ? firstPkg : readPackageJson(filePath);
|
|
33
27
|
pkg.version = newVersion;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
throw new Error(`Failed to write ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
28
|
+
return { path: filePath, content: JSON.stringify(pkg, null, 2) + '\n' };
|
|
29
|
+
});
|
|
41
30
|
}
|
|
42
31
|
function readPackageJson(filePath) {
|
|
43
32
|
let content;
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import type { WriteResult } from '@williamthorsen/nmr-core';
|
|
2
1
|
import type { ReleaseType } from './types.js';
|
|
3
|
-
export declare const RELEASE_TAGS_FILE = "tmp/.release-tags";
|
|
4
|
-
export declare const RELEASE_SUMMARY_FILE = "tmp/.release-summary";
|
|
5
2
|
export declare const prepareFlagSchema: {
|
|
6
3
|
dryRun: {
|
|
7
4
|
long: string;
|
|
@@ -42,5 +39,4 @@ export declare function parseArgs(argv: string[]): {
|
|
|
42
39
|
setVersion: string | undefined;
|
|
43
40
|
withReleaseNotes: boolean;
|
|
44
41
|
};
|
|
45
|
-
export declare function writeReleaseTags(tags: string[], dryRun: boolean): WriteResult | undefined;
|
|
46
42
|
export declare function prepareCommand(argv: string[]): Promise<void>;
|