@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,17 +1,16 @@
|
|
|
1
1
|
import { join as joinPath } from 'node:path';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { assertTaggedBaseline, findUntaggedBaseline } from "./assertTaggedBaseline.js";
|
|
3
|
+
import { attachChangelogDiagnostics } from "./attachChangelogDiagnostics.js";
|
|
4
|
+
import { readReleaseHistory, toReleaseEntries } from "./buildChangelogEntries.js";
|
|
4
5
|
import { mergeChangelogEntriesWithDisk, renderChangelogJson, resolveChangelogJsonPath } from "./changelogJsonFile.js";
|
|
5
6
|
import { applyChangelogOverrides } from "./changelogOverrides.js";
|
|
6
|
-
import { createPolicyViolationCollector } from "./collectPolicyViolations.js";
|
|
7
7
|
import { decideRelease } from "./decideRelease.js";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
|
|
8
|
+
import { DEFAULT_WORK_TYPES } from "./defaults.js";
|
|
9
|
+
import { deriveSectionOrder } from "./deriveReleaseNotesConfig.js";
|
|
11
10
|
import { planReleaseNotesPreviews } from "./planReleaseNotesPreviews.js";
|
|
12
11
|
import { planVersionBump } from "./planVersionBump.js";
|
|
12
|
+
import { planPreservedSections } from "./readChangelogSections.js";
|
|
13
13
|
import { renderChangelogMarkdown } from "./renderChangelogMarkdown.js";
|
|
14
|
-
import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
|
|
15
14
|
const ROOT_PACKAGE_FILE = './package.json';
|
|
16
15
|
const ROOT_CHANGELOG_PATH = '.';
|
|
17
16
|
export function releasePrepareProject(args) {
|
|
@@ -23,56 +22,53 @@ export function releasePrepareProject(args) {
|
|
|
23
22
|
throw new Error('releasePrepareProject called without a configured project block');
|
|
24
23
|
}
|
|
25
24
|
const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
const history = readReleaseHistory(config, { tagPrefixes: [project.tagPrefix], paths: project.paths });
|
|
26
|
+
assertTaggedBaseline([
|
|
27
|
+
findUntaggedBaseline({
|
|
28
|
+
label: 'project',
|
|
29
|
+
packageFiles: [ROOT_PACKAGE_FILE],
|
|
30
|
+
changelogPaths: [ROOT_CHANGELOG_PATH],
|
|
31
|
+
tagPrefixes: [project.tagPrefix],
|
|
32
|
+
previousTag: history.previousTag,
|
|
33
|
+
}, config),
|
|
34
|
+
]);
|
|
35
|
+
const { commits, diagnostics, parsedCommitCount, unparseableCommits } = history.unreleased;
|
|
36
|
+
const tag = history.previousTag;
|
|
30
37
|
const since = tag === undefined ? '(no previous release found)' : `since ${tag}`;
|
|
31
|
-
const collector = createPolicyViolationCollector();
|
|
32
38
|
const decision = decideRelease({
|
|
33
|
-
|
|
39
|
+
naturalBump: history.unreleased.bump,
|
|
40
|
+
commitCount: commits.length,
|
|
34
41
|
force,
|
|
35
42
|
bumpOverride,
|
|
36
|
-
workTypes,
|
|
37
|
-
versionPatterns,
|
|
38
|
-
scopeAliases: config.scopeAliases,
|
|
39
|
-
breakingPolicies,
|
|
40
|
-
onPolicyViolation: collector.onPolicyViolation,
|
|
41
43
|
skipReasons: {
|
|
42
44
|
noCommits: `No commits ${since}. Pass --force to release at patch. Skipping.`,
|
|
43
45
|
noBumpWorthy: `No bump-worthy commits ${since}. Pass --force to release at patch (or --force --bump=X for a different level). Skipping.`,
|
|
44
46
|
},
|
|
45
47
|
});
|
|
46
|
-
const policyViolations = collector.violations.length > 0 ? collector.violations : undefined;
|
|
47
48
|
if (decision.outcome === 'skip') {
|
|
48
49
|
const skipped = {
|
|
49
50
|
status: 'skipped',
|
|
50
51
|
commitCount: commits.length,
|
|
51
|
-
parsedCommitCount
|
|
52
|
+
parsedCommitCount,
|
|
52
53
|
skipReason: decision.skipReason,
|
|
53
54
|
};
|
|
54
55
|
if (tag !== undefined) {
|
|
55
56
|
skipped.previousTag = tag;
|
|
56
57
|
}
|
|
57
|
-
if (
|
|
58
|
-
skipped.unparseableCommits =
|
|
59
|
-
}
|
|
60
|
-
if (policyViolations !== undefined) {
|
|
61
|
-
skipped.policyViolations = policyViolations;
|
|
58
|
+
if (unparseableCommits !== undefined) {
|
|
59
|
+
skipped.unparseableCommits = unparseableCommits;
|
|
62
60
|
}
|
|
61
|
+
attachChangelogDiagnostics(skipped, diagnostics);
|
|
63
62
|
return skipped;
|
|
64
63
|
}
|
|
65
|
-
const { releaseType
|
|
64
|
+
const { releaseType } = decision;
|
|
66
65
|
const bump = planVersionBump([ROOT_PACKAGE_FILE], releaseType);
|
|
67
66
|
writes.push(...bump.writes);
|
|
68
67
|
const newTag = `${project.tagPrefix}${bump.newVersion}`;
|
|
69
68
|
const changelogs = planProjectChangelogs({
|
|
70
69
|
config,
|
|
71
|
-
|
|
72
|
-
commits,
|
|
73
|
-
contributingPaths,
|
|
70
|
+
history,
|
|
74
71
|
newTag,
|
|
75
|
-
newVersion: bump.newVersion,
|
|
76
72
|
rootOverrides,
|
|
77
73
|
overrideWarnings,
|
|
78
74
|
globalMatchedRootKeys,
|
|
@@ -108,18 +104,19 @@ export function releasePrepareProject(args) {
|
|
|
108
104
|
if (previewFiles.length > 0) {
|
|
109
105
|
result.previewFiles = previewFiles;
|
|
110
106
|
}
|
|
107
|
+
if (changelogs.changelogPreservation.length > 0) {
|
|
108
|
+
result.changelogPreservation = changelogs.changelogPreservation;
|
|
109
|
+
}
|
|
111
110
|
if (tag !== undefined) {
|
|
112
111
|
result.previousTag = tag;
|
|
113
112
|
}
|
|
114
113
|
if (unparseableCommits !== undefined) {
|
|
115
114
|
result.unparseableCommits = unparseableCommits;
|
|
116
115
|
}
|
|
117
|
-
if (policyViolations !== undefined) {
|
|
118
|
-
result.policyViolations = policyViolations;
|
|
119
|
-
}
|
|
120
116
|
if (bumpOverride !== undefined) {
|
|
121
117
|
result.bumpOverride = bumpOverride;
|
|
122
118
|
}
|
|
119
|
+
attachChangelogDiagnostics(result, diagnostics);
|
|
123
120
|
return result;
|
|
124
121
|
}
|
|
125
122
|
function resolveOptionalOverrideArgs(args) {
|
|
@@ -131,14 +128,10 @@ function resolveOptionalOverrideArgs(args) {
|
|
|
131
128
|
};
|
|
132
129
|
}
|
|
133
130
|
function planProjectChangelogs(args) {
|
|
134
|
-
const { config,
|
|
135
|
-
const isEmptyRange = commits.length === 0;
|
|
131
|
+
const { config, history, newTag, rootOverrides, overrideWarnings, globalMatchedRootKeys } = args;
|
|
136
132
|
const today = new Date().toISOString().slice(0, 10);
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
? [buildEmptyReleaseEntry(newVersion, today)]
|
|
140
|
-
: buildChangelogEntries(config, newTag, { tagPattern, includePaths: contributingPaths });
|
|
141
|
-
const applied = applyChangelogOverrides(newEntries, rootOverrides);
|
|
133
|
+
const builtEntries = toReleaseEntries(history, newTag, today);
|
|
134
|
+
const applied = applyChangelogOverrides(builtEntries, rootOverrides);
|
|
142
135
|
if (applied.errors.length > 0) {
|
|
143
136
|
throw new Error(`Changelog override application failed:\n - ${applied.errors.join('\n - ')}`);
|
|
144
137
|
}
|
|
@@ -148,9 +141,7 @@ function planProjectChangelogs(args) {
|
|
|
148
141
|
}
|
|
149
142
|
const changelogJsonPath = resolveChangelogJsonPath(config, ROOT_CHANGELOG_PATH);
|
|
150
143
|
const sectionOrder = deriveSectionOrder(config.workTypes ?? { ...DEFAULT_WORK_TYPES });
|
|
151
|
-
const renderEntries =
|
|
152
|
-
? mergeChangelogEntriesWithDisk(changelogJsonPath, applied.entries)
|
|
153
|
-
: applied.entries;
|
|
144
|
+
const renderEntries = mergeChangelogEntriesWithDisk(changelogJsonPath, applied.entries);
|
|
154
145
|
const writes = [];
|
|
155
146
|
const changelogJsonFiles = [];
|
|
156
147
|
if (config.changelogJson.enabled) {
|
|
@@ -158,6 +149,16 @@ function planProjectChangelogs(args) {
|
|
|
158
149
|
changelogJsonFiles.push(changelogJsonPath);
|
|
159
150
|
}
|
|
160
151
|
const changelogFile = joinPath(ROOT_CHANGELOG_PATH, 'CHANGELOG.md');
|
|
161
|
-
|
|
162
|
-
|
|
152
|
+
const preserved = planPreservedSections(changelogFile, renderEntries);
|
|
153
|
+
writes.push({
|
|
154
|
+
path: changelogFile,
|
|
155
|
+
content: renderChangelogMarkdown(renderEntries, { sectionOrder, preservedSections: preserved.sections }),
|
|
156
|
+
});
|
|
157
|
+
return {
|
|
158
|
+
changelogFiles: [changelogFile],
|
|
159
|
+
changelogJsonFiles,
|
|
160
|
+
changelogPreservation: preserved.preservation === undefined ? [] : [preserved.preservation],
|
|
161
|
+
entries: renderEntries,
|
|
162
|
+
writes,
|
|
163
|
+
};
|
|
163
164
|
}
|
|
@@ -1,22 +1,28 @@
|
|
|
1
|
+
import type { VersionedMarkdownSection } from './readChangelogSections.js';
|
|
1
2
|
import type { ChangelogEntry } from './types.js';
|
|
2
3
|
/** Options for `renderChangelogMarkdown`. */
|
|
3
4
|
export interface RenderChangelogMarkdownOptions {
|
|
4
|
-
/** Section titles in canonical order; passed through to `
|
|
5
|
+
/** Section titles in canonical order; passed through to `renderReleaseNotesSingle`. */
|
|
5
6
|
sectionOrder?: string[];
|
|
7
|
+
/**
|
|
8
|
+
* Sections of the existing `CHANGELOG.md` to keep verbatim, interleaved by version among the rendered entries. A
|
|
9
|
+
* section's version must not also be an entry's version.
|
|
10
|
+
*/
|
|
11
|
+
preservedSections?: readonly VersionedMarkdownSection[];
|
|
6
12
|
}
|
|
7
13
|
/**
|
|
8
|
-
*
|
|
14
|
+
* Renders `ChangelogEntry[]` into a complete `CHANGELOG.md`-shaped string.
|
|
9
15
|
*
|
|
10
16
|
* Format:
|
|
11
17
|
* `# Changelog`
|
|
12
18
|
* blank line
|
|
13
19
|
* `All notable changes …`
|
|
14
20
|
* blank line
|
|
15
|
-
* per-entry sections (delegated to `
|
|
21
|
+
* per-entry sections (delegated to `renderReleaseNotesSingle`), with preserved sections interleaved by version
|
|
16
22
|
* blank line
|
|
17
23
|
* footer comment
|
|
18
24
|
*
|
|
19
25
|
* Entries are rendered in the order provided; the caller sorts (typically newest-first via
|
|
20
|
-
* `
|
|
26
|
+
* `compareVersionsDescending`).
|
|
21
27
|
*/
|
|
22
28
|
export declare function renderChangelogMarkdown(entries: ChangelogEntry[], options?: RenderChangelogMarkdownOptions): string;
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { compareVersionsDescending } from "./compareVersions.js";
|
|
2
|
+
import { renderReleaseNotesSingle } from "./renderReleaseNotes.js";
|
|
2
3
|
const CHANGELOG_HEADER = `# Changelog
|
|
3
4
|
|
|
4
5
|
All notable changes to this project will be documented in this file.`;
|
|
5
6
|
const CHANGELOG_FOOTER = `<!-- Generated by release-kit. Do not edit this file. Use .meta/changelog-overrides.json to override entries. -->`;
|
|
6
7
|
export function renderChangelogMarkdown(entries, options) {
|
|
7
|
-
const
|
|
8
|
+
const sections = interleavePreservedSections(entries.map((entry) => ({ version: entry.version, text: renderReleaseNotesSingle(entry, options) })), options?.preservedSections ?? []);
|
|
9
|
+
const body = sections
|
|
10
|
+
.map((part) => part.text)
|
|
11
|
+
.filter((text) => text.length > 0)
|
|
12
|
+
.join('\n');
|
|
8
13
|
const trimmedBody = body.trimEnd();
|
|
9
14
|
const parts = [CHANGELOG_HEADER];
|
|
10
15
|
if (trimmedBody.length > 0) {
|
|
@@ -13,3 +18,23 @@ export function renderChangelogMarkdown(entries, options) {
|
|
|
13
18
|
parts.push(CHANGELOG_FOOTER);
|
|
14
19
|
return parts.join('\n\n') + '\n';
|
|
15
20
|
}
|
|
21
|
+
function interleavePreservedSections(rendered, preserved) {
|
|
22
|
+
const sorted = preserved.toSorted((a, b) => compareVersionsDescending(a.version, b.version));
|
|
23
|
+
const pending = sorted.map(withTrailingNewline);
|
|
24
|
+
const result = [];
|
|
25
|
+
let index = 0;
|
|
26
|
+
for (const part of rendered) {
|
|
27
|
+
let section = pending[index];
|
|
28
|
+
while (section !== undefined && compareVersionsDescending(section.version, part.version) < 0) {
|
|
29
|
+
result.push(section);
|
|
30
|
+
index++;
|
|
31
|
+
section = pending[index];
|
|
32
|
+
}
|
|
33
|
+
result.push(part);
|
|
34
|
+
}
|
|
35
|
+
result.push(...pending.slice(index));
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
function withTrailingNewline(section) {
|
|
39
|
+
return { version: section.version, text: `${section.text}\n` };
|
|
40
|
+
}
|
|
@@ -13,22 +13,14 @@ export interface RenderOptions {
|
|
|
13
13
|
sectionOrder?: string[];
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Creates a predicate that matches sections visible to the given audience.
|
|
17
17
|
*
|
|
18
18
|
* `"all"` matches only sections with `audience: "all"` (public-facing).
|
|
19
19
|
* `"dev"` matches all sections (developers see everything).
|
|
20
20
|
*/
|
|
21
21
|
export declare function matchesAudience(audience: ChangelogAudience): (section: ChangelogSection) => boolean;
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* Output format mirrors the existing CHANGELOG.md style: an H2 version heading followed by
|
|
26
|
-
* H3 section headings with bulleted items.
|
|
23
|
+
* Renders a single `ChangelogEntry` to markdown: an H2 version heading followed by H3 section
|
|
24
|
+
* headings with bulleted items.
|
|
27
25
|
*/
|
|
28
26
|
export declare function renderReleaseNotesSingle(entry: ChangelogEntry, options?: RenderOptions): string;
|
|
29
|
-
/**
|
|
30
|
-
* Render multiple `ChangelogEntry` values to a single markdown document.
|
|
31
|
-
*
|
|
32
|
-
* Entries are rendered in the order provided; the caller is responsible for sorting.
|
|
33
|
-
*/
|
|
34
|
-
export declare function renderReleaseNotesMulti(entries: ChangelogEntry[], options?: RenderOptions): string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CANONICAL_TAXONOMY } from '@williamthorsen/change-grammar';
|
|
2
2
|
function getBreakingPrefix() {
|
|
3
|
-
const { emoji, label } =
|
|
3
|
+
const { emoji, label } = CANONICAL_TAXONOMY.markers.breaking;
|
|
4
4
|
return `${emoji} **${label}:** `;
|
|
5
5
|
}
|
|
6
6
|
function allSections() {
|
|
@@ -62,7 +62,3 @@ function sortSectionsByOrder(sections, order) {
|
|
|
62
62
|
function indentBodyLines(body) {
|
|
63
63
|
return body.split('\n').map((line) => (line.length === 0 ? '' : ` ${line}`));
|
|
64
64
|
}
|
|
65
|
-
export function renderReleaseNotesMulti(entries, options) {
|
|
66
|
-
const parts = entries.map((entry) => renderReleaseNotesSingle(entry, options)).filter((part) => part.length > 0);
|
|
67
|
-
return parts.join('\n');
|
|
68
|
-
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type OutputStyle } from '@williamthorsen/nmr-core';
|
|
2
2
|
import type { PrepareResult } from './types.js';
|
|
3
|
-
/** How the plan was carried out, which decides the
|
|
3
|
+
/** How the plan was carried out, which decides the report's tense. */
|
|
4
4
|
export interface ReportPrepareOptions {
|
|
5
5
|
/** True when the plan was written to disk; false for a dry run. */
|
|
6
6
|
applied: boolean;
|
|
@@ -9,14 +9,13 @@ export interface ReportPrepareOptions {
|
|
|
9
9
|
style: OutputStyle;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Formats a release plan into styled terminal output, without writing it.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* Single-workspace mode (no `name` field) renders flat output; multi-workspace mode
|
|
14
|
+
* Single-workspace mode (without a `name` field) renders flat output; multi-workspace mode
|
|
16
15
|
* renders section headers per workspace, an optional project section, and a tag summary
|
|
17
16
|
* at the end.
|
|
18
17
|
*
|
|
19
|
-
* Whether the plan was applied is a property of the render, not of the plan:
|
|
18
|
+
* Whether the plan was applied is a property of the render, not of the plan: The same plan
|
|
20
19
|
* produces the dry-run report and the applied report.
|
|
21
20
|
*/
|
|
22
21
|
export declare function reportPrepare(plan: PrepareResult, options: ReportPrepareOptions): string;
|
|
@@ -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,9 +48,10 @@ 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
|
-
formatChangelogFiles(lines, workspace, dryRun);
|
|
54
|
+
formatChangelogFiles(lines, workspace, dryRun, style);
|
|
55
55
|
formatPreviewFiles(lines, workspace, dryRun);
|
|
56
56
|
formatFormatCommand(lines, result, options);
|
|
57
57
|
formatWarnings(lines, result, style);
|
|
@@ -77,7 +77,7 @@ function formatMultiWorkspace(result, options) {
|
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
else {
|
|
80
|
-
lines.push(`\n${formatStatusLine(style, 'skipped', '
|
|
80
|
+
lines.push(`\n${formatStatusLine(style, 'skipped', 'The run did not find release-worthy changes in any workspace.')}`);
|
|
81
81
|
}
|
|
82
82
|
return lines.join('\n');
|
|
83
83
|
}
|
|
@@ -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) {
|
|
@@ -108,32 +111,10 @@ function formatProjectSection(lines, project, dryRun, style) {
|
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
lines.push(dim(' Generating changelogs...'));
|
|
111
|
-
|
|
112
|
-
if (dryRun) {
|
|
113
|
-
lines.push(dim(` [dry-run] Would run: ${CLIFF_DRY_RUN_COMMAND} --output ${file}`));
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
lines.push(dim(` Generating changelog: ${file}`));
|
|
117
|
-
}
|
|
118
|
-
}
|
|
114
|
+
formatChangelogFiles(lines, project, dryRun, style, ' ');
|
|
119
115
|
formatPreviewFiles(lines, project, dryRun, ' ');
|
|
120
116
|
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(tag))}`);
|
|
121
117
|
}
|
|
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
118
|
function formatWorkspaceSection(lines, workspace, dryRun, style) {
|
|
138
119
|
if (workspace.name !== undefined) {
|
|
139
120
|
lines.push(`\n${sectionHeader(workspace.name)}`);
|
|
@@ -141,26 +122,30 @@ function formatWorkspaceSection(lines, workspace, dryRun, style) {
|
|
|
141
122
|
const since = workspace.previousTag === undefined ? '(no previous release found)' : `since ${workspace.previousTag}`;
|
|
142
123
|
lines.push(dim(` Found ${workspace.commitCount} commits ${since}`));
|
|
143
124
|
if (workspace.status === 'skipped') {
|
|
125
|
+
formatUnparseableWarning(lines, workspace, style, ' ');
|
|
126
|
+
formatPolicyViolations(lines, workspace.policyViolations, style, ' ');
|
|
127
|
+
formatChangeRecordWarnings(lines, workspace, style, ' ');
|
|
144
128
|
lines.push(` ${formatStatusLine(style, 'skipped', workspace.skipReason)}`);
|
|
145
129
|
return;
|
|
146
130
|
}
|
|
147
131
|
const { propagatedFrom } = workspace;
|
|
148
|
-
const isPropagatedOnly =
|
|
132
|
+
const isPropagatedOnly = workspace.propagatedOnly === true;
|
|
149
133
|
formatCommitSummary(lines, workspace, propagatedFrom, isPropagatedOnly);
|
|
150
134
|
formatUnparseableWarning(lines, workspace, style, ' ');
|
|
151
135
|
formatPolicyViolations(lines, workspace.policyViolations, style, ' ');
|
|
136
|
+
formatChangeRecordWarnings(lines, workspace, style, ' ');
|
|
152
137
|
formatBumpLabels(lines, workspace, isPropagatedOnly);
|
|
153
138
|
formatVersionLine(lines, workspace, propagatedFrom, isPropagatedOnly, style);
|
|
154
139
|
formatBumpFiles(lines, workspace, dryRun, ' ');
|
|
155
140
|
lines.push(dim(' Generating changelogs...'));
|
|
156
|
-
formatChangelogFiles(lines, workspace, dryRun, ' ');
|
|
141
|
+
formatChangelogFiles(lines, workspace, dryRun, style, ' ');
|
|
157
142
|
formatPreviewFiles(lines, workspace, dryRun, ' ');
|
|
158
143
|
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(workspace.tag))}`);
|
|
159
144
|
}
|
|
160
145
|
function formatCommitSummary(lines, workspace, propagatedFrom, isPropagatedOnly) {
|
|
161
146
|
if (isPropagatedOnly && propagatedFrom !== undefined) {
|
|
162
147
|
const depNames = propagatedFrom.map((p) => p.packageName).join(', ');
|
|
163
|
-
lines.push(dim(`
|
|
148
|
+
lines.push(dim(` Bumped via dependency: ${depNames}`));
|
|
164
149
|
}
|
|
165
150
|
else if (workspace.parsedCommitCount !== undefined && workspace.parsedCommitCount > 0) {
|
|
166
151
|
lines.push(dim(` Parsed ${workspace.parsedCommitCount} typed commits`));
|
|
@@ -200,14 +185,26 @@ function formatBumpFiles(lines, workspace, dryRun, indent = '') {
|
|
|
200
185
|
}
|
|
201
186
|
}
|
|
202
187
|
}
|
|
203
|
-
function formatChangelogFiles(lines,
|
|
204
|
-
for (const file of
|
|
188
|
+
function formatChangelogFiles(lines, release, dryRun, style, indent = '') {
|
|
189
|
+
for (const file of release.changelogFiles) {
|
|
205
190
|
if (dryRun) {
|
|
206
|
-
lines.push(dim(`${indent} [dry-run] Would
|
|
191
|
+
lines.push(dim(`${indent} [dry-run] Would generate changelog: ${file}`));
|
|
207
192
|
}
|
|
208
193
|
else {
|
|
209
194
|
lines.push(dim(`${indent} Generating changelog: ${file}`));
|
|
210
195
|
}
|
|
196
|
+
const preservation = release.changelogPreservation?.find((candidate) => candidate.file === file);
|
|
197
|
+
if (preservation === undefined) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (preservation.preservedVersions.length > 0) {
|
|
201
|
+
lines.push(dim(`${indent} Kept from the existing file: ${preservation.preservedVersions.join(', ')}`));
|
|
202
|
+
}
|
|
203
|
+
const dropped = preservation.droppedUnversionedHeadings;
|
|
204
|
+
if (dropped.length > 0) {
|
|
205
|
+
const message = `Dropped ${dropped.length} section${dropped.length === 1 ? '' : 's'} without a version: ${dropped.join(', ')}`;
|
|
206
|
+
lines.push(`${indent} ${formatStatusLine(style, 'warning', message)}`);
|
|
207
|
+
}
|
|
211
208
|
}
|
|
212
209
|
}
|
|
213
210
|
function formatPreviewFiles(lines, release, dryRun, indent = '') {
|
|
@@ -221,19 +218,17 @@ function formatPreviewFiles(lines, release, dryRun, indent = '') {
|
|
|
221
218
|
}
|
|
222
219
|
}
|
|
223
220
|
}
|
|
224
|
-
function formatUnparseableWarning(lines,
|
|
225
|
-
const unparseable =
|
|
221
|
+
function formatUnparseableWarning(lines, release, style, indent = '') {
|
|
222
|
+
const unparseable = release.unparseableCommits;
|
|
226
223
|
if (unparseable === undefined || unparseable.length === 0) {
|
|
227
224
|
return;
|
|
228
225
|
}
|
|
229
226
|
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}`)}`);
|
|
227
|
+
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${count} commit${count === 1 ? '' : 's'} could not be parsed`)}`);
|
|
233
228
|
for (const commit of unparseable) {
|
|
234
229
|
const shortHash = commit.hash.slice(0, 7);
|
|
235
|
-
const
|
|
236
|
-
lines.push(`${indent} · ${shortHash} ${
|
|
230
|
+
const truncatedSubject = truncateSubject(commit.subject);
|
|
231
|
+
lines.push(`${indent} · ${shortHash} ${truncatedSubject}`);
|
|
237
232
|
}
|
|
238
233
|
}
|
|
239
234
|
function formatPolicyViolations(lines, violations, style, indent = '') {
|
|
@@ -246,7 +241,37 @@ function formatPolicyViolations(lines, violations, style, indent = '') {
|
|
|
246
241
|
const shortHash = violation.commitHash.slice(0, 7);
|
|
247
242
|
const subject = violation.commitSubject;
|
|
248
243
|
const truncatedSubject = truncateSubject(subject);
|
|
249
|
-
|
|
244
|
+
const location = violation.surface === 'entry' ? `entry ${violation.entryPosition ?? '?'}` : `${violation.surface} surface`;
|
|
245
|
+
lines.push(`${indent} · ${shortHash} '${truncatedSubject}': type '${violation.type}' at ${location}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function formatChangeRecordWarnings(lines, release, style, indent = '') {
|
|
249
|
+
const { malformedBlocks = [], undeclaredEntryTypes = [], unroutedEntryScopes = [] } = release;
|
|
250
|
+
if (malformedBlocks.length > 0) {
|
|
251
|
+
const count = malformedBlocks.length;
|
|
252
|
+
const header = `${count} change-record block${count === 1 ? '' : 's'} could not be read (item taken from the title)`;
|
|
253
|
+
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
|
|
254
|
+
for (const block of malformedBlocks) {
|
|
255
|
+
lines.push(`${indent} · ${block.commitHash.slice(0, 7)} '${truncateSubject(block.commitSubject)}': ${block.reason}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (undeclaredEntryTypes.length > 0) {
|
|
259
|
+
const count = undeclaredEntryTypes.length;
|
|
260
|
+
const header = `${count} change-record entr${count === 1 ? 'y has an' : 'ies have'} undeclared type${count === 1 ? '' : 's'} (no item)`;
|
|
261
|
+
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
|
|
262
|
+
for (const entry of undeclaredEntryTypes) {
|
|
263
|
+
const shortHash = entry.commitHash.slice(0, 7);
|
|
264
|
+
lines.push(`${indent} · ${shortHash} '${truncateSubject(entry.commitSubject)}': type '${entry.type}' at entry ${entry.entryPosition}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (unroutedEntryScopes.length > 0) {
|
|
268
|
+
const count = unroutedEntryScopes.length;
|
|
269
|
+
const header = `${count} change-record entry scope${count === 1 ? ' does not match' : 's do not match'} any workspace in the commit's window (no item there)`;
|
|
270
|
+
lines.push(`${indent} ${formatStatusLine(style, 'warning', `${header}:`)}`);
|
|
271
|
+
for (const finding of unroutedEntryScopes) {
|
|
272
|
+
const shortHash = finding.commitHash.slice(0, 7);
|
|
273
|
+
lines.push(`${indent} · ${shortHash} '${truncateSubject(finding.commitSubject)}': scope '${finding.scope}' at entry ${finding.entryPosition}`);
|
|
274
|
+
}
|
|
250
275
|
}
|
|
251
276
|
}
|
|
252
277
|
function formatPropagationSuffix(propagatedFrom) {
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { type ResolvedTag } from './resolveReleaseTags.js';
|
|
2
|
+
import type { ReleaseKitConfig } from './types.js';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Discovers workspaces, resolves release tags from HEAD, validates `--tags` names against the full resolved tag
|
|
5
|
+
* names (e.g., `nmr-core-v1.3.0`), and returns the filtered tag list. Exits with an error message on any validation
|
|
6
|
+
* failure, including a workspace whose `package.json` does not have a `name` field, a config that
|
|
7
|
+
* `mergeMonorepoConfig` rejects, and a workspace that does not resolve to any package, which is not single-package
|
|
8
|
+
* mode.
|
|
8
9
|
*
|
|
9
|
-
* In
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* In monorepo mode the workspace list is `userConfig` merged over the discovered workspaces, as `prepare` builds it.
|
|
11
|
+
* A tag on HEAD whose workspace the config excludes still counts as a tag on HEAD, so `--tags` may name it and a
|
|
12
|
+
* HEAD whose tags are all excluded returns an empty list rather than failing. Each excluded tag that the run
|
|
13
|
+
* selects, every one without `--tags` or the named ones with it, is dropped with a warning.
|
|
14
|
+
* Single-package mode ignores `userConfig`.
|
|
15
|
+
*
|
|
16
|
+
* In both modes the workspace configs come from `deriveWorkspaceConfig`, so that `WorkspaceConfig.isPublishable`
|
|
17
|
+
* (read from `package.json#private`) propagates onto each `ResolvedTag`.
|
|
14
18
|
*/
|
|
15
|
-
export declare function resolveCommandTags(tags: string[] | undefined): ResolvedTag[];
|
|
19
|
+
export declare function resolveCommandTags(tags: string[] | undefined, userConfig: ReleaseKitConfig | undefined): ResolvedTag[];
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { basename } from 'node:path';
|
|
1
2
|
import { reportError } from '@williamthorsen/nmr-core';
|
|
2
3
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
3
4
|
import { deriveWorkspaceConfig } from "./deriveWorkspaceConfig.js";
|
|
4
5
|
import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
|
|
6
|
+
import { formatExcludedSkip } from "./formatExcludedSkip.js";
|
|
7
|
+
import { mergeMonorepoConfig, readRootPackageVersion } from "./loadConfig.js";
|
|
5
8
|
import { resolveReleaseTags } from "./resolveReleaseTags.js";
|
|
6
|
-
export function resolveCommandTags(tags) {
|
|
9
|
+
export function resolveCommandTags(tags, userConfig) {
|
|
7
10
|
let workspace;
|
|
8
11
|
try {
|
|
9
12
|
workspace = discoverWorkspaces();
|
|
@@ -18,12 +21,13 @@ export function resolveCommandTags(tags) {
|
|
|
18
21
|
}
|
|
19
22
|
let workspaces;
|
|
20
23
|
let singleWorkspace;
|
|
24
|
+
const excludedDirs = workspace.kind === 'packages' ? collectExcludedDirs(userConfig) : new Set();
|
|
21
25
|
try {
|
|
22
26
|
if (workspace.kind === 'single-package') {
|
|
23
27
|
singleWorkspace = deriveWorkspaceConfig('.');
|
|
24
28
|
}
|
|
25
29
|
else {
|
|
26
|
-
workspaces = workspace.packageDirs
|
|
30
|
+
workspaces = buildMonorepoWorkspaces(workspace.packageDirs, userConfig, excludedDirs);
|
|
27
31
|
}
|
|
28
32
|
}
|
|
29
33
|
catch (error) {
|
|
@@ -38,10 +42,10 @@ export function resolveCommandTags(tags) {
|
|
|
38
42
|
resolvedTags = resolveReleaseTags({ singleWorkspace });
|
|
39
43
|
}
|
|
40
44
|
else {
|
|
41
|
-
throw new Error('resolveCommandTags: invariant violated
|
|
45
|
+
throw new Error('resolveCommandTags: invariant violated; neither workspaces nor singleWorkspace was derived');
|
|
42
46
|
}
|
|
43
47
|
if (resolvedTags.length === 0) {
|
|
44
|
-
reportError('
|
|
48
|
+
reportError('HEAD does not have any release tags. Create tags with `release-kit tag` first.');
|
|
45
49
|
process.exit(1);
|
|
46
50
|
}
|
|
47
51
|
if (tags !== undefined) {
|
|
@@ -55,5 +59,31 @@ export function resolveCommandTags(tags) {
|
|
|
55
59
|
}
|
|
56
60
|
resolvedTags = resolvedTags.filter((t) => tags.includes(t.tag));
|
|
57
61
|
}
|
|
58
|
-
return resolvedTags;
|
|
62
|
+
return dropExcludedTags(resolvedTags, excludedDirs);
|
|
63
|
+
}
|
|
64
|
+
function buildMonorepoWorkspaces(packageDirs, userConfig, excludedDirs) {
|
|
65
|
+
const { workspaces } = mergeMonorepoConfig(packageDirs, userConfig, readRootPackageVersion());
|
|
66
|
+
if (excludedDirs.size === 0) {
|
|
67
|
+
return workspaces;
|
|
68
|
+
}
|
|
69
|
+
const excluded = packageDirs
|
|
70
|
+
.filter((packageDir) => excludedDirs.has(basename(packageDir)))
|
|
71
|
+
.map((packageDir) => deriveWorkspaceConfig(packageDir));
|
|
72
|
+
return [...workspaces, ...excluded];
|
|
73
|
+
}
|
|
74
|
+
function collectExcludedDirs(userConfig) {
|
|
75
|
+
const overrides = userConfig?.workspaces ?? [];
|
|
76
|
+
return new Set(overrides.filter((override) => override.shouldExclude === true).map((override) => override.dir));
|
|
77
|
+
}
|
|
78
|
+
function dropExcludedTags(resolvedTags, excludedDirs) {
|
|
79
|
+
const retained = [];
|
|
80
|
+
for (const resolvedTag of resolvedTags) {
|
|
81
|
+
if (excludedDirs.has(resolvedTag.dir)) {
|
|
82
|
+
console.warn(formatExcludedSkip(resolvedTag));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
retained.push(resolvedTag);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return retained;
|
|
59
89
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves a `--config` value against the directory from which `release-kit` was invoked, which differs from the
|
|
3
|
+
* working directory once the process has moved to the repo root.
|
|
4
|
+
*
|
|
5
|
+
* Returns `undefined` when the flag is absent, so that `loadConfig` falls back to the default path under the root.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveConfigFlag(config: string | undefined, invocationDir: string): string | undefined;
|