@williamthorsen/release-kit 10.2.1 → 10.3.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 (63) hide show
  1. package/.readyup/kits/default.js +78 -55
  2. package/.readyup/kits/npm-auto-publish.js +169 -57
  3. package/.readyup/manifest.json +6 -6
  4. package/CHANGELOG.md +54 -0
  5. package/README.md +18 -6
  6. package/bin/release-kit.js +1 -1
  7. package/dist/esm/bin/release-kit.js +4 -4
  8. package/dist/esm/buildChangelogEntries.js +17 -15
  9. package/dist/esm/buildDependencyGraph.js +6 -2
  10. package/dist/esm/buildReleaseSummary.d.ts +1 -1
  11. package/dist/esm/changelogJsonFile.d.ts +1 -3
  12. package/dist/esm/changelogJsonFile.js +4 -18
  13. package/dist/esm/changelogJsonUtils.js +7 -7
  14. package/dist/esm/changelogOverrides.js +13 -12
  15. package/dist/esm/checkWorkTypesDrift.js +1 -1
  16. package/dist/esm/commitCommand.js +3 -13
  17. package/dist/esm/createTags.js +3 -14
  18. package/dist/esm/deriveWorkspaceConfig.js +3 -3
  19. package/dist/esm/detectPackageManager.js +2 -2
  20. package/dist/esm/detectUndeclaredTagPrefixes.js +1 -1
  21. package/dist/esm/discoverWorkspaces.js +1 -1
  22. package/dist/esm/getCommitsSinceTarget.js +2 -2
  23. package/dist/esm/help/prepareHelp.d.ts +1 -1
  24. package/dist/esm/help/prepareHelp.js +2 -2
  25. package/dist/esm/init/checks.js +1 -1
  26. package/dist/esm/init/detectRepoType.js +1 -1
  27. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +2 -0
  28. package/dist/esm/injectReleaseNotesIntoReadme.js +13 -10
  29. package/dist/esm/loadConfig.js +8 -6
  30. package/dist/esm/planReleaseNotesPreviews.d.ts +13 -0
  31. package/dist/esm/planReleaseNotesPreviews.js +43 -0
  32. package/dist/esm/planVersionBump.d.ts +9 -0
  33. package/dist/esm/planVersionBump.js +54 -0
  34. package/dist/esm/prepareCommand.d.ts +0 -4
  35. package/dist/esm/prepareCommand.js +51 -45
  36. package/dist/esm/propagateBumps.js +1 -1
  37. package/dist/esm/releaseFiles.d.ts +4 -0
  38. package/dist/esm/releaseFiles.js +34 -0
  39. package/dist/esm/releasePlan.d.ts +10 -0
  40. package/dist/esm/releasePlan.js +53 -0
  41. package/dist/esm/releasePrepare.d.ts +3 -3
  42. package/dist/esm/releasePrepare.js +53 -54
  43. package/dist/esm/releasePrepareMono.d.ts +3 -2
  44. package/dist/esm/releasePrepareMono.js +50 -53
  45. package/dist/esm/releasePrepareProject.d.ts +3 -0
  46. package/dist/esm/releasePrepareProject.js +33 -37
  47. package/dist/esm/renderChangelogMarkdown.d.ts +0 -7
  48. package/dist/esm/renderChangelogMarkdown.js +0 -13
  49. package/dist/esm/reportPrepare.d.ts +5 -1
  50. package/dist/esm/reportPrepare.js +37 -17
  51. package/dist/esm/sync-labels/generateCommand.js +4 -2
  52. package/dist/esm/sync-labels/presets.js +5 -5
  53. package/dist/esm/sync-labels/resolveLabels.js +5 -3
  54. package/dist/esm/syncWorkTypes.js +1 -1
  55. package/dist/esm/types.d.ts +4 -9
  56. package/dist/esm/validateConfig.js +11 -11
  57. package/dist/esm/workTypesData.d.ts +1 -1
  58. package/dist/esm/workTypesUtils.js +1 -1
  59. package/package.json +3 -3
  60. package/dist/esm/bumpAllVersions.d.ts +0 -3
  61. package/dist/esm/bumpAllVersions.js +0 -61
  62. package/dist/esm/writeReleaseNotesPreviews.d.ts +0 -18
  63. package/dist/esm/writeReleaseNotesPreviews.js +0 -63
@@ -1,10 +1,10 @@
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 { buildDependencyGraph } from "./buildDependencyGraph.js";
4
4
  import { buildEmptyReleaseEntry } from "./buildEmptyReleaseEntry.js";
5
+ import { buildReleaseSummary } from "./buildReleaseSummary.js";
5
6
  import { buildSyntheticChangelogEntry } from "./buildSyntheticChangelogEntry.js";
