@williamthorsen/release-kit 5.1.0 → 5.2.1
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 +113 -65
- package/README.md +147 -70
- package/cliff.toml.template +26 -17
- package/dist/esm/.cache +1 -1
- package/dist/esm/bin/release-kit.js +96 -3
- package/dist/esm/buildChangelogEntries.d.ts +1 -0
- package/dist/esm/buildChangelogEntries.js +39 -25
- 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 +2 -7
- package/dist/esm/decideRelease.d.ts +3 -0
- package/dist/esm/decideRelease.js +19 -3
- package/dist/esm/defaults.d.ts +7 -0
- package/dist/esm/defaults.js +41 -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/loadConfig.js +14 -22
- package/dist/esm/parseCommitMessage.d.ts +8 -2
- package/dist/esm/parseCommitMessage.js +32 -3
- package/dist/esm/publishCommand.js +21 -2
- package/dist/esm/releasePrepare.js +39 -15
- package/dist/esm/releasePrepareMono.js +26 -3
- package/dist/esm/releasePrepareProject.js +13 -1
- package/dist/esm/renderReleaseNotes.js +2 -1
- package/dist/esm/reportPrepare.js +18 -0
- 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 +15 -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 +6 -3
- package/dist/esm/version.d.ts +0 -1
- package/dist/esm/version.js +0 -4
|
@@ -2,8 +2,9 @@ import { execSync } from "node:child_process";
|
|
|
2
2
|
import { buildChangelogEntries } from "./buildChangelogEntries.js";
|
|
3
3
|
import { bumpAllVersions, setAllVersions } from "./bumpAllVersions.js";
|
|
4
4
|
import { resolveChangelogJsonPath, upsertChangelogJson } from "./changelogJsonFile.js";
|
|
5
|
+
import { createPolicyViolationCollector } from "./collectPolicyViolations.js";
|
|
5
6
|
import { isForwardVersion } from "./compareVersions.js";
|
|
6
|
-
import { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
7
|
+
import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
7
8
|
import { determineBumpFromCommits } from "./determineBumpFromCommits.js";
|
|
8
9
|
import { generateChangelogs } from "./generateChangelogs.js";
|
|
9
10
|
import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
|
|
@@ -16,10 +17,12 @@ function releasePrepare(config, options) {
|
|
|
16
17
|
const { dryRun, bumpOverride, setVersion, withReleaseNotes } = options;
|
|
17
18
|
const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
|
|
18
19
|
const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
|
|
20
|
+
const breakingPolicies = config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES;
|
|
19
21
|
const { tag, commits } = getCommitsSinceTarget([config.tagPrefix]);
|
|
20
22
|
let releaseType;
|
|
21
23
|
let parsedCommitCount;
|
|
22
24
|
let unparseableCommits;
|
|
25
|
+
const collector = createPolicyViolationCollector();
|
|
23
26
|
let bump;
|
|
24
27
|
if (setVersion !== void 0) {
|
|
25
28
|
const primaryPackageFile = config.packageFiles[0];
|
|
@@ -36,7 +39,10 @@ function releasePrepare(config, options) {
|
|
|
36
39
|
bump = setAllVersions(config.packageFiles, setVersion, dryRun);
|
|
37
40
|
} else {
|
|
38
41
|
if (bumpOverride === void 0) {
|
|
39
|
-
const determination = determineBumpFromCommits(commits, workTypes, versionPatterns, config.scopeAliases
|
|
42
|
+
const determination = determineBumpFromCommits(commits, workTypes, versionPatterns, config.scopeAliases, {
|
|
43
|
+
breakingPolicies,
|
|
44
|
+
onPolicyViolation: collector.onPolicyViolation
|
|
45
|
+
});
|
|
40
46
|
parsedCommitCount = determination.parsedCommitCount;
|
|
41
47
|
unparseableCommits = determination.unparseableCommits;
|
|
42
48
|
releaseType = determination.releaseType;
|
|
@@ -44,20 +50,13 @@ function releasePrepare(config, options) {
|
|
|
44
50
|
releaseType = bumpOverride;
|
|
45
51
|
}
|
|
46
52
|
if (releaseType === void 0) {
|
|
47
|
-
const skipped = {
|
|
48
|
-
status: "skipped",
|
|
53
|
+
const skipped = buildSkippedSinglePackage({
|
|
49
54
|
commitCount: commits.length,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
if (parsedCommitCount !== void 0) {
|
|
56
|
-
skipped.parsedCommitCount = parsedCommitCount;
|
|
57
|
-
}
|
|
58
|
-
if (unparseableCommits !== void 0) {
|
|
59
|
-
skipped.unparseableCommits = unparseableCommits;
|
|
60
|
-
}
|
|
55
|
+
previousTag: tag,
|
|
56
|
+
parsedCommitCount,
|
|
57
|
+
unparseableCommits,
|
|
58
|
+
policyViolations: collector.violations.length > 0 ? collector.violations : void 0
|
|
59
|
+
});
|
|
61
60
|
return {
|
|
62
61
|
workspaces: [skipped],
|
|
63
62
|
tags: [],
|
|
@@ -100,6 +99,7 @@ function releasePrepare(config, options) {
|
|
|
100
99
|
parsedCommitCount,
|
|
101
100
|
releaseType,
|
|
102
101
|
unparseableCommits,
|
|
102
|
+
policyViolations: collector.violations.length > 0 ? collector.violations : void 0,
|
|
103
103
|
setVersion
|
|
104
104
|
});
|
|
105
105
|
return {
|
|
@@ -109,6 +109,26 @@ function releasePrepare(config, options) {
|
|
|
109
109
|
dryRun
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
|
+
function buildSkippedSinglePackage(args) {
|
|
113
|
+
const skipped = {
|
|
114
|
+
status: "skipped",
|
|
115
|
+
commitCount: args.commitCount,
|
|
116
|
+
skipReason: "No release-worthy changes found. Skipping."
|
|
117
|
+
};
|
|
118
|
+
if (args.previousTag !== void 0) {
|
|
119
|
+
skipped.previousTag = args.previousTag;
|
|
120
|
+
}
|
|
121
|
+
if (args.parsedCommitCount !== void 0) {
|
|
122
|
+
skipped.parsedCommitCount = args.parsedCommitCount;
|
|
123
|
+
}
|
|
124
|
+
if (args.unparseableCommits !== void 0) {
|
|
125
|
+
skipped.unparseableCommits = args.unparseableCommits;
|
|
126
|
+
}
|
|
127
|
+
if (args.policyViolations !== void 0) {
|
|
128
|
+
skipped.policyViolations = args.policyViolations;
|
|
129
|
+
}
|
|
130
|
+
return skipped;
|
|
131
|
+
}
|
|
112
132
|
function buildReleasedSinglePackage(args) {
|
|
113
133
|
const {
|
|
114
134
|
commits,
|
|
@@ -119,6 +139,7 @@ function buildReleasedSinglePackage(args) {
|
|
|
119
139
|
parsedCommitCount,
|
|
120
140
|
releaseType,
|
|
121
141
|
unparseableCommits,
|
|
142
|
+
policyViolations,
|
|
122
143
|
setVersion
|
|
123
144
|
} = args;
|
|
124
145
|
const released = {
|
|
@@ -143,6 +164,9 @@ function buildReleasedSinglePackage(args) {
|
|
|
143
164
|
if (unparseableCommits !== void 0) {
|
|
144
165
|
released.unparseableCommits = unparseableCommits;
|
|
145
166
|
}
|
|
167
|
+
if (policyViolations !== void 0) {
|
|
168
|
+
released.policyViolations = policyViolations;
|
|
169
|
+
}
|
|
146
170
|
if (setVersion !== void 0) {
|
|
147
171
|
released.setVersion = setVersion;
|
|
148
172
|
}
|
|
@@ -4,9 +4,10 @@ import { buildDependencyGraph } from "./buildDependencyGraph.js";
|
|
|
4
4
|
import { buildSyntheticChangelogEntry } from "./buildSyntheticChangelogEntry.js";
|
|
5
5
|
import { bumpAllVersions, setAllVersions } from "./bumpAllVersions.js";
|
|
6
6
|
import { resolveChangelogJsonPath, upsertChangelogJson } from "./changelogJsonFile.js";
|
|
7
|
+
import { createPolicyViolationCollector } from "./collectPolicyViolations.js";
|
|
7
8
|
import { isForwardVersion } from "./compareVersions.js";
|
|
8
9
|
import { decideRelease } from "./decideRelease.js";
|
|
9
|
-
import { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
10
|
+
import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
10
11
|
import { detectUndeclaredTagPrefixes } from "./detectUndeclaredTagPrefixes.js";
|
|
11
12
|
import { buildTagPattern, generateChangelog } from "./generateChangelogs.js";
|
|
12
13
|
import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
|
|
@@ -83,6 +84,7 @@ function determineDirectBumps(config, options) {
|
|
|
83
84
|
}
|
|
84
85
|
const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
|
|
85
86
|
const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
|
|
87
|
+
const breakingPolicies = config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES;
|
|
86
88
|
const directBumps = /* @__PURE__ */ new Map();
|
|
87
89
|
const directResults = /* @__PURE__ */ new Map();
|
|
88
90
|
const skippedResults = [];
|
|
@@ -125,11 +127,13 @@ function determineDirectBumps(config, options) {
|
|
|
125
127
|
releaseType: void 0,
|
|
126
128
|
parsedCommitCount: void 0,
|
|
127
129
|
unparseableCommits: void 0,
|
|
130
|
+
policyViolations: void 0,
|
|
128
131
|
bumpOverride: void 0,
|
|
129
132
|
setVersion
|
|
130
133
|
});
|
|
131
134
|
continue;
|
|
132
135
|
}
|
|
136
|
+
const collector = createPolicyViolationCollector();
|
|
133
137
|
const decision = tryStage(
|
|
134
138
|
stageLabel,
|
|
135
139
|
() => decideRelease({
|
|
@@ -139,12 +143,15 @@ function determineDirectBumps(config, options) {
|
|
|
139
143
|
workTypes,
|
|
140
144
|
versionPatterns,
|
|
141
145
|
scopeAliases: config.scopeAliases,
|
|
146
|
+
breakingPolicies,
|
|
147
|
+
onPolicyViolation: collector.onPolicyViolation,
|
|
142
148
|
skipReasons: {
|
|
143
149
|
noCommits: `No commits for ${name} ${since}. Pass --force to release at patch. Skipping.`,
|
|
144
150
|
noBumpWorthy: `No bump-worthy commits for ${name} ${since}. Pass --force to release at patch (or --force --bump=X for a different level). Skipping.`
|
|
145
151
|
}
|
|
146
152
|
})
|
|
147
153
|
);
|
|
154
|
+
const policyViolations = collector.violations.length > 0 ? collector.violations : void 0;
|
|
148
155
|
if (decision.outcome === "skip") {
|
|
149
156
|
skippedResults.push({
|
|
150
157
|
workspace,
|
|
@@ -152,6 +159,7 @@ function determineDirectBumps(config, options) {
|
|
|
152
159
|
commitCount: commits.length,
|
|
153
160
|
parsedCommitCount: decision.parsedCommitCount,
|
|
154
161
|
unparseableCommits: decision.unparseableCommits,
|
|
162
|
+
policyViolations,
|
|
155
163
|
skipReason: decision.skipReason
|
|
156
164
|
});
|
|
157
165
|
continue;
|
|
@@ -164,6 +172,7 @@ function determineDirectBumps(config, options) {
|
|
|
164
172
|
releaseType: decision.releaseType,
|
|
165
173
|
parsedCommitCount: decision.parsedCommitCount,
|
|
166
174
|
unparseableCommits: decision.unparseableCommits,
|
|
175
|
+
policyViolations,
|
|
167
176
|
bumpOverride
|
|
168
177
|
});
|
|
169
178
|
}
|
|
@@ -190,6 +199,9 @@ function collectSkippedWorkspaces(skippedResults, fullReleaseSet) {
|
|
|
190
199
|
if (skipped.unparseableCommits !== void 0) {
|
|
191
200
|
result.unparseableCommits = skipped.unparseableCommits;
|
|
192
201
|
}
|
|
202
|
+
if (skipped.policyViolations !== void 0) {
|
|
203
|
+
result.policyViolations = skipped.policyViolations;
|
|
204
|
+
}
|
|
193
205
|
workspaces.push(result);
|
|
194
206
|
}
|
|
195
207
|
return workspaces;
|
|
@@ -270,7 +282,16 @@ function executeWorkspaceRelease(args) {
|
|
|
270
282
|
bumpedFiles: bump.files,
|
|
271
283
|
changelogFiles
|
|
272
284
|
};
|
|
273
|
-
|
|
285
|
+
attachReleasedWorkspaceOptionals(released, {
|
|
286
|
+
previousTag: directResult?.tag ?? previousTags.get(dir),
|
|
287
|
+
directResult,
|
|
288
|
+
releaseEntry,
|
|
289
|
+
setVersionTarget
|
|
290
|
+
});
|
|
291
|
+
workspaces.push(released);
|
|
292
|
+
}
|
|
293
|
+
function attachReleasedWorkspaceOptionals(released, args) {
|
|
294
|
+
const { previousTag, directResult, releaseEntry, setVersionTarget } = args;
|
|
274
295
|
if (previousTag !== void 0) {
|
|
275
296
|
released.previousTag = previousTag;
|
|
276
297
|
}
|
|
@@ -286,6 +307,9 @@ function executeWorkspaceRelease(args) {
|
|
|
286
307
|
if (directResult?.unparseableCommits !== void 0) {
|
|
287
308
|
released.unparseableCommits = directResult.unparseableCommits;
|
|
288
309
|
}
|
|
310
|
+
if (directResult?.policyViolations !== void 0) {
|
|
311
|
+
released.policyViolations = directResult.policyViolations;
|
|
312
|
+
}
|
|
289
313
|
if (releaseEntry.propagatedFrom !== void 0) {
|
|
290
314
|
released.propagatedFrom = releaseEntry.propagatedFrom;
|
|
291
315
|
}
|
|
@@ -295,7 +319,6 @@ function executeWorkspaceRelease(args) {
|
|
|
295
319
|
if (setVersionTarget !== void 0) {
|
|
296
320
|
released.setVersion = setVersionTarget;
|
|
297
321
|
}
|
|
298
|
-
workspaces.push(released);
|
|
299
322
|
}
|
|
300
323
|
function generateWorkspaceChangelogs(args) {
|
|
301
324
|
const {
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { buildChangelogEntries } from "./buildChangelogEntries.js";
|
|
2
2
|
import { bumpAllVersions } from "./bumpAllVersions.js";
|
|
3
3
|
import { resolveChangelogJsonPath, writeChangelogJson } from "./changelogJsonFile.js";
|
|
4
|
+
import { createPolicyViolationCollector } from "./collectPolicyViolations.js";
|
|
4
5
|
import { decideRelease } from "./decideRelease.js";
|
|
5
|
-
import { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
6
|
+
import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
6
7
|
import { buildTagPattern, generateChangelog } from "./generateChangelogs.js";
|
|
7
8
|
import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
|
|
8
9
|
import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
|
|
@@ -18,9 +19,11 @@ function releasePrepareProject(args) {
|
|
|
18
19
|
}
|
|
19
20
|
const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
|
|
20
21
|
const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
|
|
22
|
+
const breakingPolicies = config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES;
|
|
21
23
|
const contributingPaths = config.workspaces.flatMap((workspace) => workspace.paths);
|
|
22
24
|
const { tag, commits } = getCommitsSinceTarget([project.tagPrefix], contributingPaths);
|
|
23
25
|
const since = tag === void 0 ? "(no previous release found)" : `since ${tag}`;
|
|
26
|
+
const collector = createPolicyViolationCollector();
|
|
24
27
|
const decision = decideRelease({
|
|
25
28
|
commits,
|
|
26
29
|
force,
|
|
@@ -28,11 +31,14 @@ function releasePrepareProject(args) {
|
|
|
28
31
|
workTypes,
|
|
29
32
|
versionPatterns,
|
|
30
33
|
scopeAliases: config.scopeAliases,
|
|
34
|
+
breakingPolicies,
|
|
35
|
+
onPolicyViolation: collector.onPolicyViolation,
|
|
31
36
|
skipReasons: {
|
|
32
37
|
noCommits: `No commits ${since}. Pass --force to release at patch. Skipping.`,
|
|
33
38
|
noBumpWorthy: `No bump-worthy commits ${since}. Pass --force to release at patch (or --force --bump=X for a different level). Skipping.`
|
|
34
39
|
}
|
|
35
40
|
});
|
|
41
|
+
const policyViolations = collector.violations.length > 0 ? collector.violations : void 0;
|
|
36
42
|
if (decision.outcome === "skip") {
|
|
37
43
|
const skipped = {
|
|
38
44
|
status: "skipped",
|
|
@@ -46,6 +52,9 @@ function releasePrepareProject(args) {
|
|
|
46
52
|
if (decision.unparseableCommits !== void 0) {
|
|
47
53
|
skipped.unparseableCommits = decision.unparseableCommits;
|
|
48
54
|
}
|
|
55
|
+
if (policyViolations !== void 0) {
|
|
56
|
+
skipped.policyViolations = policyViolations;
|
|
57
|
+
}
|
|
49
58
|
return skipped;
|
|
50
59
|
}
|
|
51
60
|
const { releaseType, parsedCommitCount, unparseableCommits } = decision;
|
|
@@ -99,6 +108,9 @@ function releasePrepareProject(args) {
|
|
|
99
108
|
if (unparseableCommits !== void 0) {
|
|
100
109
|
result.unparseableCommits = unparseableCommits;
|
|
101
110
|
}
|
|
111
|
+
if (policyViolations !== void 0) {
|
|
112
|
+
result.policyViolations = policyViolations;
|
|
113
|
+
}
|
|
102
114
|
if (bumpOverride !== void 0) {
|
|
103
115
|
result.bumpOverride = bumpOverride;
|
|
104
116
|
}
|
|
@@ -26,7 +26,8 @@ function renderReleaseNotesSingle(entry, options) {
|
|
|
26
26
|
}
|
|
27
27
|
lines.push(`### ${section.title}`, "");
|
|
28
28
|
for (const [index, item] of section.items.entries()) {
|
|
29
|
-
|
|
29
|
+
const prefix = item.breaking === true ? "\u{1F6A8} **Breaking:** " : "";
|
|
30
|
+
lines.push(`- ${prefix}${item.description}`);
|
|
30
31
|
if (item.body !== void 0 && item.body.length > 0) {
|
|
31
32
|
lines.push("", ...indentBodyLines(item.body));
|
|
32
33
|
if (index < section.items.length - 1) {
|
|
@@ -18,6 +18,7 @@ function formatSingleWorkspace(result) {
|
|
|
18
18
|
lines.push(dim(` Parsed ${workspace.parsedCommitCount} typed commits`));
|
|
19
19
|
}
|
|
20
20
|
formatUnparseableWarning(lines, workspace);
|
|
21
|
+
formatPolicyViolations(lines, workspace.policyViolations);
|
|
21
22
|
if (workspace.status === "skipped") {
|
|
22
23
|
lines.push(`\u23ED\uFE0F ${workspace.skipReason}`);
|
|
23
24
|
return lines.join("\n");
|
|
@@ -71,6 +72,7 @@ function formatProjectSection(lines, project, dryRun) {
|
|
|
71
72
|
${sectionHeader("project")}`);
|
|
72
73
|
const since = project.previousTag === void 0 ? "(no previous release found)" : `since ${project.previousTag}`;
|
|
73
74
|
lines.push(dim(` Found ${project.commitCount} commits ${since}`));
|
|
75
|
+
formatPolicyViolations(lines, project.policyViolations, " ");
|
|
74
76
|
if (project.status === "skipped") {
|
|
75
77
|
lines.push(` \u23ED\uFE0F ${project.skipReason}`);
|
|
76
78
|
return;
|
|
@@ -134,6 +136,7 @@ ${sectionHeader(workspace.name)}`);
|
|
|
134
136
|
const isPropagatedOnly = propagatedFrom !== void 0 && workspace.commitCount === 0;
|
|
135
137
|
formatCommitSummary(lines, workspace, propagatedFrom, isPropagatedOnly);
|
|
136
138
|
formatUnparseableWarning(lines, workspace, " ");
|
|
139
|
+
formatPolicyViolations(lines, workspace.policyViolations, " ");
|
|
137
140
|
formatBumpLabels(lines, workspace, isPropagatedOnly);
|
|
138
141
|
formatVersionLine(lines, workspace, propagatedFrom, isPropagatedOnly);
|
|
139
142
|
formatBumpFiles(lines, workspace, dryRun, " ");
|
|
@@ -202,6 +205,21 @@ function formatUnparseableWarning(lines, workspace, indent = "") {
|
|
|
202
205
|
lines.push(`${indent} \xB7 ${shortHash} ${truncatedMessage}`);
|
|
203
206
|
}
|
|
204
207
|
}
|
|
208
|
+
function formatPolicyViolations(lines, violations, indent = "") {
|
|
209
|
+
if (violations === void 0 || violations.length === 0) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const count = violations.length;
|
|
213
|
+
lines.push(`${indent} \u26A0\uFE0F ${count} policy violation${count === 1 ? "" : "s"}:`);
|
|
214
|
+
for (const violation of violations) {
|
|
215
|
+
const shortHash = violation.commitHash.slice(0, 7);
|
|
216
|
+
const subject = violation.commitSubject;
|
|
217
|
+
const truncatedSubject = subject.length > 72 ? `${subject.slice(0, 69)}...` : subject;
|
|
218
|
+
lines.push(
|
|
219
|
+
`${indent} \xB7 ${shortHash} '${truncatedSubject}' \u2014 type '${violation.type}' at ${violation.surface} surface`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
205
223
|
function formatPropagationSuffix(propagatedFrom) {
|
|
206
224
|
if (propagatedFrom === void 0 || propagatedFrom.length === 0) {
|
|
207
225
|
return "";
|
|
@@ -10,15 +10,25 @@ async function resolveCommandTags(tags) {
|
|
|
10
10
|
process.exit(1);
|
|
11
11
|
}
|
|
12
12
|
let workspaces;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
let singleWorkspace;
|
|
14
|
+
try {
|
|
15
|
+
if (discoveredPaths === void 0) {
|
|
16
|
+
singleWorkspace = deriveWorkspaceConfig(".");
|
|
17
|
+
} else {
|
|
15
18
|
workspaces = discoveredPaths.map((workspacePath) => deriveWorkspaceConfig(workspacePath));
|
|
16
|
-
} catch (error) {
|
|
17
|
-
console.error(`Error resolving workspaces: ${error instanceof Error ? error.message : String(error)}`);
|
|
18
|
-
process.exit(1);
|
|
19
19
|
}
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error(`Error resolving workspaces: ${error instanceof Error ? error.message : String(error)}`);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
let resolvedTags;
|
|
25
|
+
if (workspaces !== void 0) {
|
|
26
|
+
resolvedTags = resolveReleaseTags({ workspaces });
|
|
27
|
+
} else if (singleWorkspace !== void 0) {
|
|
28
|
+
resolvedTags = resolveReleaseTags({ singleWorkspace });
|
|
29
|
+
} else {
|
|
30
|
+
throw new Error("resolveCommandTags: invariant violated \u2014 neither workspaces nor singleWorkspace was derived");
|
|
20
31
|
}
|
|
21
|
-
let resolvedTags = resolveReleaseTags(workspaces);
|
|
22
32
|
if (resolvedTags.length === 0) {
|
|
23
33
|
console.error("Error: No release tags found on HEAD. Create tags with `release-kit tag` first.");
|
|
24
34
|
process.exit(1);
|
|
@@ -3,5 +3,12 @@ export interface ResolvedTag {
|
|
|
3
3
|
tag: string;
|
|
4
4
|
dir: string;
|
|
5
5
|
workspacePath: string;
|
|
6
|
+
isPublishable: boolean;
|
|
6
7
|
}
|
|
7
|
-
|
|
8
|
+
type ResolveReleaseTagsArgs = {
|
|
9
|
+
workspaces: readonly WorkspaceConfig[];
|
|
10
|
+
} | {
|
|
11
|
+
singleWorkspace: WorkspaceConfig;
|
|
12
|
+
};
|
|
13
|
+
export declare function resolveReleaseTags(args?: ResolveReleaseTagsArgs): ResolvedTag[];
|
|
14
|
+
export {};
|
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
const VERSION_PATTERN = /^v\d+\.\d+\.\d+/;
|
|
3
3
|
const SEMVER_SUFFIX_PATTERN = /^\d+\.\d+\.\d+/;
|
|
4
|
-
function resolveReleaseTags(
|
|
4
|
+
function resolveReleaseTags(args) {
|
|
5
5
|
const output = execFileSync("git", ["tag", "--points-at", "HEAD"], { encoding: "utf8" });
|
|
6
6
|
const tags = output.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
7
|
-
if (
|
|
7
|
+
if (args === void 0) {
|
|
8
8
|
return resolveSinglePackageTags(tags);
|
|
9
9
|
}
|
|
10
|
-
|
|
10
|
+
if ("workspaces" in args) {
|
|
11
|
+
return resolveMonorepoTags(tags, args.workspaces);
|
|
12
|
+
}
|
|
13
|
+
return resolveSinglePackageTags(tags, args.singleWorkspace);
|
|
11
14
|
}
|
|
12
|
-
function resolveSinglePackageTags(tags) {
|
|
15
|
+
function resolveSinglePackageTags(tags, singleWorkspace) {
|
|
13
16
|
const matched = tags.filter((tag) => VERSION_PATTERN.test(tag));
|
|
17
|
+
const isPublishable = singleWorkspace?.isPublishable ?? true;
|
|
14
18
|
if (matched.length > 1) {
|
|
15
19
|
console.warn(
|
|
16
20
|
`Warning: Multiple version tags found on HEAD: ${matched.join(", ")}. Publishing the same package multiple times is almost certainly unintended. Using only the first tag.`
|
|
17
21
|
);
|
|
18
|
-
return matched.slice(0, 1).map((tag) => ({ tag, dir: ".", workspacePath: "." }));
|
|
22
|
+
return matched.slice(0, 1).map((tag) => ({ tag, dir: ".", workspacePath: ".", isPublishable }));
|
|
19
23
|
}
|
|
20
|
-
return matched.map((tag) => ({ tag, dir: ".", workspacePath: "." }));
|
|
24
|
+
return matched.map((tag) => ({ tag, dir: ".", workspacePath: ".", isPublishable }));
|
|
21
25
|
}
|
|
22
26
|
function resolveMonorepoTags(tags, workspaces) {
|
|
23
27
|
const sortedWorkspaces = [...workspaces].sort((a, b) => b.tagPrefix.length - a.tagPrefix.length);
|
|
@@ -25,7 +29,7 @@ function resolveMonorepoTags(tags, workspaces) {
|
|
|
25
29
|
for (const tag of tags) {
|
|
26
30
|
const match = findMatchingWorkspace(tag, sortedWorkspaces);
|
|
27
31
|
if (match !== void 0) {
|
|
28
|
-
resolved.push({ tag, dir: match.dir, workspacePath: match.workspacePath });
|
|
32
|
+
resolved.push({ tag, dir: match.dir, workspacePath: match.workspacePath, isPublishable: match.isPublishable });
|
|
29
33
|
}
|
|
30
34
|
}
|
|
31
35
|
return resolved;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { copyFileSync, mkdtempSync, rmSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
function runGitCliff(cliffConfigPath, cliffArgs, stdio) {
|
|
6
|
+
let configPath = cliffConfigPath;
|
|
7
|
+
let tempDir;
|
|
8
|
+
try {
|
|
9
|
+
if (cliffConfigPath.endsWith(".template")) {
|
|
10
|
+
tempDir = mkdtempSync(join(tmpdir(), "cliff-"));
|
|
11
|
+
configPath = join(tempDir, "cliff.toml");
|
|
12
|
+
copyFileSync(cliffConfigPath, configPath);
|
|
13
|
+
}
|
|
14
|
+
return execFileSync("npx", ["--prefer-offline", "--yes", "git-cliff", "--config", configPath, ...cliffArgs], {
|
|
15
|
+
encoding: "utf8",
|
|
16
|
+
stdio,
|
|
17
|
+
env: { ...process.env, npm_config_progress: "false" }
|
|
18
|
+
});
|
|
19
|
+
} finally {
|
|
20
|
+
if (tempDir !== void 0) {
|
|
21
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export {
|
|
26
|
+
runGitCliff
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function stripEmojiPrefix(value: string): string;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface SyncResult {
|
|
2
|
+
exitCode: 0 | 2 | 3;
|
|
3
|
+
message: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SyncWorkTypesDependencies {
|
|
6
|
+
localPath?: string;
|
|
7
|
+
fetch?: typeof globalThis.fetch;
|
|
8
|
+
upstreamUrl?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function syncWorkTypes(dependencies?: SyncWorkTypesDependencies): Promise<SyncResult>;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { UPSTREAM_WORK_TYPES_URL } from "./checkWorkTypesDrift.js";
|
|
5
|
+
import { isRecord } from "./typeGuards.js";
|
|
6
|
+
import { errorMessage, hasExpectedTopLevelShape } from "./workTypesUtils.js";
|
|
7
|
+
function resolveDefaultLocalPath() {
|
|
8
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
return resolve(moduleDir, "work-types.json");
|
|
10
|
+
}
|
|
11
|
+
function extractLocalSchemaUrl(content) {
|
|
12
|
+
let parsed;
|
|
13
|
+
try {
|
|
14
|
+
parsed = JSON.parse(content);
|
|
15
|
+
} catch {
|
|
16
|
+
return void 0;
|
|
17
|
+
}
|
|
18
|
+
if (!isRecord(parsed)) {
|
|
19
|
+
return void 0;
|
|
20
|
+
}
|
|
21
|
+
const schema = parsed.$schema;
|
|
22
|
+
return typeof schema === "string" ? schema : void 0;
|
|
23
|
+
}
|
|
24
|
+
async function syncWorkTypes(dependencies = {}) {
|
|
25
|
+
const localPath = dependencies.localPath ?? resolveDefaultLocalPath();
|
|
26
|
+
const fetcher = dependencies.fetch ?? globalThis.fetch;
|
|
27
|
+
const url = dependencies.upstreamUrl ?? UPSTREAM_WORK_TYPES_URL;
|
|
28
|
+
let response;
|
|
29
|
+
try {
|
|
30
|
+
response = await fetcher(url);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
return {
|
|
33
|
+
exitCode: 2,
|
|
34
|
+
message: `Network error fetching upstream work-types.json: ${errorMessage(error)}`
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
return {
|
|
39
|
+
exitCode: 2,
|
|
40
|
+
message: `Failed to fetch upstream work-types.json: HTTP ${response.status} ${response.statusText}`
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const upstreamText = await response.text();
|
|
44
|
+
let upstreamJson;
|
|
45
|
+
try {
|
|
46
|
+
upstreamJson = JSON.parse(upstreamText);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return {
|
|
49
|
+
exitCode: 3,
|
|
50
|
+
message: `Upstream work-types.json is not valid JSON: ${errorMessage(error)}`
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (!hasExpectedTopLevelShape(upstreamJson)) {
|
|
54
|
+
return {
|
|
55
|
+
exitCode: 3,
|
|
56
|
+
message: "Upstream work-types.json does not match the expected schema shape (missing `tiers` or `types`)."
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
let priorContent;
|
|
60
|
+
try {
|
|
61
|
+
priorContent = readFileSync(localPath, "utf8");
|
|
62
|
+
} catch {
|
|
63
|
+
priorContent = void 0;
|
|
64
|
+
}
|
|
65
|
+
const localSchemaUrl = priorContent !== void 0 ? extractLocalSchemaUrl(priorContent) : void 0;
|
|
66
|
+
const outputJson = localSchemaUrl !== void 0 ? { $schema: localSchemaUrl, ...upstreamJson } : upstreamJson;
|
|
67
|
+
const formatted = `${JSON.stringify(outputJson, null, 2)}
|
|
68
|
+
`;
|
|
69
|
+
if (priorContent === formatted) {
|
|
70
|
+
return {
|
|
71
|
+
exitCode: 0,
|
|
72
|
+
message: `Local work-types.json already matches upstream (${localPath}).`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
writeFileSync(localPath, formatted, "utf8");
|
|
77
|
+
} catch (error) {
|
|
78
|
+
return {
|
|
79
|
+
exitCode: 2,
|
|
80
|
+
message: `Failed to write ${localPath}: ${errorMessage(error)}`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
exitCode: 0,
|
|
85
|
+
message: `Synced work-types.json from ${url} \u2192 ${localPath}.`
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
syncWorkTypes
|
|
90
|
+
};
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type ChangelogAudience = 'all' | 'dev';
|
|
|
3
3
|
export interface ChangelogItem {
|
|
4
4
|
description: string;
|
|
5
5
|
body?: string;
|
|
6
|
+
breaking?: boolean;
|
|
6
7
|
}
|
|
7
8
|
export interface ChangelogSection {
|
|
8
9
|
title: string;
|
|
@@ -37,6 +38,12 @@ export interface BumpResult {
|
|
|
37
38
|
newVersion: string;
|
|
38
39
|
files: string[];
|
|
39
40
|
}
|
|
41
|
+
export interface PolicyViolation {
|
|
42
|
+
commitHash: string;
|
|
43
|
+
commitSubject: string;
|
|
44
|
+
type: string;
|
|
45
|
+
surface: 'prefix' | 'body';
|
|
46
|
+
}
|
|
40
47
|
export interface ReleasedWorkspaceResult {
|
|
41
48
|
status: 'released';
|
|
42
49
|
name?: string;
|
|
@@ -44,6 +51,7 @@ export interface ReleasedWorkspaceResult {
|
|
|
44
51
|
commitCount: number;
|
|
45
52
|
parsedCommitCount?: number;
|
|
46
53
|
unparseableCommits?: Commit[];
|
|
54
|
+
policyViolations?: PolicyViolation[];
|
|
47
55
|
releaseType?: ReleaseType;
|
|
48
56
|
currentVersion: string;
|
|
49
57
|
newVersion: string;
|
|
@@ -62,6 +70,7 @@ export interface SkippedWorkspaceResult {
|
|
|
62
70
|
commitCount: number;
|
|
63
71
|
parsedCommitCount?: number;
|
|
64
72
|
unparseableCommits?: Commit[];
|
|
73
|
+
policyViolations?: PolicyViolation[];
|
|
65
74
|
skipReason: string;
|
|
66
75
|
}
|
|
67
76
|
export type WorkspacePrepareResult = ReleasedWorkspaceResult | SkippedWorkspaceResult;
|
|
@@ -71,6 +80,7 @@ export interface ReleasedProjectResult {
|
|
|
71
80
|
commitCount: number;
|
|
72
81
|
parsedCommitCount: number;
|
|
73
82
|
unparseableCommits?: Commit[];
|
|
83
|
+
policyViolations?: PolicyViolation[];
|
|
74
84
|
releaseType: ReleaseType;
|
|
75
85
|
currentVersion: string;
|
|
76
86
|
newVersion: string;
|
|
@@ -86,6 +96,7 @@ export interface SkippedProjectResult {
|
|
|
86
96
|
commitCount: number;
|
|
87
97
|
parsedCommitCount: number;
|
|
88
98
|
unparseableCommits?: Commit[];
|
|
99
|
+
policyViolations?: PolicyViolation[];
|
|
89
100
|
skipReason: string;
|
|
90
101
|
}
|
|
91
102
|
export type ProjectPrepareResult = ReleasedProjectResult | SkippedProjectResult;
|
|
@@ -113,6 +124,7 @@ export interface ReleaseKitConfig {
|
|
|
113
124
|
workspaces?: WorkspaceOverride[];
|
|
114
125
|
versionPatterns?: VersionPatterns;
|
|
115
126
|
workTypes?: Record<string, WorkTypeConfig>;
|
|
127
|
+
breakingPolicies?: Record<string, 'forbidden' | 'optional' | 'required'>;
|
|
116
128
|
formatCommand?: string;
|
|
117
129
|
cliffConfigPath?: string;
|
|
118
130
|
scopeAliases?: Record<string, string>;
|
|
@@ -152,6 +164,7 @@ export interface WorkspaceConfig {
|
|
|
152
164
|
name: string;
|
|
153
165
|
tagPrefix: string;
|
|
154
166
|
workspacePath: string;
|
|
167
|
+
isPublishable: boolean;
|
|
155
168
|
packageFiles: string[];
|
|
156
169
|
changelogPaths: string[];
|
|
157
170
|
paths: string[];
|
|
@@ -161,6 +174,7 @@ export interface MonorepoReleaseConfig {
|
|
|
161
174
|
workspaces: WorkspaceConfig[];
|
|
162
175
|
workTypes?: Record<string, WorkTypeConfig>;
|
|
163
176
|
versionPatterns?: VersionPatterns;
|
|
177
|
+
breakingPolicies?: Record<string, 'forbidden' | 'optional' | 'required'>;
|
|
164
178
|
formatCommand?: string;
|
|
165
179
|
cliffConfigPath?: string;
|
|
166
180
|
scopeAliases?: Record<string, string>;
|
|
@@ -174,6 +188,7 @@ export interface ReleaseConfig {
|
|
|
174
188
|
changelogPaths: string[];
|
|
175
189
|
workTypes?: Record<string, WorkTypeConfig>;
|
|
176
190
|
versionPatterns?: VersionPatterns;
|
|
191
|
+
breakingPolicies?: Record<string, 'forbidden' | 'optional' | 'required'>;
|
|
177
192
|
formatCommand?: string;
|
|
178
193
|
cliffConfigPath?: string;
|
|
179
194
|
scopeAliases?: Record<string, string>;
|