@williamthorsen/release-kit 14.2.1 → 14.2.3

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 (91) hide show
  1. package/.meta/changelog.json +77 -0
  2. package/.readyup/kits/default.js +142 -152
  3. package/.readyup/kits/npm-auto-publish.js +183 -184
  4. package/.readyup/manifest.json +83 -9
  5. package/CHANGELOG.md +24 -0
  6. package/README.md +7 -1
  7. package/dist/esm/applyChangelogOverrides.d.ts +50 -0
  8. package/dist/esm/applyChangelogOverrides.js +162 -0
  9. package/dist/esm/attachChangelogDiagnostics.d.ts +1 -1
  10. package/dist/esm/bin/release-kit.js +6 -238
  11. package/dist/esm/buildChangelogEntries.d.ts +20 -31
  12. package/dist/esm/buildChangelogEntries.js +71 -188
  13. package/dist/esm/buildChangelogItem.d.ts +56 -0
  14. package/dist/esm/buildChangelogItem.js +119 -0
  15. package/dist/esm/buildReleasedResult.d.ts +20 -0
  16. package/dist/esm/buildReleasedResult.js +78 -0
  17. package/dist/esm/changelogJsonUtils.d.ts +1 -1
  18. package/dist/esm/changelogOverrides.d.ts +29 -143
  19. package/dist/esm/changelogOverrides.js +35 -467
  20. package/dist/esm/configSchema.d.ts +225 -0
  21. package/dist/esm/configSchema.js +103 -0
  22. package/dist/esm/createGithubReleaseCommand.js +4 -3
  23. package/dist/esm/decideDirectReleases.d.ts +30 -0
  24. package/dist/esm/decideDirectReleases.js +66 -0
  25. package/dist/esm/defaults.js +1 -4
  26. package/dist/esm/defineConfig.d.ts +2 -2
  27. package/dist/esm/deriveReleaseNotesConfig.d.ts +2 -1
  28. package/dist/esm/deriveReleaseNotesConfig.js +1 -1
  29. package/dist/esm/determineBumpType.js +4 -3
  30. package/dist/esm/enumerateReleaseWindows.js +4 -4
  31. package/dist/esm/formatPrepareFiles.d.ts +11 -0
  32. package/dist/esm/formatPrepareFiles.js +55 -0
  33. package/dist/esm/formatPrepareWarnings.d.ts +26 -0
  34. package/dist/esm/formatPrepareWarnings.js +72 -0
  35. package/dist/esm/help/initHelp.d.ts +4 -0
  36. package/dist/esm/help/initHelp.js +31 -0
  37. package/dist/esm/help/overridesHelp.d.ts +4 -0
  38. package/dist/esm/help/overridesHelp.js +32 -0
  39. package/dist/esm/help/releaseStepsHelp.d.ts +12 -0
  40. package/dist/esm/help/releaseStepsHelp.js +91 -0
  41. package/dist/esm/help/syncLabelsHelp.d.ts +8 -0
  42. package/dist/esm/help/syncLabelsHelp.js +55 -0
  43. package/dist/esm/help/usage.d.ts +2 -0
  44. package/dist/esm/help/usage.js +29 -0
  45. package/dist/esm/index.d.ts +1 -1
  46. package/dist/esm/index.js +1 -1
  47. package/dist/esm/init/initCommand.js +3 -7
  48. package/dist/esm/loadChangelogOverrides.d.ts +31 -0
  49. package/dist/esm/loadChangelogOverrides.js +138 -0
  50. package/dist/esm/loadConfig.d.ts +0 -40
  51. package/dist/esm/loadConfig.js +0 -242
  52. package/dist/esm/loadValidatedConfig.d.ts +1 -1
  53. package/dist/esm/mergeConfig.d.ts +41 -0
  54. package/dist/esm/mergeConfig.js +118 -0
  55. package/dist/esm/monorepoConfigAssertions.d.ts +49 -0
  56. package/dist/esm/monorepoConfigAssertions.js +126 -0
  57. package/dist/esm/planChangelogFiles.d.ts +16 -0
  58. package/dist/esm/planChangelogFiles.js +41 -0
  59. package/dist/esm/prepareArgs.d.ts +55 -0
  60. package/dist/esm/prepareArgs.js +69 -0
  61. package/dist/esm/prepareCommand.d.ts +0 -48
  62. package/dist/esm/prepareCommand.js +11 -77
  63. package/dist/esm/prepareComposite.d.ts +16 -0
  64. package/dist/esm/prepareComposite.js +39 -0
  65. package/dist/esm/prepareRunContext.d.ts +38 -0
  66. package/dist/esm/prepareRunContext.js +39 -0
  67. package/dist/esm/prepareTarget.d.ts +2 -7
  68. package/dist/esm/prepareTarget.js +2 -118
  69. package/dist/esm/previewTagPrefixes.d.ts +1 -1
  70. package/dist/esm/releasePrepareTargets.js +13 -207
  71. package/dist/esm/renderReleaseNotes.js +6 -5
  72. package/dist/esm/reportPrepare.js +3 -123
  73. package/dist/esm/resolveCommandTags.d.ts +1 -1
  74. package/dist/esm/resolveCommandTags.js +2 -1
  75. package/dist/esm/resolveReleaseDeclaration.d.ts +2 -1
  76. package/dist/esm/resolveReleaseTargets.d.ts +2 -1
  77. package/dist/esm/resolveReleaseTargets.js +2 -1
  78. package/dist/esm/stripScope.js +4 -3
  79. package/dist/esm/sync-labels/generateCommand.d.ts +1 -1
  80. package/dist/esm/sync-labels/resolveLabels.d.ts +1 -1
  81. package/dist/esm/topologicalSort.d.ts +12 -0
  82. package/dist/esm/topologicalSort.js +60 -0
  83. package/dist/esm/types.d.ts +1 -225
  84. package/dist/esm/types.js +1 -103
  85. package/dist/esm/validateAllChangelogOverrides.d.ts +56 -0
  86. package/dist/esm/validateAllChangelogOverrides.js +138 -0
  87. package/dist/esm/validateConfig.d.ts +1 -1
  88. package/dist/esm/validateConfig.js +7 -9
  89. package/dist/esm/validateOverridesCommand.d.ts +1 -1
  90. package/dist/esm/validateOverridesCommand.js +2 -1
  91. package/package.json +4 -4
