@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.
Files changed (146) hide show
  1. package/.meta/changelog.json +93 -0
  2. package/.readyup/kits/default.js +14 -14
  3. package/.readyup/kits/npm-auto-publish.js +4 -4
  4. package/.readyup/manifest.json +13 -13
  5. package/CHANGELOG.md +27 -0
  6. package/README.md +12 -3
  7. package/dist/esm/applyChangelogOverrides.js +6 -6
  8. package/dist/esm/assertReleasesOnNpm.js +11 -11
  9. package/dist/esm/assertTaggedBaseline.js +5 -5
  10. package/dist/esm/assertTarballsComplete.d.ts +9 -0
  11. package/dist/esm/assertTarballsComplete.js +52 -0
  12. package/dist/esm/bin/release-kit.js +16 -16
  13. package/dist/esm/buildChangelogEntries.d.ts +19 -19
  14. package/dist/esm/buildChangelogEntries.js +40 -36
  15. package/dist/esm/buildChangelogItem.js +24 -24
  16. package/dist/esm/buildDependencyGraph.js +23 -23
  17. package/dist/esm/buildReleasedResult.js +21 -19
  18. package/dist/esm/buildSyntheticChangelogEntry.js +2 -2
  19. package/dist/esm/bumpVersion.js +8 -8
  20. package/dist/esm/changelogArchive.d.ts +1 -1
  21. package/dist/esm/changelogArchive.js +2 -2
  22. package/dist/esm/changelogJsonFile.js +5 -5
  23. package/dist/esm/changelogJsonUtils.js +3 -3
  24. package/dist/esm/changelogOverrides.d.ts +5 -5
  25. package/dist/esm/changelogOverrides.js +23 -23
  26. package/dist/esm/classifyChangelogCommit.js +4 -4
  27. package/dist/esm/commitCommand.js +3 -3
  28. package/dist/esm/compareVersions.js +13 -13
  29. package/dist/esm/configSchema.d.ts +3 -2
  30. package/dist/esm/configSchema.js +2 -1
  31. package/dist/esm/createGithubRelease.d.ts +4 -4
  32. package/dist/esm/createGithubRelease.js +10 -10
  33. package/dist/esm/createGithubReleaseCommand.js +9 -9
  34. package/dist/esm/createTags.d.ts +2 -2
  35. package/dist/esm/createTags.js +8 -8
  36. package/dist/esm/decideDirectReleases.d.ts +5 -5
  37. package/dist/esm/decideDirectReleases.js +25 -25
  38. package/dist/esm/decideRelease.d.ts +1 -1
  39. package/dist/esm/decideRelease.js +2 -2
  40. package/dist/esm/deriveDirectoryConfig.d.ts +1 -1
  41. package/dist/esm/deriveDirectoryConfig.js +18 -18
  42. package/dist/esm/deriveWorkspaceConfig.js +4 -4
  43. package/dist/esm/detectPackageManager.d.ts +2 -0
  44. package/dist/esm/detectPackageManager.js +8 -5
  45. package/dist/esm/detectUndeclaredTagPrefixes.js +6 -6
  46. package/dist/esm/enumerateReleaseWindows.d.ts +1 -1
  47. package/dist/esm/enumerateReleaseWindows.js +7 -7
  48. package/dist/esm/findMissingManifestPaths.d.ts +14 -0
  49. package/dist/esm/findMissingManifestPaths.js +90 -0
  50. package/dist/esm/findTransitiveDependents.js +5 -5
  51. package/dist/esm/findUnroutedEntryScopes.js +11 -11
  52. package/dist/esm/format.d.ts +1 -1
  53. package/dist/esm/format.js +1 -1
  54. package/dist/esm/formatPrepareFiles.d.ts +3 -3
  55. package/dist/esm/formatPrepareFiles.js +10 -10
  56. package/dist/esm/formatPrepareWarnings.js +6 -6
  57. package/dist/esm/hasPrettierConfig.js +2 -2
  58. package/dist/esm/init/checks.js +5 -3
  59. package/dist/esm/init/detectRepoType.js +3 -3
  60. package/dist/esm/init/initCommand.d.ts +4 -4
  61. package/dist/esm/init/initCommand.js +7 -7
  62. package/dist/esm/init/parseJsonRecord.d.ts +1 -1
  63. package/dist/esm/init/parseJsonRecord.js +4 -4
  64. package/dist/esm/init/scaffold.d.ts +4 -4
  65. package/dist/esm/init/scaffold.js +15 -15
  66. package/dist/esm/init/templates.d.ts +10 -10
  67. package/dist/esm/init/templates.js +5 -5
  68. package/dist/esm/init/updateTemplatesCommand.d.ts +2 -2
  69. package/dist/esm/init/updateTemplatesCommand.js +14 -12
  70. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +1 -1
  71. package/dist/esm/injectReleaseNotesIntoReadme.js +6 -6
  72. package/dist/esm/injectSection.js +8 -8
  73. package/dist/esm/loadChangelogOverrides.d.ts +1 -1
  74. package/dist/esm/loadChangelogOverrides.js +9 -9
  75. package/dist/esm/loadConfig.d.ts +3 -3
  76. package/dist/esm/loadConfig.js +10 -10
  77. package/dist/esm/loadValidatedConfig.js +4 -4
  78. package/dist/esm/mergeConfig.d.ts +1 -1
  79. package/dist/esm/mergeConfig.js +27 -27
  80. package/dist/esm/monorepoConfigAssertions.js +15 -15
  81. package/dist/esm/npmRegistry.js +4 -4
  82. package/dist/esm/packFromCleanCheckout.d.ts +11 -0
  83. package/dist/esm/packFromCleanCheckout.js +81 -0
  84. package/dist/esm/packsPath.js +3 -3
  85. package/dist/esm/parseChangeRecordBlock.js +33 -33
  86. package/dist/esm/parseCommitMessage.js +5 -5
  87. package/dist/esm/planChangelogFiles.js +8 -8
  88. package/dist/esm/planReleaseNotesPreviews.js +10 -10
  89. package/dist/esm/planVersionBump.js +24 -16
  90. package/dist/esm/prepareArgs.d.ts +9 -9
  91. package/dist/esm/prepareArgs.js +11 -11
  92. package/dist/esm/prepareCommand.js +27 -24
  93. package/dist/esm/prepareComposite.d.ts +4 -4
  94. package/dist/esm/prepareComposite.js +16 -16
  95. package/dist/esm/{releasePrepareTargets.d.ts → prepareReleaseTargets.d.ts} +9 -9
  96. package/dist/esm/{releasePrepareTargets.js → prepareReleaseTargets.js} +40 -40
  97. package/dist/esm/prepareRunContext.d.ts +1 -1
  98. package/dist/esm/prepareRunContext.js +5 -5
  99. package/dist/esm/prepareTarget.d.ts +3 -3
  100. package/dist/esm/prepareTarget.js +41 -39
  101. package/dist/esm/previewTagPrefixes.js +19 -19
  102. package/dist/esm/propagateBumps.js +8 -8
  103. package/dist/esm/publish.d.ts +3 -3
  104. package/dist/esm/publish.js +6 -11
  105. package/dist/esm/publishCommand.d.ts +3 -3
  106. package/dist/esm/publishCommand.js +28 -20
  107. package/dist/esm/pushCommand.js +8 -8
  108. package/dist/esm/pushRelease.d.ts +1 -1
  109. package/dist/esm/pushRelease.js +2 -2
  110. package/dist/esm/readChangelogSections.d.ts +1 -1
  111. package/dist/esm/readChangelogSections.js +16 -16
  112. package/dist/esm/releasePlan.js +25 -25
  113. package/dist/esm/renderChangelogMarkdown.d.ts +4 -4
  114. package/dist/esm/renderChangelogMarkdown.js +18 -15
  115. package/dist/esm/renderReleaseNotes.d.ts +1 -1
  116. package/dist/esm/renderReleaseNotes.js +16 -16
  117. package/dist/esm/reportPrepare.js +34 -34
  118. package/dist/esm/resolveCommandTags.js +9 -9
  119. package/dist/esm/resolveReleaseDeclaration.d.ts +2 -2
  120. package/dist/esm/resolveReleaseDeclaration.js +22 -22
  121. package/dist/esm/resolveReleaseTags.js +17 -15
  122. package/dist/esm/resolveReleaseTargets.js +2 -2
  123. package/dist/esm/showTagPrefixesCommand.js +4 -4
  124. package/dist/esm/sync-labels/generateCommand.d.ts +3 -3
  125. package/dist/esm/sync-labels/generateCommand.js +10 -10
  126. package/dist/esm/sync-labels/initCommand.d.ts +3 -3
  127. package/dist/esm/sync-labels/initCommand.js +15 -15
  128. package/dist/esm/sync-labels/presets.js +3 -3
  129. package/dist/esm/sync-labels/resolveLabels.js +13 -13
  130. package/dist/esm/sync-labels/templates.d.ts +2 -2
  131. package/dist/esm/sync-labels/templates.js +1 -1
  132. package/dist/esm/tagCommand.js +4 -4
  133. package/dist/esm/topologicalSort.d.ts +2 -2
  134. package/dist/esm/topologicalSort.js +7 -7
  135. package/dist/esm/types.d.ts +3 -3
  136. package/dist/esm/validateAllChangelogOverrides.js +9 -9
  137. package/dist/esm/validateConfig.d.ts +1 -1
  138. package/dist/esm/validateConfig.js +60 -50
  139. package/dist/esm/validateOverridesCommand.js +2 -2
  140. package/docs/changelogs.md +5 -5
  141. package/docs/configuration.md +16 -15
  142. package/docs/publishing.md +4 -0
  143. package/docs/releasing.md +6 -0
  144. package/docs/work-types.md +1 -1
  145. package/package.json +2 -2
  146. package/presets/labels/common.yaml +3 -0
