@williamthorsen/release-kit 10.2.1 → 10.2.2

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 (39) hide show
  1. package/.readyup/kits/default.js +1 -1
  2. package/.readyup/kits/npm-auto-publish.js +135 -23
  3. package/.readyup/manifest.json +3 -3
  4. package/CHANGELOG.md +22 -0
  5. package/README.md +13 -3
  6. package/dist/esm/buildChangelogEntries.js +1 -1
  7. package/dist/esm/buildReleaseSummary.d.ts +1 -1
  8. package/dist/esm/changelogJsonFile.d.ts +1 -3
  9. package/dist/esm/changelogJsonFile.js +4 -18
  10. package/dist/esm/commitCommand.js +3 -13
  11. package/dist/esm/createTags.js +2 -13
  12. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +2 -0
  13. package/dist/esm/injectReleaseNotesIntoReadme.js +13 -10
  14. package/dist/esm/planReleaseNotesPreviews.d.ts +13 -0
  15. package/dist/esm/planReleaseNotesPreviews.js +43 -0
  16. package/dist/esm/planVersionBump.d.ts +9 -0
  17. package/dist/esm/{bumpAllVersions.js → planVersionBump.js} +15 -26
  18. package/dist/esm/prepareCommand.d.ts +0 -4
  19. package/dist/esm/prepareCommand.js +51 -45
  20. package/dist/esm/releaseFiles.d.ts +4 -0
  21. package/dist/esm/releaseFiles.js +34 -0
  22. package/dist/esm/releasePlan.d.ts +10 -0
  23. package/dist/esm/releasePlan.js +53 -0
  24. package/dist/esm/releasePrepare.d.ts +3 -3
  25. package/dist/esm/releasePrepare.js +53 -54
  26. package/dist/esm/releasePrepareMono.d.ts +3 -2
  27. package/dist/esm/releasePrepareMono.js +48 -52
  28. package/dist/esm/releasePrepareProject.d.ts +3 -0
  29. package/dist/esm/releasePrepareProject.js +33 -37
  30. package/dist/esm/renderChangelogMarkdown.d.ts +0 -7
  31. package/dist/esm/renderChangelogMarkdown.js +0 -13
  32. package/dist/esm/reportPrepare.d.ts +5 -1
  33. package/dist/esm/reportPrepare.js +36 -17
  34. package/dist/esm/types.d.ts +4 -9
  35. package/dist/esm/workTypesData.d.ts +1 -1
  36. package/package.json +2 -2
  37. package/dist/esm/bumpAllVersions.d.ts +0 -3
  38. package/dist/esm/writeReleaseNotesPreviews.d.ts +0 -18
  39. package/dist/esm/writeReleaseNotesPreviews.js +0 -63
@@ -1,18 +1,18 @@
1
- import { parseArgsOrExit, reportError, writeFileWithCheck } from '@williamthorsen/nmr-core';
1
+ import { execSync } from 'node:child_process';
2
+ import { parseArgsOrExit, reportError } from '@williamthorsen/nmr-core';
2
3
  import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
3
4
  import { buildDependencyGraph } from "./buildDependencyGraph.js";
4
- import { buildReleaseSummary } from "./buildReleaseSummary.js";
5
5
  import { discoverWorkspaces } from "./discoverWorkspaces.js";
6
6
  import { dim } from "./format.js";
7
7
  import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
8
8
  import { loadConfig, mergeMonorepoConfig, mergeSinglePackageConfig, readRootPackageVersion } from "./loadConfig.js";
9
+ import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
10
+ import { applyReleasePlan } from "./releasePlan.js";
9
11
  import { releasePrepare } from "./releasePrepare.js";
10
12
  import { releasePrepareMono } from "./releasePrepareMono.js";
11
13
  import { reportPrepare } from "./reportPrepare.js";
12
14
  import { validateConfig } from "./validateConfig.js";