@@ -0,0 +1,55 @@
1
+ import type { ReleaseType } from './types.js';
2
+ /** Canonical `N.N.N` semver pattern for validating `--set-version` input. */
3
+ export declare const CANONICAL_SEMVER_PATTERN: RegExp;
4
+ export declare const prepareFlagSchema: {
5
+ dryRun: {
6
+ long: string;
7
+ type: "boolean";
8
+ };
9
+ force: {
10
+ long: string;
11
+ type: "boolean";
12
+ };
13
+ noGitChecks: {
14
+ long: string;
15
+ type: "boolean";
16
+ short: string;
17
+ };
18
+ bump: {
19
+ long: string;
20
+ type: "string";
21
+ };
22
+ setVersion: {
23
+ long: string;
24
+ type: "string";
25
+ };
26
+ only: {
27
+ long: string;
28
+ type: "string";
29
+ };
30
+ withReleaseNotes: {
31
+ long: string;
32
+ type: "boolean";
33
+ };
34
+ config: {
35
+ long: string;
36
+ type: "string";
37
+ };
38
+ };
39
+ /** Parses CLI arguments into structured options. Prints a usage error and exits on invalid input. */
40
+ export declare function parseArgs(argv: string[]): {
41
+ configPath: string | undefined;
42
+ dryRun: boolean;
43
+ force: boolean;
44
+ noGitChecks: boolean;
45
+ bumpOverride: ReleaseType | undefined;
46
+ only: string[] | undefined;
47
+ /** The raw `--set-version` value, whose accepted form depends on the mode. */
48
+ setVersion: string | undefined;
49
+ withReleaseNotes: boolean;
50
+ };
51
+ /**
52
+ * Parses a monorepo `--set-version` value, a comma-separated list of `<target>@N.N.N` entries, into versions keyed by
53
+ * target name. Prints a usage error and exits on a malformed entry, an unknown target, or a repeated one.
54
+ */
55
+ export declare function parseSetVersions(value: string, knownNames: readonly string[]): Map<string, string>;
@@ -0,0 +1,69 @@
1
+ import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
2
+ import { configFlagSchema } from "./configFlagSchema.js";
3
+ const VALID_BUMP_TYPES = ['major', 'minor', 'patch'];
4
+ export const CANONICAL_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
5
+ export const prepareFlagSchema = {
6
+ ...configFlagSchema,
7
+ dryRun: { long: '--dry-run', type: 'boolean' },
8
+ force: { long: '--force', type: 'boolean' },
9
+ noGitChecks: {
10
+ long: '--no-git-checks',
11
+ type: 'boolean',
12
+ short: '-n',
13
+ },
14
+ bump: { long: '--bump', type: 'string' },
15
+ setVersion: { long: '--set-version', type: 'string' },
16
+ only: { long: '--only', type: 'string' },
17
+ withReleaseNotes: { long: '--with-release-notes', type: 'boolean' },
18
+ };
19
+ export function parseArgs(argv) {
20
+ const { flags } = parseArgsOrExit(argv, prepareFlagSchema);
21
+ let bumpOverride;
22
+ if (flags.bump !== undefined) {
23
+ if (!isReleaseType(flags.bump)) {
24
+ reportError(`Invalid bump type "${flags.bump}". Must be one of: ${VALID_BUMP_TYPES.join(', ')}`);
25
+ process.exit(1);
26
+ }
27
+ bumpOverride = flags.bump;
28
+ }
29
+ let only;
30
+ if (flags.only !== undefined) {
31
+ only = flags.only.split(',');
32
+ }
33
+ return {
34
+ configPath: flags.config,
35
+ dryRun: flags.dryRun,
36
+ force: flags.force,
37
+ noGitChecks: flags.noGitChecks,
38
+ bumpOverride,
39
+ only,
40
+ setVersion: flags.setVersion,
41
+ withReleaseNotes: flags.withReleaseNotes,
42
+ };
43
+ }
44
+ export function parseSetVersions(value, knownNames) {
45
+ const setVersions = new Map();
46
+ for (const entry of value.split(',')) {
47
+ const separatorIndex = entry.lastIndexOf('@');
48
+ const name = entry.slice(0, Math.max(0, separatorIndex));
49
+ const version = entry.slice(separatorIndex + 1);
50
+ if (separatorIndex <= 0 || !CANONICAL_SEMVER_PATTERN.test(version)) {
51
+ reportError(`Invalid --set-version entry "${entry}". In monorepo mode, each entry must be <target>@N.N.N ` +
52
+ '(canonical semver, no pre-release suffix), e.g. --set-version=arrays@1.0.0.');
53
+ process.exit(1);
54
+ }
55
+ if (!knownNames.includes(name)) {
56
+ reportError(`Unknown release target "${name}" in --set-version. Known targets: ${knownNames.join(', ')}`);
57
+ process.exit(1);
58
+ }
59
+ if (setVersions.has(name)) {
60
+ reportError(`--set-version names target "${name}" more than once`);
61
+ process.exit(1);
62
+ }
63
+ setVersions.set(name, version);
64
+ }
65
+ return setVersions;
66
+ }
67
+ function isReleaseType(value) {
68
+ return VALID_BUMP_TYPES.includes(value);
69
+ }
@@ -1,52 +1,4 @@
1
1
  import { type StreamStyles } from '@williamthorsen/nmr-core';
