@williamthorsen/release-kit 5.0.0 → 5.2.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 +149 -49
- package/README.md +275 -78
- package/cliff.toml.template +26 -17
- package/dist/esm/.cache +1 -1
- package/dist/esm/assertCleanWorkingTree.js +1 -1
- package/dist/esm/bin/release-kit.js +97 -4
- package/dist/esm/buildChangelogEntries.d.ts +4 -0
- package/dist/esm/buildChangelogEntries.js +173 -0
- package/dist/esm/buildDependencyGraph.d.ts +1 -0
- package/dist/esm/buildDependencyGraph.js +8 -1
- package/dist/esm/buildReleaseSummary.js +9 -1
- package/dist/esm/buildSyntheticChangelogEntry.d.ts +5 -0
- package/dist/esm/buildSyntheticChangelogEntry.js +13 -0
- package/dist/esm/changelogJsonFile.d.ts +4 -0
- package/dist/esm/changelogJsonFile.js +68 -0
- package/dist/esm/checkWorkTypesDrift.d.ts +11 -0
- package/dist/esm/checkWorkTypesDrift.js +110 -0
- package/dist/esm/collectPolicyViolations.d.ts +6 -0
- package/dist/esm/collectPolicyViolations.js +15 -0
- package/dist/esm/createGithubRelease.d.ts +12 -2
- package/dist/esm/createGithubRelease.js +12 -8
- package/dist/esm/createGithubReleaseCommand.js +10 -6
- package/dist/esm/decideRelease.d.ts +28 -0
- package/dist/esm/decideRelease.js +44 -0
- package/dist/esm/defaults.d.ts +8 -0
- package/dist/esm/defaults.js +43 -20
- package/dist/esm/deriveWorkspaceConfig.js +3 -0
- package/dist/esm/determineBumpFromCommits.d.ts +6 -1
- package/dist/esm/determineBumpFromCommits.js +9 -3
- package/dist/esm/generateChangelogs.js +14 -29
- package/dist/esm/index.d.ts +2 -43
- package/dist/esm/index.js +0 -82
- package/dist/esm/init/templates.js +2 -2
- package/dist/esm/loadConfig.d.ts +10 -1
- package/dist/esm/loadConfig.js +110 -24
- package/dist/esm/parseCommitMessage.d.ts +8 -2
- package/dist/esm/parseCommitMessage.js +32 -3
- package/dist/esm/prepareCommand.js +51 -9
- package/dist/esm/publish.d.ts +0 -1
- package/dist/esm/publish.js +3 -3
- package/dist/esm/publishCommand.js +31 -3
- package/dist/esm/releasePrepare.js +109 -41
- package/dist/esm/releasePrepareMono.js +156 -87
- package/dist/esm/releasePrepareProject.d.ts +9 -0
- package/dist/esm/releasePrepareProject.js +121 -0
- package/dist/esm/renderReleaseNotes.js +2 -1
- package/dist/esm/reportPrepare.js +88 -24
- package/dist/esm/resolveCommandTags.js +16 -6
- package/dist/esm/resolveReleaseTags.d.ts +8 -1
- package/dist/esm/resolveReleaseTags.js +11 -7
- package/dist/esm/runGitCliff.d.ts +2 -0
- package/dist/esm/runGitCliff.js +27 -0
- package/dist/esm/stripEmojiPrefix.d.ts +1 -0
- package/dist/esm/stripEmojiPrefix.js +7 -0
- package/dist/esm/syncWorkTypes.d.ts +10 -0
- package/dist/esm/syncWorkTypes.js +90 -0
- package/dist/esm/types.d.ts +72 -14
- package/dist/esm/validateConfig.js +26 -0
- package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -0
- package/dist/esm/validateOnlyExcludesStrandedDependents.js +109 -0
- package/dist/esm/work-types.json +127 -0
- package/dist/esm/work-types.schema.json +73 -0
- package/dist/esm/workTypesData.d.ts +14 -0
- package/dist/esm/workTypesData.js +59 -0
- package/dist/esm/workTypesUtils.d.ts +5 -0
- package/dist/esm/workTypesUtils.js +16 -0
- package/package.json +9 -3
- package/presets/labels/common.yaml +9 -6
- package/schemas/label-map.json +24 -0
- package/dist/esm/generateChangelogJson.d.ts +0 -7
- package/dist/esm/generateChangelogJson.js +0 -232
- package/dist/esm/version.d.ts +0 -1
- package/dist/esm/version.js +0 -4
package/dist/esm/loadConfig.js
CHANGED
|
@@ -1,13 +1,41 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import {
|
|
4
4
|
DEFAULT_CHANGELOG_JSON_CONFIG,
|
|
5
|
+
DEFAULT_PROJECT_TAG_PREFIX,
|
|
5
6
|
DEFAULT_RELEASE_NOTES_CONFIG,
|
|
6
7
|
DEFAULT_VERSION_PATTERNS,
|
|
7
8
|
DEFAULT_WORK_TYPES
|
|
8
9
|
} from "./defaults.js";
|
|
9
10
|
import { deriveWorkspaceConfig } from "./deriveWorkspaceConfig.js";
|
|
10
11
|
import { isRecord } from "./typeGuards.js";
|
|
12
|
+
const ROOT_PACKAGE_JSON_PATH = "package.json";
|
|
13
|
+
function readRootPackageVersion() {
|
|
14
|
+
const absolutePath = path.resolve(process.cwd(), ROOT_PACKAGE_JSON_PATH);
|
|
15
|
+
if (!existsSync(absolutePath)) {
|
|
16
|
+
return { exists: false, version: void 0 };
|
|
17
|
+
}
|
|
18
|
+
let contents;
|
|
19
|
+
try {
|
|
20
|
+
contents = readFileSync(absolutePath, "utf8");
|
|
21
|
+
} catch (error) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
`Failed to read root ${ROOT_PACKAGE_JSON_PATH}: ${error instanceof Error ? error.message : String(error)}`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
let parsed;
|
|
27
|
+
try {
|
|
28
|
+
parsed = JSON.parse(contents);
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Failed to parse root ${ROOT_PACKAGE_JSON_PATH}: ${error instanceof Error ? error.message : String(error)}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
if (!isRecord(parsed)) {
|
|
35
|
+
return { exists: true, version: void 0 };
|
|
36
|
+
}
|
|
37
|
+
return { exists: true, version: typeof parsed.version === "string" ? parsed.version : void 0 };
|
|
38
|
+
}
|
|
11
39
|
const CONFIG_FILE_PATH = ".config/release-kit.config.ts";
|
|
12
40
|
async function loadConfig() {
|
|
13
41
|
const absoluteConfigPath = path.resolve(process.cwd(), CONFIG_FILE_PATH);
|
|
@@ -28,7 +56,7 @@ async function loadConfig() {
|
|
|
28
56
|
}
|
|
29
57
|
return resolved;
|
|
30
58
|
}
|
|
31
|
-
function mergeMonorepoConfig(discoveredPaths, userConfig) {
|
|
59
|
+
function mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage) {
|
|
32
60
|
let workspaces = discoveredPaths.map((workspacePath) => deriveWorkspaceConfig(workspacePath));
|
|
33
61
|
assertUniqueTagPrefixes(workspaces);
|
|
34
62
|
if (userConfig?.workspaces !== void 0) {
|
|
@@ -48,10 +76,12 @@ function mergeMonorepoConfig(discoveredPaths, userConfig) {
|
|
|
48
76
|
if (userConfig?.retiredPackages !== void 0) {
|
|
49
77
|
assertRetiredPackagesDoNotCollideWithActive(workspaces, userConfig.retiredPackages);
|
|
50
78
|
}
|
|
79
|
+
const project = resolveProjectConfig(userConfig?.project, rootPackage);
|
|
51
80
|
const workTypes = resolveWorkTypes(userConfig?.workTypes);
|
|
52
81
|
const versionPatterns = userConfig?.versionPatterns === void 0 ? { ...DEFAULT_VERSION_PATTERNS } : { ...userConfig.versionPatterns };
|
|
53
82
|
const changelogJson = mergeChangelogJsonConfig(userConfig?.changelogJson);
|
|
54
83
|
const releaseNotes = mergeReleaseNotesConfig(userConfig?.releaseNotes);
|
|
84
|
+
assertNoTagPrefixCollisions(workspaces, userConfig?.retiredPackages, project);
|
|
55
85
|
const result = {
|
|
56
86
|
workspaces,
|
|
57
87
|
workTypes,
|
|
@@ -59,21 +89,30 @@ function mergeMonorepoConfig(discoveredPaths, userConfig) {
|
|
|
59
89
|
changelogJson,
|
|
60
90
|
releaseNotes
|
|
61
91
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
result.formatCommand = formatCommand;
|
|
92
|
+
if (project !== void 0) {
|
|
93
|
+
result.project = project;
|
|
65
94
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
95
|
+
applyOptionalPassthroughFields(result, userConfig);
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
function applyOptionalPassthroughFields(result, userConfig) {
|
|
99
|
+
if (userConfig?.formatCommand !== void 0) {
|
|
100
|
+
result.formatCommand = userConfig.formatCommand;
|
|
69
101
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
102
|
+
if (userConfig?.cliffConfigPath !== void 0) {
|
|
103
|
+
result.cliffConfigPath = userConfig.cliffConfigPath;
|
|
104
|
+
}
|
|
105
|
+
if (userConfig?.scopeAliases !== void 0) {
|
|
106
|
+
result.scopeAliases = userConfig.scopeAliases;
|
|
107
|
+
}
|
|
108
|
+
if (userConfig?.breakingPolicies !== void 0) {
|
|
109
|
+
result.breakingPolicies = userConfig.breakingPolicies;
|
|
73
110
|
}
|
|
74
|
-
return result;
|
|
75
111
|
}
|
|
76
112
|
function mergeSinglePackageConfig(userConfig) {
|
|
113
|
+
if (userConfig?.project !== void 0) {
|
|
114
|
+
throw new Error("project block is not supported in single-package mode");
|
|
115
|
+
}
|
|
77
116
|
const workTypes = resolveWorkTypes(userConfig?.workTypes);
|
|
78
117
|
const versionPatterns = userConfig?.versionPatterns === void 0 ? { ...DEFAULT_VERSION_PATTERNS } : { ...userConfig.versionPatterns };
|
|
79
118
|
const changelogJson = mergeChangelogJsonConfig(userConfig?.changelogJson);
|
|
@@ -87,18 +126,7 @@ function mergeSinglePackageConfig(userConfig) {
|
|
|
87
126
|
changelogJson,
|
|
88
127
|
releaseNotes
|
|
89
128
|
};
|
|
90
|
-
|
|
91
|
-
if (formatCommand !== void 0) {
|
|
92
|
-
result.formatCommand = formatCommand;
|
|
93
|
-
}
|
|
94
|
-
const cliffConfigPath = userConfig?.cliffConfigPath;
|
|
95
|
-
if (cliffConfigPath !== void 0) {
|
|
96
|
-
result.cliffConfigPath = cliffConfigPath;
|
|
97
|
-
}
|
|
98
|
-
const scopeAliases = userConfig?.scopeAliases;
|
|
99
|
-
if (scopeAliases !== void 0) {
|
|
100
|
-
result.scopeAliases = scopeAliases;
|
|
101
|
-
}
|
|
129
|
+
applyOptionalPassthroughFields(result, userConfig);
|
|
102
130
|
return result;
|
|
103
131
|
}
|
|
104
132
|
function resolveWorkTypes(userWorkTypes) {
|
|
@@ -137,6 +165,62 @@ function assertRetiredPackagesDoNotCollideWithActive(workspaces, retiredPackages
|
|
|
137
165
|
}
|
|
138
166
|
}
|
|
139
167
|
}
|
|
168
|
+
function resolveProjectConfig(userProject, rootPackage) {
|
|
169
|
+
if (userProject === void 0) {
|
|
170
|
+
return void 0;
|
|
171
|
+
}
|
|
172
|
+
if (rootPackage === void 0 || !rootPackage.exists) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
`project block requires a root ${ROOT_PACKAGE_JSON_PATH}; create one with a 'version' field at the repo root`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
if (rootPackage.version === void 0) {
|
|
178
|
+
throw new Error(
|
|
179
|
+
`project block requires root ${ROOT_PACKAGE_JSON_PATH} to have a 'version' field; add a 'version' field to your root package.json`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
return { tagPrefix: userProject.tagPrefix ?? DEFAULT_PROJECT_TAG_PREFIX };
|
|
183
|
+
}
|
|
184
|
+
function assertNoTagPrefixCollisions(workspaces, retiredPackages, project) {
|
|
185
|
+
const sources = [];
|
|
186
|
+
for (const workspace of workspaces) {
|
|
187
|
+
const owner = `ws:${workspace.dir}`;
|
|
188
|
+
sources.push({ prefix: workspace.tagPrefix, label: `workspace '${workspace.dir}'`, owner });
|
|
189
|
+
for (const identity of workspace.legacyIdentities ?? []) {
|
|
190
|
+
sources.push({
|
|
191
|
+
prefix: identity.tagPrefix,
|
|
192
|
+
label: `workspace '${workspace.dir}' legacyIdentities entry (name='${identity.name}')`,
|
|
193
|
+
owner
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const [index, retired] of (retiredPackages ?? []).entries()) {
|
|
198
|
+
sources.push({
|
|
199
|
+
prefix: retired.tagPrefix,
|
|
200
|
+
label: `retiredPackages entry (name='${retired.name}')`,
|
|
201
|
+
owner: `retired:${index}`
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
if (project !== void 0) {
|
|
205
|
+
sources.push({ prefix: project.tagPrefix, label: "project", owner: "project" });
|
|
206
|
+
}
|
|
207
|
+
for (let i = 0; i < sources.length; i++) {
|
|
208
|
+
for (let j = i + 1; j < sources.length; j++) {
|
|
209
|
+
const a = sources[i];
|
|
210
|
+
const b = sources[j];
|
|
211
|
+
if (a === void 0 || b === void 0) continue;
|
|
212
|
+
if (a.owner === b.owner) continue;
|
|
213
|
+
if (isPrefixCollision(a.prefix, b.prefix)) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Tag prefix collision: '${a.prefix}' (${a.label}) and '${b.prefix}' (${b.label}). One prefix is identical to or a strict prefix of the other; this would cause \`git describe --match=<prefix>*\` to return cross-matches.`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function isPrefixCollision(a, b) {
|
|
222
|
+
return a === b || a.startsWith(b) || b.startsWith(a);
|
|
223
|
+
}
|
|
140
224
|
function assertUniqueTagPrefixes(workspaces) {
|
|
141
225
|
const pathsByPrefix = /* @__PURE__ */ new Map();
|
|
142
226
|
for (const workspace of workspaces) {
|
|
@@ -163,8 +247,10 @@ function mergeReleaseNotesConfig(partial) {
|
|
|
163
247
|
}
|
|
164
248
|
export {
|
|
165
249
|
CONFIG_FILE_PATH,
|
|
250
|
+
ROOT_PACKAGE_JSON_PATH,
|
|
166
251
|
loadConfig,
|
|
167
252
|
mergeMonorepoConfig,
|
|
168
253
|
mergeSinglePackageConfig,
|
|
254
|
+
readRootPackageVersion,
|
|
169
255
|
resolveWorkTypes
|
|
170
256
|
};
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
import type { ParsedCommit, WorkTypeConfig } from './types.ts';
|
|
1
|
+
import type { Commit, ParsedCommit, WorkTypeConfig } from './types.ts';
|
|
2
2
|
export declare const COMMIT_PREPROCESSOR_PATTERNS: readonly RegExp[];
|
|
3
|
-
export
|
|
3
|
+
export type PolicyViolationSurface = 'prefix' | 'body';
|
|
4
|
+
export type PolicyViolationHandler = (commit: Commit, type: string, surface: PolicyViolationSurface) => void;
|
|
5
|
+
export interface ParseCommitMessageOptions {
|
|
6
|
+
breakingPolicies?: Record<string, 'forbidden' | 'optional' | 'required'>;
|
|
7
|
+
onPolicyViolation?: PolicyViolationHandler;
|
|
8
|
+
}
|
|
9
|
+
export declare function parseCommitMessage(message: string, hash: string, workTypes: Record<string, WorkTypeConfig>, scopeAliases?: Record<string, string>, options?: ParseCommitMessageOptions): ParsedCommit | undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const COMMIT_PREPROCESSOR_PATTERNS = [/^##\s+/, /^#\d+([.-]\d+)?\s+/, /^[A-Z]+-\d+\s+/];
|
|
2
|
-
function parseCommitMessage(message, hash, workTypes, scopeAliases) {
|
|
3
|
-
const
|
|
2
|
+
function parseCommitMessage(message, hash, workTypes, scopeAliases, options) {
|
|
3
|
+
const firstLine = message.split("\n", 1)[0] ?? "";
|
|
4
|
+
const stripped = stripTicketPrefix(firstLine);
|
|
4
5
|
const match = stripped.match(/^(?:([^|]+)\|)?(\w+)(?:\(([^)]+)\))?(!)?:\s*(.*)$/);
|
|
5
6
|
if (!match) {
|
|
6
7
|
return void 0;
|
|
@@ -17,7 +18,15 @@ function parseCommitMessage(message, hash, workTypes, scopeAliases) {
|
|
|
17
18
|
if (resolvedType === void 0) {
|
|
18
19
|
return void 0;
|
|
19
20
|
}
|
|
20
|
-
const
|
|
21
|
+
const commit = { message, hash };
|
|
22
|
+
const breaking = evaluateBreakingPolicy({
|
|
23
|
+
commit,
|
|
24
|
+
resolvedType,
|
|
25
|
+
hasPrefixBreaking: breakingMarker === "!",
|
|
26
|
+
hasFooterBreaking: message.includes("BREAKING CHANGE:"),
|
|
27
|
+
policy: options?.breakingPolicies?.[resolvedType] ?? "optional",
|
|
28
|
+
onPolicyViolation: options?.onPolicyViolation
|
|
29
|
+
});
|
|
21
30
|
const rawScope = pipeScope ?? parenthesizedScope;
|
|
22
31
|
const resolvedScope = rawScope !== void 0 && scopeAliases !== void 0 ? scopeAliases[rawScope] ?? rawScope : rawScope;
|
|
23
32
|
return {
|
|
@@ -29,6 +38,26 @@ function parseCommitMessage(message, hash, workTypes, scopeAliases) {
|
|
|
29
38
|
...resolvedScope !== void 0 && { scope: resolvedScope }
|
|
30
39
|
};
|
|
31
40
|
}
|
|
41
|
+
function evaluateBreakingPolicy(inputs) {
|
|
42
|
+
const { commit, resolvedType, hasPrefixBreaking, hasFooterBreaking, policy, onPolicyViolation } = inputs;
|
|
43
|
+
if (policy === "forbidden") {
|
|
44
|
+
if (hasPrefixBreaking) {
|
|
45
|
+
onPolicyViolation?.(commit, resolvedType, "prefix");
|
|
46
|
+
}
|
|
47
|
+
if (hasFooterBreaking) {
|
|
48
|
+
onPolicyViolation?.(commit, resolvedType, "body");
|
|
49
|
+
}
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (policy === "required") {
|
|
53
|
+
if (!hasPrefixBreaking) {
|
|
54
|
+
onPolicyViolation?.(commit, resolvedType, "prefix");
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return hasPrefixBreaking || hasFooterBreaking;
|
|
60
|
+
}
|
|
32
61
|
function resolveType(rawType, workTypes) {
|
|
33
62
|
const lowered = rawType.toLowerCase();
|
|
34
63
|
for (const [key, config] of Object.entries(workTypes)) {
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { parseArgs as coreParseArgs, translateParseError, writeFileWithCheck } from "@williamthorsen/nmr-core";
|
|
2
2
|
import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
|
|
3
|
+
import { buildDependencyGraph } from "./buildDependencyGraph.js";
|
|
3
4
|
import { buildReleaseSummary } from "./buildReleaseSummary.js";
|
|
4
5
|
import { discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
5
6
|
import { dim } from "./format.js";
|
|
6
|
-
import {
|
|
7
|
+
import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
|
|
8
|
+
import { loadConfig, mergeMonorepoConfig, mergeSinglePackageConfig, readRootPackageVersion } from "./loadConfig.js";
|
|
7
9
|
import { releasePrepare } from "./releasePrepare.js";
|
|
8
10
|
import { releasePrepareMono } from "./releasePrepareMono.js";
|
|
9
11
|
import { reportPrepare } from "./reportPrepare.js";
|
|
10
12
|
import { validateConfig } from "./validateConfig.js";
|
|
13
|
+
import { validateOnlyExcludesStrandedDependents } from "./validateOnlyExcludesStrandedDependents.js";
|
|
11
14
|
const RELEASE_TAGS_FILE = "tmp/.release-tags";
|
|
12
15
|
const RELEASE_SUMMARY_FILE = "tmp/.release-summary";
|
|
13
16
|
const VALID_BUMP_TYPES = ["major", "minor", "patch"];
|
|
@@ -22,10 +25,14 @@ Usage: npx @williamthorsen/release-kit prepare [options]
|
|
|
22
25
|
Options:
|
|
23
26
|
--dry-run Run without modifying any files
|
|
24
27
|
--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.
|
|
26
|
-
|
|
28
|
+
--set-version=X.Y.Z Set an explicit version; bypasses commit-derived bumps.
|
|
29
|
+
Requires --only in monorepo mode (rejected when a 'project' block is configured).
|
|
30
|
+
--force Release even when no commits or no bump-worthy commits exist
|
|
31
|
+
since the last tag. Defaults to patch when --bump is not given;
|
|
32
|
+
use --bump=X to release at a different level.
|
|
27
33
|
--no-git-checks, -n Skip the clean-working-tree check
|
|
28
|
-
--only=name1,name2 Only process the named workspaces (comma-separated, monorepo only
|
|
34
|
+
--only=name1,name2 Only process the named workspaces (comma-separated, monorepo only;
|
|
35
|
+
rejected when a 'project' block is configured)
|
|
29
36
|
--with-release-notes Also write per-workspace release-notes previews under {workspacePath}/docs/
|
|
30
37
|
(docs/README.v{version}.md and docs/RELEASE_NOTES.v{version}.md).
|
|
31
38
|
Recommended .gitignore entry: packages/*/docs/*.v*.md (or docs/*.v*.md).
|
|
@@ -84,9 +91,6 @@ function parseArgs(argv) {
|
|
|
84
91
|
if (setVersion !== void 0 && flags.force) {
|
|
85
92
|
throw new Error("--set-version cannot be combined with --force");
|
|
86
93
|
}
|
|
87
|
-
if (flags.force && bumpOverride === void 0) {
|
|
88
|
-
throw new Error("--force requires --bump to specify the version bump type");
|
|
89
|
-
}
|
|
90
94
|
return {
|
|
91
95
|
dryRun: flags.dryRun,
|
|
92
96
|
force: flags.force,
|
|
@@ -157,17 +161,36 @@ function runSinglePackageMode(userConfig, options, only, dryRun) {
|
|
|
157
161
|
console.error("Error: --only is only supported for monorepo configurations");
|
|
158
162
|
process.exit(1);
|
|
159
163
|
}
|
|
164
|
+
if (options.force && options.bumpOverride === void 0) {
|
|
165
|
+
console.error(
|
|
166
|
+
"Error: --force without --bump is only supported for monorepo configurations. Use --bump=major|minor|patch to set the level for a single-package release."
|
|
167
|
+
);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
160
170
|
const config = mergeSinglePackageConfig(userConfig);
|
|
161
171
|
runAndReport(() => releasePrepare(config, options), dryRun);
|
|
162
172
|
}
|
|
163
173
|
function runMonorepoMode(discoveredPaths, userConfig, options, only, setVersion, dryRun) {
|
|
164
174
|
let config;
|
|
165
175
|
try {
|
|
166
|
-
|
|
176
|
+
const rootPackage = readRootPackageVersion();
|
|
177
|
+
config = mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage);
|
|
167
178
|
} catch (error) {
|
|
168
179
|
console.error(`Error resolving workspaces: ${error instanceof Error ? error.message : String(error)}`);
|
|
169
180
|
process.exit(1);
|
|
170
181
|
}
|
|
182
|
+
if (setVersion !== void 0 && config.project !== void 0) {
|
|
183
|
+
console.error(
|
|
184
|
+
"Error: --set-version cannot be combined with a project release. --set-version operates on a single workspace; a project release rolls up every contributing workspace. To use --set-version, run on a config without a `project` block."
|
|
185
|
+
);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
if (only !== void 0 && config.project !== void 0) {
|
|
189
|
+
console.error(
|
|
190
|
+
"Error: --only cannot be combined with a project release. To release a single workspace, use a config without a `project` block, or run a full `prepare` (no --only) to include the project release."
|
|
191
|
+
);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
171
194
|
if (only !== void 0) {
|
|
172
195
|
const knownNames = config.workspaces.map((w) => w.dir);
|
|
173
196
|
for (const name of only) {
|
|
@@ -176,6 +199,25 @@ function runMonorepoMode(discoveredPaths, userConfig, options, only, setVersion,
|
|
|
176
199
|
process.exit(1);
|
|
177
200
|
}
|
|
178
201
|
}
|
|
202
|
+
const graph = buildDependencyGraph(config.workspaces);
|
|
203
|
+
const violations = validateOnlyExcludesStrandedDependents(config.workspaces, only, graph, (workspace) => {
|
|
204
|
+
const tagPrefixes = [
|
|
205
|
+
workspace.tagPrefix,
|
|
206
|
+
...workspace.legacyIdentities?.map((identity) => identity.tagPrefix) ?? []
|
|
207
|
+
];
|
|
208
|
+
const result = getCommitsSinceTarget(tagPrefixes, workspace.paths);
|
|
209
|
+
return { has: result.commits.length > 0, tag: result.tag };
|
|
210
|
+
});
|
|
211
|
+
if (violations !== void 0) {
|
|
212
|
+
console.error("Error: --only excludes packages with changes that would be stranded by the release.");
|
|
213
|
+
console.error("The following packages must be added to --only or have their dependencies removed:");
|
|
214
|
+
for (const violation of violations) {
|
|
215
|
+
const since = violation.tag ?? "the beginning";
|
|
216
|
+
console.error(` - ${violation.dir} (downstream of ${violation.downstreamOf}; has commits since ${since})`);
|
|
217
|
+
}
|
|
218
|
+
console.error("Alternatively, run `release-kit prepare` without --only to release everything.");
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
179
221
|
config.workspaces = config.workspaces.filter((w) => only.includes(w.dir));
|
|
180
222
|
}
|
|
181
223
|
if (setVersion !== void 0) {
|
|
@@ -221,7 +263,7 @@ function runAndReport(execute, dryRun) {
|
|
|
221
263
|
try {
|
|
222
264
|
result = execute();
|
|
223
265
|
} catch (error) {
|
|
224
|
-
console.error(
|
|
266
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
225
267
|
process.exit(1);
|
|
226
268
|
}
|
|
227
269
|
process.stdout.write(reportPrepare(result) + "\n");
|
package/dist/esm/publish.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import type { PackageManager } from './detectPackageManager.ts';
|
|
|
2
2
|
import type { ResolvedTag } from './resolveReleaseTags.ts';
|
|
3
3
|
export interface PublishOptions {
|
|
4
4
|
dryRun: boolean;
|
|
5
|
-
noGitChecks: boolean;
|
|
6
5
|
provenance: boolean;
|
|
7
6
|
}
|
|
8
7
|
export declare function publishPackage(resolvedTag: ResolvedTag, packageManager: PackageManager, options: PublishOptions): void;
|
package/dist/esm/publish.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
function publishPackage(resolvedTag, packageManager, options) {
|
|
3
|
-
const { dryRun,
|
|
3
|
+
const { dryRun, provenance } = options;
|
|
4
4
|
const executable = resolveExecutable(packageManager);
|
|
5
|
-
const args = buildPublishArgs(packageManager, { dryRun,
|
|
5
|
+
const args = buildPublishArgs(packageManager, { dryRun, provenance });
|
|
6
6
|
console.info(
|
|
7
7
|
`
|
|
8
8
|
${dryRun ? "[dry-run] " : ""}Running: ${executable} ${args.join(" ")} (cwd: ${resolvedTag.workspacePath})`
|
|
@@ -20,7 +20,7 @@ function buildPublishArgs(packageManager, options) {
|
|
|
20
20
|
if (options.dryRun) {
|
|
21
21
|
args.push("--dry-run");
|
|
22
22
|
}
|
|
23
|
-
if (
|
|
23
|
+
if (packageManager === "pnpm") {
|
|
24
24
|
args.push("--no-git-checks");
|
|
25
25
|
}
|
|
26
26
|
if (options.provenance && packageManager !== "yarn") {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { writeFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { parseArgs, translateParseError } from "@williamthorsen/nmr-core";
|
|
4
|
+
import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
|
|
4
5
|
import { detectPackageManager } from "./detectPackageManager.js";
|
|
5
6
|
import { injectReleaseNotesIntoReadme, resolveReadmePath } from "./injectReleaseNotesIntoReadme.js";
|
|
6
7
|
import { parseRequestedTags } from "./parseRequestedTags.js";
|
|
@@ -22,21 +23,34 @@ async function publishCommand(argv) {
|
|
|
22
23
|
process.exit(1);
|
|
23
24
|
}
|
|
24
25
|
const { dryRun, noGitChecks, provenance } = parsed.flags;
|
|
26
|
+
if (!dryRun && !noGitChecks) {
|
|
27
|
+
try {
|
|
28
|
+
assertCleanWorkingTree();
|
|
29
|
+
} catch (error) {
|
|
30
|
+
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
25
34
|
const requestedTags = parseRequestedTags(parsed.flags.tags);
|
|
26
35
|
const resolvedTags = await resolveCommandTags(requestedTags);
|
|
27
36
|
if (resolvedTags.length === 0) {
|
|
28
37
|
return;
|
|
29
38
|
}
|
|
39
|
+
const publishableTags = filterPublishableTags(resolvedTags, requestedTags !== void 0);
|
|
40
|
+
if (publishableTags.length === 0) {
|
|
41
|
+
console.info("Nothing to publish.");
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
30
44
|
const packageManager = detectPackageManager();
|
|
31
45
|
const { releaseNotes, changelogJsonOutputPath, sectionOrder } = await resolveReleaseNotesConfig();
|
|
32
46
|
const shouldInject = releaseNotes.shouldInjectIntoReadme;
|
|
33
47
|
console.info(dryRun ? "[dry-run] Would publish:" : "Publishing:");
|
|
34
|
-
for (const { tag, workspacePath } of
|
|
48
|
+
for (const { tag, workspacePath } of publishableTags) {
|
|
35
49
|
console.info(` ${tag} (${workspacePath})`);
|
|
36
50
|
}
|
|
37
51
|
const published = [];
|
|
38
52
|
try {
|
|
39
|
-
for (const resolvedTag of
|
|
53
|
+
for (const resolvedTag of publishableTags) {
|
|
40
54
|
let readmePath;
|
|
41
55
|
let originalReadme;
|
|
42
56
|
if (shouldInject) {
|
|
@@ -51,7 +65,7 @@ async function publishCommand(argv) {
|
|
|
51
65
|
}
|
|
52
66
|
}
|
|
53
67
|
try {
|
|
54
|
-
publishPackage(resolvedTag, packageManager, { dryRun,
|
|
68
|
+
publishPackage(resolvedTag, packageManager, { dryRun, provenance });
|
|
55
69
|
published.push(resolvedTag.tag);
|
|
56
70
|
} finally {
|
|
57
71
|
if (readmePath !== void 0 && originalReadme !== void 0) {
|
|
@@ -70,6 +84,20 @@ async function publishCommand(argv) {
|
|
|
70
84
|
process.exit(1);
|
|
71
85
|
}
|
|
72
86
|
}
|
|
87
|
+
function filterPublishableTags(resolvedTags, isExplicit) {
|
|
88
|
+
const publishable = [];
|
|
89
|
+
const unpublishable = [];
|
|
90
|
+
for (const tag of resolvedTags) {
|
|
91
|
+
(tag.isPublishable ? publishable : unpublishable).push(tag);
|
|
92
|
+
}
|
|
93
|
+
if (isExplicit && unpublishable.length > 0) {
|
|
94
|
+
for (const { tag, workspacePath } of unpublishable) {
|
|
95
|
+
console.error(`Error: ${tag} (${workspacePath}) cannot be published: package.json#private is true.`);
|
|
96
|
+
}
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
return publishable;
|
|
100
|
+
}
|
|
73
101
|
export {
|
|
74
102
|
publishCommand
|
|
75
103
|
};
|