13
15
  import { validateOnlyExcludesStrandedDependents } from "./validateOnlyExcludesStrandedDependents.js";
14
- export const RELEASE_TAGS_FILE = 'tmp/.release-tags';
15
- export const RELEASE_SUMMARY_FILE = 'tmp/.release-summary';
16
16
  const VALID_BUMP_TYPES = ['major', 'minor', 'patch'];
17
17
  const CANONICAL_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
18
18
  function isReleaseType(value) {
@@ -71,19 +71,9 @@ export function parseArgs(argv) {
71
71
  withReleaseNotes: flags.withReleaseNotes,
72
72
  };
73
73
  }
74
- export function writeReleaseTags(tags, dryRun) {
75
- if (tags.length === 0) {
76
- return undefined;
77
- }
78
- return writeFileWithCheck(RELEASE_TAGS_FILE, tags.join('\n'), {
79
- dryRun,
80
- overwrite: true,
81
- });
82
- }
83
74
  export async function prepareCommand(argv) {
84
75
  const { dryRun, force, noGitChecks, bumpOverride, only, setVersion, withReleaseNotes } = parseArgs(argv);
85
76
  const options = {
86
- dryRun,
87
77
  force,
88
78
  ...(bumpOverride !== undefined && { bumpOverride }),
89
79
  ...(setVersion !== undefined && { setVersion }),
@@ -219,48 +209,64 @@ async function loadAndValidateConfig() {
219
209
  }
220
210
  return config;
221
211
  }
222
- function runAndReport(execute, dryRun) {
223
- let result;
212
+ function runAndReport(computePlan, dryRun) {
213
+ let plan;
224
214
  try {
225
- result = execute();
215
+ plan = computePlan();
226
216
  }
227
217
  catch (error) {
228
218
  reportError(error instanceof Error ? error.message : String(error));
219
+ process.stderr.write('No files were written; the working tree is unchanged.\n');
229
220
  process.exit(1);
230
221
  }
231
- process.stdout.write(reportPrepare(result) + '\n');
232
- const writeResult = writeReleaseTags(result.tags, dryRun);
233
- if (writeResult?.outcome === 'failed') {
234
- reportError(`Failed to write release tags: ${writeResult.error ?? 'unknown error'}`);
222
+ if (dryRun) {
223
+ process.stdout.write(reportPrepare(plan, { applied: false }) + '\n');
224
+ reportPlanFiles(plan, true);
225
+ return;
226
+ }
227
+ try {
228
+ applyReleasePlan(plan);
229
+ }
230
+ catch (error) {
231
+ reportError(error instanceof Error ? error.message : String(error));
235
232
  process.exit(1);
236
233
  }
237
- if (writeResult) {
238
- if (dryRun) {
239
- console.info(dim(` [dry-run] Would write ${RELEASE_TAGS_FILE}: ${result.tags.join(' ')}`));
240
- }
241
- else {
242
- console.info(dim(` Wrote ${RELEASE_TAGS_FILE}: ${result.tags.join(' ')}`));
243
- console.info(dim(`\n Release tags file: ${RELEASE_TAGS_FILE}`));
244
- }
234
+ const formatError = runFormatCommand(plan.formatCommand);
235
+ process.stdout.write(reportPrepare(plan, { applied: true, ...(formatError !== undefined && { formatError }) }) + '\n');
236
+ reportPlanFiles(plan, false);
237
+ if (formatError !== undefined) {
238
+ reportError(`The format command failed, but the release is prepared and ${RELEASE_TAGS_FILE} is written. ` +
239
+ `Format the release files and run 'release-kit commit'.`);
240
+ process.exit(1);
245
241
  }
246
- const summary = buildReleaseSummary(result);
247
- if (summary.length > 0) {
248
- const summaryResult = writeFileWithCheck(RELEASE_SUMMARY_FILE, summary, {
249
- dryRun,
250
- overwrite: true,
251
- });
252
- if (summaryResult.outcome === 'failed') {
253
- reportError(`Failed to write release summary: ${summaryResult.error ?? 'unknown error'}`);
254
- process.exit(1);
255
- }
256
- if (dryRun) {
242
+ }
243
+ function runFormatCommand(formatCommand) {
244
+ if (formatCommand === undefined) {
245
+ return undefined;
246
+ }
247
+ try {
248
+ execSync(formatCommand.command, { stdio: 'inherit' });
249
+ return undefined;
250
+ }
251
+ catch (error) {
252
+ return error instanceof Error ? error.message : String(error);
253
+ }
254
+ }
255
+ function reportPlanFiles(plan, dryRun) {
256
+ if (plan.tags.length === 0) {
257
+ return;
258
+ }
259
+ if (dryRun) {
260
+ console.info(dim(` [dry-run] Would write ${RELEASE_TAGS_FILE}: ${plan.tags.join(' ')}`));
261
+ if (plan.summary.length > 0) {
257
262
  console.info(dim(` [dry-run] Would write ${RELEASE_SUMMARY_FILE}`));
258
263
  }
259
- else {
260
- console.info(dim(` Wrote ${RELEASE_SUMMARY_FILE}`));
261
- }
264
+ return;
262
265
  }
263
- if (writeResult && !dryRun) {
264
- process.stderr.write(`\nRun 'release-kit commit' to create the release commit.\n`);
266
+ console.info(dim(` Wrote ${RELEASE_TAGS_FILE}: ${plan.tags.join(' ')}`));
267
+ console.info(dim(`\n Release tags file: ${RELEASE_TAGS_FILE}`));
268
+ if (plan.summary.length > 0) {
269
+ console.info(dim(` Wrote ${RELEASE_SUMMARY_FILE}`));
265
270
  }
271
+ process.stderr.write(`\nRun 'release-kit commit' to create the release commit.\n`);
266
272
  }
@@ -0,0 +1,4 @@
1
+ export declare const RELEASE_TAGS_FILE = "tmp/.release-tags";
2
+ export declare const RELEASE_SUMMARY_FILE = "tmp/.release-summary";
3
+ export declare function resolveReleaseTagsPath(): string;
4
+ export declare function readReleaseTags(): string[];
@@ -0,0 +1,34 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ export const RELEASE_TAGS_FILE = 'tmp/.release-tags';
4
+ export const RELEASE_SUMMARY_FILE = 'tmp/.release-summary';
5
+ export function resolveReleaseTagsPath() {
6
+ return resolve(RELEASE_TAGS_FILE);
7
+ }
8
+ export function readReleaseTags() {
9
+ let content;
10
+ try {
11
+ content = readFileSync(RELEASE_TAGS_FILE, 'utf8');
12
+ }
13
+ catch (error) {
14
+ throw new Error(describeTagsReadFailure(error), { cause: error });
15
+ }
16
+ return content
17
+ .split('\n')
18
+ .map((line) => line.trim())
19
+ .filter((line) => line.length > 0);
20
+ }
21
+ function describeTagsReadFailure(error) {
22
+ const tagsPath = resolveReleaseTagsPath();
23
+ if (getErrnoCode(error) === 'ENOENT') {
24
+ return `No tags file found at ${tagsPath}. Run \`release-kit prepare\` first.`;
25
+ }
26
+ const detail = error instanceof Error ? error.message : String(error);
27
+ return `Cannot read the tags file at ${tagsPath}: ${detail}`;
28
+ }
29
+ function getErrnoCode(error) {
30
+ if (error instanceof Error && 'code' in error && typeof error.code === 'string') {
31
+ return error.code;
32
+ }
33
+ return undefined;
34
+ }
@@ -0,0 +1,10 @@
1
+ import type { PrepareResult } from './types.js';
2
+ export interface PlannedWrite {
3
+ path: string;
4
+ content: string;
5
+ }
6
+ export interface ReleasePlan extends PrepareResult {
7
+ writes: readonly PlannedWrite[];
8
+ summary: string;
9
+ }
10
+ export declare function applyReleasePlan(plan: ReleasePlan): string[];
@@ -0,0 +1,53 @@
1
+ import { writeFileWithCheck } from '@williamthorsen/nmr-core';
2
+ import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./releaseFiles.js";
3
+ const HANDOFF_FILES = new Set([RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE]);
4
+ export function applyReleasePlan(plan) {
5
+ const ordered = orderPlannedWrites(plan);
6
+ const applied = [];
7
+ for (const [index, write] of ordered.entries()) {
8
+ const result = writeFileWithCheck(write.path, write.content, { dryRun: false, overwrite: true });
9
+ if (result.outcome === 'failed') {
10
+ const unwritten = ordered.slice(index).map((pending) => pending.path);
11
+ throw new Error(describeApplyFailure({ failedPath: write.path, error: result.error, applied, unwritten }));
12
+ }
13
+ applied.push({ path: write.path, created: result.outcome === 'created' });
14
+ }
15
+ return applied.map((write) => write.path);
16
+ }
17
+ function orderPlannedWrites(plan) {
18
+ const ordered = [...plan.writes];
19
+ if (plan.summary.length > 0) {
20
+ ordered.push({ path: RELEASE_SUMMARY_FILE, content: plan.summary });
21
+ }
22
+ if (plan.tags.length > 0) {
23
+ ordered.push({ path: RELEASE_TAGS_FILE, content: plan.tags.join('\n') });
24
+ }
25
+ return ordered;
26
+ }
27
+ function describeApplyFailure(args) {
28
+ const { failedPath, error, applied, unwritten } = args;
29
+ const total = applied.length + unwritten.length;
30
+ const lines = [`Failed to write ${failedPath}: ${error ?? 'unknown error'}`, ''];
31
+ if (applied.length === 0) {
32
+ lines.push(`The release was not applied. None of its ${total} files were written.`);
33
+ return lines.join('\n');
34
+ }
35
+ lines.push(`The release was partially applied. ${applied.length} of ${total} files were written:`, ...applied.map((write) => ` ${write.path}`), '', 'Not written:', ...unwritten.map((path) => ` ${path}`), ...describeRecovery(applied));
36
+ return lines.join('\n');
37
+ }
38
+ function describeRecovery(applied) {
39
+ const releaseWrites = applied.filter((write) => !HANDOFF_FILES.has(write.path));
40
+ const replaced = releaseWrites.filter((write) => !write.created).map((write) => write.path);
41
+ const created = releaseWrites.filter((write) => write.created).map((write) => write.path);
42
+ if (replaced.length === 0 && created.length === 0) {
43
+ return [];
44
+ }
45
+ const lines = ['', 'Undo the written release files before re-running `release-kit prepare`:'];
46
+ if (replaced.length > 0) {
47
+ lines.push(` git restore ${replaced.join(' ')}`);
48
+ }
49
+ if (created.length > 0) {
50
+ lines.push(` rm ${created.join(' ')}`);
51
+ }
52
+ return lines;
53
+ }
@@ -1,9 +1,9 @@
1
- import type { PrepareResult, ReleaseConfig, ReleaseType } from './types.js';
1
+ import type { ReleasePlan } from './releasePlan.js';
2
+ import type { ReleaseConfig, ReleaseType } from './types.js';
2
3
  export interface ReleasePrepareOptions {
3
- dryRun: boolean;
4
4
  force?: boolean;
5
5
  bumpOverride?: ReleaseType;
6
6
  setVersion?: string;
7
7
  withReleaseNotes?: boolean;
8
8
  }
9
- export declare function releasePrepare(config: ReleaseConfig, options: ReleasePrepareOptions): PrepareResult;
9
+ export declare function releasePrepare(config: ReleaseConfig, options: ReleasePrepareOptions): ReleasePlan;
@@ -1,8 +1,8 @@
1
- import { execSync } from 'node:child_process';
1
+ import { join as joinPath } from 'node:path';
2
2
  import { buildChangelogEntries } from "./buildChangelogEntries.js";
3
3
  import { buildEmptyReleaseEntry } from "./buildEmptyReleaseEntry.js";
4
- import { bumpAllVersions, setAllVersions } from "./bumpAllVersions.js";
5
- import { mergeChangelogEntriesWithDisk, resolveChangelogJsonPath, upsertChangelogJsonAndReturn, } from "./changelogJsonFile.js";
4
+ import { buildReleaseSummary } from "./buildReleaseSummary.js";
5
+ import { mergeChangelogEntriesWithDisk, renderChangelogJson, resolveChangelogJsonPath } from "./changelogJsonFile.js";
6
6
  import { applyChangelogOverrides, formatStaleOverrideKeyWarning, loadOverridesForScopes, } from "./changelogOverrides.js";
7
7
  import { createPolicyViolationCollector } from "./collectPolicyViolations.js";
8
8
  import { isForwardVersion } from "./compareVersions.js";
@@ -11,13 +11,15 @@ import { determineBumpFromCommits } from "./determineBumpFromCommits.js";
11
11
  import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
12
12
  import { hasPrettierConfig } from "./hasPrettierConfig.js";
13
13
  import { resolveWorkTypes } from "./loadConfig.js";
14
+ import { planReleaseNotesPreviews } from "./planReleaseNotesPreviews.js";
15
+ import { planVersionBump, planVersionSet } from "./planVersionBump.js";
14
16
  import { readCurrentVersion } from "./readCurrentVersion.js";
15
- import { writeChangelogMarkdown } from "./renderChangelogMarkdown.js";
17
+ import { renderChangelogMarkdown } from "./renderChangelogMarkdown.js";
16
18
  import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
17
19
  import { refreshGitCliffCache } from "./runGitCliff.js";
18
- import { writeReleaseNotesPreviews } from "./writeReleaseNotesPreviews.js";
19
20
  export function releasePrepare(config, options) {
20
- const { dryRun, bumpOverride, setVersion, withReleaseNotes } = options;
21
+ const { bumpOverride, setVersion, withReleaseNotes } = options;
22
+ const writes = [];
21
23
  const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
22
24
  const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
23
25
  const breakingPolicies = config.breakingPolicies ?? DEFAULT_BREAKING_POLICIES;
@@ -45,7 +47,7 @@ export function releasePrepare(config, options) {
45
47
  throw new Error(`--set-version ${setVersion} is not greater than current version ${currentVersion}`);
46
48
  }
47
49
  refreshGitCliffCache();
48
- bump = setAllVersions(config.packageFiles, setVersion, dryRun);
50
+ bump = planVersionSet(config.packageFiles, setVersion);
49
51
  }
50
52
  else {
51
53
  if (bumpOverride === undefined) {
@@ -71,46 +73,37 @@ export function releasePrepare(config, options) {
71
73
  return {
72
74
  workspaces: [skipped],
73
75
  tags: [],
74
- dryRun,
76
+ writes: [],
77
+ summary: '',
78
+ formatCommand: undefined,
75
79
  };
76
80
  }
77
81
  refreshGitCliffCache();
78
- bump = bumpAllVersions(config.packageFiles, releaseType, dryRun);
82
+ bump = planVersionBump(config.packageFiles, releaseType);
79
83
  }
84
+ writes.push(...bump.writes);
80
85
  const newTag = `${config.tagPrefix}${bump.newVersion}`;
81
86
  const overrideWarnings = [];
82
- const { changelogFiles, changelogJsonFiles } = writeSinglePackageChangelogs({
87
+ const changelogs = planSinglePackageChangelogs({
83
88
  config,
84
89
  commits,
85
90
  newTag,
86
91
  newVersion: bump.newVersion,
87
- dryRun,
88
92
  overrides,
89
93
  overrideWarnings,
90
94
  });
91
- maybeWriteSinglePackagePreviews(withReleaseNotes === true, config, newTag, changelogJsonFiles[0], dryRun);
95
+ const { changelogFiles, changelogJsonFiles } = changelogs;
96
+ const previewWrites = planSinglePackagePreviews(withReleaseNotes === true, config, newTag, changelogs.entries, overrideWarnings);
97
+ writes.push(...changelogs.writes, ...previewWrites);
92
98
  const formatCommandStr = config.formatCommand ?? (hasPrettierConfig() ? 'npx prettier --write' : undefined);
93
99
  let formatCommand;
94
100
  if (formatCommandStr !== undefined) {
95
101
  const modifiedFiles = [
96
102
  ...config.packageFiles,
97
- ...config.changelogPaths.map((p) => `${p}/CHANGELOG.md`),
103
+ ...config.changelogPaths.map((changelogPath) => joinPath(changelogPath, 'CHANGELOG.md')),
98
104
  ...changelogJsonFiles,
99
105
  ];
100
- const fullCommand = `${formatCommandStr} ${modifiedFiles.join(' ')}`;
101
- if (dryRun) {
102
- formatCommand = { command: fullCommand, executed: false, files: modifiedFiles };
103
- }
104
- else {
105
- try {
106
- execSync(fullCommand, { stdio: 'inherit' });
107
- }
108
- catch (error) {
109
- const baseMessage = error instanceof Error ? error.message : String(error);
110
- throw new Error(`format stage: ${baseMessage} (command: '${fullCommand}')`, { cause: error });
111
- }
112
- formatCommand = { command: fullCommand, executed: true, files: modifiedFiles };
113
- }
106
+ formatCommand = { command: `${formatCommandStr} ${modifiedFiles.join(' ')}`, files: modifiedFiles };
114
107
  }
115
108
  const released = buildReleasedSinglePackage({
116
109
  commits,
@@ -123,17 +116,19 @@ export function releasePrepare(config, options) {
123
116
  unparseableCommits,
124
117
  policyViolations: collector.violations.length > 0 ? collector.violations : undefined,
125
118
  setVersion,
119
+ previewFiles: previewWrites.map((write) => write.path),
126
120
  });
127
- const result = {
121
+ const plan = {
128
122
  workspaces: [released],
129
123
  tags: [newTag],
124
+ writes,
125
+ summary: buildReleaseSummary({ workspaces: [released] }),
130
126
  formatCommand,
131
- dryRun,
132
127
  };
133
128
  if (overrideWarnings.length > 0) {
134
- result.warnings = overrideWarnings;
129
+ plan.warnings = overrideWarnings;
135
130
  }
136
- return result;
131
+ return plan;
137
132
  }
138
133
  function buildSkippedSinglePackage(args) {
139
134
  const skipped = {
@@ -163,7 +158,7 @@ function buildReleasedSinglePackage(args) {
163
158
  currentVersion: bump.currentVersion,
164
159
  newVersion: bump.newVersion,
165
160
  tag: newTag,
166
- bumpedFiles: bump.files,
161
+ bumpedFiles: bump.writes.map((write) => write.path),
167
162
  changelogFiles,
168
163
  commits,
169
164
  };
@@ -185,10 +180,13 @@ function buildReleasedSinglePackage(args) {
185
180
  if (setVersion !== undefined) {
186
181
  released.setVersion = setVersion;
187
182
  }
183
+ if (args.previewFiles.length > 0) {
184
+ released.previewFiles = args.previewFiles;
185
+ }
188
186
  return released;
189
187
  }
190
- function writeSinglePackageChangelogs(args) {
191
- const { config, commits, newTag, newVersion, dryRun, overrides, overrideWarnings } = args;
188
+ function planSinglePackageChangelogs(args) {
189
+ const { config, commits, newTag, newVersion, overrides, overrideWarnings } = args;
192
190
  const isEmptyRange = commits.length === 0;
193
191
  const today = new Date().toISOString().slice(0, 10);
194
192
  const baseEntries = isEmptyRange
@@ -208,40 +206,41 @@ function writeSinglePackageChangelogs(args) {
208
206
  const sectionOrder = deriveSectionOrder(resolveWorkTypes(config.workTypes));
209
207
  const changelogFiles = [];
210
208
  const changelogJsonFiles = [];
209
+ const writes = [];
210
+ let firstMergedEntries = [];
211
211
  for (const changelogPath of config.changelogPaths) {
212
212
  const jsonPath = resolveChangelogJsonPath(config, changelogPath);
213
- const shouldWriteJson = config.changelogJson.enabled && !dryRun;
214
- const mergedEntries = shouldWriteJson
215
- ? upsertChangelogJsonAndReturn(jsonPath, applied.entries)
216
- : mergeChangelogEntriesWithDisk(jsonPath, applied.entries);
213
+ const mergedEntries = mergeChangelogEntriesWithDisk(jsonPath, applied.entries);
214
+ if (changelogFiles.length === 0) {
215
+ firstMergedEntries = mergedEntries;
216
+ }
217
217
  if (config.changelogJson.enabled) {
218
+ writes.push({ path: jsonPath, content: renderChangelogJson(mergedEntries) });
218
219
  changelogJsonFiles.push(jsonPath);
219
220
  }
220
- changelogFiles.push(writeChangelogMarkdown({
221
- changelogPath,
222
- entries: mergedEntries,
223
- sectionOrder,
224
- dryRun,
225
- }));
226
- }
227
- return { changelogFiles, changelogJsonFiles };
221
+ const changelogFile = joinPath(changelogPath, 'CHANGELOG.md');
222
+ writes.push({ path: changelogFile, content: renderChangelogMarkdown(mergedEntries, { sectionOrder }) });
223
+ changelogFiles.push(changelogFile);
224
+ }
225
+ return { changelogFiles, changelogJsonFiles, entries: firstMergedEntries, writes };
228
226
  }
229
- function maybeWriteSinglePackagePreviews(withReleaseNotes, config, newTag, changelogJsonPath, dryRun) {
227
+ function planSinglePackagePreviews(withReleaseNotes, config, newTag, entries, warnings) {
230
228
  if (!withReleaseNotes) {
231
- return;
229
+ return [];
232
230
  }
233
231
  if (!config.changelogJson.enabled) {
234
232
  console.warn('Warning: --with-release-notes requires changelogJson.enabled; skipping preview generation');
235
- return;
233
+ return [];
236
234
  }
237
- if (changelogJsonPath === undefined) {
238
- return;
235
+ if (config.changelogPaths.length === 0) {
236
+ return [];
239
237
  }
240
- writeReleaseNotesPreviews({
238
+ const previews = planReleaseNotesPreviews({
241
239
  workspacePath: process.cwd(),
242
240
  tag: newTag,
243
- changelogJsonPath,
241
+ entries,
244
242
  sectionOrder: deriveSectionOrder(resolveWorkTypes(config.workTypes)),
245
- dryRun,
246
243
  });
244
+ warnings.push(...previews.warnings);
245
+ return previews.writes;
247
246
  }
@@ -1,3 +1,4 @@
1
+ import type { ReleasePlan } from './releasePlan.js';
1
2
  import type { ReleasePrepareOptions } from './releasePrepare.js';
2
- import type { MonorepoReleaseConfig, PrepareResult } from './types.js';
3
- export declare function releasePrepareMono(config: MonorepoReleaseConfig, options: ReleasePrepareOptions): PrepareResult;
3
+ import type { MonorepoReleaseConfig } from './types.js';
4
+ export declare function releasePrepareMono(config: MonorepoReleaseConfig, options: ReleasePrepareOptions): ReleasePlan;