@williamthorsen/release-kit 2.3.2 → 4.4.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 (49) hide show
  1. package/CHANGELOG.md +90 -24
  2. package/README.md +123 -173
  3. package/bin/release-kit.js +10 -1
  4. package/cliff.toml.template +13 -12
  5. package/dist/esm/.cache +1 -1
  6. package/dist/esm/bin/release-kit.js +55 -13
  7. package/dist/esm/buildDependencyGraph.d.ts +7 -0
  8. package/dist/esm/buildDependencyGraph.js +59 -0
  9. package/dist/esm/buildReleaseSummary.d.ts +2 -0
  10. package/dist/esm/buildReleaseSummary.js +22 -0
  11. package/dist/esm/commitCommand.d.ts +1 -0
  12. package/dist/esm/commitCommand.js +60 -0
  13. package/dist/esm/createTags.js +5 -13
  14. package/dist/esm/deleteFileIfExists.d.ts +1 -0
  15. package/dist/esm/deleteFileIfExists.js +14 -0
  16. package/dist/esm/determineBumpFromCommits.d.ts +1 -1
  17. package/dist/esm/determineBumpFromCommits.js +2 -2
  18. package/dist/esm/generateChangelogs.d.ts +2 -0
  19. package/dist/esm/generateChangelogs.js +9 -1
  20. package/dist/esm/index.d.ts +5 -1
  21. package/dist/esm/index.js +10 -1
  22. package/dist/esm/init/initCommand.js +4 -2
  23. package/dist/esm/init/scaffold.js +3 -2
  24. package/dist/esm/init/templates.d.ts +1 -0
  25. package/dist/esm/init/templates.js +24 -2
  26. package/dist/esm/loadConfig.js +6 -6
  27. package/dist/esm/parseCommitMessage.d.ts +1 -1
  28. package/dist/esm/parseCommitMessage.js +9 -7
  29. package/dist/esm/prepareCommand.d.ts +1 -0
  30. package/dist/esm/prepareCommand.js +64 -34
  31. package/dist/esm/propagateBumps.d.ts +8 -0
  32. package/dist/esm/propagateBumps.js +54 -0
  33. package/dist/esm/publish.d.ts +1 -0
  34. package/dist/esm/publish.js +6 -3
  35. package/dist/esm/publishCommand.js +15 -9
  36. package/dist/esm/releasePrepare.js +2 -1
  37. package/dist/esm/releasePrepareMono.js +237 -48
  38. package/dist/esm/reportPrepare.js +29 -3
  39. package/dist/esm/stripScope.d.ts +1 -0
  40. package/dist/esm/stripScope.js +24 -0
  41. package/dist/esm/sync-labels/templates.js +1 -1
  42. package/dist/esm/tagCommand.js +11 -6
  43. package/dist/esm/types.d.ts +11 -4
  44. package/dist/esm/validateConfig.js +6 -6
  45. package/dist/esm/version.d.ts +1 -0
  46. package/dist/esm/version.js +4 -0
  47. package/dist/esm/writeSyntheticChangelog.d.ts +9 -0
  48. package/dist/esm/writeSyntheticChangelog.js +27 -0
  49. package/package.json +2 -2
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env node
2
+ import { parseArgs, translateParseError } from "@williamthorsen/node-monorepo-core";
3
+ import { commitCommand } from "../commitCommand.js";
2
4
  import { initCommand } from "../init/initCommand.js";
3
5
  import { prepareCommand } from "../prepareCommand.js";
4
6
  import { publishCommand } from "../publishCommand.js";
@@ -6,12 +8,14 @@ import { generateCommand } from "../sync-labels/generateCommand.js";
6
8
  import { syncLabelsInitCommand } from "../sync-labels/initCommand.js";
7
9
  import { syncLabelsCommand } from "../sync-labels/syncCommand.js";
8
10
  import { tagCommand } from "../tagCommand.js";
11
+ import { VERSION } from "../version.js";
9
12
  function showUsage() {
10
13
  console.info(`
11
14
  Usage: release-kit <command> [options]
12
15
 
13
16
  Commands:
14
17
  prepare Run release preparation (auto-discovers workspaces)
18
+ commit Stage changes and create the release commit
15
19
  tag Create annotated git tags from the tags file
16
20
  publish Publish packages with release tags on HEAD
17
21
  init Initialize release-kit in the current repository
@@ -97,6 +101,18 @@ Options:
97
101
  --help, -h Show this help message
98
102
  `);
99
103
  }
