@williamthorsen/release-kit 11.0.0 → 13.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/.meta/changelog.json +274 -2
- package/.readyup/kits/default.js +18 -33
- package/.readyup/kits/npm-auto-publish.js +24 -23
- package/.readyup/manifest.json +16 -11
- package/CHANGELOG.md +98 -0
- package/README.md +57 -69
- package/bin/release-kit.js +1 -1
- package/dist/esm/assertCleanWorkingTree.d.ts +1 -5
- package/dist/esm/assertTaggedBaseline.d.ts +29 -0
- package/dist/esm/assertTaggedBaseline.js +41 -0
- package/dist/esm/attachChangelogDiagnostics.d.ts +4 -0
- package/dist/esm/attachChangelogDiagnostics.js +14 -0
- package/dist/esm/bin/release-kit.js +88 -24
- package/dist/esm/buildChangelogEntries.d.ts +69 -17
- package/dist/esm/buildChangelogEntries.js +222 -120
- package/dist/esm/buildDependencyGraph.d.ts +6 -6
- package/dist/esm/buildEmptyReleaseEntry.d.ts +1 -8
- package/dist/esm/buildReleaseSummary.d.ts +2 -8
- package/dist/esm/buildReleaseSummary.js +2 -2
- package/dist/esm/buildSyntheticChangelogEntry.d.ts +3 -5
- package/dist/esm/buildSyntheticChangelogEntry.js +2 -2
- package/dist/esm/bumpVersion.d.ts +1 -8
- package/dist/esm/changelogJsonFile.d.ts +5 -7
- package/dist/esm/changelogJsonFile.js +2 -17
- package/dist/esm/changelogJsonUtils.d.ts +7 -10
- package/dist/esm/changelogOverrides.d.ts +79 -100
- package/dist/esm/changelogOverrides.js +138 -58
- package/dist/esm/classifyChangelogCommit.d.ts +36 -0
- package/dist/esm/classifyChangelogCommit.js +34 -0
- package/dist/esm/commitCommand.d.ts +2 -4
- package/dist/esm/compareVersions.d.ts +10 -4
- package/dist/esm/compareVersions.js +16 -0
- package/dist/esm/configFlagSchema.d.ts +1 -1
- package/dist/esm/createGithubRelease.d.ts +7 -19
- package/dist/esm/createGithubReleaseCommand.d.ts +5 -5
- package/dist/esm/createGithubReleaseCommand.js +9 -9
- package/dist/esm/createTags.d.ts +2 -3
- package/dist/esm/decideRelease.d.ts +11 -41
- package/dist/esm/decideRelease.js +5 -27
- package/dist/esm/defaults.d.ts +5 -24
- package/dist/esm/defaults.js +7 -5
- package/dist/esm/defineConfig.d.ts +1 -1
- package/dist/esm/deleteFileIfExists.d.ts +1 -1
- package/dist/esm/deriveReleaseNotesConfig.d.ts +20 -0
- package/dist/esm/deriveReleaseNotesConfig.js +21 -0
- package/dist/esm/deriveWorkspaceConfig.d.ts +3 -5
- package/dist/esm/detectPackageManager.d.ts +2 -4
- package/dist/esm/detectUndeclaredTagPrefixes.d.ts +4 -6
- package/dist/esm/detectUndeclaredTagPrefixes.js +1 -1
- package/dist/esm/determineBumpType.d.ts +11 -11
- package/dist/esm/determineBumpType.js +6 -7
- package/dist/esm/discoverWorkspaces.d.ts +14 -15
- package/dist/esm/discoverWorkspaces.js +12 -12
- package/dist/esm/enterRepoRoot.d.ts +12 -0
- package/dist/esm/enterRepoRoot.js +16 -0
- package/dist/esm/enumerateReleaseWindows.d.ts +50 -0
- package/dist/esm/enumerateReleaseWindows.js +124 -0
- package/dist/esm/extractMigration.d.ts +6 -8
- package/dist/esm/findUnroutedEntryScopes.d.ts +16 -0
- package/dist/esm/findUnroutedEntryScopes.js +57 -0
- package/dist/esm/formatExcludedSkip.d.ts +3 -0
- package/dist/esm/formatExcludedSkip.js +3 -0
- package/dist/esm/formatPrivateSkip.d.ts +1 -6
- package/dist/esm/generateChangelogs.d.ts +13 -26
- package/dist/esm/generateChangelogs.js +0 -14
- package/dist/esm/glyphs.d.ts +2 -2
- package/dist/esm/help/prepareHelp.d.ts +3 -4
- package/dist/esm/help/prepareHelp.js +7 -8
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/init/checks.d.ts +3 -3
- package/dist/esm/init/checks.js +4 -1
- package/dist/esm/init/detectRepoType.d.ts +2 -3
- package/dist/esm/init/hasProvenance.d.ts +2 -0
- package/dist/esm/init/hasProvenance.js +3 -0
- package/dist/esm/init/initCommand.d.ts +3 -6
- package/dist/esm/init/initCommand.js +11 -11
- package/dist/esm/init/parseJsonRecord.d.ts +1 -4
- package/dist/esm/init/scaffold.d.ts +1 -3
- package/dist/esm/init/scaffold.js +2 -30
- package/dist/esm/init/templates.d.ts +9 -11
- package/dist/esm/init/templates.js +12 -11
- package/dist/esm/init/updateTemplatesCommand.d.ts +13 -0
- package/dist/esm/init/updateTemplatesCommand.js +64 -0
- package/dist/esm/injectReleaseNotesIntoReadme.d.ts +12 -17
- package/dist/esm/injectSection.d.ts +3 -3
- package/dist/esm/loadConfig.d.ts +16 -14
- package/dist/esm/loadConfig.js +2 -5
- package/dist/esm/loadValidatedConfig.d.ts +16 -19
- package/dist/esm/loadValidatedConfig.js +6 -1
- package/dist/esm/packsPath.d.ts +9 -9
- package/dist/esm/parseChangeRecordBlock.d.ts +37 -0
- package/dist/esm/parseChangeRecordBlock.js +134 -0
- package/dist/esm/parseCommitMessage.d.ts +42 -32
- package/dist/esm/parseCommitMessage.js +33 -41
- package/dist/esm/parseRequestedTags.d.ts +3 -3
- package/dist/esm/planReleaseNotesPreviews.d.ts +6 -6
- package/dist/esm/planVersionBump.d.ts +5 -3
- package/dist/esm/planVersionBump.js +3 -0
- package/dist/esm/prepareCommand.d.ts +4 -7
- package/dist/esm/prepareCommand.js +17 -18
- package/dist/esm/previewTagPrefixes.d.ts +8 -10
- package/dist/esm/propagateBumps.d.ts +6 -7
- package/dist/esm/publish.d.ts +1 -1
- package/dist/esm/publishCommand.d.ts +4 -3
- package/dist/esm/publishCommand.js +8 -8
- package/dist/esm/pushCommand.d.ts +4 -3
- package/dist/esm/pushCommand.js +8 -2
- package/dist/esm/pushRelease.d.ts +2 -2
- package/dist/esm/readChangelogSections.d.ts +33 -0
- package/dist/esm/readChangelogSections.js +80 -0
- package/dist/esm/releaseFiles.d.ts +5 -7
- package/dist/esm/releasePlan.d.ts +11 -13
- package/dist/esm/releasePrepare.d.ts +25 -25
- package/dist/esm/releasePrepare.js +80 -80
- package/dist/esm/releasePrepareMono.d.ts +9 -7
- package/dist/esm/releasePrepareMono.js +114 -99
- package/dist/esm/releasePrepareProject.d.ts +19 -29
- package/dist/esm/releasePrepareProject.js +46 -45
- package/dist/esm/renderChangelogMarkdown.d.ts +10 -4
- package/dist/esm/renderChangelogMarkdown.js +27 -2
- package/dist/esm/renderReleaseNotes.d.ts +3 -11
- package/dist/esm/renderReleaseNotes.js +2 -6
- package/dist/esm/reportPrepare.d.ts +4 -5
- package/dist/esm/reportPrepare.js +67 -42
- package/dist/esm/resolveCommandTags.d.ts +15 -11
- package/dist/esm/resolveCommandTags.js +35 -5
- package/dist/esm/resolveConfigFlag.d.ts +7 -0
- package/dist/esm/resolveConfigFlag.js +4 -0
- package/dist/esm/resolveReleaseTags.d.ts +6 -8
- package/dist/esm/resolveReleaseTags.js +1 -1
- package/dist/esm/resolveStylesOrExit.d.ts +2 -2
- package/dist/esm/showTagPrefixesCommand.d.ts +7 -10
- package/dist/esm/showTagPrefixesCommand.js +4 -4
- package/dist/esm/stripEmojiPrefix.d.ts +1 -0
- package/dist/esm/stripScope.d.ts +2 -2
- package/dist/esm/stripScope.js +3 -3
- package/dist/esm/sync-labels/generateCommand.d.ts +11 -10
- package/dist/esm/sync-labels/generateCommand.js +11 -8
- package/dist/esm/sync-labels/initCommand.d.ts +6 -7
- package/dist/esm/sync-labels/initCommand.js +2 -2
- package/dist/esm/sync-labels/presets.d.ts +5 -4
- package/dist/esm/sync-labels/presets.js +9 -9
- package/dist/esm/sync-labels/resolveLabels.d.ts +7 -7
- package/dist/esm/sync-labels/resolveLabels.js +1 -1
- package/dist/esm/sync-labels/retiredConfig.d.ts +4 -5
- package/dist/esm/sync-labels/syncCommand.d.ts +1 -4
- package/dist/esm/sync-labels/templates.d.ts +3 -3
- package/dist/esm/sync-labels/templates.js +3 -3
- package/dist/esm/sync-labels/typeLabels.d.ts +14 -0
- package/dist/esm/sync-labels/typeLabels.js +38 -0
- package/dist/esm/sync-labels/types.d.ts +1 -1
- package/dist/esm/tagCommand.d.ts +1 -1
- package/dist/esm/typeGuards.d.ts +2 -2
- package/dist/esm/types.d.ts +185 -116
- package/dist/esm/types.js +2 -2
- package/dist/esm/validateConfig.d.ts +2 -11
- package/dist/esm/validateConfig.js +5 -1
- package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -12
- package/dist/esm/validateOverridesCommand.d.ts +19 -21
- package/dist/esm/validateOverridesCommand.js +16 -23
- package/docs/api.md +6 -6
- package/docs/changelogs.md +90 -28
- package/docs/configuration.md +30 -29
- package/docs/editorial-overrides.md +90 -31
- package/docs/labels.md +21 -19
- package/docs/project-releases.md +14 -14
- package/docs/publishing.md +5 -5
- package/docs/readiness-checks.md +3 -3
- package/docs/releasing.md +54 -24
- package/docs/work-types.md +46 -58
- package/package.json +7 -7
- package/presets/labels/common.yaml +1 -41
- package/schemas/changelog-overrides.json +39 -0
- package/cliff.toml.template +0 -65
- package/dist/esm/collectPolicyViolations.d.ts +0 -15
- package/dist/esm/collectPolicyViolations.js +0 -12
- package/dist/esm/determineBumpFromCommits.d.ts +0 -17
- package/dist/esm/determineBumpFromCommits.js +0 -28
- package/dist/esm/getCommitsSinceTarget.d.ts +0 -21
- package/dist/esm/getCommitsSinceTarget.js +0 -66
- package/dist/esm/resolveCliffConfigPath.d.ts +0 -11
- package/dist/esm/resolveCliffConfigPath.js +0 -20
- package/dist/esm/resolveReleaseNotesConfig.d.ts +0 -26
- package/dist/esm/resolveReleaseNotesConfig.js +0 -29
- package/dist/esm/runGitCliff.d.ts +0 -38
- package/dist/esm/runGitCliff.js +0 -25
- package/dist/esm/workTypesData.d.ts +0 -50
- package/dist/esm/workTypesData.js +0 -145
- package/schemas/label-map.json +0 -24
|
@@ -1,161 +1,263 @@
|
|
|
1
1
|
import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
|
|
2
|
+
import { buildEmptyReleaseEntry } from "./buildEmptyReleaseEntry.js";
|
|
2
3
|
import { extractVersion } from "./changelogJsonUtils.js";
|
|
3
|
-
import {
|
|
4
|
+
import { classifyChangelogCommit, isNonChangeSubject, isReleaseSubject } from "./classifyChangelogCommit.js";
|
|
5
|
+
import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
6
|
+
import { determineBumpType } from "./determineBumpType.js";
|
|
7
|
+
import { enumerateReleaseWindows } from "./enumerateReleaseWindows.js";
|
|
4
8
|
import { extractMigration } from "./extractMigration.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { runGitCliff } from "./runGitCliff.js";
|
|
9
|
+
import { parseChangeRecordBlock, stripChangeRecordBlocks } from "./parseChangeRecordBlock.js";
|
|
10
|
+
import { evaluateBreakingPolicy, parseSubject, resolveType } from "./parseCommitMessage.js";
|
|
8
11
|
import { stripEmojiPrefix } from "./stripEmojiPrefix.js";
|
|
9
|
-
|
|
10
|
-
const HTML_COMMENT_PREFIX_PATTERN = /^<!--[^>]*-->/;
|
|
12
|
+
const UNRELEASED_TAG = 'unreleased';
|
|
11
13
|
const CANONICAL_SECTION_ORDER = new Map(Object.values(DEFAULT_WORK_TYPES).map((config, index) => [stripGroupDecorations(config.header), index]));
|
|
12
14
|
function canonicalSectionPriority(title) {
|
|
13
15
|
const index = CANONICAL_SECTION_ORDER.get(stripGroupDecorations(title));
|
|
14
16
|
return index ?? Infinity;
|
|
15
17
|
}
|
|
16
18
|
export function stripGroupDecorations(group) {
|
|
17
|
-
return stripEmojiPrefix(group
|
|
19
|
+
return stripEmojiPrefix(group);
|
|
18
20
|
}
|
|
19
21
|
export function buildChangelogEntries(config, tag, options) {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
const includePaths = options?.includePaths ?? [];
|
|
26
|
-
for (const includePath of includePaths) {
|
|
27
|
-
cliffArgs.push('--include-path', includePath);
|
|
28
|
-
}
|
|
22
|
+
const history = readReleaseHistory(config, options);
|
|
23
|
+
return { entries: toChangelogEntries(history, tag), diagnostics: history.unreleased.diagnostics };
|
|
24
|
+
}
|
|
25
|
+
export function readReleaseHistory(config, options) {
|
|
29
26
|
try {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
const windows = enumerateReleaseWindows({
|
|
28
|
+
...(options.paths !== undefined && { paths: options.paths }),
|
|
29
|
+
tagPrefixes: options.tagPrefixes,
|
|
30
|
+
unreleasedTag: UNRELEASED_TAG,
|
|
31
|
+
});
|
|
32
|
+
const context = {
|
|
33
|
+
breakingPolicies: config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES,
|
|
34
|
+
devOnlySections: new Set(config.changelogJson.devOnlySections.map(stripGroupDecorations)),
|
|
35
|
+
...(options.workspaceDir !== undefined && {
|
|
36
|
+
routing: { workspaceDir: options.workspaceDir, scopeAliases: config.scopeAliases ?? {} },
|
|
37
|
+
}),
|
|
38
|
+
versionPatterns: config.versionPatterns ?? DEFAULT_VERSION_PATTERNS,
|
|
39
|
+
workTypes: config.workTypes ?? DEFAULT_WORK_TYPES,
|
|
40
|
+
};
|
|
41
|
+
return transformReleases(windows, context);
|
|
36
42
|
}
|
|
37
43
|
catch (error) {
|
|
38
|
-
throw chainError(`Failed to
|
|
44
|
+
throw chainError(`Failed to read the release history for ${options.tagPrefixes.join(', ')}`, error);
|
|
39
45
|
}
|
|
40
46
|
}
|
|
41
|
-
function
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
|
|
47
|
+
export function toChangelogEntries(history, tag) {
|
|
48
|
+
const { unreleased } = history;
|
|
49
|
+
if (unreleased.sections.length === 0) {
|
|
50
|
+
return [...history.releasedEntries];
|
|
45
51
|
}
|
|
46
|
-
return
|
|
52
|
+
return [
|
|
53
|
+
{ version: extractVersion(tag), date: unreleased.date, sections: unreleased.sections },
|
|
54
|
+
...history.releasedEntries,
|
|
55
|
+
];
|
|
47
56
|
}
|
|
48
|
-
function
|
|
49
|
-
if (
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
const release = {};
|
|
53
|
-
if (typeof value['version'] === 'string') {
|
|
54
|
-
release.version = value['version'];
|
|
55
|
-
}
|
|
56
|
-
if (typeof value['timestamp'] === 'number') {
|
|
57
|
-
release.timestamp = value['timestamp'];
|
|
58
|
-
}
|
|
59
|
-
if (isUnknownArray(value['commits'])) {
|
|
60
|
-
release.commits = value['commits'].map(toCliffContextCommit);
|
|
57
|
+
export function toReleaseEntries(history, tag, today) {
|
|
58
|
+
if (history.unreleased.sections.length === 0) {
|
|
59
|
+
return [buildEmptyReleaseEntry(extractVersion(tag), today), ...history.releasedEntries];
|
|
61
60
|
}
|
|
62
|
-
return
|
|
61
|
+
return toChangelogEntries(history, tag);
|
|
63
62
|
}
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
63
|
+
function transformReleases(windows, context) {
|
|
64
|
+
const [unreleasedWindow, baselineWindow] = windows;
|
|
65
|
+
const diagnostics = {
|
|
66
|
+
malformedBlocks: [],
|
|
67
|
+
policyViolations: [],
|
|
68
|
+
undeclaredEntryTypes: [],
|
|
69
|
+
unroutedEntryScopes: [],
|
|
70
70
|
};
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const devOnlyNormalised = new Set([...devOnlySections].map(stripGroupDecorations));
|
|
82
|
-
for (const release of releases) {
|
|
83
|
-
if (release.version === undefined) {
|
|
84
|
-
continue;
|
|
71
|
+
const signals = [];
|
|
72
|
+
const unparseable = [];
|
|
73
|
+
let parsedCommitCount = 0;
|
|
74
|
+
const sectionMap = new Map();
|
|
75
|
+
const unreleasedCommits = unreleasedWindow?.commits ?? [];
|
|
76
|
+
for (const commit of unreleasedCommits) {
|
|
77
|
+
const reading = readCommit(commit, context, diagnostics);
|
|
78
|
+
for (const derived of reading.items) {
|
|
79
|
+
appendItem(sectionMap, derived.header, derived.item);
|
|
80
|
+
signals.push(derived.signal);
|
|
85
81
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const group = stripCommentPrefix(commit.group ?? 'Other');
|
|
92
|
-
const description = extractDescription(commit.message);
|
|
93
|
-
const body = extractBody(commit.message);
|
|
94
|
-
const breaking = isBreakingUnderPolicy(commit, workTypes, breakingPolicies);
|
|
95
|
-
let items = sectionMap.get(group);
|
|
96
|
-
if (items === undefined) {
|
|
97
|
-
items = [];
|
|
98
|
-
sectionMap.set(group, items);
|
|
99
|
-
}
|
|
100
|
-
const item = { description };
|
|
101
|
-
if (body !== undefined) {
|
|
102
|
-
item.body = body;
|
|
103
|
-
}
|
|
104
|
-
if (breaking) {
|
|
105
|
-
item.breaking = true;
|
|
106
|
-
}
|
|
107
|
-
const migration = extractMigration(body);
|
|
108
|
-
if (migration !== undefined) {
|
|
109
|
-
item.migration = migration;
|
|
110
|
-
}
|
|
111
|
-
if (commit.id !== undefined && commit.id !== '') {
|
|
112
|
-
item.hash = commit.id;
|
|
113
|
-
}
|
|
114
|
-
items.push(item);
|
|
82
|
+
if (reading.items.length > 0) {
|
|
83
|
+
parsedCommitCount += 1;
|
|
84
|
+
}
|
|
85
|
+
else if (reading.isUnparseable) {
|
|
86
|
+
unparseable.push(commit);
|
|
115
87
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
88
|
+
}
|
|
89
|
+
const releasedEntries = [];
|
|
90
|
+
for (const release of windows.slice(1)) {
|
|
91
|
+
const releaseSections = new Map();
|
|
92
|
+
for (const commit of release.commits) {
|
|
93
|
+
for (const derived of readCommit(commit, context, undefined).items) {
|
|
94
|
+
appendItem(releaseSections, derived.header, derived.item);
|
|
120
95
|
}
|
|
121
|
-
sections.push({
|
|
122
|
-
title,
|
|
123
|
-
audience: devOnlyNormalised.has(stripGroupDecorations(title)) ? 'dev' : 'all',
|
|
124
|
-
items,
|
|
125
|
-
});
|
|
126
96
|
}
|
|
127
|
-
sections
|
|
97
|
+
const sections = buildSections(releaseSections, context.devOnlySections);
|
|
128
98
|
if (sections.length > 0) {
|
|
129
|
-
|
|
99
|
+
releasedEntries.push({ version: extractVersion(release.version), date: formatDate(release.timestamp), sections });
|
|
130
100
|
}
|
|
131
101
|
}
|
|
132
|
-
return
|
|
102
|
+
return {
|
|
103
|
+
previousTag: baselineWindow?.version,
|
|
104
|
+
releasedEntries,
|
|
105
|
+
unreleased: {
|
|
106
|
+
bump: determineBumpType(signals, context.workTypes, context.versionPatterns),
|
|
107
|
+
commits: unreleasedCommits.filter((commit) => !isReleaseSubject(commit.subject)).toReversed(),
|
|
108
|
+
date: formatDate(unreleasedWindow?.timestamp ?? Math.floor(Date.now() / 1_000)),
|
|
109
|
+
diagnostics,
|
|
110
|
+
parsedCommitCount,
|
|
111
|
+
sections: buildSections(sectionMap, context.devOnlySections),
|
|
112
|
+
unparseableCommits: unparseable.length > 0 ? unparseable.toReversed() : undefined,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
133
115
|
}
|
|
134
|
-
function
|
|
135
|
-
|
|
116
|
+
function readCommit(commit, context, diagnostics) {
|
|
117
|
+
if (isNonChangeSubject(commit.subject)) {
|
|
118
|
+
return { items: [], isUnparseable: false };
|
|
119
|
+
}
|
|
120
|
+
const reading = parseChangeRecordBlock(commit.message);
|
|
121
|
+
if (reading.kind === 'malformed') {
|
|
122
|
+
diagnostics?.malformedBlocks.push({
|
|
123
|
+
commitHash: commit.hash,
|
|
124
|
+
commitSubject: commit.subject,
|
|
125
|
+
reason: reading.reason,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (reading.kind === 'read' && reading.entries.length > 0) {
|
|
129
|
+
const items = reading.entries.flatMap((entry, entryIndex) => buildEntryItem(commit, entry, entryIndex + 1, reading.prNumber, context, diagnostics) ?? []);
|
|
130
|
+
return { items, isUnparseable: false };
|
|
131
|
+
}
|
|
132
|
+
const classification = classifyChangelogCommit(commit, context.workTypes, {
|
|
133
|
+
breakingPolicies: context.breakingPolicies,
|
|
134
|
+
...(diagnostics !== undefined && {
|
|
135
|
+
onPolicyViolation: (violating, type, surface) => {
|
|
136
|
+
diagnostics.policyViolations.push({
|
|
137
|
+
commitHash: violating.hash,
|
|
138
|
+
commitSubject: violating.subject,
|
|
139
|
+
type,
|
|
140
|
+
surface,
|
|
141
|
+
});
|
|
142
|
+
},
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
if (classification.kind !== 'header') {
|
|
146
|
+
return { items: [], isUnparseable: classification.kind === 'unparseable' && reading.kind !== 'malformed' };
|
|
147
|
+
}
|
|
148
|
+
const item = buildTitleItem(commit, classification.breaking, context.workTypes);
|
|
149
|
+
return {
|
|
150
|
+
items: [
|
|
151
|
+
{ header: classification.header, item, signal: { type: classification.type, breaking: item.breaking === true } },
|
|
152
|
+
],
|
|
153
|
+
isUnparseable: false,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function buildSections(sectionMap, devOnlySections) {
|
|
157
|
+
const sections = [];
|
|
158
|
+
for (const [title, items] of sectionMap) {
|
|
159
|
+
sections.push({
|
|
160
|
+
title,
|
|
161
|
+
audience: devOnlySections.has(stripGroupDecorations(title)) ? 'dev' : 'all',
|
|
162
|
+
items,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return sections.toSorted((a, b) => canonicalSectionPriority(a.title) - canonicalSectionPriority(b.title));
|
|
136
166
|
}
|
|
137
|
-
function
|
|
138
|
-
if (
|
|
139
|
-
return
|
|
167
|
+
function isRoutedTo(scopes, routing) {
|
|
168
|
+
if (scopes.length === 0) {
|
|
169
|
+
return true;
|
|
140
170
|
}
|
|
141
|
-
|
|
142
|
-
|
|
171
|
+
return scopes.some((scope) => {
|
|
172
|
+
const resolved = routing.scopeAliases[scope] ?? scope;
|
|
173
|
+
return resolved === '*' || resolved === routing.workspaceDir;
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function formatDate(timestamp) {
|
|
177
|
+
return new Date(timestamp * 1_000).toISOString().slice(0, 10);
|
|
143
178
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
179
|
+
function appendItem(sectionMap, header, item) {
|
|
180
|
+
let items = sectionMap.get(header);
|
|
181
|
+
if (items === undefined) {
|
|
182
|
+
items = [];
|
|
183
|
+
sectionMap.set(header, items);
|
|
149
184
|
}
|
|
150
|
-
|
|
185
|
+
items.push(item);
|
|
186
|
+
}
|
|
187
|
+
function buildEntryItem(commit, entry, position, prNumber, context, diagnostics) {
|
|
188
|
+
const { breakingPolicies, routing, workTypes } = context;
|
|
189
|
+
if (routing !== undefined && !isRoutedTo(entry.scopes, routing)) {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
const type = resolveType(entry.type, workTypes);
|
|
193
|
+
if (type === undefined) {
|
|
194
|
+
diagnostics?.undeclaredEntryTypes.push({
|
|
195
|
+
commitHash: commit.hash,
|
|
196
|
+
commitSubject: commit.subject,
|
|
197
|
+
entryPosition: position,
|
|
198
|
+
type: entry.type,
|
|
199
|
+
});
|
|
200
|
+
return undefined;
|
|
201
|
+
}
|
|
202
|
+
const config = workTypes[type];
|
|
203
|
+
if (config === undefined || config.excludedFromChangelog === true) {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
206
|
+
const breaking = evaluateBreakingPolicy({
|
|
207
|
+
commit: { message: commit.message, subject: commit.subject, hash: commit.hash },
|
|
208
|
+
resolvedType: type,
|
|
209
|
+
hasPrefixBreaking: entry.breaking,
|
|
210
|
+
hasFooterBreaking: false,
|
|
211
|
+
policy: breakingPolicies[type] ?? 'optional',
|
|
212
|
+
prefixSurface: 'entry',
|
|
213
|
+
onPolicyViolation: diagnostics === undefined
|
|
214
|
+
? undefined
|
|
215
|
+
: (violating, violatedType, surface) => {
|
|
216
|
+
diagnostics.policyViolations.push({
|
|
217
|
+
commitHash: violating.hash,
|
|
218
|
+
commitSubject: violating.subject,
|
|
219
|
+
type: violatedType,
|
|
220
|
+
surface,
|
|
221
|
+
entryPosition: position,
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
const item = {
|
|
226
|
+
description: prNumber === undefined ? entry.text : `${entry.text} (#${prNumber})`,
|
|
227
|
+
};
|
|
228
|
+
if (breaking) {
|
|
229
|
+
item.breaking = true;
|
|
230
|
+
}
|
|
231
|
+
if (entry.migration !== undefined) {
|
|
232
|
+
item.migration = entry.migration;
|
|
233
|
+
}
|
|
234
|
+
item.hash = commit.hash;
|
|
235
|
+
item.entry = position;
|
|
236
|
+
return { header: config.header, item, signal: { type, breaking } };
|
|
237
|
+
}
|
|
238
|
+
function buildTitleItem(commit, isParsedBreaking, workTypes) {
|
|
239
|
+
const body = extractBody(stripChangeRecordBlocks(commit.message));
|
|
240
|
+
const item = { description: extractDescription(commit.message) };
|
|
241
|
+
if (body !== undefined) {
|
|
242
|
+
item.body = body;
|
|
243
|
+
}
|
|
244
|
+
if (isParsedBreaking && subjectHasBreakingMarker(commit.message, workTypes)) {
|
|
245
|
+
item.breaking = true;
|
|
246
|
+
}
|
|
247
|
+
const migration = extractMigration(body);
|
|
248
|
+
if (migration !== undefined) {
|
|
249
|
+
item.migration = migration;
|
|
250
|
+
}
|
|
251
|
+
item.hash = commit.hash;
|
|
252
|
+
return item;
|
|
253
|
+
}
|
|
254
|
+
function subjectHasBreakingMarker(message, workTypes) {
|
|
255
|
+
return parseSubject(message.split('\n', 1)[0] ?? '', workTypes)?.breaking === true;
|
|
151
256
|
}
|
|
152
257
|
function extractDescription(message) {
|
|
153
258
|
const firstLine = message.split('\n', 1)[0] ?? message;
|
|
154
259
|
const afterColon = firstLine.split(': ').slice(1).join(': ');
|
|
155
|
-
|
|
156
|
-
return afterColon.charAt(0).toUpperCase() + afterColon.slice(1);
|
|
157
|
-
}
|
|
158
|
-
return firstLine;
|
|
260
|
+
return afterColon.charAt(0).toUpperCase() + afterColon.slice(1);
|
|
159
261
|
}
|
|
160
262
|
const TRAILER_PATTERNS = [
|
|
161
263
|
/^Change:/i,
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import type { WorkspaceConfig } from './types.js';
|
|
2
2
|
/** Identities, current versions, and `workspace:` dependency edges of the workspaces' packages. */
|
|
3
3
|
export interface DependencyGraph {
|
|
4
|
-
/**
|
|
4
|
+
/** Maps a package name to its workspace `dir`. */
|
|
5
5
|
packageNameToDir: Map<string, string>;
|
|
6
|
-
/**
|
|
6
|
+
/** Maps a workspace `dir` to its package name (inverse of `packageNameToDir`). */
|
|
7
7
|
dirToPackageName: Map<string, string>;
|
|
8
|
-
/**
|
|
8
|
+
/** Maps a workspace `dir` to its current version; absent when its `package.json` doesn't have a string `version`. */
|
|
9
9
|
dirToVersion: Map<string, string>;
|
|
10
|
-
/**
|
|
10
|
+
/** Maps a package name to the workspaces that depend on it. */
|
|
11
11
|
dependentsOf: Map<string, WorkspaceConfig[]>;
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Maps a workspace `dir` to the `workspace:`-protocol package names that it declares in `dependencies` or
|
|
14
|
+
* `peerDependencies` (the forward counterpart of `dependentsOf`).
|
|
15
15
|
*/
|
|
16
16
|
dependenciesOf: Map<string, Set<string>>;
|
|
17
17
|
}
|
|
@@ -1,10 +1,3 @@
|
|
|
1
1
|
import type { ChangelogEntry } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Build a synthetic changelog entry for a forced empty-range release.
|
|
4
|
-
*
|
|
5
|
-
* Produces a single `ChangelogEntry` with one `'Notes'` section (audience `'dev'`)
|
|
6
|
-
* containing a single `'Forced version bump.'` item. Used when `release-kit prepare`
|
|
7
|
-
* proceeds via `--force`, `--bump=X`, or `--set-version` against a unit with zero
|
|
8
|
-
* qualifying commits since its last tag. Pure function: no I/O.
|
|
9
|
-
*/
|
|
2
|
+
/** Builds the changelog entry of a forced release whose unreleased window doesn't yield any changelog item. */
|
|
10
3
|
export declare function buildEmptyReleaseEntry(version: string, date: string): ChangelogEntry;
|
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
import type { PrepareResult } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Each released workspace with commits gets a section headed by its tag,
|
|
6
|
-
* followed by scope-stripped commit messages as bullet points. Sections
|
|
7
|
-
* are separated by blank lines. The project release (when present) is
|
|
8
|
-
* appended as a final section using the same format. Returns an empty
|
|
9
|
-
* string when no released workspaces have commits and there is no project
|
|
10
|
-
* release.
|
|
3
|
+
* Builds a release commit body from a prepare result: one section per released workspace or project that has commits,
|
|
4
|
+
* the project last, each headed by its tag and listing its scope-stripped commit subjects.
|
|
11
5
|
*/
|
|
12
6
|
export declare function buildReleaseSummary(result: Pick<PrepareResult, 'project' | 'workspaces'>): string;
|
|
@@ -11,7 +11,7 @@ export function buildReleaseSummary(result) {
|
|
|
11
11
|
}
|
|
12
12
|
const lines = [workspace.tag];
|
|
13
13
|
for (const commit of commits) {
|
|
14
|
-
lines.push(`- ${stripScope(commit.
|
|
14
|
+
lines.push(`- ${stripScope(commit.subject)}`);
|
|
15
15
|
}
|
|
16
16
|
sections.push(lines.join('\n'));
|
|
17
17
|
}
|
|
@@ -19,7 +19,7 @@ export function buildReleaseSummary(result) {
|
|
|
19
19
|
if (project !== undefined && project.status === 'released' && project.commits.length > 0) {
|
|
20
20
|
const lines = [project.tag];
|
|
21
21
|
for (const commit of project.commits) {
|
|
22
|
-
lines.push(`- ${stripScope(commit.
|
|
22
|
+
lines.push(`- ${stripScope(commit.subject)}`);
|
|
23
23
|
}
|
|
24
24
|
sections.push(lines.join('\n'));
|
|
25
25
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import type { ChangelogEntry } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Produces a single `ChangelogEntry` with one `'Dependency updates'` section (audience `'dev'`)
|
|
6
|
-
* containing one item per propagated-from dependency. Pure function: no I/O.
|
|
3
|
+
* Builds the changelog entry of a propagation-only bump, with one item per dependency whose bump propagated, under
|
|
4
|
+
* `sectionTitle`, the resolved `deps` work type's header.
|
|
7
5
|
*/
|
|
8
6
|
export declare function buildSyntheticChangelogEntry(propagatedFrom: ReadonlyArray<{
|
|
9
7
|
packageName: string;
|
|
10
8
|
newVersion: string;
|
|
11
|
-
}>, version: string, date: string): ChangelogEntry;
|
|
9
|
+
}>, version: string, date: string, sectionTitle: string): ChangelogEntry;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export function buildSyntheticChangelogEntry(propagatedFrom, version, date) {
|
|
1
|
+
export function buildSyntheticChangelogEntry(propagatedFrom, version, date, sectionTitle) {
|
|
2
2
|
const items = propagatedFrom.map((dep) => ({
|
|
3
3
|
description: `Bumped \`${dep.packageName}\` to ${dep.newVersion}`,
|
|
4
4
|
}));
|
|
5
5
|
return {
|
|
6
6
|
version,
|
|
7
7
|
date,
|
|
8
|
-
sections: [{ title:
|
|
8
|
+
sections: [{ title: sectionTitle, audience: 'dev', items }],
|
|
9
9
|
};
|
|
10
10
|
}
|
|
@@ -1,10 +1,3 @@
|
|
|
1
1
|
import type { ReleaseType } from './types.js';
|
|
2
|
-
/**
|
|
3
|
-
* Bumps a semver version string by the given release type.
|
|
4
|
-
*
|
|
5
|
-
* @param version - A semver version string (e.g., '1.2.3').
|
|
6
|
-
* @param releaseType - The type of release bump to apply.
|
|
7
|
-
* @returns The bumped version string.
|
|
8
|
-
* @throws If the version string is not a valid semver format.
|
|
9
|
-
*/
|
|
2
|
+
/** Bumps a `major.minor.patch` version by the release type; throws on a version of any other form. */
|
|
10
3
|
export declare function bumpVersion(version: string, releaseType: ReleaseType): string;
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import type { ChangelogEntry, ReleaseConfig } from './types.js';
|
|
2
|
-
/**
|
|
2
|
+
/** Resolves the path of the `changelog.json` file under a workspace's changelog directory. */
|
|
3
3
|
export declare function resolveChangelogJsonPath(config: Pick<ReleaseConfig, 'changelogJson'>, changelogPath: string): string;
|
|
4
4
|
/** Renders changelog entries as a `changelog.json` file's complete content, sorted newest-first. */
|
|
5
5
|
export declare function renderChangelogJson(entries: ChangelogEntry[]): string;
|
|
6
6
|
/**
|
|
7
|
-
* Merges `entries` with the on-disk entries at `filePath` and returns the merged set in
|
|
8
|
-
*
|
|
7
|
+
* Merges `entries` with the on-disk entries at `filePath` and returns the merged set in newest-first order, without
|
|
8
|
+
* writing.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* missing or malformed file as an empty existing set, so a malformed file does not abort the
|
|
13
|
-
* release.
|
|
10
|
+
* Keeps an on-disk entry that `entries` lacks, which preserves synthetic entries across propagation runs. A missing or
|
|
11
|
+
* malformed file counts as empty, so that a malformed file does not abort the release.
|
|
14
12
|
*/
|
|
15
13
|
export declare function mergeChangelogEntriesWithDisk(filePath: string, entries: ChangelogEntry[]): ChangelogEntry[];
|
|
@@ -2,8 +2,8 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
4
4
|
import stringify from 'json-stringify-pretty-compact';
|
|
5
|
-
import semver from 'semver';
|
|
6
5
|
import { isChangelogEntry } from "./changelogJsonUtils.js";
|
|
6
|
+
import { compareVersionsDescending } from "./compareVersions.js";
|
|
7
7
|
import { isUnknownArray } from "./typeGuards.js";
|
|
8
8
|
export function resolveChangelogJsonPath(config, changelogPath) {
|
|
9
9
|
return join(changelogPath, config.changelogJson.outputPath);
|
|
@@ -16,7 +16,7 @@ export function mergeChangelogEntriesWithDisk(filePath, entries) {
|
|
|
16
16
|
return mergeEntries(entries, existing);
|
|
17
17
|
}
|
|
18
18
|
function sortNewestFirst(entries) {
|
|
19
|
-
return [...entries].
|
|
19
|
+
return [...entries].toSorted((a, b) => compareVersionsDescending(a.version, b.version));
|
|
20
20
|
}
|
|
21
21
|
function readExistingEntries(filePath) {
|
|
22
22
|
if (!existsSync(filePath)) {
|
|
@@ -45,18 +45,3 @@ function mergeEntries(newEntries, existingEntries) {
|
|
|
45
45
|
}
|
|
46
46
|
return sortNewestFirst(versionMap.values());
|
|
47
47
|
}
|
|
48
|
-
function compareVersionsDescending(a, b) {
|
|
49
|
-
const aValid = semver.valid(a);
|
|
50
|
-
const bValid = semver.valid(b);
|
|
51
|
-
if (aValid && bValid)
|
|
52
|
-
return semver.rcompare(aValid, bValid);
|
|
53
|
-
if (aValid)
|
|
54
|
-
return -1;
|
|
55
|
-
if (bValid)
|
|
56
|
-
return 1;
|
|
57
|
-
if (a > b)
|
|
58
|
-
return -1;
|
|
59
|
-
if (a < b)
|
|
60
|
-
return 1;
|
|
61
|
-
return 0;
|
|
62
|
-
}
|
|
@@ -1,19 +1,16 @@
|
|
|
1
1
|
import type { ChangelogEntry } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Reports whether a value parsed from JSON is a `ChangelogEntry`.
|
|
4
4
|
*
|
|
5
|
-
* Checks the entry's top level only: `sections`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Checks the entry's top level only: `sections` must be an array, and its contents pass through unvalidated.
|
|
6
|
+
* `.meta/changelog.json` is written by release-kit and read tolerantly; strict validation applies to the
|
|
7
|
+
* human-authored `.meta/changelog-overrides.json`, in `changelogOverrides.ts`.
|
|
8
8
|
*/
|
|
9
9
|
export declare function isChangelogEntry(value: unknown): value is ChangelogEntry;
|
|
10
|
-
/**
|
|
10
|
+
/** Extracts the version from a tag: the text from its first `N.N.N` onward, or the whole tag when it contains none. */
|
|
11
11
|
export declare function extractVersion(tag: string): string;
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* Returns `undefined` if the file cannot be read or does not contain a valid array.
|
|
16
|
-
* Silent on parse error — for the warn-and-treat-as-empty variant used by upsert callers,
|
|
17
|
-
* see `readExistingEntries` in `./changelogJsonFile.ts`.
|
|
13
|
+
* Reads the changelog entries in a JSON file; undefined when the file is missing or unreadable, or doesn't contain an
|
|
14
|
+
* array.
|
|
18
15
|
*/
|
|
19
16
|
export declare function readChangelogEntries(filePath: string): ChangelogEntry[] | undefined;
|