6
- import { bumpAllVersions, setAllVersions } from "./bumpAllVersions.js";
7
- import { mergeChangelogEntriesWithDisk, resolveChangelogJsonPath, upsertChangelogJsonAndReturn, } from "./changelogJsonFile.js";
7
+ import { mergeChangelogEntriesWithDisk, renderChangelogJson, resolveChangelogJsonPath } from "./changelogJsonFile.js";
8
8
  import { applyWorkspaceOverrides, createOverrideContext, formatStaleOverrideKeyWarning, } from "./changelogOverrides.js";
9
9
  import { createPolicyViolationCollector } from "./collectPolicyViolations.js";
10
10
  import { isForwardVersion } from "./compareVersions.js";
@@ -15,15 +15,17 @@ import { buildTagPattern, getAllTagPrefixes } from "./generateChangelogs.js";
15
15
  import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
16
16
  import { hasPrettierConfig } from "./hasPrettierConfig.js";
17
17
  import { resolveWorkTypes } from "./loadConfig.js";
18
+ import { planReleaseNotesPreviews } from "./planReleaseNotesPreviews.js";
19
+ import { planVersionBump, planVersionSet } from "./planVersionBump.js";
18
20
  import { propagateBumps } from "./propagateBumps.js";
19
21
  import { readCurrentVersion } from "./readCurrentVersion.js";
20
22
  import { releasePrepareProject } from "./releasePrepareProject.js";
21
- import { writeChangelogMarkdown } from "./renderChangelogMarkdown.js";
23
+ import { renderChangelogMarkdown } from "./renderChangelogMarkdown.js";
22
24
  import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
23
25
  import { refreshGitCliffCache } from "./runGitCliff.js";
24
- import { writeReleaseNotesPreviews } from "./writeReleaseNotesPreviews.js";
25
26
  export function releasePrepareMono(config, options) {
26
- const { dryRun, withReleaseNotes } = options;
27
+ const { withReleaseNotes } = options;
28
+ const writes = [];
27
29
  if (withReleaseNotes === true && !config.changelogJson.enabled) {
28
30
  console.warn('Warning: --with-release-notes requires changelogJson.enabled; skipping preview generation');
29
31
  }
@@ -59,7 +61,8 @@ export function releasePrepareMono(config, options) {
59
61
  config,
60
62
  directResults,
61
63
  previousTags,
62
- dryRun,
64
+ writes,
65
+ warnings,
63
66
  workspaces,
64
67
  previewOptions,
65
68
  overrideContext,
@@ -77,13 +80,15 @@ export function releasePrepareMono(config, options) {
77
80
  config,
78
81
  options,
79
82
  modifiedFiles,
83
+ writes,
80
84
  tags,
85
+ warnings,
81
86
  rootOverrides: overrideContext.project,
82
87
  overrideWarnings: overrideContext.overrideWarnings,
83
88
  globalMatchedRootKeys: overrideContext.globalMatchedRootKeys,
84
89
  }));
85
90
  }