104
+ function showCommitHelp() {
105
+ console.info(`
106
+ Usage: release-kit commit [options]
107
+
108
+ Stage all changes and create the release commit using tags and summary
109
+ produced by \`prepare\`.
110
+
111
+ Options:
112
+ --dry-run Preview the commit message without creating it
113
+ --help, -h Show this help message
114
+ `);
115
+ }
100
116
  function showTagHelp() {
101
117
  console.info(`
102
118
  Usage: release-kit tag [options]
@@ -119,12 +135,17 @@ Options:
119
135
  --dry-run Preview without publishing
120
136
  --no-git-checks Skip git checks (pnpm only)
121
137
  --only=name1,name2 Only publish the named packages (comma-separated, monorepo only)
138
+ --provenance Generate provenance statement (requires OIDC, not supported by classic yarn)
122
139
  --help, -h Show this help message
123
140
  `);
124
141
  }
125
142
  const args = process.argv.slice(2);
126
143
  const command = args[0];
127
144
  const flags = args.slice(1);
145
+ if (command === "--version" || command === "-V") {
146
+ console.info(VERSION);
147
+ process.exit(0);
148
+ }
128
149
  if (command === "--help" || command === "-h" || command === void 0) {
129
150
  showUsage();
130
151
  process.exit(0);
@@ -137,6 +158,19 @@ if (command === "prepare") {
137
158
  await prepareCommand(flags);
138
159
  process.exit(0);
139
160
  }
161
+ if (command === "commit") {
162
+ if (flags.some((f) => f === "--help" || f === "-h")) {
163
+ showCommitHelp();
164
+ process.exit(0);
165
+ }
166
+ try {
167
+ commitCommand(flags);
168
+ } catch (error) {
169
+ console.error(error instanceof Error ? error.message : String(error));
170
+ process.exit(1);
171
+ }
172
+ process.exit(0);
173
+ }
140
174
  if (command === "tag") {
141
175
  if (flags.some((f) => f === "--help" || f === "-h")) {
142
176
  showTagHelp();
@@ -158,15 +192,19 @@ if (command === "init") {
158
192
  showInitHelp();
159
193
  process.exit(0);
160
194
  }
161
- const knownInitFlags = /* @__PURE__ */ new Set(["--dry-run", "--force", "--with-config", "--help", "-h"]);
162
- const unknownFlags = flags.filter((f) => !knownInitFlags.has(f));
163
- if (unknownFlags.length > 0) {
164
- console.error(`Error: Unknown option: ${unknownFlags[0]}`);
195
+ const initFlagSchema = {
196
+ dryRun: { long: "--dry-run", type: "boolean" },
197
+ force: { long: "--force", type: "boolean" },
198
+ withConfig: { long: "--with-config", type: "boolean" }
199
+ };
200
+ let parsed;
201
+ try {
202
+ parsed = parseArgs(flags, initFlagSchema);
203
+ } catch (error) {
204
+ console.error(`Error: ${translateParseError(error)}`);
165
205
  process.exit(1);
166
206
  }
167
- const dryRun = flags.includes("--dry-run");
168
- const force = flags.includes("--force");
169
- const withConfig = flags.includes("--with-config");
207
+ const { dryRun, force, withConfig } = parsed.flags;
170
208
  const exitCode = initCommand({ dryRun, force, withConfig });
171
209
  process.exit(exitCode);
172
210
  }
@@ -182,14 +220,18 @@ if (command === "sync-labels") {
182
220
  showSyncLabelsInitHelp();
183
221
  process.exit(0);
184
222
  }
185
- const knownFlags = /* @__PURE__ */ new Set(["--dry-run", "--force", "--help", "-h"]);
186
- const unknownFlags = subflags.filter((f) => !knownFlags.has(f));
187
- if (unknownFlags.length > 0) {
188
- console.error(`Error: Unknown option: ${unknownFlags[0]}`);
223
+ const syncLabelsInitFlagSchema = {
224
+ dryRun: { long: "--dry-run", type: "boolean" },
225
+ force: { long: "--force", type: "boolean" }
226
+ };
227
+ let syncParsed;
228
+ try {
229
+ syncParsed = parseArgs(subflags, syncLabelsInitFlagSchema);
230
+ } catch (error) {
231
+ console.error(`Error: ${translateParseError(error)}`);
189
232
  process.exit(1);
190
233
  }
191
- const dryRun = subflags.includes("--dry-run");
192
- const force = subflags.includes("--force");
234
+ const { dryRun, force } = syncParsed.flags;
193
235
  const exitCode = await syncLabelsInitCommand({ dryRun, force });
194
236
  process.exit(exitCode);
195
237
  }
@@ -0,0 +1,7 @@
1
+ import type { ComponentConfig } from './types.ts';
2
+ export interface DependencyGraph {
3
+ packageNameToDir: Map<string, string>;
4
+ dirToPackageName: Map<string, string>;
5
+ dependentsOf: Map<string, ComponentConfig[]>;
6
+ }
7
+ export declare function buildDependencyGraph(components: readonly ComponentConfig[]): DependencyGraph;
@@ -0,0 +1,59 @@
1
+ import { readFileSync } from "node:fs";
2
+ function isPackageJsonSubset(value) {
3
+ return typeof value === "object" && value !== null;
4
+ }
5
+ function buildDependencyGraph(components) {
6
+ const packageNameToDir = /* @__PURE__ */ new Map();
7
+ const dirToPackageName = /* @__PURE__ */ new Map();
8
+ const dependentsOf = /* @__PURE__ */ new Map();
9
+ const componentPackages = /* @__PURE__ */ new Map();
10
+ for (const component of components) {
11
+ const primaryPackageFile = component.packageFiles[0];
12
+ if (primaryPackageFile === void 0) {
13
+ continue;
14
+ }
15
+ const pkg = readPackageJsonSubset(primaryPackageFile);
16
+ componentPackages.set(component, pkg);
17
+ if (pkg.name === void 0) {
18
+ continue;
19
+ }
20
+ packageNameToDir.set(pkg.name, component.dir);
21
+ dirToPackageName.set(component.dir, pkg.name);
22
+ }
23
+ for (const [component, pkg] of componentPackages) {
24
+ const allDeps = { ...pkg.dependencies, ...pkg.peerDependencies };
25
+ for (const [depName, depVersion] of Object.entries(allDeps)) {
26
+ if (typeof depVersion !== "string" || !depVersion.startsWith("workspace:")) {
27
+ continue;
28
+ }
29
+ const existing = dependentsOf.get(depName);
30
+ if (existing === void 0) {
31
+ dependentsOf.set(depName, [component]);
32
+ } else {
33
+ existing.push(component);
34
+ }
35
+ }
36
+ }
37
+ return { packageNameToDir, dirToPackageName, dependentsOf };
38
+ }
39
+ function readPackageJsonSubset(filePath) {
40
+ let content;
41
+ try {
42
+ content = readFileSync(filePath, "utf8");
43
+ } catch (error) {
44
+ throw new Error(`Failed to read ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
45
+ }
46
+ let parsed;
47
+ try {
48
+ parsed = JSON.parse(content);
49
+ } catch (error) {
50
+ throw new Error(`Failed to parse JSON in ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
51
+ }
52
+ if (!isPackageJsonSubset(parsed)) {
53
+ throw new Error(`Invalid package.json at ${filePath}`);
54
+ }
55
+ return parsed;
56
+ }
57
+ export {
58
+ buildDependencyGraph
59
+ };
@@ -0,0 +1,2 @@
1
+ import type { PrepareResult } from './types.ts';
2
+ export declare function buildReleaseSummary(result: PrepareResult): string;
@@ -0,0 +1,22 @@
1
+ import { stripScope } from "./stripScope.js";
2
+ function buildReleaseSummary(result) {
3
+ const sections = [];
4
+ for (const component of result.components) {
5
+ if (component.status !== "released" || component.tag === void 0) {
6
+ continue;
7
+ }
8
+ const commits = component.commits;
9
+ if (commits === void 0 || commits.length === 0) {
10
+ continue;
11
+ }
12
+ const lines = [component.tag];
13
+ for (const commit of commits) {
14
+ lines.push(`- ${stripScope(commit.message)}`);
15
+ }
16
+ sections.push(lines.join("\n"));
17
+ }
18
+ return sections.join("\n\n");
19
+ }
20
+ export {
21
+ buildReleaseSummary
22
+ };
@@ -0,0 +1 @@
1
+ export declare function commitCommand(argv: string[]): void;
@@ -0,0 +1,60 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
3
+ import { parseArgs, translateParseError } from "@williamthorsen/node-monorepo-core";
4
+ import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./prepareCommand.js";
5
+ const commitFlagSchema = {
6
+ dryRun: { long: "--dry-run", type: "boolean" }
7
+ };
8
+ function commitCommand(argv) {
9
+ let parsed;
10
+ try {
11
+ parsed = parseArgs(argv, commitFlagSchema);
12
+ } catch (error) {
13
+ console.error(`Error: ${translateParseError(error)}`);
14
+ process.exit(1);
15
+ }
16
+ const dryRun = parsed.flags.dryRun;
17
+ let tagsContent;
18
+ try {
19
+ tagsContent = readFileSync(RELEASE_TAGS_FILE, "utf8");
20
+ } catch {
21
+ throw new Error("No tags file found. Run `release-kit prepare` first.");
22
+ }
23
+ const tags = tagsContent.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
24
+ if (tags.length === 0) {
25
+ throw new Error("Tags file is empty. Run `release-kit prepare` first.");
26
+ }
27
+ let summary = "";
28
+ try {
29
+ summary = readFileSync(RELEASE_SUMMARY_FILE, "utf8").trim();
30
+ } catch (error) {
31
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
32
+ } else {
33
+ throw error;
34
+ }
35
+ }
36
+ const title = `release: ${tags.join(" ")}`;
37
+ const message = summary.length > 0 ? `${title}
38
+
39
+ ${summary}` : title;
40
+ if (dryRun) {
41
+ console.info("[dry-run] Would create commit with message:\n");
42
+ console.info(message);
43
+ try {
44
+ const status = execFileSync("git", ["status", "--porcelain"], { encoding: "utf8" });
45
+ if (status.trim().length > 0) {
46
+ console.info("\nUncommitted changes:");
47
+ console.info(status.trimEnd());
48
+ }
49
+ } catch {
50
+ console.info("(Could not determine uncommitted changes)");
51
+ }
52
+ return;
53
+ }
54
+ execFileSync("git", ["add", "-A"]);
55
+ execFileSync("git", ["commit", "-m", message]);
56
+ console.info(`Created release commit: ${title}`);
57
+ }
58
+ export {
59
+ commitCommand
60
+ };
@@ -1,6 +1,7 @@
1
1
  import { execFileSync } from "node:child_process";
2
- import { readFileSync, unlinkSync } from "node:fs";
3
- import { RELEASE_TAGS_FILE } from "./prepareCommand.js";
2
+ import { readFileSync } from "node:fs";
3
+ import { deleteFileIfExists } from "./deleteFileIfExists.js";
4
+ import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE } from "./prepareCommand.js";
4
5
  function createTags(options) {
5
6
  const { dryRun, noGitChecks } = options;
6
7
  let content;
@@ -42,19 +43,10 @@ function createTags(options) {
42
43
  for (const tag of tags) {
43
44
  console.info(`\u{1F3F7}\uFE0F ${tag}`);
44
45
  }
45
- deleteTagsFile();
46
+ deleteFileIfExists(RELEASE_TAGS_FILE);
47
+ deleteFileIfExists(RELEASE_SUMMARY_FILE);
46
48
  return tags;
47
49
  }
48
- function deleteTagsFile() {
49
- try {
50
- unlinkSync(RELEASE_TAGS_FILE);
51
- } catch (error) {
52
- if (error instanceof Error && "code" in error && error.code === "ENOENT") {
53
- return;
54
- }
55
- throw error;
56
- }
57
- }
58
50
  function assertCleanWorkingTree() {
59
51
  try {
60
52
  execFileSync("git", ["diff", "--quiet"]);
@@ -0,0 +1 @@
1
+ export declare function deleteFileIfExists(filePath: string): void;
@@ -0,0 +1,14 @@
1
+ import { unlinkSync } from "node:fs";
2
+ function deleteFileIfExists(filePath) {
3
+ try {
4
+ unlinkSync(filePath);
5
+ } catch (error) {
6
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
7
+ return;
8
+ }
9
+ throw error;
10
+ }
11
+ }
12
+ export {
13
+ deleteFileIfExists
14
+ };
@@ -4,4 +4,4 @@ export interface BumpDetermination {
4
4
  parsedCommitCount: number;
5
5
  unparseableCommits: Commit[] | undefined;
6
6
  }
7
- export declare function determineBumpFromCommits(commits: Commit[], workTypes: Record<string, WorkTypeConfig>, versionPatterns: VersionPatterns, workspaceAliases: Record<string, string> | undefined): BumpDetermination;
7
+ export declare function determineBumpFromCommits(commits: Commit[], workTypes: Record<string, WorkTypeConfig>, versionPatterns: VersionPatterns, scopeAliases: Record<string, string> | undefined): BumpDetermination;
@@ -1,10 +1,10 @@
1
1
  import { determineBumpType } from "./determineBumpType.js";
2
2
  import { parseCommitMessage } from "./parseCommitMessage.js";
3
- function determineBumpFromCommits(commits, workTypes, versionPatterns, workspaceAliases) {
3
+ function determineBumpFromCommits(commits, workTypes, versionPatterns, scopeAliases) {
4
4
  const parsedCommits = [];
5
5
  const unparseable = [];
6
6
  for (const commit of commits) {
7
- const parsed = parseCommitMessage(commit.message, commit.hash, workTypes, workspaceAliases);
7
+ const parsed = parseCommitMessage(commit.message, commit.hash, workTypes, scopeAliases);
8
8
  if (parsed === void 0) {
9
9
  unparseable.push(commit);
10
10
  } else {
@@ -1,5 +1,7 @@
1
1
  import type { ReleaseConfig } from './types.ts';
2
+ export declare function buildTagPattern(tagPrefix: string): string;
2
3
  export interface GenerateChangelogOptions {
4
+ tagPattern?: string;
3
5
  includePaths?: string[];
4
6
  }
5
7
  export declare function generateChangelog(config: Pick<ReleaseConfig, 'cliffConfigPath'>, changelogPath: string, tag: string, dryRun: boolean, options?: GenerateChangelogOptions): string[];
@@ -1,9 +1,15 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { resolveCliffConfigPath } from "./resolveCliffConfigPath.js";
3
+ function buildTagPattern(tagPrefix) {
4
+ return `${tagPrefix}[0-9].*`;
5
+ }
3
6
  function generateChangelog(config, changelogPath, tag, dryRun, options) {
4
7
  const cliffConfigPath = resolveCliffConfigPath(config.cliffConfigPath, import.meta.url);
5
8
  const outputFile = `${changelogPath}/CHANGELOG.md`;
6
9
  const args = ["--config", cliffConfigPath, "--output", outputFile, "--tag", tag];
10
+ if (options?.tagPattern !== void 0) {
11
+ args.push("--tag-pattern", options.tagPattern);
12
+ }
7
13
  for (const includePath of options?.includePaths ?? []) {
8
14
  args.push("--include-path", includePath);
9
15
  }
@@ -19,13 +25,15 @@ function generateChangelog(config, changelogPath, tag, dryRun, options) {
19
25
  return [outputFile];
20
26
  }
21
27
  function generateChangelogs(config, tag, dryRun) {
28
+ const tagPattern = buildTagPattern(config.tagPrefix);
22
29
  const results = [];
23
30
  for (const changelogPath of config.changelogPaths) {
24
- results.push(...generateChangelog(config, changelogPath, tag, dryRun));
31
+ results.push(...generateChangelog(config, changelogPath, tag, dryRun, { tagPattern }));
25
32
  }
26
33
  return results;
27
34
  }
28
35
  export {
36
+ buildTagPattern,
29
37
  generateChangelog,
30
38
  generateChangelogs
31
39
  };
@@ -7,19 +7,23 @@ export type { ResolvedTag } from './resolveReleaseTags.ts';
7
7
  export type { LabelDefinition, SyncLabelsConfig } from './sync-labels/types.ts';
8
8
  export type { BumpResult, Commit, ComponentConfig, ComponentOverride, ComponentPrepareResult, MonorepoReleaseConfig, ParsedCommit, PrepareResult, ReleaseConfig, ReleaseKitConfig, ReleaseType, VersionPatterns, WorkTypeConfig, } from './types.ts';
9
9
  export { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from './defaults.ts';
10
+ export { buildReleaseSummary } from './buildReleaseSummary.ts';
10
11
  export { bumpAllVersions } from './bumpAllVersions.ts';
11
12
  export { bumpVersion } from './bumpVersion.ts';
13
+ export { commitCommand } from './commitCommand.ts';
12
14
  export { component } from './component.ts';
13
15
  export { createTags } from './createTags.ts';
16
+ export { deleteFileIfExists } from './deleteFileIfExists.ts';
14
17
  export { detectPackageManager } from './detectPackageManager.ts';
15
18
  export { determineBumpType } from './determineBumpType.ts';
16
19
  export { discoverWorkspaces } from './discoverWorkspaces.ts';
17
20
  export { generateChangelog, generateChangelogs } from './generateChangelogs.ts';
18
21
  export { getCommitsSinceTarget } from './getCommitsSinceTarget.ts';
19
22
  export { COMMIT_PREPROCESSOR_PATTERNS, parseCommitMessage } from './parseCommitMessage.ts';
20
- export { RELEASE_TAGS_FILE, writeReleaseTags } from './prepareCommand.ts';
23
+ export { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE, writeReleaseTags } from './prepareCommand.ts';
21
24
  export { publish } from './publish.ts';
22
25
  export { releasePrepare } from './releasePrepare.ts';
23
26
  export { releasePrepareMono } from './releasePrepareMono.ts';
24
27
  export { reportPrepare } from './reportPrepare.ts';
25
28
  export { resolveReleaseTags } from './resolveReleaseTags.ts';
29
+ export { stripScope } from './stripScope.ts';
package/dist/esm/index.js CHANGED
@@ -1,29 +1,37 @@
1
1
  import { DEFAULT_VERSION_PATTERNS, DEFAULT_WORK_TYPES } from "./defaults.js";
2
+ import { buildReleaseSummary } from "./buildReleaseSummary.js";
2
3
  import { bumpAllVersions } from "./bumpAllVersions.js";
3
4
  import { bumpVersion } from "./bumpVersion.js";
5
+ import { commitCommand } from "./commitCommand.js";
4
6
  import { component } from "./component.js";
5
7
  import { createTags } from "./createTags.js";
8
+ import { deleteFileIfExists } from "./deleteFileIfExists.js";
6
9
  import { detectPackageManager } from "./detectPackageManager.js";
7
10
  import { determineBumpType } from "./determineBumpType.js";
8
11
  import { discoverWorkspaces } from "./discoverWorkspaces.js";
9
12
  import { generateChangelog, generateChangelogs } from "./generateChangelogs.js";
10
13
  import { getCommitsSinceTarget } from "./getCommitsSinceTarget.js";
11
14
  import { COMMIT_PREPROCESSOR_PATTERNS, parseCommitMessage } from "./parseCommitMessage.js";
12
- import { RELEASE_TAGS_FILE, writeReleaseTags } from "./prepareCommand.js";
15
+ import { RELEASE_SUMMARY_FILE, RELEASE_TAGS_FILE, writeReleaseTags } from "./prepareCommand.js";
13
16
  import { publish } from "./publish.js";
14
17
  import { releasePrepare } from "./releasePrepare.js";
15
18
  import { releasePrepareMono } from "./releasePrepareMono.js";
16
19
  import { reportPrepare } from "./reportPrepare.js";
17
20
  import { resolveReleaseTags } from "./resolveReleaseTags.js";
21
+ import { stripScope } from "./stripScope.js";
18
22
  export {
19
23
  COMMIT_PREPROCESSOR_PATTERNS,
20
24
  DEFAULT_VERSION_PATTERNS,
21
25
  DEFAULT_WORK_TYPES,
26
+ RELEASE_SUMMARY_FILE,
22
27
  RELEASE_TAGS_FILE,
28
+ buildReleaseSummary,
23
29
  bumpAllVersions,
24
30
  bumpVersion,
31
+ commitCommand,
25
32
  component,
26
33
  createTags,
34
+ deleteFileIfExists,
27
35
  detectPackageManager,
28
36
  determineBumpType,
29
37
  discoverWorkspaces,
@@ -36,5 +44,6 @@ export {
36
44
  releasePrepareMono,
37
45
  reportPrepare,
38
46
  resolveReleaseTags,
47
+ stripScope,
39
48
  writeReleaseTags
40
49
  };
@@ -56,8 +56,10 @@ function initCommand({ dryRun, force, withConfig }) {
56
56
  const configHint = withConfig ? "1. (Optional) Customize .config/release-kit.config.ts and .config/git-cliff.toml." : "1. (Optional) Run again with --with-config to scaffold config files.";
57
57
  console.info(`
58
58
  ${configHint}
59
- 2. Test by running: npx @williamthorsen/release-kit prepare --dry-run
60
- 3. Commit the generated files.
59
+ 2. If this is a public repo, set provenance: true in .github/workflows/publish.yaml.
60
+ 3. Test by running: npx @williamthorsen/release-kit prepare --dry-run
61
+ 4. Commit the generated files.
62
+ 5. Register each package as a trusted publisher on npmjs.com.
61
63
  `);
62
64
  return 0;
63
65
  }
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
3
  import { writeFileWithCheck } from "@williamthorsen/node-monorepo-core";
4
4
  import { findPackageRoot } from "../findPackageRoot.js";
5
- import { releaseConfigScript, releaseWorkflow } from "./templates.js";
5
+ import { publishWorkflow, releaseConfigScript, releaseWorkflow } from "./templates.js";
6
6
  function copyCliffTemplate(dryRun, overwrite) {
7
7
  const destPath = ".config/git-cliff.toml";
8
8
  const root = findPackageRoot(import.meta.url);
@@ -21,7 +21,8 @@ function copyCliffTemplate(dryRun, overwrite) {
21
21
  }
22
22
  function scaffoldFiles({ repoType, dryRun, overwrite, withConfig }) {
23
23
  const results = [
24
- writeFileWithCheck(".github/workflows/release.yaml", releaseWorkflow(repoType), { dryRun, overwrite })
24
+ writeFileWithCheck(".github/workflows/release.yaml", releaseWorkflow(repoType), { dryRun, overwrite }),
25
+ writeFileWithCheck(".github/workflows/publish.yaml", publishWorkflow(repoType), { dryRun, overwrite })
25
26
  ];
26
27
  if (withConfig) {
27
28
  results.push(
@@ -1,3 +1,4 @@
1
1
  import type { RepoType } from './detectRepoType.ts';
2
2
  export declare function releaseConfigScript(repoType: RepoType): string;
3
+ export declare function publishWorkflow(repoType: RepoType): string;
3
4
  export declare function releaseWorkflow(repoType: RepoType): string;
@@ -35,6 +35,27 @@ const config: ReleaseKitConfig = {
35
35
  export default config;
36
36
  `;
37
37
  }
38
+ function publishWorkflow(repoType) {
39
+ const tagPattern = repoType === "monorepo" ? "'*-v[0-9]*'" : "'v[0-9]*'";
40
+ return `# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
41
+ name: Publish
42
+
43
+ on:
44
+ push:
45
+ tags:
46
+ - ${tagPattern}
47
+
48
+ permissions:
49
+ id-token: write
50
+ contents: read
51
+
52
+ jobs:
53
+ publish:
54
+ uses: williamthorsen/node-monorepo-tools/.github/workflows/publish.reusable.yaml@publish-workflow-v1
55
+ with:
56
+ provenance: false # Set to true for public repos to generate npm provenance attestations
57
+ `;
58
+ }
38
59
  function releaseWorkflow(repoType) {
39
60
  if (repoType === "monorepo") {
40
61
  return `# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
@@ -68,7 +89,7 @@ permissions:
68
89
 
69
90
  jobs:
70
91
  release:
71
- uses: williamthorsen/node-monorepo-tools/.github/workflows/release-workflow.yaml@release-workflow-v1
92
+ uses: williamthorsen/node-monorepo-tools/.github/workflows/release.reusable.yaml@release-workflow-v1
72
93
  with:
73
94
  only: \${{ inputs.only }}
74
95
  bump: \${{ inputs.bump }}
@@ -102,13 +123,14 @@ permissions:
102
123
 
103
124
  jobs:
104
125
  release:
105
- uses: williamthorsen/node-monorepo-tools/.github/workflows/release-workflow.yaml@release-workflow-v1
126
+ uses: williamthorsen/node-monorepo-tools/.github/workflows/release.reusable.yaml@release-workflow-v1
106
127
  with:
107
128
  bump: \${{ inputs.bump }}
108
129
  force: \${{ inputs.force }}
109
130
  `;
110
131
  }
111
132
  export {
133
+ publishWorkflow,
112
134
  releaseConfigScript,
113
135
  releaseWorkflow
114
136
  };
@@ -47,9 +47,9 @@ function mergeMonorepoConfig(discoveredPaths, userConfig) {
47
47
  if (cliffConfigPath !== void 0) {
48
48
  result.cliffConfigPath = cliffConfigPath;
49
49
  }
50
- const workspaceAliases = userConfig?.workspaceAliases;
51
- if (workspaceAliases !== void 0) {
52
- result.workspaceAliases = workspaceAliases;
50
+ const scopeAliases = userConfig?.scopeAliases;
51
+ if (scopeAliases !== void 0) {
52
+ result.scopeAliases = scopeAliases;
53
53
  }
54
54
  return result;
55
55
  }
@@ -71,9 +71,9 @@ function mergeSinglePackageConfig(userConfig) {
71
71
  if (cliffConfigPath !== void 0) {
72
72
  result.cliffConfigPath = cliffConfigPath;
73
73
  }
74
- const workspaceAliases = userConfig?.workspaceAliases;
75
- if (workspaceAliases !== void 0) {
76
- result.workspaceAliases = workspaceAliases;
74
+ const scopeAliases = userConfig?.scopeAliases;
75
+ if (scopeAliases !== void 0) {
76
+ result.scopeAliases = scopeAliases;
77
77
  }
78
78
  return result;
79
79
  }
@@ -1,3 +1,3 @@
1
1
  import type { ParsedCommit, WorkTypeConfig } from './types.ts';
2
2
  export declare const COMMIT_PREPROCESSOR_PATTERNS: readonly RegExp[];
3
- export declare function parseCommitMessage(message: string, hash: string, workTypes: Record<string, WorkTypeConfig>, workspaceAliases?: Record<string, string>): ParsedCommit | undefined;
3
+ export declare function parseCommitMessage(message: string, hash: string, workTypes: Record<string, WorkTypeConfig>, scopeAliases?: Record<string, string>): ParsedCommit | undefined;
@@ -1,14 +1,15 @@
1
1
  const COMMIT_PREPROCESSOR_PATTERNS = [/^#\d+\s+/, /^[A-Z]+-\d+\s+/];
2
- function parseCommitMessage(message, hash, workTypes, workspaceAliases) {
2
+ function parseCommitMessage(message, hash, workTypes, scopeAliases) {
3
3
  const stripped = stripTicketPrefix(message);
4
- const match = stripped.match(/^(?:([^|]+)\|)?(\w+)(!)?:\s*(.*)$/);
4
+ const match = stripped.match(/^(?:([^|]+)\|)?(\w+)(?:\(([^)]+)\))?(!)?:\s*(.*)$/);
5
5
  if (!match) {
6
6
  return void 0;
7
7
  }
8
- const workspace = match[1];
8
+ const pipeScope = match[1];
9
9
  const rawType = match[2];
10
- const breakingMarker = match[3];
11
- const description = match[4];
10
+ const parenthesizedScope = match[3];
11
+ const breakingMarker = match[4];
12
+ const description = match[5];
12
13
  if (rawType === void 0 || description === void 0) {
13
14
  return void 0;
14
15
  }
@@ -17,14 +18,15 @@ function parseCommitMessage(message, hash, workTypes, workspaceAliases) {
17
18
  return void 0;
18
19
  }
19
20
  const breaking = breakingMarker === "!" || message.includes("BREAKING CHANGE:");
20
- const resolvedWorkspace = workspace !== void 0 && workspaceAliases !== void 0 ? workspaceAliases[workspace] ?? workspace : workspace;
21
+ const rawScope = pipeScope ?? parenthesizedScope;
22
+ const resolvedScope = rawScope !== void 0 && scopeAliases !== void 0 ? scopeAliases[rawScope] ?? rawScope : rawScope;
21
23
  return {
22
24
  message,
23
25
  hash,
24
26
  type: resolvedType,
25
27
  description,
26
28
  breaking,
27
- ...resolvedWorkspace !== void 0 && { workspace: resolvedWorkspace }
29
+ ...resolvedScope !== void 0 && { scope: resolvedScope }
28
30
  };
29
31
  }
30
32
  function resolveType(rawType, workTypes) {
@@ -1,6 +1,7 @@
1
1
  import type { WriteResult } from '@williamthorsen/node-monorepo-core';
2
2
  import type { ReleaseType } from './types.ts';
3
3
  export declare const RELEASE_TAGS_FILE = "tmp/.release-tags";
4
+ export declare const RELEASE_SUMMARY_FILE = "tmp/.release-summary";
4
5
  export declare function parseArgs(argv: string[]): {
5
6
  dryRun: boolean;
6
7
  force: boolean;