@@ -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 { dryRun, provenance } = options;
4
+ const { isDryRun, shouldGenerateProvenance } = options;
4
5
  const executable = resolveExecutable(packageManager);
5
- const args = buildPublishArgs(packageManager, { dryRun, provenance });
6
- console.info(`\n${dryRun ? '[dry-run] ' : ''}Running: ${executable} ${args.join(' ')} (cwd: ${resolvedTag.workspacePath})`);
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.dryRun) {
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.provenance && packageManager !== 'yarn') {
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, and publishes
4
- * each publishable one, injecting release notes into its README for the duration of the publish.
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
- dryRun: { long: '--dry-run', type: 'boolean' },
21
- noGitChecks: { long: '--no-git-checks', type: 'boolean' },
22
- provenance: { long: '--provenance', type: 'boolean' },
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 parsed = parseArgsOrExit(argv, publishFlagSchema);
27
- const configPath = resolveConfigFlag(parsed.flags.config, invocationDir);
28
- const { dryRun, noGitChecks, provenance } = parsed.flags;
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 (!dryRun && !noGitChecks) {
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(parsed.flags.tags);
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(dryRun ? '[dry-run] Would publish:' : 'Publishing:');
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 published = [];
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, { dryRun, provenance });
69
- published.push(resolvedTag.tag);
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 (published.length > 0) {
87
+ if (publishedTags.length > 0) {
80
88
  console.warn('Packages published before failure:');
81
- for (const t of published) {
82
- console.warn(` ${t}`);
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 publishable = [];
91
- const unpublishable = [];
98
+ const publishableTags = [];
99
+ const unpublishableTags = [];
92
100
  for (const tag of resolvedTags) {
93
- (tag.isPublishable ? publishable : unpublishable).push(tag);
101
+ (tag.isPublishable ? publishableTags : unpublishableTags).push(tag);
94
102
  }
95
103
  if (isExplicit) {
96
- for (const resolvedTag of unpublishable) {
104
+ for (const resolvedTag of unpublishableTags) {
97
105
  console.warn(formatPublishSkip(resolvedTag));
98
106
  }
99
107
  }
100
- return publishable;
108
+ return publishableTags;
101
109
  }
102
110
  function formatPublishSkip(resolvedTag) {
103
111
  switch (resolvedTag.kind) {
@@ -8,21 +8,21 @@ import { resolveCommandTags } from "./resolveCommandTags.js";
8
8
  import { resolveConfigFlag } from "./resolveConfigFlag.js";
9
9
  const pushFlagSchema = {
10
10
  ...configFlagSchema,
11
- dryRun: { long: '--dry-run', type: 'boolean' },
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 parsed = parseArgsOrExit(argv, pushFlagSchema);
17
- const configPath = resolveConfigFlag(parsed.flags.config, invocationDir);
18
- const { dryRun, tagsOnly } = parsed.flags;
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(parsed.flags.tags);
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 = dryRun ? '[dry-run] Would push' : 'Pushing';
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, { dryRun, tagsOnly });
37
- if (dryRun) {
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
  }
@@ -1,6 +1,6 @@
1
1
  import type { ResolvedTag } from './resolveReleaseTags.js';
2
2
  export interface PushReleaseOptions {
3
- dryRun?: boolean;
3
+ isDryRun?: boolean;
4
4
  tagsOnly?: boolean;
5
5
  }
6
6
  export interface PushStep {
@@ -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 { dryRun = false, tagsOnly = false } = options;
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 (!dryRun) {
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
- versioned: VersionedMarkdownSection[];
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 read = readChangelogSections(file);
13
- for (const section of read.versioned) {
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(...read.unversionedHeadings);
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 { versioned: [], unversionedHeadings: [] };
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 current;
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
- current = undefined;
46
+ currentSection = undefined;
47
47
  continue;
48
48
  }
49
49
  if (SECTION_HEADING_PATTERN.test(line)) {
50
- current = { heading: line.trimEnd(), lines: [] };
51
- sections.push(current);
50
+ currentSection = { heading: line.trimEnd(), lines: [] };
51
+ sections.push(currentSection);
52
52
  }
53
53
  }
54
54
  fence = updateFence(fence, line);
55
- current?.lines.push(line);
55
+ currentSection?.lines.push(line);
56
56
  }
57
- const versioned = [];
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
- versioned.push({ version, text: trimTrailingBlankLines(lines).join('\n') });
65
+ versionedSections.push({ version, text: trimTrailingBlankLines(lines).join('\n') });
66
66
  }
67
67
  }
68
- return { versioned, unversionedHeadings };
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 end = lines.length;
82
- while (end > 0 && lines[end - 1]?.trim() === '') {
83
- end--;
81
+ let endIndex = lines.length;
82
+ while (endIndex > 0 && lines[endIndex - 1]?.trim() === '') {
83
+ endIndex--;
84
84
  }
85
- return lines.slice(0, end);
85
+ return lines.slice(0, endIndex);
86
86
  }
87
87
  function updateFence(fence, line) {
88
88
  const marker = FENCE_PATTERN.exec(line)?.[1];
@@ -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 ordered = orderPlannedSteps(plan);
8
- const applied = [];
9
- for (const [index, step] of ordered.entries()) {
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 unwritten = ordered.slice(index).map((pending) => pending.path);
13
- throw new Error(describeApplyFailure({ failedStep: step, error: outcome.error, applied, unwritten }));
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
- applied.push({ path: step.path, created: outcome.outcome === 'created' });
15
+ appliedWrites.push({ path: step.path, created: outcome.outcome === 'created' });
16
16
  }
17
- return applied.map((write) => write.path);
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 ordered = [
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
- ordered.push({ kind: 'write', path: RELEASE_SUMMARY_FILE, content: plan.summary });
41
+ orderedSteps.push({ kind: 'write', path: RELEASE_SUMMARY_FILE, content: plan.summary });
42
42
  }
43
43
  if (plan.tags.length > 0) {
44
- ordered.push({ kind: 'write', path: RELEASE_TAGS_FILE, content: plan.tags.join('\n') });
44
+ orderedSteps.push({ kind: 'write', path: RELEASE_TAGS_FILE, content: plan.tags.join('\n') });
45
45
  }
46
- return ordered;
46
+ return orderedSteps;
47
47
  }
48
48
  function describeApplyFailure(args) {
49
- const { failedStep, error, applied, unwritten } = args;
50
- const total = applied.length + unwritten.length;
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 (applied.length === 0) {
54
- lines.push(`The release was not applied. None of its ${total} files were written.`);
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. ${applied.length} of ${total} files were written:`, ...applied.map((write) => ` ${write.path}`), '', 'Not written:', ...unwritten.map((path) => ` ${path}`), ...describeRecovery(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(applied) {
61
- const releaseWrites = applied.filter((write) => !HANDOFF_FILES.has(write.path));
62
- const replaced = releaseWrites.filter((write) => !write.created).map((write) => write.path);
63
- const created = releaseWrites.filter((write) => write.created).map((write) => write.path);
64
- if (replaced.length === 0 && created.length === 0) {
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 (replaced.length > 0) {
69
- lines.push(` git restore ${replaced.join(' ')}`);
68
+ if (replacedPaths.length > 0) {
69
+ lines.push(` git restore ${replacedPaths.join(' ')}`);
70
70
  }
71
- if (created.length > 0) {
72
- lines.push(` rm ${created.join(' ')}`);
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
- keepVersions?: number;
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 `keepVersions` sections, counting preserved sections, and links to each archive file
32
- * from a paragraph below its header. Each older section goes to the archive file of its major version. Every file
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 keepVersions = options?.keepVersions;
12
- if (keepVersions === undefined || sections.length <= keepVersions) {
11
+ const keptVersionCount = options?.keptVersionCount;
12
+ if (keptVersionCount === undefined || sections.length <= keptVersionCount) {
13
13
  return { main: renderFile(sections), archives: [] };
14
14
  }
15
- const archived = groupByMajor(sections.slice(keepVersions));
15
+ const archivedGroups = groupByMajor(sections.slice(keptVersionCount));
16
16
  return {
17
- main: renderFile(sections.slice(0, keepVersions), describeArchivePointer(archived.map(({ major }) => major))),
18
- archives: archived.map(({ major, sections: majorSections }) => ({ major, content: renderFile(majorSections) })),
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(rendered, preserved) {
43
- const sorted = preserved.toSorted((a, b) => compareVersionsDescending(a.version, b.version));
44
- const pending = sorted.map(withTrailingNewline);
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 rendered) {
48
- let section = pending[index];
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 = pending[index];
58
+ section = pendingSections[index];
53
59
  }
54
60
  result.push(part);
55
61
  }
56
- result.push(...pending.slice(index));
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
- includeHeading?: boolean;
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 allSections() {
6
+ function matchAllSections() {
7
7
  return true;
8
8
  }
9
- function publicSections(section) {
9
+ function matchPublicSections(section) {
10
10
  return section.audience === 'all';
11
11
  }
12
12
  export function matchesAudience(audience) {
13
- return audience === 'dev' ? allSections : publicSections;
13
+ return audience === 'dev' ? matchAllSections : matchPublicSections;
14
14
  }
15
15
  export function renderReleaseNotesSingle(entry, options) {
16
16
  const filter = options?.filter;
17
- const includeHeading = options?.includeHeading ?? true;
17
+ const shouldIncludeHeading = options?.shouldIncludeHeading ?? true;
18
18
  const sectionOrder = options?.sectionOrder;
19
- const filtered = filter !== undefined ? entry.sections.filter(filter) : entry.sections;
20
- const sections = sectionOrder !== undefined ? sortSectionsByOrder(filtered, sectionOrder) : filtered;
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 (includeHeading) {
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 priority = new Map();
48
+ const priorityByTitle = new Map();
49
49
  for (const [index, title] of order.entries()) {
50
- priority.set(title, index);
50
+ priorityByTitle.set(title, index);
51
51
  }
52
- const indexed = sections.map((section, index) => ({ section, index }));
53
- indexed.sort((a, b) => {
54
- const priorityA = priority.get(a.section.title) ?? Infinity;
55
- const priorityB = priority.get(b.section.title) ?? Infinity;
56
- if (priorityA !== priorityB) {
57
- return priorityA - priorityB;
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 indexed.map(({ section }) => section);
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}`));