@releasekit/version 0.2.0-next.8 → 0.2.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sam Maister
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,12 +1,16 @@
1
1
  import {
2
2
  BaseVersionError
3
3
  } from "./chunk-GQLJ7JQY.js";
4
+ import {
5
+ execAsync,
6
+ execSync
7
+ } from "./chunk-LMPZV35Z.js";
4
8
 
5
9
  // src/config.ts
6
- import { loadVersionConfig } from "@releasekit/config";
10
+ import { loadConfig as loadReleaseKitConfig } from "@releasekit/config";
7
11
 
8
12
  // src/types.ts
9
- function toVersionConfig(config) {
13
+ function toVersionConfig(config, gitConfig) {
10
14
  if (!config) {
11
15
  return {
12
16
  tagTemplate: "v{version}",
@@ -15,7 +19,9 @@ function toVersionConfig(config) {
15
19
  sync: true,
16
20
  packages: [],
17
21
  updateInternalDependencies: "minor",
18
- versionPrefix: ""
22
+ versionPrefix: "",
23
+ baseBranch: gitConfig?.branch,
24
+ skipHooks: gitConfig?.skipHooks
19
25
  };
20
26
  }
21
27
  return {
@@ -34,19 +40,19 @@ function toVersionConfig(config) {
34
40
  releaseType: bp.releaseType
35
41
  })),
36
42
  defaultReleaseType: config.defaultReleaseType,
37
- skipHooks: config.skipHooks,
43
+ skipHooks: gitConfig?.skipHooks,
38
44
  mismatchStrategy: config.mismatchStrategy,
39
45
  versionPrefix: config.versionPrefix ?? "",
40
46
  prereleaseIdentifier: config.prereleaseIdentifier,
41
- baseBranch: config.baseBranch,
47
+ baseBranch: gitConfig?.branch,
42
48
  cargo: config.cargo
43
49
  };
44
50
  }
45
51
 
46
52
  // src/config.ts
47
53
  function loadConfig(options) {
48
- const versionConfig = loadVersionConfig(options);
49
- return toVersionConfig(versionConfig);
54
+ const fullConfig = loadReleaseKitConfig(options);
55
+ return toVersionConfig(fullConfig.version, fullConfig.git);
50
56
  }
51
57
 
52
58
  // src/errors/versionError.ts
@@ -202,24 +208,6 @@ import semver3 from "semver";
202
208
  // src/git/repository.ts
203
209
  import { existsSync, statSync } from "fs";
204
210
  import { join } from "path";
205
-
206
- // src/git/commandExecutor.ts
207
- import { execFile, execFileSync } from "child_process";
208
- var execAsync = (file, args, options) => {
209
- const defaultOptions = { maxBuffer: 1024 * 1024 * 10, ...options };
210
- return new Promise((resolve2, reject) => {
211
- execFile(file, args, defaultOptions, (error, stdout, stderr) => {
212
- if (error) {
213
- reject(error);
214
- } else {
215
- resolve2({ stdout: stdout.toString(), stderr: stderr.toString() });
216
- }
217
- });
218
- });
219
- };
220
- var execSync = (file, args, options) => execFileSync(file, args, { maxBuffer: 1024 * 1024 * 10, ...options });
221
-
222
- // src/git/repository.ts
223
211
  function isGitRepository(directory) {
224
212
  const gitDir = join(directory, ".git");
225
213
  if (!existsSync(gitDir)) {
@@ -289,8 +277,8 @@ To fix this:
289
277
  let result = template.replace(/\$\{version\}/g, version).replace(/\$\{packageName\}/g, packageName || "");
290
278
  if (additionalContext) {
291
279
  for (const [key, value] of Object.entries(additionalContext)) {
292
- const placeholder = `\${${key}}`;
293
- result = result.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), value);
280
+ const placeholder = `${key ? `\${${key}}` : ""}`;
281
+ result = result.replace(new RegExp(escapeRegExp(placeholder), "g"), value);
294
282
  }
295
283
  }
296
284
  return result;
@@ -374,28 +362,37 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
374
362
  `Looking for tags for package ${packageName} with prefix ${versionPrefix || "none"}, packageSpecificTags: ${packageSpecificTags}`,
375
363
  "debug"
376
364
  );
377
- const allTags = await getSemverTags({
378
- tagPrefix: versionPrefix
379
- });
380
- log(`Retrieved ${allTags.length} tags: ${allTags.join(", ")}`, "debug");
365
+ let allTags = [];
366
+ try {
367
+ const { execSync: execSync2 } = await import("./commandExecutor-E44ID5U4.js");
368
+ const tagsOutput = execSync2("git", ["tag", "-l"], { cwd: process.cwd() });
369
+ allTags = tagsOutput.toString().trim().split("\n").filter((tag) => tag.length > 0);
370
+ } catch (err) {
371
+ log(`Error getting tags: ${err instanceof Error ? err.message : String(err)}`, "error");
372
+ }
373
+ log(`Retrieved ${allTags.length} tags`, "debug");
381
374
  if (packageSpecificTags) {
382
375
  const packageTagPattern = escapeRegExp(tagTemplate).replace(/\\\$\\\{packageName\\\}/g, `(?:${escapedPackageName})`).replace(/\\\$\\\{prefix\\\}/g, `(?:${escapedPrefix})`).replace(/\\\$\\\{version\\\}/g, "(?:[0-9]+\\.[0-9]+\\.[0-9]+(?:-[a-zA-Z0-9.-]+)?)");
383
376
  log(`Using package tag pattern: ${packageTagPattern}`, "debug");
384
377
  const packageTagRegex = new RegExp(`^${packageTagPattern}$`);
385
378
  let packageTags = allTags.filter((tag) => packageTagRegex.test(tag));
379
+ log(`Found ${packageTags.length} matching tags for ${packageName}`, "debug");
386
380
  if (packageTags.length > 0) {
387
381
  const chronologicalFirst = packageTags[0];
382
+ void chronologicalFirst;
388
383
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
389
384
  let versionA = "";
390
385
  let versionB = "";
391
386
  if (a.includes("@")) {
392
- const afterAt = a.split("@")[1] || "";
387
+ const parts = a.split("@");
388
+ const afterAt = parts[parts.length - 1] || "";
393
389
  versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
394
390
  } else {
395
391
  versionA = a.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
396
392
  }
397
393
  if (b.includes("@")) {
398
- const afterAtB = b.split("@")[1] || "";
394
+ const parts = b.split("@");
395
+ const afterAtB = parts[parts.length - 1] || "";
399
396
  versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
400
397
  } else {
401
398
  versionB = b.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
@@ -406,12 +403,6 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
406
403
  });
407
404
  log(`Found ${packageTags.length} package tags using configured pattern`, "debug");
408
405
  log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
409
- if (sortedPackageTags2[0] !== chronologicalFirst) {
410
- log(
411
- `Package tag ordering differs: chronological first is ${chronologicalFirst}, semantic latest is ${sortedPackageTags2[0]}`,
412
- "debug"
413
- );
414
- }
415
406
  return sortedPackageTags2[0];
416
407
  }