86
- const formatCommand = runFormatCommand(config, tags, modifiedFiles, dryRun);
91
+ const formatCommand = planFormatCommand(config, tags, modifiedFiles);
87
92
  for (const overrideKey of overrideContext.project.keys()) {
88
93
  if (!overrideContext.globalMatchedRootKeys.has(overrideKey)) {
89
94
  overrideContext.overrideWarnings.push(formatStaleOverrideKeyWarning(overrideKey));
@@ -93,8 +98,9 @@ export function releasePrepareMono(config, options) {
93
98
  return {
94
99
  workspaces,
95
100
  tags,
101
+ writes,
102
+ summary: buildReleaseSummary({ workspaces, ...(project !== undefined && { project }) }),
96
103
  formatCommand,
97
- dryRun,
98
104
  ...(allWarnings.length > 0 && { warnings: allWarnings }),
99
105
  ...(project !== undefined && { project }),
100
106
  };
@@ -221,7 +227,7 @@ function collectSkippedWorkspaces(skippedResults, fullReleaseSet) {
221
227
  return workspaces;
222
228
  }
223
229
  function executeReleaseSet(args) {
224
- const { sortedDirs, fullReleaseSet, config, directResults, previousTags, dryRun, workspaces, previewOptions, overrideContext, sectionOrder, } = args;
230
+ const { sortedDirs, fullReleaseSet, config, directResults, previousTags, writes, warnings, workspaces, previewOptions, overrideContext, sectionOrder, } = args;
225
231
  const tags = [];
226
232
  const modifiedFiles = [];
227
233
  const today = new Date().toISOString().slice(0, 10);
@@ -241,10 +247,11 @@ function executeReleaseSet(args) {
241
247
  directResult: directResults.get(dir),
242
248
  previousTags,
243
249
  config,
244
- dryRun,
245
250
  today,
246
251
  tags,
247
252
  modifiedFiles,
253
+ writes,
254
+ warnings,
248
255
  workspaces,
249
256
  previewOptions,
250
257
  overrideContext,
@@ -254,17 +261,18 @@ function executeReleaseSet(args) {
254
261
  return { tags, modifiedFiles };
255
262
  }
256
263
  function executeWorkspaceRelease(args) {
257
- const { dir, workspace, releaseEntry, directResult, previousTags, config, dryRun, today, tags, modifiedFiles, workspaces, previewOptions, overrideContext, sectionOrder, } = args;
264
+ const { dir, workspace, releaseEntry, directResult, previousTags, config, today, tags, modifiedFiles, writes, warnings, workspaces, previewOptions, overrideContext, sectionOrder, } = args;
258
265
  const setVersionTarget = directResult?.setVersion;
259
266
  const bump = setVersionTarget === undefined
260
- ? bumpAllVersions(workspace.packageFiles, releaseEntry.releaseType, dryRun)
261
- : setAllVersions(workspace.packageFiles, setVersionTarget, dryRun);
267
+ ? planVersionBump(workspace.packageFiles, releaseEntry.releaseType)
268
+ : planVersionSet(workspace.packageFiles, setVersionTarget);
262
269
  const newTag = `${workspace.tagPrefix}${bump.newVersion}`;
263
270
  tags.push(newTag);
264
- modifiedFiles.push(...workspace.packageFiles, ...workspace.changelogPaths.map((p) => `${p}/CHANGELOG.md`));
271
+ writes.push(...bump.writes);
272
+ modifiedFiles.push(...workspace.packageFiles, ...workspace.changelogPaths.map((changelogPath) => joinPath(changelogPath, 'CHANGELOG.md')));
265
273
  const isPropagationOnly = directResult === undefined;
266
274
  const isEmptyRange = directResult !== undefined && directResult.commits.length === 0;
267
- const changelogFiles = generateWorkspaceChangelogs({
275
+ const { changelogFiles, previewFiles } = generateWorkspaceChangelogs({
268
276
  workspace,
269
277
  releaseEntry,
270
278
  newTag,
@@ -272,9 +280,10 @@ function executeWorkspaceRelease(args) {
272
280
  isPropagationOnly,
273
281
  isEmptyRange,
274
282
  config,
275
- dryRun,
276
283
  today,
277
284
  modifiedFiles,
285
+ writes,
286
+ warnings,
278
287
  previewOptions,
279
288
  overrideContext,
280
289
  sectionOrder,
@@ -286,8 +295,9 @@ function executeWorkspaceRelease(args) {
286
295
  currentVersion: bump.currentVersion,
287
296
  newVersion: bump.newVersion,
288
297
  tag: newTag,
289
- bumpedFiles: bump.files,
298
+ bumpedFiles: bump.writes.map((write) => write.path),
290
299
  changelogFiles,
300
+ ...(previewFiles.length > 0 && { previewFiles }),
291
301
  };
292
302
  attachReleasedWorkspaceOptionals(released, {
293
303
  previousTag: directResult?.tag ?? previousTags.get(dir),
@@ -328,7 +338,7 @@ function attachReleasedWorkspaceOptionals(released, args) {
328
338
  }
329
339
  }
330
340
  function generateWorkspaceChangelogs(args) {
331
- const { workspace, releaseEntry, newTag, newVersion, isPropagationOnly, isEmptyRange, config, dryRun, today, modifiedFiles, previewOptions, overrideContext, sectionOrder, } = args;
341
+ const { workspace, releaseEntry, newTag, newVersion, isPropagationOnly, isEmptyRange, config, today, modifiedFiles, writes, warnings, previewOptions, overrideContext, sectionOrder, } = args;
332
342
  const newEntries = buildWorkspaceEntries({
333
343
  workspace,
334
344
  releaseEntry,
@@ -341,26 +351,22 @@ function generateWorkspaceChangelogs(args) {
341
351
  });
342
352
  const applied = applyWorkspaceOverrides(newEntries, workspace.workspacePath, overrideContext);
343
353
  const changelogFiles = [];
344
- let firstChangelogJsonPath;
354
+ let firstMergedEntries;
345
355
  for (const changelogPath of workspace.changelogPaths) {
346
356
  const jsonPath = resolveChangelogJsonPath(config, changelogPath);
347
- const shouldWriteJson = config.changelogJson.enabled && !dryRun;
348
- const mergedEntries = shouldWriteJson
349
- ? upsertChangelogJsonAndReturn(jsonPath, applied.entries)
350
- : mergeChangelogEntriesWithDisk(jsonPath, applied.entries);
357
+ const mergedEntries = mergeChangelogEntriesWithDisk(jsonPath, applied.entries);
351
358
  if (config.changelogJson.enabled) {
359
+ writes.push({ path: jsonPath, content: renderChangelogJson(mergedEntries) });
352
360
  modifiedFiles.push(jsonPath);
353
- firstChangelogJsonPath ??= jsonPath;
361
+ firstMergedEntries ??= mergedEntries;
354
362
  }
355
- changelogFiles.push(writeChangelogMarkdown({
356
- changelogPath,
357
- entries: mergedEntries,
358
- sectionOrder,
359
- dryRun,
360
- }));
363
+ const changelogFile = joinPath(changelogPath, 'CHANGELOG.md');
364
+ writes.push({ path: changelogFile, content: renderChangelogMarkdown(mergedEntries, { sectionOrder }) });
365
+ changelogFiles.push(changelogFile);
361
366
  }
362
- maybeWritePreviews(workspace, newTag, firstChangelogJsonPath, previewOptions, dryRun);
363
- return changelogFiles;
367
+ const previews = planPreviews(workspace, newTag, firstMergedEntries, previewOptions, warnings);
368
+ writes.push(...previews);
369
+ return { changelogFiles, previewFiles: previews.map((write) => write.path) };
364
370
  }
365
371
  function buildWorkspaceEntries(args) {
366
372
  const { workspace, releaseEntry, newTag, newVersion, isPropagationOnly, isEmptyRange, config, today } = args;
@@ -373,35 +379,25 @@ function buildWorkspaceEntries(args) {
373
379
  const tagPattern = buildTagPattern(getAllTagPrefixes(workspace));
374
380
  return buildChangelogEntries(config, newTag, { tagPattern, includePaths: workspace.paths });
375
381
  }
376
- function maybeWritePreviews(workspace, newTag, changelogJsonPath, previewOptions, dryRun) {
377
- if (!previewOptions.enabled || changelogJsonPath === undefined) {
378
- return;
382
+ function planPreviews(workspace, newTag, entries, previewOptions, warnings) {
383
+ if (!previewOptions.enabled || entries === undefined) {
384
+ return [];
379
385
  }
380
- writeReleaseNotesPreviews({
386
+ const previews = planReleaseNotesPreviews({
381
387
  workspacePath: workspace.workspacePath,
382
388
  tag: newTag,
383
- changelogJsonPath,
389
+ entries,
384
390
  sectionOrder: previewOptions.sectionOrder,
385
- dryRun,
386
391
  });
392
+ warnings.push(...previews.warnings);
393
+ return previews.writes;
387
394
  }
388
- function runFormatCommand(config, tags, modifiedFiles, dryRun) {
395
+ function planFormatCommand(config, tags, modifiedFiles) {
389
396
  const formatCommandStr = config.formatCommand ?? (hasPrettierConfig() ? 'npx prettier --write' : undefined);
390
397
  if (tags.length === 0 || formatCommandStr === undefined) {
391
398
  return undefined;
392
399
  }
393
- const fullCommand = `${formatCommandStr} ${modifiedFiles.join(' ')}`;
394
- if (dryRun) {
395
- return { command: fullCommand, executed: false, files: modifiedFiles };
396
- }
397
- try {
398
- execSync(fullCommand, { stdio: 'inherit' });
399
- }
400
- catch (error) {
401
- const baseMessage = error instanceof Error ? error.message : String(error);
402
- throw new Error(`format stage: ${baseMessage} (command: '${fullCommand}')`, { cause: error });
403
- }
404
- return { command: fullCommand, executed: true, files: modifiedFiles };
400
+ return { command: `${formatCommandStr} ${modifiedFiles.join(' ')}`, files: modifiedFiles };
405
401
  }
406
402
  function findWorkspace(workspaces, dir) {
407
403
  return workspaces.find((w) => w.dir === dir);
@@ -476,7 +472,8 @@ function topologicalSort(releaseSet, graph) {
476
472
  break;
477
473
  }
478
474
  sorted.push(dir);
479
- for (const dependent of forwardEdges.get(dir) ?? []) {
475
+ const dependents = forwardEdges.get(dir) ?? [];
476
+ for (const dependent of dependents) {
480
477
  const newDegree = (inDegree.get(dependent) ?? 1) - 1;
481
478
  inDegree.set(dependent, newDegree);
482
479
  if (newDegree === 0) {
@@ -1,10 +1,13 @@
1
+ import type { PlannedWrite } from './releasePlan.js';
1
2
  import type { ReleasePrepareOptions } from './releasePrepare.js';
2
3
  import type { ChangelogOverride, MonorepoReleaseConfig, ProjectPrepareResult } from './types.js';
3
4
  export interface ReleasePrepareProjectArgs {
4
5
  config: MonorepoReleaseConfig;
5
6
  options: ReleasePrepareOptions;
6
7
  modifiedFiles: string[];
8
+ writes: PlannedWrite[];
7
9
  tags: string[];
10
+ warnings?: string[];
8
11
  rootOverrides?: Map<string, ChangelogOverride>;
9
12
  overrideWarnings?: string[];
10
13
  globalMatchedRootKeys?: Set<string>;
@@ -1,22 +1,23 @@
1
+ import { join as joinPath } from 'node:path';
1
2
  import { buildChangelogEntries } from "./buildChangelogEntries.js";
2
3
  import { buildEmptyReleaseEntry } from "./buildEmptyReleaseEntry.js";
3
- import { bumpAllVersions } from "./bumpAllVersions.js";
4
- import { mergeChangelogEntriesWithDisk, resolveChangelogJsonPath, writeChangelogJson } from "./changelogJsonFile.js";
4
+ import { mergeChangelogEntriesWithDisk, renderChangelogJson, resolveChangelogJsonPath } from "./changelogJsonFile.js";
5
5
  import { applyChangelogOverrides } from "./changelogOverrides.js";
6
6
  import { createPolicyViolationCollector } from "./collectPolicyViolations.js";
7
7
  import { decideRelease } from "./decideRelease.js";
8
8
  import { DEFAULT_BREAKING_POLICIES, DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
9
9
  import { buildTagPattern } from "./generateChangelogs.js";
10
10
  import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
11
- import { writeChangelogMarkdown } from "./renderChangelogMarkdown.js";
11
+ import { planReleaseNotesPreviews } from "./planReleaseNotesPreviews.js";
12
+ import { planVersionBump } from "./planVersionBump.js";
13
+ import { renderChangelogMarkdown } from "./renderChangelogMarkdown.js";
12
14
  import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
13
- import { writeReleaseNotesPreviews } from "./writeReleaseNotesPreviews.js";
14
15
  const ROOT_PACKAGE_FILE = './package.json';
15
16
  const ROOT_CHANGELOG_PATH = '.';
16
17
  export function releasePrepareProject(args) {
17
- const { config, options, modifiedFiles, tags } = args;
18
- const { rootOverrides, overrideWarnings, globalMatchedRootKeys } = resolveOptionalOverrideArgs(args);
19
- const { dryRun, bumpOverride, withReleaseNotes, force } = options;
18
+ const { config, options, modifiedFiles, writes, tags } = args;
19
+ const { rootOverrides, overrideWarnings, globalMatchedRootKeys, warnings } = resolveOptionalOverrideArgs(args);
20
+ const { bumpOverride, withReleaseNotes, force } = options;
20
21
  const project = config.project;
21
22
  if (project === undefined) {
22
23
  throw new Error('releasePrepareProject called without a configured project block');
@@ -62,30 +63,33 @@ export function releasePrepareProject(args) {
62
63
  return skipped;
63
64
  }
64
65
  const { releaseType, parsedCommitCount, unparseableCommits } = decision;
65
- const bump = bumpAllVersions([ROOT_PACKAGE_FILE], releaseType, dryRun);
66
+ const bump = planVersionBump([ROOT_PACKAGE_FILE], releaseType);
67
+ writes.push(...bump.writes);
66
68
  const newTag = `${project.tagPrefix}${bump.newVersion}`;
67
- const { changelogFiles, changelogJsonFiles } = writeProjectChangelogs({
69
+ const changelogs = planProjectChangelogs({
68
70
  config,
69
71
  project,
70
72
  commits,
71
73
  contributingPaths,
72
74
  newTag,
73
75
  newVersion: bump.newVersion,
74
- dryRun,
75
76
  rootOverrides,
76
77
  overrideWarnings,
77
78
  globalMatchedRootKeys,
78
79
  });
79
- const firstChangelogJsonPath = changelogJsonFiles[0];
80
- if (withReleaseNotes === true && config.changelogJson.enabled && firstChangelogJsonPath !== undefined) {
81
- const sectionOrder = deriveSectionOrder(workTypes);
82
- writeReleaseNotesPreviews({
80
+ const { changelogFiles, changelogJsonFiles } = changelogs;
81
+ writes.push(...changelogs.writes);
82
+ const previewFiles = [];
83
+ if (withReleaseNotes === true && config.changelogJson.enabled && changelogJsonFiles.length > 0) {
84
+ const previews = planReleaseNotesPreviews({
83
85
  workspacePath: ROOT_CHANGELOG_PATH,
84
86
  tag: newTag,
85
- changelogJsonPath: firstChangelogJsonPath,
86
- sectionOrder,
87
- dryRun,
87
+ entries: changelogs.entries,
88
+ sectionOrder: deriveSectionOrder(workTypes),
88
89
  });
90
+ writes.push(...previews.writes);
91
+ warnings.push(...previews.warnings);
92
+ previewFiles.push(...previews.writes.map((write) => write.path));
89
93
  }
90
94
  tags.push(newTag);
91
95
  modifiedFiles.push(ROOT_PACKAGE_FILE, ...changelogFiles, ...changelogJsonFiles);
@@ -97,10 +101,13 @@ export function releasePrepareProject(args) {
97
101
  currentVersion: bump.currentVersion,
98
102
  newVersion: bump.newVersion,
99
103
  tag: newTag,
100
- bumpedFiles: bump.files,
104
+ bumpedFiles: bump.writes.map((write) => write.path),
101
105
  changelogFiles,
102
106
  commits,
103
107
  };
108
+ if (previewFiles.length > 0) {
109
+ result.previewFiles = previewFiles;
110
+ }
104
111
  if (tag !== undefined) {
105
112
  result.previousTag = tag;
106
113
  }
@@ -120,10 +127,11 @@ function resolveOptionalOverrideArgs(args) {
120
127
  rootOverrides: args.rootOverrides ?? new Map(),
121
128
  overrideWarnings: args.overrideWarnings ?? [],
122
129
  globalMatchedRootKeys: args.globalMatchedRootKeys ?? new Set(),
130
+ warnings: args.warnings ?? [],
123
131
  };
124
132
  }
125
- function writeProjectChangelogs(args) {
126
- const { config, project, commits, contributingPaths, newTag, newVersion, dryRun, rootOverrides, overrideWarnings, globalMatchedRootKeys, } = args;
133
+ function planProjectChangelogs(args) {
134
+ const { config, project, commits, contributingPaths, newTag, newVersion, rootOverrides, overrideWarnings, globalMatchedRootKeys, } = args;
127
135
  const isEmptyRange = commits.length === 0;
128
136
  const today = new Date().toISOString().slice(0, 10);
129
137
  const tagPattern = buildTagPattern([project.tagPrefix]);
@@ -143,25 +151,13 @@ function writeProjectChangelogs(args) {
143
151
  const renderEntries = isEmptyRange
144
152
  ? mergeChangelogEntriesWithDisk(changelogJsonPath, applied.entries)
145
153
  : applied.entries;
146
- if (config.changelogJson.enabled && !dryRun) {
147
- if (isEmptyRange) {
148
- writeChangelogJson(changelogJsonPath, renderEntries);
149
- }
150
- else {
151
- writeChangelogJson(changelogJsonPath, applied.entries);
152
- }
153
- }
154
- const changelogFiles = [
155
- writeChangelogMarkdown({
156
- changelogPath: ROOT_CHANGELOG_PATH,
157
- entries: renderEntries,
158
- sectionOrder,
159
- dryRun,
160
- }),
161
- ];
154
+ const writes = [];
162
155
  const changelogJsonFiles = [];
163
156
  if (config.changelogJson.enabled) {
157
+ writes.push({ path: changelogJsonPath, content: renderChangelogJson(renderEntries) });
164
158
  changelogJsonFiles.push(changelogJsonPath);
165
159
  }
166
- return { changelogFiles, changelogJsonFiles };
160
+ const changelogFile = joinPath(ROOT_CHANGELOG_PATH, 'CHANGELOG.md');
161
+ writes.push({ path: changelogFile, content: renderChangelogMarkdown(renderEntries, { sectionOrder }) });
162
+ return { changelogFiles: [changelogFile], changelogJsonFiles, entries: renderEntries, writes };
167
163
  }
@@ -3,10 +3,3 @@ export interface RenderChangelogMarkdownOptions {
3
3
  sectionOrder?: string[];
4
4
  }
5
5
  export declare function renderChangelogMarkdown(entries: ChangelogEntry[], options?: RenderChangelogMarkdownOptions): string;
6
- export interface WriteChangelogMarkdownParams {
7
- changelogPath: string;
8
- entries: ChangelogEntry[];
9
- sectionOrder?: string[];
10
- dryRun?: boolean;
11
- }
12
- export declare function writeChangelogMarkdown(params: WriteChangelogMarkdownParams): string;
@@ -1,5 +1,3 @@
1
- import { mkdirSync, writeFileSync } from 'node:fs';
2
- import { dirname, join as joinPath } from 'node:path';
3
1
  import { renderReleaseNotesMulti } from "./renderReleaseNotes.js";
4
2
  const CHANGELOG_HEADER = `# Changelog
5
3
 
@@ -15,14 +13,3 @@ export function renderChangelogMarkdown(entries, options) {
15
13
  parts.push(CHANGELOG_FOOTER);
16
14
  return parts.join('\n\n') + '\n';
17
15
  }
18
- export function writeChangelogMarkdown(params) {
19
- const { changelogPath, entries, sectionOrder, dryRun = false } = params;
20
- const filePath = joinPath(changelogPath, 'CHANGELOG.md');
21
- if (dryRun) {
22
- return filePath;
23
- }
24
- const rendered = renderChangelogMarkdown(entries, sectionOrder !== undefined ? { sectionOrder } : undefined);
25
- mkdirSync(dirname(filePath), { recursive: true });
26
- writeFileSync(filePath, rendered, 'utf8');
27
- return filePath;
28
- }
@@ -1,2 +1,6 @@
1
1
  import type { PrepareResult } from './types.js';
2
- export declare function reportPrepare(result: PrepareResult): string;
2
+ export interface ReportPrepareOptions {
3
+ applied: boolean;
4
+ formatError?: string;
5
+ }
6
+ export declare function reportPrepare(plan: PrepareResult, options: ReportPrepareOptions): string;
@@ -1,12 +1,13 @@
1
1
  import { bold, dim, sectionHeader } from "./format.js";
2
- export function reportPrepare(result) {
3
- const isMultiWorkspace = result.workspaces.some((w) => w.name !== undefined) || result.project !== undefined;
2
+ export function reportPrepare(plan, options) {
3
+ const isMultiWorkspace = plan.workspaces.some((w) => w.name !== undefined) || plan.project !== undefined;
4
4
  if (isMultiWorkspace) {
5
- return formatMultiWorkspace(result);
5
+ return formatMultiWorkspace(plan, options);
6
6
  }
7
- return formatSingleWorkspace(result);
7
+ return formatSingleWorkspace(plan, options);
8
8
  }
9
- function formatSingleWorkspace(result) {
9
+ function formatSingleWorkspace(result, options) {
10
+ const dryRun = !options.applied;
10
11
  const lines = [];
11
12
  const workspace = result.workspaces[0];
12
13
  if (workspace === undefined) {
@@ -41,22 +42,25 @@ function formatSingleWorkspace(result) {
41
42
  else if (workspace.releaseType !== undefined) {
42
43
  lines.push(`šŸ“¦ ${workspace.currentVersion} → ${bold(workspace.newVersion)} (${workspace.releaseType})`);
43
44
  }
44
- formatBumpFiles(lines, workspace, result.dryRun);
45
+ formatBumpFiles(lines, workspace, dryRun);
45
46
  lines.push(dim('Generating changelogs...'));
46
- formatChangelogFiles(lines, workspace, result.dryRun);
47
- formatFormatCommand(lines, result);
47
+ formatChangelogFiles(lines, workspace, dryRun);
48
+ formatPreviewFiles(lines, workspace, dryRun);
49
+ formatFormatCommand(lines, result, options);
50
+ formatWarnings(lines, result);
48
51
  lines.push(`āœ… Release preparation complete.`, ` šŸ·ļø ${bold(workspace.tag)}`);
49
52
  return lines.join('\n');
50
53
  }
51
- function formatMultiWorkspace(result) {
54
+ function formatMultiWorkspace(result, options) {
55
+ const dryRun = !options.applied;
52
56
  const lines = [];
53
57
  for (const workspace of result.workspaces) {
54
- formatWorkspaceSection(lines, workspace, result.dryRun);
58
+ formatWorkspaceSection(lines, workspace, dryRun);
55
59
  }
56
60
  if (result.project !== undefined) {
57
- formatProjectSection(lines, result.project, result.dryRun);
61
+ formatProjectSection(lines, result.project, dryRun);
58
62
  }
59
- formatFormatCommand(lines, result);
63
+ formatFormatCommand(lines, result, options);
60
64
  formatWarnings(lines, result);
61
65
  if (result.tags.length > 0) {
62
66
  lines.push(`\nāœ… Release preparation complete.`);
@@ -104,6 +108,7 @@ function formatProjectSection(lines, project, dryRun) {
104
108
  lines.push(dim(` Generating changelog: ${file}`));
105
109
  }
106
110
  }
111
+ formatPreviewFiles(lines, project, dryRun, ' ');
107
112
  lines.push(` šŸ·ļø ${bold(tag)}`);
108
113
  }
109
114
  function formatProjectUnparseable(lines, project) {
@@ -141,6 +146,7 @@ function formatWorkspaceSection(lines, workspace, dryRun) {
141
146
  formatBumpFiles(lines, workspace, dryRun, ' ');
142
147
  lines.push(dim(' Generating changelogs...'));
143
148
  formatChangelogFiles(lines, workspace, dryRun, ' ');
149
+ formatPreviewFiles(lines, workspace, dryRun, ' ');
144
150
  lines.push(` šŸ·ļø ${bold(workspace.tag)}`);
145
151
  }
146
152
  function formatCommitSummary(lines, workspace, propagatedFrom, isPropagatedOnly) {
@@ -195,6 +201,17 @@ function formatChangelogFiles(lines, workspace, dryRun, indent = '') {
195
201
  }
196
202
  }
197
203
  }
204
+ function formatPreviewFiles(lines, release, dryRun, indent = '') {
205
+ const previewFiles = release.previewFiles ?? [];
206
+ for (const file of previewFiles) {
207
+ if (dryRun) {
208
+ lines.push(dim(`${indent} [dry-run] Would write ${file}`));
209
+ }
210
+ else {
211
+ lines.push(dim(`${indent} Wrote ${file}`));
212
+ }
213
+ }
214
+ }
198
215
  function formatUnparseableWarning(lines, workspace, indent = '') {
199
216
  const unparseable = workspace.unparseableCommits;
200
217
  if (unparseable === undefined || unparseable.length === 0) {
@@ -240,14 +257,17 @@ function formatWarnings(lines, result) {
240
257
  lines.push(`āš ļø ${warning}`);
241
258
  }
242
259
  }
243
- function formatFormatCommand(lines, result) {
260
+ function formatFormatCommand(lines, result, options) {
244
261
  if (result.formatCommand === undefined) {
245
262
  return;
246
263
  }
247
- if (result.formatCommand.executed) {
248
- lines.push(dim(`\n Running format command: ${result.formatCommand.command}`));
249
- }
250
- else {
264
+ if (!options.applied) {
251
265
  lines.push(dim(`\n [dry-run] Would run format command: ${result.formatCommand.command}`));
266
+ return;
267
+ }
268
+ if (options.formatError === undefined) {
269
+ lines.push(dim(`\n Running format command: ${result.formatCommand.command}`));
270
+ return;
252
271
  }
272
+ lines.push(`\n āš ļø Format command failed: ${result.formatCommand.command}`, ` ${options.formatError}`);
253
273
  }
@@ -10,7 +10,8 @@ import { checkRetiredSyncLabelsConfig } from "./retiredConfig.js";
10
10
  export const LABELS_OUTPUT_PATH = '.github/labels.yaml';
11
11
  export function formatLabelsYaml(labels, presetHashes) {
12
12
  const headerLines = ['# Generated by release-kit sync-labels — do not edit.', `# Source: ${CONFIG_FILE_PATH}`];
13
- for (const [name, hash] of [...presetHashes].sort(([a], [b]) => a.localeCompare(b))) {
13
+ const sortedPresetHashes = [...presetHashes].sort(([a], [b]) => a.localeCompare(b));
14
+ for (const [name, hash] of sortedPresetHashes) {
14
15
  headerLines.push(`# ${name} preset hash: ${hash}`);
15
16
  }
16
17
  const declared = labels.map((label) => ({ ...label, description: label.description ?? '' }));
@@ -50,7 +51,8 @@ export async function generateCommand({ check = false } = {}) {
50
51
  return 1;
51
52
  }
52
53
  const presetHashes = new Map();
53
- for (const presetName of repoLabels.extends ?? []) {
54
+ const presetNames = repoLabels.extends ?? [];
55
+ for (const presetName of presetNames) {
54
56
  presetHashes.set(presetName, hashPresetFile(presetName));
55
57
  }
56
58
  const content = formatLabelsYaml(labels, presetHashes);
@@ -23,7 +23,7 @@ export function loadPreset(presetName) {
23
23
  }
24
24
  catch (error) {
25
25
  const message = error instanceof Error ? error.message : String(error);
26
- throw new Error(`Failed to read preset "${presetName}": ${message}`);
26
+ throw new Error(`Failed to read preset "${presetName}": ${message}`, { cause: error });
27
27
  }
28
28
  const parsed = parse(content);
29
29
  if (!Array.isArray(parsed)) {
@@ -34,13 +34,13 @@ export function loadPreset(presetName) {
34
34
  if (!isRecord(entry)) {
35
35
  throw new Error(`Preset "${presetName}" contains an invalid label entry: ${JSON.stringify(entry)}`);
36
36
  }
37
- const { description } = entry;
38
- if (typeof entry.name !== 'string' ||
39
- typeof entry.color !== 'string' ||
37
+ const { color, description, name } = entry;
38
+ if (typeof name !== 'string' ||
39
+ typeof color !== 'string' ||
40
40
  (description !== undefined && typeof description !== 'string')) {
41
41
  throw new TypeError(`Preset "${presetName}" contains a label with invalid fields: ${JSON.stringify(entry)}`);
42
42
  }
43
- labels.push({ name: entry.name, color: entry.color, ...(description !== undefined && { description }) });
43
+ labels.push({ name, color, ...(description !== undefined && { description }) });
44
44
  }
45
45
  return labels;
46
46
  }
@@ -1,12 +1,14 @@
1
1
  import { loadPreset } from "./presets.js";
2
2
  export function resolveLabels(config) {
3
3
  const resolved = new Map();
4
- for (const presetName of config.extends ?? []) {
4
+ const presetNames = config.extends ?? [];
5
+ for (const presetName of presetNames) {
5
6
  for (const label of loadPreset(presetName)) {
6
7
  resolved.set(label.name, label);
7
8
  }
8
9
  }
9
- for (const [name, spec] of Object.entries(config.labels ?? {})) {
10
+ const labelEntries = Object.entries(config.labels ?? {});
11
+ for (const [name, spec] of labelEntries) {
10
12
  if (spec === null) {
11
13
  if (!resolved.has(name)) {
12
14
  throw new Error(`Label '${name}' is set to null (remove), but no preset in 'extends' defines it. Fix the name or delete the entry.`);
@@ -21,7 +23,7 @@ export function resolveLabels(config) {
21
23
  });
22
24
  }
23
25
  }
24
- return sortLabels([...resolved.values()]);
26
+ return sortLabels(resolved.values().toArray());
25
27
  }
26
28
  function sortLabels(labels) {
27
29
  return [...labels].sort((a, b) => a.name.localeCompare(b.name));
@@ -20,7 +20,7 @@ function extractLocalSchemaUrl(content) {
20
20
  if (!isRecord(parsed)) {
21
21
  return undefined;
22
22
  }
23
- const schema = parsed.$schema;
23
+ const schema = parsed['$schema'];
24
24
  return typeof schema === 'string' ? schema : undefined;
25
25
  }
26
26
  export async function syncWorkTypes(dependencies = {}) {