@williamthorsen/release-kit 4.8.0 → 5.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 +74 -4
- package/README.md +310 -40
- package/cliff.toml.template +2 -1
- package/dist/esm/.cache +1 -1
- package/dist/esm/bin/release-kit.js +44 -13
- package/dist/esm/buildDependencyGraph.d.ts +3 -3
- package/dist/esm/buildDependencyGraph.js +10 -10
- package/dist/esm/buildReleaseSummary.js +4 -4
- package/dist/esm/bumpAllVersions.d.ts +1 -0
- package/dist/esm/bumpAllVersions.js +16 -2
- package/dist/esm/bumpVersion.js +3 -0
- package/dist/esm/commitCommand.js +1 -1
- package/dist/esm/compareVersions.d.ts +1 -0
- package/dist/esm/compareVersions.js +27 -0
- package/dist/esm/createGithubRelease.d.ts +6 -2
- package/dist/esm/createGithubRelease.js +17 -17
- package/dist/esm/createGithubReleaseCommand.d.ts +1 -0
- package/dist/esm/createGithubReleaseCommand.js +41 -0
- package/dist/esm/defaults.js +5 -3
- package/dist/esm/deriveWorkspaceConfig.d.ts +2 -0
- package/dist/esm/deriveWorkspaceConfig.js +37 -0
- package/dist/esm/detectUndeclaredTagPrefixes.d.ts +7 -0
- package/dist/esm/detectUndeclaredTagPrefixes.js +46 -0
- package/dist/esm/generateChangelogJson.js +37 -1
- package/dist/esm/generateChangelogs.d.ts +1 -1
- package/dist/esm/generateChangelogs.js +14 -3
- package/dist/esm/getCommitsSinceTarget.d.ts +1 -1
- package/dist/esm/getCommitsSinceTarget.js +8 -4
- package/dist/esm/index.d.ts +7 -3
- package/dist/esm/index.js +10 -3
- package/dist/esm/init/initCommand.js +1 -1
- package/dist/esm/init/scaffold.d.ts +1 -1
- package/dist/esm/init/scaffold.js +8 -5
- package/dist/esm/init/templates.d.ts +1 -0
- package/dist/esm/init/templates.js +33 -3
- package/dist/esm/injectReleaseNotesIntoReadme.d.ts +6 -1
- package/dist/esm/injectReleaseNotesIntoReadme.js +20 -7
- package/dist/esm/loadConfig.d.ts +2 -1
- package/dist/esm/loadConfig.js +65 -12
- package/dist/esm/parseRequestedTags.d.ts +1 -0
- package/dist/esm/parseRequestedTags.js +10 -0
- package/dist/esm/prepareCommand.d.ts +3 -1
- package/dist/esm/prepareCommand.js +74 -26
- package/dist/esm/previewTagPrefixes.d.ts +30 -0
- package/dist/esm/previewTagPrefixes.js +120 -0
- package/dist/esm/propagateBumps.d.ts +1 -0
- package/dist/esm/propagateBumps.js +1 -1
- package/dist/esm/publishCommand.js +8 -13
- package/dist/esm/pushCommand.js +5 -4
- package/dist/esm/readCurrentVersion.d.ts +1 -0
- package/dist/esm/readCurrentVersion.js +21 -0
- package/dist/esm/releasePrepare.d.ts +2 -0
- package/dist/esm/releasePrepare.js +72 -30
- package/dist/esm/releasePrepareMono.js +235 -112
- package/dist/esm/renderReleaseNotes.d.ts +1 -0
- package/dist/esm/renderReleaseNotes.js +29 -2
- package/dist/esm/reportPrepare.js +100 -73
- package/dist/esm/resolveCliffConfigPath.js +1 -1
- package/dist/esm/resolveCommandTags.d.ts +1 -1
- package/dist/esm/resolveCommandTags.js +17 -13
- package/dist/esm/resolveReleaseNotesConfig.d.ts +8 -1
- package/dist/esm/resolveReleaseNotesConfig.js +17 -7
- package/dist/esm/resolveReleaseTags.d.ts +2 -1
- package/dist/esm/resolveReleaseTags.js +19 -14
- package/dist/esm/showTagPrefixesCommand.d.ts +1 -0
- package/dist/esm/showTagPrefixesCommand.js +84 -0
- package/dist/esm/sync-labels/initCommand.js +1 -1
- package/dist/esm/sync-labels/presets.js +1 -1
- package/dist/esm/tagCommand.js +1 -1
- package/dist/esm/types.d.ts +22 -7
- package/dist/esm/validateConfig.js +179 -36
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/writeReleaseNotesPreviews.d.ts +18 -0
- package/dist/esm/writeReleaseNotesPreviews.js +65 -0
- package/package.json +2 -2
- package/dist/esm/component.d.ts +0 -2
- package/dist/esm/component.js +0 -14
- package/dist/esm/findPackageRoot.d.ts +0 -1
- package/dist/esm/findPackageRoot.js +0 -17
- package/dist/esm/githubReleaseCommand.d.ts +0 -1
- package/dist/esm/githubReleaseCommand.js +0 -35
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { parseArgs, translateParseError } from "@williamthorsen/
|
|
2
|
+
import { parseArgs, translateParseError } from "@williamthorsen/nmr-core";
|
|
3
3
|
import { commitCommand } from "../commitCommand.js";
|
|
4
|
-
import {
|
|
4
|
+
import { createGithubReleaseCommand } from "../createGithubReleaseCommand.js";
|
|
5
5
|
import { initCommand } from "../init/initCommand.js";
|
|
6
6
|
import { prepareCommand } from "../prepareCommand.js";
|
|
7
7
|
import { publishCommand } from "../publishCommand.js";
|
|
8
8
|
import { pushCommand } from "../pushCommand.js";
|
|
9
|
+
import { showTagPrefixesCommand } from "../showTagPrefixesCommand.js";
|
|
9
10
|
import { generateCommand } from "../sync-labels/generateCommand.js";
|
|
10
11
|
import { syncLabelsInitCommand } from "../sync-labels/initCommand.js";
|
|
11
12
|
import { syncLabelsCommand } from "../sync-labels/syncCommand.js";
|
|
@@ -21,7 +22,8 @@ Commands:
|
|
|
21
22
|
tag Create annotated git tags from the tags file
|
|
22
23
|
push Push release commit and tags (one push per tag)
|
|
23
24
|
publish Publish packages with release tags on HEAD
|
|
24
|
-
github-release
|
|
25
|
+
create-github-release Create GitHub Releases from changelog.json for tags on HEAD
|
|
26
|
+
show-tag-prefixes Show derived and declared legacy tag prefixes per workspace
|
|
25
27
|
init Initialize release-kit in the current repository
|
|
26
28
|
sync-labels Manage GitHub label synchronization
|
|
27
29
|
|
|
@@ -100,9 +102,13 @@ Run release preparation with automatic workspace discovery.
|
|
|
100
102
|
|
|
101
103
|
Options:
|
|
102
104
|
--dry-run Run without modifying any files
|
|
103
|
-
--bump=major|minor|patch Override the bump type for all
|
|
105
|
+
--bump=major|minor|patch Override the bump type for all workspaces
|
|
106
|
+
--set-version=X.Y.Z Set an explicit version; bypasses commit-derived bumps. Requires --only in monorepo mode.
|
|
104
107
|
--no-git-checks, -n Skip the clean-working-tree check
|
|
105
|
-
--only=name1,name2 Only process the named
|
|
108
|
+
--only=name1,name2 Only process the named workspaces (comma-separated, monorepo only)
|
|
109
|
+
--with-release-notes Also write per-workspace release-notes previews under {workspacePath}/docs/
|
|
110
|
+
(docs/README.v{version}.md and docs/RELEASE_NOTES.v{version}.md).
|
|
111
|
+
Recommended .gitignore entry: packages/*/docs/*.v*.md (or docs/*.v*.md).
|
|
106
112
|
--help, -h Show this help message
|
|
107
113
|
`);
|
|
108
114
|
}
|
|
@@ -139,23 +145,36 @@ fires a separate workflow run per tag.
|
|
|
139
145
|
|
|
140
146
|
Options:
|
|
141
147
|
--dry-run Preview without pushing
|
|
142
|
-
--
|
|
148
|
+
--tags=tag1,tag2 Only push the named tags (comma-separated, full tag names)
|
|
143
149
|
--tags-only Skip the branch push (push tags only)
|
|
144
150
|
--help, -h Show this help message
|
|
145
151
|
`);
|
|
146
152
|
}
|
|
147
|
-
function
|
|
153
|
+
function showCreateGithubReleaseHelp() {
|
|
148
154
|
console.info(`
|
|
149
|
-
Usage: release-kit github-release [options]
|
|
155
|
+
Usage: release-kit create-github-release [options]
|
|
150
156
|
|
|
151
157
|
Create GitHub Releases from changelog.json for tags on HEAD.
|
|
152
158
|
|
|
153
159
|
Options:
|
|
154
160
|
--dry-run Preview without creating releases
|
|
155
|
-
--
|
|
161
|
+
--tags=tag1,tag2 Only create releases for the named tags (comma-separated, full tag names)
|
|
156
162
|
--help, -h Show this help message
|
|
157
163
|
`);
|
|
158
164
|
}
|
|
165
|
+
function showShowTagPrefixesHelp() {
|
|
166
|
+
console.info(`
|
|
167
|
+
Usage: release-kit show-tag-prefixes [options]
|
|
168
|
+
|
|
169
|
+
Print a per-workspace table of derived tag prefixes, tag counts, and declared
|
|
170
|
+
legacy tag prefixes. Surfaces any release-shaped tags whose prefix is neither a
|
|
171
|
+
derived prefix nor declared in \`legacyIdentities\`, with a copy-pasteable
|
|
172
|
+
config snippet.
|
|
173
|
+
|
|
174
|
+
Options:
|
|
175
|
+
--help, -h Show this help message
|
|
176
|
+
`);
|
|
177
|
+
}
|
|
159
178
|
function showPublishHelp() {
|
|
160
179
|
console.info(`
|
|
161
180
|
Usage: release-kit publish [options]
|
|
@@ -165,7 +184,7 @@ Publish packages that have release tags on HEAD.
|
|
|
165
184
|
Options:
|
|
166
185
|
--dry-run Preview without publishing
|
|
167
186
|
--no-git-checks Skip git checks (pnpm only)
|
|
168
|
-
--
|
|
187
|
+
--tags=tag1,tag2 Only publish the named tags (comma-separated, full tag names)
|
|
169
188
|
--provenance Generate provenance statement (requires OIDC, not supported by classic yarn)
|
|
170
189
|
--help, -h Show this help message
|
|
171
190
|
`);
|
|
@@ -218,12 +237,12 @@ if (command === "push") {
|
|
|
218
237
|
await pushCommand(flags);
|
|
219
238
|
process.exit(0);
|
|
220
239
|
}
|
|
221
|
-
if (command === "github-release") {
|
|
240
|
+
if (command === "create-github-release") {
|
|
222
241
|
if (flags.some((f) => f === "--help" || f === "-h")) {
|
|
223
|
-
|
|
242
|
+
showCreateGithubReleaseHelp();
|
|
224
243
|
process.exit(0);
|
|
225
244
|
}
|
|
226
|
-
await
|
|
245
|
+
await createGithubReleaseCommand(flags);
|
|
227
246
|
process.exit(0);
|
|
228
247
|
}
|
|
229
248
|
if (command === "publish") {
|
|
@@ -234,6 +253,18 @@ if (command === "publish") {
|
|
|
234
253
|
await publishCommand(flags);
|
|
235
254
|
process.exit(0);
|
|
236
255
|
}
|
|
256
|
+
if (command === "show-tag-prefixes") {
|
|
257
|
+
if (flags.some((f) => f === "--help" || f === "-h")) {
|
|
258
|
+
showShowTagPrefixesHelp();
|
|
259
|
+
process.exit(0);
|
|
260
|
+
}
|
|
261
|
+
if (flags.length > 0) {
|
|
262
|
+
console.error(`Error: Unknown option: ${flags[0]}`);
|
|
263
|
+
process.exit(1);
|
|
264
|
+
}
|
|
265
|
+
const exitCode = await showTagPrefixesCommand();
|
|
266
|
+
process.exit(exitCode);
|
|
267
|
+
}
|
|
237
268
|
if (command === "init") {
|
|
238
269
|
if (flags.some((f) => f === "--help" || f === "-h")) {
|
|
239
270
|
showInitHelp();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { WorkspaceConfig } from './types.ts';
|
|
2
2
|
export interface DependencyGraph {
|
|
3
3
|
packageNameToDir: Map<string, string>;
|
|
4
4
|
dirToPackageName: Map<string, string>;
|
|
5
|
-
dependentsOf: Map<string,
|
|
5
|
+
dependentsOf: Map<string, WorkspaceConfig[]>;
|
|
6
6
|
}
|
|
7
|
-
export declare function buildDependencyGraph(
|
|
7
|
+
export declare function buildDependencyGraph(workspaces: readonly WorkspaceConfig[]): DependencyGraph;
|
|
@@ -2,25 +2,25 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
function isPackageJsonSubset(value) {
|
|
3
3
|
return typeof value === "object" && value !== null;
|
|
4
4
|
}
|
|
5
|
-
function buildDependencyGraph(
|
|
5
|
+
function buildDependencyGraph(workspaces) {
|
|
6
6
|
const packageNameToDir = /* @__PURE__ */ new Map();
|
|
7
7
|
const dirToPackageName = /* @__PURE__ */ new Map();
|
|
8
8
|
const dependentsOf = /* @__PURE__ */ new Map();
|
|
9
|
-
const
|
|
10
|
-
for (const
|
|
11
|
-
const primaryPackageFile =
|
|
9
|
+
const workspacePackages = /* @__PURE__ */ new Map();
|
|
10
|
+
for (const workspace of workspaces) {
|
|
11
|
+
const primaryPackageFile = workspace.packageFiles[0];
|
|
12
12
|
if (primaryPackageFile === void 0) {
|
|
13
13
|
continue;
|
|
14
14
|
}
|
|
15
15
|
const pkg = readPackageJsonSubset(primaryPackageFile);
|
|
16
|
-
|
|
16
|
+
workspacePackages.set(workspace, pkg);
|
|
17
17
|
if (pkg.name === void 0) {
|
|
18
18
|
continue;
|
|
19
19
|
}
|
|
20
|
-
packageNameToDir.set(pkg.name,
|
|
21
|
-
dirToPackageName.set(
|
|
20
|
+
packageNameToDir.set(pkg.name, workspace.dir);
|
|
21
|
+
dirToPackageName.set(workspace.dir, pkg.name);
|
|
22
22
|
}
|
|
23
|
-
for (const [
|
|
23
|
+
for (const [workspace, pkg] of workspacePackages) {
|
|
24
24
|
const allDeps = { ...pkg.dependencies, ...pkg.peerDependencies };
|
|
25
25
|
for (const [depName, depVersion] of Object.entries(allDeps)) {
|
|
26
26
|
if (typeof depVersion !== "string" || !depVersion.startsWith("workspace:")) {
|
|
@@ -28,9 +28,9 @@ function buildDependencyGraph(components) {
|
|
|
28
28
|
}
|
|
29
29
|
const existing = dependentsOf.get(depName);
|
|
30
30
|
if (existing === void 0) {
|
|
31
|
-
dependentsOf.set(depName, [
|
|
31
|
+
dependentsOf.set(depName, [workspace]);
|
|
32
32
|
} else {
|
|
33
|
-
existing.push(
|
|
33
|
+
existing.push(workspace);
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { stripScope } from "./stripScope.js";
|
|
2
2
|
function buildReleaseSummary(result) {
|
|
3
3
|
const sections = [];
|
|
4
|
-
for (const
|
|
5
|
-
if (
|
|
4
|
+
for (const workspace of result.workspaces) {
|
|
5
|
+
if (workspace.status !== "released" || workspace.tag === void 0) {
|
|
6
6
|
continue;
|
|
7
7
|
}
|
|
8
|
-
const commits =
|
|
8
|
+
const commits = workspace.commits;
|
|
9
9
|
if (commits === void 0 || commits.length === 0) {
|
|
10
10
|
continue;
|
|
11
11
|
}
|
|
12
|
-
const lines = [
|
|
12
|
+
const lines = [workspace.tag];
|
|
13
13
|
for (const commit of commits) {
|
|
14
14
|
lines.push(`- ${stripScope(commit.message)}`);
|
|
15
15
|
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
import type { BumpResult, ReleaseType } from './types.ts';
|
|
2
2
|
export declare function bumpAllVersions(packageFiles: readonly string[], releaseType: ReleaseType, dryRun: boolean): BumpResult;
|
|
3
|
+
export declare function setAllVersions(packageFiles: readonly string[], newVersion: string, dryRun: boolean): BumpResult;
|
|
@@ -11,6 +11,20 @@ function bumpAllVersions(packageFiles, releaseType, dryRun) {
|
|
|
11
11
|
const firstPkg = readPackageJson(firstFile);
|
|
12
12
|
const currentVersion = firstPkg.version;
|
|
13
13
|
const newVersion = bumpVersion(currentVersion, releaseType);
|
|
14
|
+
writeVersionToAllFiles(packageFiles, firstFile, firstPkg, newVersion, dryRun);
|
|
15
|
+
return { currentVersion, newVersion, files: [...packageFiles] };
|
|
16
|
+
}
|
|
17
|
+
function setAllVersions(packageFiles, newVersion, dryRun) {
|
|
18
|
+
const firstFile = packageFiles[0];
|
|
19
|
+
if (firstFile === void 0) {
|
|
20
|
+
throw new Error("No package files specified");
|
|
21
|
+
}
|
|
22
|
+
const firstPkg = readPackageJson(firstFile);
|
|
23
|
+
const currentVersion = firstPkg.version;
|
|
24
|
+
writeVersionToAllFiles(packageFiles, firstFile, firstPkg, newVersion, dryRun);
|
|
25
|
+
return { currentVersion, newVersion, files: [...packageFiles] };
|
|
26
|
+
}
|
|
27
|
+
function writeVersionToAllFiles(packageFiles, firstFile, firstPkg, newVersion, dryRun) {
|
|
14
28
|
for (const filePath of packageFiles) {
|
|
15
29
|
if (dryRun) {
|
|
16
30
|
continue;
|
|
@@ -23,7 +37,6 @@ function bumpAllVersions(packageFiles, releaseType, dryRun) {
|
|
|
23
37
|
throw new Error(`Failed to write ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
24
38
|
}
|
|
25
39
|
}
|
|
26
|
-
return { currentVersion, newVersion, files: [...packageFiles] };
|
|
27
40
|
}
|
|
28
41
|
function readPackageJson(filePath) {
|
|
29
42
|
let content;
|
|
@@ -44,5 +57,6 @@ function readPackageJson(filePath) {
|
|
|
44
57
|
return parsed;
|
|
45
58
|
}
|
|
46
59
|
export {
|
|
47
|
-
bumpAllVersions
|
|
60
|
+
bumpAllVersions,
|
|
61
|
+
setAllVersions
|
|
48
62
|
};
|
package/dist/esm/bumpVersion.js
CHANGED
|
@@ -14,6 +14,9 @@ function bumpVersion(version, releaseType) {
|
|
|
14
14
|
const patchNum = Number.parseInt(patch, 10);
|
|
15
15
|
switch (releaseType) {
|
|
16
16
|
case "major":
|
|
17
|
+
if (majorNum === 0) {
|
|
18
|
+
return `0.${minorNum + 1}.0`;
|
|
19
|
+
}
|
|
17
20
|
return `${majorNum + 1}.0.0`;
|
|
18
21
|
case "minor":
|
|
19
22
|
return `${majorNum}.${minorNum + 1}.0`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
-
import { parseArgs, translateParseError } from "@williamthorsen/
|
|
3
|
+
import { parseArgs, translateParseError } 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" }
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isForwardVersion(current: string, target: string): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const CANONICAL_SEMVER_PATTERN = /^(\d+)\.(\d+)\.(\d+)$/;
|
|
2
|
+
function parseCanonicalSemver(version) {
|
|
3
|
+
const match = version.match(CANONICAL_SEMVER_PATTERN);
|
|
4
|
+
if (!match) {
|
|
5
|
+
throw new Error(`Invalid semver version: '${version}'`);
|
|
6
|
+
}
|
|
7
|
+
const [, major = "", minor = "", patch = ""] = match;
|
|
8
|
+
return {
|
|
9
|
+
major: Number.parseInt(major, 10),
|
|
10
|
+
minor: Number.parseInt(minor, 10),
|
|
11
|
+
patch: Number.parseInt(patch, 10)
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function isForwardVersion(current, target) {
|
|
15
|
+
const currentParsed = parseCanonicalSemver(current);
|
|
16
|
+
const targetParsed = parseCanonicalSemver(target);
|
|
17
|
+
if (targetParsed.major !== currentParsed.major) {
|
|
18
|
+
return targetParsed.major > currentParsed.major;
|
|
19
|
+
}
|
|
20
|
+
if (targetParsed.minor !== currentParsed.minor) {
|
|
21
|
+
return targetParsed.minor > currentParsed.minor;
|
|
22
|
+
}
|
|
23
|
+
return targetParsed.patch > currentParsed.patch;
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
isForwardVersion
|
|
27
|
+
};
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import type { ReleaseNotesConfig } from './types.ts';
|
|
2
1
|
export interface CreateGithubReleaseOptions {
|
|
3
2
|
tag: string;
|
|
4
3
|
changelogJsonPath: string;
|
|
5
4
|
dryRun: boolean;
|
|
5
|
+
sectionOrder?: string[];
|
|
6
6
|
}
|
|
7
7
|
export declare function createGithubRelease(options: CreateGithubReleaseOptions): boolean;
|
|
8
|
+
export interface CreateGithubReleasesOutcome {
|
|
9
|
+
created: string[];
|
|
10
|
+
skipped: string[];
|
|
11
|
+
}
|
|
8
12
|
export declare function createGithubReleases(tags: Array<{
|
|
9
13
|
tag: string;
|
|
10
14
|
workspacePath: string;
|
|
11
|
-
}>,
|
|
15
|
+
}>, changelogJsonOutputPath: string, dryRun: boolean, sectionOrder?: string[]): CreateGithubReleasesOutcome;
|
|
@@ -4,7 +4,7 @@ import { join } from "node:path";
|
|
|
4
4
|
import { extractVersion, readChangelogEntries } from "./changelogJsonUtils.js";
|
|
5
5
|
import { matchesAudience, renderReleaseNotesSingle } from "./renderReleaseNotes.js";
|
|
6
6
|
function createGithubRelease(options) {
|
|
7
|
-
const { tag, changelogJsonPath, dryRun } = options;
|
|
7
|
+
const { tag, changelogJsonPath, dryRun, sectionOrder } = options;
|
|
8
8
|
if (!existsSync(changelogJsonPath)) {
|
|
9
9
|
console.warn(`Warning: ${changelogJsonPath} not found; skipping GitHub Release creation`);
|
|
10
10
|
return false;
|
|
@@ -25,7 +25,8 @@ function createGithubRelease(options) {
|
|
|
25
25
|
}
|
|
26
26
|
const body = renderReleaseNotesSingle(entry, {
|
|
27
27
|
filter: matchesAudience("all"),
|
|
28
|
-
includeHeading: false
|
|
28
|
+
includeHeading: false,
|
|
29
|
+
...sectionOrder === void 0 ? {} : { sectionOrder }
|
|
29
30
|
});
|
|
30
31
|
if (body.trim() === "") {
|
|
31
32
|
return false;
|
|
@@ -35,24 +36,23 @@ function createGithubRelease(options) {
|
|
|
35
36
|
console.info(`[dry-run] Would run: gh ${args.join(" ")}`);
|
|
36
37
|
return true;
|
|
37
38
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return true;
|
|
41
|
-
} catch (error) {
|
|
42
|
-
console.warn(
|
|
43
|
-
`Warning: failed to create GitHub Release for ${tag}: ${error instanceof Error ? error.message : String(error)}`
|
|
44
|
-
);
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
39
|
+
execFileSync("gh", args, { stdio: "inherit" });
|
|
40
|
+
return true;
|
|
47
41
|
}
|
|
48
|
-
function createGithubReleases(tags,
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
42
|
+
function createGithubReleases(tags, changelogJsonOutputPath, dryRun, sectionOrder) {
|
|
43
|
+
const created = [];
|
|
44
|
+
const skipped = [];
|
|
52
45
|
for (const { tag, workspacePath } of tags) {
|
|
53
46
|
const changelogJsonPath = join(workspacePath, changelogJsonOutputPath);
|
|
54
|
-
createGithubRelease({
|
|
55
|
-
|
|
47
|
+
const result = createGithubRelease({
|
|
48
|
+
tag,
|
|
49
|
+
changelogJsonPath,
|
|
50
|
+
dryRun,
|
|
51
|
+
...sectionOrder === void 0 ? {} : { sectionOrder }
|
|
52
|
+
});
|
|
53
|
+
(result ? created : skipped).push(tag);
|
|
54
|
+
}
|
|
55
|
+
return { created, skipped };
|
|
56
56
|
}
|
|
57
57
|
export {
|
|
58
58
|
createGithubRelease,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createGithubReleaseCommand(argv: string[]): Promise<void>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { parseArgs, translateParseError } from "@williamthorsen/nmr-core";
|
|
2
|
+
import { createGithubReleases } from "./createGithubRelease.js";
|
|
3
|
+
import { parseRequestedTags } from "./parseRequestedTags.js";
|
|
4
|
+
import { resolveCommandTags } from "./resolveCommandTags.js";
|
|
5
|
+
import { resolveReleaseNotesConfig } from "./resolveReleaseNotesConfig.js";
|
|
6
|
+
const createGithubReleaseFlagSchema = {
|
|
7
|
+
dryRun: { long: "--dry-run", type: "boolean" },
|
|
8
|
+
tags: { long: "--tags", type: "string" }
|
|
9
|
+
};
|
|
10
|
+
async function createGithubReleaseCommand(argv) {
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = parseArgs(argv, createGithubReleaseFlagSchema);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(`Error: ${translateParseError(error)}`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const { dryRun } = parsed.flags;
|
|
19
|
+
const requestedTags = parseRequestedTags(parsed.flags.tags);
|
|
20
|
+
const resolvedTags = await resolveCommandTags(requestedTags);
|
|
21
|
+
const { changelogJsonOutputPath, sectionOrder } = await resolveReleaseNotesConfig({ strictLoad: true });
|
|
22
|
+
let outcome;
|
|
23
|
+
try {
|
|
24
|
+
outcome = createGithubReleases(resolvedTags, changelogJsonOutputPath, dryRun, sectionOrder);
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error(`Error creating GitHub Releases: ${error instanceof Error ? error.message : String(error)}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
if (requestedTags !== void 0 && outcome.created.length === 0) {
|
|
30
|
+
console.error(
|
|
31
|
+
`Error: no GitHub Releases were created for requested tags: ${outcome.skipped.join(", ")}. Each was skipped (missing changelog entry, no all-audience content, or empty rendered body).`
|
|
32
|
+
);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
if (outcome.skipped.length > 0) {
|
|
36
|
+
console.info(`Skipped ${outcome.skipped.length} tag(s) with no releasable content: ${outcome.skipped.join(", ")}.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
createGithubReleaseCommand
|
|
41
|
+
};
|
package/dist/esm/defaults.js
CHANGED
|
@@ -11,6 +11,9 @@ const DEFAULT_WORK_TYPES = {
|
|
|
11
11
|
ci: { header: "CI" },
|
|
12
12
|
deps: { header: "Dependencies", aliases: ["dep"] },
|
|
13
13
|
docs: { header: "Documentation", aliases: ["doc"] },
|
|
14
|
+
ai: { header: "Agentic support" },
|
|
15
|
+
// `fmt` is retained for bump-determination (`parseCommitMessage`), even though
|
|
16
|
+
// `cliff.toml.template` skips `fmt:` commits so they never enter the changelog.
|
|
14
17
|
fmt: { header: "Formatting" }
|
|
15
18
|
};
|
|
16
19
|
const DEFAULT_VERSION_PATTERNS = {
|
|
@@ -20,11 +23,10 @@ const DEFAULT_VERSION_PATTERNS = {
|
|
|
20
23
|
const DEFAULT_CHANGELOG_JSON_CONFIG = {
|
|
21
24
|
enabled: true,
|
|
22
25
|
outputPath: ".meta/changelog.json",
|
|
23
|
-
devOnlySections: ["CI", "Dependencies", "
|
|
26
|
+
devOnlySections: ["Agentic support", "CI", "Dependencies", "Internal", "Refactoring", "Tests", "Tooling"]
|
|
24
27
|
};
|
|
25
28
|
const DEFAULT_RELEASE_NOTES_CONFIG = {
|
|
26
|
-
shouldInjectIntoReadme: false
|
|
27
|
-
shouldCreateGithubRelease: false
|
|
29
|
+
shouldInjectIntoReadme: false
|
|
28
30
|
};
|
|
29
31
|
export {
|
|
30
32
|
DEFAULT_CHANGELOG_JSON_CONFIG,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { isRecord } from "./typeGuards.js";
|
|
4
|
+
function deriveWorkspaceConfig(workspacePath) {
|
|
5
|
+
const dir = basename(workspacePath);
|
|
6
|
+
const packageJsonPath = `${workspacePath}/package.json`;
|
|
7
|
+
let parsed;
|
|
8
|
+
try {
|
|
9
|
+
parsed = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
10
|
+
} catch (error) {
|
|
11
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12
|
+
throw new Error(`Failed to read ${packageJsonPath}: ${message}`);
|
|
13
|
+
}
|
|
14
|
+
const name = isRecord(parsed) ? parsed.name : void 0;
|
|
15
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
16
|
+
throw new Error(`${packageJsonPath} is missing a 'name' field (required for tag derivation).`);
|
|
17
|
+
}
|
|
18
|
+
const unscopedName = stripNpmScope(name);
|
|
19
|
+
return {
|
|
20
|
+
dir,
|
|
21
|
+
name,
|
|
22
|
+
tagPrefix: `${unscopedName}-v`,
|
|
23
|
+
workspacePath,
|
|
24
|
+
packageFiles: [packageJsonPath],
|
|
25
|
+
changelogPaths: [workspacePath],
|
|
26
|
+
paths: [`${workspacePath}/**`]
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function stripNpmScope(name) {
|
|
30
|
+
if (name.startsWith("@") && name.includes("/")) {
|
|
31
|
+
return name.slice(name.indexOf("/") + 1);
|
|
32
|
+
}
|
|
33
|
+
return name;
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
deriveWorkspaceConfig
|
|
37
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
const EXAMPLE_TAG_LIMIT = 3;
|
|
3
|
+
const CANDIDATE_TAG_PATTERN = /^(?<prefix>[a-z][a-z0-9-]*-v)\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/;
|
|
4
|
+
function detectUndeclaredTagPrefixes(knownPrefixes) {
|
|
5
|
+
const known = new Set(knownPrefixes);
|
|
6
|
+
let rawOutput;
|
|
7
|
+
try {
|
|
8
|
+
rawOutput = execFileSync("git", ["tag", "--list"], {
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
11
|
+
});
|
|
12
|
+
} catch {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
16
|
+
for (const line of rawOutput.split("\n")) {
|
|
17
|
+
const tag = line.trim();
|
|
18
|
+
if (tag === "") continue;
|
|
19
|
+
const match = CANDIDATE_TAG_PATTERN.exec(tag);
|
|
20
|
+
if (match === null) continue;
|
|
21
|
+
const prefix = match.groups?.prefix ?? "";
|
|
22
|
+
if (prefix === "" || known.has(prefix)) continue;
|
|
23
|
+
let tags = grouped.get(prefix);
|
|
24
|
+
if (tags === void 0) {
|
|
25
|
+
tags = [];
|
|
26
|
+
grouped.set(prefix, tags);
|
|
27
|
+
}
|
|
28
|
+
tags.push(tag);
|
|
29
|
+
}
|
|
30
|
+
const results = [];
|
|
31
|
+
for (const [prefix, tags] of grouped) {
|
|
32
|
+
results.push({
|
|
33
|
+
prefix,
|
|
34
|
+
tagCount: tags.length,
|
|
35
|
+
exampleTags: tags.slice(0, EXAMPLE_TAG_LIMIT),
|
|
36
|
+
suggestedDir: stripTrailingTagMarker(prefix)
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return results.sort((a, b) => a.prefix.localeCompare(b.prefix));
|
|
40
|
+
}
|
|
41
|
+
function stripTrailingTagMarker(prefix) {
|
|
42
|
+
return prefix.endsWith("-v") ? prefix.slice(0, -2) : prefix;
|
|
43
|
+
}
|
|
44
|
+
export {
|
|
45
|
+
detectUndeclaredTagPrefixes
|
|
46
|
+
};
|
|
@@ -115,12 +115,17 @@ function transformReleases(releases, devOnlySections) {
|
|
|
115
115
|
for (const commit of release.commits ?? []) {
|
|
116
116
|
const group = commit.group ?? "Other";
|
|
117
117
|
const description = extractDescription(commit.message);
|
|
118
|
+
const body = extractBody(commit.message);
|
|
118
119
|
let items = sectionMap.get(group);
|
|
119
120
|
if (items === void 0) {
|
|
120
121
|
items = [];
|
|
121
122
|
sectionMap.set(group, items);
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
+
const item = { description };
|
|
125
|
+
if (body !== void 0) {
|
|
126
|
+
item.body = body;
|
|
127
|
+
}
|
|
128
|
+
items.push(item);
|
|
124
129
|
}
|
|
125
130
|
const sections = [];
|
|
126
131
|
for (const [title, items] of sectionMap) {
|
|
@@ -147,6 +152,37 @@ function extractDescription(message) {
|
|
|
147
152
|
}
|
|
148
153
|
return firstLine;
|
|
149
154
|
}
|
|
155
|
+
const TRAILER_PATTERNS = [
|
|
156
|
+
/^Signed-off-by:/i,
|
|
157
|
+
/^Co-authored-by:/i,
|
|
158
|
+
/^(Closes|Fixes|Resolves)\s+#\d+\s*$/i,
|
|
159
|
+
/^https?:\/\/\S+\/pull\/\d+\/?\s*$/
|
|
160
|
+
];
|
|
161
|
+
function extractBody(message) {
|
|
162
|
+
const lines = message.split("\n").slice(1);
|
|
163
|
+
let start = 0;
|
|
164
|
+
while (start < lines.length && (lines[start] ?? "").trim() === "") {
|
|
165
|
+
start += 1;
|
|
166
|
+
}
|
|
167
|
+
let end = lines.length;
|
|
168
|
+
while (end > start) {
|
|
169
|
+
const line = lines[end - 1] ?? "";
|
|
170
|
+
const trimmed = line.trim();
|
|
171
|
+
if (trimmed === "") {
|
|
172
|
+
end -= 1;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (TRAILER_PATTERNS.some((pattern) => pattern.test(trimmed))) {
|
|
176
|
+
end -= 1;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
if (end <= start) {
|
|
182
|
+
return void 0;
|
|
183
|
+
}
|
|
184
|
+
return lines.slice(start, end).join("\n").trim();
|
|
185
|
+
}
|
|
150
186
|
function readExistingEntries(filePath) {
|
|
151
187
|
if (!existsSync(filePath)) {
|
|
152
188
|
return [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ReleaseConfig } from './types.ts';
|
|
2
|
-
export declare function buildTagPattern(
|
|
2
|
+
export declare function buildTagPattern(tagPrefixes: readonly string[]): string;
|
|
3
3
|
export interface GenerateChangelogOptions {
|
|
4
4
|
tagPattern?: string;
|
|
5
5
|
includePaths?: string[];
|
|
@@ -3,8 +3,19 @@ import { copyFileSync, mkdtempSync, rmSync } from "node:fs";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { resolveCliffConfigPath } from "./resolveCliffConfigPath.js";
|
|
6
|
-
function buildTagPattern(
|
|
7
|
-
|
|
6
|
+
function buildTagPattern(tagPrefixes) {
|
|
7
|
+
if (tagPrefixes.length === 0) {
|
|
8
|
+
throw new Error("buildTagPattern: tagPrefixes must contain at least one entry");
|
|
9
|
+
}
|
|
10
|
+
if (tagPrefixes.length === 1) {
|
|
11
|
+
const single = tagPrefixes[0] ?? "";
|
|
12
|
+
return `${single}[0-9].*`;
|
|
13
|
+
}
|
|
14
|
+
const escaped = tagPrefixes.map(escapeRegex);
|
|
15
|
+
return `(${escaped.join("|")})[0-9].*`;
|
|
16
|
+
}
|
|
17
|
+
function escapeRegex(value) {
|
|
18
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
8
19
|
}
|
|
9
20
|
function generateChangelog(config, changelogPath, tag, dryRun, options) {
|
|
10
21
|
const resolvedConfigPath = resolveCliffConfigPath(config.cliffConfigPath, import.meta.url);
|
|
@@ -41,7 +52,7 @@ function generateChangelog(config, changelogPath, tag, dryRun, options) {
|
|
|
41
52
|
}
|
|
42
53
|
}
|
|
43
54
|
function generateChangelogs(config, tag, dryRun) {
|
|
44
|
-
const tagPattern = buildTagPattern(config.tagPrefix);
|
|
55
|
+
const tagPattern = buildTagPattern([config.tagPrefix]);
|
|
45
56
|
const results = [];
|
|
46
57
|
for (const changelogPath of config.changelogPaths) {
|
|
47
58
|
results.push(...generateChangelog(config, changelogPath, tag, dryRun, { tagPattern }));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Commit } from './types.ts';
|
|
2
|
-
export declare function getCommitsSinceTarget(
|
|
2
|
+
export declare function getCommitsSinceTarget(tagPrefixes: readonly string[], paths?: string[]): {
|
|
3
3
|
tag: string | undefined;
|
|
4
4
|
commits: Commit[];
|
|
5
5
|
};
|