@williamthorsen/release-kit 11.0.0 → 12.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 +62 -2
- package/.readyup/kits/default.js +1 -17
- package/.readyup/manifest.json +4 -4
- package/CHANGELOG.md +40 -0
- package/README.md +25 -44
- package/dist/esm/attachChangelogDiagnostics.d.ts +4 -0
- package/dist/esm/attachChangelogDiagnostics.js +11 -0
- package/dist/esm/bin/release-kit.js +4 -3
- package/dist/esm/buildChangelogEntries.d.ts +59 -17
- package/dist/esm/buildChangelogEntries.js +194 -111
- package/dist/esm/buildReleaseSummary.js +2 -2
- package/dist/esm/changelogOverrides.d.ts +31 -13
- package/dist/esm/changelogOverrides.js +138 -58
- package/dist/esm/classifyChangelogCommit.d.ts +38 -0
- package/dist/esm/classifyChangelogCommit.js +33 -0
- package/dist/esm/decideRelease.d.ts +11 -37
- package/dist/esm/decideRelease.js +5 -27
- package/dist/esm/defaults.d.ts +1 -1
- package/dist/esm/defaults.js +3 -0
- package/dist/esm/determineBumpType.d.ts +11 -11
- package/dist/esm/determineBumpType.js +6 -7
- package/dist/esm/enumerateReleaseWindows.d.ts +50 -0
- package/dist/esm/enumerateReleaseWindows.js +124 -0
- package/dist/esm/generateChangelogs.d.ts +5 -23
- package/dist/esm/generateChangelogs.js +0 -14
- package/dist/esm/glyphs.d.ts +1 -1
- package/dist/esm/help/prepareHelp.d.ts +1 -1
- package/dist/esm/help/prepareHelp.js +4 -6
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/init/initCommand.js +1 -1
- package/dist/esm/init/scaffold.d.ts +0 -2
- package/dist/esm/init/scaffold.js +2 -30
- package/dist/esm/loadConfig.d.ts +1 -1
- package/dist/esm/loadConfig.js +1 -4
- package/dist/esm/parseChangeRecordBlock.d.ts +37 -0
- package/dist/esm/parseChangeRecordBlock.js +134 -0
- package/dist/esm/parseCommitMessage.d.ts +31 -6
- package/dist/esm/parseCommitMessage.js +6 -5
- package/dist/esm/prepareCommand.js +4 -12
- package/dist/esm/releasePrepare.d.ts +7 -8
- package/dist/esm/releasePrepare.js +53 -73
- package/dist/esm/releasePrepareMono.js +48 -85
- package/dist/esm/releasePrepareProject.d.ts +1 -1
- package/dist/esm/releasePrepareProject.js +19 -37
- package/dist/esm/reportPrepare.js +39 -28
- package/dist/esm/types.d.ts +99 -55
- package/dist/esm/types.js +1 -1
- package/dist/esm/validateConfig.js +4 -0
- package/dist/esm/validateOverridesCommand.d.ts +6 -5
- package/dist/esm/validateOverridesCommand.js +16 -23
- package/docs/changelogs.md +64 -25
- package/docs/configuration.md +2 -3
- package/docs/editorial-overrides.md +75 -16
- package/docs/project-releases.md +4 -4
- package/docs/releasing.md +13 -8
- package/docs/work-types.md +13 -9
- package/package.json +1 -2
- 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/runGitCliff.d.ts +0 -38
- package/dist/esm/runGitCliff.js +0 -25
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { formatGlyphLine, formatStatusLine, measureGlyphColumn, STATUS_GLYPHS, truncateToWidth, } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { bold, dim, sectionHeader } from "./format.js";
|
|
3
3
|
import { RELEASE_GLYPHS } from "./glyphs.js";
|
|
4
|
-
import { GIT_CLIFF_NPX_ARGS } from "./runGitCliff.js";
|
|
5
|
-
const CLIFF_DRY_RUN_COMMAND = `npx ${GIT_CLIFF_NPX_ARGS.join(' ')} ...`;
|
|
6
4
|
const SUBJECT_COLUMN_BUDGET = 72;
|
|
7
5
|
export function reportPrepare(plan, options) {
|
|
8
6
|
const isMultiWorkspace = plan.workspaces.some((w) => w.name !== undefined) || plan.project !== undefined;
|
|
@@ -27,6 +25,7 @@ function formatSingleWorkspace(result, options) {
|
|
|
27
25
|
formatUnparseableWarning(lines, workspace, style);
|
|
28
26
|
formatPolicyViolations(lines, workspace.policyViolations, style);
|
|
29
27
|
if (workspace.status === 'skipped') {
|
|
28
|
+
formatChangeRecordWarnings(lines, workspace, style);
|
|
30
29
|
lines.push(formatStatusLine(style, 'skipped', workspace.skipReason));
|
|
31
30
|
formatWarnings(lines, result, style);
|
|
32
31
|
return lines.join('\n');
|
|
@@ -49,6 +48,7 @@ function formatSingleWorkspace(result, options) {
|
|
|
49
48
|
else if (workspace.releaseType !== undefined) {
|
|
50
49
|
lines.push(formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${workspace.currentVersion} → ${bold(workspace.newVersion)} (${workspace.releaseType})`));
|
|
51
50
|
}
|
|
51
|
+
formatChangeRecordWarnings(lines, workspace, style);
|
|
52
52
|
formatBumpFiles(lines, workspace, dryRun);
|
|
53
53
|
lines.push(dim('Generating changelogs...'));
|
|
54
54
|
formatChangelogFiles(lines, workspace, dryRun);
|
|
@@ -87,6 +87,8 @@ function formatProjectSection(lines, project, dryRun, style) {
|
|
|
87
87
|
lines.push(dim(` Found ${project.commitCount} commits ${since}`));
|
|
88
88
|
formatPolicyViolations(lines, project.policyViolations, style, ' ');
|
|
89
89
|
if (project.status === 'skipped') {
|
|
90
|
+
formatUnparseableWarning(lines, project, style, ' ');
|
|
91
|
+
formatChangeRecordWarnings(lines, project, style, ' ');
|
|
90
92
|
lines.push(` ${formatStatusLine(style, 'skipped', project.skipReason)}`);
|
|
91
93
|
return;
|
|
92
94
|
}
|
|
@@ -97,7 +99,8 @@ function formatProjectSection(lines, project, dryRun, style) {
|
|
|
97
99
|
if (project.bumpOverride !== undefined) {
|
|
98
100
|
lines.push(` Using bump override: ${project.bumpOverride}`);
|
|
99
101
|
}
|
|
100
|
-
|
|
102
|
+
formatUnparseableWarning(lines, project, style, ' ');
|
|
103
|
+
formatChangeRecordWarnings(lines, project, style, ' ');
|
|
101
104
|
lines.push(dim(` Bumping versions (${releaseType})...`), ` ${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${currentVersion} → ${bold(newVersion)} (${releaseType})`)}`);
|
|
102
105
|
for (const file of project.bumpedFiles) {
|
|
103
106
|
if (dryRun) {
|
|
@@ -110,7 +113,7 @@ function formatProjectSection(lines, project, dryRun, style) {
|
|
|
110
113
|
lines.push(dim(' Generating changelogs...'));
|
|
111
114
|
for (const file of project.changelogFiles) {
|
|
112
115
|
if (dryRun) {
|
|
113
|
-
lines.push(dim(` [dry-run] Would
|
|
116
|
+
lines.push(dim(` [dry-run] Would generate changelog: ${file}`));
|
|
114
117
|
}
|
|
115
118
|
else {
|
|
116
119
|
lines.push(dim(` Generating changelog: ${file}`));
|
|
@@ -119,21 +122,6 @@ function formatProjectSection(lines, project, dryRun, style) {
|
|
|
119
122
|
formatPreviewFiles(lines, project, dryRun, ' ');
|
|
120
123
|
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(tag))}`);
|
|
121
124
|
}
|
|
122
|
-
function formatProjectUnparseable(lines, project, style) {
|
|
123
|
-
const unparseable = project.unparseableCommits;
|
|
124
|
-
if (unparseable === undefined || unparseable.length === 0) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
const count = unparseable.length;
|
|
128
|
-
const isPatchFloor = project.parsedCommitCount === 0;
|
|
129
|
-
const suffix = isPatchFloor ? ' (defaulting to patch bump)' : '';
|
|
130
|
-
lines.push(` ${formatStatusLine(style, 'warning', `${count} commit${count === 1 ? '' : 's'} could not be parsed${suffix}`)}`);
|
|
131
|
-
for (const commit of unparseable) {
|
|
132
|
-
const shortHash = commit.hash.slice(0, 7);
|
|
133
|
-
const truncatedMessage = truncateSubject(commit.message);
|
|
134
|
-
lines.push(` · ${shortHash} ${truncatedMessage}`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
125
|
function formatWorkspaceSection(lines, workspace, dryRun, style) {
|
|
138
126
|
if (workspace.name !== undefined) {
|
|
139
127
|
lines.push(`\n${sectionHeader(workspace.name)}`);
|
|
@@ -141,6 +129,9 @@ function formatWorkspaceSection(lines, workspace, dryRun, style) {
|
|
|
141
129
|
const since = workspace.previousTag === undefined ? '(no previous release found)' : `since ${workspace.previousTag}`;
|
|
142
130
|
lines.push(dim(` Found ${workspace.commitCount} commits ${since}`));
|
|
143
131
|
if (workspace.status === 'skipped') {
|
|
132
|
+
formatUnparseableWarning(lines, workspace, style, ' ');
|
|
133
|
+
formatPolicyViolations(lines, workspace.policyViolations, style, ' ');
|
|
134
|
+
formatChangeRecordWarnings(lines, workspace, style, ' ');
|
|
144
135
|
lines.push(` ${formatStatusLine(style, 'skipped', workspace.skipReason)}`);
|
|
145
136
|
return;
|
|
146
137
|
}
|
|
@@ -149,6 +140,7 @@ function formatWorkspaceSection(lines, workspace, dryRun, style) {
|
|
|
149
140
|
formatCommitSummary(lines, workspace, propagatedFrom, isPropagatedOnly);
|
|
150
141
|
formatUnparseableWarning(lines, workspace, style, ' ');
|
|
151
142
|
formatPolicyViolations(lines, workspace.policyViolations, style, ' ');
|
|
143
|
+
formatChangeRecordWarnings(lines, workspace, style, ' ');
|
|
152
144
|
formatBumpLabels(lines, workspace, isPropagatedOnly);
|
|
153
145
|
formatVersionLine(lines, workspace, propagatedFrom, isPropagatedOnly, style);
|
|
154
146
|
formatBumpFiles(lines, workspace, dryRun, ' ');
|
|
@@ -203,7 +195,7 @@ function formatBumpFiles(lines, workspace, dryRun, indent = '') {
|
|
|
203
195
|
function formatChangelogFiles(lines, workspace, dryRun, indent = '') {
|
|
204
196
|
for (const file of workspace.changelogFiles) {
|
|
205
197
|
if (dryRun) {
|
|
206
|
-
lines.push(dim(`${indent} [dry-run] Would
|
|
198
|
+
lines.push(dim(`${indent} [dry-run] Would generate changelog: ${file}`));
|
|
207
199
|
}
|
|
208
200
|
else {
|
|
209
201
|
lines.push(dim(`${indent} Generating changelog: ${file}`));
|
|
@@ -221,19 +213,17 @@ function formatPreviewFiles(lines, release, dryRun, indent = '') {
|
|
|
221
213
|
}
|
|
222
214
|
}
|
|
223
215
|
}
|
|
224
|
-
function formatUnparseableWarning(lines,
|
|
225
|
-
const unparseable =
|
|
216
|
+
function formatUnparseableWarning(lines, release, style, indent = '') {
|
|
217
|
+
const unparseable = release.unparseableCommits;
|
|
226
218
|
if (unparseable === undefined || unparseable.length === 0) {
|
|
227
219
|
return;
|
|
228
220
|
}
|
|
229
221
|
const count = unparseable.length;
|
|
230
|
-
|
|
231
|
-
const suffix = isPatchFloor ? ' (defaulting to patch bump)' : '';
|
|
232
|
-
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${count} commit${count === 1 ? '' : 's'} could not be parsed${suffix}`)}`);
|
|
222
|
+
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${count} commit${count === 1 ? '' : 's'} could not be parsed`)}`);
|
|
233
223
|
for (const commit of unparseable) {
|
|
234
224
|
const shortHash = commit.hash.slice(0, 7);
|
|
235
|
-
const
|
|
236
|
-
lines.push(`${indent} · ${shortHash} ${
|
|
225
|
+
const truncatedSubject = truncateSubject(commit.subject);
|
|
226
|
+
lines.push(`${indent} · ${shortHash} ${truncatedSubject}`);
|
|
237
227
|
}
|
|
238
228
|
}
|
|
239
229
|
function formatPolicyViolations(lines, violations, style, indent = '') {
|
|
@@ -246,7 +236,28 @@ function formatPolicyViolations(lines, violations, style, indent = '') {
|
|
|
246
236
|
const shortHash = violation.commitHash.slice(0, 7);
|
|
247
237
|
const subject = violation.commitSubject;
|
|
248
238
|
const truncatedSubject = truncateSubject(subject);
|
|
249
|
-
|
|
239
|
+
const location = violation.surface === 'entry' ? `entry ${violation.entryPosition ?? '?'}` : `${violation.surface} surface`;
|
|
240
|
+
lines.push(`${indent} · ${shortHash} '${truncatedSubject}' — type '${violation.type}' at ${location}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
function formatChangeRecordWarnings(lines, release, style, indent = '') {
|
|
244
|
+
const { malformedBlocks = [], undeclaredEntryTypes = [] } = release;
|
|
245
|
+
if (malformedBlocks.length > 0) {
|
|
246
|
+
const count = malformedBlocks.length;
|
|
247
|
+
const header = `${count} change-record block${count === 1 ? '' : 's'} could not be read (item taken from the title)`;
|
|
248
|
+
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
|
|
249
|
+
for (const block of malformedBlocks) {
|
|
250
|
+
lines.push(`${indent} · ${block.commitHash.slice(0, 7)} '${truncateSubject(block.commitSubject)}' — ${block.reason}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (undeclaredEntryTypes.length > 0) {
|
|
254
|
+
const count = undeclaredEntryTypes.length;
|
|
255
|
+
const header = `${count} change-record entr${count === 1 ? 'y has an' : 'ies have'} undeclared type${count === 1 ? '' : 's'} (no item)`;
|
|
256
|
+
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
|
|
257
|
+
for (const entry of undeclaredEntryTypes) {
|
|
258
|
+
const shortHash = entry.commitHash.slice(0, 7);
|
|
259
|
+
lines.push(`${indent} · ${shortHash} '${truncateSubject(entry.commitSubject)}' — type '${entry.type}' at entry ${entry.entryPosition}`);
|
|
260
|
+
}
|
|
250
261
|
}
|
|
251
262
|
}
|
|
252
263
|
function formatPropagationSuffix(propagatedFrom) {
|
package/dist/esm/types.d.ts
CHANGED
|
@@ -11,8 +11,9 @@ export interface ChangelogItem {
|
|
|
11
11
|
/**
|
|
12
12
|
* Whether this item represents a breaking change.
|
|
13
13
|
*
|
|
14
|
-
* `true` when the commit subject carries the `!` prefix (e.g. `feat!:` or `drop(scope)!:`)
|
|
15
|
-
*
|
|
14
|
+
* `true` when the commit subject carries the `!` prefix (e.g. `feat!:` or `drop(scope)!:`), or the
|
|
15
|
+
* change-record entry from which the item derives sets `breaking: true`, and the work-type policy
|
|
16
|
+
* permits it, so the item agrees with the version bump.
|
|
16
17
|
* The `BREAKING CHANGE:` body footer is intentionally NOT considered here. Renderers prefix
|
|
17
18
|
* breaking-item bullets with the marker constructed from `WORK_TYPES_DATA.markers.breaking`
|
|
18
19
|
* (rendered as `🚨 **Breaking:** ` with the canonical SSOT values) to surface them prominently
|
|
@@ -20,29 +21,34 @@ export interface ChangelogItem {
|
|
|
20
21
|
*/
|
|
21
22
|
breaking?: boolean;
|
|
22
23
|
/**
|
|
23
|
-
* Migration instruction for a consumer, taken from the `Migration:` paragraph of `body
|
|
24
|
+
* Migration instruction for a consumer, taken from the `Migration:` paragraph of `body`, or from
|
|
25
|
+
* the `migration` of the change-record entry from which the item derives.
|
|
24
26
|
*
|
|
25
|
-
*
|
|
26
|
-
* paragraph. Independent of `breaking`: a `deprecate` commit cannot carry `!` under the default
|
|
27
|
+
* On a title-derived item, derived from `body` wherever `body` is set, and absent where the body
|
|
28
|
+
* carries no labeled paragraph. Independent of `breaking`: a `deprecate` commit cannot carry `!` under the default
|
|
27
29
|
* breaking policy and still calls for a migration. `body` keeps the paragraph, so
|
|
28
30
|
* `CHANGELOG.md` goes on rendering it in place.
|
|
29
31
|
*/
|
|
30
32
|
migration?: string;
|
|
31
33
|
/**
|
|
32
|
-
* Full git commit SHA when known. Captured from
|
|
34
|
+
* Full git commit SHA when known. Captured from the commit's release window and
|
|
33
35
|
* persisted in `changelog.json` so that override files can target items by hash. Synthetic
|
|
34
36
|
* propagation entries (`buildSyntheticChangelogEntry`, `buildEmptyReleaseEntry`) leave
|
|
35
37
|
* this field absent — they have no underlying commit.
|
|
36
38
|
*/
|
|
37
39
|
hash?: string;
|
|
40
|
+
/**
|
|
41
|
+
* 1-based position of the change-record entry from which this item derives, counted over the block's entries as
|
|
42
|
+
* written. Absent for an item derived from the commit's title.
|
|
43
|
+
*/
|
|
44
|
+
entry?: number;
|
|
38
45
|
}
|
|
39
46
|
/** A grouped section within a changelog entry (e.g., "Features", "Bug fixes"). */
|
|
40
47
|
export interface ChangelogSection {
|
|
41
48
|
/**
|
|
42
|
-
* Section title
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* (e.g. `sectionOrder` configs) must include the emoji prefix.
|
|
49
|
+
* Section title, read from the work type of the commit or of its change-record entry. A default
|
|
50
|
+
* title carries the type's emoji prefix (e.g. `"🐛 Bug fixes"`), so callers that match against
|
|
51
|
+
* `title` (e.g. `sectionOrder` configs) include that prefix.
|
|
46
52
|
*/
|
|
47
53
|
title: string;
|
|
48
54
|
audience: ChangelogAudience;
|
|
@@ -61,8 +67,9 @@ export interface ChangelogJsonConfig {
|
|
|
61
67
|
devOnlySections: string[];
|
|
62
68
|
}
|
|
63
69
|
/**
|
|
64
|
-
* Editorial override for
|
|
65
|
-
* the
|
|
70
|
+
* Editorial override for changelog items, keyed in the override file by `<hash>` (every item of the commit) or
|
|
71
|
+
* `<hash>:<n>` (the item derived from the commit's `n`th change-record entry), where the hash is lowercase hex
|
|
72
|
+
* matched as a prefix. All fields are optional; an entry with no fields is a validation error.
|
|
66
73
|
*
|
|
67
74
|
* `audience` declares the full forward-compatible vocabulary, but only `'skip'` is currently
|
|
68
75
|
* supported at runtime. `'all'` and `'dev'` reclassification is reserved for a v2 follow-up
|
|
@@ -75,7 +82,10 @@ export interface ChangelogOverride {
|
|
|
75
82
|
body?: string;
|
|
76
83
|
breaking?: boolean;
|
|
77
84
|
}
|
|
78
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* Overrides in `.meta/changelog-overrides.json`: a flat record keyed by `<hash>` or `<hash>:<n>`. The file may also contain
|
|
87
|
+
* a top-level `$schema` string, which the loader drops.
|
|
88
|
+
*/
|
|
79
89
|
export type ChangelogOverridesFile = Record<string, ChangelogOverride>;
|
|
80
90
|
/** Configuration for release notes consumption (README injection). */
|
|
81
91
|
export interface ReleaseNotesConfig {
|
|
@@ -88,8 +98,8 @@ export interface ReleaseNotesConfig {
|
|
|
88
98
|
*/
|
|
89
99
|
export interface ResolvedProjectConfig {
|
|
90
100
|
/**
|
|
91
|
-
* Resolved commit window for the project release: the git pathspecs
|
|
92
|
-
*
|
|
101
|
+
* Resolved commit window for the project release: the git pathspecs that select which commits
|
|
102
|
+
* the stage considers. Defaults to the union of every contributing
|
|
93
103
|
* workspace's `paths`.
|
|
94
104
|
*/
|
|
95
105
|
paths: string[];
|
|
@@ -102,7 +112,7 @@ export interface PropagationSource {
|
|
|
102
112
|
newVersion: string;
|
|
103
113
|
}
|
|
104
114
|
/**
|
|
105
|
-
* A `!`-policy violation detected while
|
|
115
|
+
* A `!`-policy violation detected while reading the unreleased window during release preparation.
|
|
106
116
|
*
|
|
107
117
|
* Surfaces from `releasePrepare`, `releasePrepareMono`, and `releasePrepareProject` via the
|
|
108
118
|
* `policyViolations` field on each per-workspace/project result. The policy itself is enforced
|
|
@@ -115,16 +125,40 @@ export interface PolicyViolation {
|
|
|
115
125
|
commitSubject: string;
|
|
116
126
|
/** Resolved canonical work type whose policy was violated. */
|
|
117
127
|
type: string;
|
|
118
|
-
/**
|
|
119
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Where the violation was detected: `'prefix'` for `!`, `'body'` for `BREAKING CHANGE:` footer, `'entry'` for a
|
|
130
|
+
* change-record entry's `breaking`.
|
|
131
|
+
*/
|
|
132
|
+
surface: 'prefix' | 'body' | 'entry';
|
|
133
|
+
/** 1-based position of the offending change-record entry; present only for surface `'entry'`. */
|
|
134
|
+
entryPosition?: number;
|
|
135
|
+
}
|
|
136
|
+
/** A commit whose `change-record` block could not be read, so that its changelog item came from its title. */
|
|
137
|
+
export interface MalformedChangeRecordBlock {
|
|
138
|
+
/** Full hash of the commit. */
|
|
139
|
+
commitHash: string;
|
|
140
|
+
/** First line of the commit message. */
|
|
141
|
+
commitSubject: string;
|
|
142
|
+
/** The defect that stopped the read. */
|
|
143
|
+
reason: string;
|
|
144
|
+
}
|
|
145
|
+
/** A change-record entry whose type the work-type taxonomy does not declare, so that it yielded no changelog item. */
|
|
146
|
+
export interface UndeclaredEntryType {
|
|
147
|
+
/** Full hash of the commit. */
|
|
148
|
+
commitHash: string;
|
|
149
|
+
/** First line of the commit message. */
|
|
150
|
+
commitSubject: string;
|
|
151
|
+
/** 1-based position of the entry in its block. */
|
|
152
|
+
entryPosition: number;
|
|
153
|
+
/** The type as the entry declares it. */
|
|
154
|
+
type: string;
|
|
120
155
|
}
|
|
121
156
|
/**
|
|
122
157
|
* Result of preparing a single workspace (package) for release when a release was produced.
|
|
123
158
|
*
|
|
124
159
|
* `currentVersion`, `newVersion`, `tag`, `bumpedFiles`, and `changelogFiles` are always
|
|
125
|
-
* populated. `releaseType`
|
|
126
|
-
* `--set-version` workspaces. `
|
|
127
|
-
* single-package bump-override path does not populate it. `commits` stays optional
|
|
160
|
+
* populated. `releaseType` and `parsedCommitCount` stay optional because they are left
|
|
161
|
+
* undefined for `--set-version` and propagation-only workspaces. `commits` stays optional
|
|
128
162
|
* because propagation-only releases have no direct commits.
|
|
129
163
|
*/
|
|
130
164
|
export interface ReleasedWorkspaceResult {
|
|
@@ -134,18 +168,19 @@ export interface ReleasedWorkspaceResult {
|
|
|
134
168
|
previousTag?: string;
|
|
135
169
|
commitCount: number;
|
|
136
170
|
/**
|
|
137
|
-
* Count of commits that
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
* produced by the legacy single-package executor's bump-override path. Use
|
|
141
|
-
* `bumpOverride` (not `parsedCommitCount === undefined`) as the signal for "the
|
|
142
|
-
* user supplied --bump=X".
|
|
171
|
+
* Count of commits that yield at least one changelog item — `0` when there are no commits or
|
|
172
|
+
* when none does. Absent for `--set-version` and propagation-only workspaces. Use
|
|
173
|
+
* `bumpOverride` as the signal for "the user supplied --bump=X".
|
|
143
174
|
*/
|
|
144
175
|
parsedCommitCount?: number;
|
|
145
|
-
/** Commits that
|
|
176
|
+
/** Commits that yield no changelog item and that no exclusion or diagnostic accounts for. */
|
|
146
177
|
unparseableCommits?: Commit[];
|
|
147
|
-
/** Policy violations
|
|
178
|
+
/** Policy violations of the unreleased window's titles and change-record entries; omitted when none. */
|
|
148
179
|
policyViolations?: PolicyViolation[];
|
|
180
|
+
/** Commits of the unreleased window whose `change-record` block could not be read; omitted when none. */
|
|
181
|
+
malformedBlocks?: MalformedChangeRecordBlock[];
|
|
182
|
+
/** Change-record entries of the unreleased window whose type is undeclared; omitted when none. */
|
|
183
|
+
undeclaredEntryTypes?: UndeclaredEntryType[];
|
|
149
184
|
releaseType?: ReleaseType;
|
|
150
185
|
currentVersion: string;
|
|
151
186
|
newVersion: string;
|
|
@@ -179,15 +214,16 @@ export interface SkippedWorkspaceResult {
|
|
|
179
214
|
name?: string;
|
|
180
215
|
previousTag?: string;
|
|
181
216
|
commitCount: number;
|
|
182
|
-
/**
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
*/
|
|
186
|
-
parsedCommitCount?: number;
|
|
187
|
-
/** Commits that could not be parsed into a recognized work type. */
|
|
217
|
+
/** Count of commits that yield at least one changelog item — `0` when there are no commits or when none does. */
|
|
218
|
+
parsedCommitCount: number;
|
|
219
|
+
/** Commits that yield no changelog item and that no exclusion or diagnostic accounts for. */
|
|
188
220
|
unparseableCommits?: Commit[];
|
|
189
|
-
/** Policy violations
|
|
221
|
+
/** Policy violations of the unreleased window's titles and change-record entries; omitted when none. */
|
|
190
222
|
policyViolations?: PolicyViolation[];
|
|
223
|
+
/** Commits of the unreleased window whose `change-record` block could not be read; omitted when none. */
|
|
224
|
+
malformedBlocks?: MalformedChangeRecordBlock[];
|
|
225
|
+
/** Change-record entries of the unreleased window whose type is undeclared; omitted when none. */
|
|
226
|
+
undeclaredEntryTypes?: UndeclaredEntryType[];
|
|
191
227
|
skipReason: string;
|
|
192
228
|
}
|
|
193
229
|
/**
|
|
@@ -210,16 +246,19 @@ export interface ReleasedProjectResult {
|
|
|
210
246
|
previousTag?: string;
|
|
211
247
|
commitCount: number;
|
|
212
248
|
/**
|
|
213
|
-
* Count of commits that
|
|
214
|
-
*
|
|
215
|
-
* parse. Use `bumpOverride` (not `parsedCommitCount === 0`) as the signal for "the
|
|
249
|
+
* Count of commits that yield at least one changelog item — `0` when there are no commits or
|
|
250
|
+
* when none does. Use `bumpOverride` (not `parsedCommitCount === 0`) as the signal for "the
|
|
216
251
|
* user supplied --bump=X".
|
|
217
252
|
*/
|
|
218
253
|
parsedCommitCount: number;
|
|
219
|
-
/** Commits that
|
|
254
|
+
/** Commits that yield no changelog item and that no exclusion or diagnostic accounts for. */
|
|
220
255
|
unparseableCommits?: Commit[];
|
|
221
|
-
/** Policy violations
|
|
256
|
+
/** Policy violations of the unreleased window's titles and change-record entries; omitted when none. */
|
|
222
257
|
policyViolations?: PolicyViolation[];
|
|
258
|
+
/** Commits of the unreleased window whose `change-record` block could not be read; omitted when none. */
|
|
259
|
+
malformedBlocks?: MalformedChangeRecordBlock[];
|
|
260
|
+
/** Change-record entries of the unreleased window whose type is undeclared; omitted when none. */
|
|
261
|
+
undeclaredEntryTypes?: UndeclaredEntryType[];
|
|
223
262
|
releaseType: ReleaseType;
|
|
224
263
|
currentVersion: string;
|
|
225
264
|
newVersion: string;
|
|
@@ -247,16 +286,16 @@ export interface SkippedProjectResult {
|
|
|
247
286
|
status: 'skipped';
|
|
248
287
|
previousTag?: string;
|
|
249
288
|
commitCount: number;
|
|
250
|
-
/**
|
|
251
|
-
* Count of commits that parsed into a recognized work type. Always populated by the
|
|
252
|
-
* unified `decideRelease` algorithm — `0` when there are no commits or when none
|
|
253
|
-
* parse.
|
|
254
|
-
*/
|
|
289
|
+
/** Count of commits that yield at least one changelog item — `0` when there are no commits or when none does. */
|
|
255
290
|
parsedCommitCount: number;
|
|
256
|
-
/** Commits that
|
|
291
|
+
/** Commits that yield no changelog item and that no exclusion or diagnostic accounts for. */
|
|
257
292
|
unparseableCommits?: Commit[];
|
|
258
|
-
/** Policy violations
|
|
293
|
+
/** Policy violations of the unreleased window's titles and change-record entries; omitted when none. */
|
|
259
294
|
policyViolations?: PolicyViolation[];
|
|
295
|
+
/** Commits of the unreleased window whose `change-record` block could not be read; omitted when none. */
|
|
296
|
+
malformedBlocks?: MalformedChangeRecordBlock[];
|
|
297
|
+
/** Change-record entries of the unreleased window whose type is undeclared; omitted when none. */
|
|
298
|
+
undeclaredEntryTypes?: UndeclaredEntryType[];
|
|
260
299
|
skipReason: string;
|
|
261
300
|
}
|
|
262
301
|
/**
|
|
@@ -287,6 +326,12 @@ export interface WorkTypeConfig {
|
|
|
287
326
|
header: string;
|
|
288
327
|
/** Optional aliases that map to this work type (e.g., 'feature' -> 'feat'). */
|
|
289
328
|
aliases?: string[] | undefined;
|
|
329
|
+
/**
|
|
330
|
+
* Keeps commits and change-record entries of this type out of every changelog. They yield no
|
|
331
|
+
* item, so they contribute nothing to the version bump, and the release report does not list
|
|
332
|
+
* them as unparseable.
|
|
333
|
+
*/
|
|
334
|
+
excludedFromChangelog?: boolean | undefined;
|
|
290
335
|
}
|
|
291
336
|
/**
|
|
292
337
|
* Defines which commit types trigger major or minor version bumps.
|
|
@@ -361,6 +406,7 @@ export declare const releaseNotesInputSchema: z.ZodObject<{
|
|
|
361
406
|
export declare const workTypeConfigSchema: z.ZodObject<{
|
|
362
407
|
header: z.ZodString;
|
|
363
408
|
aliases: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
409
|
+
excludedFromChangelog: z.ZodOptional<z.ZodBoolean>;
|
|
364
410
|
}, z.core.$strict>;
|
|
365
411
|
/** Schema for the `versionPatterns` block. Both arrays are required when the field is provided. */
|
|
366
412
|
export declare const versionPatternsSchema: z.ZodObject<{
|
|
@@ -415,7 +461,6 @@ export declare const releaseKitConfigSchema: z.ZodObject<{
|
|
|
415
461
|
outputPath: z.ZodOptional<z.ZodString>;
|
|
416
462
|
devOnlySections: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
417
463
|
}, z.core.$strict>>;
|
|
418
|
-
cliffConfigPath: z.ZodOptional<z.ZodString>;
|
|
419
464
|
formatCommand: z.ZodOptional<z.ZodString>;
|
|
420
465
|
project: z.ZodOptional<z.ZodObject<{
|
|
421
466
|
paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -452,6 +497,7 @@ export declare const releaseKitConfigSchema: z.ZodObject<{
|
|
|
452
497
|
workTypes: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
453
498
|
header: z.ZodString;
|
|
454
499
|
aliases: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
500
|
+
excludedFromChangelog: z.ZodOptional<z.ZodBoolean>;
|
|
455
501
|
}, z.core.$strict>>>;
|
|
456
502
|
}, z.core.$strict>;
|
|
457
503
|
/**
|
|
@@ -461,14 +507,16 @@ export declare const releaseKitConfigSchema: z.ZodObject<{
|
|
|
461
507
|
export type ReleaseKitConfig = z.infer<typeof releaseKitConfigSchema>;
|
|
462
508
|
/** A raw commit from the git log. */
|
|
463
509
|
export interface Commit {
|
|
464
|
-
/** The
|
|
510
|
+
/** The commit message, subject and body together. */
|
|
465
511
|
message: string;
|
|
512
|
+
/** The first line of the commit message. */
|
|
513
|
+
subject: string;
|
|
466
514
|
/** The commit hash. */
|
|
467
515
|
hash: string;
|
|
468
516
|
}
|
|
469
517
|
/** A commit that has been parsed to extract structured metadata. */
|
|
470
518
|
export interface ParsedCommit {
|
|
471
|
-
/** The original commit message. */
|
|
519
|
+
/** The original commit message, subject and body together. */
|
|
472
520
|
message: string;
|
|
473
521
|
/** The commit hash. */
|
|
474
522
|
hash: string;
|
|
@@ -530,8 +578,6 @@ export interface MonorepoReleaseConfig {
|
|
|
530
578
|
* arguments. Paths are repo-relative; file paths containing spaces are not supported.
|
|
531
579
|
*/
|
|
532
580
|
formatCommand?: string;
|
|
533
|
-
/** Path to the cliff.toml file; defaults to 'cliff.toml' when absent. */
|
|
534
|
-
cliffConfigPath?: string;
|
|
535
581
|
/**
|
|
536
582
|
* Maps scope shorthand names to their canonical names.
|
|
537
583
|
* When a commit uses `shorthand|type: description` or `type(shorthand): description`,
|
|
@@ -572,8 +618,6 @@ export interface ReleaseConfig {
|
|
|
572
618
|
* Paths are repo-relative; file paths containing spaces are not supported.
|
|
573
619
|
*/
|
|
574
620
|
formatCommand?: string;
|
|
575
|
-
/** Path to the cliff.toml file; defaults to 'cliff.toml' when absent. */
|
|
576
|
-
cliffConfigPath?: string;
|
|
577
621
|
/**
|
|
578
622
|
* Maps scope shorthand names to their canonical names.
|
|
579
623
|
* When a commit uses `shorthand|type: description` or `type(shorthand): description`,
|
package/dist/esm/types.js
CHANGED
|
@@ -41,6 +41,7 @@ export const workTypeConfigSchema = z
|
|
|
41
41
|
.object({
|
|
42
42
|
header: z.string(),
|
|
43
43
|
aliases: z.array(z.string()).optional(),
|
|
44
|
+
excludedFromChangelog: z.boolean().optional(),
|
|
44
45
|
})
|
|
45
46
|
.strict();
|
|
46
47
|
export const versionPatternsSchema = z
|
|
@@ -66,7 +67,6 @@ export const releaseKitConfigSchema = z
|
|
|
66
67
|
.object({
|
|
67
68
|
breakingPolicies: z.record(z.string(), breakingPolicyValueSchema).optional(),
|
|
68
69
|
changelogJson: changelogJsonInputSchema.optional(),
|
|
69
|
-
cliffConfigPath: z.string().min(1).optional(),
|
|
70
70
|
formatCommand: z.string().min(1).optional(),
|
|
71
71
|
project: projectConfigSchema.optional(),
|
|
72
72
|
releaseNotes: releaseNotesInputSchema.optional(),
|
|
@@ -30,6 +30,10 @@ function preprocessDeprecatedKeys(raw) {
|
|
|
30
30
|
return { cleaned: raw, deprecationErrors: [] };
|
|
31
31
|
const errors = [];
|
|
32
32
|
const cleaned = { ...raw };
|
|
33
|
+
if (Object.hasOwn(cleaned, 'cliffConfigPath')) {
|
|
34
|
+
errors.push('cliffConfigPath is no longer supported. release-kit reads changelog history from git directly and uses no external config file. Remove this field from your config, and delete the file it names.');
|
|
35
|
+
delete cleaned['cliffConfigPath'];
|
|
36
|
+
}
|
|
33
37
|
if (isRecord(cleaned['releaseNotes']) && Object.hasOwn(cleaned['releaseNotes'], 'shouldCreateGithubRelease')) {
|
|
34
38
|
errors.push('releaseNotes.shouldCreateGithubRelease is no longer supported. Adoption is now signaled by installing the create-github-release workflow, which `release-kit init` scaffolds. Remove this field from your config.');
|
|
35
39
|
const releaseNotesCopy = { ...cleaned['releaseNotes'] };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type OutputStyle, type StreamStyles } from '@williamthorsen/nmr-core';
|
|
2
2
|
import { type ValidateAllChangelogOverridesInputs, type ValidateAllChangelogOverridesResult } from './changelogOverrides.js';
|
|
3
3
|
import { type WorkspaceDiscovery } from './discoverWorkspaces.js';
|
|
4
|
+
import { type GenerateChangelogOptions } from './generateChangelogs.js';
|
|
4
5
|
import { type LoadValidatedConfigResult } from './loadValidatedConfig.js';
|
|
5
6
|
import type { ChangelogEntry, ReleaseConfig } from './types.js';
|
|
6
7
|
/**
|
|
@@ -10,7 +11,7 @@ import type { ChangelogEntry, ReleaseConfig } from './types.js';
|
|
|
10
11
|
* Exit codes:
|
|
11
12
|
* - `0` — clean: no errors, no warnings.
|
|
12
13
|
* - `1` — only stale-key warnings.
|
|
13
|
-
* - `2` — schema/parse or ambiguous
|
|
14
|
+
* - `2` — schema/parse errors or keys that fail to match (ambiguous prefix, overlapping keys, a bare key setting fields on several items) (errors dominate when both classes exist).
|
|
14
15
|
*/
|
|
15
16
|
export interface ValidateOverridesCommandResult {
|
|
16
17
|
exitCode: 0 | 1 | 2;
|
|
@@ -22,17 +23,17 @@ export interface ValidateOverridesCommandDependencies {
|
|
|
22
23
|
loadValidatedConfig?: () => Promise<LoadValidatedConfigResult>;
|
|
23
24
|
/**
|
|
24
25
|
* Build changelog entries for a scope. Defaults to `buildChangelogEntries`, the same path
|
|
25
|
-
* `release-kit prepare` uses — anchoring `validate`'s
|
|
26
|
-
* construction.
|
|
26
|
+
* `release-kit prepare` uses — anchoring `validate`'s item universe to `prepare`'s by
|
|
27
|
+
* construction.
|
|
27
28
|
*/
|
|
28
|
-
buildEntries?: (config: Pick<ReleaseConfig, '
|
|
29
|
+
buildEntries?: (config: Pick<ReleaseConfig, 'breakingPolicies' | 'changelogJson' | 'workTypes'>, options: GenerateChangelogOptions) => ChangelogEntry[];
|
|
29
30
|
/** Pluggable validator (default: the production library function). Tests use this to drive specific result shapes through the formatter. */
|
|
30
31
|
validate?: (inputs: ValidateAllChangelogOverridesInputs) => ValidateAllChangelogOverridesResult;
|
|
31
32
|
}
|
|
32
33
|
/**
|
|
33
34
|
* Validate every changelog override file across the project and per-workspace scopes, and
|
|
34
35
|
* return a tiered exit-code-plus-message result. Performs workspace discovery, config load,
|
|
35
|
-
* and per-scope
|
|
36
|
+
* and per-scope item collection, then delegates the actual validation to
|
|
36
37
|
* {@link validateAllChangelogOverrides}.
|
|
37
38
|
*
|
|
38
39
|
* Single-package and monorepo modes are handled uniformly: single-package collapses to one
|
|
@@ -3,7 +3,7 @@ import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
|
3
3
|
import { buildChangelogEntries } from "./buildChangelogEntries.js";
|
|
4
4
|
import { resolveOverridePath, validateAllChangelogOverrides, } from "./changelogOverrides.js";
|
|
5
5
|
import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
6
|
-
import {
|
|
6
|
+
import { getAllTagPrefixes } from "./generateChangelogs.js";
|
|
7
7
|
import { mergeMonorepoConfig, mergeSinglePackageConfig, readRootPackageVersion } from "./loadConfig.js";
|
|
8
8
|
import { loadValidatedConfig } from "./loadValidatedConfig.js";
|
|
9
9
|
const SYNTHETIC_VALIDATE_TAG = 'validate-only';
|
|
@@ -68,28 +68,21 @@ function formatSummaryLine(errorCount, warningCount) {
|
|
|
68
68
|
function pluralize(count, noun) {
|
|
69
69
|
return count === 1 ? `${count} ${noun}` : `${count} ${noun}s`;
|
|
70
70
|
}
|
|
71
|
-
function defaultBuildEntries(config,
|
|
72
|
-
|
|
73
|
-
if (tagPattern !== undefined) {
|
|
74
|
-
options.tagPattern = tagPattern;
|
|
75
|
-
}
|
|
76
|
-
if (includePaths !== undefined) {
|
|
77
|
-
options.includePaths = includePaths;
|
|
78
|
-
}
|
|
79
|
-
return buildChangelogEntries(config, SYNTHETIC_VALIDATE_TAG, options);
|
|
71
|
+
function defaultBuildEntries(config, options) {
|
|
72
|
+
return buildChangelogEntries(config, SYNTHETIC_VALIDATE_TAG, options).entries;
|
|
80
73
|
}
|
|
81
|
-
function
|
|
82
|
-
const
|
|
74
|
+
function flattenEntriesToItems(entries) {
|
|
75
|
+
const items = [];
|
|
83
76
|
for (const entry of entries) {
|
|
84
77
|
for (const section of entry.sections) {
|
|
85
|
-
for (const
|
|
86
|
-
if (
|
|
87
|
-
|
|
88
|
-
}
|
|
78
|
+
for (const { hash, entry: position } of section.items) {
|
|
79
|
+
if (hash === undefined)
|
|
80
|
+
continue;
|
|
81
|
+
items.push(position === undefined ? { hash } : { hash, entry: position });
|
|
89
82
|
}
|
|
90
83
|
}
|
|
91
84
|
}
|
|
92
|
-
return
|
|
85
|
+
return items;
|
|
93
86
|
}
|
|
94
87
|
function formatConfigProblem(problem) {
|
|
95
88
|
return problem.kind === 'load'
|
|
@@ -98,19 +91,19 @@ function formatConfigProblem(problem) {
|
|
|
98
91
|
}
|
|
99
92
|
function buildSinglePackageInputs(userConfig, buildEntries) {
|
|
100
93
|
const config = mergeSinglePackageConfig(userConfig);
|
|
101
|
-
const
|
|
94
|
+
const items = flattenEntriesToItems(buildEntries(config, { tagPrefixes: [config.tagPrefix] }));
|
|
102
95
|
return {
|
|
103
|
-
project: { filePath: resolveOverridePath('.'),
|
|
96
|
+
project: { filePath: resolveOverridePath('.'), items },
|
|
104
97
|
};
|
|
105
98
|
}
|
|
106
99
|
function buildMonorepoInputs(discoveredPaths, userConfig, buildEntries) {
|
|
107
100
|
const rootPackage = readRootPackageVersion();
|
|
108
101
|
const config = mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage);
|
|
109
102
|
const workspaces = config.workspaces.map((workspace) => {
|
|
110
|
-
const
|
|
103
|
+
const options = { tagPrefixes: getAllTagPrefixes(workspace), paths: workspace.paths };
|
|
111
104
|
return {
|
|
112
105
|
filePath: resolveOverridePath(workspace.workspacePath),
|
|
113
|
-
|
|
106
|
+
items: flattenEntriesToItems(buildEntries(config, options)),
|
|
114
107
|
};
|
|
115
108
|
});
|
|
116
109
|
const project = config.project;
|
|
@@ -118,8 +111,8 @@ function buildMonorepoInputs(discoveredPaths, userConfig, buildEntries) {
|
|
|
118
111
|
filePath: resolveOverridePath('.'),
|
|
119
112
|
};
|
|
120
113
|
if (project !== undefined) {
|
|
121
|
-
const
|
|
122
|
-
projectScope.
|
|
114
|
+
const options = { tagPrefixes: [project.tagPrefix], paths: project.paths };
|
|
115
|
+
projectScope.items = flattenEntriesToItems(buildEntries(config, options));
|
|
123
116
|
}
|
|
124
117
|
return { project: projectScope, workspaces };
|
|
125
118
|
}
|