417
408
  if (versionPrefix) {
@@ -419,9 +410,11 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
419
410
  packageTags = allTags.filter((tag) => pattern1.test(tag));
420
411
  if (packageTags.length > 0) {
421
412
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
422
- const afterAt = a.split("@")[1] || "";
413
+ const aParts = a.split("@");
414
+ const afterAt = aParts[aParts.length - 1] || "";
423
415
  const versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
424
- const afterAtB = b.split("@")[1] || "";
416
+ const bParts = b.split("@");
417
+ const afterAtB = bParts[bParts.length - 1] || "";
425
418
  const versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
426
419
  const cleanVersionA = semver.clean(versionA) || "0.0.0";
427
420
  const cleanVersionB = semver.clean(versionB) || "0.0.0";
@@ -437,8 +430,10 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
437
430
  packageTags = allTags.filter((tag) => pattern2.test(tag));
438
431
  if (packageTags.length > 0) {
439
432
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
440
- const versionA = semver.clean(a.split("@")[1] || "") || "0.0.0";
441
- const versionB = semver.clean(b.split("@")[1] || "") || "0.0.0";
433
+ const aParts = a.split("@");
434
+ const versionA = semver.clean(aParts[aParts.length - 1] || "") || "0.0.0";
435
+ const bParts = b.split("@");
436
+ const versionB = semver.clean(bParts[bParts.length - 1] || "") || "0.0.0";
442
437
  return semver.rcompare(versionA, versionB);
443
438
  });
444
439
  log(`Found ${packageTags.length} package tags using pattern: ${versionPrefix}packageName@...`, "debug");
@@ -458,8 +453,10 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
458
453
  return "";
459
454
  }
460
455
  const sortedPackageTags = [...packageTags].sort((a, b) => {
461
- const versionA = semver.clean(a.split("@")[1] || "") || "0.0.0";
462
- const versionB = semver.clean(b.split("@")[1] || "") || "0.0.0";
456
+ const aParts = a.split("@");
457
+ const versionA = semver.clean(aParts[aParts.length - 1] || "") || "0.0.0";
458
+ const bParts = b.split("@");
459
+ const versionB = semver.clean(bParts[bParts.length - 1] || "") || "0.0.0";
463
460
  return semver.rcompare(versionA, versionB);
464
461
  });
465
462
  log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
@@ -974,13 +971,83 @@ import { exit } from "process";
974
971
  // src/changelog/commitParser.ts
975
972
  var CONVENTIONAL_COMMIT_REGEX = /^(\w+)(?:\(([^)]+)\))?(!)?: (.+)(?:\n\n([\s\S]*))?/;
976
973
  var BREAKING_CHANGE_REGEX = /BREAKING CHANGE: ([\s\S]+?)(?:\n\n|$)/;