2
- import type { ReleaseType } from './types.js';
3
- export declare const prepareFlagSchema: {
4
- dryRun: {
5
- long: string;
6
- type: "boolean";
7
- };
8
- force: {
9
- long: string;
10
- type: "boolean";
11
- };
12
- noGitChecks: {
13
- long: string;
14
- type: "boolean";
15
- short: string;
16
- };
17
- bump: {
18
- long: string;
19
- type: "string";
20
- };
21
- setVersion: {
22
- long: string;
23
- type: "string";
24
- };
25
- only: {
26
- long: string;
27
- type: "string";
28
- };
29
- withReleaseNotes: {
30
- long: string;
31
- type: "boolean";
32
- };
33
- config: {
34
- long: string;
35
- type: "string";
36
- };
37
- };
38
- /** Parses CLI arguments into structured options. Prints a usage error and exits on invalid input. */
39
- export declare function parseArgs(argv: string[]): {
40
- configPath: string | undefined;
41
- dryRun: boolean;
42
- force: boolean;
43
- noGitChecks: boolean;
44
- bumpOverride: ReleaseType | undefined;
45
- only: string[] | undefined;
46
- /** The raw `--set-version` value, whose accepted form depends on the mode. */
47
- setVersion: string | undefined;
48
- withReleaseNotes: boolean;
49
- };
50
2
  /**
51
3
  * Orchestrates the CLI `prepare` command: checks the working tree, loads and validates the config, discovers the
52
4
  * workspaces, and prepares the release over the targets of the repo shape.
@@ -1,63 +1,19 @@
1
1
  import { execSync } from 'node:child_process';
2
- import { formatGlyphLine, parseArgsOrExit, reportError, } from '@williamthorsen/nmr-core';
2
+ 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 { configFlagSchema } from "./configFlagSchema.js";
7
6
  import { describeEmptyWorkspace, discoverWorkspaces } from "./discoverWorkspaces.js";
8
7
  import { dim } from "./format.js";
9
8
  import { RELEASE_GLYPHS } from "./glyphs.js";
10
9
  import { loadValidatedConfig, reportConfigProblem, reportConfigWarnings } from "./loadValidatedConfig.js";
10
+ import { CANONICAL_SEMVER_PATTERN, parseArgs, parseSetVersions } from "./prepareArgs.js";
11
11
  import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
12
12
  import { applyReleasePlan } from "./releasePlan.js";
13
13
  import { releasePrepareTargets } from "./releasePrepareTargets.js";
14
14
  import { reportPrepare } from "./reportPrepare.js";
15
15
  import { resolveConfigFlag } from "./resolveConfigFlag.js";
16
16
  import { isSinglePackageTargets, PROJECT_TARGET_ID, resolveReleaseTargets, SINGLE_PACKAGE_TARGET_ID, } from "./resolveReleaseTargets.js";
17
- const VALID_BUMP_TYPES = ['major', 'minor', 'patch'];
18
- const CANONICAL_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
19
- function isReleaseType(value) {
20
- return VALID_BUMP_TYPES.includes(value);
21
- }
22
- export const prepareFlagSchema = {
23
- ...configFlagSchema,
24
- dryRun: { long: '--dry-run', type: 'boolean' },
25
- force: { long: '--force', type: 'boolean' },
26
- noGitChecks: {
27
- long: '--no-git-checks',
28
- type: 'boolean',
29
- short: '-n',
30
- },
31
- bump: { long: '--bump', type: 'string' },
32
- setVersion: { long: '--set-version', type: 'string' },
33
- only: { long: '--only', type: 'string' },
34
- withReleaseNotes: { long: '--with-release-notes', type: 'boolean' },
35
- };
36
- export function parseArgs(argv) {
37
- const { flags } = parseArgsOrExit(argv, prepareFlagSchema);
38
- let bumpOverride;
39
- if (flags.bump !== undefined) {
40
- if (!isReleaseType(flags.bump)) {
41
- reportError(`Invalid bump type "${flags.bump}". Must be one of: ${VALID_BUMP_TYPES.join(', ')}`);
42
- process.exit(1);
43
- }
44
- bumpOverride = flags.bump;
45
- }
46
- let only;
47
- if (flags.only !== undefined) {
48
- only = flags.only.split(',');
49
- }
50
- return {
51
- configPath: flags.config,
52
- dryRun: flags.dryRun,
53
- force: flags.force,
54
- noGitChecks: flags.noGitChecks,
55
- bumpOverride,
56
- only,
57
- setVersion: flags.setVersion,
58
- withReleaseNotes: flags.withReleaseNotes,
59
- };
60
- }
61
17
  export async function prepareCommand(argv, styles, invocationDir) {
62
18
  const { configPath: configFlag, dryRun, force, noGitChecks, bumpOverride, only, setVersion, withReleaseNotes, } = parseArgs(argv);
63
19
  const options = {
@@ -112,8 +68,8 @@ export async function prepareCommand(argv, styles, invocationDir) {
112
68
  }, dryRun, styles.stdout);
113
69
  }
114
70
  function findReleasedPackagePaths(plan, targets) {
115
- const releasedIds = new Set(plan.targets.flatMap((result) => result.status === 'released' && result.kind === 'package' ? [result.targetId] : []));
116
- return targets.flatMap((target) => target.kind === 'package' && releasedIds.has(target.id) ? [target.packagePath] : []);
71
+ const releasedIds = new Set(plan.targets.flatMap((result) => result.status === 'released' && result.kind === 'package' ? result.targetId : []));
72
+ return targets.flatMap((target) => target.kind === 'package' && releasedIds.has(target.id) ? target.packagePath : []);
117
73
  }
118
74
  function resolveTargetsOrExit(userConfig, discovery, only, setVersion) {
119
75
  if (discovery.kind === 'single-package') {
@@ -159,33 +115,10 @@ function resolveSetVersions(setVersion, targets) {
159
115
  return parseSetVersions(setVersion, listMemberIds(targets));
160
116
  }
161
117
  function listSelectableIds(targets) {
162
- return targets.flatMap((target) => (target.id === PROJECT_TARGET_ID ? [] : [target.id]));
118
+ return targets.flatMap((target) => (target.id === PROJECT_TARGET_ID ? [] : target.id));
163
119
  }
164
120
  function listMemberIds(targets) {
165
- return targets.flatMap((target) => (target.kind === 'composite' ? [] : [target.id]));
166
- }
167
- function parseSetVersions(value, knownNames) {
168
- const setVersions = new Map();
169
- for (const entry of value.split(',')) {
170
- const separatorIndex = entry.lastIndexOf('@');
171
- const name = entry.slice(0, Math.max(0, separatorIndex));
172
- const version = entry.slice(separatorIndex + 1);
173
- if (separatorIndex <= 0 || !CANONICAL_SEMVER_PATTERN.test(version)) {
174
- reportError(`Invalid --set-version entry "${entry}". In monorepo mode, each entry must be <target>@N.N.N ` +
175
- '(canonical semver, no pre-release suffix), e.g. --set-version=arrays@1.0.0.');
176
- process.exit(1);
177
- }
178
- if (!knownNames.includes(name)) {
179
- reportError(`Unknown release target "${name}" in --set-version. Known targets: ${knownNames.join(', ')}`);
180
- process.exit(1);
181
- }
182
- if (setVersions.has(name)) {
183
- reportError(`--set-version names target "${name}" more than once`);
184
- process.exit(1);
185
- }
186
- setVersions.set(name, version);
187
- }
188
- return setVersions;
121
+ return targets.flatMap((target) => (target.kind === 'composite' ? [] : target.id));
189
122
  }
190
123
  function runAndReport(computePlan, dryRun, style) {
191
124
  let plan;
@@ -212,11 +145,12 @@ function runAndReport(computePlan, dryRun, style) {
212
145
  const formatError = runFormatCommand(plan.formatCommand);
213
146
  process.stdout.write(reportPrepare(plan, { applied: true, style, ...(formatError !== undefined && { formatError }) }) + '\n');
214
147
  reportPlanFiles(plan, false);
215
- if (formatError !== undefined) {
216
- reportError(`The format command failed, but the release is prepared and ${RELEASE_TAGS_FILE} is written. ` +
217
- `Format the release files and run 'release-kit commit'.`);
218
- process.exit(1);
148
+ if (formatError === undefined) {
149
+ return;
219
150
  }
151
+ reportError(`The format command failed, but the release is prepared and ${RELEASE_TAGS_FILE} is written. ` +
152
+ `Format the release files and run 'release-kit commit'.`);
153
+ process.exit(1);
220
154
  }
221
155
  function runFormatCommand(formatCommand) {
222
156
  if (formatCommand === undefined) {
@@ -0,0 +1,16 @@
1
+ import { type CollectedReleases, type RunContext } from './prepareRunContext.js';
2
+ import type { CompositeTarget, ReleaseType, TargetPrepareResult } from './types.js';
3
+ /**
4
+ * Describes why a run narrowed by `only` skips a composite and how to include it, or returns `undefined` when the run
5
+ * prepares it: every run that `only` doesn't narrow, and every one that names it. A `project` block's composite
6
+ * cannot be named.
7
+ */
8
+ export declare function describeCompositeSkip(composite: CompositeTarget, only: readonly string[] | undefined): string | undefined;
9
+ /**
10
+ * Prepares a composite target: reads its history, asserts its baseline, and decides and plans its release, appending
11
+ * its tag, writes, and modified files to the run's collection and its preview warnings to `warnings`.
12
+ */
13
+ export declare function prepareComposite(composite: CompositeTarget, context: RunContext, options: {
14
+ force?: boolean;
15
+ bumpOverride?: ReleaseType;
16
+ }, collected: CollectedReleases, warnings: string[]): TargetPrepareResult;
@@ -0,0 +1,39 @@
1
+ import { assertTaggedBaseline } from "./assertTaggedBaseline.js";
2
+ import { collect } from "./prepareRunContext.js";
3
+ import { buildSkippedTargetResult, decideTargetRelease, planTargetRelease, readTargetHistory, } from "./prepareTarget.js";
4
+ import { PROJECT_TARGET_ID } from "./resolveReleaseTargets.js";
5
+ export function describeCompositeSkip(composite, only) {
6
+ if (only === undefined || only.includes(composite.id)) {
7
+ return undefined;
8
+ }
9
+ if (composite.id === PROJECT_TARGET_ID) {
10
+ return (`Project release skipped: --only narrows this run to ${only.join(', ')}. ` +
11
+ 'Run `release-kit prepare` without --only to include the project release.');
12
+ }
13
+ return `Release '${composite.id}' skipped: --only does not name it. Add ${composite.id} to --only to include it.`;
14
+ }
15
+ export function prepareComposite(composite, context, options, collected, warnings) {
16
+ const { history, untagged } = readTargetHistory(composite, context.settings);
17
+ assertTaggedBaseline([untagged]);
18
+ const decision = decideTargetRelease(composite, history, {
19
+ force: options.force === true,
20
+ bumpOverride: options.bumpOverride,
21
+ setVersion: undefined,
22
+ });
23
+ if (decision.outcome === 'skip') {
24
+ return buildSkippedTargetResult(composite, history, decision.skipReason);
25
+ }
26
+ if (decision.setVersion !== undefined) {
27
+ throw new Error('A composite target cannot take --set-version');
28
+ }
29
+ const planned = planTargetRelease({
30
+ ...context,
31
+ target: composite,
32
+ release: { releaseType: decision.releaseType },
33
+ decision,
34
+ history,
35
+ });
36
+ collect(collected, planned);
37
+ warnings.push(...planned.warnings);
38
+ return planned.result;
39
+ }
@@ -0,0 +1,38 @@
1
+ import type { ReleaseHistory } from './buildChangelogEntries.js';
2
+ import type { OverrideContext } from './changelogOverrides.js';
3
+ import { type TargetPrepareSettings } from './prepareTarget.js';
4
+ import type { PlannedWrite } from './releasePlan.js';
5
+ import type { DirectoryTarget, PackageTarget, ReleaseTarget } from './types.js';
6
+ /** Appends one planned release's tag, writes, removals, and modified files to the run's collection. */
7
+ export declare function collect(collected: CollectedReleases, planned: {
8
+ tag: string;
9
+ writes: PlannedWrite[];
10
+ removals: string[];
11
+ modifiedFiles: string[];
12
+ }): void;
13
+ /** The tags, writes, removals, and modified files of the run's releases, in the order in which they were planned. */
14
+ export interface CollectedReleases {
15
+ tags: string[];
16
+ writes: PlannedWrite[];
17
+ removals: string[];
18
+ modifiedFiles: string[];
19
+ }
20
+ /** Names the stage whose errors a target's work raises; the single-package root's errors are not labeled. */
21
+ export declare function describeStage(target: ReleaseTarget): string | undefined;
22
+ /** Reports whether a target is the directory target of the repo root, whose window is the whole repo. */
23
+ export declare function isRepoRootTarget(target: MemberTarget): boolean;
24
+ /** A target that releases on its own window and takes part in selection: a package or a directory. */
25
+ export type MemberTarget = PackageTarget | DirectoryTarget;
26
+ /** Returns the history that the direct-release pass read for a planned target. */
27
+ export declare function readHistory(readings: ReadonlyMap<string, ReleaseHistory>, target: MemberTarget): ReleaseHistory;
28
+ /** What every target's planning reads from the run. */
29
+ export interface RunContext {
30
+ settings: TargetPrepareSettings;
31
+ overrideContext: OverrideContext;
32
+ sectionOrder: string[];
33
+ dependenciesHeader: string;
34
+ shouldPlanPreviews: boolean;
35
+ today: string;
36
+ }
37
+ /** Runs `fn`, rethrowing any thrown value behind the stage label as `<stage>: …`; runs it unlabeled without one. */
38
+ export declare function runStage<T>(stage: string | undefined, fn: () => T): T;
@@ -0,0 +1,39 @@
1
+ import { chainError } from '@williamthorsen/toolbelt.errors/candidate';
2
+ import { isRepoRoot } from "./prepareTarget.js";
3
+ export function collect(collected, planned) {
4
+ collected.tags.push(planned.tag);
5
+ collected.writes.push(...planned.writes);
6
+ collected.removals.push(...planned.removals);
7
+ collected.modifiedFiles.push(...planned.modifiedFiles);
8
+ }
9
+ export function describeStage(target) {
10
+ switch (target.kind) {
11
+ case 'composite':
12
+ return 'project release stage';
13
+ case 'directory':
14
+ return `directory '${target.id}' release stage`;
15
+ case 'package':
16
+ return target.isWorkspace ? `workspace '${target.id}' release stage` : undefined;
17
+ }
18
+ }
19
+ export function isRepoRootTarget(target) {
20
+ return target.kind === 'directory' && isRepoRoot(target);
21
+ }
22
+ export function readHistory(readings, target) {
23
+ const history = readings.get(target.id);
24
+ if (history === undefined) {
25
+ throw new Error(`Target '${target.id}' was planned without a history`);
26
+ }
27
+ return history;
28
+ }
29
+ export function runStage(stage, fn) {
30
+ if (stage === undefined) {
31
+ return fn();
32
+ }
33
+ try {
34
+ return fn();
35
+ }
36
+ catch (error) {
37
+ throw chainError(stage, error);
38
+ }
39
+ }
@@ -1,9 +1,10 @@
1
1
  import { type UntaggedBaseline } from './assertTaggedBaseline.js';
