@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,16 +1,16 @@
1
1
  import type { RepoType } from './detectRepoType.js';
2
- /** Generates the `.config/release-kit.config.ts` starter config, with commented-out options to customize. */
3
- export declare function releaseConfigScript(repoType: RepoType): string;
2
+ /** Renders the `.config/release-kit.config.ts` starter config, with commented-out options to customize. */
3
+ export declare function renderReleaseConfigScript(repoType: RepoType): string;
4
4
  /**
5
- * Generates the publish.yaml GitHub Actions entry-point workflow.
5
+ * Renders the publish.yaml GitHub Actions entry-point workflow.
6
6
  *
7
7
  * The caller's `permissions` block caps what the reusable workflow receives, so it must grant the `id-token: write`
8
- * that OIDC publishing needs. `provenance: false` omits the provenance input, which a private repo cannot use.
8
+ * that OIDC publishing needs. `shouldGenerateProvenance: false` omits the provenance input, which a private repo cannot use.
9
9
  */
10
- export declare function publishWorkflow(repoType: RepoType, { provenance }?: {
11
- provenance?: boolean;
10
+ export declare function renderPublishWorkflow(repoType: RepoType, { shouldGenerateProvenance }?: {
11
+ shouldGenerateProvenance?: boolean;
12
12
  }): string;
13
- /** Generates the create-github-release.yaml GitHub Actions caller workflow. */
14
- export declare function createGithubReleaseWorkflow(repoType: RepoType): string;
15
- /** Generates the release.yaml GitHub Actions workflow. */
16
- export declare function releaseWorkflow(repoType: RepoType): string;
13
+ /** Renders the create-github-release.yaml GitHub Actions caller workflow. */
14
+ export declare function renderCreateGithubReleaseWorkflow(repoType: RepoType): string;
15
+ /** Renders the release.yaml GitHub Actions workflow. */
16
+ export declare function renderReleaseWorkflow(repoType: RepoType): string;
@@ -1,4 +1,4 @@
1
- export function releaseConfigScript(repoType) {
1
+ export function renderReleaseConfigScript(repoType) {
2
2
  if (repoType === 'monorepo') {
3
3
  return `import { defineConfig } from '@williamthorsen/release-kit/config';
4
4
 
@@ -39,7 +39,7 @@ export default defineConfig({
39
39
  });
40
40
  `;
41
41
  }
42
- export function publishWorkflow(repoType, { provenance = true } = {}) {
42
+ export function renderPublishWorkflow(repoType, { shouldGenerateProvenance = true } = {}) {
43
43
  const tagPattern = repoType === 'monorepo' ? "'*-v[0-9]*.[0-9]*.[0-9]*'" : "'v[0-9]*.[0-9]*.[0-9]*'";
44
44
  return `# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
45
45
  name: Publish
@@ -57,10 +57,10 @@ jobs:
57
57
  publish:
58
58
  uses: williamthorsen/node-monorepo-tools/.github/workflows/publish.reusable.yaml@workflow/publish-v1
59
59
  with:
60
- ${provenance ? ' provenance: true\n' : ''} tags: \${{ github.ref_name }}
60
+ ${shouldGenerateProvenance ? ' provenance: true\n' : ''} tags: \${{ github.ref_name }}
61
61
  `;
62
62
  }
63
- export function createGithubReleaseWorkflow(repoType) {
63
+ export function renderCreateGithubReleaseWorkflow(repoType) {
64
64
  const tagPattern = repoType === 'monorepo' ? "'*-v[0-9]*.[0-9]*.[0-9]*'" : "'v[0-9]*.[0-9]*.[0-9]*'";
65
65
  return `# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
66
66
  name: Create GitHub release
@@ -80,7 +80,7 @@ jobs:
80
80
  tag: \${{ github.ref_name }}
81
81
  `;
82
82
  }
83
- export function releaseWorkflow(repoType) {
83
+ export function renderReleaseWorkflow(repoType) {
84
84
  if (repoType === 'monorepo') {
85
85
  return `# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
86
86
  name: Release
@@ -1,6 +1,6 @@
1
1
  import { type StreamStyles } from '@williamthorsen/nmr-core';
2
2
  interface UpdateTemplatesOptions {
3
- dryRun: boolean;
3
+ isDryRun: boolean;
4
4
  styles: StreamStyles;
5
5
  }
6
6
  /**
@@ -10,5 +10,5 @@ interface UpdateTemplatesOptions {
10
10
  * `sync-labels.yaml` is refreshed only when it exists, as are the publishing workflows in a repo whose packages are
11
11
  * all private. Config files are never read or written.
12
12
  */
13
- export declare function updateTemplatesCommand({ dryRun, styles }: UpdateTemplatesOptions): number;
13
+ export declare function updateTemplatesCommand({ isDryRun, styles }: UpdateTemplatesOptions): number;
14
14
  export {};
@@ -7,10 +7,10 @@ import { hasProvenance } from "./hasProvenance.js";
7
7
  import { formatPrivateWorkflowSkip, hasPublishablePackage } from "./hasPublishablePackage.js";
8
8
  import { checkEligibility } from "./initCommand.js";
9
9
  import { CREATE_GITHUB_RELEASE_WORKFLOW_PATH, PUBLISH_WORKFLOW_PATH, PUBLISHING_WORKFLOW_PATHS } from "./scaffold.js";
10
- import { createGithubReleaseWorkflow, publishWorkflow, releaseWorkflow } from "./templates.js";
10
+ import { renderCreateGithubReleaseWorkflow, renderPublishWorkflow, renderReleaseWorkflow } from "./templates.js";
11
11
  const SYNC_LABELS_WORKFLOW_PATH = '.github/workflows/sync-labels.yaml';
12
- export function updateTemplatesCommand({ dryRun, styles }) {
13
- if (dryRun) {
12
+ export function updateTemplatesCommand({ isDryRun, styles }) {
13
+ if (isDryRun) {
14
14
  console.info('[dry-run mode]');
15
15
  }
16
16
  let eligible;
@@ -36,32 +36,34 @@ export function updateTemplatesCommand({ dryRun, styles }) {
36
36
  printStep('Updating workflows');
37
37
  let results;
38
38
  try {
39
- const { files, skipped } = buildManagedFiles(repoType, hasPublishablePackage());
40
- for (const filePath of skipped) {
39
+ const { files, skippedPaths } = buildManagedFiles(repoType, hasPublishablePackage());
40
+ for (const filePath of skippedPaths) {
41
41
  printSkip(formatPrivateWorkflowSkip(filePath), styles.stdout);
42
42
  }
43
- results = files.map(([filePath, content]) => updateManagedFile(filePath, content, { isDryRun: dryRun }));
43
+ results = files.map(([filePath, content]) => updateManagedFile(filePath, content, { isDryRun }));
44
44
  }
45
45
  catch (error) {
46
46
  printError(`Failed to render workflows: ${describeError(error)}`, styles.stderr);
47
47
  return 1;
48
48
  }
49
49
  for (const result of results) {
50
- reportTemplateUpdate(result, dryRun, styles);
50
+ reportTemplateUpdate(result, isDryRun, styles);
51
51
  }
52
52
  return results.some((result) => result.outcome === 'failed') ? 1 : 0;
53
53
  }
54
54
  function buildManagedFiles(repoType, publishable) {
55
- const skipped = publishable ? [] : PUBLISHING_WORKFLOW_PATHS.filter((filePath) => !existsSync(filePath));
55
+ const skippedPaths = publishable
56
+ ? []
57
+ : PUBLISHING_WORKFLOW_PATHS.filter((filePath) => !existsSync(filePath));
56
58
  const files = [
57
- [CREATE_GITHUB_RELEASE_WORKFLOW_PATH, createGithubReleaseWorkflow(repoType)],
58
- [PUBLISH_WORKFLOW_PATH, publishWorkflow(repoType, { provenance: readProvenanceSetting() })],
59
- ['.github/workflows/release.yaml', releaseWorkflow(repoType)],
59
+ [CREATE_GITHUB_RELEASE_WORKFLOW_PATH, renderCreateGithubReleaseWorkflow(repoType)],
60
+ [PUBLISH_WORKFLOW_PATH, renderPublishWorkflow(repoType, { shouldGenerateProvenance: readProvenanceSetting() })],
61
+ ['.github/workflows/release.yaml', renderReleaseWorkflow(repoType)],
60
62
  ];
61
63
  if (existsSync(SYNC_LABELS_WORKFLOW_PATH)) {
62
64
  files.push([SYNC_LABELS_WORKFLOW_PATH, readSyncLabelsWorkflowTemplate()]);
63
65
  }
64
- return { files: files.filter(([filePath]) => !skipped.includes(filePath)), skipped };
66
+ return { files: files.filter(([filePath]) => !skippedPaths.includes(filePath)), skippedPaths };
65
67
  }
66
68
  function readProvenanceSetting() {
67
69
  if (!existsSync(PUBLISH_WORKFLOW_PATH))
@@ -16,7 +16,7 @@ export type RenderInjectedReadmeSkipReason = 'no-entry' | 'empty-body';
16
16
  /** Discriminated outcome of rendering release notes for a tag. */
17
17
  export type RenderInjectedReadmeResult = {
18
18
  status: 'rendered';
19
- rendered: RenderedInjectedReadme;
19
+ renderedReadme: RenderedInjectedReadme;
20
20
  } | {
21
21
  status: 'skipped';
22
22
  reason: RenderInjectedReadmeSkipReason;
@@ -11,7 +11,7 @@ export function renderInjectedReadmeFromEntries(readme, entries, tag, sectionOrd
11
11
  }
12
12
  const renderedSections = renderReleaseNotesSingle(entry, {
13
13
  filter: matchesAudience('all'),
14
- includeHeading: false,
14
+ shouldIncludeHeading: false,
15
15
  ...(sectionOrder !== undefined && { sectionOrder }),
16
16
  });
17
17
  if (renderedSections.trimEnd().length === 0) {
@@ -20,7 +20,7 @@ export function renderInjectedReadmeFromEntries(readme, entries, tag, sectionOrd
20
20
  const labeledHeading = `## Release notes — v${version} (${entry.date})`;
21
21
  const releaseNotesMarkdown = `${labeledHeading}\n\n${renderedSections.trimEnd()}`;
22
22
  const injectedReadme = injectSection(readme, 'release-notes', releaseNotesMarkdown);
23
- return { status: 'rendered', rendered: { injectedReadme, releaseNotesMarkdown } };
23
+ return { status: 'rendered', renderedReadme: { injectedReadme, releaseNotesMarkdown } };
24
24
  }
25
25
  export function renderInjectedReadme(readme, changelogJsonPath, tag, sectionOrder) {
26
26
  if (!existsSync(changelogJsonPath)) {
@@ -37,15 +37,15 @@ export function renderInjectedReadme(readme, changelogJsonPath, tag, sectionOrde
37
37
  console.warn(`Warning: ${describeRenderSkip(result.reason, result.version)}; skipping README injection`);
38
38
  return undefined;
39
39
  }
40
- return result.rendered;
40
+ return result.renderedReadme;
41
41
  }
42
42
  export function injectReleaseNotesIntoReadme(readmePath, changelogJsonPath, tag, sectionOrder) {
43
43
  const originalReadme = readFileSync(readmePath, 'utf8');
44
- const rendered = renderInjectedReadme(originalReadme, changelogJsonPath, tag, sectionOrder);
45
- if (rendered === undefined) {
44
+ const renderResult = renderInjectedReadme(originalReadme, changelogJsonPath, tag, sectionOrder);
45
+ if (renderResult === undefined) {
46
46
  return undefined;
47
47
  }
48
- writeFileSync(readmePath, rendered.injectedReadme, 'utf8');
48
+ writeFileSync(readmePath, renderResult.injectedReadme, 'utf8');
49
49
  return originalReadme;
50
50
  }
51
51
  export function resolveReadmePath(workspacePath) {
@@ -5,16 +5,16 @@ function closeMarker(key) {
5
5
  return `<!-- /section:${key} -->`;
6
6
  }
7
7
  export function injectSection(content, key, injection) {
8
- const open = openMarker(key);
9
- const close = closeMarker(key);
10
- const openIndex = content.indexOf(open);
11
- const closeIndex = content.indexOf(close);
8
+ const openComment = openMarker(key);
9
+ const closeComment = closeMarker(key);
10
+ const openIndex = content.indexOf(openComment);
11
+ const closeIndex = content.indexOf(closeComment);
12
12
  if (openIndex !== -1 && closeIndex !== -1 && closeIndex > openIndex) {
13
- const before = content.slice(0, openIndex + open.length);
14
- const after = content.slice(closeIndex);
15
- return `${before}\n${injection}\n${after}`;
13
+ const contentBefore = content.slice(0, openIndex + openComment.length);
14
+ const contentAfter = content.slice(closeIndex);
15
+ return `${contentBefore}\n${injection}\n${contentAfter}`;
16
16
  }
17
- const section = `${open}\n${injection}\n${close}`;
17
+ const section = `${openComment}\n${injection}\n${closeComment}`;
18
18
  if (content.length === 0) {
19
19
  return `${section}\n`;
20
20
  }
@@ -25,7 +25,7 @@ export declare function parseOverrideKey(key: string): {
25
25
  * Each error names the offending key (e.g. `overrides['abc']: 'audience' must be one of …`) so that the user can find
26
26
  * it in the override file.
27
27
  */
28
- export declare function validateChangelogOverrides(raw: unknown): {
28
+ export declare function validateChangelogOverrides(rawValue: unknown): {
29
29
  overrides: Map<string, ChangelogOverride>;
30
30
  errors: string[];
31
31
  };
@@ -18,16 +18,16 @@ export function loadChangelogOverrides(path) {
18
18
  errors: [`Failed to read override file ${path}: ${describeError(error)}`],
19
19
  };
20
20
  }
21
- let parsed;
21
+ let parsedJson;
22
22
  try {
23
- parsed = JSON.parse(content);
23
+ parsedJson = JSON.parse(content);
24
24
  }
25
25
  catch (error) {
26
26
  return {
27
27
  errors: [`Failed to parse override file ${path}: ${describeError(error)}`],
28
28
  };
29
29
  }
30
- const result = validateChangelogOverrides(parsed);
30
+ const result = validateChangelogOverrides(parsedJson);
31
31
  if (result.errors.length > 0) {
32
32
  return { errors: result.errors };
33
33
  }
@@ -41,14 +41,14 @@ export function parseOverrideKey(key) {
41
41
  }
42
42
  return { hashPrefix: key.slice(0, separatorIndex), entry: Number(key.slice(separatorIndex + 1)) };
43
43
  }
44
- export function validateChangelogOverrides(raw) {
44
+ export function validateChangelogOverrides(rawValue) {
45
45
  const overrides = new Map();
46
46
  const errors = [];
47
- if (!isRecord(raw)) {
47
+ if (!isRecord(rawValue)) {
48
48
  errors.push('Override file: top-level value must be an object keyed by commit hash');
49
49
  return { overrides, errors };
50
50
  }
51
- for (const [key, rawEntry] of Object.entries(raw)) {
51
+ for (const [key, rawEntry] of Object.entries(rawValue)) {
52
52
  if (key === SCHEMA_KEY) {
53
53
  if (typeof rawEntry !== 'string') {
54
54
  errors.push(`Override file: '${SCHEMA_KEY}' must be a string`);
@@ -59,9 +59,9 @@ export function validateChangelogOverrides(raw) {
59
59
  errors.push(`overrides['${key}']: key must be a lowercase hex commit hash or prefix, optionally followed by ':<n>'`);
60
60
  continue;
61
61
  }
62
- const validated = validateSingleOverride(key, rawEntry, errors);
63
- if (validated !== undefined) {
64
- overrides.set(key, validated);
62
+ const validatedOverride = validateSingleOverride(key, rawEntry, errors);
63
+ if (validatedOverride !== undefined) {
64
+ overrides.set(key, validatedOverride);
65
65
  }
66
66
  }
67
67
  return { overrides, errors };
@@ -3,13 +3,13 @@ export declare const ROOT_PACKAGE_JSON_PATH = "package.json";
3
3
  /**
4
4
  * Reads the root `package.json` and returns its `version` field.
5
5
  *
6
- * Returns `{ exists: false }` when the file is missing, `{ exists: true, version: undefined }`
7
- * when the file exists but does not have a `version` field, and `{ exists: true, version }`
6
+ * Returns `{ doesExist: false }` when the file is missing, `{ doesExist: true, version: undefined }`
7
+ * when the file exists but does not have a `version` field, and `{ doesExist: true, version }`
8
8
  * when both are present; `mergeMonorepoConfig` decides whether the situation is an error.
9
9
  * Throws when the file cannot be read or parsed.
10
10
  */
11
11
  export declare function readRootPackageVersion(): {
12
- exists: boolean;
12
+ doesExist: boolean;
13
13
  version: string | undefined;
14
14
  };
15
15
  /** The path where the consumer-facing config file is expected. */
@@ -15,7 +15,7 @@ export const ROOT_PACKAGE_JSON_PATH = 'package.json';
15
15
  export function readRootPackageVersion() {
16
16
  const absolutePath = path.resolve(process.cwd(), ROOT_PACKAGE_JSON_PATH);
17
17
  if (!existsSync(absolutePath)) {
18
- return { exists: false, version: undefined };
18
+ return { doesExist: false, version: undefined };
19
19
  }
20
20
  let contents;
21
21
  try {
@@ -24,17 +24,17 @@ export function readRootPackageVersion() {
24
24
  catch (error) {
25
25
  throw chainError(`Failed to read root ${ROOT_PACKAGE_JSON_PATH}`, error);
26
26
  }
27
- let parsed;
27
+ let parsedJson;
28
28
  try {
29
- parsed = JSON.parse(contents);
29
+ parsedJson = JSON.parse(contents);
30
30
  }
31
31
  catch (error) {
32
32
  throw chainError(`Failed to parse root ${ROOT_PACKAGE_JSON_PATH}`, error);
33
33
  }
34
- if (!isRecord(parsed)) {
35
- return { exists: true, version: undefined };
34
+ if (!isRecord(parsedJson)) {
35
+ return { doesExist: true, version: undefined };
36
36
  }
37
- return { exists: true, version: typeof parsed['version'] === 'string' ? parsed['version'] : undefined };
37
+ return { doesExist: true, version: typeof parsedJson['version'] === 'string' ? parsedJson['version'] : undefined };
38
38
  }
39
39
  export const CONFIG_FILE_PATH = '.config/release-kit.config.ts';
40
40
  export async function loadConfig(configPath) {
@@ -45,10 +45,10 @@ export async function loadConfig(configPath) {
45
45
  }
46
46
  throw new Error(`Config file not found: ${absoluteConfigPath}`);
47
47
  }
48
- const imported = await import(__rewriteRelativeImportExtension(pathToFileURL(absoluteConfigPath).href));
49
- const resolved = isRecord(imported) ? (imported['default'] ?? imported['config']) : undefined;
50
- if (resolved === undefined) {
48
+ const importedModule = await import(__rewriteRelativeImportExtension(pathToFileURL(absoluteConfigPath).href));
49
+ const resolvedConfig = isRecord(importedModule) ? (importedModule['default'] ?? importedModule['config']) : undefined;
50
+ if (resolvedConfig === undefined) {
51
51
  throw new Error('Config file must have a default export or a named `config` export (e.g., `export default { ... }` or `export const config = { ... }`)');
52
52
  }
53
- return resolved;
53
+ return resolvedConfig;
54
54
  }
@@ -4,17 +4,17 @@ import { CONFIG_FILE_PATH, loadConfig } from "./loadConfig.js";
4
4
  import { validateConfig } from "./validateConfig.js";
5
5
  export async function loadValidatedConfig(configPath) {
6
6
  const configFilePath = configPath ?? CONFIG_FILE_PATH;
7
- let raw;
7
+ let rawConfig;
8
8
  try {
9
- raw = await loadConfig(configPath);
9
+ rawConfig = await loadConfig(configPath);
10
10
  }
11
11
  catch (error) {
12
12
  return { status: 'invalid', configFilePath, problem: { kind: 'load', message: describeError(error) } };
13
13
  }
14
- if (raw === undefined) {
14
+ if (rawConfig === undefined) {
15
15
  return { status: 'missing', configFilePath };
16
16
  }
17
- const { config, errors, warnings } = validateConfig(raw);
17
+ const { config, errors, warnings } = validateConfig(rawConfig);
18
18
  if (errors.length > 0) {
19
19
  return { status: 'invalid', configFilePath, problem: { kind: 'validation', errors } };
20
20
  }
@@ -36,6 +36,6 @@ export declare function resolveWorkTypes(userWorkTypes?: ReleaseKitConfig['workT
36
36
  * `mergeMonorepoConfig` does not perform any I/O.
37
37
  */
38
38
  export interface RootPackageInfo {
39
- exists: boolean;
39
+ doesExist: boolean;
40
40
  version: string | undefined;
41
41
  }
@@ -5,10 +5,10 @@ import { ROOT_PACKAGE_JSON_PATH } from "./loadConfig.js";
5
5
  import { assertLegacyIdentityDoesNotMatchCurrent, assertNoSharedVersionFiles, assertNoTagPrefixCollisions, assertRetiredPackagesDoNotCollideWithActive, assertUniqueTagPrefixes, } from "./monorepoConfigAssertions.js";
6
6
  import { resolveReleaseDeclaration } from "./resolveReleaseDeclaration.js";
7
7
  export function mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage) {
8
- const discovered = discoveredPaths.map((workspacePath) => deriveWorkspaceConfig(workspacePath));
9
- assertUniqueTagPrefixes(discovered);
10
- const { retained: workspaces, excluded, directories: declaredDirectories, composite, } = resolveReleaseDeclaration(discovered, userConfig);
11
- const directories = declaredDirectories.map((declared) => deriveDirectoryConfig(declared));
8
+ const discoveredWorkspaces = discoveredPaths.map((workspacePath) => deriveWorkspaceConfig(workspacePath));
9
+ assertUniqueTagPrefixes(discoveredWorkspaces);
10
+ const { retainedWorkspaces: workspaces, excludedWorkspaces, directories: declaredDirectories, composite: declaredComposite, } = resolveReleaseDeclaration(discoveredWorkspaces, userConfig);
11
+ const directories = declaredDirectories.map((declaredDirectory) => deriveDirectoryConfig(declaredDirectory));
12
12
  for (const workspace of workspaces) {
13
13
  if (workspace.legacyIdentities !== undefined) {
14
14
  assertLegacyIdentityDoesNotMatchCurrent(workspace.dir, workspace.name, workspace.tagPrefix, workspace.legacyIdentities);
@@ -17,13 +17,13 @@ export function mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage) {
17
17
  if (userConfig?.retiredPackages !== undefined) {
18
18
  assertRetiredPackagesDoNotCollideWithActive(workspaces, userConfig.retiredPackages);
19
19
  }
20
- const project = resolveProjectConfig(composite, rootPackage, workspaces);
20
+ const projectConfig = resolveProjectConfig(declaredComposite, rootPackage, workspaces);
21
21
  const workTypes = resolveWorkTypes(userConfig?.workTypes);
22
22
  const versionPatterns = userConfig?.versionPatterns === undefined ? { ...DEFAULT_VERSION_PATTERNS } : { ...userConfig.versionPatterns };
23
23
  const changelogJson = mergeChangelogJsonConfig(userConfig?.changelogJson);
24
24
  const releaseNotes = mergeReleaseNotesConfig(userConfig?.releaseNotes);
25
- assertNoSharedVersionFiles(workspaces, directories, project);
26
- assertNoTagPrefixCollisions(workspaces, directories, userConfig?.retiredPackages, project);
25
+ assertNoSharedVersionFiles(workspaces, directories, projectConfig);
26
+ assertNoTagPrefixCollisions(workspaces, directories, userConfig?.retiredPackages, projectConfig);
27
27
  const result = {
28
28
  workspaces,
29
29
  workTypes,
@@ -31,14 +31,14 @@ export function mergeMonorepoConfig(discoveredPaths, userConfig, rootPackage) {
31
31
  changelogJson,
32
32
  releaseNotes,
33
33
  };
34
- if (excluded.length > 0) {
35
- result.excludedWorkspaces = excluded;
34
+ if (excludedWorkspaces.length > 0) {
35
+ result.excludedWorkspaces = excludedWorkspaces;
36
36
  }
37
37
  if (directories.length > 0) {
38
38
  result.directories = directories;
39
39
  }
40
- if (project !== undefined) {
41
- result.project = project;
40
+ if (projectConfig !== undefined) {
41
+ result.project = projectConfig;
42
42
  }
43
43
  applyOptionalPassthroughFields(result, userConfig);
44
44
  return result;
@@ -80,39 +80,39 @@ function applyOptionalPassthroughFields(result, userConfig) {
80
80
  result.breakingPolicies = userConfig.breakingPolicies;
81
81
  }
82
82
  }
83
- function mergeChangelogJsonConfig(partial) {
84
- if (partial === undefined) {
83
+ function mergeChangelogJsonConfig(partialConfig) {
84
+ if (partialConfig === undefined) {
85
85
  return { ...DEFAULT_CHANGELOG_JSON_CONFIG };
86
86
  }
87
87
  return {
88
- enabled: partial.enabled ?? DEFAULT_CHANGELOG_JSON_CONFIG.enabled,
89
- outputPath: partial.outputPath ?? DEFAULT_CHANGELOG_JSON_CONFIG.outputPath,
90
- devOnlySections: partial.devOnlySections ?? [...DEFAULT_CHANGELOG_JSON_CONFIG.devOnlySections],
88
+ enabled: partialConfig.enabled ?? DEFAULT_CHANGELOG_JSON_CONFIG.enabled,
89
+ outputPath: partialConfig.outputPath ?? DEFAULT_CHANGELOG_JSON_CONFIG.outputPath,
90
+ devOnlySections: partialConfig.devOnlySections ?? [...DEFAULT_CHANGELOG_JSON_CONFIG.devOnlySections],
91
91
  };
92
92
  }
93
- function mergeReleaseNotesConfig(partial) {
94
- if (partial === undefined) {
93
+ function mergeReleaseNotesConfig(partialConfig) {
94
+ if (partialConfig === undefined) {
95
95
  return { ...DEFAULT_RELEASE_NOTES_CONFIG };
96
96
  }
97
97
  return {
98
- shouldInjectIntoReadme: partial.shouldInjectIntoReadme ?? DEFAULT_RELEASE_NOTES_CONFIG.shouldInjectIntoReadme,
98
+ shouldInjectIntoReadme: partialConfig.shouldInjectIntoReadme ?? DEFAULT_RELEASE_NOTES_CONFIG.shouldInjectIntoReadme,
99
99
  };
100
100
  }
101
- function resolveProjectConfig(composite, rootPackage, workspaces) {
102
- if (composite === undefined) {
101
+ function resolveProjectConfig(declaredComposite, rootPackage, workspaces) {
102
+ if (declaredComposite === undefined) {
103
103
  return undefined;
104
104
  }
105
- const subject = composite.name === undefined ? 'project block' : `composite release '${composite.name}'`;
106
- if (rootPackage === undefined || !rootPackage.exists) {
105
+ const subject = declaredComposite.name === undefined ? 'project block' : `composite release '${declaredComposite.name}'`;
106
+ if (rootPackage === undefined || !rootPackage.doesExist) {
107
107
  throw new Error(`${subject} requires a root ${ROOT_PACKAGE_JSON_PATH}; create one with a 'version' field at the repo root`);
108
108
  }
109
109
  if (rootPackage.version === undefined) {
110
110
  throw new Error(`${subject} requires root ${ROOT_PACKAGE_JSON_PATH} to have a 'version' field; add a 'version' field to the root package.json`);
111
111
  }
112
112
  return {
113
- paths: composite.paths ?? workspaces.flatMap((workspace) => workspace.paths),
114
- tagPrefix: composite.tagPrefix ?? DEFAULT_PROJECT_TAG_PREFIX,
115
- ...(composite.name !== undefined && { name: composite.name }),
116
- ...(composite.members !== undefined && { members: composite.members }),
113
+ paths: declaredComposite.paths ?? workspaces.flatMap((workspace) => workspace.paths),
114
+ tagPrefix: declaredComposite.tagPrefix ?? DEFAULT_PROJECT_TAG_PREFIX,
115
+ ...(declaredComposite.name !== undefined && { name: declaredComposite.name }),
116
+ ...(declaredComposite.members !== undefined && { members: declaredComposite.members }),
117
117
  };
118
118
  }
@@ -63,10 +63,10 @@ export function assertNoTagPrefixCollisions(workspaces, directories, retiredPack
63
63
  }
64
64
  }
65
65
  const retiredEntries = (retiredPackages ?? []).entries();
66
- for (const [index, retired] of retiredEntries) {
66
+ for (const [index, retiredPackage] of retiredEntries) {
67
67
  sources.push({
68
- prefix: retired.tagPrefix,
69
- label: `retiredPackages entry (name='${retired.name}')`,
68
+ prefix: retiredPackage.tagPrefix,
69
+ label: `retiredPackages entry (name='${retiredPackage.name}')`,
70
70
  owner: `retired:${index}`,
71
71
  });
72
72
  }
@@ -77,10 +77,10 @@ export function assertNoTagPrefixCollisions(workspaces, directories, retiredPack
77
77
  owner: 'project',
78
78
  });
79
79
  }
80
- for (let i = 0; i < sources.length; i++) {
81
- for (let j = i + 1; j < sources.length; j++) {
82
- const a = sources[i];
83
- const b = sources[j];
80
+ for (let aIndex = 0; aIndex < sources.length; aIndex++) {
81
+ for (let bIndex = aIndex + 1; bIndex < sources.length; bIndex++) {
82
+ const a = sources[aIndex];
83
+ const b = sources[bIndex];
84
84
  if (a === undefined || b === undefined || a.owner === b.owner)
85
85
  continue;
86
86
  if (isPrefixCollision(a.prefix, b.prefix)) {
@@ -96,23 +96,23 @@ export function assertRetiredPackagesDoNotCollideWithActive(workspaces, retiredP
96
96
  for (const workspace of workspaces) {
97
97
  workspaceByDerivedPrefix.set(workspace.tagPrefix, workspace);
98
98
  }
99
- for (const retired of retiredPackages) {
100
- const active = workspaceByDerivedPrefix.get(retired.tagPrefix);
101
- if (active !== undefined) {
102
- throw new Error(`retiredPackages: tagPrefix '${retired.tagPrefix}' collides with active workspace '${active.dir}' ` +
103
- `(derived prefix '${active.tagPrefix}'). A retired package's tagPrefix cannot belong to an active workspace.`);
99
+ for (const retiredPackage of retiredPackages) {
100
+ const activeWorkspace = workspaceByDerivedPrefix.get(retiredPackage.tagPrefix);
101
+ if (activeWorkspace !== undefined) {
102
+ throw new Error(`retiredPackages: tagPrefix '${retiredPackage.tagPrefix}' collides with active workspace '${activeWorkspace.dir}' ` +
103
+ `(derived prefix '${activeWorkspace.tagPrefix}'). A retired package's tagPrefix cannot belong to an active workspace.`);
104
104
  }
105
105
  }
106
106
  }
107
107
  export function assertUniqueTagPrefixes(workspaces) {
108
108
  const pathsByPrefix = new Map();
109
109
  for (const workspace of workspaces) {
110
- const existing = pathsByPrefix.get(workspace.tagPrefix);
111
- if (existing === undefined) {
110
+ const existingPaths = pathsByPrefix.get(workspace.tagPrefix);
111
+ if (existingPaths === undefined) {
112
112
  pathsByPrefix.set(workspace.tagPrefix, [workspace.workspacePath]);
113
113
  }
114
114
  else {
115
- existing.push(workspace.workspacePath);
115
+ existingPaths.push(workspace.workspacePath);
116
116
  }
117
117
  }
118
118
  for (const [prefix, paths] of pathsByPrefix) {
@@ -34,17 +34,17 @@ export function lookUpNpmPackage(packageName, registry) {
34
34
  return classifyNpmPackageLookup(runNpmJson(args));
35
35
  }
36
36
  export function readNpmError(stdout) {
37
- let parsed;
37
+ let parsedJson;
38
38
  try {
39
- parsed = JSON.parse(stdout);
39
+ parsedJson = JSON.parse(stdout);
40
40
  }
41
41
  catch {
42
42
  return undefined;
43
43
  }
44
- if (!isRecord(parsed) || !isRecord(parsed['error']) || typeof parsed['error']['code'] !== 'string') {
44
+ if (!isRecord(parsedJson) || !isRecord(parsedJson['error']) || typeof parsedJson['error']['code'] !== 'string') {
45
45
  return undefined;
46
46
  }
47
- const { code, summary } = parsed['error'];
47
+ const { code, summary } = parsedJson['error'];
48
48
  return { code, summary: typeof summary === 'string' ? summary : '' };
49
49
  }
50
50
  export function runNpmJson(args) {
@@ -0,0 +1,11 @@
1
+ import { type PackageManager } from './detectPackageManager.js';
2
+ /**
3
+ * Packs each workspace as the publish environment would and returns each one's tarball entries, keyed by workspace
4
+ * path and relative to the package root.
5
+ *
6
+ * Clones HEAD of the repo at the current directory into a temporary directory, runs a frozen install there, then for
7
+ * each workspace runs its `prepublishOnly` when it declares one and packs it. Workspace paths are relative to the repo
8
+ * root. Throws an error that names the failing step and contains its output. Leaves the repo and its working tree
9
+ * untouched, and removes the temporary directory.
10
+ */
11
+ export declare function packFromCleanCheckout(workspacePaths: readonly string[], packageManager: PackageManager): Map<string, string[]>;