974
+ function extractAllChangelogEntriesWithHash(projectDir, revisionRange) {
975
+ try {
976
+ const args = ["log", revisionRange, "--pretty=format:%H|||%B---COMMIT_DELIMITER---", "--no-merges"];
977
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
978
+ const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
979
+ return commits.map((commit) => {
980
+ const [hash, ...messageParts] = commit.split("|||");
981
+ const message = messageParts.join("|||").trim();
982
+ const entry = parseCommitMessage(message);
983
+ if (entry && hash) {
984
+ return { hash: hash.trim(), entry };
985
+ }
986
+ return null;
987
+ }).filter((item) => item !== null);
988
+ } catch (error) {
989
+ const errorMessage = error instanceof Error ? error.message : String(error);
990
+ log(`Error extracting all commits with hash: ${errorMessage}`, "error");
991
+ return [];
992
+ }
993
+ }
994
+ function commitTouchesAnyPackage(projectDir, commitHash, packageDirs, sharedPackageDirs = []) {
995
+ try {
996
+ const output = execSync("git", ["diff-tree", "--no-commit-id", "--name-only", "-r", commitHash], {
997
+ cwd: projectDir,
998
+ encoding: "utf8"
999
+ }).toString().trim();
1000
+ if (!output) {
1001
+ return false;
1002
+ }
1003
+ const changedFiles = output.split("\n");
1004
+ return changedFiles.some((file) => {
1005
+ return packageDirs.some((pkgDir) => {
1006
+ if (sharedPackageDirs.some((sharedDir) => pkgDir.includes(sharedDir))) {
1007
+ return false;
1008
+ }
1009
+ const normalizedFile = file.replace(/\\/g, "/");
1010
+ const normalizedPkgDir = pkgDir.replace(/\\/g, "/").replace(/^\.\//, "");
1011
+ return normalizedFile.startsWith(normalizedPkgDir);
1012
+ });
1013
+ });
1014
+ } catch (error) {
1015
+ log(
1016
+ `Error checking if commit ${commitHash} touches packages: ${error instanceof Error ? error.message : String(error)}`,
1017
+ "debug"
1018
+ );
1019
+ return false;
1020
+ }
1021
+ }
1022
+ function extractRepoLevelChangelogEntries(projectDir, revisionRange, packageDirs, sharedPackageDirs = []) {
1023
+ try {
1024
+ const allCommits = extractAllChangelogEntriesWithHash(projectDir, revisionRange);
1025
+ const repoLevelCommits = allCommits.filter((commit) => {
1026
+ const touchesPackage = commitTouchesAnyPackage(projectDir, commit.hash, packageDirs, sharedPackageDirs);
1027
+ return !touchesPackage;
1028
+ });
1029
+ if (repoLevelCommits.length > 0) {
1030
+ log(
1031
+ `Found ${repoLevelCommits.length} repo-level commit(s) (including shared packages: ${sharedPackageDirs.join(", ")})`,
1032
+ "debug"
1033
+ );
1034
+ }
1035
+ return repoLevelCommits.map((c) => c.entry);
1036
+ } catch (error) {
1037
+ log(`Error extracting repo-level commits: ${error instanceof Error ? error.message : String(error)}`, "warning");
1038
+ return [];
1039
+ }
1040
+ }
977
1041
  function extractChangelogEntriesFromCommits(projectDir, revisionRange) {
1042
+ return extractCommitsFromGitLog(projectDir, revisionRange, true);
1043
+ }
1044
+ function extractCommitsFromGitLog(projectDir, revisionRange, filterToPath) {
978
1045
  try {
979
- const output = execSync(
980
- "git",
981
- ["log", revisionRange, "--pretty=format:%B---COMMIT_DELIMITER---", "--no-merges", "--", "."],
982
- { cwd: projectDir, encoding: "utf8" }
983
- ).toString();
1046
+ const args = ["log", revisionRange, "--pretty=format:%B---COMMIT_DELIMITER---", "--no-merges"];
1047
+ if (filterToPath) {
1048
+ args.push("--", ".");
1049
+ }
1050
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
984
1051
  const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
985
1052
  return commits.map((commit) => parseCommitMessage(commit)).filter((entry) => entry !== null);
986
1053
  } catch (error) {
@@ -1420,6 +1487,19 @@ var PackageProcessor = class {
1420
1487
  revisionRange = "HEAD";
1421
1488
  }
1422
1489
  changelogEntries = extractChangelogEntriesFromCommits(pkgPath, revisionRange);
1490
+ const allPackageDirs = packages.map((p) => p.dir);
1491
+ const sharedPackageNames = ["config", "core", "@releasekit/config", "@releasekit/core"];
1492
+ const sharedPackageDirs = packages.filter((p) => sharedPackageNames.includes(p.packageJson.name)).map((p) => p.dir);
1493
+ const repoLevelEntries = extractRepoLevelChangelogEntries(
1494
+ pkgPath,
1495
+ revisionRange,
1496
+ allPackageDirs,
1497
+ sharedPackageDirs
1498
+ );
1499
+ if (repoLevelEntries.length > 0) {
1500
+ log(`Adding ${repoLevelEntries.length} repo-level commit(s) to ${name} changelog`, "debug");
1501
+ changelogEntries = [...repoLevelEntries, ...changelogEntries];
1502
+ }
1423
1503
  if (changelogEntries.length === 0) {
1424
1504
  changelogEntries = [
1425
1505
  {
@@ -1554,7 +1634,12 @@ var PackageProcessor = class {
1554
1634
  const packageNames = updatedPackagesInfo.map((p) => p.name).join(", ");
1555
1635
  const representativeVersion = updatedPackagesInfo[0]?.version || "multiple";
1556
1636
  let commitMessage = this.commitMessageTemplate || "chore(release): publish packages";
1557
- const placeholderRegex = /\$\{[^}]+\}/;
1637
+ const MAX_COMMIT_MSG_LENGTH = 1e4;
1638
+ if (commitMessage.length > MAX_COMMIT_MSG_LENGTH) {
1639
+ log("Commit message template too long, truncating", "warning");
1640
+ commitMessage = commitMessage.slice(0, MAX_COMMIT_MSG_LENGTH);
1641
+ }
1642
+ const placeholderRegex = /\$\{[^{}$]{1,1000}\}/;
1558
1643
  if (updatedPackagesInfo.length === 1 && placeholderRegex.test(commitMessage)) {
1559
1644
  const packageName = updatedPackagesInfo[0].name;
1560
1645
  commitMessage = formatCommitMessage(commitMessage, representativeVersion, packageName);
@@ -0,0 +1,20 @@
1
+ // src/git/commandExecutor.ts
2
+ import { execFile, execFileSync } from "child_process";
3
+ var execAsync = (file, args, options) => {
4
+ const defaultOptions = { maxBuffer: 1024 * 1024 * 10, ...options };
5
+ return new Promise((resolve, reject) => {
6
+ execFile(file, args, defaultOptions, (error, stdout, stderr) => {
7
+ if (error) {
8
+ reject(error);
9
+ } else {
10
+ resolve({ stdout: stdout.toString(), stderr: stderr.toString() });
11
+ }
12
+ });
13
+ });
14
+ };
15
+ var execSync = (file, args, options) => execFileSync(file, args, { maxBuffer: 1024 * 1024 * 10, ...options });
16
+
17
+ export {
18
+ execAsync,
19
+ execSync
20
+ };
package/dist/cli.cjs CHANGED
@@ -57,6 +57,33 @@ var init_baseError = __esm({
57
57
  }
58
58
  });
59
59
 
60
+ // src/git/commandExecutor.ts
61
+ var commandExecutor_exports = {};
62
+ __export(commandExecutor_exports, {
63
+ execAsync: () => execAsync,
64
+ execSync: () => execSync
65
+ });
66
+ var import_node_child_process, execAsync, execSync;
67
+ var init_commandExecutor = __esm({
68
+ "src/git/commandExecutor.ts"() {
69
+ "use strict";
70
+ import_node_child_process = require("child_process");
71
+ execAsync = (file, args, options) => {
72
+ const defaultOptions = { maxBuffer: 1024 * 1024 * 10, ...options };
73
+ return new Promise((resolve2, reject) => {
74
+ (0, import_node_child_process.execFile)(file, args, defaultOptions, (error, stdout, stderr) => {
75
+ if (error) {
76
+ reject(error);
77
+ } else {
78
+ resolve2({ stdout: stdout.toString(), stderr: stderr.toString() });
79
+ }
80
+ });
81
+ });
82
+ };
83
+ execSync = (file, args, options) => (0, import_node_child_process.execFileSync)(file, args, { maxBuffer: 1024 * 1024 * 10, ...options });
84
+ }
85
+ });
86
+
60
87
  // src/cli.ts
61
88
  var cli_exports = {};
62
89
  __export(cli_exports, {
@@ -71,7 +98,7 @@ var import_commander = require("commander");
71
98
  var import_config = require("@releasekit/config");
72
99
 
73
100
  // src/types.ts
74
- function toVersionConfig(config) {
101
+ function toVersionConfig(config, gitConfig) {
75
102
  if (!config) {
76
103
  return {
77
104
  tagTemplate: "v{version}",
@@ -80,7 +107,9 @@ function toVersionConfig(config) {
80
107
  sync: true,
81
108
  packages: [],
82
109
  updateInternalDependencies: "minor",
83
- versionPrefix: ""
110
+ versionPrefix: "",
111
+ baseBranch: gitConfig?.branch,
112
+ skipHooks: gitConfig?.skipHooks
84
113
  };
85
114
  }
86
115
  return {
@@ -99,19 +128,19 @@ function toVersionConfig(config) {
99
128
  releaseType: bp.releaseType
100
129
  })),
101
130
  defaultReleaseType: config.defaultReleaseType,
102
- skipHooks: config.skipHooks,
131
+ skipHooks: gitConfig?.skipHooks,
103
132
  mismatchStrategy: config.mismatchStrategy,
104
133
  versionPrefix: config.versionPrefix ?? "",
105
134
  prereleaseIdentifier: config.prereleaseIdentifier,
106
- baseBranch: config.baseBranch,
135
+ baseBranch: gitConfig?.branch,
107
136
  cargo: config.cargo
108
137
  };
109
138
  }
110
139
 
111
140
  // src/config.ts
112
141
  function loadConfig(options) {
113
- const versionConfig = (0, import_config.loadVersionConfig)(options);
114
- return toVersionConfig(versionConfig);
142
+ const fullConfig = (0, import_config.loadConfig)(options);
143
+ return toVersionConfig(fullConfig.version, fullConfig.git);
115
144
  }
116
145
 
117
146
  // src/core/versionEngine.ts
@@ -370,32 +399,87 @@ function matchesPackageNamePattern(packageName, pattern) {
370
399
  var import_node_fs6 = __toESM(require("fs"), 1);
371
400
  var path6 = __toESM(require("path"), 1);
372
401
 
373
- // src/git/commandExecutor.ts
374
- var import_node_child_process = require("child_process");
375
- var execAsync = (file, args, options) => {
376
- const defaultOptions = { maxBuffer: 1024 * 1024 * 10, ...options };
377
- return new Promise((resolve2, reject) => {
378
- (0, import_node_child_process.execFile)(file, args, defaultOptions, (error, stdout, stderr) => {
379
- if (error) {
380
- reject(error);
381
- } else {
382
- resolve2({ stdout: stdout.toString(), stderr: stderr.toString() });
383
- }
384
- });
385
- });
386
- };
387
- var execSync = (file, args, options) => (0, import_node_child_process.execFileSync)(file, args, { maxBuffer: 1024 * 1024 * 10, ...options });
388
-
389
402
  // src/changelog/commitParser.ts
403
+ init_commandExecutor();
390
404
  var CONVENTIONAL_COMMIT_REGEX = /^(\w+)(?:\(([^)]+)\))?(!)?: (.+)(?:\n\n([\s\S]*))?/;
391
405
  var BREAKING_CHANGE_REGEX = /BREAKING CHANGE: ([\s\S]+?)(?:\n\n|$)/;
406
+ function extractAllChangelogEntriesWithHash(projectDir, revisionRange) {
407
+ try {
408
+ const args = ["log", revisionRange, "--pretty=format:%H|||%B---COMMIT_DELIMITER---", "--no-merges"];
409
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
410
+ const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
411
+ return commits.map((commit) => {
412
+ const [hash, ...messageParts] = commit.split("|||");
413
+ const message = messageParts.join("|||").trim();
414
+ const entry = parseCommitMessage(message);
415
+ if (entry && hash) {
416
+ return { hash: hash.trim(), entry };
417
+ }
418
+ return null;
419
+ }).filter((item) => item !== null);
420
+ } catch (error) {
421
+ const errorMessage = error instanceof Error ? error.message : String(error);
422
+ log(`Error extracting all commits with hash: ${errorMessage}`, "error");
423
+ return [];
424
+ }
425
+ }
426
+ function commitTouchesAnyPackage(projectDir, commitHash, packageDirs, sharedPackageDirs = []) {
427
+ try {
428
+ const output = execSync("git", ["diff-tree", "--no-commit-id", "--name-only", "-r", commitHash], {
429
+ cwd: projectDir,
430
+ encoding: "utf8"
431
+ }).toString().trim();
432
+ if (!output) {
433
+ return false;
434
+ }
435
+ const changedFiles = output.split("\n");
436
+ return changedFiles.some((file) => {
437
+ return packageDirs.some((pkgDir) => {
438
+ if (sharedPackageDirs.some((sharedDir) => pkgDir.includes(sharedDir))) {
439
+ return false;
440
+ }
441
+ const normalizedFile = file.replace(/\\/g, "/");
442
+ const normalizedPkgDir = pkgDir.replace(/\\/g, "/").replace(/^\.\//, "");
443
+ return normalizedFile.startsWith(normalizedPkgDir);
444
+ });
445
+ });
446
+ } catch (error) {
447
+ log(
448
+ `Error checking if commit ${commitHash} touches packages: ${error instanceof Error ? error.message : String(error)}`,
449
+ "debug"
450
+ );
451
+ return false;
452
+ }
453
+ }
454
+ function extractRepoLevelChangelogEntries(projectDir, revisionRange, packageDirs, sharedPackageDirs = []) {
455
+ try {
456
+ const allCommits = extractAllChangelogEntriesWithHash(projectDir, revisionRange);
457
+ const repoLevelCommits = allCommits.filter((commit) => {
458
+ const touchesPackage = commitTouchesAnyPackage(projectDir, commit.hash, packageDirs, sharedPackageDirs);
459
+ return !touchesPackage;
460
+ });
461
+ if (repoLevelCommits.length > 0) {
462
+ log(
463
+ `Found ${repoLevelCommits.length} repo-level commit(s) (including shared packages: ${sharedPackageDirs.join(", ")})`,
464
+ "debug"
465
+ );
466
+ }
467
+ return repoLevelCommits.map((c) => c.entry);
468
+ } catch (error) {
469
+ log(`Error extracting repo-level commits: ${error instanceof Error ? error.message : String(error)}`, "warning");
470
+ return [];
471
+ }
472
+ }
392
473
  function extractChangelogEntriesFromCommits(projectDir, revisionRange) {
474
+ return extractCommitsFromGitLog(projectDir, revisionRange, true);
475
+ }
476
+ function extractCommitsFromGitLog(projectDir, revisionRange, filterToPath) {
393
477
  try {
394
- const output = execSync(
395
- "git",
396
- ["log", revisionRange, "--pretty=format:%B---COMMIT_DELIMITER---", "--no-merges", "--", "."],
397
- { cwd: projectDir, encoding: "utf8" }
398
- ).toString();
478
+ const args = ["log", revisionRange, "--pretty=format:%B---COMMIT_DELIMITER---", "--no-merges"];
479
+ if (filterToPath) {
480
+ args.push("--", ".");
481
+ }
482
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
399
483
  const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
400
484
  return commits.map((commit) => parseCommitMessage(commit)).filter((entry) => entry !== null);
401
485
  } catch (error) {
@@ -490,13 +574,16 @@ function extractIssueIds(body) {
490
574
 
491
575
  // src/core/versionStrategies.ts
492
576
  init_baseError();
577
+ init_commandExecutor();
493
578
 
494
579
  // src/git/commands.ts
495
580
  var import_node_process = require("process");
581
+ init_commandExecutor();
496
582
 
497
583
  // src/git/repository.ts
498
584
  var import_node_fs = require("fs");
499
585
  var import_node_path2 = require("path");
586
+ init_commandExecutor();
500
587
  function isGitRepository(directory) {
501
588
  const gitDir = (0, import_node_path2.join)(directory, ".git");
502
589
  if (!(0, import_node_fs.existsSync)(gitDir)) {
@@ -693,14 +780,15 @@ To fix this:
693
780
  let result = template.replace(/\$\{version\}/g, version).replace(/\$\{packageName\}/g, packageName || "");
694
781
  if (additionalContext) {
695
782
  for (const [key, value] of Object.entries(additionalContext)) {
696
- const placeholder = `\${${key}}`;
697
- result = result.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), value);
783
+ const placeholder = `${key ? `\${${key}}` : ""}`;
784
+ result = result.replace(new RegExp(escapeRegExp(placeholder), "g"), value);
698
785
  }
699
786
  }
700
787
  return result;
701
788
  }
702
789
 
703
790
  // src/git/tagsAndBranches.ts
791
+ init_commandExecutor();
704
792
  function getCommitsLength(pkgRoot, sinceTag) {
705
793
  try {
706
794
  let amount;
@@ -778,28 +866,37 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
778
866
  `Looking for tags for package ${packageName} with prefix ${versionPrefix || "none"}, packageSpecificTags: ${packageSpecificTags}`,
779
867
  "debug"
780
868
  );
781
- const allTags = await (0, import_git_semver_tags.getSemverTags)({
782
- tagPrefix: versionPrefix
783
- });
784
- log(`Retrieved ${allTags.length} tags: ${allTags.join(", ")}`, "debug");
869
+ let allTags = [];
870
+ try {
871
+ const { execSync: execSync2 } = await Promise.resolve().then(() => (init_commandExecutor(), commandExecutor_exports));
872
+ const tagsOutput = execSync2("git", ["tag", "-l"], { cwd: process.cwd() });
873
+ allTags = tagsOutput.toString().trim().split("\n").filter((tag) => tag.length > 0);
874
+ } catch (err) {
875
+ log(`Error getting tags: ${err instanceof Error ? err.message : String(err)}`, "error");
876
+ }
877
+ log(`Retrieved ${allTags.length} tags`, "debug");
785
878
  if (packageSpecificTags) {
786
879
  const packageTagPattern = escapeRegExp(tagTemplate).replace(/\\\$\\\{packageName\\\}/g, `(?:${escapedPackageName})`).replace(/\\\$\\\{prefix\\\}/g, `(?:${escapedPrefix})`).replace(/\\\$\\\{version\\\}/g, "(?:[0-9]+\\.[0-9]+\\.[0-9]+(?:-[a-zA-Z0-9.-]+)?)");
787
880
  log(`Using package tag pattern: ${packageTagPattern}`, "debug");
788
881
  const packageTagRegex = new RegExp(`^${packageTagPattern}$`);
789
882
  let packageTags = allTags.filter((tag) => packageTagRegex.test(tag));
883
+ log(`Found ${packageTags.length} matching tags for ${packageName}`, "debug");
790
884
  if (packageTags.length > 0) {
791
885
  const chronologicalFirst = packageTags[0];
886
+ void chronologicalFirst;
792
887
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
793
888
  let versionA = "";
794
889
  let versionB = "";
795
890
  if (a.includes("@")) {
796
- const afterAt = a.split("@")[1] || "";
891
+ const parts = a.split("@");
892
+ const afterAt = parts[parts.length - 1] || "";
797
893
  versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
798
894
  } else {
799
895
  versionA = a.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
800
896
  }
801
897
  if (b.includes("@")) {
802
- const afterAtB = b.split("@")[1] || "";
898
+ const parts = b.split("@");
899
+ const afterAtB = parts[parts.length - 1] || "";
803
900
  versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
804
901
  } else {
805
902
  versionB = b.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
@@ -810,12 +907,6 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
810
907
  });
811
908
  log(`Found ${packageTags.length} package tags using configured pattern`, "debug");
812
909
  log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
813
- if (sortedPackageTags2[0] !== chronologicalFirst) {
814
- log(
815
- `Package tag ordering differs: chronological first is ${chronologicalFirst}, semantic latest is ${sortedPackageTags2[0]}`,
816
- "debug"
817
- );
818
- }
819
910
  return sortedPackageTags2[0];
820
911
  }
821
912
  if (versionPrefix) {
@@ -823,9 +914,11 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
823
914
  packageTags = allTags.filter((tag) => pattern1.test(tag));
824
915
  if (packageTags.length > 0) {
825
916
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
826
- const afterAt = a.split("@")[1] || "";
917
+ const aParts = a.split("@");
918
+ const afterAt = aParts[aParts.length - 1] || "";
827
919
  const versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
828
- const afterAtB = b.split("@")[1] || "";
920
+ const bParts = b.split("@");
921
+ const afterAtB = bParts[bParts.length - 1] || "";
829
922
  const versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
830
923
  const cleanVersionA = import_semver.default.clean(versionA) || "0.0.0";
831
924
  const cleanVersionB = import_semver.default.clean(versionB) || "0.0.0";
@@ -841,8 +934,10 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
841
934
  packageTags = allTags.filter((tag) => pattern2.test(tag));
842
935
  if (packageTags.length > 0) {
843
936
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
844
- const versionA = import_semver.default.clean(a.split("@")[1] || "") || "0.0.0";
845
- const versionB = import_semver.default.clean(b.split("@")[1] || "") || "0.0.0";
937
+ const aParts = a.split("@");
938
+ const versionA = import_semver.default.clean(aParts[aParts.length - 1] || "") || "0.0.0";
939
+ const bParts = b.split("@");
940
+ const versionB = import_semver.default.clean(bParts[bParts.length - 1] || "") || "0.0.0";
846
941
  return import_semver.default.rcompare(versionA, versionB);
847
942
  });
848
943
  log(`Found ${packageTags.length} package tags using pattern: ${versionPrefix}packageName@...`, "debug");
@@ -862,8 +957,10 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
862
957
  return "";
863
958
  }
864
959
  const sortedPackageTags = [...packageTags].sort((a, b) => {
865
- const versionA = import_semver.default.clean(a.split("@")[1] || "") || "0.0.0";
866
- const versionB = import_semver.default.clean(b.split("@")[1] || "") || "0.0.0";
960
+ const aParts = a.split("@");
961
+ const versionA = import_semver.default.clean(aParts[aParts.length - 1] || "") || "0.0.0";
962
+ const bParts = b.split("@");
963
+ const versionB = import_semver.default.clean(bParts[bParts.length - 1] || "") || "0.0.0";
867
964
  return import_semver.default.rcompare(versionA, versionB);
868
965
  });
869
966
  log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
@@ -1040,6 +1137,7 @@ var import_config3 = require("@releasekit/config");
1040
1137
  var import_semver2 = __toESM(require("semver"), 1);
1041
1138
 
1042
1139
  // src/git/tagVerification.ts
1140
+ init_commandExecutor();
1043
1141
  function verifyTag(tagName, cwd4) {
1044
1142
  if (!tagName || tagName.trim() === "") {
1045
1143
  return { exists: false, reachable: false, error: "Empty tag name" };
@@ -1565,6 +1663,19 @@ var PackageProcessor = class {
1565
1663
  revisionRange = "HEAD";
1566
1664
  }
1567
1665
  changelogEntries = extractChangelogEntriesFromCommits(pkgPath, revisionRange);
1666
+ const allPackageDirs = packages.map((p) => p.dir);
1667
+ const sharedPackageNames = ["config", "core", "@releasekit/config", "@releasekit/core"];
1668
+ const sharedPackageDirs = packages.filter((p) => sharedPackageNames.includes(p.packageJson.name)).map((p) => p.dir);
1669
+ const repoLevelEntries = extractRepoLevelChangelogEntries(
1670
+ pkgPath,
1671
+ revisionRange,
1672
+ allPackageDirs,
1673
+ sharedPackageDirs
1674
+ );
1675
+ if (repoLevelEntries.length > 0) {
1676
+ log(`Adding ${repoLevelEntries.length} repo-level commit(s) to ${name} changelog`, "debug");
1677
+ changelogEntries = [...repoLevelEntries, ...changelogEntries];
1678
+ }
1568
1679
  if (changelogEntries.length === 0) {
1569
1680
  changelogEntries = [
1570
1681
  {
@@ -1699,7 +1810,12 @@ var PackageProcessor = class {
1699
1810
  const packageNames = updatedPackagesInfo.map((p) => p.name).join(", ");
1700
1811
  const representativeVersion = updatedPackagesInfo[0]?.version || "multiple";
1701
1812
  let commitMessage = this.commitMessageTemplate || "chore(release): publish packages";
1702
- const placeholderRegex = /\$\{[^}]+\}/;
1813
+ const MAX_COMMIT_MSG_LENGTH = 1e4;
1814
+ if (commitMessage.length > MAX_COMMIT_MSG_LENGTH) {
1815
+ log("Commit message template too long, truncating", "warning");
1816
+ commitMessage = commitMessage.slice(0, MAX_COMMIT_MSG_LENGTH);
1817
+ }
1818
+ const placeholderRegex = /\$\{[^{}$]{1,1000}\}/;
1703
1819
  if (updatedPackagesInfo.length === 1 && placeholderRegex.test(commitMessage)) {
1704
1820
  const packageName = updatedPackagesInfo[0].name;
1705
1821
  commitMessage = formatCommitMessage(commitMessage, representativeVersion, packageName);
package/dist/cli.js CHANGED
@@ -5,8 +5,9 @@ import {
5
5
  loadConfig,
6
6
  log,
7
7
  printJsonOutput
8
- } from "./chunk-ORO3VYQ7.js";
8
+ } from "./chunk-GH75HGCN.js";
9
9
  import "./chunk-GQLJ7JQY.js";
10
+ import "./chunk-LMPZV35Z.js";
10
11
 
11
12
  // src/cli.ts
12
13
  import * as fs from "fs";
@@ -0,0 +1,8 @@
1
+ import {
2
+ execAsync,
3
+ execSync
4
+ } from "./chunk-LMPZV35Z.js";
5
+ export {
6
+ execAsync,
7
+ execSync
8
+ };
package/dist/index.cjs CHANGED
@@ -5,6 +5,9 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
8
11
  var __export = (target, all) => {
9
12
  for (var name in all)
10
13
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -27,6 +30,33 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
27
30
  ));
28
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
32
 
33
+ // src/git/commandExecutor.ts
34
+ var commandExecutor_exports = {};
35
+ __export(commandExecutor_exports, {
36
+ execAsync: () => execAsync,
37
+ execSync: () => execSync
38
+ });
39
+ var import_node_child_process, execAsync, execSync;
40
+ var init_commandExecutor = __esm({
41
+ "src/git/commandExecutor.ts"() {
42
+ "use strict";
43
+ import_node_child_process = require("child_process");
44
+ execAsync = (file, args, options) => {
45
+ const defaultOptions = { maxBuffer: 1024 * 1024 * 10, ...options };
46
+ return new Promise((resolve2, reject) => {
47
+ (0, import_node_child_process.execFile)(file, args, defaultOptions, (error, stdout, stderr) => {
48
+ if (error) {
49
+ reject(error);
50
+ } else {
51
+ resolve2({ stdout: stdout.toString(), stderr: stderr.toString() });
52
+ }
53
+ });
54
+ });
55
+ };
56
+ execSync = (file, args, options) => (0, import_node_child_process.execFileSync)(file, args, { maxBuffer: 1024 * 1024 * 10, ...options });
57
+ }
58
+ });
59
+
30
60
  // src/index.ts
31
61
  var index_exports = {};
32
62
  __export(index_exports, {
@@ -49,7 +79,7 @@ module.exports = __toCommonJS(index_exports);
49
79
  var import_config = require("@releasekit/config");
50
80
 
51
81
  // src/types.ts
52
- function toVersionConfig(config) {
82
+ function toVersionConfig(config, gitConfig) {
53
83
  if (!config) {
54
84
  return {
55
85
  tagTemplate: "v{version}",
@@ -58,7 +88,9 @@ function toVersionConfig(config) {
58
88
  sync: true,
59
89
  packages: [],
60
90
  updateInternalDependencies: "minor",
61
- versionPrefix: ""
91
+ versionPrefix: "",
92
+ baseBranch: gitConfig?.branch,
93
+ skipHooks: gitConfig?.skipHooks
62
94
  };
63
95
  }
64
96
  return {
@@ -77,19 +109,19 @@ function toVersionConfig(config) {
77
109
  releaseType: bp.releaseType
78
110
  })),
79
111
  defaultReleaseType: config.defaultReleaseType,
80
- skipHooks: config.skipHooks,
112
+ skipHooks: gitConfig?.skipHooks,
81
113
  mismatchStrategy: config.mismatchStrategy,
82
114
  versionPrefix: config.versionPrefix ?? "",
83
115
  prereleaseIdentifier: config.prereleaseIdentifier,
84
- baseBranch: config.baseBranch,
116
+ baseBranch: gitConfig?.branch,
85
117
  cargo: config.cargo
86
118
  };
87
119
  }
88
120
 
89
121
  // src/config.ts
90
122
  function loadConfig(options) {
91
- const versionConfig = (0, import_config.loadVersionConfig)(options);
92
- return toVersionConfig(versionConfig);
123
+ const fullConfig = (0, import_config.loadConfig)(options);
124
+ return toVersionConfig(fullConfig.version, fullConfig.git);
93
125
  }
94
126
 
95
127
  // src/core/versionCalculator.ts
@@ -100,24 +132,7 @@ var import_semver3 = __toESM(require("semver"), 1);
100
132
  // src/git/repository.ts
101
133
  var import_node_fs = require("fs");
102
134
  var import_node_path = require("path");
103
-
104
- // src/git/commandExecutor.ts
105
- var import_node_child_process = require("child_process");
106
- var execAsync = (file, args, options) => {
107
- const defaultOptions = { maxBuffer: 1024 * 1024 * 10, ...options };
108
- return new Promise((resolve2, reject) => {
109
- (0, import_node_child_process.execFile)(file, args, defaultOptions, (error, stdout, stderr) => {
110
- if (error) {
111
- reject(error);
112
- } else {
113
- resolve2({ stdout: stdout.toString(), stderr: stderr.toString() });
114
- }
115
- });
116
- });
117
- };
118
- var execSync = (file, args, options) => (0, import_node_child_process.execFileSync)(file, args, { maxBuffer: 1024 * 1024 * 10, ...options });
119
-
120
- // src/git/repository.ts
135
+ init_commandExecutor();
121
136
  function isGitRepository(directory) {
122
137
  const gitDir = (0, import_node_path.join)(directory, ".git");
123
138
  if (!(0, import_node_fs.existsSync)(gitDir)) {
@@ -272,14 +287,15 @@ To fix this:
272
287
  let result = template.replace(/\$\{version\}/g, version).replace(/\$\{packageName\}/g, packageName || "");
273
288
  if (additionalContext) {
274
289
  for (const [key, value] of Object.entries(additionalContext)) {
275
- const placeholder = `\${${key}}`;
276
- result = result.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"), value);
290
+ const placeholder = `${key ? `\${${key}}` : ""}`;
291
+ result = result.replace(new RegExp(escapeRegExp(placeholder), "g"), value);
277
292
  }
278
293
  }
279
294
  return result;
280
295
  }
281
296
 
282
297
  // src/git/tagsAndBranches.ts
298
+ init_commandExecutor();
283
299
  function getCommitsLength(pkgRoot, sinceTag) {
284
300
  try {
285
301
  let amount;
@@ -357,28 +373,37 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
357
373
  `Looking for tags for package ${packageName} with prefix ${versionPrefix || "none"}, packageSpecificTags: ${packageSpecificTags}`,
358
374
  "debug"
359
375
  );
360
- const allTags = await (0, import_git_semver_tags.getSemverTags)({
361
- tagPrefix: versionPrefix
362
- });
363
- log(`Retrieved ${allTags.length} tags: ${allTags.join(", ")}`, "debug");
376
+ let allTags = [];
377
+ try {
378
+ const { execSync: execSync2 } = await Promise.resolve().then(() => (init_commandExecutor(), commandExecutor_exports));
379
+ const tagsOutput = execSync2("git", ["tag", "-l"], { cwd: process.cwd() });
380
+ allTags = tagsOutput.toString().trim().split("\n").filter((tag) => tag.length > 0);
381
+ } catch (err) {
382
+ log(`Error getting tags: ${err instanceof Error ? err.message : String(err)}`, "error");
383
+ }
384
+ log(`Retrieved ${allTags.length} tags`, "debug");
364
385
  if (packageSpecificTags) {
365
386
  const packageTagPattern = escapeRegExp(tagTemplate).replace(/\\\$\\\{packageName\\\}/g, `(?:${escapedPackageName})`).replace(/\\\$\\\{prefix\\\}/g, `(?:${escapedPrefix})`).replace(/\\\$\\\{version\\\}/g, "(?:[0-9]+\\.[0-9]+\\.[0-9]+(?:-[a-zA-Z0-9.-]+)?)");
366
387
  log(`Using package tag pattern: ${packageTagPattern}`, "debug");
367
388
  const packageTagRegex = new RegExp(`^${packageTagPattern}$`);
368
389
  let packageTags = allTags.filter((tag) => packageTagRegex.test(tag));
390
+ log(`Found ${packageTags.length} matching tags for ${packageName}`, "debug");
369
391
  if (packageTags.length > 0) {
370
392
  const chronologicalFirst = packageTags[0];
393
+ void chronologicalFirst;
371
394
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
372
395
  let versionA = "";
373
396
  let versionB = "";
374
397
  if (a.includes("@")) {
375
- const afterAt = a.split("@")[1] || "";
398
+ const parts = a.split("@");
399
+ const afterAt = parts[parts.length - 1] || "";
376
400
  versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
377
401
  } else {
378
402
  versionA = a.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
379
403
  }
380
404
  if (b.includes("@")) {
381
- const afterAtB = b.split("@")[1] || "";
405
+ const parts = b.split("@");
406
+ const afterAtB = parts[parts.length - 1] || "";
382
407
  versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
383
408
  } else {
384
409
  versionB = b.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
@@ -389,12 +414,6 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
389
414
  });
390
415
  log(`Found ${packageTags.length} package tags using configured pattern`, "debug");
391
416
  log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
392
- if (sortedPackageTags2[0] !== chronologicalFirst) {
393
- log(
394
- `Package tag ordering differs: chronological first is ${chronologicalFirst}, semantic latest is ${sortedPackageTags2[0]}`,
395
- "debug"
396
- );
397
- }
398
417
  return sortedPackageTags2[0];
399
418
  }
400
419
  if (versionPrefix) {
@@ -402,9 +421,11 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
402
421
  packageTags = allTags.filter((tag) => pattern1.test(tag));
403
422
  if (packageTags.length > 0) {
404
423
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
405
- const afterAt = a.split("@")[1] || "";
424
+ const aParts = a.split("@");
425
+ const afterAt = aParts[aParts.length - 1] || "";
406
426
  const versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
407
- const afterAtB = b.split("@")[1] || "";
427
+ const bParts = b.split("@");
428
+ const afterAtB = bParts[bParts.length - 1] || "";
408
429
  const versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
409
430
  const cleanVersionA = import_semver.default.clean(versionA) || "0.0.0";
410
431
  const cleanVersionB = import_semver.default.clean(versionB) || "0.0.0";
@@ -420,8 +441,10 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
420
441
  packageTags = allTags.filter((tag) => pattern2.test(tag));
421
442
  if (packageTags.length > 0) {
422
443
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
423
- const versionA = import_semver.default.clean(a.split("@")[1] || "") || "0.0.0";
424
- const versionB = import_semver.default.clean(b.split("@")[1] || "") || "0.0.0";
444
+ const aParts = a.split("@");
445
+ const versionA = import_semver.default.clean(aParts[aParts.length - 1] || "") || "0.0.0";
446
+ const bParts = b.split("@");
447
+ const versionB = import_semver.default.clean(bParts[bParts.length - 1] || "") || "0.0.0";
425
448
  return import_semver.default.rcompare(versionA, versionB);
426
449
  });
427
450
  log(`Found ${packageTags.length} package tags using pattern: ${versionPrefix}packageName@...`, "debug");
@@ -441,8 +464,10 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
441
464
  return "";
442
465
  }
443
466
  const sortedPackageTags = [...packageTags].sort((a, b) => {
444
- const versionA = import_semver.default.clean(a.split("@")[1] || "") || "0.0.0";
445
- const versionB = import_semver.default.clean(b.split("@")[1] || "") || "0.0.0";
467
+ const aParts = a.split("@");
468
+ const versionA = import_semver.default.clean(aParts[aParts.length - 1] || "") || "0.0.0";
469
+ const bParts = b.split("@");
470
+ const versionB = import_semver.default.clean(bParts[bParts.length - 1] || "") || "0.0.0";
446
471
  return import_semver.default.rcompare(versionA, versionB);
447
472
  });
448
473
  log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
@@ -578,6 +603,7 @@ var import_config3 = require("@releasekit/config");
578
603
  var import_semver2 = __toESM(require("semver"), 1);
579
604
 
580
605
  // src/git/tagVerification.ts
606
+ init_commandExecutor();
581
607
  function verifyTag(tagName, cwd4) {
582
608
  if (!tagName || tagName.trim() === "") {
583
609
  return { exists: false, reachable: false, error: "Empty tag name" };
@@ -1141,15 +1167,86 @@ var import_node_fs6 = __toESM(require("fs"), 1);
1141
1167
  var path6 = __toESM(require("path"), 1);
1142
1168
 
1143
1169
  // src/changelog/commitParser.ts
1170
+ init_commandExecutor();
1144
1171
  var CONVENTIONAL_COMMIT_REGEX = /^(\w+)(?:\(([^)]+)\))?(!)?: (.+)(?:\n\n([\s\S]*))?/;
1145
1172
  var BREAKING_CHANGE_REGEX = /BREAKING CHANGE: ([\s\S]+?)(?:\n\n|$)/;
1173
+ function extractAllChangelogEntriesWithHash(projectDir, revisionRange) {
1174
+ try {
1175
+ const args = ["log", revisionRange, "--pretty=format:%H|||%B---COMMIT_DELIMITER---", "--no-merges"];
1176
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
1177
+ const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
1178
+ return commits.map((commit) => {
1179
+ const [hash, ...messageParts] = commit.split("|||");
1180
+ const message = messageParts.join("|||").trim();
1181
+ const entry = parseCommitMessage(message);
1182
+ if (entry && hash) {
1183
+ return { hash: hash.trim(), entry };
1184
+ }
1185
+ return null;
1186
+ }).filter((item) => item !== null);
1187
+ } catch (error) {
1188
+ const errorMessage = error instanceof Error ? error.message : String(error);
1189
+ log(`Error extracting all commits with hash: ${errorMessage}`, "error");
1190
+ return [];
1191
+ }
1192
+ }
1193
+ function commitTouchesAnyPackage(projectDir, commitHash, packageDirs, sharedPackageDirs = []) {
1194
+ try {
1195
+ const output = execSync("git", ["diff-tree", "--no-commit-id", "--name-only", "-r", commitHash], {
1196
+ cwd: projectDir,
1197
+ encoding: "utf8"
1198
+ }).toString().trim();
1199
+ if (!output) {
1200
+ return false;
1201
+ }
1202
+ const changedFiles = output.split("\n");
1203
+ return changedFiles.some((file) => {
1204
+ return packageDirs.some((pkgDir) => {
1205
+ if (sharedPackageDirs.some((sharedDir) => pkgDir.includes(sharedDir))) {
1206
+ return false;
1207
+ }
1208
+ const normalizedFile = file.replace(/\\/g, "/");
1209
+ const normalizedPkgDir = pkgDir.replace(/\\/g, "/").replace(/^\.\//, "");
1210
+ return normalizedFile.startsWith(normalizedPkgDir);
1211
+ });
1212
+ });
1213
+ } catch (error) {
1214
+ log(
1215
+ `Error checking if commit ${commitHash} touches packages: ${error instanceof Error ? error.message : String(error)}`,
1216
+ "debug"
1217
+ );
1218
+ return false;
1219
+ }
1220
+ }
1221
+ function extractRepoLevelChangelogEntries(projectDir, revisionRange, packageDirs, sharedPackageDirs = []) {
1222
+ try {
1223
+ const allCommits = extractAllChangelogEntriesWithHash(projectDir, revisionRange);
1224
+ const repoLevelCommits = allCommits.filter((commit) => {
1225
+ const touchesPackage = commitTouchesAnyPackage(projectDir, commit.hash, packageDirs, sharedPackageDirs);
1226
+ return !touchesPackage;
1227
+ });
1228
+ if (repoLevelCommits.length > 0) {
1229
+ log(
1230
+ `Found ${repoLevelCommits.length} repo-level commit(s) (including shared packages: ${sharedPackageDirs.join(", ")})`,
1231
+ "debug"
1232
+ );
1233
+ }
1234
+ return repoLevelCommits.map((c) => c.entry);
1235
+ } catch (error) {
1236
+ log(`Error extracting repo-level commits: ${error instanceof Error ? error.message : String(error)}`, "warning");
1237
+ return [];
1238
+ }
1239
+ }
1146
1240
  function extractChangelogEntriesFromCommits(projectDir, revisionRange) {
1241
+ return extractCommitsFromGitLog(projectDir, revisionRange, true);
1242
+ }
1243
+ function extractCommitsFromGitLog(projectDir, revisionRange, filterToPath) {
1147
1244
  try {
1148
- const output = execSync(
1149
- "git",
1150
- ["log", revisionRange, "--pretty=format:%B---COMMIT_DELIMITER---", "--no-merges", "--", "."],
1151
- { cwd: projectDir, encoding: "utf8" }
1152
- ).toString();
1245
+ const args = ["log", revisionRange, "--pretty=format:%B---COMMIT_DELIMITER---", "--no-merges"];
1246
+ if (filterToPath) {
1247
+ args.push("--", ".");
1248
+ }
1249
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
1153
1250
  const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
1154
1251
  return commits.map((commit) => parseCommitMessage(commit)).filter((entry) => entry !== null);
1155
1252
  } catch (error) {
@@ -1242,8 +1339,12 @@ function extractIssueIds(body) {
1242
1339
  return issueIds;
1243
1340
  }
1244
1341
 
1342
+ // src/core/versionStrategies.ts
1343
+ init_commandExecutor();
1344
+
1245
1345
  // src/git/commands.ts
1246
1346
  var import_node_process2 = require("process");
1347
+ init_commandExecutor();
1247
1348
  async function gitAdd(files) {
1248
1349
  return execAsync("git", ["add", ...files]);
1249
1350
  }
@@ -1560,6 +1661,19 @@ var PackageProcessor = class {
1560
1661
  revisionRange = "HEAD";
1561
1662
  }
1562
1663
  changelogEntries = extractChangelogEntriesFromCommits(pkgPath, revisionRange);
1664
+ const allPackageDirs = packages.map((p) => p.dir);
1665
+ const sharedPackageNames = ["config", "core", "@releasekit/config", "@releasekit/core"];
1666
+ const sharedPackageDirs = packages.filter((p) => sharedPackageNames.includes(p.packageJson.name)).map((p) => p.dir);
1667
+ const repoLevelEntries = extractRepoLevelChangelogEntries(
1668
+ pkgPath,
1669
+ revisionRange,
1670
+ allPackageDirs,
1671
+ sharedPackageDirs
1672
+ );
1673
+ if (repoLevelEntries.length > 0) {
1674
+ log(`Adding ${repoLevelEntries.length} repo-level commit(s) to ${name} changelog`, "debug");
1675
+ changelogEntries = [...repoLevelEntries, ...changelogEntries];
1676
+ }
1563
1677
  if (changelogEntries.length === 0) {
1564
1678
  changelogEntries = [
1565
1679
  {
@@ -1694,7 +1808,12 @@ var PackageProcessor = class {
1694
1808
  const packageNames = updatedPackagesInfo.map((p) => p.name).join(", ");
1695
1809
  const representativeVersion = updatedPackagesInfo[0]?.version || "multiple";
1696
1810
  let commitMessage = this.commitMessageTemplate || "chore(release): publish packages";
1697
- const placeholderRegex = /\$\{[^}]+\}/;
1811
+ const MAX_COMMIT_MSG_LENGTH = 1e4;
1812
+ if (commitMessage.length > MAX_COMMIT_MSG_LENGTH) {
1813
+ log("Commit message template too long, truncating", "warning");
1814
+ commitMessage = commitMessage.slice(0, MAX_COMMIT_MSG_LENGTH);
1815
+ }
1816
+ const placeholderRegex = /\$\{[^{}$]{1,1000}\}/;
1698
1817
  if (updatedPackagesInfo.length === 1 && placeholderRegex.test(commitMessage)) {
1699
1818
  const packageName = updatedPackagesInfo[0].name;
1700
1819
  commitMessage = formatCommitMessage(commitMessage, representativeVersion, packageName);
package/dist/index.js CHANGED
@@ -10,10 +10,11 @@ import {
10
10
  enableJsonOutput,
11
11
  getJsonData,
12
12
  loadConfig
13
- } from "./chunk-ORO3VYQ7.js";
13
+ } from "./chunk-GH75HGCN.js";
14
14
  import {
15
15
  BaseVersionError
16
16
  } from "./chunk-GQLJ7JQY.js";
17
+ import "./chunk-LMPZV35Z.js";
17
18
  export {
18
19
  BaseVersionError,
19
20
  PackageProcessor,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@releasekit/version",
3
- "version": "0.2.0-next.8",
3
+ "version": "0.2.0",
4
4
  "description": "Semantic versioning based on Git history and conventional commits",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -21,17 +21,12 @@
21
21
  "bin": {
22
22
  "releasekit-version": "dist/cli.js"
23
23
  },
24
- "scripts": {
25
- "build": "tsup src/index.ts src/cli.ts --format esm,cjs --dts",
26
- "dev": "tsup src/index.ts src/cli.ts --format esm,cjs --watch --dts",
27
- "clean": "rm -rf dist coverage .turbo",
28
- "test": "vitest run --dir test/unit",
29
- "test:unit": "vitest run --coverage --dir test/unit",
30
- "test:coverage": "vitest run --coverage --dir test/unit",
31
- "lint": "biome check .",
32
- "typecheck": "tsc --noEmit"
33
- },
34
- "keywords": ["version", "semver", "git", "package"],
24
+ "keywords": [
25
+ "version",
26
+ "semver",
27
+ "git",
28
+ "package"
29
+ ],
35
30
  "author": {
36
31
  "name": "Sam Maister",
37
32
  "email": "goosewobbler@protonmail.com"
@@ -42,39 +37,53 @@
42
37
  "directory": "packages/version"
43
38
  },
44
39
  "license": "MIT",
45
- "files": ["dist", "docs", "version.schema.json"],
40
+ "files": [
41
+ "dist",
42
+ "docs",
43
+ "version.schema.json"
44
+ ],
46
45
  "publishConfig": {
47
46
  "access": "public"
48
47
  },
49
48
  "dependencies": {
50
49
  "@manypkg/get-packages": "^3.1.0",
51
- "@releasekit/config": "workspace:*",
52
- "@releasekit/core": "workspace:*",
53
50
  "@types/micromatch": "^4.0.10",
54
- "chalk": "catalog:",
55
- "commander": "catalog:",
56
- "conventional-changelog-angular": "^8.1.0",
51
+ "chalk": "^5.6.2",
52
+ "commander": "^14.0.3",
53
+ "conventional-changelog-angular": "^8.3.0",
57
54
  "conventional-changelog-conventional-commits": "npm:conventional-changelog-conventionalcommits@^9.0.0",
58
- "conventional-changelog-conventionalcommits": "^9.1.0",
55
+ "conventional-changelog-conventionalcommits": "^9.3.0",
59
56
  "conventional-commits-filter": "^5.0.0",
60
57
  "conventional-recommended-bump": "^11.2.0",
61
- "figlet": "^1.10.0",
62
- "git-semver-tags": "^8.0.0",
58
+ "figlet": "^1.11.0",
59
+ "git-semver-tags": "^8.0.1",
63
60
  "micromatch": "^4.0.8",
64
- "semver": "catalog:",
65
- "smol-toml": "catalog:"
61
+ "semver": "^7.7.4",
62
+ "smol-toml": "^1.6.0",
63
+ "@releasekit/config": "0.1.0",
64
+ "@releasekit/core": "0.1.0"
66
65
  },
67
66
  "devDependencies": {
68
- "@biomejs/biome": "catalog:",
67
+ "@biomejs/biome": "^2.4.6",
69
68
  "@types/figlet": "^1.5.5",
70
- "@types/node": "catalog:",
71
- "@types/semver": "catalog:",
72
- "@vitest/coverage-v8": "catalog:",
73
- "tsup": "catalog:",
74
- "typescript": "catalog:",
75
- "vitest": "catalog:"
69
+ "@types/node": "^22.19.15",
70
+ "@types/semver": "^7.7.1",
71
+ "@vitest/coverage-v8": "^4.1.0",
72
+ "tsup": "^8.5.1",
73
+ "typescript": "^5.9.3",
74
+ "vitest": "^4.1.0"
76
75
  },
77
76
  "engines": {
78
77
  "node": ">=20"
78
+ },
79
+ "scripts": {
80
+ "build": "tsup src/index.ts src/cli.ts --format esm,cjs --dts",
81
+ "dev": "tsup src/index.ts src/cli.ts --format esm,cjs --watch --dts",
82
+ "clean": "rm -rf dist coverage .turbo",
83
+ "test": "vitest run --dir test/unit",
84
+ "test:unit": "vitest run --coverage --dir test/unit",
85
+ "test:coverage": "vitest run --coverage --dir test/unit",
86
+ "lint": "biome check .",
87
+ "typecheck": "tsc --noEmit"
79
88
  }
80
- }
89
+ }