2
2
  import { type ReleaseHistory } from './buildChangelogEntries.js';
3
+ import { type TargetRelease } from './buildReleasedResult.js';
3
4
  import { type OverrideContext } from './changelogOverrides.js';
4
5
  import { type GenerateChangelogOptions } from './generateChangelogs.js';
5
6
  import type { PlannedWrite } from './releasePlan.js';
6
- import type { DirectoryTarget, PropagationSource, ReleasedTargetResult, ReleaseSettings, ReleaseTarget, ReleaseType, TargetPrepareResult } from './types.js';
7
+ import type { DirectoryTarget, ReleasedTargetResult, ReleaseSettings, ReleaseTarget, ReleaseType, TargetPrepareResult } from './types.js';
7
8
  /** The shared settings that the prepare step reads, which do not include the release-notes settings. */
8
9
  export type TargetPrepareSettings = Omit<ReleaseSettings, 'releaseNotes'>;
9
10
  /** One read of a target's release windows, and the untagged baseline that it found, if any. */
@@ -26,12 +27,6 @@ export type TargetDecision = {
26
27
  outcome: 'skip';
27
28
  skipReason: string;
28
29
  };
29
- /** The release level that a target takes, whether its own commits or a dependency's bump decided it. */
30
- export interface TargetRelease {
31
- releaseType: ReleaseType;
32
- /** Dependencies whose bumps this release records; present for a propagated or mixed release. */
33
- propagatedFrom?: PropagationSource[];
34
- }
35
30
  /** Inputs to {@link planTargetRelease}. */
