@williamthorsen/release-kit 4.8.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/CHANGELOG.md +134 -4
  2. package/README.md +404 -40
  3. package/cliff.toml.template +2 -1
  4. package/dist/esm/.cache +1 -1
  5. package/dist/esm/assertCleanWorkingTree.js +1 -1
  6. package/dist/esm/bin/release-kit.js +45 -14
  7. package/dist/esm/buildChangelogEntries.d.ts +3 -0
  8. package/dist/esm/{generateChangelogJson.js → buildChangelogEntries.js} +40 -77
  9. package/dist/esm/buildDependencyGraph.d.ts +4 -3
  10. package/dist/esm/buildDependencyGraph.js +18 -11
  11. package/dist/esm/buildReleaseSummary.js +12 -4
  12. package/dist/esm/buildSyntheticChangelogEntry.d.ts +5 -0
  13. package/dist/esm/buildSyntheticChangelogEntry.js +13 -0
  14. package/dist/esm/bumpAllVersions.d.ts +1 -0
  15. package/dist/esm/bumpAllVersions.js +16 -2
  16. package/dist/esm/bumpVersion.js +3 -0
  17. package/dist/esm/changelogJsonFile.d.ts +4 -0
  18. package/dist/esm/changelogJsonFile.js +68 -0
  19. package/dist/esm/commitCommand.js +1 -1
  20. package/dist/esm/compareVersions.d.ts +1 -0
  21. package/dist/esm/compareVersions.js +27 -0
  22. package/dist/esm/createGithubRelease.d.ts +6 -2
  23. package/dist/esm/createGithubRelease.js +17 -17
  24. package/dist/esm/createGithubReleaseCommand.d.ts +1 -0
  25. package/dist/esm/createGithubReleaseCommand.js +41 -0
  26. package/dist/esm/decideRelease.d.ts +25 -0
  27. package/dist/esm/decideRelease.js +28 -0
  28. package/dist/esm/defaults.d.ts +1 -0
  29. package/dist/esm/defaults.js +7 -3
  30. package/dist/esm/deriveWorkspaceConfig.d.ts +2 -0
  31. package/dist/esm/deriveWorkspaceConfig.js +37 -0
  32. package/dist/esm/detectUndeclaredTagPrefixes.d.ts +7 -0
  33. package/dist/esm/detectUndeclaredTagPrefixes.js +46 -0
  34. package/dist/esm/generateChangelogs.d.ts +1 -1
  35. package/dist/esm/generateChangelogs.js +14 -3
  36. package/dist/esm/getCommitsSinceTarget.d.ts +1 -1
  37. package/dist/esm/getCommitsSinceTarget.js +8 -4
  38. package/dist/esm/index.d.ts +2 -39
  39. package/dist/esm/index.js +0 -75
  40. package/dist/esm/init/initCommand.js +1 -1
  41. package/dist/esm/init/scaffold.d.ts +1 -1
  42. package/dist/esm/init/scaffold.js +8 -5
  43. package/dist/esm/init/templates.d.ts +1 -0
  44. package/dist/esm/init/templates.js +35 -5
  45. package/dist/esm/injectReleaseNotesIntoReadme.d.ts +6 -1
  46. package/dist/esm/injectReleaseNotesIntoReadme.js +20 -7
  47. package/dist/esm/loadConfig.d.ts +12 -2
  48. package/dist/esm/loadConfig.js +161 -14
  49. package/dist/esm/parseRequestedTags.d.ts +1 -0
  50. package/dist/esm/parseRequestedTags.js +10 -0
  51. package/dist/esm/prepareCommand.d.ts +3 -1
  52. package/dist/esm/prepareCommand.js +121 -31
  53. package/dist/esm/previewTagPrefixes.d.ts +30 -0
  54. package/dist/esm/previewTagPrefixes.js +120 -0
  55. package/dist/esm/propagateBumps.d.ts +1 -0
  56. package/dist/esm/propagateBumps.js +1 -1
  57. package/dist/esm/publish.d.ts +0 -1
  58. package/dist/esm/publish.js +3 -3
  59. package/dist/esm/publishCommand.js +18 -14
  60. package/dist/esm/pushCommand.js +5 -4
  61. package/dist/esm/readCurrentVersion.d.ts +1 -0
  62. package/dist/esm/readCurrentVersion.js +21 -0
  63. package/dist/esm/releasePrepare.d.ts +2 -0
  64. package/dist/esm/releasePrepare.js +140 -54
  65. package/dist/esm/releasePrepareMono.js +312 -143
  66. package/dist/esm/releasePrepareProject.d.ts +9 -0
  67. package/dist/esm/releasePrepareProject.js +109 -0
  68. package/dist/esm/renderReleaseNotes.d.ts +1 -0
  69. package/dist/esm/renderReleaseNotes.js +29 -2
  70. package/dist/esm/reportPrepare.js +146 -73
  71. package/dist/esm/resolveCliffConfigPath.js +1 -1
  72. package/dist/esm/resolveCommandTags.d.ts +1 -1
  73. package/dist/esm/resolveCommandTags.js +17 -13
  74. package/dist/esm/resolveReleaseNotesConfig.d.ts +8 -1
  75. package/dist/esm/resolveReleaseNotesConfig.js +17 -7
  76. package/dist/esm/resolveReleaseTags.d.ts +2 -1
  77. package/dist/esm/resolveReleaseTags.js +19 -14
  78. package/dist/esm/showTagPrefixesCommand.d.ts +1 -0
  79. package/dist/esm/showTagPrefixesCommand.js +84 -0
  80. package/dist/esm/sync-labels/initCommand.js +1 -1
  81. package/dist/esm/sync-labels/presets.js +1 -1
  82. package/dist/esm/tagCommand.js +1 -1
  83. package/dist/esm/types.d.ts +77 -19
  84. package/dist/esm/validateConfig.js +205 -36
  85. package/dist/esm/validateOnlyExcludesStrandedDependents.d.ts +14 -0
  86. package/dist/esm/validateOnlyExcludesStrandedDependents.js +109 -0
  87. package/dist/esm/version.d.ts +1 -1
  88. package/dist/esm/version.js +1 -1
  89. package/dist/esm/writeReleaseNotesPreviews.d.ts +18 -0
  90. package/dist/esm/writeReleaseNotesPreviews.js +65 -0
  91. package/package.json +5 -2
  92. package/presets/labels/common.yaml +9 -6
  93. package/schemas/label-map.json +24 -0
  94. package/dist/esm/component.d.ts +0 -2
  95. package/dist/esm/component.js +0 -14
  96. package/dist/esm/findPackageRoot.d.ts +0 -1
  97. package/dist/esm/findPackageRoot.js +0 -17
  98. package/dist/esm/generateChangelogJson.d.ts +0 -7
  99. package/dist/esm/githubReleaseCommand.d.ts +0 -1
  100. package/dist/esm/githubReleaseCommand.js +0 -35
