@williamthorsen/release-kit 15.0.3 → 16.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/.meta/changelog.json +66 -0
  2. package/.readyup/kits/default.js +13 -13
  3. package/.readyup/kits/npm-auto-publish.js +4 -4
  4. package/.readyup/manifest.json +12 -12
  5. package/CHANGELOG.md +19 -0
  6. package/README.md +7 -1
  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/bin/release-kit.js +16 -16
  11. package/dist/esm/buildChangelogEntries.d.ts +19 -19
  12. package/dist/esm/buildChangelogEntries.js +40 -36
  13. package/dist/esm/buildChangelogItem.js +24 -24
  14. package/dist/esm/buildDependencyGraph.js +23 -23
  15. package/dist/esm/buildReleasedResult.js +21 -19
  16. package/dist/esm/buildSyntheticChangelogEntry.js +2 -2
  17. package/dist/esm/bumpVersion.js +8 -8
  18. package/dist/esm/changelogArchive.d.ts +1 -1
  19. package/dist/esm/changelogArchive.js +2 -2
  20. package/dist/esm/changelogJsonFile.js +5 -5
  21. package/dist/esm/changelogJsonUtils.js +3 -3
  22. package/dist/esm/changelogOverrides.d.ts +5 -5
  23. package/dist/esm/changelogOverrides.js +23 -23
  24. package/dist/esm/classifyChangelogCommit.js +4 -4
  25. package/dist/esm/commitCommand.js +3 -3
  26. package/dist/esm/compareVersions.js +13 -13
  27. package/dist/esm/configSchema.d.ts +2 -2
  28. package/dist/esm/configSchema.js +1 -1
  29. package/dist/esm/createGithubRelease.d.ts +4 -4
  30. package/dist/esm/createGithubRelease.js +10 -10
  31. package/dist/esm/createGithubReleaseCommand.js +9 -9
  32. package/dist/esm/createTags.d.ts +2 -2
  33. package/dist/esm/createTags.js +8 -8
  34. package/dist/esm/decideDirectReleases.d.ts +5 -5
  35. package/dist/esm/decideDirectReleases.js +25 -25
  36. package/dist/esm/decideRelease.d.ts +1 -1
  37. package/dist/esm/decideRelease.js +2 -2
  38. package/dist/esm/deriveDirectoryConfig.d.ts +1 -1
  39. package/dist/esm/deriveDirectoryConfig.js +18 -18
  40. package/dist/esm/deriveWorkspaceConfig.js +4 -4
  41. package/dist/esm/detectPackageManager.js +5 -5
  42. package/dist/esm/detectUndeclaredTagPrefixes.js +6 -6
  43. package/dist/esm/enumerateReleaseWindows.d.ts +1 -1
  44. package/dist/esm/enumerateReleaseWindows.js +7 -7
  45. package/dist/esm/findTransitiveDependents.js +5 -5
  46. package/dist/esm/findUnroutedEntryScopes.js +11 -11
  47. package/dist/esm/format.d.ts +1 -1
  48. package/dist/esm/format.js +1 -1
  49. package/dist/esm/formatPrepareFiles.d.ts +3 -3
  50. package/dist/esm/formatPrepareFiles.js +10 -10
  51. package/dist/esm/formatPrepareWarnings.js +6 -6
  52. package/dist/esm/hasPrettierConfig.js +2 -2
  53. package/dist/esm/init/checks.js +5 -3
  54. package/dist/esm/init/detectRepoType.js +3 -3
  55. package/dist/esm/init/initCommand.d.ts +4 -4
  56. package/dist/esm/init/initCommand.js +7 -7
  57. package/dist/esm/init/parseJsonRecord.d.ts +1 -1
  58. package/dist/esm/init/parseJsonRecord.js +4 -4
  59. package/dist/esm/init/scaffold.d.ts +4 -4
  60. package/dist/esm/init/scaffold.js +15 -15
  61. package/dist/esm/init/templates.d.ts +10 -10
  62. package/dist/esm/init/templates.js +5 -5
  63. package/dist/esm/init/updateTemplatesCommand.d.ts +2 -2
  64. package/dist/esm/init/updateTemplatesCommand.js +14 -12
  65. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +1 -1
  66. package/dist/esm/injectReleaseNotesIntoReadme.js +6 -6
  67. package/dist/esm/injectSection.js +8 -8
  68. package/dist/esm/loadChangelogOverrides.d.ts +1 -1
  69. package/dist/esm/loadChangelogOverrides.js +9 -9
  70. package/dist/esm/loadConfig.d.ts +3 -3
  71. package/dist/esm/loadConfig.js +10 -10
  72. package/dist/esm/loadValidatedConfig.js +4 -4
  73. package/dist/esm/mergeConfig.d.ts +1 -1
  74. package/dist/esm/mergeConfig.js +27 -27
  75. package/dist/esm/monorepoConfigAssertions.js +15 -15
  76. package/dist/esm/npmRegistry.js +4 -4
  77. package/dist/esm/packsPath.js +3 -3
  78. package/dist/esm/parseChangeRecordBlock.js +33 -33
  79. package/dist/esm/parseCommitMessage.js +5 -5
  80. package/dist/esm/planChangelogFiles.js +8 -8
  81. package/dist/esm/planReleaseNotesPreviews.js +10 -10
  82. package/dist/esm/planVersionBump.js +24 -16
  83. package/dist/esm/prepareArgs.d.ts +9 -9
  84. package/dist/esm/prepareArgs.js +11 -11
  85. package/dist/esm/prepareCommand.js +23 -23
  86. package/dist/esm/prepareComposite.d.ts +4 -4
  87. package/dist/esm/prepareComposite.js +16 -16
  88. package/dist/esm/{releasePrepareTargets.d.ts → prepareReleaseTargets.d.ts} +9 -9
  89. package/dist/esm/{releasePrepareTargets.js → prepareReleaseTargets.js} +40 -40
  90. package/dist/esm/prepareRunContext.d.ts +1 -1
  91. package/dist/esm/prepareRunContext.js +5 -5
  92. package/dist/esm/prepareTarget.d.ts +3 -3
  93. package/dist/esm/prepareTarget.js +41 -39
  94. package/dist/esm/previewTagPrefixes.js +19 -19
  95. package/dist/esm/propagateBumps.js +8 -8
  96. package/dist/esm/publish.d.ts +2 -2
  97. package/dist/esm/publish.js +5 -5
  98. package/dist/esm/publishCommand.js +20 -20
  99. package/dist/esm/pushCommand.js +8 -8
  100. package/dist/esm/pushRelease.d.ts +1 -1
  101. package/dist/esm/pushRelease.js +2 -2
  102. package/dist/esm/readChangelogSections.d.ts +1 -1
  103. package/dist/esm/readChangelogSections.js +16 -16
  104. package/dist/esm/releasePlan.js +25 -25
  105. package/dist/esm/renderChangelogMarkdown.d.ts +4 -4
  106. package/dist/esm/renderChangelogMarkdown.js +18 -15
  107. package/dist/esm/renderReleaseNotes.d.ts +1 -1
  108. package/dist/esm/renderReleaseNotes.js +16 -16
  109. package/dist/esm/reportPrepare.js +34 -34
  110. package/dist/esm/resolveCommandTags.js +9 -9
  111. package/dist/esm/resolveReleaseDeclaration.d.ts +2 -2
  112. package/dist/esm/resolveReleaseDeclaration.js +22 -22
  113. package/dist/esm/resolveReleaseTags.js +17 -15
  114. package/dist/esm/resolveReleaseTargets.js +2 -2
  115. package/dist/esm/showTagPrefixesCommand.js +4 -4
  116. package/dist/esm/sync-labels/generateCommand.d.ts +3 -3
  117. package/dist/esm/sync-labels/generateCommand.js +10 -10
  118. package/dist/esm/sync-labels/initCommand.d.ts +3 -3
  119. package/dist/esm/sync-labels/initCommand.js +15 -15
  120. package/dist/esm/sync-labels/presets.js +3 -3
  121. package/dist/esm/sync-labels/resolveLabels.js +13 -13
  122. package/dist/esm/sync-labels/templates.d.ts +2 -2
  123. package/dist/esm/sync-labels/templates.js +1 -1
  124. package/dist/esm/tagCommand.js +4 -4
  125. package/dist/esm/topologicalSort.d.ts +2 -2
  126. package/dist/esm/topologicalSort.js +7 -7
  127. package/dist/esm/types.d.ts +3 -3
  128. package/dist/esm/validateAllChangelogOverrides.js +9 -9
  129. package/dist/esm/validateConfig.d.ts +1 -1
  130. package/dist/esm/validateConfig.js +60 -50
  131. package/dist/esm/validateOverridesCommand.js +2 -2
  132. package/docs/changelogs.md +5 -5
  133. package/docs/configuration.md +1 -1
  134. package/docs/work-types.md +1 -1
  135. package/package.json +1 -1
