@williamthorsen/release-kit 4.7.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 +80 -0
- package/README.md +310 -40
- package/cliff.toml.template +2 -1
- package/dist/esm/.cache +1 -1
- package/dist/esm/bin/release-kit.js +67 -12
- 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 +9 -3
- package/dist/esm/index.js +12 -3
- package/dist/esm/init/detectRepoType.js +1 -2
- 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 +40 -10
- 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 +75 -27
- 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.d.ts +1 -0
- package/dist/esm/pushCommand.js +47 -0
- package/dist/esm/pushRelease.d.ts +11 -0
- package/dist/esm/pushRelease.js +26 -0
- 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/sync-labels/templates.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,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
parseArgs as coreParseArgs,
|
|
3
|
-
translateParseError,
|
|
4
|
-
writeFileWithCheck
|
|
5
|
-
} from "@williamthorsen/node-monorepo-core";
|
|
1
|
+
import { parseArgs as coreParseArgs, translateParseError, writeFileWithCheck } from "@williamthorsen/nmr-core";
|
|
6
2
|
import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
|
|
7
3
|
import { buildReleaseSummary } from "./buildReleaseSummary.js";
|
|
8
4
|
import { discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
@@ -15,6 +11,7 @@ import { validateConfig } from "./validateConfig.js";
|
|
|
15
11
|
const RELEASE_TAGS_FILE = "tmp/.release-tags";
|
|
16
12
|
const RELEASE_SUMMARY_FILE = "tmp/.release-summary";
|
|
17
13
|
const VALID_BUMP_TYPES = ["major", "minor", "patch"];
|
|
14
|
+
const CANONICAL_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
18
15
|
function isReleaseType(value) {
|
|
19
16
|
return VALID_BUMP_TYPES.includes(value);
|
|
20
17
|
}
|
|
@@ -24,10 +21,14 @@ Usage: npx @williamthorsen/release-kit prepare [options]
|
|
|
24
21
|
|
|
25
22
|
Options:
|
|
26
23
|
--dry-run Run without modifying any files
|
|
27
|
-
--bump=major|minor|patch Override the bump type for all
|
|
28
|
-
--
|
|
24
|
+
--bump=major|minor|patch Override the bump type for all workspaces
|
|
25
|
+
--set-version=X.Y.Z Set an explicit version; bypasses commit-derived bumps. Requires --only in monorepo mode.
|
|
26
|
+
--force Force a release even when there are no commits since the last tag (requires --bump)
|
|
29
27
|
--no-git-checks, -n Skip the clean-working-tree check
|
|
30
|
-
--only=name1,name2 Only process the named
|
|
28
|
+
--only=name1,name2 Only process the named workspaces (comma-separated, monorepo only)
|
|
29
|
+
--with-release-notes Also write per-workspace release-notes previews under {workspacePath}/docs/
|
|
30
|
+
(docs/README.v{version}.md and docs/RELEASE_NOTES.v{version}.md).
|
|
31
|
+
Recommended .gitignore entry: packages/*/docs/*.v*.md (or docs/*.v*.md).
|
|
31
32
|
--help Show this help message
|
|
32
33
|
`);
|
|
33
34
|
}
|
|
@@ -40,7 +41,9 @@ const prepareFlagSchema = {
|
|
|
40
41
|
short: "-n"
|
|
41
42
|
},
|
|
42
43
|
bump: { long: "--bump", type: "string" },
|
|
44
|
+
setVersion: { long: "--set-version", type: "string" },
|
|
43
45
|
only: { long: "--only", type: "string" },
|
|
46
|
+
withReleaseNotes: { long: "--with-release-notes", type: "boolean" },
|
|
44
47
|
help: { long: "--help", type: "boolean", short: "-h" }
|
|
45
48
|
};
|
|
46
49
|
function parseArgs(argv) {
|
|
@@ -62,10 +65,25 @@ function parseArgs(argv) {
|
|
|
62
65
|
}
|
|
63
66
|
bumpOverride = flags.bump;
|
|
64
67
|
}
|
|
68
|
+
let setVersion;
|
|
69
|
+
if (flags.setVersion !== void 0) {
|
|
70
|
+
if (!CANONICAL_SEMVER_PATTERN.test(flags.setVersion)) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Invalid --set-version value "${flags.setVersion}". Must be canonical semver (N.N.N, no pre-release suffix).`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
setVersion = flags.setVersion;
|
|
76
|
+
}
|
|
65
77
|
let only;
|
|
66
78
|
if (flags.only !== void 0) {
|
|
67
79
|
only = flags.only.split(",");
|
|
68
80
|
}
|
|
81
|
+
if (setVersion !== void 0 && bumpOverride !== void 0) {
|
|
82
|
+
throw new Error("--set-version cannot be combined with --bump");
|
|
83
|
+
}
|
|
84
|
+
if (setVersion !== void 0 && flags.force) {
|
|
85
|
+
throw new Error("--set-version cannot be combined with --force");
|
|
86
|
+
}
|
|
69
87
|
if (flags.force && bumpOverride === void 0) {
|
|
70
88
|
throw new Error("--force requires --bump to specify the version bump type");
|
|
71
89
|
}
|
|
@@ -74,7 +92,9 @@ function parseArgs(argv) {
|
|
|
74
92
|
force: flags.force,
|
|
75
93
|
noGitChecks: flags.noGitChecks,
|
|
76
94
|
bumpOverride,
|
|
77
|
-
only
|
|
95
|
+
only,
|
|
96
|
+
setVersion,
|
|
97
|
+
withReleaseNotes: flags.withReleaseNotes
|
|
78
98
|
};
|
|
79
99
|
}
|
|
80
100
|
function writeReleaseTags(tags, dryRun) {
|
|
@@ -92,8 +112,10 @@ async function prepareCommand(argv) {
|
|
|
92
112
|
let noGitChecks;
|
|
93
113
|
let bumpOverride;
|
|
94
114
|
let only;
|
|
115
|
+
let setVersion;
|
|
116
|
+
let withReleaseNotes;
|
|
95
117
|
try {
|
|
96
|
-
({ dryRun, force, noGitChecks, bumpOverride, only } = parseArgs(argv));
|
|
118
|
+
({ dryRun, force, noGitChecks, bumpOverride, only, setVersion, withReleaseNotes } = parseArgs(argv));
|
|
97
119
|
} catch (error) {
|
|
98
120
|
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
99
121
|
process.exit(1);
|
|
@@ -101,7 +123,9 @@ async function prepareCommand(argv) {
|
|
|
101
123
|
const options = {
|
|
102
124
|
dryRun,
|
|
103
125
|
force,
|
|
104
|
-
...bumpOverride === void 0 ? {} : { bumpOverride }
|
|
126
|
+
...bumpOverride === void 0 ? {} : { bumpOverride },
|
|
127
|
+
...setVersion === void 0 ? {} : { setVersion },
|
|
128
|
+
...withReleaseNotes ? { withReleaseNotes: true } : {}
|
|
105
129
|
};
|
|
106
130
|
if (dryRun) {
|
|
107
131
|
console.info("\n\u{1F50D} DRY RUN \u2014 no files will be modified\n");
|
|
@@ -123,26 +147,50 @@ async function prepareCommand(argv) {
|
|
|
123
147
|
process.exit(1);
|
|
124
148
|
}
|
|
125
149
|
if (discoveredPaths === void 0) {
|
|
126
|
-
|
|
127
|
-
console.error("Error: --only is only supported for monorepo configurations");
|
|
128
|
-
process.exit(1);
|
|
129
|
-
}
|
|
130
|
-
const config = mergeSinglePackageConfig(userConfig);
|
|
131
|
-
runAndReport(() => releasePrepare(config, options), dryRun);
|
|
150
|
+
runSinglePackageMode(userConfig, options, only, dryRun);
|
|
132
151
|
} else {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
152
|
+
runMonorepoMode(discoveredPaths, userConfig, options, only, setVersion, dryRun);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function runSinglePackageMode(userConfig, options, only, dryRun) {
|
|
156
|
+
if (only !== void 0) {
|
|
157
|
+
console.error("Error: --only is only supported for monorepo configurations");
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
const config = mergeSinglePackageConfig(userConfig);
|
|
161
|
+
runAndReport(() => releasePrepare(config, options), dryRun);
|
|
162
|
+
}
|
|
163
|
+
function runMonorepoMode(discoveredPaths, userConfig, options, only, setVersion, dryRun) {
|
|
164
|
+
let config;
|
|
165
|
+
try {
|
|
166
|
+
config = mergeMonorepoConfig(discoveredPaths, userConfig);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.error(`Error resolving workspaces: ${error instanceof Error ? error.message : String(error)}`);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
if (only !== void 0) {
|
|
172
|
+
const knownNames = config.workspaces.map((w) => w.dir);
|
|
173
|
+
for (const name of only) {
|
|
174
|
+
if (!knownNames.includes(name)) {
|
|
175
|
+
console.error(`Error: Unknown workspace "${name}". Known workspaces: ${knownNames.join(", ")}`);
|
|
176
|
+
process.exit(1);
|
|
141
177
|
}
|
|
142
|
-
config.components = config.components.filter((c) => only.includes(c.dir));
|
|
143
178
|
}
|
|
144
|
-
|
|
179
|
+
config.workspaces = config.workspaces.filter((w) => only.includes(w.dir));
|
|
180
|
+
}
|
|
181
|
+
if (setVersion !== void 0) {
|
|
182
|
+
if (only === void 0) {
|
|
183
|
+
console.error("Error: --set-version requires --only in monorepo mode");
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
if (config.workspaces.length !== 1) {
|
|
187
|
+
console.error(
|
|
188
|
+
`Error: --set-version requires --only to match exactly one workspace; matched ${config.workspaces.length}`
|
|
189
|
+
);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
145
192
|
}
|
|
193
|
+
runAndReport(() => releasePrepareMono(config, options), dryRun);
|
|
146
194
|
}
|
|
147
195
|
async function loadAndValidateConfig() {
|
|
148
196
|
let rawConfig;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { UndeclaredTagPrefix } from './detectUndeclaredTagPrefixes.ts';
|
|
2
|
+
export interface TagPrefixPreviewRow {
|
|
3
|
+
workspacePath: string;
|
|
4
|
+
dir: string;
|
|
5
|
+
derivedPrefix: string | null;
|
|
6
|
+
derivationError: string | null;
|
|
7
|
+
derivedTagCount: number;
|
|
8
|
+
legacyEntries: LegacyTagPrefixEntry[];
|
|
9
|
+
}
|
|
10
|
+
export interface LegacyTagPrefixEntry {
|
|
11
|
+
prefix: string;
|
|
12
|
+
tagCount: number;
|
|
13
|
+
}
|
|
14
|
+
export interface RetiredPackagePreviewEntry {
|
|
15
|
+
name: string;
|
|
16
|
+
tagPrefix: string;
|
|
17
|
+
successor?: string;
|
|
18
|
+
tagCount: number;
|
|
19
|
+
}
|
|
20
|
+
export interface TagPrefixCollision {
|
|
21
|
+
tagPrefix: string;
|
|
22
|
+
workspacePaths: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface TagPrefixPreview {
|
|
25
|
+
workspaces: TagPrefixPreviewRow[];
|
|
26
|
+
collisions: TagPrefixCollision[];
|
|
27
|
+
undeclaredCandidates: UndeclaredTagPrefix[];
|
|
28
|
+
retiredPackages: RetiredPackagePreviewEntry[];
|
|
29
|
+
}
|
|
30
|
+
export declare function previewTagPrefixes(): Promise<TagPrefixPreview>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { basename } from "node:path";
|
|
3
|
+
import { deriveWorkspaceConfig } from "./deriveWorkspaceConfig.js";
|
|
4
|
+
import { detectUndeclaredTagPrefixes } from "./detectUndeclaredTagPrefixes.js";
|
|
5
|
+
import { discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
6
|
+
import { loadConfig } from "./loadConfig.js";
|
|
7
|
+
import { validateConfig } from "./validateConfig.js";
|
|
8
|
+
async function previewTagPrefixes() {
|
|
9
|
+
const workspacePaths = await discoverWorkspaces() ?? [];
|
|
10
|
+
const userConfig = await loadUserConfig();
|
|
11
|
+
const overridesByDir = buildOverrideMap(userConfig);
|
|
12
|
+
const workspaces = [];
|
|
13
|
+
for (const workspacePath of workspacePaths) {
|
|
14
|
+
workspaces.push(buildPreviewRow(workspacePath, overridesByDir));
|
|
15
|
+
}
|
|
16
|
+
const retiredPackages = buildRetiredPreviewEntries(userConfig?.retiredPackages ?? []);
|
|
17
|
+
const collisions = detectCollisions(workspaces);
|
|
18
|
+
const knownPrefixes = collectKnownPrefixes(workspaces, retiredPackages);
|
|
19
|
+
const undeclaredCandidates = detectUndeclaredTagPrefixes(knownPrefixes);
|
|
20
|
+
return { workspaces, collisions, undeclaredCandidates, retiredPackages };
|
|
21
|
+
}
|
|
22
|
+
async function loadUserConfig() {
|
|
23
|
+
let raw;
|
|
24
|
+
try {
|
|
25
|
+
raw = await loadConfig();
|
|
26
|
+
} catch {
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
if (raw === void 0) return void 0;
|
|
30
|
+
const { config, errors } = validateConfig(raw);
|
|
31
|
+
return errors.length === 0 ? config : void 0;
|
|
32
|
+
}
|
|
33
|
+
function buildOverrideMap(userConfig) {
|
|
34
|
+
const map = /* @__PURE__ */ new Map();
|
|
35
|
+
if (userConfig?.workspaces === void 0) return map;
|
|
36
|
+
for (const entry of userConfig.workspaces) {
|
|
37
|
+
if (entry.legacyIdentities !== void 0) {
|
|
38
|
+
map.set(entry.dir, entry.legacyIdentities);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return map;
|
|
42
|
+
}
|
|
43
|
+
function buildPreviewRow(workspacePath, overridesByDir) {
|
|
44
|
+
const dir = basename(workspacePath);
|
|
45
|
+
let derivedPrefix = null;
|
|
46
|
+
let derivationError = null;
|
|
47
|
+
try {
|
|
48
|
+
derivedPrefix = deriveWorkspaceConfig(workspacePath).tagPrefix;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
derivationError = error instanceof Error ? error.message : String(error);
|
|
51
|
+
}
|
|
52
|
+
const derivedTagCount = derivedPrefix === null ? 0 : countTagsMatching(derivedPrefix);
|
|
53
|
+
const declaredIdentities = overridesByDir.get(dir) ?? [];
|
|
54
|
+
const legacyEntries = declaredIdentities.map((identity) => ({
|
|
55
|
+
prefix: identity.tagPrefix,
|
|
56
|
+
tagCount: countTagsMatching(identity.tagPrefix)
|
|
57
|
+
}));
|
|
58
|
+
return {
|
|
59
|
+
workspacePath,
|
|
60
|
+
dir,
|
|
61
|
+
derivedPrefix,
|
|
62
|
+
derivationError,
|
|
63
|
+
derivedTagCount,
|
|
64
|
+
legacyEntries
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function countTagsMatching(prefix) {
|
|
68
|
+
try {
|
|
69
|
+
const output = execFileSync("git", ["tag", "--list", `${prefix}*`], {
|
|
70
|
+
encoding: "utf8",
|
|
71
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
72
|
+
});
|
|
73
|
+
return output.split("\n").filter((line) => line.trim() !== "").length;
|
|
74
|
+
} catch {
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function detectCollisions(rows) {
|
|
79
|
+
const pathsByPrefix = /* @__PURE__ */ new Map();
|
|
80
|
+
for (const row of rows) {
|
|
81
|
+
if (row.derivedPrefix === null) continue;
|
|
82
|
+
const existing = pathsByPrefix.get(row.derivedPrefix);
|
|
83
|
+
if (existing === void 0) {
|
|
84
|
+
pathsByPrefix.set(row.derivedPrefix, [row.workspacePath]);
|
|
85
|
+
} else {
|
|
86
|
+
existing.push(row.workspacePath);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const collisions = [];
|
|
90
|
+
for (const [tagPrefix, workspacePaths] of pathsByPrefix) {
|
|
91
|
+
if (workspacePaths.length > 1) {
|
|
92
|
+
collisions.push({ tagPrefix, workspacePaths });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return collisions;
|
|
96
|
+
}
|
|
97
|
+
function collectKnownPrefixes(rows, retiredPackages) {
|
|
98
|
+
const known = /* @__PURE__ */ new Set();
|
|
99
|
+
for (const row of rows) {
|
|
100
|
+
if (row.derivedPrefix !== null) known.add(row.derivedPrefix);
|
|
101
|
+
for (const entry of row.legacyEntries) {
|
|
102
|
+
known.add(entry.prefix);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const retired of retiredPackages) {
|
|
106
|
+
known.add(retired.tagPrefix);
|
|
107
|
+
}
|
|
108
|
+
return [...known];
|
|
109
|
+
}
|
|
110
|
+
function buildRetiredPreviewEntries(retiredPackages) {
|
|
111
|
+
return retiredPackages.map((retired) => ({
|
|
112
|
+
name: retired.name,
|
|
113
|
+
tagPrefix: retired.tagPrefix,
|
|
114
|
+
tagCount: countTagsMatching(retired.tagPrefix),
|
|
115
|
+
...retired.successor !== void 0 ? { successor: retired.successor } : {}
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
export {
|
|
119
|
+
previewTagPrefixes
|
|
120
|
+
};
|
|
@@ -3,6 +3,7 @@ import type { PropagationSource, ReleaseType } from './types.ts';
|
|
|
3
3
|
export interface ReleaseEntry {
|
|
4
4
|
releaseType: ReleaseType;
|
|
5
5
|
propagatedFrom?: PropagationSource[];
|
|
6
|
+
newVersionOverride?: string;
|
|
6
7
|
}
|
|
7
8
|
export type CurrentVersions = Map<string, string>;
|
|
8
9
|
export declare function propagateBumps(directBumps: Map<string, ReleaseEntry>, graph: DependencyGraph, currentVersions: CurrentVersions): Map<string, ReleaseEntry>;
|
|
@@ -24,7 +24,7 @@ function propagateBumps(directBumps, graph, currentVersions) {
|
|
|
24
24
|
if (currentVersion === void 0 || entry === void 0) {
|
|
25
25
|
continue;
|
|
26
26
|
}
|
|
27
|
-
const newVersion = bumpVersion(currentVersion, entry.releaseType);
|
|
27
|
+
const newVersion = entry.newVersionOverride ?? bumpVersion(currentVersion, entry.releaseType);
|
|
28
28
|
const dependents = graph.dependentsOf.get(packageName);
|
|
29
29
|
if (dependents === void 0) {
|
|
30
30
|
continue;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { parseArgs, translateParseError } from "@williamthorsen/
|
|
4
|
-
import { createGithubReleases } from "./createGithubRelease.js";
|
|
3
|
+
import { parseArgs, translateParseError } from "@williamthorsen/nmr-core";
|
|
5
4
|
import { detectPackageManager } from "./detectPackageManager.js";
|
|
6
5
|
import { injectReleaseNotesIntoReadme, resolveReadmePath } from "./injectReleaseNotesIntoReadme.js";
|
|
6
|
+
import { parseRequestedTags } from "./parseRequestedTags.js";
|
|
7
7
|
import { publishPackage } from "./publish.js";
|
|
8
8
|
import { resolveCommandTags } from "./resolveCommandTags.js";
|
|
9
9
|
import { resolveReleaseNotesConfig } from "./resolveReleaseNotesConfig.js";
|
|
@@ -11,7 +11,7 @@ const publishFlagSchema = {
|
|
|
11
11
|
dryRun: { long: "--dry-run", type: "boolean" },
|
|
12
12
|
noGitChecks: { long: "--no-git-checks", type: "boolean" },
|
|
13
13
|
provenance: { long: "--provenance", type: "boolean" },
|
|
14
|
-
|
|
14
|
+
tags: { long: "--tags", type: "string" }
|
|
15
15
|
};
|
|
16
16
|
async function publishCommand(argv) {
|
|
17
17
|
let parsed;
|
|
@@ -22,13 +22,13 @@ async function publishCommand(argv) {
|
|
|
22
22
|
process.exit(1);
|
|
23
23
|
}
|
|
24
24
|
const { dryRun, noGitChecks, provenance } = parsed.flags;
|
|
25
|
-
const
|
|
26
|
-
const resolvedTags = await resolveCommandTags(
|
|
25
|
+
const requestedTags = parseRequestedTags(parsed.flags.tags);
|
|
26
|
+
const resolvedTags = await resolveCommandTags(requestedTags);
|
|
27
27
|
if (resolvedTags.length === 0) {
|
|
28
28
|
return;
|
|
29
29
|
}
|
|
30
30
|
const packageManager = detectPackageManager();
|
|
31
|
-
const { releaseNotes, changelogJsonOutputPath } = await resolveReleaseNotesConfig();
|
|
31
|
+
const { releaseNotes, changelogJsonOutputPath, sectionOrder } = await resolveReleaseNotesConfig();
|
|
32
32
|
const shouldInject = releaseNotes.shouldInjectIntoReadme;
|
|
33
33
|
console.info(dryRun ? "[dry-run] Would publish:" : "Publishing:");
|
|
34
34
|
for (const { tag, workspacePath } of resolvedTags) {
|
|
@@ -45,7 +45,8 @@ async function publishCommand(argv) {
|
|
|
45
45
|
originalReadme = injectReleaseNotesIntoReadme(
|
|
46
46
|
readmePath,
|
|
47
47
|
join(resolvedTag.workspacePath, changelogJsonOutputPath),
|
|
48
|
-
resolvedTag.tag
|
|
48
|
+
resolvedTag.tag,
|
|
49
|
+
sectionOrder
|
|
49
50
|
);
|
|
50
51
|
}
|
|
51
52
|
}
|
|
@@ -68,12 +69,6 @@ async function publishCommand(argv) {
|
|
|
68
69
|
console.error(error instanceof Error ? error.message : String(error));
|
|
69
70
|
process.exit(1);
|
|
70
71
|
}
|
|
71
|
-
try {
|
|
72
|
-
createGithubReleases(resolvedTags, releaseNotes, changelogJsonOutputPath, dryRun);
|
|
73
|
-
} catch (error) {
|
|
74
|
-
console.error(`Error creating GitHub Releases: ${error instanceof Error ? error.message : String(error)}`);
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
72
|
}
|
|
78
73
|
export {
|
|
79
74
|
publishCommand
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function pushCommand(argv: string[]): Promise<void>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { parseArgs, translateParseError } from "@williamthorsen/nmr-core";
|
|
2
|
+
import { parseRequestedTags } from "./parseRequestedTags.js";
|
|
3
|
+
import { pushRelease } from "./pushRelease.js";
|
|
4
|
+
import { resolveCommandTags } from "./resolveCommandTags.js";
|
|
5
|
+
const pushFlagSchema = {
|
|
6
|
+
dryRun: { long: "--dry-run", type: "boolean" },
|
|
7
|
+
tags: { long: "--tags", type: "string" },
|
|
8
|
+
tagsOnly: { long: "--tags-only", type: "boolean" }
|
|
9
|
+
};
|
|
10
|
+
async function pushCommand(argv) {
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = parseArgs(argv, pushFlagSchema);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error(`Error: ${translateParseError(error)}`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
const { dryRun, tagsOnly } = parsed.flags;
|
|
19
|
+
const requestedTags = parseRequestedTags(parsed.flags.tags);
|
|
20
|
+
const resolvedTags = await resolveCommandTags(requestedTags);
|
|
21
|
+
if (resolvedTags.length === 0) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const prefix = dryRun ? "[dry-run] Would push" : "Pushing";
|
|
25
|
+
if (!tagsOnly) {
|
|
26
|
+
console.info(`${prefix} branch and ${resolvedTags.length} tag(s):`);
|
|
27
|
+
} else {
|
|
28
|
+
console.info(`${prefix} ${resolvedTags.length} tag(s):`);
|
|
29
|
+
}
|
|
30
|
+
for (const { tag } of resolvedTags) {
|
|
31
|
+
console.info(` ${tag}`);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const steps = pushRelease(resolvedTags, { dryRun, tagsOnly });
|
|
35
|
+
if (dryRun) {
|
|
36
|
+
for (const step of steps) {
|
|
37
|
+
console.info(`[dry-run] ${step.command.join(" ")}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
pushCommand
|
|
47
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ResolvedTag } from './resolveReleaseTags.ts';
|
|
2
|
+
export interface PushReleaseOptions {
|
|
3
|
+
dryRun?: boolean;
|
|
4
|
+
tagsOnly?: boolean;
|
|
5
|
+
}
|
|
6
|
+
export interface PushStep {
|
|
7
|
+
type: 'branch' | 'tag';
|
|
8
|
+
ref: string;
|
|
9
|
+
command: readonly [string, ...string[]];
|
|
10
|
+
}
|
|
11
|
+
export declare function pushRelease(resolvedTags: ResolvedTag[], options?: PushReleaseOptions): PushStep[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
function pushRelease(resolvedTags, options = {}) {
|
|
3
|
+
const { dryRun = false, tagsOnly = false } = options;
|
|
4
|
+
const steps = [];
|
|
5
|
+
if (!tagsOnly) {
|
|
6
|
+
const branch = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
7
|
+
encoding: "utf8",
|
|
8
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
9
|
+
}).trim();
|
|
10
|
+
const command = ["git", "push", "origin", branch];
|
|
11
|
+
steps.push({ type: "branch", ref: branch, command });
|
|
12
|
+
}
|
|
13
|
+
for (const { tag } of resolvedTags) {
|
|
14
|
+
const command = ["git", "push", "--no-follow-tags", "origin", tag];
|
|
15
|
+
steps.push({ type: "tag", ref: tag, command });
|
|
16
|
+
}
|
|
17
|
+
if (!dryRun) {
|
|
18
|
+
for (const step of steps) {
|
|
19
|
+
execFileSync(step.command[0], step.command.slice(1), { stdio: "inherit" });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return steps;
|
|
23
|
+
}
|
|
24
|
+
export {
|
|
25
|
+
pushRelease
|
|
26
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function readCurrentVersion(filePath: string): string | undefined;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
function hasVersionField(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && "version" in value && typeof value.version === "string";
|
|
4
|
+
}
|
|
5
|
+
function readCurrentVersion(filePath) {
|
|
6
|
+
try {
|
|
7
|
+
const content = readFileSync(filePath, "utf8");
|
|
8
|
+
const parsed = JSON.parse(content);
|
|
9
|
+
if (hasVersionField(parsed)) {
|
|
10
|
+
return parsed.version;
|
|
11
|
+
}
|
|
12
|
+
} catch (error) {
|
|
13
|
+
console.warn(
|
|
14
|
+
`Failed to read current version from ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
readCurrentVersion
|
|
21
|
+
};
|
|
@@ -3,5 +3,7 @@ export interface ReleasePrepareOptions {
|
|
|
3
3
|
dryRun: boolean;
|
|
4
4
|
force?: boolean;
|
|
5
5
|
bumpOverride?: ReleaseType;
|
|
6
|
+
setVersion?: string;
|
|
7
|
+
withReleaseNotes?: boolean;
|
|
6
8
|
}
|
|
7
9
|
export declare function releasePrepare(config: ReleaseConfig, options: ReleasePrepareOptions): PrepareResult;
|
|
@@ -1,46 +1,67 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
|
-
import { bumpAllVersions } from "./bumpAllVersions.js";
|
|
2
|
+
import { bumpAllVersions, setAllVersions } from "./bumpAllVersions.js";
|
|
3
|
+
import { isForwardVersion } from "./compareVersions.js";
|
|
3
4
|
import { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
4
5
|
import { determineBumpFromCommits } from "./determineBumpFromCommits.js";
|
|
5
6
|
import { generateChangelogJson } from "./generateChangelogJson.js";
|
|
6
7
|
import { generateChangelogs } from "./generateChangelogs.js";
|
|
7
8
|
import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
|
|
8
9
|
import { hasPrettierConfig } from "./hasPrettierConfig.js";
|
|
10
|
+
import { resolveWorkTypes } from "./loadConfig.js";
|
|
11
|
+
import { readCurrentVersion } from "./readCurrentVersion.js";
|
|
12
|
+
import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
|
|
13
|
+
import { writeReleaseNotesPreviews } from "./writeReleaseNotesPreviews.js";
|
|
9
14
|
function releasePrepare(config, options) {
|
|
10
|
-
const { dryRun, bumpOverride } = options;
|
|
15
|
+
const { dryRun, bumpOverride, setVersion, withReleaseNotes } = options;
|
|
11
16
|
const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
|
|
12
17
|
const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
|
|
13
|
-
const { tag, commits } = getCommitsSinceTarget(config.tagPrefix);
|
|
18
|
+
const { tag, commits } = getCommitsSinceTarget([config.tagPrefix]);
|
|
14
19
|
let releaseType;
|
|
15
20
|
let parsedCommitCount;
|
|
16
21
|
let unparseableCommits;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
let bump;
|
|
23
|
+
if (setVersion !== void 0) {
|
|
24
|
+
const primaryPackageFile = config.packageFiles[0];
|
|
25
|
+
if (primaryPackageFile === void 0) {
|
|
26
|
+
throw new Error("No package files specified");
|
|
27
|
+
}
|
|
28
|
+
const currentVersion = readCurrentVersion(primaryPackageFile);
|
|
29
|
+
if (currentVersion === void 0) {
|
|
30
|
+
throw new Error(`Cannot validate --set-version: failed to read current version from ${primaryPackageFile}`);
|
|
31
|
+
}
|
|
32
|
+
if (!isForwardVersion(currentVersion, setVersion)) {
|
|
33
|
+
throw new Error(`--set-version ${setVersion} is not greater than current version ${currentVersion}`);
|
|
34
|
+
}
|
|
35
|
+
bump = setAllVersions(config.packageFiles, setVersion, dryRun);
|
|
22
36
|
} else {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
if (bumpOverride === void 0) {
|
|
38
|
+
const determination = determineBumpFromCommits(commits, workTypes, versionPatterns, config.scopeAliases);
|
|
39
|
+
parsedCommitCount = determination.parsedCommitCount;
|
|
40
|
+
unparseableCommits = determination.unparseableCommits;
|
|
41
|
+
releaseType = determination.releaseType;
|
|
42
|
+
} else {
|
|
43
|
+
releaseType = bumpOverride;
|
|
44
|
+
}
|
|
45
|
+
if (releaseType === void 0) {
|
|
46
|
+
return {
|
|
47
|
+
workspaces: [
|
|
48
|
+
{
|
|
49
|
+
status: "skipped",
|
|
50
|
+
previousTag: tag,
|
|
51
|
+
commitCount: commits.length,
|
|
52
|
+
parsedCommitCount,
|
|
53
|
+
unparseableCommits,
|
|
54
|
+
bumpedFiles: [],
|
|
55
|
+
changelogFiles: [],
|
|
56
|
+
skipReason: "No release-worthy changes found. Skipping."
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
tags: [],
|
|
60
|
+
dryRun
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
bump = bumpAllVersions(config.packageFiles, releaseType, dryRun);
|
|
42
64
|
}
|
|
43
|
-
const bump = bumpAllVersions(config.packageFiles, releaseType, dryRun);
|
|
44
65
|
const newTag = `${config.tagPrefix}${bump.newVersion}`;
|
|
45
66
|
const changelogFiles = generateChangelogs(config, newTag, dryRun);
|
|
46
67
|
const changelogJsonFiles = [];
|
|
@@ -49,6 +70,7 @@ function releasePrepare(config, options) {
|
|
|
49
70
|
changelogJsonFiles.push(...generateChangelogJson(config, changelogPath, newTag, dryRun));
|
|
50
71
|
}
|
|
51
72
|
}
|
|
73
|
+
maybeWriteSinglePackagePreviews(withReleaseNotes === true, config, newTag, changelogJsonFiles[0], dryRun);
|
|
52
74
|
const formatCommandStr = config.formatCommand ?? (hasPrettierConfig() ? "npx prettier --write" : void 0);
|
|
53
75
|
let formatCommand;
|
|
54
76
|
if (formatCommandStr !== void 0) {
|
|
@@ -72,7 +94,7 @@ function releasePrepare(config, options) {
|
|
|
72
94
|
}
|
|
73
95
|
}
|
|
74
96
|
return {
|
|
75
|
-
|
|
97
|
+
workspaces: [
|
|
76
98
|
{
|
|
77
99
|
status: "released",
|
|
78
100
|
previousTag: tag,
|
|
@@ -85,7 +107,8 @@ function releasePrepare(config, options) {
|
|
|
85
107
|
bumpedFiles: bump.files,
|
|
86
108
|
changelogFiles,
|
|
87
109
|
commits,
|
|
88
|
-
unparseableCommits
|
|
110
|
+
unparseableCommits,
|
|
111
|
+
...setVersion === void 0 ? {} : { setVersion }
|
|
89
112
|
}
|
|
90
113
|
],
|
|
91
114
|
tags: [newTag],
|
|
@@ -93,6 +116,25 @@ function releasePrepare(config, options) {
|
|
|
93
116
|
dryRun
|
|
94
117
|
};
|
|
95
118
|
}
|
|
119
|
+
function maybeWriteSinglePackagePreviews(withReleaseNotes, config, newTag, changelogJsonPath, dryRun) {
|
|
120
|
+
if (!withReleaseNotes) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (!config.changelogJson.enabled) {
|
|
124
|
+
console.warn("Warning: --with-release-notes requires changelogJson.enabled; skipping preview generation");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (changelogJsonPath === void 0) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
writeReleaseNotesPreviews({
|
|
131
|
+
workspacePath: process.cwd(),
|
|
132
|
+
tag: newTag,
|
|
133
|
+
changelogJsonPath,
|
|
134
|
+
sectionOrder: deriveSectionOrder(resolveWorkTypes(config.workTypes)),
|
|
135
|
+
dryRun
|
|
136
|
+
});
|
|
137
|
+
}
|
|
96
138
|
export {
|
|
97
139
|
releasePrepare
|
|
98
140
|
};
|