@williamthorsen/release-kit 5.1.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 +105 -65
- package/README.md +160 -57
- 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 +10 -6
- 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
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const UPSTREAM_WORK_TYPES_URL = "https://raw.githubusercontent.com/williamthorsen/codeassembly/main/packages/agents/content/skills/_data/work-types.json";
|
|
2
|
+
export interface DriftCheckResult {
|
|
3
|
+
exitCode: 0 | 1 | 2 | 3;
|
|
4
|
+
message: string;
|
|
5
|
+
}
|
|
6
|
+
export interface CheckWorkTypesDriftDependencies {
|
|
7
|
+
localPath?: string;
|
|
8
|
+
fetch?: typeof globalThis.fetch;
|
|
9
|
+
upstreamUrl?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function checkWorkTypesDrift(dependencies?: CheckWorkTypesDriftDependencies): Promise<DriftCheckResult>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { errorMessage, hasExpectedTopLevelShape } from "./workTypesUtils.js";
|
|
5
|
+
const UPSTREAM_WORK_TYPES_URL = "https://raw.githubusercontent.com/williamthorsen/codeassembly/main/packages/agents/content/skills/_data/work-types.json";
|
|
6
|
+
function resolveDefaultLocalPath() {
|
|
7
|
+
const moduleDir = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
return resolve(moduleDir, "work-types.json");
|
|
9
|
+
}
|
|
10
|
+
async function checkWorkTypesDrift(dependencies = {}) {
|
|
11
|
+
const localPath = dependencies.localPath ?? resolveDefaultLocalPath();
|
|
12
|
+
const fetcher = dependencies.fetch ?? globalThis.fetch;
|
|
13
|
+
const url = dependencies.upstreamUrl ?? UPSTREAM_WORK_TYPES_URL;
|
|
14
|
+
const localContent = readFileSync(localPath, "utf8");
|
|
15
|
+
let localJson;
|
|
16
|
+
try {
|
|
17
|
+
localJson = JSON.parse(localContent);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
return {
|
|
20
|
+
exitCode: 3,
|
|
21
|
+
message: `Local work-types.json is not valid JSON: ${errorMessage(error)}`
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
let response;
|
|
25
|
+
try {
|
|
26
|
+
response = await fetcher(url);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
return {
|
|
29
|
+
exitCode: 2,
|
|
30
|
+
message: `Network error fetching upstream work-types.json: ${errorMessage(error)}`
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (response.status === 404) {
|
|
34
|
+
return {
|
|
35
|
+
exitCode: 0,
|
|
36
|
+
message: `Upstream work-types.json not yet published at ${url}; skipping drift check.`
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (!response.ok) {
|
|
40
|
+
return {
|
|
41
|
+
exitCode: 2,
|
|
42
|
+
message: `Failed to fetch upstream work-types.json: HTTP ${response.status} ${response.statusText}`
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
const upstreamText = await response.text();
|
|
46
|
+
let upstreamJson;
|
|
47
|
+
try {
|
|
48
|
+
upstreamJson = JSON.parse(upstreamText);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return {
|
|
51
|
+
exitCode: 3,
|
|
52
|
+
message: `Upstream work-types.json is not valid JSON: ${errorMessage(error)}`
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (!hasExpectedTopLevelShape(upstreamJson)) {
|
|
56
|
+
return {
|
|
57
|
+
exitCode: 3,
|
|
58
|
+
message: "Upstream work-types.json does not match the expected schema shape (missing `tiers` or `types`)."
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const normalisedLocal = stripLocalOnlyFields(localJson);
|
|
62
|
+
if (deepEqual(normalisedLocal, upstreamJson)) {
|
|
63
|
+
return {
|
|
64
|
+
exitCode: 0,
|
|
65
|
+
message: "Local work-types.json matches upstream."
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
exitCode: 1,
|
|
70
|
+
message: `Drift detected. Local and upstream work-types.json differ. Run \`nmr work-types:sync\` to update from upstream.
|
|
71
|
+
Local: ${localPath}
|
|
72
|
+
Upstream: ${url}`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function stripLocalOnlyFields(value) {
|
|
76
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
const record = { ...value };
|
|
80
|
+
delete record.$schema;
|
|
81
|
+
return record;
|
|
82
|
+
}
|
|
83
|
+
function deepEqual(a, b) {
|
|
84
|
+
if (a === b) return true;
|
|
85
|
+
if (typeof a !== typeof b) return false;
|
|
86
|
+
if (a === null || b === null) return a === b;
|
|
87
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
88
|
+
if (a.length !== b.length) return false;
|
|
89
|
+
for (let i = 0; i < a.length; i++) {
|
|
90
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
if (typeof a === "object" && typeof b === "object" && !Array.isArray(a) && !Array.isArray(b)) {
|
|
95
|
+
const aRecord = { ...a };
|
|
96
|
+
const bRecord = { ...b };
|
|
97
|
+
const aKeys = Object.keys(aRecord);
|
|
98
|
+
const bKeys = Object.keys(bRecord);
|
|
99
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
100
|
+
for (const key of aKeys) {
|
|
101
|
+
if (!deepEqual(aRecord[key], bRecord[key])) return false;
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
UPSTREAM_WORK_TYPES_URL,
|
|
109
|
+
checkWorkTypesDrift
|
|
110
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function createPolicyViolationCollector() {
|
|
2
|
+
const violations = [];
|
|
3
|
+
const onPolicyViolation = (commit, type, surface) => {
|
|
4
|
+
violations.push({
|
|
5
|
+
commitHash: commit.hash,
|
|
6
|
+
commitSubject: commit.message.split("\n", 1)[0] ?? "",
|
|
7
|
+
type,
|
|
8
|
+
surface
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
return { violations, onPolicyViolation };
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
createPolicyViolationCollector
|
|
15
|
+
};
|
|
@@ -4,10 +4,20 @@ export interface CreateGithubReleaseOptions {
|
|
|
4
4
|
dryRun: boolean;
|
|
5
5
|
sectionOrder?: string[];
|
|
6
6
|
}
|
|
7
|
-
export
|
|
7
|
+
export type CreateReleaseSkipReason = 'no-entry' | 'no-audience-content' | 'empty-body';
|
|
8
|
+
export type CreateReleaseResult = {
|
|
9
|
+
status: 'created';
|
|
10
|
+
} | {
|
|
11
|
+
status: 'skipped';
|
|
12
|
+
reason: CreateReleaseSkipReason;
|
|
13
|
+
};
|
|
14
|
+
export declare function createGithubRelease(options: CreateGithubReleaseOptions): CreateReleaseResult;
|
|
8
15
|
export interface CreateGithubReleasesOutcome {
|
|
9
16
|
created: string[];
|
|
10
|
-
skipped:
|
|
17
|
+
skipped: Array<{
|
|
18
|
+
tag: string;
|
|
19
|
+
reason: CreateReleaseSkipReason;
|
|
20
|
+
}>;
|
|
11
21
|
}
|
|
12
22
|
export declare function createGithubReleases(tags: Array<{
|
|
13
23
|
tag: string;
|
|
@@ -7,21 +7,21 @@ function createGithubRelease(options) {
|
|
|
7
7
|
const { tag, changelogJsonPath, dryRun, sectionOrder } = options;
|
|
8
8
|
if (!existsSync(changelogJsonPath)) {
|
|
9
9
|
console.warn(`Warning: ${changelogJsonPath} not found; skipping GitHub Release creation`);
|
|
10
|
-
return
|
|
10
|
+
return { status: "skipped", reason: "no-entry" };
|
|
11
11
|
}
|
|
12
12
|
const version = extractVersion(tag);
|
|
13
13
|
const entries = readChangelogEntries(changelogJsonPath);
|
|
14
14
|
if (entries === void 0) {
|
|
15
15
|
console.warn(`Warning: could not parse ${changelogJsonPath}; skipping GitHub Release creation`);
|
|
16
|
-
return
|
|
16
|
+
return { status: "skipped", reason: "no-entry" };
|
|
17
17
|
}
|
|
18
18
|
const entry = entries.find((e) => e.version === version);
|
|
19
19
|
if (entry === void 0) {
|
|
20
20
|
console.warn(`Warning: no changelog entry for version ${version}; skipping GitHub Release creation`);
|
|
21
|
-
return
|
|
21
|
+
return { status: "skipped", reason: "no-entry" };
|
|
22
22
|
}
|
|
23
23
|
if (!entry.sections.some(matchesAudience("all"))) {
|
|
24
|
-
return
|
|
24
|
+
return { status: "skipped", reason: "no-audience-content" };
|
|
25
25
|
}
|
|
26
26
|
const body = renderReleaseNotesSingle(entry, {
|
|
27
27
|
filter: matchesAudience("all"),
|
|
@@ -29,15 +29,15 @@ function createGithubRelease(options) {
|
|
|
29
29
|
...sectionOrder === void 0 ? {} : { sectionOrder }
|
|
30
30
|
});
|
|
31
31
|
if (body.trim() === "") {
|
|
32
|
-
return
|
|
32
|
+
return { status: "skipped", reason: "empty-body" };
|
|
33
33
|
}
|
|
34
34
|
const args = ["release", "create", tag, "--title", tag, "--notes", body];
|
|
35
35
|
if (dryRun) {
|
|
36
36
|
console.info(`[dry-run] Would run: gh ${args.join(" ")}`);
|
|
37
|
-
return
|
|
37
|
+
return { status: "created" };
|
|
38
38
|
}
|
|
39
39
|
execFileSync("gh", args, { stdio: "inherit" });
|
|
40
|
-
return
|
|
40
|
+
return { status: "created" };
|
|
41
41
|
}
|
|
42
42
|
function createGithubReleases(tags, changelogJsonOutputPath, dryRun, sectionOrder) {
|
|
43
43
|
const created = [];
|
|
@@ -50,7 +50,11 @@ function createGithubReleases(tags, changelogJsonOutputPath, dryRun, sectionOrde
|
|
|
50
50
|
dryRun,
|
|
51
51
|
...sectionOrder === void 0 ? {} : { sectionOrder }
|
|
52
52
|
});
|
|
53
|
-
(result
|
|
53
|
+
if (result.status === "created") {
|
|
54
|
+
created.push(tag);
|
|
55
|
+
} else {
|
|
56
|
+
skipped.push({ tag, reason: result.reason });
|
|
57
|
+
}
|
|
54
58
|
}
|
|
55
59
|
return { created, skipped };
|
|
56
60
|
}
|
|
@@ -26,14 +26,18 @@ async function createGithubReleaseCommand(argv) {
|
|
|
26
26
|
console.error(`Error creating GitHub Releases: ${error instanceof Error ? error.message : String(error)}`);
|
|
27
27
|
process.exit(1);
|
|
28
28
|
}
|
|
29
|
-
if (requestedTags !== void 0
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
if (requestedTags !== void 0) {
|
|
30
|
+
const noEntryTags = outcome.skipped.filter((s) => s.reason === "no-entry").map((s) => s.tag);
|
|
31
|
+
if (noEntryTags.length > 0) {
|
|
32
|
+
console.error(
|
|
33
|
+
`Error: requested tags have no changelog entry: ${noEntryTags.join(", ")}. Verify the tag names match a published changelog version.`
|
|
34
|
+
);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
34
37
|
}
|
|
35
38
|
if (outcome.skipped.length > 0) {
|
|
36
|
-
|
|
39
|
+
const formatted = outcome.skipped.map((s) => `${s.tag} (${s.reason})`).join(", ");
|
|
40
|
+
console.info(`Skipped ${outcome.skipped.length} tag(s) with no releasable content: ${formatted}.`);
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
43
|
export {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PolicyViolationHandler } from './parseCommitMessage.ts';
|
|
1
2
|
import type { Commit, ReleaseType, VersionPatterns, WorkTypeConfig } from './types.ts';
|
|
2
3
|
export interface DecideReleaseArgs {
|
|
3
4
|
commits: readonly Commit[];
|
|
@@ -6,6 +7,8 @@ export interface DecideReleaseArgs {
|
|
|
6
7
|
workTypes: Record<string, WorkTypeConfig>;
|
|
7
8
|
versionPatterns: VersionPatterns;
|
|
8
9
|
scopeAliases: Record<string, string> | undefined;
|
|
10
|
+
breakingPolicies?: Record<string, 'forbidden' | 'optional' | 'required'>;
|
|
11
|
+
onPolicyViolation?: PolicyViolationHandler;
|
|
9
12
|
skipReasons: {
|
|
10
13
|
noCommits: string;
|
|
11
14
|
noBumpWorthy: string;
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import { determineBumpType } from "./determineBumpType.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
parseCommitMessage
|
|
4
|
+
} from "./parseCommitMessage.js";
|
|
3
5
|
function decideRelease(args) {
|
|
4
|
-
const {
|
|
6
|
+
const {
|
|
7
|
+
commits,
|
|
8
|
+
force = false,
|
|
9
|
+
bumpOverride,
|
|
10
|
+
workTypes,
|
|
11
|
+
versionPatterns,
|
|
12
|
+
scopeAliases,
|
|
13
|
+
breakingPolicies,
|
|
14
|
+
onPolicyViolation,
|
|
15
|
+
skipReasons
|
|
16
|
+
} = args;
|
|
17
|
+
const parseOptions = {
|
|
18
|
+
...breakingPolicies !== void 0 && { breakingPolicies },
|
|
19
|
+
...onPolicyViolation !== void 0 && { onPolicyViolation }
|
|
20
|
+
};
|
|
5
21
|
const parsedCommits = [];
|
|
6
22
|
const unparseable = [];
|
|
7
23
|
for (const commit of commits) {
|
|
8
|
-
const parsed = parseCommitMessage(commit.message, commit.hash, workTypes, scopeAliases);
|
|
24
|
+
const parsed = parseCommitMessage(commit.message, commit.hash, workTypes, scopeAliases, parseOptions);
|
|
9
25
|
if (parsed === void 0) {
|
|
10
26
|
unparseable.push(commit);
|
|
11
27
|
} else {
|
package/dist/esm/defaults.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { ChangelogJsonConfig, ReleaseNotesConfig, VersionPatterns, WorkTypeConfig } from './types.ts';
|
|
2
|
+
export type { WorkTypesData } from './workTypesData.ts';
|
|
3
|
+
export { WORK_TYPES_DATA } from './workTypesData.ts';
|
|
4
|
+
export declare function composeHeader(entry: {
|
|
5
|
+
emoji: string;
|
|
6
|
+
label: string;
|
|
7
|
+
}): string;
|
|
2
8
|
export declare const DEFAULT_WORK_TYPES: Record<string, WorkTypeConfig>;
|
|
9
|
+
export declare const DEFAULT_BREAKING_POLICIES: Record<string, 'forbidden' | 'optional' | 'required'>;
|
|
3
10
|
export declare const DEFAULT_VERSION_PATTERNS: VersionPatterns;
|
|
4
11
|
export declare const DEFAULT_CHANGELOG_JSON_CONFIG: ChangelogJsonConfig;
|
|
5
12
|
export declare const DEFAULT_RELEASE_NOTES_CONFIG: ReleaseNotesConfig;
|
package/dist/esm/defaults.js
CHANGED
|
@@ -1,21 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
1
|
+
import { WORK_TYPES_DATA } from "./workTypesData.js";
|
|
2
|
+
import { WORK_TYPES_DATA as WORK_TYPES_DATA2 } from "./workTypesData.js";
|
|
3
|
+
function composeHeader(entry) {
|
|
4
|
+
return `${entry.emoji} ${entry.label}`;
|
|
5
|
+
}
|
|
6
|
+
const DEV_ONLY_TIERS = /* @__PURE__ */ new Set(["Internal", "Process"]);
|
|
7
|
+
function deriveDefaultWorkTypes() {
|
|
8
|
+
const result = {};
|
|
9
|
+
for (const entry of WORK_TYPES_DATA.types) {
|
|
10
|
+
const config = {
|
|
11
|
+
header: composeHeader(entry)
|
|
12
|
+
};
|
|
13
|
+
if (entry.aliases.length > 0) {
|
|
14
|
+
config.aliases = [...entry.aliases];
|
|
15
|
+
}
|
|
16
|
+
result[entry.key] = config;
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
function deriveDevOnlySections() {
|
|
21
|
+
const sections = [];
|
|
22
|
+
for (const entry of WORK_TYPES_DATA.types) {
|
|
23
|
+
if (!DEV_ONLY_TIERS.has(entry.tier)) {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (entry.excludedFromChangelog === true) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
sections.push(composeHeader(entry));
|
|
30
|
+
}
|
|
31
|
+
return sections;
|
|
32
|
+
}
|
|
33
|
+
const DEFAULT_WORK_TYPES = deriveDefaultWorkTypes();
|
|
34
|
+
const DEFAULT_BREAKING_POLICIES = Object.fromEntries(
|
|
35
|
+
WORK_TYPES_DATA.types.map((entry) => [entry.key, entry.breakingPolicy])
|
|
36
|
+
);
|
|
19
37
|
const DEFAULT_VERSION_PATTERNS = {
|
|
20
38
|
major: ["!"],
|
|
21
39
|
minor: ["feat"]
|
|
@@ -23,16 +41,19 @@ const DEFAULT_VERSION_PATTERNS = {
|
|
|
23
41
|
const DEFAULT_CHANGELOG_JSON_CONFIG = {
|
|
24
42
|
enabled: true,
|
|
25
43
|
outputPath: ".meta/changelog.json",
|
|
26
|
-
devOnlySections:
|
|
44
|
+
devOnlySections: deriveDevOnlySections()
|
|
27
45
|
};
|
|
28
46
|
const DEFAULT_RELEASE_NOTES_CONFIG = {
|
|
29
47
|
shouldInjectIntoReadme: false
|
|
30
48
|
};
|
|
31
49
|
const DEFAULT_PROJECT_TAG_PREFIX = "v";
|
|
32
50
|
export {
|
|
51
|
+
DEFAULT_BREAKING_POLICIES,
|
|
33
52
|
DEFAULT_CHANGELOG_JSON_CONFIG,
|
|
34
53
|
DEFAULT_PROJECT_TAG_PREFIX,
|
|
35
54
|
DEFAULT_RELEASE_NOTES_CONFIG,
|
|
36
55
|
DEFAULT_VERSION_PATTERNS,
|
|
37
|
-
DEFAULT_WORK_TYPES
|
|
56
|
+
DEFAULT_WORK_TYPES,
|
|
57
|
+
WORK_TYPES_DATA2 as WORK_TYPES_DATA,
|
|
58
|
+
composeHeader
|
|
38
59
|
};
|
|
@@ -16,11 +16,14 @@ function deriveWorkspaceConfig(workspacePath) {
|
|
|
16
16
|
throw new Error(`${packageJsonPath} is missing a 'name' field (required for tag derivation).`);
|
|
17
17
|
}
|
|
18
18
|
const unscopedName = stripNpmScope(name);
|
|
19
|
+
const privateField = isRecord(parsed) ? parsed.private : void 0;
|
|
20
|
+
const isPublishable = privateField === void 0 || privateField === false;
|
|
19
21
|
return {
|
|
20
22
|
dir,
|
|
21
23
|
name,
|
|
22
24
|
tagPrefix: `${unscopedName}-v`,
|
|
23
25
|
workspacePath,
|
|
26
|
+
isPublishable,
|
|
24
27
|
packageFiles: [packageJsonPath],
|
|
25
28
|
changelogPaths: [workspacePath],
|
|
26
29
|
paths: [`${workspacePath}/**`]
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
import { type PolicyViolationHandler } from './parseCommitMessage.ts';
|
|
1
2
|
import type { Commit, ReleaseType, VersionPatterns, WorkTypeConfig } from './types.ts';
|
|
2
3
|
export interface BumpDetermination {
|
|
3
4
|
releaseType: ReleaseType | undefined;
|
|
4
5
|
parsedCommitCount: number;
|
|
5
6
|
unparseableCommits: Commit[] | undefined;
|
|
6
7
|
}
|
|
7
|
-
export
|
|
8
|
+
export interface DetermineBumpOptions {
|
|
9
|
+
breakingPolicies?: Record<string, 'forbidden' | 'optional' | 'required'>;
|
|
10
|
+
onPolicyViolation?: PolicyViolationHandler;
|
|
11
|
+
}
|
|
12
|
+
export declare function determineBumpFromCommits(commits: Commit[], workTypes: Record<string, WorkTypeConfig>, versionPatterns: VersionPatterns, scopeAliases: Record<string, string> | undefined, options?: DetermineBumpOptions): BumpDetermination;
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { determineBumpType } from "./determineBumpType.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
2
|
+
import {
|
|
3
|
+
parseCommitMessage
|
|
4
|
+
} from "./parseCommitMessage.js";
|
|
5
|
+
function determineBumpFromCommits(commits, workTypes, versionPatterns, scopeAliases, options) {
|
|
6
|
+
const parseOptions = {
|
|
7
|
+
...options?.breakingPolicies !== void 0 && { breakingPolicies: options.breakingPolicies },
|
|
8
|
+
...options?.onPolicyViolation !== void 0 && { onPolicyViolation: options.onPolicyViolation }
|
|
9
|
+
};
|
|
4
10
|
const parsedCommits = [];
|
|
5
11
|
const unparseable = [];
|
|
6
12
|
for (const commit of commits) {
|
|
7
|
-
const parsed = parseCommitMessage(commit.message, commit.hash, workTypes, scopeAliases);
|
|
13
|
+
const parsed = parseCommitMessage(commit.message, commit.hash, workTypes, scopeAliases, parseOptions);
|
|
8
14
|
if (parsed === void 0) {
|
|
9
15
|
unparseable.push(commit);
|
|
10
16
|
} else {
|
|
@@ -1,8 +1,5 @@
|
|
|
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
1
|
import { resolveCliffConfigPath } from "./resolveCliffConfigPath.js";
|
|
2
|
+
import { runGitCliff } from "./runGitCliff.js";
|
|
6
3
|
function buildTagPattern(tagPrefixes) {
|
|
7
4
|
if (tagPrefixes.length === 0) {
|
|
8
5
|
throw new Error("buildTagPattern: tagPrefixes must contain at least one entry");
|
|
@@ -18,38 +15,26 @@ function escapeRegex(value) {
|
|
|
18
15
|
return value.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
19
16
|
}
|
|
20
17
|
function generateChangelog(config, changelogPath, tag, dryRun, options) {
|
|
21
|
-
const resolvedConfigPath = resolveCliffConfigPath(config.cliffConfigPath, import.meta.url);
|
|
22
|
-
let cliffConfigPath = resolvedConfigPath;
|
|
23
|
-
let tempDir;
|
|
24
|
-
if (resolvedConfigPath.endsWith(".template")) {
|
|
25
|
-
tempDir = mkdtempSync(join(tmpdir(), "cliff-"));
|
|
26
|
-
cliffConfigPath = join(tempDir, "cliff.toml");
|
|
27
|
-
copyFileSync(resolvedConfigPath, cliffConfigPath);
|
|
28
|
-
}
|
|
29
18
|
const outputFile = `${changelogPath}/CHANGELOG.md`;
|
|
30
|
-
|
|
19
|
+
if (dryRun) {
|
|
20
|
+
return [outputFile];
|
|
21
|
+
}
|
|
22
|
+
const resolvedConfigPath = resolveCliffConfigPath(config.cliffConfigPath, import.meta.url);
|
|
23
|
+
const cliffArgs = ["--output", outputFile, "--tag", tag];
|
|
31
24
|
if (options?.tagPattern !== void 0) {
|
|
32
|
-
|
|
25
|
+
cliffArgs.push("--tag-pattern", options.tagPattern);
|
|
33
26
|
}
|
|
34
27
|
for (const includePath of options?.includePaths ?? []) {
|
|
35
|
-
|
|
28
|
+
cliffArgs.push("--include-path", includePath);
|
|
36
29
|
}
|
|
37
30
|
try {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
`Failed to generate changelog for ${outputFile}: ${error instanceof Error ? error.message : String(error)}`
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return [outputFile];
|
|
48
|
-
} finally {
|
|
49
|
-
if (tempDir !== void 0) {
|
|
50
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
51
|
-
}
|
|
31
|
+
runGitCliff(resolvedConfigPath, cliffArgs, "inherit");
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Failed to generate changelog for ${outputFile}: ${error instanceof Error ? error.message : String(error)}`
|
|
35
|
+
);
|
|
52
36
|
}
|
|
37
|
+
return [outputFile];
|
|
53
38
|
}
|
|
54
39
|
function generateChangelogs(config, tag, dryRun) {
|
|
55
40
|
const tagPattern = buildTagPattern([config.tagPrefix]);
|
package/dist/esm/loadConfig.js
CHANGED
|
@@ -92,19 +92,22 @@ function mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage) {
|
|
|
92
92
|
if (project !== void 0) {
|
|
93
93
|
result.project = project;
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
applyOptionalPassthroughFields(result, userConfig);
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
function applyOptionalPassthroughFields(result, userConfig) {
|
|
99
|
+
if (userConfig?.formatCommand !== void 0) {
|
|
100
|
+
result.formatCommand = userConfig.formatCommand;
|
|
98
101
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
result.cliffConfigPath = cliffConfigPath;
|
|
102
|
+
if (userConfig?.cliffConfigPath !== void 0) {
|
|
103
|
+
result.cliffConfigPath = userConfig.cliffConfigPath;
|
|
102
104
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
if (userConfig?.scopeAliases !== void 0) {
|
|
106
|
+
result.scopeAliases = userConfig.scopeAliases;
|
|
107
|
+
}
|
|
108
|
+
if (userConfig?.breakingPolicies !== void 0) {
|
|
109
|
+
result.breakingPolicies = userConfig.breakingPolicies;
|
|
106
110
|
}
|
|
107
|
-
return result;
|
|
108
111
|
}
|
|
109
112
|
function mergeSinglePackageConfig(userConfig) {
|
|
110
113
|
if (userConfig?.project !== void 0) {
|
|
@@ -123,18 +126,7 @@ function mergeSinglePackageConfig(userConfig) {
|
|
|
123
126
|
changelogJson,
|
|
124
127
|
releaseNotes
|
|
125
128
|
};
|
|
126
|
-
|
|
127
|
-
if (formatCommand !== void 0) {
|
|
128
|
-
result.formatCommand = formatCommand;
|
|
129
|
-
}
|
|
130
|
-
const cliffConfigPath = userConfig?.cliffConfigPath;
|
|
131
|
-
if (cliffConfigPath !== void 0) {
|
|
132
|
-
result.cliffConfigPath = cliffConfigPath;
|
|
133
|
-
}
|
|
134
|
-
const scopeAliases = userConfig?.scopeAliases;
|
|
135
|
-
if (scopeAliases !== void 0) {
|
|
136
|
-
result.scopeAliases = scopeAliases;
|
|
137
|
-
}
|
|
129
|
+
applyOptionalPassthroughFields(result, userConfig);
|
|
138
130
|
return result;
|
|
139
131
|
}
|
|
140
132
|
function resolveWorkTypes(userWorkTypes) {
|
|
@@ -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)) {
|