@williamthorsen/release-kit 15.0.3 → 16.1.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 +93 -0
- package/.readyup/kits/default.js +14 -14
- package/.readyup/kits/npm-auto-publish.js +4 -4
- package/.readyup/manifest.json +13 -13
- package/CHANGELOG.md +27 -0
- package/README.md +12 -3
- package/dist/esm/applyChangelogOverrides.js +6 -6
- package/dist/esm/assertReleasesOnNpm.js +11 -11
- package/dist/esm/assertTaggedBaseline.js +5 -5
- package/dist/esm/assertTarballsComplete.d.ts +9 -0
- package/dist/esm/assertTarballsComplete.js +52 -0
- 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 +3 -3
- package/dist/esm/compareVersions.js +13 -13
- package/dist/esm/configSchema.d.ts +3 -2
- package/dist/esm/configSchema.js +2 -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 +8 -8
- 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.d.ts +2 -0
- package/dist/esm/detectPackageManager.js +8 -5
- package/dist/esm/detectUndeclaredTagPrefixes.js +6 -6
- package/dist/esm/enumerateReleaseWindows.d.ts +1 -1
- package/dist/esm/enumerateReleaseWindows.js +7 -7
- package/dist/esm/findMissingManifestPaths.d.ts +14 -0
- package/dist/esm/findMissingManifestPaths.js +90 -0
- 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 +5 -3
- 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 +15 -15
- 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 +14 -12
- 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/packFromCleanCheckout.d.ts +11 -0
- package/dist/esm/packFromCleanCheckout.js +81 -0
- 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 +27 -24
- 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 +19 -19
- package/dist/esm/propagateBumps.js +8 -8
- package/dist/esm/publish.d.ts +3 -3
- package/dist/esm/publish.js +6 -11
- package/dist/esm/publishCommand.d.ts +3 -3
- package/dist/esm/publishCommand.js +28 -20
- package/dist/esm/pushCommand.js +8 -8
- package/dist/esm/pushRelease.d.ts +1 -1
- package/dist/esm/pushRelease.js +2 -2
- package/dist/esm/readChangelogSections.d.ts +1 -1
- package/dist/esm/readChangelogSections.js +16 -16
- package/dist/esm/releasePlan.js +25 -25
- 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 +17 -15
- 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 +15 -15
- package/dist/esm/sync-labels/presets.js +3 -3
- package/dist/esm/sync-labels/resolveLabels.js +13 -13
- package/dist/esm/sync-labels/templates.d.ts +2 -2
- package/dist/esm/sync-labels/templates.js +1 -1
- 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 +16 -15
- package/docs/publishing.md +4 -0
- package/docs/releasing.md +6 -0
- package/docs/work-types.md +1 -1
- package/package.json +2 -2
- package/presets/labels/common.yaml +3 -0
package/dist/esm/publish.js
CHANGED
|
@@ -1,26 +1,21 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { resolveExecutable } from "./detectPackageManager.js";
|
|
2
3
|
export function publishPackage(resolvedTag, packageManager, options) {
|
|
3
|
-
const {
|
|
4
|
+
const { isDryRun, shouldGenerateProvenance } = options;
|
|
4
5
|
const executable = resolveExecutable(packageManager);
|
|
5
|
-
const args = buildPublishArgs(packageManager, {
|
|
6
|
-
console.info(`\n${
|
|
6
|
+
const args = buildPublishArgs(packageManager, { isDryRun, shouldGenerateProvenance });
|
|
7
|
+
console.info(`\n${isDryRun ? '[dry-run] ' : ''}Running: ${executable} ${args.join(' ')} (cwd: ${resolvedTag.workspacePath})`);
|
|
7
8
|
execFileSync(executable, args, { cwd: resolvedTag.workspacePath, stdio: 'inherit' });
|
|
8
9
|
}
|
|
9
|
-
function resolveExecutable(packageManager) {
|
|
10
|
-
if (packageManager === 'yarn-berry') {
|
|
11
|
-
return 'yarn';
|
|
12
|
-
}
|
|
13
|
-
return packageManager;
|
|
14
|
-
}
|
|
15
10
|
function buildPublishArgs(packageManager, options) {
|
|
16
11
|
const args = packageManager === 'yarn-berry' ? ['npm', 'publish'] : ['publish'];
|
|
17
|
-
if (options.
|
|
12
|
+
if (options.isDryRun) {
|
|
18
13
|
args.push('--dry-run');
|
|
19
14
|
}
|
|
20
15
|
if (packageManager === 'pnpm') {
|
|
21
16
|
args.push('--no-git-checks');
|
|
22
17
|
}
|
|
23
|
-
if (options.
|
|
18
|
+
if (options.shouldGenerateProvenance && packageManager !== 'yarn') {
|
|
24
19
|
args.push('--provenance');
|
|
25
20
|
}
|
|
26
21
|
return args;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type StreamStyles } from '@williamthorsen/nmr-core';
|
|
2
2
|
/**
|
|
3
|
-
* Orchestrates the CLI `publish` command: checks for a clean working tree, resolves the tags on HEAD,
|
|
4
|
-
*
|
|
5
|
-
* A relative `--config` resolves against `invocationDir`.
|
|
3
|
+
* Orchestrates the CLI `publish` command: checks for a clean working tree, resolves the tags on HEAD, checks the
|
|
4
|
+
* tarballs of the publishable ones from a clean checkout, and publishes each one, injecting release notes into its
|
|
5
|
+
* README for the duration of the publish. A relative `--config` resolves against `invocationDir`.
|
|
6
6
|
*/
|
|
7
7
|
export declare function publishCommand(argv: string[], styles: StreamStyles, invocationDir: string): Promise<void>;
|
|
@@ -3,6 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
|
|
4
4
|
import { describeError } from '@williamthorsen/toolbelt.errors';
|
|
5
5
|
import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
|
|
6
|
+
import { assertTarballsComplete } from "./assertTarballsComplete.js";
|
|
6
7
|
import { configFlagSchema } from "./configFlagSchema.js";
|
|
7
8
|
import { deriveReleaseNotesConfig } from "./deriveReleaseNotesConfig.js";
|
|
8
9
|
import { detectPackageManager } from "./detectPackageManager.js";
|
|
@@ -17,17 +18,17 @@ import { resolveCommandTags } from "./resolveCommandTags.js";
|
|
|
17
18
|
import { resolveConfigFlag } from "./resolveConfigFlag.js";
|
|
18
19
|
const publishFlagSchema = {
|
|
19
20
|
...configFlagSchema,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
isDryRun: { long: '--dry-run', type: 'boolean' },
|
|
22
|
+
shouldSkipGitChecks: { long: '--no-git-checks', type: 'boolean' },
|
|
23
|
+
shouldGenerateProvenance: { long: '--provenance', type: 'boolean' },
|
|
23
24
|
tags: { long: '--tags', type: 'string' },
|
|
24
25
|
};
|
|
25
26
|
export async function publishCommand(argv, styles, invocationDir) {
|
|
26
|
-
const
|
|
27
|
-
const configPath = resolveConfigFlag(
|
|
28
|
-
const {
|
|
27
|
+
const parsedArgs = parseArgsOrExit(argv, publishFlagSchema);
|
|
28
|
+
const configPath = resolveConfigFlag(parsedArgs.flags.config, invocationDir);
|
|
29
|
+
const { isDryRun, shouldSkipGitChecks, shouldGenerateProvenance } = parsedArgs.flags;
|
|
29
30
|
const userConfig = await loadUsableConfig(styles.stderr, configPath);
|
|
30
|
-
if (!
|
|
31
|
+
if (!isDryRun && !shouldSkipGitChecks) {
|
|
31
32
|
try {
|
|
32
33
|
assertCleanWorkingTree();
|
|
33
34
|
}
|
|
@@ -36,7 +37,7 @@ export async function publishCommand(argv, styles, invocationDir) {
|
|
|
36
37
|
process.exit(1);
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
|
-
const requestedTags = parseRequestedTags(
|
|
40
|
+
const requestedTags = parseRequestedTags(parsedArgs.flags.tags);
|
|
40
41
|
const resolvedTags = resolveCommandTags(requestedTags, userConfig);
|
|
41
42
|
if (resolvedTags.length === 0) {
|
|
42
43
|
return;
|
|
@@ -46,14 +47,21 @@ export async function publishCommand(argv, styles, invocationDir) {
|
|
|
46
47
|
console.info('Nothing to publish.');
|
|
47
48
|
return;
|
|
48
49
|
}
|
|
50
|
+
try {
|
|
51
|
+
assertTarballsComplete(publishableTags.map(({ workspacePath }) => workspacePath), userConfig?.devExportConditions ?? []);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
reportError(describeError(error));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
49
57
|
const packageManager = detectPackageManager();
|
|
50
58
|
const { releaseNotes, changelogJsonOutputPath, sectionOrder } = deriveReleaseNotesConfig(userConfig);
|
|
51
59
|
const shouldInject = releaseNotes.shouldInjectIntoReadme;
|
|
52
|
-
console.info(
|
|
60
|
+
console.info(isDryRun ? '[dry-run] Would publish:' : 'Publishing:');
|
|
53
61
|
for (const { tag, workspacePath } of publishableTags) {
|
|
54
62
|
console.info(` ${tag} (${workspacePath})`);
|
|
55
63
|
}
|
|
56
|
-
const
|
|
64
|
+
const publishedTags = [];
|
|
57
65
|
try {
|
|
58
66
|
for (const resolvedTag of publishableTags) {
|
|
59
67
|
let readmePath;
|
|
@@ -65,8 +73,8 @@ export async function publishCommand(argv, styles, invocationDir) {
|
|
|
65
73
|
}
|
|
66
74
|
}
|
|
67
75
|
try {
|
|
68
|
-
publishPackage(resolvedTag, packageManager, {
|
|
69
|
-
|
|
76
|
+
publishPackage(resolvedTag, packageManager, { isDryRun, shouldGenerateProvenance });
|
|
77
|
+
publishedTags.push(resolvedTag.tag);
|
|
70
78
|
}
|
|
71
79
|
finally {
|
|
72
80
|
if (readmePath !== undefined && originalReadme !== undefined) {
|
|
@@ -76,10 +84,10 @@ export async function publishCommand(argv, styles, invocationDir) {
|
|
|
76
84
|
}
|
|
77
85
|
}
|
|
78
86
|
catch (error) {
|
|
79
|
-
if (
|
|
87
|
+
if (publishedTags.length > 0) {
|
|
80
88
|
console.warn('Packages published before failure:');
|
|
81
|
-
for (const
|
|
82
|
-
console.warn(` ${
|
|
89
|
+
for (const tag of publishedTags) {
|
|
90
|
+
console.warn(` ${tag}`);
|
|
83
91
|
}
|
|
84
92
|
}
|
|
85
93
|
reportError(describeError(error));
|
|
@@ -87,17 +95,17 @@ export async function publishCommand(argv, styles, invocationDir) {
|
|
|
87
95
|
}
|
|
88
96
|
}
|
|
89
97
|
function filterPublishableTags(resolvedTags, isExplicit) {
|
|
90
|
-
const
|
|
91
|
-
const
|
|
98
|
+
const publishableTags = [];
|
|
99
|
+
const unpublishableTags = [];
|
|
92
100
|
for (const tag of resolvedTags) {
|
|
93
|
-
(tag.isPublishable ?
|
|
101
|
+
(tag.isPublishable ? publishableTags : unpublishableTags).push(tag);
|
|
94
102
|
}
|
|
95
103
|
if (isExplicit) {
|
|
96
|
-
for (const resolvedTag of
|
|
104
|
+
for (const resolvedTag of unpublishableTags) {
|
|
97
105
|
console.warn(formatPublishSkip(resolvedTag));
|
|
98
106
|
}
|
|
99
107
|
}
|
|
100
|
-
return
|
|
108
|
+
return publishableTags;
|
|
101
109
|
}
|
|
102
110
|
function formatPublishSkip(resolvedTag) {
|
|
103
111
|
switch (resolvedTag.kind) {
|
package/dist/esm/pushCommand.js
CHANGED
|
@@ -8,21 +8,21 @@ import { resolveCommandTags } from "./resolveCommandTags.js";
|
|
|
8
8
|
import { resolveConfigFlag } from "./resolveConfigFlag.js";
|
|
9
9
|
const pushFlagSchema = {
|
|
10
10
|
...configFlagSchema,
|
|
11
|
-
|
|
11
|
+
isDryRun: { long: '--dry-run', type: 'boolean' },
|
|
12
12
|
tags: { long: '--tags', type: 'string' },
|
|
13
13
|
tagsOnly: { long: '--tags-only', type: 'boolean' },
|
|
14
14
|
};
|
|
15
15
|
export async function pushCommand(argv, styles, invocationDir) {
|
|
16
|
-
const
|
|
17
|
-
const configPath = resolveConfigFlag(
|
|
18
|
-
const {
|
|
16
|
+
const parsedArgs = parseArgsOrExit(argv, pushFlagSchema);
|
|
17
|
+
const configPath = resolveConfigFlag(parsedArgs.flags.config, invocationDir);
|
|
18
|
+
const { isDryRun, tagsOnly } = parsedArgs.flags;
|
|
19
19
|
const userConfig = await loadUsableConfig(styles.stderr, configPath);
|
|
20
|
-
const requestedTags = parseRequestedTags(
|
|
20
|
+
const requestedTags = parseRequestedTags(parsedArgs.flags.tags);
|
|
21
21
|
const resolvedTags = resolveCommandTags(requestedTags, userConfig);
|
|
22
22
|
if (resolvedTags.length === 0) {
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
|
-
const prefix =
|
|
25
|
+
const prefix = isDryRun ? '[dry-run] Would push' : 'Pushing';
|
|
26
26
|
if (!tagsOnly) {
|
|
27
27
|
console.info(`${prefix} branch and ${resolvedTags.length} tag(s):`);
|
|
28
28
|
}
|
|
@@ -33,8 +33,8 @@ export async function pushCommand(argv, styles, invocationDir) {
|
|
|
33
33
|
console.info(` ${tag}`);
|
|
34
34
|
}
|
|
35
35
|
try {
|
|
36
|
-
const steps = pushRelease(resolvedTags, {
|
|
37
|
-
if (
|
|
36
|
+
const steps = pushRelease(resolvedTags, { isDryRun, tagsOnly });
|
|
37
|
+
if (isDryRun) {
|
|
38
38
|
for (const step of steps) {
|
|
39
39
|
console.info(`[dry-run] ${step.command.join(' ')}`);
|
|
40
40
|
}
|
package/dist/esm/pushRelease.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { GIT_OUTPUT_LIMIT_BYTES } from '@williamthorsen/nmr-core';
|
|
3
3
|
export function pushRelease(resolvedTags, options = {}) {
|
|
4
|
-
const {
|
|
4
|
+
const { isDryRun = false, tagsOnly = false } = options;
|
|
5
5
|
const steps = [];
|
|
6
6
|
if (!tagsOnly) {
|
|
7
7
|
const branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
|
@@ -16,7 +16,7 @@ export function pushRelease(resolvedTags, options = {}) {
|
|
|
16
16
|
const command = ['git', 'push', '--no-follow-tags', 'origin', tag];
|
|
17
17
|
steps.push({ type: 'tag', ref: tag, command });
|
|
18
18
|
}
|
|
19
|
-
if (!
|
|
19
|
+
if (!isDryRun) {
|
|
20
20
|
for (const step of steps) {
|
|
21
21
|
execFileSync(step.command[0], step.command.slice(1), { stdio: 'inherit' });
|
|
22
22
|
}
|
|
@@ -7,7 +7,7 @@ export interface VersionedMarkdownSection {
|
|
|
7
7
|
}
|
|
8
8
|
/** The `##` sections of a `CHANGELOG.md`, split by whether their heading names a version. */
|
|
9
9
|
export interface ChangelogMarkdownSections {
|
|
10
|
-
|
|
10
|
+
versionedSections: VersionedMarkdownSection[];
|
|
11
11
|
/** Headings of the `##` sections that do not name a version, as written. */
|
|
12
12
|
unversionedHeadings: string[];
|
|
13
13
|
}
|
|
@@ -9,14 +9,14 @@ export function planPreservedSections(changelogDir, entries) {
|
|
|
9
9
|
const sections = [];
|
|
10
10
|
const unversionedHeadings = [];
|
|
11
11
|
for (const file of files) {
|
|
12
|
-
const
|
|
13
|
-
for (const section of
|
|
12
|
+
const fileSections = readChangelogSections(file);
|
|
13
|
+
for (const section of fileSections.versionedSections) {
|
|
14
14
|
if (seenVersions.has(section.version))
|
|
15
15
|
continue;
|
|
16
16
|
seenVersions.add(section.version);
|
|
17
17
|
sections.push(section);
|
|
18
18
|
}
|
|
19
|
-
unversionedHeadings.push(...
|
|
19
|
+
unversionedHeadings.push(...fileSections.unversionedHeadings);
|
|
20
20
|
}
|
|
21
21
|
if (sections.length === 0 && unversionedHeadings.length === 0) {
|
|
22
22
|
return { sections, preservation: undefined };
|
|
@@ -32,29 +32,29 @@ export function planPreservedSections(changelogDir, entries) {
|
|
|
32
32
|
}
|
|
33
33
|
export function readChangelogSections(filePath) {
|
|
34
34
|
if (!existsSync(filePath)) {
|
|
35
|
-
return {
|
|
35
|
+
return { versionedSections: [], unversionedHeadings: [] };
|
|
36
36
|
}
|
|
37
37
|
return parseChangelogSections(readFileSync(filePath, 'utf8'));
|
|
38
38
|
}
|
|
39
39
|
export function parseChangelogSections(content) {
|
|
40
40
|
const sections = [];
|
|
41
|
-
let
|
|
41
|
+
let currentSection;
|
|
42
42
|
let fence;
|
|
43
43
|
for (const line of content.split(/\r?\n/)) {
|
|
44
44
|
if (fence === undefined) {
|
|
45
45
|
if (FOOTER_PATTERN.test(line)) {
|
|
46
|
-
|
|
46
|
+
currentSection = undefined;
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
49
|
if (SECTION_HEADING_PATTERN.test(line)) {
|
|
50
|
-
|
|
51
|
-
sections.push(
|
|
50
|
+
currentSection = { heading: line.trimEnd(), lines: [] };
|
|
51
|
+
sections.push(currentSection);
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
fence = updateFence(fence, line);
|
|
55
|
-
|
|
55
|
+
currentSection?.lines.push(line);
|
|
56
56
|
}
|
|
57
|
-
const
|
|
57
|
+
const versionedSections = [];
|
|
58
58
|
const unversionedHeadings = [];
|
|
59
59
|
for (const { heading, lines } of sections) {
|
|
60
60
|
const version = extractHeadingVersion(heading);
|
|
@@ -62,10 +62,10 @@ export function parseChangelogSections(content) {
|
|
|
62
62
|
unversionedHeadings.push(heading);
|
|
63
63
|
}
|
|
64
64
|
else {
|
|
65
|
-
|
|
65
|
+
versionedSections.push({ version, text: trimTrailingBlankLines(lines).join('\n') });
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
-
return {
|
|
68
|
+
return { versionedSections, unversionedHeadings };
|
|
69
69
|
}
|
|
70
70
|
const FOOTER_PATTERN = /^<!-- Generated by release-kit\b/;
|
|
71
71
|
const SECTION_HEADING_PATTERN = /^## /;
|
|
@@ -78,11 +78,11 @@ function extractHeadingVersion(heading) {
|
|
|
78
78
|
return semver.valid(token) ?? undefined;
|
|
79
79
|
}
|
|
80
80
|
function trimTrailingBlankLines(lines) {
|
|
81
|
-
let
|
|
82
|
-
while (
|
|
83
|
-
|
|
81
|
+
let endIndex = lines.length;
|
|
82
|
+
while (endIndex > 0 && lines[endIndex - 1]?.trim() === '') {
|
|
83
|
+
endIndex--;
|
|
84
84
|
}
|
|
85
|
-
return lines.slice(0,
|
|
85
|
+
return lines.slice(0, endIndex);
|
|
86
86
|
}
|
|
87
87
|
function updateFence(fence, line) {
|
|
88
88
|
const marker = FENCE_PATTERN.exec(line)?.[1];
|
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') {
|
|
@@ -33,43 +33,43 @@ function applyStep(step) {
|
|
|
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}`));
|