@@ -1,8 +1,8 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  function publishPackage(resolvedTag, packageManager, options) {
3
- const { dryRun, noGitChecks, provenance } = options;
3
+ const { dryRun, provenance } = options;
4
4
  const executable = resolveExecutable(packageManager);
5
- const args = buildPublishArgs(packageManager, { dryRun, noGitChecks, provenance });
5
+ const args = buildPublishArgs(packageManager, { dryRun, provenance });
6
6
  console.info(
7
7
  `
8
8
  ${dryRun ? "[dry-run] " : ""}Running: ${executable} ${args.join(" ")} (cwd: ${resolvedTag.workspacePath})`
@@ -20,7 +20,7 @@ function buildPublishArgs(packageManager, options) {
20
20
  if (options.dryRun) {
21
21
  args.push("--dry-run");
22
22
  }
23
- if (options.noGitChecks && packageManager === "pnpm") {
23
+ if (packageManager === "pnpm") {
24
24
  args.push("--no-git-checks");
25
25
  }
26
26
  if (options.provenance && packageManager !== "yarn") {
@@ -1,9 +1,10 @@
1
1
  import { writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { parseArgs, translateParseError } from "@williamthorsen/node-monorepo-core";
4
- import { createGithubReleases } from "./createGithubRelease.js";
3
+ import { parseArgs, translateParseError } from "@williamthorsen/nmr-core";
4
+ import { assertCleanWorkingTree } from "./assertCleanWorkingTree.js";
5
5
  import { detectPackageManager } from "./detectPackageManager.js";
6
6
  import { injectReleaseNotesIntoReadme, resolveReadmePath } from "./injectReleaseNotesIntoReadme.js";
7
+ import { parseRequestedTags } from "./parseRequestedTags.js";
7
8
  import { publishPackage } from "./publish.js";
8
9
  import { resolveCommandTags } from "./resolveCommandTags.js";
9
10
  import { resolveReleaseNotesConfig } from "./resolveReleaseNotesConfig.js";
@@ -11,7 +12,7 @@ const publishFlagSchema = {
11
12
  dryRun: { long: "--dry-run", type: "boolean" },
12
13
  noGitChecks: { long: "--no-git-checks", type: "boolean" },
13
14
  provenance: { long: "--provenance", type: "boolean" },
14
- only: { long: "--only", type: "string" }
15
+ tags: { long: "--tags", type: "string" }
15
16
  };
16
17
  async function publishCommand(argv) {
17
18
  let parsed;
@@ -22,13 +23,21 @@ async function publishCommand(argv) {
22
23
  process.exit(1);
23
24
  }
24
25
  const { dryRun, noGitChecks, provenance } = parsed.flags;
25
- const only = parsed.flags.only?.split(",");
26
- const resolvedTags = await resolveCommandTags(only);
26
+ if (!dryRun && !noGitChecks) {
27
+ try {
28
+ assertCleanWorkingTree();
29
+ } catch (error) {
30
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
31
+ process.exit(1);
32
+ }
33
+ }
34
+ const requestedTags = parseRequestedTags(parsed.flags.tags);
35
+ const resolvedTags = await resolveCommandTags(requestedTags);
27
36
  if (resolvedTags.length === 0) {
28
37
  return;
29
38
  }
30
39
  const packageManager = detectPackageManager();
31
- const { releaseNotes, changelogJsonOutputPath } = await resolveReleaseNotesConfig();
40
+ const { releaseNotes, changelogJsonOutputPath, sectionOrder } = await resolveReleaseNotesConfig();
32
41
  const shouldInject = releaseNotes.shouldInjectIntoReadme;
33
42
  console.info(dryRun ? "[dry-run] Would publish:" : "Publishing:");
34
43
  for (const { tag, workspacePath } of resolvedTags) {
@@ -45,12 +54,13 @@ async function publishCommand(argv) {
45
54
  originalReadme = injectReleaseNotesIntoReadme(
46
55
  readmePath,
47
56
  join(resolvedTag.workspacePath, changelogJsonOutputPath),
48
- resolvedTag.tag
57
+ resolvedTag.tag,
58
+ sectionOrder
49
59
  );
50
60
  }
51
61
  }
52
62
  try {
53
- publishPackage(resolvedTag, packageManager, { dryRun, noGitChecks, provenance });
63
+ publishPackage(resolvedTag, packageManager, { dryRun, provenance });
54
64
  published.push(resolvedTag.tag);
55
65
  } finally {
56
66
  if (readmePath !== void 0 && originalReadme !== void 0) {
@@ -68,12 +78,6 @@ async function publishCommand(argv) {
68
78
  console.error(error instanceof Error ? error.message : String(error));
69
79
  process.exit(1);
70
80
  }
71
- try {
72
- createGithubReleases(resolvedTags, releaseNotes, changelogJsonOutputPath, dryRun);
73
- } catch (error) {
74
- console.error(`Error creating GitHub Releases: ${error instanceof Error ? error.message : String(error)}`);
75
- process.exit(1);
76
- }
77
81
  }
78
82
  export {
79
83
  publishCommand
@@ -1,9 +1,10 @@
1
- import { parseArgs, translateParseError } from "@williamthorsen/node-monorepo-core";
1
+ import { parseArgs, translateParseError } from "@williamthorsen/nmr-core";
2
+ import { parseRequestedTags } from "./parseRequestedTags.js";
2
3
  import { pushRelease } from "./pushRelease.js";
3
4
  import { resolveCommandTags } from "./resolveCommandTags.js";
4
5
  const pushFlagSchema = {
5
6
  dryRun: { long: "--dry-run", type: "boolean" },
6
- only: { long: "--only", type: "string" },
7
+ tags: { long: "--tags", type: "string" },
7
8
  tagsOnly: { long: "--tags-only", type: "boolean" }
8
9
  };
9
10
  async function pushCommand(argv) {
@@ -15,8 +16,8 @@ async function pushCommand(argv) {
15
16
  process.exit(1);
16
17
  }
17
18
  const { dryRun, tagsOnly } = parsed.flags;
18
- const only = parsed.flags.only?.split(",");
19
- const resolvedTags = await resolveCommandTags(only);
19
+ const requestedTags = parseRequestedTags(parsed.flags.tags);
20
+ const resolvedTags = await resolveCommandTags(requestedTags);
20
21
  if (resolvedTags.length === 0) {
21
22
  return;
22
23
  }
@@ -0,0 +1 @@
1
+ export declare function readCurrentVersion(filePath: string): string | undefined;
@@ -0,0 +1,21 @@
1
+ import { readFileSync } from "node:fs";
2
+ function hasVersionField(value) {
3
+ return typeof value === "object" && value !== null && "version" in value && typeof value.version === "string";
4
+ }
5
+ function readCurrentVersion(filePath) {
6
+ try {
7
+ const content = readFileSync(filePath, "utf8");
8
+ const parsed = JSON.parse(content);
9
+ if (hasVersionField(parsed)) {
10
+ return parsed.version;
11
+ }
12
+ } catch (error) {
13
+ console.warn(
14
+ `Failed to read current version from ${filePath}: ${error instanceof Error ? error.message : String(error)}`
15
+ );
16
+ }
17
+ return void 0;
18
+ }
19
+ export {
20
+ readCurrentVersion
21
+ };
@@ -3,5 +3,7 @@ export interface ReleasePrepareOptions {
3
3
  dryRun: boolean;
4
4
  force?: boolean;
5
5
  bumpOverride?: ReleaseType;
6
+ setVersion?: string;
7
+ withReleaseNotes?: boolean;
6
8
  }
7
9
  export declare function releasePrepare(config: ReleaseConfig, options: ReleasePrepareOptions): PrepareResult;
@@ -1,54 +1,75 @@
1
1
  import { execSync } from "node:child_process";
2
- import { bumpAllVersions } from "./bumpAllVersions.js";
2
+ import { buildChangelogEntries } from "./buildChangelogEntries.js";
3
+ import { bumpAllVersions, setAllVersions } from "./bumpAllVersions.js";
4
+ import { resolveChangelogJsonPath, upsertChangelogJson } from "./changelogJsonFile.js";
5
+ import { isForwardVersion } from "./compareVersions.js";
3
6
  import { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
4
7
  import { determineBumpFromCommits } from "./determineBumpFromCommits.js";
5
- import { generateChangelogJson } from "./generateChangelogJson.js";
6
8
  import { generateChangelogs } from "./generateChangelogs.js";
7
9
  import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
8
10
  import { hasPrettierConfig } from "./hasPrettierConfig.js";
11
+ import { resolveWorkTypes } from "./loadConfig.js";
12
+ import { readCurrentVersion } from "./readCurrentVersion.js";
13
+ import { deriveSectionOrder } from "./resolveReleaseNotesConfig.js";
14
+ import { writeReleaseNotesPreviews } from "./writeReleaseNotesPreviews.js";
9
15
  function releasePrepare(config, options) {
10
- const { dryRun, bumpOverride } = options;
16
+ const { dryRun, bumpOverride, setVersion, withReleaseNotes } = options;
11
17
  const workTypes = config.workTypes ?? { ...DEFAULT_WORK_TYPES };
12
18
  const versionPatterns = config.versionPatterns ?? { ...DEFAULT_VERSION_PATTERNS };
13
- const { tag, commits } = getCommitsSinceTarget(config.tagPrefix);
19
+ const { tag, commits } = getCommitsSinceTarget([config.tagPrefix]);
14
20
  let releaseType;
15
21
  let parsedCommitCount;
16
22
  let unparseableCommits;
17
- if (bumpOverride === void 0) {
18
- const determination = determineBumpFromCommits(commits, workTypes, versionPatterns, config.scopeAliases);
19
- parsedCommitCount = determination.parsedCommitCount;
20
- unparseableCommits = determination.unparseableCommits;
21
- releaseType = determination.releaseType;
23
+ let bump;
24
+ if (setVersion !== void 0) {
25
+ const primaryPackageFile = config.packageFiles[0];
26
+ if (primaryPackageFile === void 0) {
27
+ throw new Error("No package files specified");
28
+ }
29
+ const currentVersion = readCurrentVersion(primaryPackageFile);
30
+ if (currentVersion === void 0) {
31
+ throw new Error(`Cannot validate --set-version: failed to read current version from ${primaryPackageFile}`);
32
+ }
33
+ if (!isForwardVersion(currentVersion, setVersion)) {
34
+ throw new Error(`--set-version ${setVersion} is not greater than current version ${currentVersion}`);
35
+ }
36
+ bump = setAllVersions(config.packageFiles, setVersion, dryRun);
22
37
  } else {
23
- releaseType = bumpOverride;
24
- }
25
- if (releaseType === void 0) {
26
- return {
27
- components: [
28
- {
29
- status: "skipped",
30
- previousTag: tag,
31
- commitCount: commits.length,
32
- parsedCommitCount,
33
- unparseableCommits,
34
- bumpedFiles: [],
35
- changelogFiles: [],
36
- skipReason: "No release-worthy changes found. Skipping."
37
- }
38
- ],
39
- tags: [],
40
- dryRun
41
- };
38
+ if (bumpOverride === void 0) {
39
+ const determination = determineBumpFromCommits(commits, workTypes, versionPatterns, config.scopeAliases);
40
+ parsedCommitCount = determination.parsedCommitCount;
41
+ unparseableCommits = determination.unparseableCommits;
42
+ releaseType = determination.releaseType;
43
+ } else {
44
+ releaseType = bumpOverride;
45
+ }
46
+ if (releaseType === void 0) {
47
+ const skipped = {
48
+ status: "skipped",
49
+ commitCount: commits.length,
50
+ skipReason: "No release-worthy changes found. Skipping."
51
+ };
52
+ if (tag !== void 0) {
53
+ skipped.previousTag = tag;
54
+ }
55
+ if (parsedCommitCount !== void 0) {
56
+ skipped.parsedCommitCount = parsedCommitCount;
57
+ }
58
+ if (unparseableCommits !== void 0) {
59
+ skipped.unparseableCommits = unparseableCommits;
60
+ }
61
+ return {
62
+ workspaces: [skipped],
63
+ tags: [],
64
+ dryRun
65
+ };
66
+ }
67
+ bump = bumpAllVersions(config.packageFiles, releaseType, dryRun);
42
68
  }
43
- const bump = bumpAllVersions(config.packageFiles, releaseType, dryRun);
44
69
  const newTag = `${config.tagPrefix}${bump.newVersion}`;
45
70
  const changelogFiles = generateChangelogs(config, newTag, dryRun);
46
- const changelogJsonFiles = [];
47
- if (config.changelogJson.enabled) {
48
- for (const changelogPath of config.changelogPaths) {
49
- changelogJsonFiles.push(...generateChangelogJson(config, changelogPath, newTag, dryRun));
50
- }
51
- }
71
+ const changelogJsonFiles = config.changelogJson.enabled ? buildAndPersistChangelogJson(config, newTag, dryRun) : [];
72
+ maybeWriteSinglePackagePreviews(withReleaseNotes === true, config, newTag, changelogJsonFiles[0], dryRun);
52
73
  const formatCommandStr = config.formatCommand ?? (hasPrettierConfig() ? "npx prettier --write" : void 0);
53
74
  let formatCommand;
54
75
  if (formatCommandStr !== void 0) {
@@ -64,35 +85,100 @@ function releasePrepare(config, options) {
64
85
  try {
65
86
  execSync(fullCommand, { stdio: "inherit" });
66
87
  } catch (error) {
67
- throw new Error(
68
- `Format command failed ('${fullCommand}'): ${error instanceof Error ? error.message : String(error)}`
69
- );
88
+ const baseMessage = error instanceof Error ? error.message : String(error);
89
+ throw new Error(`format stage: ${baseMessage} (command: '${fullCommand}')`, { cause: error });
70
90
  }
71
91
  formatCommand = { command: fullCommand, executed: true, files: modifiedFiles };
72
92
  }
73
93
  }
94
+ const released = buildReleasedSinglePackage({
95
+ commits,
96
+ bump,
97
+ newTag,
98
+ changelogFiles,
99
+ previousTag: tag,
100
+ parsedCommitCount,
101
+ releaseType,
102
+ unparseableCommits,
103
+ setVersion
104
+ });
74
105
  return {
75
- components: [
76
- {
77
- status: "released",
78
- previousTag: tag,
79
- commitCount: commits.length,
80
- parsedCommitCount,
81
- releaseType,
82
- currentVersion: bump.currentVersion,
83
- newVersion: bump.newVersion,
84
- tag: newTag,
85
- bumpedFiles: bump.files,
86
- changelogFiles,
87
- commits,
88
- unparseableCommits
89
- }
90
- ],
106
+ workspaces: [released],
91
107
  tags: [newTag],
92
108
  formatCommand,
93
109
  dryRun
94
110
  };
95
111
  }
112
+ function buildReleasedSinglePackage(args) {
113
+ const {
114
+ commits,
115
+ bump,
116
+ newTag,
117
+ changelogFiles,
118
+ previousTag,
119
+ parsedCommitCount,
120
+ releaseType,
121
+ unparseableCommits,
122
+ setVersion
123
+ } = args;
124
+ const released = {
125
+ status: "released",
126
+ commitCount: commits.length,
127
+ currentVersion: bump.currentVersion,
128
+ newVersion: bump.newVersion,
129
+ tag: newTag,
130
+ bumpedFiles: bump.files,
131
+ changelogFiles,
132
+ commits
133
+ };
134
+ if (previousTag !== void 0) {
135
+ released.previousTag = previousTag;
136
+ }
137
+ if (parsedCommitCount !== void 0) {
138
+ released.parsedCommitCount = parsedCommitCount;
139
+ }
140
+ if (releaseType !== void 0) {
141
+ released.releaseType = releaseType;
142
+ }
143
+ if (unparseableCommits !== void 0) {
144
+ released.unparseableCommits = unparseableCommits;
145
+ }
146
+ if (setVersion !== void 0) {
147
+ released.setVersion = setVersion;
148
+ }
149
+ return released;
150
+ }
151
+ function buildAndPersistChangelogJson(config, newTag, dryRun) {
152
+ const changelogJsonFiles = [];
153
+ const entries = buildChangelogEntries(config, newTag);
154
+ for (const changelogPath of config.changelogPaths) {
155
+ const jsonPath = resolveChangelogJsonPath(config, changelogPath);
156
+ if (!dryRun) {
157
+ upsertChangelogJson(jsonPath, entries);
158
+ }
159
+ changelogJsonFiles.push(jsonPath);
160
+ }
161
+ return changelogJsonFiles;
162
+ }
163
+ function maybeWriteSinglePackagePreviews(withReleaseNotes, config, newTag, changelogJsonPath, dryRun) {
164
+ if (!withReleaseNotes) {
165
+ return;
166
+ }
167
+ if (!config.changelogJson.enabled) {
168
+ console.warn("Warning: --with-release-notes requires changelogJson.enabled; skipping preview generation");
169
+ return;
170
+ }
171
+ if (changelogJsonPath === void 0) {
172
+ return;
173
+ }
174
+ writeReleaseNotesPreviews({
175
+ workspacePath: process.cwd(),
176
+ tag: newTag,
177
+ changelogJsonPath,
178
+ sectionOrder: deriveSectionOrder(resolveWorkTypes(config.workTypes)),
179
+ dryRun
180
+ });
181
+ }
96
182
  export {
97
183
  releasePrepare
98
184
  };