36
31
  export interface PlanTargetReleaseArgs {
37
32
  target: ReleaseTarget;
@@ -1,17 +1,13 @@
1
- import { join as joinPath } from 'node:path';
2
1
  import { findUntaggedBaseline } from "./assertTaggedBaseline.js";
3
2
  import { attachChangelogDiagnostics } from "./attachChangelogDiagnostics.js";
4
3
  import { readReleaseHistory, toReleaseEntries } from "./buildChangelogEntries.js";
5
- import { buildSyntheticChangelogEntry } from "./buildSyntheticChangelogEntry.js";
6
- import { describeArchivePath, listArchiveFiles } from "./changelogArchive.js";
7
- import { mergeChangelogEntriesWithDisk, renderChangelogJson, resolveChangelogJsonPath } from "./changelogJsonFile.js";
4
+ import { buildPropagationEntries, buildReleasedResult } from "./buildReleasedResult.js";
8
5
  import { applyWorkspaceOverrides } from "./changelogOverrides.js";
9
6
  import { decideRelease } from "./decideRelease.js";
10
7
  import { getAllTagPrefixes } from "./generateChangelogs.js";
8
+ import { planChangelogFiles } from "./planChangelogFiles.js";
11
9
  import { planReleaseNotesPreviews } from "./planReleaseNotesPreviews.js";
12
10
  import { planVersionBump, planVersionSet } from "./planVersionBump.js";
13
- import { planPreservedSections } from "./readChangelogSections.js";
14
- import { renderChangelogFiles } from "./renderChangelogMarkdown.js";
15
11
  import { PROJECT_TARGET_ID } from "./resolveReleaseTargets.js";
16
12
  export function readTargetHistory(target, settings) {
17
13
  const options = describeHistoryWindow(target);
@@ -124,82 +120,6 @@ export function buildSkippedTargetResult(target, history, skipReason) {
124
120
  export function isRepoRoot(target) {
125
121
  return target.directoryPath === '.';
126
122
  }
127
- function buildReleasedDirectory(target, fields, release, decision, history) {
128
- const { unreleased } = history;
129
- const setVersion = decision?.setVersion;
130
- const result = {
131
- ...fields,
132
- status: 'released',
133
- kind: 'directory',
134
- name: target.id,
135
- commits: unreleased.commits,
136
- ...(setVersion === undefined && {
137
- releaseType: release.releaseType,
138
- parsedCommitCount: unreleased.parsedCommitCount,
139
- }),
140
- ...(decision?.bumpOverride !== undefined && { bumpOverride: decision.bumpOverride }),
141
- ...(setVersion !== undefined && { setVersion }),
142
- };
143
- if (setVersion === undefined && unreleased.unparseableCommits !== undefined) {
144
- result.unparseableCommits = unreleased.unparseableCommits;
145
- }
146
- attachChangelogDiagnostics(result, unreleased.diagnostics);
147
- return result;
148
- }
149
- function buildReleasedComposite(fields, release, decision, history) {
150
- const { unreleased } = history;
151
- const result = {
152
- ...fields,
153
- status: 'released',
154
- kind: 'composite',
155
- parsedCommitCount: unreleased.parsedCommitCount,
156
- releaseType: release.releaseType,
157
- commits: unreleased.commits,
158
- ...(unreleased.unparseableCommits !== undefined && { unparseableCommits: unreleased.unparseableCommits }),
159
- ...(decision?.bumpOverride !== undefined && { bumpOverride: decision.bumpOverride }),
160
- };
161
- attachChangelogDiagnostics(result, unreleased.diagnostics);
162
- return result;
163
- }
164
- function buildReleasedPackage(target, fields, release, decision, history) {
165
- const { unreleased } = history;
166
- const setVersion = decision?.setVersion;
167
- const hasParseCounts = decision !== undefined && setVersion === undefined;
168
- const result = {
169
- ...fields,
170
- status: 'released',
171
- kind: 'package',
172
- commits: unreleased.commits,
173
- ...(target.isWorkspace && { name: target.id }),
174
- ...(setVersion === undefined && { releaseType: release.releaseType }),
175
- ...(hasParseCounts && { parsedCommitCount: unreleased.parsedCommitCount }),
176
- ...(decision === undefined && { propagatedOnly: true }),
177
- ...(release.propagatedFrom !== undefined && { propagatedFrom: release.propagatedFrom }),
178
- ...(decision?.bumpOverride !== undefined && { bumpOverride: decision.bumpOverride }),
179
- ...(setVersion !== undefined && { setVersion }),
180
- };
181
- if (setVersion === undefined && unreleased.unparseableCommits !== undefined) {
182
- result.unparseableCommits = unreleased.unparseableCommits;
183
- }
184
- attachChangelogDiagnostics(result, unreleased.diagnostics);
185
- return result;
186
- }
187
- function buildReleasedResult(target, fields, release, decision, history) {
188
- switch (target.kind) {
189
- case 'package':
190
- return buildReleasedPackage(target, fields, release, decision, history);
191
- case 'directory':
192
- return buildReleasedDirectory(target, fields, release, decision, history);
193
- case 'composite':
194
- return buildReleasedComposite(fields, release, decision, history);
195
- }
196
- }
197
- function buildPropagationEntries(target, release, newVersion, today, dependenciesHeader) {
198
- if (release.propagatedFrom === undefined) {
199
- throw new Error(`Workspace '${target.id}' has neither a direct release nor a propagation source`);
200
- }
201
- return [buildSyntheticChangelogEntry(release.propagatedFrom, newVersion, today, dependenciesHeader)];
202
- }
203
123
  function describeBaselineLabel(target) {
204
124
  switch (target.kind) {
205
125
  case 'composite':
@@ -236,39 +156,3 @@ function isScopeRouted(target) {
236
156
  }
237
157
  return target.kind === 'package' && target.isWorkspace;
238
158
  }
239
- function planChangelogFiles(changelogDir, newEntries, settings, sectionOrder) {
240
- const jsonPath = resolveChangelogJsonPath(settings, changelogDir);
241
- const entries = mergeChangelogEntriesWithDisk(jsonPath, newEntries);
242
- const writes = [];
243
- const changelogJsonFiles = [];
244
- if (settings.changelogJson.enabled) {
245
- writes.push({ path: jsonPath, content: renderChangelogJson(entries) });
246
- changelogJsonFiles.push(jsonPath);
247
- }
248
- const changelogFile = joinPath(changelogDir, 'CHANGELOG.md');
249
- const preserved = planPreservedSections(changelogDir, entries);
250
- const keepVersions = settings.changelogMarkdown?.keepVersions;
251
- const rendered = renderChangelogFiles(entries, {
252
- sectionOrder,
253
- preservedSections: preserved.sections,
254
- ...(keepVersions !== undefined && { keepVersions }),
255
- });
256
- writes.push({ path: changelogFile, content: rendered.main });
257
- const archiveFiles = [];
258
- for (const { major, content } of rendered.archives) {
259
- const path = joinPath(changelogDir, describeArchivePath(major));
260
- writes.push({ path, content });
261
- archiveFiles.push(path);
262
- }
263
- const removals = listArchiveFiles(changelogDir)
264
- .map((archive) => archive.path)
265
- .filter((path) => !archiveFiles.includes(path));
266
- return {
267
- changelogFiles: [changelogFile, ...archiveFiles],
268
- changelogJsonFiles,
269
- entries,
270
- preservation: preserved.preservation,
271
- removals,
272
- writes,
273
- };
274
- }
@@ -1,5 +1,5 @@
1
+ import type { ReleaseKitConfig } from './configSchema.js';
1
2
  import { type UndeclaredTagPrefix } from './detectUndeclaredTagPrefixes.js';
2
- import type { ReleaseKitConfig } from './types.js';
3
3
  /** One workspace's or directory target's preview row in the tag-prefix preview. */
4
4
  export interface TagPrefixPreviewRow {
5
5
  /** Directory path relative to the repo root (e.g., `packages/core`). */