@@ -3,10 +3,10 @@ import { readFileSync } from 'node:fs';
3
3
  import { GIT_OUTPUT_LIMIT_BYTES, hasErrnoCode, parseArgsOrExit } from '@williamthorsen/nmr-core';
4
4
  import { readReleaseTags, RELEASE_SUMMARY_FILE, resolveReleaseTagsPath } from "./releaseFiles.js";
5
5
  const commitFlagSchema = {
6
- dryRun: { long: '--dry-run', type: 'boolean' },
6
+ isDryRun: { long: '--dry-run', type: 'boolean' },
7
7
  };
8
8
  export function commitCommand(argv) {
9
- const dryRun = parseArgsOrExit(argv, commitFlagSchema).flags.dryRun;
9
+ const isDryRun = parseArgsOrExit(argv, commitFlagSchema).flags.isDryRun;
10
10
  const tags = readReleaseTags();
11
11
  if (tags.length === 0) {
12
12
  throw new Error(`Tags file at ${resolveReleaseTagsPath()} is empty. Run \`release-kit prepare\` first.`);
@@ -24,7 +24,7 @@ export function commitCommand(argv) {
24
24
  }
25
25
  const title = `release: ${tags.join(' ')}`;
26
26
  const message = summary.length > 0 ? `${title}\n\n${summary}` : title;
27
- if (dryRun) {
27
+ if (isDryRun) {
28
28
  console.info('[dry-run] Would create commit with message:\n');
29
29
  console.info(message);
30
30
  try {
@@ -13,13 +13,13 @@ function parseCanonicalSemver(version) {
13
13
  };
14
14
  }
15
15
  export function compareVersionsDescending(a, b) {
16
- const aValid = semver.valid(a);
17
- const bValid = semver.valid(b);
18
- if (aValid && bValid)
19
- return semver.rcompare(aValid, bValid);
20
- if (aValid)
16
+ const aValidVersion = semver.valid(a);
17
+ const bValidVersion = semver.valid(b);
18
+ if (aValidVersion && bValidVersion)
19
+ return semver.rcompare(aValidVersion, bValidVersion);
20
+ if (aValidVersion)
21
21
  return -1;
22
- if (bValid)
22
+ if (bValidVersion)
23
23
  return 1;
24
24
  if (a > b)
25
25
  return -1;
@@ -28,13 +28,13 @@ export function compareVersionsDescending(a, b) {
28
28
  return 0;
29
29
  }
30
30
  export function isForwardVersion(current, target) {
31
- const currentParsed = parseCanonicalSemver(current);
32
- const targetParsed = parseCanonicalSemver(target);
33
- if (targetParsed.major !== currentParsed.major) {
34
- return targetParsed.major > currentParsed.major;
31
+ const currentParsedVersion = parseCanonicalSemver(current);
32
+ const targetParsedVersion = parseCanonicalSemver(target);
33
+ if (targetParsedVersion.major !== currentParsedVersion.major) {
34
+ return targetParsedVersion.major > currentParsedVersion.major;
35
35
  }
36
- if (targetParsed.minor !== currentParsed.minor) {
37
- return targetParsed.minor > currentParsed.minor;
36
+ if (targetParsedVersion.minor !== currentParsedVersion.minor) {
37
+ return targetParsedVersion.minor > currentParsedVersion.minor;
38
38
  }
39
- return targetParsed.patch > currentParsed.patch;
39
+ return targetParsedVersion.patch > currentParsedVersion.patch;
40
40
  }
@@ -93,7 +93,7 @@ export declare const changelogJsonInputSchema: z.ZodObject<{
93
93
  }, z.core.$strict>;
94
94
  /** Schema for the optional `changelogMarkdown` block (input shape, all fields optional). */
95
95
  export declare const changelogMarkdownInputSchema: z.ZodObject<{
96
- keepVersions: z.ZodOptional<z.ZodNumber>;
96
+ keptVersionCount: z.ZodOptional<z.ZodNumber>;
97
97
  }, z.core.$strict>;
98
98
  /** Schema for the optional `releaseNotes` block (input shape, all fields optional). */
99
99
  export declare const releaseNotesInputSchema: z.ZodObject<{
@@ -164,7 +164,7 @@ export declare const releaseKitConfigSchema: z.ZodObject<{
164
164
  devOnlySections: z.ZodOptional<z.ZodArray<z.ZodString>>;
165
165
  }, z.core.$strict>>;
166
166
  changelogMarkdown: z.ZodOptional<z.ZodObject<{
167
- keepVersions: z.ZodOptional<z.ZodNumber>;
167
+ keptVersionCount: z.ZodOptional<z.ZodNumber>;
168
168
  }, z.core.$strict>>;
169
169
  formatCommand: z.ZodOptional<z.ZodString>;
170
170
  project: z.ZodOptional<z.ZodObject<{
@@ -48,7 +48,7 @@ export const changelogJsonInputSchema = z
48
48
  .strict();
49
49
  export const changelogMarkdownInputSchema = z
50
50
  .object({
51
- keepVersions: z.number().int().min(1).optional(),
51
+ keptVersionCount: z.number().int().min(1).optional(),
52
52
  })
53
53
  .strict();
54
54
  export const releaseNotesInputSchema = z
@@ -2,7 +2,7 @@
2
2
  export interface CreateGithubReleaseOptions {
3
3
  tag: string;
4
4
  changelogJsonPath: string;
5
- dryRun: boolean;
5
+ isDryRun: boolean;
6
6
  /** Section titles in priority order. When omitted, entry order is preserved. */
7
7
  sectionOrder?: string[];
8
8
  }
@@ -29,9 +29,9 @@ export declare function createGithubRelease(options: CreateGithubReleaseOptions)
29
29
  /** Outcome of a `createGithubReleases` invocation. */
30
30
  export interface CreateGithubReleasesOutcome {
31
31
  /** Tags for which a Release was created (or would be, under `--dry-run`). */
32
- created: string[];
32
+ createdTags: string[];
33
33
  /** Tags that were skipped, paired with the discriminated reason for each skip. */
34
- skipped: Array<{
34
+ skippedTags: Array<{
35
35
  tag: string;
36
36
  reason: CreateReleaseSkipReason;
37
37
  }>;
@@ -40,4 +40,4 @@ export interface CreateGithubReleasesOutcome {
40
40
  export declare function createGithubReleases(tags: Array<{
41
41
  tag: string;
42
42
  workspacePath: string;
43
- }>, changelogJsonOutputPath: string, dryRun: boolean, sectionOrder?: string[]): CreateGithubReleasesOutcome;
43
+ }>, changelogJsonOutputPath: string, isDryRun: boolean, sectionOrder?: string[]): CreateGithubReleasesOutcome;
@@ -4,7 +4,7 @@ import { join } from 'node:path';
4
4
  import { extractVersion, readChangelogEntries } from "./changelogJsonUtils.js";
5
5
  import { matchesAudience, renderReleaseNotesSingle } from "./renderReleaseNotes.js";
6
6
  export function createGithubRelease(options) {
7
- const { tag, changelogJsonPath, dryRun, sectionOrder } = options;
7
+ const { tag, changelogJsonPath, isDryRun, sectionOrder } = options;
8
8
  if (!existsSync(changelogJsonPath)) {
9
9
  console.warn(`Warning: ${changelogJsonPath} not found; skipping GitHub Release creation`);
10
10
  return { status: 'skipped', reason: 'no-entry' };
@@ -25,37 +25,37 @@ export function createGithubRelease(options) {
25
25
  }
26
26
  const body = renderReleaseNotesSingle(entry, {
27
27
  filter: matchesAudience('all'),
28
- includeHeading: false,
28
+ shouldIncludeHeading: false,
29
29
  ...(sectionOrder !== undefined && { sectionOrder }),
30
30
  });
31
31
  if (body.trim() === '') {
32
32
  return { status: 'skipped', reason: 'empty-body' };
33
33
  }
34
34
  const args = ['release', 'create', tag, '--title', tag, '--notes', body];
35
- if (dryRun) {
35
+ if (isDryRun) {
36
36
  console.info(`[dry-run] Would run: gh ${args.join(' ')}`);
37
37
  return { status: 'created' };
38
38
  }
39
39
  execFileSync('gh', args, { stdio: 'inherit' });
40
40
  return { status: 'created' };
41
41
  }
42
- export function createGithubReleases(tags, changelogJsonOutputPath, dryRun, sectionOrder) {
43
- const created = [];
44
- const skipped = [];
42
+ export function createGithubReleases(tags, changelogJsonOutputPath, isDryRun, sectionOrder) {
43
+ const createdTags = [];
44
+ const skippedTags = [];
45
45
  for (const { tag, workspacePath } of tags) {
46
46
  const changelogJsonPath = join(workspacePath, changelogJsonOutputPath);
47
47
  const result = createGithubRelease({
48
48
  tag,
49
49
  changelogJsonPath,
50
- dryRun,
50
+ isDryRun,
51
51
  ...(sectionOrder !== undefined && { sectionOrder }),
52
52
  });
53
53
  if (result.status === 'created') {
54
- created.push(tag);
54
+ createdTags.push(tag);
55
55
  }
56
56
  else {
57
- skipped.push({ tag, reason: result.reason });
57
+ skippedTags.push({ tag, reason: result.reason });
58
58
  }
59
59
  }
60
- return { created, skipped };
60
+ return { createdTags, skippedTags };
61
61
  }
@@ -10,15 +10,15 @@ import { resolveCommandTags } from "./resolveCommandTags.js";
10
10
  import { resolveConfigFlag } from "./resolveConfigFlag.js";
11
11
  const createGithubReleaseFlagSchema = {
12
12
  ...configFlagSchema,
13
- dryRun: { long: '--dry-run', type: 'boolean' },
13
+ isDryRun: { long: '--dry-run', type: 'boolean' },
14
14
  tags: { long: '--tags', type: 'string' },
15
15
  };
16
16
  export async function createGithubReleaseCommand(argv, styles, invocationDir) {
17
- const parsed = parseArgsOrExit(argv, createGithubReleaseFlagSchema);
18
- const configPath = resolveConfigFlag(parsed.flags.config, invocationDir);
19
- const { dryRun } = parsed.flags;
17
+ const parsedArgs = parseArgsOrExit(argv, createGithubReleaseFlagSchema);
18
+ const configPath = resolveConfigFlag(parsedArgs.flags.config, invocationDir);
19
+ const { isDryRun } = parsedArgs.flags;
20
20
  const userConfig = await loadUsableConfig(styles.stderr, configPath);
21
- const requestedTags = parseRequestedTags(parsed.flags.tags);
21
+ const requestedTags = parseRequestedTags(parsedArgs.flags.tags);
22
22
  const resolvedTags = resolveCommandTags(requestedTags, userConfig);
23
23
  const releasableTags = resolvedTags.filter(isReleasable);
24
24
  for (const resolvedTag of resolvedTags) {
@@ -32,17 +32,17 @@ export async function createGithubReleaseCommand(argv, styles, invocationDir) {
32
32
  const { changelogJsonOutputPath, sectionOrder } = deriveReleaseNotesConfig(userConfig);
33
33
  let outcome;
34
34
  try {
35
- outcome = createGithubReleases(releasableTags, changelogJsonOutputPath, dryRun, sectionOrder);
35
+ outcome = createGithubReleases(releasableTags, changelogJsonOutputPath, isDryRun, sectionOrder);
36
36
  }
37
37
  catch (error) {
38
38
  reportError(`Failed to create GitHub Releases: ${describeError(error)}`);
39
39
  process.exit(1);
40
40
  }
41
- if (outcome.skipped.length === 0) {
41
+ if (outcome.skippedTags.length === 0) {
42
42
  return;
43
43
  }
44
- const formatted = outcome.skipped.map((s) => `${s.tag} (${s.reason})`).join(', ');
45
- console.info(`Skipped ${outcome.skipped.length} tag(s) without releasable content: ${formatted}.`);
44
+ const formattedSkips = outcome.skippedTags.map((s) => `${s.tag} (${s.reason})`).join(', ');
45
+ console.info(`Skipped ${outcome.skippedTags.length} tag(s) without releasable content: ${formattedSkips}.`);
46
46
  }
47
47
  function isReleasable(resolvedTag) {
48
48
  return resolvedTag.kind !== 'package' || resolvedTag.isPublishable;
@@ -1,7 +1,7 @@
1
1
  import { type OutputStyle } from '@williamthorsen/nmr-core';
2
2
  export interface CreateTagsOptions {
3
- dryRun: boolean;
4
- noGitChecks: boolean;
3
+ isDryRun: boolean;
4
+ shouldSkipGitChecks: boolean;
5
5
  style: OutputStyle;
6
6
  }
7
7
  /**
@@ -4,32 +4,32 @@ import { deleteFileIfExists } from "./deleteFileIfExists.js";
4
4
  import { RELEASE_GLYPHS } from "./glyphs.js";
5
5
  import { readReleaseTags, RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
6
6
  export function createTags(options) {
7
- const { dryRun, noGitChecks, style } = options;
7
+ const { isDryRun, shouldSkipGitChecks, style } = options;
8
8
  const tags = readReleaseTags();
9
9
  if (tags.length === 0) {
10
10
  return [];
11
11
  }
12
- if (!dryRun && !noGitChecks) {
12
+ if (!isDryRun && !shouldSkipGitChecks) {
13
13
  assertCleanWorkingTree();
14
14
  }
15
- if (dryRun) {
15
+ if (isDryRun) {
16
16
  console.info('[dry-run] Would create tags:');
17
17
  for (const tag of tags) {
18
18
  console.info(formatGlyphLine(RELEASE_GLYPHS, style, 'tag', tag));
19
19
  }
20
20
  return tags;
21
21
  }
22
- const created = [];
22
+ const createdTags = [];
23
23
  for (const tag of tags) {
24
24
  try {
25
25
  execFileSync('git', ['tag', '-a', tag, '-m', tag], { maxBuffer: GIT_OUTPUT_LIMIT_BYTES });
26
- created.push(tag);
26
+ createdTags.push(tag);
27
27
  }
28
28
  catch (error) {
29
- if (created.length > 0) {
29
+ if (createdTags.length > 0) {
30
30
  console.warn('Tags created before failure:');
31
- for (const t of created) {
32
- console.warn(` ${t}`);
31
+ for (const tag of createdTags) {
32
+ console.warn(` ${tag}`);
33
33
  }
34
34
  }
35
35
  throw error;
@@ -13,13 +13,13 @@ export interface DirectReleases {
13
13
  untaggedBaselines: Map<string, UntaggedBaseline>;
14
14
  }
15
15
  /**
16
- * Reads each planned package and directory target's history and decides its direct release. `force` applies only to
16
+ * Reads each planned package and directory target's history and decides its direct release. `shouldForce` applies only to
17
17
  * the targets from which the release starts.
18
18
  */
19
- export declare function decideDirectReleases(planned: readonly MemberTarget[], settings: TargetPrepareSettings, options: {
20
- force?: boolean;
19
+ export declare function decideDirectReleases(plannedTargets: readonly MemberTarget[], settings: TargetPrepareSettings, options: {
20
+ shouldForce?: boolean;
21
21
  bumpOverride?: ReleaseType;
22
- setVersions?: ReadonlyMap<string, string>;
22
+ versionOverridesById?: ReadonlyMap<string, string>;
23
23
  }, startIds: ReadonlySet<string>): DirectReleases;
24
24
  /**
25
25
  * Records on each planned workspace's and directory's history the entry scopes that route nowhere in its unreleased
@@ -27,4 +27,4 @@ export declare function decideDirectReleases(planned: readonly MemberTarget[], s
27
27
  * targets first, in planned order, which sets the order of each target's findings. The repo-root directory target
28
28
  * renders every entry in its window, so it records none, but its window still routes the scope that names it.
29
29
  */
30
- export declare function reportUnroutedEntryScopes(planned: readonly MemberTarget[], direct: DirectReleases, settings: TargetPrepareSettings, memberTargets: readonly MemberTarget[]): void;
30
+ export declare function reportUnroutedEntryScopes(plannedTargets: readonly MemberTarget[], directReleases: DirectReleases, settings: TargetPrepareSettings, memberTargets: readonly MemberTarget[]): void;
@@ -3,64 +3,64 @@ import { findUnroutedEntryScopes } from "./findUnroutedEntryScopes.js";
3
3
  import { getAllTagPrefixes } from "./generateChangelogs.js";
4
4
  import { describeStage, isRepoRootTarget, readHistory, runStage } from "./prepareRunContext.js";
5
5
  import { decideTargetRelease, readTargetHistory, } from "./prepareTarget.js";
6
- export function decideDirectReleases(planned, settings, options, startIds) {
7
- const { force, bumpOverride, setVersions } = options;
8
- const direct = {
6
+ export function decideDirectReleases(plannedTargets, settings, options, startIds) {
7
+ const { shouldForce, bumpOverride, versionOverridesById } = options;
8
+ const directReleases = {
9
9
  readings: new Map(),
10
10
  decisions: new Map(),
11
11
  bumps: new Map(),
12
12
  untaggedBaselines: new Map(),
13
13
  };
14
14
  const hintState = { emitted: false };
15
- const knownPrefixes = planned.flatMap(getAllTagPrefixes);
16
- for (const target of planned) {
17
- const { history, untagged } = runStage(describeStage(target), () => readTargetHistory(target, settings));
18
- direct.readings.set(target.id, history);
19
- if (untagged !== undefined) {
20
- direct.untaggedBaselines.set(target.id, untagged);
15
+ const knownPrefixes = plannedTargets.flatMap(getAllTagPrefixes);
16
+ for (const target of plannedTargets) {
17
+ const { history, untaggedBaseline } = runStage(describeStage(target), () => readTargetHistory(target, settings));
18
+ directReleases.readings.set(target.id, history);
19
+ if (untaggedBaseline !== undefined) {
20
+ directReleases.untaggedBaselines.set(target.id, untaggedBaseline);
21
21
  }
22
22
  if (history.previousTag === undefined && target.kind === 'package' && target.isWorkspace) {
23
- maybeEmitBaselineHint(target, knownPrefixes, hintState);
23
+ emitBaselineHintOnce(target, knownPrefixes, hintState);
24
24
  }
25
- const setVersion = setVersions?.get(target.id);
25
+ const versionOverride = versionOverridesById?.get(target.id);
26
26
  const decision = decideTargetRelease(target, history, {
27
- force: force === true && startIds.has(target.id),
27
+ shouldForce: shouldForce === true && startIds.has(target.id),
28
28
  bumpOverride,
29
- setVersion,
29
+ setVersion: versionOverride,
30
30
  });
31
- direct.decisions.set(target.id, decision);
31
+ directReleases.decisions.set(target.id, decision);
32
32
  if (decision.outcome === 'skip') {
33
33
  continue;
34
34
  }
35
- direct.bumps.set(target.id, decision.setVersion === undefined
35
+ directReleases.bumps.set(target.id, decision.setVersion === undefined
36
36
  ? { releaseType: decision.releaseType }
37
37
  : { releaseType: 'patch', newVersionOverride: decision.setVersion });
38
38
  }
39
- return direct;
39
+ return directReleases;
40
40
  }
41
- export function reportUnroutedEntryScopes(planned, direct, settings, memberTargets) {
42
- const releasing = planned.filter((target) => direct.decisions.get(target.id)?.outcome === 'release');
43
- const windows = [...releasing, ...planned.filter((target) => !releasing.includes(target))].map((target) => ({
41
+ export function reportUnroutedEntryScopes(plannedTargets, directReleases, settings, memberTargets) {
42
+ const releasingTargets = plannedTargets.filter((target) => directReleases.decisions.get(target.id)?.outcome === 'release');
43
+ const windows = [...releasingTargets, ...plannedTargets.filter((target) => !releasingTargets.includes(target))].map((target) => ({
44
44
  target,
45
- history: readHistory(direct.readings, target),
45
+ history: readHistory(directReleases.readings, target),
46
46
  }));
47
- const findings = findUnroutedEntryScopes(windows.map(({ target, history }) => ({ dir: target.id, commits: history.unreleased.commits })), memberTargets.map((target) => target.id), settings.scopeAliases ?? {});
47
+ const findings = findUnroutedEntryScopes(windows.map(({ target, history }) => ({ dir: target.id, commits: history.unreleasedReading.commits })), memberTargets.map((target) => target.id), settings.scopeAliases ?? {});
48
48
  for (const { target, history } of windows) {
49
- history.unreleased.diagnostics.unroutedEntryScopes = isRepoRootTarget(target)
49
+ history.unreleasedReading.diagnostics.unroutedEntryScopes = isRepoRootTarget(target)
50
50
  ? []
51
51
  : (findings.get(target.id) ?? []);
52
52
  }
53
53
  }
54
- function maybeEmitBaselineHint(target, knownPrefixes, state) {
54
+ function emitBaselineHintOnce(target, knownPrefixes, state) {
55
55
  if (state.emitted || (target.legacyIdentities?.length ?? 0) > 0)
56
56
  return;
57
57
  const candidates = detectUndeclaredTagPrefixes(knownPrefixes);
58
58
  if (candidates.length === 0)
59
59
  return;
60
60
  const totalTags = candidates.reduce((sum, candidate) => sum + candidate.tagCount, 0);
61
- const example = candidates[0]?.exampleTags[0] ?? `${candidates[0]?.prefix ?? ''}?`;
61
+ const exampleTag = candidates[0]?.exampleTags[0] ?? `${candidates[0]?.prefix ?? ''}?`;
62
62
  process.stderr.write(`Hint: no baseline tag found for ${target.id} under '${target.tagPrefix}', but ` +
63
- `${totalTags} candidate-shaped tags exist (e.g., ${example}). ` +
63
+ `${totalTags} candidate-shaped tags exist (e.g., ${exampleTag}). ` +
64
64
  "Run 'release-kit show-tag-prefixes' to check for undeclared legacy prefixes.\n");
65
65
  state.emitted = true;
66
66
  }
@@ -6,7 +6,7 @@ export interface DecideReleaseArgs {
6
6
  /** The number of commits in the window, which selects the skip reason. */
7
7
  commitCount: number;
8
8
  /** True when `--force` was passed; treats absence of a natural bump as patch. Defaults to false. */
9
- force?: boolean | undefined;
9
+ shouldForce?: boolean | undefined;
10
10
  /** Explicit `--bump=X` override. When set, the level is X regardless of the natural bump. */
11
11
  bumpOverride: ReleaseType | undefined;
12
12
  /**
@@ -1,6 +1,6 @@
1
1
  export function decideRelease(args) {
2
- const { naturalBump, commitCount, force = false, bumpOverride, skipReasons } = args;
3
- if (naturalBump === undefined && !force) {
2
+ const { naturalBump, commitCount, shouldForce = false, bumpOverride, skipReasons } = args;
3
+ if (naturalBump === undefined && !shouldForce) {
4
4
  const skipReason = commitCount === 0 ? skipReasons.noCommits : skipReasons.noBumpWorthy;
5
5
  return { outcome: 'skip', skipReason };
6
6
  }
@@ -10,4 +10,4 @@ export declare const DIRECTORY_VERSION_FILE = "version.json";
10
10
  * repo root. Throws when the path isn't a directory, when the version file is missing or lacks a `version`, and when
11
11
  * the basename would not make a valid tag prefix.
12
12
  */
13
- export declare function deriveDirectoryConfig(declared: DeclaredDirectory, root?: string): DirectoryConfig;
13
+ export declare function deriveDirectoryConfig(declaredDirectory: DeclaredDirectory, root?: string): DirectoryConfig;
@@ -4,24 +4,24 @@ import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
4
4
  import { isRecord } from "./typeGuards.js";
5
5
  export const DIRECTORY_VERSION_FILE = 'version.json';
6
6
  const TAG_PREFIX_PATTERN = /^[\w.-]+$/;
7
- export function deriveDirectoryConfig(declared, root = process.cwd()) {
8
- const label = `releases.${declared.name}`;
9
- if (statSync(path.resolve(root, declared.path), { throwIfNoEntry: false })?.isDirectory() !== true) {
10
- throw new Error(`${label}: path '${declared.path}' is not a directory; declare a directory that exists in the repo`);
7
+ export function deriveDirectoryConfig(declaredDirectory, root = process.cwd()) {
8
+ const label = `releases.${declaredDirectory.name}`;
9
+ if (statSync(path.resolve(root, declaredDirectory.path), { throwIfNoEntry: false })?.isDirectory() !== true) {
10
+ throw new Error(`${label}: path '${declaredDirectory.path}' is not a directory; declare a directory that exists in the repo`);
11
11
  }
12
- const versionFile = resolveVersionFile(declared.path, root);
12
+ const versionFile = resolveVersionFile(declaredDirectory.path, root);
13
13
  assertVersionField(label, versionFile, root);
14
14
  const config = {
15
- name: declared.name,
16
- path: declared.path,
15
+ name: declaredDirectory.name,
16
+ path: declaredDirectory.path,
17
17
  versionFile,
18
- tagPrefix: declared.tagPrefix ?? deriveTagPrefix(label, declared.path),
18
+ tagPrefix: declaredDirectory.tagPrefix ?? deriveTagPrefix(label, declaredDirectory.path),
19
19
  };
20
- if (declared.paths !== undefined) {
21
- config.paths = [...declared.paths];
20
+ if (declaredDirectory.paths !== undefined) {
21
+ config.paths = [...declaredDirectory.paths];
22
22
  }
23
- if (declared.legacyIdentities !== undefined) {
24
- config.legacyIdentities = declared.legacyIdentities;
23
+ if (declaredDirectory.legacyIdentities !== undefined) {
24
+ config.legacyIdentities = declaredDirectory.legacyIdentities;
25
25
  }
26
26
  return config;
27
27
  }
@@ -30,14 +30,14 @@ function assertVersionField(label, versionFile, root) {
30
30
  if (!existsSync(absolutePath)) {
31
31
  throw new Error(`${label}: ${versionFile} not found; create it with the directory's current version, such as { "version": "0.0.0" }`);
32
32
  }
33
- let parsed;
33
+ let parsedJson;
34
34
  try {
35
- parsed = JSON.parse(readFileSync(absolutePath, 'utf8'));
35
+ parsedJson = JSON.parse(readFileSync(absolutePath, 'utf8'));
36
36
  }
37
37
  catch (error) {
38
38
  throw chainError(`${label}: failed to read ${versionFile}`, error);
39
39
  }
40
- if (!isRecord(parsed) || typeof parsed['version'] !== 'string') {
40
+ if (!isRecord(parsedJson) || typeof parsedJson['version'] !== 'string') {
41
41
  throw new Error(`${label}: ${versionFile} does not contain a string 'version' field; add one`);
42
42
  }
43
43
  }
@@ -52,8 +52,8 @@ function deriveTagPrefix(label, directoryPath) {
52
52
  return `${basename}-v`;
53
53
  }
54
54
  function resolveVersionFile(directoryPath, root) {
55
- const packageJson = path.posix.join(directoryPath, 'package.json');
56
- return existsSync(path.resolve(root, packageJson))
57
- ? packageJson
55
+ const packageJsonPath = path.posix.join(directoryPath, 'package.json');
56
+ return existsSync(path.resolve(root, packageJsonPath))
57
+ ? packageJsonPath
58
58
  : path.posix.join(directoryPath, DIRECTORY_VERSION_FILE);
59
59
  }
@@ -6,19 +6,19 @@ import { isRecord } from "./typeGuards.js";
6
6
  export function deriveWorkspaceConfig(workspacePath) {
7
7
  const dir = basename(workspacePath);
8
8
  const packageJsonPath = `${workspacePath}/package.json`;
9
- let parsed;
9
+ let parsedJson;
10
10
  try {
11
- parsed = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
11
+ parsedJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
12
12
  }
13
13
  catch (error) {
14
14
  throw chainError(`Failed to read ${packageJsonPath}`, error);
15
15
  }
16
- const name = isRecord(parsed) ? parsed['name'] : undefined;
16
+ const name = isRecord(parsedJson) ? parsedJson['name'] : undefined;
17
17
  if (typeof name !== 'string' || name.length === 0) {
18
18
  throw new Error(`${packageJsonPath} is missing a 'name' field (required for tag derivation).`);
19
19
  }
20
20
  const unscopedName = stripNpmScope(name);
21
- const isPublishable = isPublishableManifest(parsed);
21
+ const isPublishable = isPublishableManifest(parsedJson);
22
22
  return {
23
23
  dir,
24
24
  name,
@@ -5,9 +5,9 @@ export function detectPackageManager() {
5
5
  const packageJsonPath = join(process.cwd(), 'package.json');
6
6
  try {
7
7
  const content = readFileSync(packageJsonPath, 'utf8');
8
- const parsed = JSON.parse(content);
9
- if (isRecord(parsed) && typeof parsed['packageManager'] === 'string') {
10
- const [name, version] = parsed['packageManager'].split('@', 2);
8
+ const parsedJson = JSON.parse(content);
9
+ if (isRecord(parsedJson) && typeof parsedJson['packageManager'] === 'string') {
10
+ const [name, version] = parsedJson['packageManager'].split('@', 2);
11
11
  if (name === 'pnpm' || name === 'npm') {
12
12
  return name;
13
13
  }
@@ -24,8 +24,8 @@ function isYarnBerry(version) {
24
24
  if (version === undefined) {
25
25
  return false;
26
26
  }
27
- const major = Number.parseInt(version, 10);
28
- return !Number.isNaN(major) && major >= 2;
27
+ const majorVersion = Number.parseInt(version, 10);
28
+ return !Number.isNaN(majorVersion) && majorVersion >= 2;
29
29
  }
30
30
  function detectFromLockfile() {
31
31
  const cwd = process.cwd();
@@ -3,7 +3,7 @@ import { GIT_OUTPUT_LIMIT_BYTES } from '@williamthorsen/nmr-core';
3
3
  const EXAMPLE_TAG_LIMIT = 3;
4
4
  const CANDIDATE_TAG_PATTERN = /^(?<prefix>[a-z][a-z0-9-]*-v)\d+\.\d+\.\d+(?:-[A-Za-z0-9.-]+)?$/;
5
5
  export function detectUndeclaredTagPrefixes(knownPrefixes) {
6
- const known = new Set(knownPrefixes);
6
+ const knownPrefixSet = new Set(knownPrefixes);
7
7
  let rawOutput;
8
8
  try {
9
9
  rawOutput = execFileSync('git', ['tag', '--list'], {
@@ -15,7 +15,7 @@ export function detectUndeclaredTagPrefixes(knownPrefixes) {
15
15
  catch {
16
16
  return [];
17
17
  }
18
- const grouped = new Map();
18
+ const tagsByPrefix = new Map();
19
19
  for (const line of rawOutput.split('\n')) {
20
20
  const tag = line.trim();
21
21
  if (tag === '')
@@ -24,17 +24,17 @@ export function detectUndeclaredTagPrefixes(knownPrefixes) {
24
24
  if (match === null)
25
25
  continue;
26
26
  const prefix = match.groups?.['prefix'] ?? '';
27
- if (prefix === '' || known.has(prefix))
27
+ if (prefix === '' || knownPrefixSet.has(prefix))
28
28
  continue;
29
- let tags = grouped.get(prefix);
29
+ let tags = tagsByPrefix.get(prefix);
30
30
  if (tags === undefined) {
31
31
  tags = [];
32
- grouped.set(prefix, tags);
32
+ tagsByPrefix.set(prefix, tags);
33
33
  }
34
34
  tags.push(tag);
35
35
  }
36
36
  const results = [];
37
- for (const [prefix, tags] of grouped) {
37
+ for (const [prefix, tags] of tagsByPrefix) {
38
38
  results.push({
39
39
  prefix,
40
40
  tagCount: tags.length,
@@ -14,7 +14,7 @@ export interface ReleaseWindow {
14
14
  /** The tag name for a released window, the caller's unreleased-tag name for the newest one. */
15
15
  version: string;
16
16
  /** Unix seconds: the tag's creation date for a released window, the clock for the unreleased one. */
17
- timestamp: number;
17
+ timestampSec: number;
18
18
  /** Oldest first. */
19
19
  commits: RawCommit[];
20
20
  }
@@ -11,7 +11,7 @@ export function enumerateReleaseWindows(options) {
11
11
  }
12
12
  const unreleasedWindow = {
13
13
  version: unreleasedTag,
14
- timestamp: Math.floor(now() / 1_000),
14
+ timestampSec: Math.floor(now() / 1_000),
15
15
  commits: [],
16
16
  };
17
17
  const ancestry = readAncestry();
@@ -21,7 +21,7 @@ export function enumerateReleaseWindows(options) {
21
21
  const boundaries = readTagBoundaries(tagPrefixes, ancestry.positionByHash);
22
22
  const releasedWindows = boundaries.map((boundary) => ({
23
23
  version: boundary.tag,
24
- timestamp: boundary.timestamp,
24
+ timestampSec: boundary.timestampSec,
25
25
  commits: [],
26
26
  }));
27
27
  const windowIndexByHash = claimAncestors(boundaries, ancestry.parentsByHash);
@@ -35,14 +35,14 @@ export function enumerateReleaseWindows(options) {
35
35
  function claimAncestors(boundaries, parentsByHash) {
36
36
  const windowIndexByHash = new Map();
37
37
  for (const [windowIndex, boundary] of boundaries.entries()) {
38
- const pending = [boundary.hash];
39
- while (pending.length > 0) {
40
- const hash = pending.pop();
38
+ const pendingHashes = [boundary.hash];
39
+ while (pendingHashes.length > 0) {
40
+ const hash = pendingHashes.pop();
41
41
  if (hash === undefined || windowIndexByHash.has(hash)) {
42
42
  continue;
43
43
  }
44
44
  windowIndexByHash.set(hash, windowIndex);
45
- pending.push(...(parentsByHash.get(hash) ?? []));
45
+ pendingHashes.push(...(parentsByHash.get(hash) ?? []));
46
46
  }
47
47
  }
48
48
  return windowIndexByHash;
@@ -103,7 +103,7 @@ function readTagBoundaries(tagPrefixes, positionByHash) {
103
103
  const hash = dereferencedName || objectName;
104
104
  const position = positionByHash.get(hash);
105
105
  if (position !== undefined) {
106
- boundaries.push({ hash, position, tag, timestamp: Number(creatorDate) });
106
+ boundaries.push({ hash, position, tag, timestampSec: Number(creatorDate) });
107
107
  }
108
108
  }
109
109
  return boundaries.toSorted((left, right) => right.position - left.position);