@williamthorsen/release-kit 15.0.2 → 16.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 +88 -0
- package/.readyup/kits/default.js +14 -14
- package/.readyup/kits/npm-auto-publish.js +5 -5
- package/.readyup/manifest.json +14 -14
- package/CHANGELOG.md +28 -0
- package/README.md +3 -3
- package/dist/esm/applyChangelogOverrides.js +6 -6
- package/dist/esm/assertCleanWorkingTree.js +2 -2
- package/dist/esm/assertReleasesOnNpm.js +11 -11
- package/dist/esm/assertTaggedBaseline.js +5 -5
- package/dist/esm/bin/release-kit.js +16 -16
- package/dist/esm/buildChangelogEntries.d.ts +19 -19
- package/dist/esm/buildChangelogEntries.js +40 -36
- package/dist/esm/buildChangelogItem.js +24 -24
- package/dist/esm/buildDependencyGraph.js +23 -23
- package/dist/esm/buildReleasedResult.js +21 -19
- package/dist/esm/buildSyntheticChangelogEntry.js +2 -2
- package/dist/esm/bumpVersion.js +8 -8
- package/dist/esm/changelogArchive.d.ts +1 -1
- package/dist/esm/changelogArchive.js +2 -2
- package/dist/esm/changelogJsonFile.js +5 -5
- package/dist/esm/changelogJsonUtils.js +3 -3
- package/dist/esm/changelogOverrides.d.ts +5 -5
- package/dist/esm/changelogOverrides.js +23 -23
- package/dist/esm/classifyChangelogCommit.js +4 -4
- package/dist/esm/commitCommand.js +7 -7
- package/dist/esm/compareVersions.js +13 -13
- package/dist/esm/configSchema.d.ts +2 -2
- package/dist/esm/configSchema.js +1 -1
- package/dist/esm/createGithubRelease.d.ts +4 -4
- package/dist/esm/createGithubRelease.js +10 -10
- package/dist/esm/createGithubReleaseCommand.js +9 -9
- package/dist/esm/createTags.d.ts +2 -2
- package/dist/esm/createTags.js +12 -12
- package/dist/esm/decideDirectReleases.d.ts +5 -5
- package/dist/esm/decideDirectReleases.js +25 -25
- package/dist/esm/decideRelease.d.ts +1 -1
- package/dist/esm/decideRelease.js +2 -2
- package/dist/esm/deriveDirectoryConfig.d.ts +1 -1
- package/dist/esm/deriveDirectoryConfig.js +18 -18
- package/dist/esm/deriveWorkspaceConfig.js +4 -4
- package/dist/esm/detectPackageManager.js +5 -5
- package/dist/esm/detectUndeclaredTagPrefixes.js +8 -8
- package/dist/esm/enumerateReleaseWindows.d.ts +1 -1
- package/dist/esm/enumerateReleaseWindows.js +9 -9
- package/dist/esm/findTransitiveDependents.js +5 -5
- package/dist/esm/findUnroutedEntryScopes.js +11 -11
- package/dist/esm/format.d.ts +1 -1
- package/dist/esm/format.js +1 -1
- package/dist/esm/formatPrepareFiles.d.ts +3 -3
- package/dist/esm/formatPrepareFiles.js +10 -10
- package/dist/esm/formatPrepareWarnings.js +6 -6
- package/dist/esm/hasPrettierConfig.js +2 -2
- package/dist/esm/init/checks.js +7 -5
- package/dist/esm/init/detectRepoType.js +3 -3
- package/dist/esm/init/initCommand.d.ts +4 -4
- package/dist/esm/init/initCommand.js +7 -7
- package/dist/esm/init/parseJsonRecord.d.ts +1 -1
- package/dist/esm/init/parseJsonRecord.js +4 -4
- package/dist/esm/init/scaffold.d.ts +4 -4
- package/dist/esm/init/scaffold.js +18 -9
- package/dist/esm/init/templates.d.ts +10 -10
- package/dist/esm/init/templates.js +5 -5
- package/dist/esm/init/updateTemplatesCommand.d.ts +2 -2
- package/dist/esm/init/updateTemplatesCommand.js +16 -14
- package/dist/esm/injectReleaseNotesIntoReadme.d.ts +1 -1
- package/dist/esm/injectReleaseNotesIntoReadme.js +6 -6
- package/dist/esm/injectSection.js +8 -8
- package/dist/esm/loadChangelogOverrides.d.ts +1 -1
- package/dist/esm/loadChangelogOverrides.js +9 -9
- package/dist/esm/loadConfig.d.ts +3 -3
- package/dist/esm/loadConfig.js +10 -10
- package/dist/esm/loadValidatedConfig.js +4 -4
- package/dist/esm/mergeConfig.d.ts +1 -1
- package/dist/esm/mergeConfig.js +27 -27
- package/dist/esm/monorepoConfigAssertions.js +15 -15
- package/dist/esm/npmRegistry.js +4 -4
- package/dist/esm/packsPath.js +3 -3
- package/dist/esm/parseChangeRecordBlock.js +33 -33
- package/dist/esm/parseCommitMessage.js +5 -5
- package/dist/esm/planChangelogFiles.js +8 -8
- package/dist/esm/planReleaseNotesPreviews.js +10 -10
- package/dist/esm/planVersionBump.js +24 -16
- package/dist/esm/prepareArgs.d.ts +9 -9
- package/dist/esm/prepareArgs.js +11 -11
- package/dist/esm/prepareCommand.js +23 -23
- package/dist/esm/prepareComposite.d.ts +4 -4
- package/dist/esm/prepareComposite.js +16 -16
- package/dist/esm/{releasePrepareTargets.d.ts → prepareReleaseTargets.d.ts} +9 -9
- package/dist/esm/{releasePrepareTargets.js → prepareReleaseTargets.js} +40 -40
- package/dist/esm/prepareRunContext.d.ts +1 -1
- package/dist/esm/prepareRunContext.js +5 -5
- package/dist/esm/prepareTarget.d.ts +3 -3
- package/dist/esm/prepareTarget.js +41 -39
- package/dist/esm/previewTagPrefixes.js +21 -21
- package/dist/esm/propagateBumps.js +8 -8
- package/dist/esm/publish.d.ts +2 -2
- package/dist/esm/publish.js +5 -5
- package/dist/esm/publishCommand.js +20 -20
- package/dist/esm/pushCommand.js +8 -8
- package/dist/esm/pushRelease.d.ts +1 -1
- package/dist/esm/pushRelease.js +4 -4
- package/dist/esm/readChangelogSections.d.ts +1 -1
- package/dist/esm/readChangelogSections.js +16 -16
- package/dist/esm/releasePlan.js +26 -26
- package/dist/esm/renderChangelogMarkdown.d.ts +4 -4
- package/dist/esm/renderChangelogMarkdown.js +18 -15
- package/dist/esm/renderReleaseNotes.d.ts +1 -1
- package/dist/esm/renderReleaseNotes.js +16 -16
- package/dist/esm/reportPrepare.js +34 -34
- package/dist/esm/resolveCommandTags.js +9 -9
- package/dist/esm/resolveReleaseDeclaration.d.ts +2 -2
- package/dist/esm/resolveReleaseDeclaration.js +22 -22
- package/dist/esm/resolveReleaseTags.js +19 -17
- package/dist/esm/resolveReleaseTargets.js +2 -2
- package/dist/esm/showTagPrefixesCommand.js +4 -4
- package/dist/esm/sync-labels/generateCommand.d.ts +3 -3
- package/dist/esm/sync-labels/generateCommand.js +10 -10
- package/dist/esm/sync-labels/initCommand.d.ts +3 -3
- package/dist/esm/sync-labels/initCommand.js +23 -15
- package/dist/esm/sync-labels/presets.js +3 -3
- package/dist/esm/sync-labels/resolveLabels.js +13 -13
- package/dist/esm/sync-labels/syncCommand.js +2 -2
- package/dist/esm/sync-labels/templates.d.ts +4 -4
- package/dist/esm/sync-labels/templates.js +13 -40
- package/dist/esm/tagCommand.js +4 -4
- package/dist/esm/topologicalSort.d.ts +2 -2
- package/dist/esm/topologicalSort.js +7 -7
- package/dist/esm/types.d.ts +3 -3
- package/dist/esm/validateAllChangelogOverrides.js +9 -9
- package/dist/esm/validateConfig.d.ts +1 -1
- package/dist/esm/validateConfig.js +60 -50
- package/dist/esm/validateOverridesCommand.js +2 -2
- package/docs/changelogs.md +5 -5
- package/docs/configuration.md +1 -1
- package/docs/work-types.md +1 -1
- package/package.json +9 -8
- package/templates/sync-labels.yaml.template +36 -0
package/dist/esm/releasePlan.js
CHANGED
|
@@ -4,17 +4,17 @@ import { deleteFileIfExists } from "./deleteFileIfExists.js";
|
|
|
4
4
|
import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
|
|
5
5
|
const HANDOFF_FILES = new Set([RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE]);
|
|
6
6
|
export function applyReleasePlan(plan) {
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
for (const [index, step] of
|
|
7
|
+
const orderedSteps = orderPlannedSteps(plan);
|
|
8
|
+
const appliedWrites = [];
|
|
9
|
+
for (const [index, step] of orderedSteps.entries()) {
|
|
10
10
|
const outcome = applyStep(step);
|
|
11
11
|
if (outcome.outcome === 'failed') {
|
|
12
|
-
const
|
|
13
|
-
throw new Error(describeApplyFailure({ failedStep: step, error: outcome.error,
|
|
12
|
+
const unwrittenPaths = orderedSteps.slice(index).map((pending) => pending.path);
|
|
13
|
+
throw new Error(describeApplyFailure({ failedStep: step, error: outcome.error, appliedWrites, unwrittenPaths }));
|
|
14
14
|
}
|
|
15
|
-
|
|
15
|
+
appliedWrites.push({ path: step.path, created: outcome.outcome === 'created' });
|
|
16
16
|
}
|
|
17
|
-
return
|
|
17
|
+
return appliedWrites.map((write) => write.path);
|
|
18
18
|
}
|
|
19
19
|
function applyStep(step) {
|
|
20
20
|
if (step.kind === 'remove') {
|
|
@@ -26,50 +26,50 @@ function applyStep(step) {
|
|
|
26
26
|
return { outcome: 'failed', error: describeError(error) };
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
const result = writeFileWithCheck(step.path, step.content, {
|
|
29
|
+
const result = writeFileWithCheck(step.path, step.content, { isDryRun: false, shouldOverwrite: true });
|
|
30
30
|
if (result.outcome === 'failed') {
|
|
31
31
|
return { outcome: 'failed', ...(result.error !== undefined && { error: result.error }) };
|
|
32
32
|
}
|
|
33
33
|
return { outcome: result.outcome === 'created' ? 'created' : 'applied' };
|
|
34
34
|
}
|
|
35
35
|
function orderPlannedSteps(plan) {
|
|
36
|
-
const
|
|
36
|
+
const orderedSteps = [
|
|
37
37
|
...plan.writes.map((write) => ({ kind: 'write', ...write })),
|
|
38
38
|
...plan.removals.map((path) => ({ kind: 'remove', path })),
|
|
39
39
|
];
|
|
40
40
|
if (plan.summary.length > 0) {
|
|
41
|
-
|
|
41
|
+
orderedSteps.push({ kind: 'write', path: RELEASE_SUMMARY_FILE, content: plan.summary });
|
|
42
42
|
}
|
|
43
43
|
if (plan.tags.length > 0) {
|
|
44
|
-
|
|
44
|
+
orderedSteps.push({ kind: 'write', path: RELEASE_TAGS_FILE, content: plan.tags.join('\n') });
|
|
45
45
|
}
|
|
46
|
-
return
|
|
46
|
+
return orderedSteps;
|
|
47
47
|
}
|
|
48
48
|
function describeApplyFailure(args) {
|
|
49
|
-
const { failedStep, error,
|
|
50
|
-
const
|
|
49
|
+
const { failedStep, error, appliedWrites, unwrittenPaths } = args;
|
|
50
|
+
const totalCount = appliedWrites.length + unwrittenPaths.length;
|
|
51
51
|
const verb = failedStep.kind === 'remove' ? 'remove' : 'write';
|
|
52
52
|
const lines = [`Failed to ${verb} ${failedStep.path}: ${error ?? 'unknown error'}`, ''];
|
|
53
|
-
if (
|
|
54
|
-
lines.push(`The release was not applied. None of its ${
|
|
53
|
+
if (appliedWrites.length === 0) {
|
|
54
|
+
lines.push(`The release was not applied. None of its ${totalCount} files were written.`);
|
|
55
55
|
return lines.join('\n');
|
|
56
56
|
}
|
|
57
|
-
lines.push(`The release was partially applied. ${
|
|
57
|
+
lines.push(`The release was partially applied. ${appliedWrites.length} of ${totalCount} files were written:`, ...appliedWrites.map((write) => ` ${write.path}`), '', 'Not written:', ...unwrittenPaths.map((path) => ` ${path}`), ...describeRecovery(appliedWrites));
|
|
58
58
|
return lines.join('\n');
|
|
59
59
|
}
|
|
60
|
-
function describeRecovery(
|
|
61
|
-
const releaseWrites =
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
60
|
+
function describeRecovery(appliedWrites) {
|
|
61
|
+
const releaseWrites = appliedWrites.filter((write) => !HANDOFF_FILES.has(write.path));
|
|
62
|
+
const replacedPaths = releaseWrites.filter((write) => !write.created).map((write) => write.path);
|
|
63
|
+
const createdPaths = releaseWrites.filter((write) => write.created).map((write) => write.path);
|
|
64
|
+
if (replacedPaths.length === 0 && createdPaths.length === 0) {
|
|
65
65
|
return [];
|
|
66
66
|
}
|
|
67
67
|
const lines = ['', 'Undo the written release files before re-running `release-kit prepare`:'];
|
|
68
|
-
if (
|
|
69
|
-
lines.push(` git restore ${
|
|
68
|
+
if (replacedPaths.length > 0) {
|
|
69
|
+
lines.push(` git restore ${replacedPaths.join(' ')}`);
|
|
70
70
|
}
|
|
71
|
-
if (
|
|
72
|
-
lines.push(` rm ${
|
|
71
|
+
if (createdPaths.length > 0) {
|
|
72
|
+
lines.push(` rm ${createdPaths.join(' ')}`);
|
|
73
73
|
}
|
|
74
74
|
return lines;
|
|
75
75
|
}
|
|
@@ -13,7 +13,7 @@ export interface RenderChangelogMarkdownOptions {
|
|
|
13
13
|
/** Options for `renderChangelogFiles`. */
|
|
14
14
|
export interface RenderChangelogFilesOptions extends RenderChangelogMarkdownOptions {
|
|
15
15
|
/** How many of the newest sections the main file keeps; every section when omitted. Must be at least 1. */
|
|
16
|
-
|
|
16
|
+
keptVersionCount?: number;
|
|
17
17
|
}
|
|
18
18
|
/** A rendered archive file: the sections of one major version that the main file does not keep. */
|
|
19
19
|
export interface RenderedChangelogArchive {
|
|
@@ -28,9 +28,9 @@ export interface RenderedChangelogFiles {
|
|
|
28
28
|
/**
|
|
29
29
|
* Renders entries and preserved sections into `CHANGELOG.md` and its per-major archive files.
|
|
30
30
|
*
|
|
31
|
-
* The main file keeps the newest `
|
|
32
|
-
* from a paragraph below its header. Each older section goes to the archive file of its
|
|
33
|
-
* has the same header and footer.
|
|
31
|
+
* The main file keeps as many of the newest sections as `keptVersionCount` names, counting preserved sections, and
|
|
32
|
+
* links to each archive file from a paragraph below its header. Each older section goes to the archive file of its
|
|
33
|
+
* major version. Every file has the same header and footer.
|
|
34
34
|
*/
|
|
35
35
|
export declare function renderChangelogFiles(entries: ChangelogEntry[], options?: RenderChangelogFilesOptions): RenderedChangelogFiles;
|
|
36
36
|
/**
|
|
@@ -8,19 +8,25 @@ All notable changes to this project will be documented in this file.`;
|
|
|
8
8
|
const CHANGELOG_FOOTER = `<!-- Generated by release-kit. Do not edit this file. Use .meta/changelog-overrides.json to override entries. -->`;
|
|
9
9
|
export function renderChangelogFiles(entries, options) {
|
|
10
10
|
const sections = interleavePreservedSections(entries.map((entry) => ({ version: entry.version, text: renderReleaseNotesSingle(entry, options) })), options?.preservedSections ?? []).filter((section) => section.text.length > 0);
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
11
|
+
const keptVersionCount = options?.keptVersionCount;
|
|
12
|
+
if (keptVersionCount === undefined || sections.length <= keptVersionCount) {
|
|
13
13
|
return { main: renderFile(sections), archives: [] };
|
|
14
14
|
}
|
|
15
|
-
const
|
|
15
|
+
const archivedGroups = groupByMajor(sections.slice(keptVersionCount));
|
|
16
16
|
return {
|
|
17
|
-
main: renderFile(sections.slice(0,
|
|
18
|
-
archives:
|
|
17
|
+
main: renderFile(sections.slice(0, keptVersionCount), describeArchivePointer(archivedGroups.map(({ major }) => major))),
|
|
18
|
+
archives: archivedGroups.map(({ major, sections: majorSections }) => ({
|
|
19
|
+
major,
|
|
20
|
+
content: renderFile(majorSections),
|
|
21
|
+
})),
|
|
19
22
|
};
|
|
20
23
|
}
|
|
21
24
|
export function renderChangelogMarkdown(entries, options) {
|
|
22
25
|
return renderChangelogFiles(entries, options).main;
|
|
23
26
|
}
|
|
27
|
+
function appendTrailingNewline(section) {
|
|
28
|
+
return { version: section.version, text: `${section.text}\n` };
|
|
29
|
+
}
|
|
24
30
|
function describeArchivePointer(majors) {
|
|
25
31
|
const links = majors.map((major) => `[v${major}](${describeArchivePath(major)})`);
|
|
26
32
|
return `Earlier versions: ${links.join(', ')}.`;
|
|
@@ -39,21 +45,21 @@ function groupByMajor(sections) {
|
|
|
39
45
|
}
|
|
40
46
|
return Array.from(groups, ([major, majorSections]) => ({ major, sections: majorSections })).toSorted((a, b) => b.major - a.major);
|
|
41
47
|
}
|
|
42
|
-
function interleavePreservedSections(
|
|
43
|
-
const
|
|
44
|
-
const
|
|
48
|
+
function interleavePreservedSections(renderedSections, preservedSections) {
|
|
49
|
+
const sortedSections = preservedSections.toSorted((a, b) => compareVersionsDescending(a.version, b.version));
|
|
50
|
+
const pendingSections = sortedSections.map(appendTrailingNewline);
|
|
45
51
|
const result = [];
|
|
46
52
|
let index = 0;
|
|
47
|
-
for (const part of
|
|
48
|
-
let section =
|
|
53
|
+
for (const part of renderedSections) {
|
|
54
|
+
let section = pendingSections[index];
|
|
49
55
|
while (section !== undefined && compareVersionsDescending(section.version, part.version) < 0) {
|
|
50
56
|
result.push(section);
|
|
51
57
|
index++;
|
|
52
|
-
section =
|
|
58
|
+
section = pendingSections[index];
|
|
53
59
|
}
|
|
54
60
|
result.push(part);
|
|
55
61
|
}
|
|
56
|
-
result.push(...
|
|
62
|
+
result.push(...pendingSections.slice(index));
|
|
57
63
|
return result;
|
|
58
64
|
}
|
|
59
65
|
function renderFile(sections, paragraph) {
|
|
@@ -71,6 +77,3 @@ function renderFile(sections, paragraph) {
|
|
|
71
77
|
parts.push(CHANGELOG_FOOTER);
|
|
72
78
|
return parts.join('\n\n') + '\n';
|
|
73
79
|
}
|
|
74
|
-
function withTrailingNewline(section) {
|
|
75
|
-
return { version: section.version, text: `${section.text}\n` };
|
|
76
|
-
}
|
|
@@ -4,7 +4,7 @@ export interface RenderOptions {
|
|
|
4
4
|
/** Predicate to filter sections. When absent, all sections are included. */
|
|
5
5
|
filter?: (section: ChangelogSection) => boolean;
|
|
6
6
|
/** Whether to include the version/date heading. Defaults to `true`. */
|
|
7
|
-
|
|
7
|
+
shouldIncludeHeading?: boolean;
|
|
8
8
|
/**
|
|
9
9
|
* Section titles in desired priority order. Known titles are emitted first in this order;
|
|
10
10
|
* titles not listed preserve their relative position after known ones. When absent, section
|
|
@@ -3,26 +3,26 @@ function getBreakingPrefix() {
|
|
|
3
3
|
const { emoji, label } = CANONICAL_TAXONOMY.markers.breaking;
|
|
4
4
|
return `${emoji} **${label}:** `;
|
|
5
5
|
}
|
|
6
|
-
function
|
|
6
|
+
function matchAllSections() {
|
|
7
7
|
return true;
|
|
8
8
|
}
|
|
9
|
-
function
|
|
9
|
+
function matchPublicSections(section) {
|
|
10
10
|
return section.audience === 'all';
|
|
11
11
|
}
|
|
12
12
|
export function matchesAudience(audience) {
|
|
13
|
-
return audience === 'dev' ?
|
|
13
|
+
return audience === 'dev' ? matchAllSections : matchPublicSections;
|
|
14
14
|
}
|
|
15
15
|
export function renderReleaseNotesSingle(entry, options) {
|
|
16
16
|
const filter = options?.filter;
|
|
17
|
-
const
|
|
17
|
+
const shouldIncludeHeading = options?.shouldIncludeHeading ?? true;
|
|
18
18
|
const sectionOrder = options?.sectionOrder;
|
|
19
|
-
const
|
|
20
|
-
const sections = sectionOrder !== undefined ? sortSectionsByOrder(
|
|
19
|
+
const filteredSections = filter !== undefined ? entry.sections.filter(filter) : entry.sections;
|
|
20
|
+
const sections = sectionOrder !== undefined ? sortSectionsByOrder(filteredSections, sectionOrder) : filteredSections;
|
|
21
21
|
if (sections.length === 0) {
|
|
22
22
|
return '';
|
|
23
23
|
}
|
|
24
24
|
const lines = [];
|
|
25
|
-
if (
|
|
25
|
+
if (shouldIncludeHeading) {
|
|
26
26
|
lines.push(`## ${entry.version} — ${entry.date}`);
|
|
27
27
|
}
|
|
28
28
|
for (const section of sections) {
|
|
@@ -45,20 +45,20 @@ export function renderReleaseNotesSingle(entry, options) {
|
|
|
45
45
|
return lines.join('\n') + '\n';
|
|
46
46
|
}
|
|
47
47
|
function sortSectionsByOrder(sections, order) {
|
|
48
|
-
const
|
|
48
|
+
const priorityByTitle = new Map();
|
|
49
49
|
for (const [index, title] of order.entries()) {
|
|
50
|
-
|
|
50
|
+
priorityByTitle.set(title, index);
|
|
51
51
|
}
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
57
|
-
return
|
|
52
|
+
const indexedSections = sections.map((section, index) => ({ section, index }));
|
|
53
|
+
indexedSections.sort((a, b) => {
|
|
54
|
+
const aPriority = priorityByTitle.get(a.section.title) ?? Infinity;
|
|
55
|
+
const bPriority = priorityByTitle.get(b.section.title) ?? Infinity;
|
|
56
|
+
if (aPriority !== bPriority) {
|
|
57
|
+
return aPriority - bPriority;
|
|
58
58
|
}
|
|
59
59
|
return a.index - b.index;
|
|
60
60
|
});
|
|
61
|
-
return
|
|
61
|
+
return indexedSections.map(({ section }) => section);
|
|
62
62
|
}
|
|
63
63
|
function indentBodyLines(body) {
|
|
64
64
|
return body.split('\n').map((line) => (line.length === 0 ? '' : ` ${line}`));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { formatGlyphLine, formatStatusLine, measureGlyphColumn, STATUS_GLYPHS, } from '@williamthorsen/nmr-core';
|
|
2
|
-
import { bold, dim,
|
|
2
|
+
import { bold, dim, renderSectionHeader } from "./format.js";
|
|
3
3
|
import { formatBumpFiles, formatChangelogFiles, formatPreviewFiles } from "./formatPrepareFiles.js";
|
|
4
4
|
import { formatChangeRecordWarnings, formatPolicyViolations, formatUnparseableWarning, formatWarnings, } from "./formatPrepareWarnings.js";
|
|
5
5
|
import { RELEASE_GLYPHS } from "./glyphs.js";
|
|
@@ -12,15 +12,15 @@ export function reportPrepare(plan, options) {
|
|
|
12
12
|
return formatSingleWorkspace(plan, options);
|
|
13
13
|
}
|
|
14
14
|
function formatSingleWorkspace(result, options) {
|
|
15
|
-
const
|
|
15
|
+
const isDryRun = !options.applied;
|
|
16
16
|
const { style } = options;
|
|
17
17
|
const lines = [];
|
|
18
18
|
const workspace = result.targets[0];
|
|
19
19
|
if (workspace === undefined || workspace.kind === 'composite') {
|
|
20
20
|
return '';
|
|
21
21
|
}
|
|
22
|
-
const
|
|
23
|
-
lines.push(dim(`Found ${workspace.commitCount} commits since ${
|
|
22
|
+
const sinceLabel = workspace.previousTag === undefined ? 'the beginning' : workspace.previousTag;
|
|
23
|
+
lines.push(dim(`Found ${workspace.commitCount} commits since ${sinceLabel}`));
|
|
24
24
|
if (workspace.parsedCommitCount !== undefined) {
|
|
25
25
|
lines.push(dim(` Parsed ${workspace.parsedCommitCount} typed commits`));
|
|
26
26
|
}
|
|
@@ -32,8 +32,8 @@ function formatSingleWorkspace(result, options) {
|
|
|
32
32
|
formatWarnings(lines, result, style);
|
|
33
33
|
return lines.join('\n');
|
|
34
34
|
}
|
|
35
|
-
if (workspace.
|
|
36
|
-
lines.push(` Using version override: ${workspace.
|
|
35
|
+
if (workspace.versionOverride !== undefined) {
|
|
36
|
+
lines.push(` Using version override: ${workspace.versionOverride}`);
|
|
37
37
|
}
|
|
38
38
|
else if (workspace.bumpOverride !== undefined) {
|
|
39
39
|
lines.push(` Using bump override: ${workspace.bumpOverride}`);
|
|
@@ -41,35 +41,35 @@ function formatSingleWorkspace(result, options) {
|
|
|
41
41
|
if (workspace.releaseType !== undefined) {
|
|
42
42
|
lines.push(dim(`Bumping versions (${workspace.releaseType})...`));
|
|
43
43
|
}
|
|
44
|
-
else if (workspace.
|
|
44
|
+
else if (workspace.versionOverride !== undefined) {
|
|
45
45
|
lines.push(dim(`Bumping versions (version override)...`));
|
|
46
46
|
}
|
|
47
|
-
if (workspace.
|
|
47
|
+
if (workspace.versionOverride !== undefined) {
|
|
48
48
|
lines.push(formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${workspace.currentVersion} → ${bold(workspace.newVersion)} (version override)`));
|
|
49
49
|
}
|
|
50
50
|
else if (workspace.releaseType !== undefined) {
|
|
51
51
|
lines.push(formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${workspace.currentVersion} → ${bold(workspace.newVersion)} (${workspace.releaseType})`));
|
|
52
52
|
}
|
|
53
53
|
formatChangeRecordWarnings(lines, workspace, style);
|
|
54
|
-
formatBumpFiles(lines, workspace,
|
|
54
|
+
formatBumpFiles(lines, workspace, isDryRun);
|
|
55
55
|
lines.push(dim('Generating changelogs...'));
|
|
56
|
-
formatChangelogFiles(lines, workspace,
|
|
57
|
-
formatPreviewFiles(lines, workspace,
|
|
56
|
+
formatChangelogFiles(lines, workspace, isDryRun, style);
|
|
57
|
+
formatPreviewFiles(lines, workspace, isDryRun);
|
|
58
58
|
formatFormatCommand(lines, result, options);
|
|
59
59
|
formatWarnings(lines, result, style);
|
|
60
60
|
lines.push(formatStatusLine(style, 'passed', 'Release preparation complete.'), ` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(workspace.tag))}`);
|
|
61
61
|
return lines.join('\n');
|
|
62
62
|
}
|
|
63
63
|
function formatMultiWorkspace(result, options) {
|
|
64
|
-
const
|
|
64
|
+
const isDryRun = !options.applied;
|
|
65
65
|
const { style } = options;
|
|
66
66
|
const lines = [];
|
|
67
67
|
for (const target of result.targets) {
|
|
68
68
|
if (target.kind === 'composite') {
|
|
69
|
-
formatProjectSection(lines, target,
|
|
69
|
+
formatProjectSection(lines, target, isDryRun, style);
|
|
70
70
|
}
|
|
71
71
|
else {
|
|
72
|
-
formatWorkspaceSection(lines, target,
|
|
72
|
+
formatWorkspaceSection(lines, target, isDryRun, style);
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
formatFormatCommand(lines, result, options);
|
|
@@ -85,10 +85,10 @@ function formatMultiWorkspace(result, options) {
|
|
|
85
85
|
}
|
|
86
86
|
return lines.join('\n');
|
|
87
87
|
}
|
|
88
|
-
function formatProjectSection(lines, project,
|
|
89
|
-
lines.push(`\n${
|
|
90
|
-
const
|
|
91
|
-
lines.push(dim(` Found ${project.commitCount} commits ${
|
|
88
|
+
function formatProjectSection(lines, project, isDryRun, style) {
|
|
89
|
+
lines.push(`\n${renderSectionHeader(project.targetId === PROJECT_TARGET_ID ? 'project' : project.targetId)}`);
|
|
90
|
+
const sinceLabel = project.previousTag === undefined ? '(no previous release found)' : `since ${project.previousTag}`;
|
|
91
|
+
lines.push(dim(` Found ${project.commitCount} commits ${sinceLabel}`));
|
|
92
92
|
formatPolicyViolations(lines, project.policyViolations, style, ' ');
|
|
93
93
|
if (project.status === 'skipped') {
|
|
94
94
|
formatUnparseableWarning(lines, project, style, ' ');
|
|
@@ -107,7 +107,7 @@ function formatProjectSection(lines, project, dryRun, style) {
|
|
|
107
107
|
formatChangeRecordWarnings(lines, project, style, ' ');
|
|
108
108
|
lines.push(dim(` Bumping versions (${releaseType})...`), ` ${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${currentVersion} → ${bold(newVersion)} (${releaseType})`)}`);
|
|
109
109
|
for (const file of project.bumpedFiles) {
|
|
110
|
-
if (
|
|
110
|
+
if (isDryRun) {
|
|
111
111
|
lines.push(dim(` [dry-run] Would bump ${file}`));
|
|
112
112
|
}
|
|
113
113
|
else {
|
|
@@ -115,16 +115,16 @@ function formatProjectSection(lines, project, dryRun, style) {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
lines.push(dim(' Generating changelogs...'));
|
|
118
|
-
formatChangelogFiles(lines, project,
|
|
119
|
-
formatPreviewFiles(lines, project,
|
|
118
|
+
formatChangelogFiles(lines, project, isDryRun, style, ' ');
|
|
119
|
+
formatPreviewFiles(lines, project, isDryRun, ' ');
|
|
120
120
|
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(tag))}`);
|
|
121
121
|
}
|
|
122
|
-
function formatWorkspaceSection(lines, workspace,
|
|
122
|
+
function formatWorkspaceSection(lines, workspace, isDryRun, style) {
|
|
123
123
|
if (workspace.name !== undefined) {
|
|
124
|
-
lines.push(`\n${
|
|
124
|
+
lines.push(`\n${renderSectionHeader(workspace.name)}`);
|
|
125
125
|
}
|
|
126
|
-
const
|
|
127
|
-
lines.push(dim(` Found ${workspace.commitCount} commits ${
|
|
126
|
+
const sinceLabel = workspace.previousTag === undefined ? '(no previous release found)' : `since ${workspace.previousTag}`;
|
|
127
|
+
lines.push(dim(` Found ${workspace.commitCount} commits ${sinceLabel}`));
|
|
128
128
|
if (workspace.status === 'skipped') {
|
|
129
129
|
formatUnparseableWarning(lines, workspace, style, ' ');
|
|
130
130
|
formatPolicyViolations(lines, workspace.policyViolations, style, ' ');
|
|
@@ -140,24 +140,24 @@ function formatWorkspaceSection(lines, workspace, dryRun, style) {
|
|
|
140
140
|
formatChangeRecordWarnings(lines, workspace, style, ' ');
|
|
141
141
|
formatBumpLabels(lines, workspace, isPropagatedOnly);
|
|
142
142
|
formatVersionLine(lines, workspace, propagatedFrom, isPropagatedOnly, style);
|
|
143
|
-
formatBumpFiles(lines, workspace,
|
|
143
|
+
formatBumpFiles(lines, workspace, isDryRun, ' ');
|
|
144
144
|
lines.push(dim(' Generating changelogs...'));
|
|
145
|
-
formatChangelogFiles(lines, workspace,
|
|
146
|
-
formatPreviewFiles(lines, workspace,
|
|
145
|
+
formatChangelogFiles(lines, workspace, isDryRun, style, ' ');
|
|
146
|
+
formatPreviewFiles(lines, workspace, isDryRun, ' ');
|
|
147
147
|
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'tag', bold(workspace.tag))}`);
|
|
148
148
|
}
|
|
149
149
|
function formatCommitSummary(lines, workspace, propagatedFrom, isPropagatedOnly) {
|
|
150
150
|
if (isPropagatedOnly && propagatedFrom !== undefined) {
|
|
151
|
-
const
|
|
152
|
-
lines.push(dim(` Bumped via dependency: ${
|
|
151
|
+
const dependencyNames = propagatedFrom.map((p) => p.packageName).join(', ');
|
|
152
|
+
lines.push(dim(` Bumped via dependency: ${dependencyNames}`));
|
|
153
153
|
}
|
|
154
154
|
else if (workspace.parsedCommitCount !== undefined && workspace.parsedCommitCount > 0) {
|
|
155
155
|
lines.push(dim(` Parsed ${workspace.parsedCommitCount} typed commits`));
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
function formatBumpLabels(lines, workspace, isPropagatedOnly) {
|
|
159
|
-
if (workspace.
|
|
160
|
-
lines.push(` Using version override: ${workspace.
|
|
159
|
+
if (workspace.versionOverride !== undefined) {
|
|
160
|
+
lines.push(` Using version override: ${workspace.versionOverride}`);
|
|
161
161
|
}
|
|
162
162
|
else if (workspace.bumpOverride !== undefined && !isPropagatedOnly) {
|
|
163
163
|
lines.push(` Using bump override: ${workspace.bumpOverride}`);
|
|
@@ -165,13 +165,13 @@ function formatBumpLabels(lines, workspace, isPropagatedOnly) {
|
|
|
165
165
|
if (workspace.releaseType !== undefined) {
|
|
166
166
|
lines.push(dim(` Bumping versions (${workspace.releaseType})...`));
|
|
167
167
|
}
|
|
168
|
-
else if (workspace.
|
|
168
|
+
else if (workspace.versionOverride !== undefined) {
|
|
169
169
|
lines.push(dim(` Bumping versions (version override)...`));
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
172
|
function formatVersionLine(lines, workspace, propagatedFrom, isPropagatedOnly, style) {
|
|
173
173
|
const versions = `${workspace.currentVersion} → ${bold(workspace.newVersion)}`;
|
|
174
|
-
if (workspace.
|
|
174
|
+
if (workspace.versionOverride !== undefined) {
|
|
175
175
|
lines.push(` ${formatGlyphLine(RELEASE_GLYPHS, style, 'bump', `${versions} (version override)`)}`);
|
|
176
176
|
}
|
|
177
177
|
else if (workspace.releaseType !== undefined) {
|
|
@@ -29,12 +29,12 @@ export function resolveCommandTags(tags, userConfig) {
|
|
|
29
29
|
singleWorkspace = deriveWorkspaceConfig('.');
|
|
30
30
|
}
|
|
31
31
|
else {
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
workspaces = [...
|
|
35
|
-
directories =
|
|
36
|
-
project =
|
|
37
|
-
excludedPaths = new Set(
|
|
32
|
+
const mergedConfig = mergeMonorepoConfig(workspace.packageDirs, userConfig, readRootPackageVersion());
|
|
33
|
+
const excludedWorkspaces = mergedConfig.excludedWorkspaces ?? [];
|
|
34
|
+
workspaces = [...mergedConfig.workspaces, ...excludedWorkspaces];
|
|
35
|
+
directories = mergedConfig.directories ?? [];
|
|
36
|
+
project = mergedConfig.project;
|
|
37
|
+
excludedPaths = new Set(excludedWorkspaces.map((excludedWorkspace) => excludedWorkspace.workspacePath));
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
40
|
catch (error) {
|
|
@@ -69,15 +69,15 @@ export function resolveCommandTags(tags, userConfig) {
|
|
|
69
69
|
return dropExcludedTags(resolvedTags, excludedPaths, userConfig?.releases === undefined ? 'legacy' : 'releases');
|
|
70
70
|
}
|
|
71
71
|
function dropExcludedTags(resolvedTags, excludedPaths, surface) {
|
|
72
|
-
const
|
|
72
|
+
const retainedTags = [];
|
|
73
73
|
for (const resolvedTag of resolvedTags) {
|
|
74
74
|
if (resolvedTag.kind === 'package' && excludedPaths.has(resolvedTag.workspacePath)) {
|
|
75
75
|
const setting = surface === 'releases' ? `releases.${resolvedTag.dir}: false` : undefined;
|
|
76
76
|
console.warn(formatExcludedSkip(resolvedTag, setting));
|
|
77
77
|
}
|
|
78
78
|
else {
|
|
79
|
-
|
|
79
|
+
retainedTags.push(resolvedTag);
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
|
-
return
|
|
82
|
+
return retainedTags;
|
|
83
83
|
}
|
|
@@ -24,9 +24,9 @@ export interface DeclaredDirectory {
|
|
|
24
24
|
/** The release targets that a config declares over the discovered workspaces. */
|
|
25
25
|
export interface ReleaseDeclaration {
|
|
26
26
|
/** Retained workspaces in discovery order, each with its target name and legacy identities applied. */
|
|
27
|
-
|
|
27
|
+
retainedWorkspaces: WorkspaceConfig[];
|
|
28
28
|
/** Discovered workspaces that the config excludes, in discovery order. */
|
|
29
|
-
|
|
29
|
+
excludedWorkspaces: WorkspaceConfig[];
|
|
30
30
|
/** Directories that `releases` entries declare as targets, in config order. */
|
|
31
31
|
directories: DeclaredDirectory[];
|
|
32
32
|
composite?: DeclaredComposite;
|
|
@@ -3,8 +3,8 @@ export function getTargetName(workspace) {
|
|
|
3
3
|
return workspace.targetName ?? workspace.dir;
|
|
4
4
|
}
|
|
5
5
|
export function normalizeWorkspacePath(configuredPath) {
|
|
6
|
-
const
|
|
7
|
-
return
|
|
6
|
+
const normalizedPath = path.posix.normalize(configuredPath.replaceAll('\\', '/'));
|
|
7
|
+
return normalizedPath.replace(/^\.\//, '').replace(/\/+$/, '');
|
|
8
8
|
}
|
|
9
9
|
export function resolveReleaseDeclaration(workspaces, userConfig) {
|
|
10
10
|
if (userConfig?.releases !== undefined) {
|
|
@@ -19,9 +19,9 @@ function applyLegacyIdentities(workspace, legacyIdentities) {
|
|
|
19
19
|
return { ...workspace, legacyIdentities: legacyIdentities.map((identity) => ({ ...identity })) };
|
|
20
20
|
}
|
|
21
21
|
function claimPath(entryByPath, name, claimedPath, kind) {
|
|
22
|
-
const
|
|
23
|
-
if (
|
|
24
|
-
throw new Error(`releases.${name}: ${kind} '${claimedPath}' is already declared by releases.${
|
|
22
|
+
const claimingEntryName = entryByPath.get(claimedPath);
|
|
23
|
+
if (claimingEntryName !== undefined) {
|
|
24
|
+
throw new Error(`releases.${name}: ${kind} '${claimedPath}' is already declared by releases.${claimingEntryName}; declare each ${kind} once`);
|
|
25
25
|
}
|
|
26
26
|
entryByPath.set(claimedPath, name);
|
|
27
27
|
}
|
|
@@ -40,9 +40,9 @@ function declareDirectory(name, entry, normalizedPath) {
|
|
|
40
40
|
}),
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
function resolveCompositeMembers(composite,
|
|
43
|
+
function resolveCompositeMembers(composite, retainedWorkspaces, excludedWorkspaces, directories) {
|
|
44
44
|
const membersByName = new Map([
|
|
45
|
-
...
|
|
45
|
+
...retainedWorkspaces.map((workspace) => [getTargetName(workspace), { name: getTargetName(workspace), paths: workspace.paths }]),
|
|
46
46
|
...directories.map((directory) => [directory.name, { name: directory.name, paths: directory.paths }]),
|
|
47
47
|
]);
|
|
48
48
|
return composite.includes.map((memberName) => {
|
|
@@ -54,7 +54,7 @@ function resolveCompositeMembers(composite, retained, excluded, directories) {
|
|
|
54
54
|
if (memberName === composite.name) {
|
|
55
55
|
throw new Error(`${label} is the composite itself; a composite includes package and directory targets only`);
|
|
56
56
|
}
|
|
57
|
-
if (
|
|
57
|
+
if (excludedWorkspaces.some((workspace) => workspace.dir === memberName)) {
|
|
58
58
|
throw new Error(`${label} is excluded, so the composite cannot include it`);
|
|
59
59
|
}
|
|
60
60
|
throw new Error(`${label} is not a release target. Known targets: ${membersByName.keys().toArray().join(', ')}`);
|
|
@@ -62,18 +62,18 @@ function resolveCompositeMembers(composite, retained, excluded, directories) {
|
|
|
62
62
|
}
|
|
63
63
|
function resolveLegacyBlocks(workspaces, overrides, project) {
|
|
64
64
|
const overridesByDir = new Map(overrides.map((override) => [override.dir, override]));
|
|
65
|
-
const
|
|
66
|
-
const
|
|
65
|
+
const retainedWorkspaces = [];
|
|
66
|
+
const excludedWorkspaces = [];
|
|
67
67
|
for (const workspace of workspaces) {
|
|
68
68
|
const override = overridesByDir.get(workspace.dir);
|
|
69
69
|
if (override?.shouldExclude === true) {
|
|
70
|
-
|
|
70
|
+
excludedWorkspaces.push(workspace);
|
|
71
71
|
}
|
|
72
72
|
else {
|
|
73
|
-
|
|
73
|
+
retainedWorkspaces.push(applyLegacyIdentities(workspace, override?.legacyIdentities));
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
const declaration = {
|
|
76
|
+
const declaration = { retainedWorkspaces, excludedWorkspaces, directories: [] };
|
|
77
77
|
if (project !== undefined) {
|
|
78
78
|
declaration.composite = {
|
|
79
79
|
...(project.paths !== undefined && { paths: [...project.paths] }),
|
|
@@ -87,7 +87,7 @@ function resolveReleasesBlock(workspaces, releases) {
|
|
|
87
87
|
const workspaceByDir = new Map(workspaces.map((workspace) => [workspace.dir, workspace]));
|
|
88
88
|
const entryByPath = new Map();
|
|
89
89
|
const excludedPaths = new Set();
|
|
90
|
-
const
|
|
90
|
+
const renamedWorkspaces = new Map();
|
|
91
91
|
const directories = [];
|
|
92
92
|
let composite;
|
|
93
93
|
for (const [name, entry] of Object.entries(releases)) {
|
|
@@ -126,22 +126,22 @@ function resolveReleasesBlock(workspaces, releases) {
|
|
|
126
126
|
"whose tag prefix derives from its package name; declare the workspace's earlier prefixes in legacyIdentities");
|
|
127
127
|
}
|
|
128
128
|
claimPath(entryByPath, name, workspace.workspacePath, 'workspace');
|
|
129
|
-
const
|
|
130
|
-
|
|
129
|
+
const identifiedWorkspace = applyLegacyIdentities(workspace, entry.legacyIdentities);
|
|
130
|
+
renamedWorkspaces.set(workspace.workspacePath, name === workspace.dir ? identifiedWorkspace : { ...identifiedWorkspace, targetName: name });
|
|
131
131
|
}
|
|
132
|
-
const
|
|
133
|
-
const
|
|
132
|
+
const retainedWorkspaces = [];
|
|
133
|
+
const excludedWorkspaces = [];
|
|
134
134
|
for (const workspace of workspaces) {
|
|
135
135
|
if (excludedPaths.has(workspace.workspacePath)) {
|
|
136
|
-
|
|
136
|
+
excludedWorkspaces.push(workspace);
|
|
137
137
|
}
|
|
138
138
|
else {
|
|
139
|
-
|
|
139
|
+
retainedWorkspaces.push(renamedWorkspaces.get(workspace.workspacePath) ?? workspace);
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
|
-
const declaration = {
|
|
142
|
+
const declaration = { retainedWorkspaces, excludedWorkspaces, directories };
|
|
143
143
|
if (composite !== undefined) {
|
|
144
|
-
const members = resolveCompositeMembers(composite,
|
|
144
|
+
const members = resolveCompositeMembers(composite, retainedWorkspaces, excludedWorkspaces, directories);
|
|
145
145
|
declaration.composite = {
|
|
146
146
|
name: composite.name,
|
|
147
147
|
members: members.map((member) => member.name),
|