@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
@@ -0,0 +1,81 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { resolveExecutable } from "./detectPackageManager.js";
6
+ import { isRecord } from "./typeGuards.js";
7
+ export function packFromCleanCheckout(workspacePaths, packageManager) {
8
+ const repoRoot = runStep('find the repository root', 'git', ['rev-parse', '--show-toplevel'], process.cwd()).trim();
9
+ const headSha = runStep('resolve HEAD', 'git', ['rev-parse', 'HEAD'], repoRoot).trim();
10
+ const scratchDir = mkdtempSync(join(tmpdir(), 'release-kit-pack-'));
11
+ try {
12
+ const cloneDir = join(scratchDir, 'checkout');
13
+ runStep('clone HEAD', 'git', ['clone', '--quiet', '--shared', '--no-checkout', repoRoot, cloneDir], scratchDir);
14
+ runStep('check out HEAD', 'git', ['checkout', '--quiet', '--detach', headSha], cloneDir);
15
+ const executable = resolveExecutable(packageManager);
16
+ const commands = PACKAGE_MANAGER_COMMANDS[packageManager];
17
+ runStep('install dependencies', executable, commands.installArgs, cloneDir);
18
+ const filesByWorkspace = new Map();
19
+ for (const [index, workspacePath] of workspacePaths.entries()) {
20
+ const packageDir = join(cloneDir, workspacePath);
21
+ if (declaresPrepublishOnly(packageDir)) {
22
+ runStep(`run prepublishOnly in ${workspacePath}`, executable, ['run', 'prepublishOnly'], packageDir);
23
+ }
24
+ const packDir = join(scratchDir, 'packs', String(index));
25
+ mkdirSync(packDir, { recursive: true });
26
+ runStep(`pack ${workspacePath}`, executable, commands.buildPackArgs(packDir), packageDir);
27
+ filesByWorkspace.set(workspacePath, listTarballEntries(findTarball(packDir, workspacePath)));
28
+ }
29
+ return filesByWorkspace;
30
+ }
31
+ finally {
32
+ rmSync(scratchDir, { force: true, recursive: true });
33
+ }
34
+ }
35
+ const PACKAGE_MANAGER_COMMANDS = {
36
+ npm: {
37
+ installArgs: ['ci', '--prefer-offline'],
38
+ buildPackArgs: (packDir) => ['pack', '--pack-destination', packDir],
39
+ },
40
+ pnpm: {
41
+ installArgs: ['install', '--frozen-lockfile', '--prefer-offline'],
42
+ buildPackArgs: (packDir) => ['pack', '--pack-destination', packDir],
43
+ },
44
+ yarn: {
45
+ installArgs: ['install', '--frozen-lockfile'],
46
+ buildPackArgs: (packDir) => ['pack', '--filename', join(packDir, 'package.tgz')],
47
+ },
48
+ 'yarn-berry': {
49
+ installArgs: ['install', '--immutable'],
50
+ buildPackArgs: (packDir) => ['pack', '--out', join(packDir, 'package.tgz')],
51
+ },
52
+ };
53
+ function declaresPrepublishOnly(packageDir) {
54
+ const manifest = JSON.parse(readFileSync(join(packageDir, 'package.json'), 'utf8'));
55
+ const scripts = isRecord(manifest) ? manifest['scripts'] : undefined;
56
+ return isRecord(scripts) && typeof scripts['prepublishOnly'] === 'string';
57
+ }
58
+ function findTarball(packDir, workspacePath) {
59
+ const tarballs = readdirSync(packDir).filter((name) => name.endsWith('.tgz'));
60
+ const [tarball] = tarballs;
61
+ if (tarballs.length !== 1 || tarball === undefined) {
62
+ throw new Error(`Could not pack ${workspacePath}: expected one tarball, found ${tarballs.length}.`);
63
+ }
64
+ return join(packDir, tarball);
65
+ }
66
+ function listTarballEntries(tarballPath) {
67
+ const listing = runStep('list the tarball', 'tar', ['--list', '--gzip', '--file', tarballPath], process.cwd());
68
+ return listing
69
+ .split('\n')
70
+ .filter((entry) => entry !== '' && !entry.endsWith('/'))
71
+ .map((entry) => entry.replace(/^package\//, ''));
72
+ }
73
+ function runStep(step, command, args, cwd) {
74
+ const result = spawnSync(command, args, { cwd, encoding: 'utf8', maxBuffer: 2 ** 26 });
75
+ if (result.error === undefined && result.status === 0) {
76
+ return result.stdout;
77
+ }
78
+ const outcome = result.error === undefined ? `exited with status ${result.status}` : result.error.message;
79
+ const output = [result.stdout, result.stderr].filter((text) => text !== '').join('\n');
80
+ throw new Error(`Could not ${step}: \`${[command, ...args].join(' ')}\` ${outcome}.${output === '' ? '' : `\n${output}`}`);
81
+ }
@@ -11,10 +11,10 @@ const GLOB_TOKEN_PATTERN = /\*\*\/|\*\*|\*|\?|[^*?]+/g;
11
11
  function compileEntry(entry) {
12
12
  if (entry.startsWith('!'))
13
13
  return undefined;
14
- const normalized = entry.replace(/^\.?\//, '').replace(/\/+$/, '');
15
- if (normalized === '')
14
+ const normalizedEntry = entry.replace(/^\.?\//, '').replace(/\/+$/, '');
15
+ if (normalizedEntry === '')
16
16
  return undefined;
17
- return new RegExp(`^${translateGlob(normalized)}$`);
17
+ return new RegExp(`^${translateGlob(normalizedEntry)}$`);
18
18
  }
19
19
  function isStringArray(value) {
20
20
  return Array.isArray(value) && value.every((entry) => typeof entry === 'string');
@@ -6,10 +6,10 @@ export function parseChangeRecordBlock(message) {
6
6
  if (fence === undefined) {
7
7
  return { kind: 'absent' };
8
8
  }
9
- if (fence.close === undefined) {
9
+ if (fence.closeIndex === undefined) {
10
10
  return { kind: 'malformed', reason: 'the block opens but never closes' };
11
11
  }
12
- const document = parseDocument(lines.slice(fence.open + 1, fence.close).join('\n'));
12
+ const document = parseDocument(lines.slice(fence.openIndex + 1, fence.closeIndex).join('\n'));
13
13
  const [error] = document.errors;
14
14
  if (error !== undefined) {
15
15
  const detail = (error.message.split('\n', 1)[0] ?? '').replace(/:$/, '');
@@ -21,60 +21,60 @@ export function stripChangeRecordBlocks(message) {
21
21
  const lines = message.split(/\r?\n/);
22
22
  const fences = findFences(lines);
23
23
  return lines
24
- .filter((_line, index) => fences.every((fence) => index < fence.open || index > (fence.close ?? lines.length)))
24
+ .filter((_line, index) => fences.every((fence) => index < fence.openIndex || index > (fence.closeIndex ?? lines.length)))
25
25
  .join('\n');
26
26
  }
27
27
  const FENCE = '```';
28
28
  function findFences(lines) {
29
29
  const fences = [];
30
- let open;
30
+ let openIndex;
31
31
  for (const [index, line] of lines.entries()) {
32
- if (open === undefined) {
32
+ if (openIndex === undefined) {
33
33
  if (line.trim() === `${FENCE}${INFO_STRING}`) {
34
- open = index;
34
+ openIndex = index;
35
35
  }
36
36
  }
37
37
  else if (line.trim() === FENCE) {
38
- fences.push({ close: index, open });
39
- open = undefined;
38
+ fences.push({ closeIndex: index, openIndex });
39
+ openIndex = undefined;
40
40
  }
41
41
  }
42
- if (open !== undefined) {
43
- fences.push({ open });
42
+ if (openIndex !== undefined) {
43
+ fences.push({ openIndex });
44
44
  }
45
45
  return fences;
46
46
  }
47
47
  const INFO_STRING = 'change-record';
48
48
  function readEntry(item, index) {
49
- const at = `entries[${index}]`;
49
+ const entryPath = `entries[${index}]`;
50
50
  if (!isRecord(item)) {
51
- return { reason: `\`${at}\` is not a mapping` };
51
+ return { reason: `\`${entryPath}\` is not a mapping` };
52
52
  }
53
53
  const { breaking, migration, scopes } = item;
54
54
  if (breaking !== undefined && breaking !== null && typeof breaking !== 'boolean') {
55
- return { reason: `\`${at}.breaking\` is not a boolean` };
55
+ return { reason: `\`${entryPath}.breaking\` is not a boolean` };
56
56
  }
57
57
  if (migration !== undefined && migration !== null && typeof migration !== 'string') {
58
- return { reason: `\`${at}.migration\` is not a string` };
58
+ return { reason: `\`${entryPath}.migration\` is not a string` };
59
59
  }
60
60
  if (scopes !== undefined && scopes !== null && !Array.isArray(scopes)) {
61
- return { reason: `\`${at}.scopes\` is not a list` };
61
+ return { reason: `\`${entryPath}.scopes\` is not a list` };
62
62
  }
63
63
  const declaredScopes = scopes ?? [];
64
64
  const readScopes = [];
65
65
  for (const [position, scope] of declaredScopes.entries()) {
66
66
  if (typeof scope !== 'string') {
67
- return { reason: `\`${at}.scopes[${position}]\` is not a string` };
67
+ return { reason: `\`${entryPath}.scopes[${position}]\` is not a string` };
68
68
  }
69
69
  readScopes.push(scope.trim());
70
70
  }
71
- const type = readRequiredString(item, at, 'type');
72
- if ('reason' in type) {
73
- return type;
71
+ const typeResult = readRequiredString(item, entryPath, 'type');
72
+ if ('reason' in typeResult) {
73
+ return typeResult;
74
74
  }
75
- const text = readRequiredString(item, at, 'text');
76
- if ('reason' in text) {
77
- return text;
75
+ const textResult = readRequiredString(item, entryPath, 'text');
76
+ if ('reason' in textResult) {
77
+ return textResult;
78
78
  }
79
79
  const trimmedMigration = migration?.trim();
80
80
  return {
@@ -82,8 +82,8 @@ function readEntry(item, index) {
82
82
  breaking: breaking === true,
83
83
  ...(trimmedMigration !== undefined && trimmedMigration !== '' && { migration: trimmedMigration }),
84
84
  scopes: readScopes,
85
- text: text.value,
86
- type: type.value,
85
+ text: textResult.value,
86
+ type: typeResult.value,
87
87
  },
88
88
  };
89
89
  }
@@ -106,11 +106,11 @@ function readPayload(payload) {
106
106
  return { kind: 'malformed', reason: '`entries` is not a list' };
107
107
  }
108
108
  for (const [index, item] of entries.entries()) {
109
- const read = readEntry(item, index);
110
- if ('reason' in read) {
111
- return { kind: 'malformed', reason: read.reason };
109
+ const readResult = readEntry(item, index);
110
+ if ('reason' in readResult) {
111
+ return { kind: 'malformed', reason: readResult.reason };
112
112
  }
113
- readEntries.push(read.entry);
113
+ readEntries.push(readResult.entry);
114
114
  }
115
115
  }
116
116
  const trimmedTicketRef = ticketRef?.trim();
@@ -121,14 +121,14 @@ function readPayload(payload) {
121
121
  ...(trimmedTicketRef !== undefined && trimmedTicketRef !== '' && { ticketRef: trimmedTicketRef }),
122
122
  };
123
123
  }
124
- function readRequiredString(item, at, name) {
124
+ function readRequiredString(item, entryPath, name) {
125
125
  const value = item[name];
126
126
  if (value === undefined || value === null) {
127
- return { reason: `\`${at}.${name}\` is missing` };
127
+ return { reason: `\`${entryPath}.${name}\` is missing` };
128
128
  }
129
129
  if (typeof value !== 'string') {
130
- return { reason: `\`${at}.${name}\` is not a string` };
130
+ return { reason: `\`${entryPath}.${name}\` is not a string` };
131
131
  }
132
- const trimmed = value.trim();
133
- return trimmed === '' ? { reason: `\`${at}.${name}\` is blank` } : { value: trimmed };
132
+ const trimmedValue = value.trim();
133
+ return trimmedValue === '' ? { reason: `\`${entryPath}.${name}\` is blank` } : { value: trimmedValue };
134
134
  }
@@ -30,7 +30,7 @@ export function parseCommitMessage(message, hash, workTypes, scopeAliases, optio
30
30
  };
31
31
  }
32
32
  export function parseSubject(subject, workTypes) {
33
- const taxonomy = toTaxonomy(workTypes);
33
+ const taxonomy = adaptToTaxonomy(workTypes);
34
34
  for (const nodes of SUBJECT_TEMPLATES) {
35
35
  const record = parse(nodes, subject, taxonomy);
36
36
  if (record !== undefined) {
@@ -54,14 +54,14 @@ export function evaluateBreakingPolicy(inputs) {
54
54
  return hasPrefixBreaking || hasFooterBreaking;
55
55
  }
56
56
  export function resolveType(rawType, workTypes) {
57
- const lowered = rawType.toLowerCase();
57
+ const loweredType = rawType.toLowerCase();
58
58
  for (const [key, config] of Object.entries(workTypes)) {
59
- if (key === lowered) {
59
+ if (key === loweredType) {
60
60
  return key;
61
61
  }
62
62
  if (config.aliases !== undefined) {
63
63
  for (const alias of config.aliases) {
64
- if (alias === lowered) {
64
+ if (alias === loweredType) {
65
65
  return key;
66
66
  }
67
67
  }
@@ -69,7 +69,7 @@ export function resolveType(rawType, workTypes) {
69
69
  }
70
70
  return undefined;
71
71
  }
72
- function toTaxonomy(workTypes) {
72
+ function adaptToTaxonomy(workTypes) {
73
73
  return {
74
74
  tiers: [],
75
75
  types: Object.entries(workTypes).map(([key, config]) => ({ aliases: config.aliases ?? [], key, tier: '' })),
@@ -13,16 +13,16 @@ export function planChangelogFiles(changelogDir, newEntries, settings, sectionOr
13
13
  changelogJsonFiles.push(jsonPath);
14
14
  }
15
15
  const changelogFile = joinPath(changelogDir, 'CHANGELOG.md');
16
- const preserved = planPreservedSections(changelogDir, entries);
17
- const keepVersions = settings.changelogMarkdown?.keepVersions;
18
- const rendered = renderChangelogFiles(entries, {
16
+ const preservedPlan = planPreservedSections(changelogDir, entries);
17
+ const keptVersionCount = settings.changelogMarkdown?.keptVersionCount;
18
+ const renderedFiles = renderChangelogFiles(entries, {
19
19
  sectionOrder,
20
- preservedSections: preserved.sections,
21
- ...(keepVersions !== undefined && { keepVersions }),
20
+ preservedSections: preservedPlan.sections,
21
+ ...(keptVersionCount !== undefined && { keptVersionCount }),
22
22
  });
23
- writes.push({ path: changelogFile, content: rendered.main });
23
+ writes.push({ path: changelogFile, content: renderedFiles.main });
24
24
  const archiveFiles = [];
25
- for (const { major, content } of rendered.archives) {
25
+ for (const { major, content } of renderedFiles.archives) {
26
26
  const path = joinPath(changelogDir, describeArchivePath(major));
27
27
  writes.push({ path, content });
28
28
  archiveFiles.push(path);
@@ -34,7 +34,7 @@ export function planChangelogFiles(changelogDir, newEntries, settings, sectionOr
34
34
  changelogFiles: [changelogFile, ...archiveFiles],
35
35
  changelogJsonFiles,
36
36
  entries,
37
- preservation: preserved.preservation,
37
+ preservation: preservedPlan.preservation,
38
38
  removals,
39
39
  writes,
40
40
  };
@@ -13,21 +13,21 @@ export function planReleaseNotesPreviews(options) {
13
13
  warnings.push(describePreviewSkip(workspacePath, result.reason, result.version));
14
14
  return { writes: [], warnings };
15
15
  }
16
- const { rendered } = result;
17
- if (!readme.exists && !readme.unreadable) {
16
+ const { renderedReadme } = result;
17
+ if (!readme.doesExist && !readme.unreadable) {
18
18
  warnings.push(`${readmePath} not found; skipping injected-README preview but still writing standalone release notes`);
19
19
  }
20
20
  const version = extractVersion(tag);
21
21
  const docsDir = path.join(workspacePath, 'docs');
22
22
  const writes = [];
23
- if (readme.exists) {
24
- writes.push({ path: path.join(docsDir, `README.v${version}.md`), content: rendered.injectedReadme });
23
+ if (readme.doesExist) {
24
+ writes.push({ path: path.join(docsDir, `README.v${version}.md`), content: renderedReadme.injectedReadme });
25
25
  }
26
26
  writes.push({
27
27
  path: path.join(docsDir, `RELEASE_NOTES.v${version}.md`),
28
- content: rendered.releaseNotesMarkdown.endsWith('\n')
29
- ? rendered.releaseNotesMarkdown
30
- : `${rendered.releaseNotesMarkdown}\n`,
28
+ content: renderedReadme.releaseNotesMarkdown.endsWith('\n')
29
+ ? renderedReadme.releaseNotesMarkdown
30
+ : `${renderedReadme.releaseNotesMarkdown}\n`,
31
31
  });
32
32
  return { writes, warnings };
33
33
  }
@@ -39,14 +39,14 @@ function describePreviewSkip(workspacePath, reason, version) {
39
39
  }
40
40
  function readWorkspaceReadme(readmePath, warnings) {
41
41
  if (!existsSync(readmePath)) {
42
- return { content: '', exists: false, unreadable: false };
42
+ return { content: '', doesExist: false, unreadable: false };
43
43
  }
44
44
  try {
45
- return { content: readFileSync(readmePath, 'utf8'), exists: true, unreadable: false };
45
+ return { content: readFileSync(readmePath, 'utf8'), doesExist: true, unreadable: false };
46
46
  }
47
47
  catch (error) {
48
48
  const message = describeError(error);
49
49
  warnings.push(`failed to read ${readmePath}: ${message}; skipping injected-README preview`);
50
- return { content: '', exists: false, unreadable: true };
50
+ return { content: '', doesExist: false, unreadable: true };
51
51
  }
52
52
  }
@@ -6,34 +6,42 @@ function isPackageJson(value) {
6
6
  return typeof value === 'object' && value !== null && 'version' in value && typeof value.version === 'string';
7
7
  }
8
8
  export function planVersionBump(packageFiles, releaseType) {
9
- const { firstFile, firstPkg } = readPrimaryPackage(packageFiles);
10
- const currentVersion = firstPkg.version;
9
+ const { firstFile, firstPackageJson } = readPrimaryPackage(packageFiles);
10
+ const currentVersion = firstPackageJson.version;
11
11
  const newVersion = bumpVersion(currentVersion, releaseType);
12
- return { currentVersion, newVersion, writes: renderVersionWrites(packageFiles, firstFile, firstPkg, newVersion) };
12
+ return {
13
+ currentVersion,
14
+ newVersion,
15
+ writes: renderVersionWrites(packageFiles, firstFile, firstPackageJson, newVersion),
16
+ };
13
17
  }
14
18
  export function planVersionSet(packageFiles, newVersion) {
15
- const { firstFile, firstPkg } = readPrimaryPackage(packageFiles);
16
- const currentVersion = firstPkg.version;
19
+ const { firstFile, firstPackageJson } = readPrimaryPackage(packageFiles);
20
+ const currentVersion = firstPackageJson.version;
17
21
  if (!isForwardVersion(currentVersion, newVersion)) {
18
22
  throw new Error(`--set-version ${newVersion} is not greater than current version ${currentVersion}`);
19
23
  }
20
- return { currentVersion, newVersion, writes: renderVersionWrites(packageFiles, firstFile, firstPkg, newVersion) };
24
+ return {
25
+ currentVersion,
26
+ newVersion,
27
+ writes: renderVersionWrites(packageFiles, firstFile, firstPackageJson, newVersion),
28
+ };
21
29
  }
22
30
  export function readCurrentVersion(packageFiles) {
23
- return readPrimaryPackage(packageFiles).firstPkg.version;
31
+ return readPrimaryPackage(packageFiles).firstPackageJson.version;
24
32
  }
25
33
  function readPrimaryPackage(packageFiles) {
26
34
  const firstFile = packageFiles[0];
27
35
  if (firstFile === undefined) {
28
36
  throw new Error('No package files specified');
29
37
  }
30
- return { firstFile, firstPkg: readPackageJson(firstFile) };
38
+ return { firstFile, firstPackageJson: readPackageJson(firstFile) };
31
39
  }
32
- function renderVersionWrites(packageFiles, firstFile, firstPkg, newVersion) {
40
+ function renderVersionWrites(packageFiles, firstFile, firstPackageJson, newVersion) {
33
41
  return packageFiles.map((filePath) => {
34
- const pkg = filePath === firstFile ? firstPkg : readPackageJson(filePath);
35
- pkg.version = newVersion;
36
- return { path: filePath, content: JSON.stringify(pkg, null, 2) + '\n' };
42
+ const packageJson = filePath === firstFile ? firstPackageJson : readPackageJson(filePath);
43
+ packageJson.version = newVersion;
44
+ return { path: filePath, content: JSON.stringify(packageJson, null, 2) + '\n' };
37
45
  });
38
46
  }
39
47
  function readPackageJson(filePath) {
@@ -44,15 +52,15 @@ function readPackageJson(filePath) {
44
52
  catch (error) {
45
53
  throw chainError(`Failed to read ${filePath}`, error);
46
54
  }
47
- let parsed;
55
+ let parsedJson;
48
56
  try {
49
- parsed = JSON.parse(content);
57
+ parsedJson = JSON.parse(content);
50
58
  }
51
59
  catch (error) {
52
60
  throw chainError(`Failed to parse JSON in ${filePath}`, error);
53
61
  }
54
- if (!isPackageJson(parsed)) {
62
+ if (!isPackageJson(parsedJson)) {
55
63
  throw new Error(`No valid 'version' field found in ${filePath}`);
56
64
  }
57
- return parsed;
65
+ return parsedJson;
58
66
  }
@@ -2,15 +2,15 @@ import type { ReleaseType } from './types.js';
2
2
  /** Canonical `N.N.N` semver pattern for validating `--set-version` input. */
3
3
  export declare const CANONICAL_SEMVER_PATTERN: RegExp;
4
4
  export declare const prepareFlagSchema: {
5
- dryRun: {
5
+ isDryRun: {
6
6
  long: string;
7
7
  type: "boolean";
8
8
  };
9
- force: {
9
+ shouldForce: {
10
10
  long: string;
11
11
  type: "boolean";
12
12
  };
13
- noGitChecks: {
13
+ shouldSkipGitChecks: {
14
14
  long: string;
15
15
  type: "boolean";
16
16
  short: string;
@@ -27,7 +27,7 @@ export declare const prepareFlagSchema: {
27
27
  long: string;
28
28
  type: "string";
29
29
  };
30
- withReleaseNotes: {
30
+ shouldIncludeReleaseNotes: {
31
31
  long: string;
32
32
  type: "boolean";
33
33
  };
@@ -39,14 +39,14 @@ export declare const prepareFlagSchema: {
39
39
  /** Parses CLI arguments into structured options. Prints a usage error and exits on invalid input. */
40
40
  export declare function parseArgs(argv: string[]): {
41
41
  configPath: string | undefined;
42
- dryRun: boolean;
43
- force: boolean;
44
- noGitChecks: boolean;
42
+ isDryRun: boolean;
43
+ shouldForce: boolean;
44
+ shouldSkipGitChecks: boolean;
45
45
  bumpOverride: ReleaseType | undefined;
46
- only: string[] | undefined;
46
+ onlyNames: string[] | undefined;
47
47
  /** The raw `--set-version` value, whose accepted form depends on the mode. */
48
48
  setVersion: string | undefined;
49
- withReleaseNotes: boolean;
49
+ shouldIncludeReleaseNotes: boolean;
50
50
  };
51
51
  /**
52
52
  * Parses a monorepo `--set-version` value, a comma-separated list of `<target>@N.N.N` entries, into versions keyed by
@@ -4,9 +4,9 @@ const VALID_BUMP_TYPES = ['major', 'minor', 'patch'];
4
4
  export const CANONICAL_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
5
5
  export const prepareFlagSchema = {
6
6
  ...configFlagSchema,
7
- dryRun: { long: '--dry-run', type: 'boolean' },
8
- force: { long: '--force', type: 'boolean' },
9
- noGitChecks: {
7
+ isDryRun: { long: '--dry-run', type: 'boolean' },
8
+ shouldForce: { long: '--force', type: 'boolean' },
9
+ shouldSkipGitChecks: {
10
10
  long: '--no-git-checks',
11
11
  type: 'boolean',
12
12
  short: '-n',
@@ -14,7 +14,7 @@ export const prepareFlagSchema = {
14
14
  bump: { long: '--bump', type: 'string' },
15
15
  setVersion: { long: '--set-version', type: 'string' },
16
16
  only: { long: '--only', type: 'string' },
17
- withReleaseNotes: { long: '--with-release-notes', type: 'boolean' },
17
+ shouldIncludeReleaseNotes: { long: '--with-release-notes', type: 'boolean' },
18
18
  };
19
19
  export function parseArgs(argv) {
20
20
  const { flags } = parseArgsOrExit(argv, prepareFlagSchema);
@@ -26,19 +26,19 @@ export function parseArgs(argv) {
26
26
  }
27
27
  bumpOverride = flags.bump;
28
28
  }
29
- let only;
29
+ let onlyNames;
30
30
  if (flags.only !== undefined) {
31
- only = flags.only.split(',');
31
+ onlyNames = flags.only.split(',');
32
32
  }
33
33
  return {
34
34
  configPath: flags.config,
35
- dryRun: flags.dryRun,
36
- force: flags.force,
37
- noGitChecks: flags.noGitChecks,
35
+ isDryRun: flags.isDryRun,
36
+ shouldForce: flags.shouldForce,
37
+ shouldSkipGitChecks: flags.shouldSkipGitChecks,
38
38
  bumpOverride,
39
- only,
39
+ onlyNames,
40
40
  setVersion: flags.setVersion,
41
- withReleaseNotes: flags.withReleaseNotes,
41
+ shouldIncludeReleaseNotes: flags.shouldIncludeReleaseNotes,
42
42
  };
43
43
  }
44
44
  export function parseSetVersions(value, knownNames) {
@@ -3,28 +3,29 @@ import { formatGlyphLine, reportError } from '@williamthorsen/nmr-core';
3
3
  import { describeError } from '@williamthorsen/toolbelt.errors';
4
4
  import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
5
5
  import { assertReleasesOnNpm } from "./assertReleasesOnNpm.js";
6
+ import { assertTarballsComplete } from "./assertTarballsComplete.js";
6
7
  import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
7
8
  import { dim } from "./format.js";
8
9
  import { RELEASE_GLYPHS } from "./glyphs.js";
9
10
  import { loadValidatedConfig, reportConfigProblem, reportConfigWarnings } from "./loadValidatedConfig.js";
10
11
  import { CANONICAL_SEMVER_PATTERN, parseArgs, parseSetVersions } from "./prepareArgs.js";
12
+ import { prepareReleaseTargets } from "./prepareReleaseTargets.js";
11
13
  import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
12
14
  import { applyReleasePlan } from "./releasePlan.js";
13
- import { releasePrepareTargets } from "./releasePrepareTargets.js";
14
15
  import { reportPrepare } from "./reportPrepare.js";
15
16
  import { resolveConfigFlag } from "./resolveConfigFlag.js";
16
17
  import { isSinglePackageTargets, PROJECT_TARGET_ID, resolveReleaseTargets, SINGLE_PACKAGE_TARGET_ID, } from "./resolveReleaseTargets.js";
17
18
  export async function prepareCommand(argv, styles, invocationDir) {
18
- const { configPath: configFlag, dryRun, force, noGitChecks, bumpOverride, only, setVersion, withReleaseNotes, } = parseArgs(argv);
19
+ const { configPath: configFlag, isDryRun, shouldForce, shouldSkipGitChecks, bumpOverride, onlyNames, setVersion, shouldIncludeReleaseNotes, } = parseArgs(argv);
19
20
  const options = {
20
- force,
21
+ shouldForce,
21
22
  ...(bumpOverride !== undefined && { bumpOverride }),
22
- ...(withReleaseNotes && { withReleaseNotes: true }),
23
+ ...(shouldIncludeReleaseNotes && { shouldIncludeReleaseNotes: true }),
23
24
  };
24
- if (dryRun) {
25
+ if (isDryRun) {
25
26
  console.info(`\n${formatGlyphLine(RELEASE_GLYPHS, styles.stdout, 'dryRun', 'DRY RUN: release-kit will not modify any files')}\n`);
26
27
  }
27
- if (!dryRun && !noGitChecks) {
28
+ if (!isDryRun && !shouldSkipGitChecks) {
28
29
  try {
29
30
  assertCleanWorkingTree();
30
31
  }
@@ -43,37 +44,39 @@ export async function prepareCommand(argv, styles, invocationDir) {
43
44
  reportConfigWarnings(configResult.warnings, styles.stderr);
44
45
  userConfig = configResult.config;
45
46
  }
46
- let workspace;
47
+ let workspaceDiscovery;
47
48
  try {
48
- workspace = discoverWorkspaces();
49
+ workspaceDiscovery = discoverWorkspaces();
49
50
  }
50
51
  catch (error) {
51
52
  reportError(`Failed to discover workspaces: ${describeError(error)}`);
52
53
  process.exit(1);
53
54
  }
54
- if (workspace.kind === 'empty') {
55
- reportError(`No workspace package to release. ${describeEmptyWorkspace(workspace)}`);
55
+ if (workspaceDiscovery.kind === 'empty') {
56
+ reportError(`No workspace package to release. ${describeEmptyWorkspace(workspaceDiscovery)}`);
56
57
  process.exit(1);
57
58
  }
58
- const config = resolveTargetsOrExit(userConfig, workspace, only, setVersion);
59
+ const config = resolveTargetsOrExit(userConfig, workspaceDiscovery, onlyNames, setVersion);
59
60
  const targetOptions = {
60
61
  ...options,
61
- ...(only !== undefined && { only }),
62
- ...(setVersion !== undefined && { setVersions: resolveSetVersions(setVersion, config.targets) }),
62
+ ...(onlyNames !== undefined && { onlyTargetIds: onlyNames }),
63
+ ...(setVersion !== undefined && { versionOverridesById: resolveSetVersions(setVersion, config.targets) }),
63
64
  };
64
65
  runAndReport(() => {
65
- const plan = releasePrepareTargets(config, targetOptions);
66
- assertReleasesOnNpm(findReleasedPackagePaths(plan, config.targets));
66
+ const plan = prepareReleaseTargets(config, targetOptions);
67
+ const releasedPackagePaths = findReleasedPackagePaths(plan, config.targets);
68
+ assertReleasesOnNpm(releasedPackagePaths);
69
+ assertTarballsComplete(releasedPackagePaths, userConfig?.devExportConditions ?? []);
67
70
  return plan;
68
- }, dryRun, styles.stdout);
71
+ }, isDryRun, styles.stdout);
69
72
  }
70
73
  function findReleasedPackagePaths(plan, targets) {
71
74
  const releasedIds = new Set(plan.targets.flatMap((result) => result.status === 'released' && result.kind === 'package' ? result.targetId : []));
72
75
  return targets.flatMap((target) => target.kind === 'package' && releasedIds.has(target.id) ? target.packagePath : []);
73
76
  }
74
- function resolveTargetsOrExit(userConfig, discovery, only, setVersion) {
77
+ function resolveTargetsOrExit(userConfig, discovery, onlyNames, setVersion) {
75
78
  if (discovery.kind === 'single-package') {
76
- if (only !== undefined) {
79
+ if (onlyNames !== undefined) {
77
80
  reportError('--only is only supported for monorepo configurations');
78
81
  process.exit(1);
79
82
  }
@@ -98,8 +101,8 @@ function resolveTargetsOrExit(userConfig, discovery, only, setVersion) {
98
101
  process.exit(1);
99
102
  }
100
103
  const knownNames = listSelectableIds(config.targets);
101
- const onlyNames = only ?? [];
102
- for (const name of onlyNames) {
104
+ const requestedNames = onlyNames ?? [];
105
+ for (const name of requestedNames) {
103
106
  if (knownNames.includes(name)) {
104
107
  continue;
105
108
  }
@@ -120,7 +123,7 @@ function listSelectableIds(targets) {
120
123
  function listMemberIds(targets) {
121
124
  return targets.flatMap((target) => (target.kind === 'composite' ? [] : target.id));
122
125
  }
123
- function runAndReport(computePlan, dryRun, style) {
126
+ function runAndReport(computePlan, isDryRun, style) {
124
127
  let plan;
125
128
  try {
126
129
  plan = computePlan();
@@ -130,7 +133,7 @@ function runAndReport(computePlan, dryRun, style) {
130
133
  process.stderr.write('The command did not write any files; the working tree is unchanged.\n');
131
134
  process.exit(1);
132
135
  }
133
- if (dryRun) {
136
+ if (isDryRun) {
134
137
  process.stdout.write(reportPrepare(plan, { applied: false, style }) + '\n');
135
138
  reportPlanFiles(plan, true);
136
139
  return;
@@ -164,11 +167,11 @@ function runFormatCommand(formatCommand) {
164
167
  return describeError(error);
165
168
  }
166
169
  }
167
- function reportPlanFiles(plan, dryRun) {
170
+ function reportPlanFiles(plan, isDryRun) {
168
171
  if (plan.tags.length === 0) {
169
172
  return;
170
173
  }
171
- if (dryRun) {
174
+ if (isDryRun) {
172
175
  console.info(dim(` [dry-run] Would write ${RELEASE_TAGS_FILE}: ${plan.tags.join(' ')}`));
173
176
  if (plan.summary.length > 0) {
174
177
  console.info(dim(` [dry-run] Would write ${RELEASE_SUMMARY_FILE}`));