@releasekit/version 0.2.0-next.1 → 0.2.0-next.10

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.
@@ -1,6 +1,10 @@
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
10
  import { loadVersionConfig } from "@releasekit/config";
@@ -202,24 +206,6 @@ import semver3 from "semver";
202
206
  // src/git/repository.ts
203
207
  import { existsSync, statSync } from "fs";
204
208
  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
209
  function isGitRepository(directory) {
224
210
  const gitDir = join(directory, ".git");
225
211
  if (!existsSync(gitDir)) {
@@ -374,28 +360,37 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
374
360
  `Looking for tags for package ${packageName} with prefix ${versionPrefix || "none"}, packageSpecificTags: ${packageSpecificTags}`,
375
361
  "debug"
376
362
  );
377
- const allTags = await getSemverTags({
378
- tagPrefix: versionPrefix
379
- });
380
- log(`Retrieved ${allTags.length} tags: ${allTags.join(", ")}`, "debug");
363
+ let allTags = [];
364
+ try {
365
+ const { execSync: execSync2 } = await import("./commandExecutor-E44ID5U4.js");
366
+ const tagsOutput = execSync2("git", ["tag", "-l"], { cwd: process.cwd() });
367
+ allTags = tagsOutput.toString().trim().split("\n").filter((tag) => tag.length > 0);
368
+ } catch (err) {
369
+ log(`Error getting tags: ${err instanceof Error ? err.message : String(err)}`, "error");
370
+ }
371
+ log(`Retrieved ${allTags.length} tags`, "debug");
381
372
  if (packageSpecificTags) {
382
373
  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
374
  log(`Using package tag pattern: ${packageTagPattern}`, "debug");
384
375
  const packageTagRegex = new RegExp(`^${packageTagPattern}$`);
385
376
  let packageTags = allTags.filter((tag) => packageTagRegex.test(tag));
377
+ log(`Found ${packageTags.length} matching tags for ${packageName}`, "debug");
386
378
  if (packageTags.length > 0) {
387
379
  const chronologicalFirst = packageTags[0];
380
+ void chronologicalFirst;
388
381
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
389
382
  let versionA = "";
390
383
  let versionB = "";
391
384
  if (a.includes("@")) {
392
- const afterAt = a.split("@")[1] || "";
385
+ const parts = a.split("@");
386
+ const afterAt = parts[parts.length - 1] || "";
393
387
  versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
394
388
  } else {
395
389
  versionA = a.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
396
390
  }
397
391
  if (b.includes("@")) {
398
- const afterAtB = b.split("@")[1] || "";
392
+ const parts = b.split("@");
393
+ const afterAtB = parts[parts.length - 1] || "";
399
394
  versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
400
395
  } else {
401
396
  versionB = b.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
@@ -406,12 +401,6 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
406
401
  });
407
402
  log(`Found ${packageTags.length} package tags using configured pattern`, "debug");
408
403
  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
404
  return sortedPackageTags2[0];
416
405
  }
417
406
  if (versionPrefix) {
@@ -419,9 +408,11 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
419
408
  packageTags = allTags.filter((tag) => pattern1.test(tag));
420
409
  if (packageTags.length > 0) {
421
410
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
422
- const afterAt = a.split("@")[1] || "";
411
+ const aParts = a.split("@");
412
+ const afterAt = aParts[aParts.length - 1] || "";
423
413
  const versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
424
- const afterAtB = b.split("@")[1] || "";
414
+ const bParts = b.split("@");
415
+ const afterAtB = bParts[bParts.length - 1] || "";
425
416
  const versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
426
417
  const cleanVersionA = semver.clean(versionA) || "0.0.0";
427
418
  const cleanVersionB = semver.clean(versionB) || "0.0.0";
@@ -437,8 +428,10 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
437
428
  packageTags = allTags.filter((tag) => pattern2.test(tag));
438
429
  if (packageTags.length > 0) {
439
430
  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";
431
+ const aParts = a.split("@");
432
+ const versionA = semver.clean(aParts[aParts.length - 1] || "") || "0.0.0";
433
+ const bParts = b.split("@");
434
+ const versionB = semver.clean(bParts[bParts.length - 1] || "") || "0.0.0";
442
435
  return semver.rcompare(versionA, versionB);
443
436
  });
444
437
  log(`Found ${packageTags.length} package tags using pattern: ${versionPrefix}packageName@...`, "debug");
@@ -458,8 +451,10 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
458
451
  return "";
459
452
  }
460
453
  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";
454
+ const aParts = a.split("@");
455
+ const versionA = semver.clean(aParts[aParts.length - 1] || "") || "0.0.0";
456
+ const bParts = b.split("@");
457
+ const versionB = semver.clean(bParts[bParts.length - 1] || "") || "0.0.0";
463
458
  return semver.rcompare(versionA, versionB);
464
459
  });
465
460
  log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
@@ -698,12 +693,17 @@ var VersionMismatchError = class extends Error {
698
693
  this.name = "VersionMismatchError";
699
694
  }
700
695
  };
701
- async function getBestVersionSource(tagName, packageVersion, cwd4, mismatchStrategy = "error") {
696
+ async function getBestVersionSource(tagName, packageVersion, cwd4, mismatchStrategy = "error", strictReachable = false) {
702
697
  if (!tagName?.trim()) {
703
698
  return packageVersion ? { source: "package", version: packageVersion, reason: "No git tag provided" } : { source: "initial", version: "0.1.0", reason: "No git tag or package version available" };
704
699
  }
705
700
  const verification = verifyTag(tagName, cwd4);
706
701
  if (!verification.exists || !verification.reachable) {
702
+ if (strictReachable) {
703
+ throw new Error(
704
+ `Git tag '${tagName}' is not reachable from the current commit. The tag exists but cannot be reached from HEAD, which usually means you're on a different branch or the tag is orphaned. To allow fallback to package version, set strictReachable to false in your configuration.`
705
+ );
706
+ }
707
707
  if (packageVersion) {
708
708
  log(
709
709
  `Git tag '${tagName}' unreachable (${verification.error}), using package version: ${packageVersion}`,
@@ -809,7 +809,8 @@ async function calculateVersion(config, options) {
809
809
  prereleaseIdentifier: configPrereleaseIdentifier,
810
810
  branchPattern,
811
811
  baseBranch,
812
- mismatchStrategy
812
+ mismatchStrategy,
813
+ strictReachable
813
814
  } = config;
814
815
  const {
815
816
  latestTag,
@@ -854,7 +855,13 @@ async function calculateVersion(config, options) {
854
855
  const packageDir = pkgPath || cwd();
855
856
  const manifestResult = getVersionFromManifests(packageDir);
856
857
  const packageVersion = manifestResult.manifestFound && manifestResult.version ? manifestResult.version : void 0;
857
- versionSource = await getBestVersionSource(latestTag, packageVersion, packageDir, mismatchStrategy);
858
+ versionSource = await getBestVersionSource(
859
+ latestTag,
860
+ packageVersion,
861
+ packageDir,
862
+ mismatchStrategy,
863
+ strictReachable
864
+ );
858
865
  log(`Using version source: ${versionSource.source} (${versionSource.reason})`, "info");
859
866
  }
860
867
  const specifiedType = type;
@@ -962,12 +969,56 @@ import { exit } from "process";
962
969
  // src/changelog/commitParser.ts
963
970
  var CONVENTIONAL_COMMIT_REGEX = /^(\w+)(?:\(([^)]+)\))?(!)?: (.+)(?:\n\n([\s\S]*))?/;
964
971
  var BREAKING_CHANGE_REGEX = /BREAKING CHANGE: ([\s\S]+?)(?:\n\n|$)/;
972
+ function extractChangelogEntriesWithHash(projectDir, revisionRange) {
973
+ try {
974
+ const args = ["log", revisionRange, "--pretty=format:%H|||%B---COMMIT_DELIMITER---", "--no-merges", "--", "."];
975
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
976
+ const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
977
+ return commits.map((commit) => {
978
+ const [hash, ...messageParts] = commit.split("|||");
979
+ const message = messageParts.join("|||").trim();
980
+ const entry = parseCommitMessage(message);
981
+ if (entry && hash) {
982
+ return { hash: hash.trim(), entry };
983
+ }
984
+ return null;
985
+ }).filter((item) => item !== null);
986
+ } catch (error) {
987
+ const errorMessage = error instanceof Error ? error.message : String(error);
988
+ log(`Error extracting commits with hash: ${errorMessage}`, "error");
989
+ return [];
990
+ }
991
+ }
992
+ function extractAllChangelogEntriesWithHash(projectDir, revisionRange) {
993
+ try {
994
+ const args = ["log", revisionRange, "--pretty=format:%H|||%B---COMMIT_DELIMITER---", "--no-merges"];
995
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
996
+ const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
997
+ return commits.map((commit) => {
998
+ const [hash, ...messageParts] = commit.split("|||");
999
+ const message = messageParts.join("|||").trim();
1000
+ const entry = parseCommitMessage(message);
1001
+ if (entry && hash) {
1002
+ return { hash: hash.trim(), entry };
1003
+ }
1004
+ return null;
1005
+ }).filter((item) => item !== null);
1006
+ } catch (error) {
1007
+ const errorMessage = error instanceof Error ? error.message : String(error);
1008
+ log(`Error extracting all commits with hash: ${errorMessage}`, "error");
1009
+ return [];
1010
+ }
1011
+ }
965
1012
  function extractChangelogEntriesFromCommits(projectDir, revisionRange) {
1013
+ return extractCommitsFromGitLog(projectDir, revisionRange, true);
1014
+ }
1015
+ function extractCommitsFromGitLog(projectDir, revisionRange, filterToPath) {
966
1016
  try {
967
- const output = execSync("git", ["log", revisionRange, "--pretty=format:%B---COMMIT_DELIMITER---", "--no-merges"], {
968
- cwd: projectDir,
969
- encoding: "utf8"
970
- }).toString();
1017
+ const args = ["log", revisionRange, "--pretty=format:%B---COMMIT_DELIMITER---", "--no-merges"];
1018
+ if (filterToPath) {
1019
+ args.push("--", ".");
1020
+ }
1021
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
971
1022
  const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
972
1023
  return commits.map((commit) => parseCommitMessage(commit)).filter((entry) => entry !== null);
973
1024
  } catch (error) {
@@ -1395,6 +1446,11 @@ var PackageProcessor = class {
1395
1446
  if (verification.exists && verification.reachable) {
1396
1447
  revisionRange = `${latestTag}..HEAD`;
1397
1448
  } else {
1449
+ if (this.config.strictReachable) {
1450
+ throw new Error(
1451
+ `Cannot generate changelog: tag '${latestTag}' is not reachable from the current commit. When strictReachable is enabled, all tags must be reachable. To allow fallback to all commits, set strictReachable to false.`
1452
+ );
1453
+ }
1398
1454
  log(`Tag ${latestTag} is unreachable (${verification.error}), using all commits for changelog`, "debug");
1399
1455
  revisionRange = "HEAD";
1400
1456
  }
@@ -1402,6 +1458,14 @@ var PackageProcessor = class {
1402
1458
  revisionRange = "HEAD";
1403
1459
  }
1404
1460
  changelogEntries = extractChangelogEntriesFromCommits(pkgPath, revisionRange);
1461
+ const allCommitsWithHash = extractAllChangelogEntriesWithHash(pkgPath, revisionRange);
1462
+ const packageCommitsWithHash = extractChangelogEntriesWithHash(pkgPath, revisionRange);
1463
+ const packageCommitHashes = new Set(packageCommitsWithHash.map((c) => c.hash));
1464
+ const globalCommits = allCommitsWithHash.filter((c) => !packageCommitHashes.has(c.hash)).map((c) => c.entry);
1465
+ if (globalCommits.length > 0) {
1466
+ log(`Adding ${globalCommits.length} global commit(s) to ${name} changelog`, "debug");
1467
+ changelogEntries = [...globalCommits, ...changelogEntries];
1468
+ }
1405
1469
  if (changelogEntries.length === 0) {
1406
1470
  changelogEntries = [
1407
1471
  {
@@ -1712,6 +1776,11 @@ function createSyncStrategy(config) {
1712
1776
  });
1713
1777
  revisionRange = `${latestTag}..HEAD`;
1714
1778
  } catch {
1779
+ if (config.strictReachable) {
1780
+ throw new Error(
1781
+ `Cannot generate changelog: tag '${latestTag}' is not reachable from the current commit. When strictReachable is enabled, all tags must be reachable. To allow fallback to all commits, set strictReachable to false.`
1782
+ );
1783
+ }
1715
1784
  log(`Tag ${latestTag} doesn't exist, using all commits for changelog`, "debug");
1716
1785
  revisionRange = "HEAD";
1717
1786
  }
@@ -1831,6 +1900,11 @@ function createSingleStrategy(config) {
1831
1900
  });
1832
1901
  revisionRange = `${latestTag}..HEAD`;
1833
1902
  } catch {
1903
+ if (config.strictReachable) {
1904
+ throw new Error(
1905
+ `Cannot generate changelog: tag '${latestTag}' is not reachable from the current commit. When strictReachable is enabled, all tags must be reachable. To allow fallback to all commits, set strictReachable to false.`
1906
+ );
1907
+ }
1834
1908
  log(`Tag ${latestTag} doesn't exist, using all commits for changelog`, "debug");
1835
1909
  revisionRange = "HEAD";
1836
1910
  }
@@ -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, {
@@ -370,31 +397,60 @@ function matchesPackageNamePattern(packageName, pattern) {
370
397
  var import_node_fs6 = __toESM(require("fs"), 1);
371
398
  var path6 = __toESM(require("path"), 1);
372
399
 
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
400
  // src/changelog/commitParser.ts
401
+ init_commandExecutor();
390
402
  var CONVENTIONAL_COMMIT_REGEX = /^(\w+)(?:\(([^)]+)\))?(!)?: (.+)(?:\n\n([\s\S]*))?/;
391
403
  var BREAKING_CHANGE_REGEX = /BREAKING CHANGE: ([\s\S]+?)(?:\n\n|$)/;
404
+ function extractChangelogEntriesWithHash(projectDir, revisionRange) {
405
+ try {
406
+ const args = ["log", revisionRange, "--pretty=format:%H|||%B---COMMIT_DELIMITER---", "--no-merges", "--", "."];
407
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
408
+ const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
409
+ return commits.map((commit) => {
410
+ const [hash, ...messageParts] = commit.split("|||");
411
+ const message = messageParts.join("|||").trim();
412
+ const entry = parseCommitMessage(message);
413
+ if (entry && hash) {
414
+ return { hash: hash.trim(), entry };
415
+ }
416
+ return null;
417
+ }).filter((item) => item !== null);
418
+ } catch (error) {
419
+ const errorMessage = error instanceof Error ? error.message : String(error);
420
+ log(`Error extracting commits with hash: ${errorMessage}`, "error");
421
+ return [];
422
+ }
423
+ }
424
+ function extractAllChangelogEntriesWithHash(projectDir, revisionRange) {
425
+ try {
426
+ const args = ["log", revisionRange, "--pretty=format:%H|||%B---COMMIT_DELIMITER---", "--no-merges"];
427
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
428
+ const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
429
+ return commits.map((commit) => {
430
+ const [hash, ...messageParts] = commit.split("|||");
431
+ const message = messageParts.join("|||").trim();
432
+ const entry = parseCommitMessage(message);
433
+ if (entry && hash) {
434
+ return { hash: hash.trim(), entry };
435
+ }
436
+ return null;
437
+ }).filter((item) => item !== null);
438
+ } catch (error) {
439
+ const errorMessage = error instanceof Error ? error.message : String(error);
440
+ log(`Error extracting all commits with hash: ${errorMessage}`, "error");
441
+ return [];
442
+ }
443
+ }
392
444
  function extractChangelogEntriesFromCommits(projectDir, revisionRange) {
445
+ return extractCommitsFromGitLog(projectDir, revisionRange, true);
446
+ }
447
+ function extractCommitsFromGitLog(projectDir, revisionRange, filterToPath) {
393
448
  try {
394
- const output = execSync("git", ["log", revisionRange, "--pretty=format:%B---COMMIT_DELIMITER---", "--no-merges"], {
395
- cwd: projectDir,
396
- encoding: "utf8"
397
- }).toString();
449
+ const args = ["log", revisionRange, "--pretty=format:%B---COMMIT_DELIMITER---", "--no-merges"];
450
+ if (filterToPath) {
451
+ args.push("--", ".");
452
+ }
453
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
398
454
  const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
399
455
  return commits.map((commit) => parseCommitMessage(commit)).filter((entry) => entry !== null);
400
456
  } catch (error) {
@@ -489,13 +545,16 @@ function extractIssueIds(body) {
489
545
 
490
546
  // src/core/versionStrategies.ts
491
547
  init_baseError();
548
+ init_commandExecutor();
492
549
 
493
550
  // src/git/commands.ts
494
551
  var import_node_process = require("process");
552
+ init_commandExecutor();
495
553
 
496
554
  // src/git/repository.ts
497
555
  var import_node_fs = require("fs");
498
556
  var import_node_path2 = require("path");
557
+ init_commandExecutor();
499
558
  function isGitRepository(directory) {
500
559
  const gitDir = (0, import_node_path2.join)(directory, ".git");
501
560
  if (!(0, import_node_fs.existsSync)(gitDir)) {
@@ -700,6 +759,7 @@ To fix this:
700
759
  }
701
760
 
702
761
  // src/git/tagsAndBranches.ts
762
+ init_commandExecutor();
703
763
  function getCommitsLength(pkgRoot, sinceTag) {
704
764
  try {
705
765
  let amount;
@@ -777,28 +837,37 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
777
837
  `Looking for tags for package ${packageName} with prefix ${versionPrefix || "none"}, packageSpecificTags: ${packageSpecificTags}`,
778
838
  "debug"
779
839
  );
780
- const allTags = await (0, import_git_semver_tags.getSemverTags)({
781
- tagPrefix: versionPrefix
782
- });
783
- log(`Retrieved ${allTags.length} tags: ${allTags.join(", ")}`, "debug");
840
+ let allTags = [];
841
+ try {
842
+ const { execSync: execSync2 } = await Promise.resolve().then(() => (init_commandExecutor(), commandExecutor_exports));
843
+ const tagsOutput = execSync2("git", ["tag", "-l"], { cwd: process.cwd() });
844
+ allTags = tagsOutput.toString().trim().split("\n").filter((tag) => tag.length > 0);
845
+ } catch (err) {
846
+ log(`Error getting tags: ${err instanceof Error ? err.message : String(err)}`, "error");
847
+ }
848
+ log(`Retrieved ${allTags.length} tags`, "debug");
784
849
  if (packageSpecificTags) {
785
850
  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.-]+)?)");
786
851
  log(`Using package tag pattern: ${packageTagPattern}`, "debug");
787
852
  const packageTagRegex = new RegExp(`^${packageTagPattern}$`);
788
853
  let packageTags = allTags.filter((tag) => packageTagRegex.test(tag));
854
+ log(`Found ${packageTags.length} matching tags for ${packageName}`, "debug");
789
855
  if (packageTags.length > 0) {
790
856
  const chronologicalFirst = packageTags[0];
857
+ void chronologicalFirst;
791
858
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
792
859
  let versionA = "";
793
860
  let versionB = "";
794
861
  if (a.includes("@")) {
795
- const afterAt = a.split("@")[1] || "";
862
+ const parts = a.split("@");
863
+ const afterAt = parts[parts.length - 1] || "";
796
864
  versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
797
865
  } else {
798
866
  versionA = a.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
799
867
  }
800
868
  if (b.includes("@")) {
801
- const afterAtB = b.split("@")[1] || "";
869
+ const parts = b.split("@");
870
+ const afterAtB = parts[parts.length - 1] || "";
802
871
  versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
803
872
  } else {
804
873
  versionB = b.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
@@ -809,12 +878,6 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
809
878
  });
810
879
  log(`Found ${packageTags.length} package tags using configured pattern`, "debug");
811
880
  log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
812
- if (sortedPackageTags2[0] !== chronologicalFirst) {
813
- log(
814
- `Package tag ordering differs: chronological first is ${chronologicalFirst}, semantic latest is ${sortedPackageTags2[0]}`,
815
- "debug"
816
- );
817
- }
818
881
  return sortedPackageTags2[0];
819
882
  }
820
883
  if (versionPrefix) {
@@ -822,9 +885,11 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
822
885
  packageTags = allTags.filter((tag) => pattern1.test(tag));
823
886
  if (packageTags.length > 0) {
824
887
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
825
- const afterAt = a.split("@")[1] || "";
888
+ const aParts = a.split("@");
889
+ const afterAt = aParts[aParts.length - 1] || "";
826
890
  const versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
827
- const afterAtB = b.split("@")[1] || "";
891
+ const bParts = b.split("@");
892
+ const afterAtB = bParts[bParts.length - 1] || "";
828
893
  const versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
829
894
  const cleanVersionA = import_semver.default.clean(versionA) || "0.0.0";
830
895
  const cleanVersionB = import_semver.default.clean(versionB) || "0.0.0";
@@ -840,8 +905,10 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
840
905
  packageTags = allTags.filter((tag) => pattern2.test(tag));
841
906
  if (packageTags.length > 0) {
842
907
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
843
- const versionA = import_semver.default.clean(a.split("@")[1] || "") || "0.0.0";
844
- const versionB = import_semver.default.clean(b.split("@")[1] || "") || "0.0.0";
908
+ const aParts = a.split("@");
909
+ const versionA = import_semver.default.clean(aParts[aParts.length - 1] || "") || "0.0.0";
910
+ const bParts = b.split("@");
911
+ const versionB = import_semver.default.clean(bParts[bParts.length - 1] || "") || "0.0.0";
845
912
  return import_semver.default.rcompare(versionA, versionB);
846
913
  });
847
914
  log(`Found ${packageTags.length} package tags using pattern: ${versionPrefix}packageName@...`, "debug");
@@ -861,8 +928,10 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
861
928
  return "";
862
929
  }
863
930
  const sortedPackageTags = [...packageTags].sort((a, b) => {
864
- const versionA = import_semver.default.clean(a.split("@")[1] || "") || "0.0.0";
865
- const versionB = import_semver.default.clean(b.split("@")[1] || "") || "0.0.0";
931
+ const aParts = a.split("@");
932
+ const versionA = import_semver.default.clean(aParts[aParts.length - 1] || "") || "0.0.0";
933
+ const bParts = b.split("@");
934
+ const versionB = import_semver.default.clean(bParts[bParts.length - 1] || "") || "0.0.0";
866
935
  return import_semver.default.rcompare(versionA, versionB);
867
936
  });
868
937
  log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
@@ -1039,6 +1108,7 @@ var import_config3 = require("@releasekit/config");
1039
1108
  var import_semver2 = __toESM(require("semver"), 1);
1040
1109
 
1041
1110
  // src/git/tagVerification.ts
1111
+ init_commandExecutor();
1042
1112
  function verifyTag(tagName, cwd4) {
1043
1113
  if (!tagName || tagName.trim() === "") {
1044
1114
  return { exists: false, reachable: false, error: "Empty tag name" };
@@ -1142,12 +1212,17 @@ var VersionMismatchError = class extends Error {
1142
1212
  this.name = "VersionMismatchError";
1143
1213
  }
1144
1214
  };
1145
- async function getBestVersionSource(tagName, packageVersion, cwd4, mismatchStrategy = "error") {
1215
+ async function getBestVersionSource(tagName, packageVersion, cwd4, mismatchStrategy = "error", strictReachable = false) {
1146
1216
  if (!tagName?.trim()) {
1147
1217
  return packageVersion ? { source: "package", version: packageVersion, reason: "No git tag provided" } : { source: "initial", version: "0.1.0", reason: "No git tag or package version available" };
1148
1218
  }
1149
1219
  const verification = verifyTag(tagName, cwd4);
1150
1220
  if (!verification.exists || !verification.reachable) {
1221
+ if (strictReachable) {
1222
+ throw new Error(
1223
+ `Git tag '${tagName}' is not reachable from the current commit. The tag exists but cannot be reached from HEAD, which usually means you're on a different branch or the tag is orphaned. To allow fallback to package version, set strictReachable to false in your configuration.`
1224
+ );
1225
+ }
1151
1226
  if (packageVersion) {
1152
1227
  log(
1153
1228
  `Git tag '${tagName}' unreachable (${verification.error}), using package version: ${packageVersion}`,
@@ -1253,7 +1328,8 @@ async function calculateVersion(config, options) {
1253
1328
  prereleaseIdentifier: configPrereleaseIdentifier,
1254
1329
  branchPattern,
1255
1330
  baseBranch,
1256
- mismatchStrategy
1331
+ mismatchStrategy,
1332
+ strictReachable
1257
1333
  } = config;
1258
1334
  const {
1259
1335
  latestTag,
@@ -1298,7 +1374,13 @@ async function calculateVersion(config, options) {
1298
1374
  const packageDir = pkgPath || (0, import_node_process2.cwd)();
1299
1375
  const manifestResult = getVersionFromManifests(packageDir);
1300
1376
  const packageVersion = manifestResult.manifestFound && manifestResult.version ? manifestResult.version : void 0;
1301
- versionSource = await getBestVersionSource(latestTag, packageVersion, packageDir, mismatchStrategy);
1377
+ versionSource = await getBestVersionSource(
1378
+ latestTag,
1379
+ packageVersion,
1380
+ packageDir,
1381
+ mismatchStrategy,
1382
+ strictReachable
1383
+ );
1302
1384
  log(`Using version source: ${versionSource.source} (${versionSource.reason})`, "info");
1303
1385
  }
1304
1386
  const specifiedType = type;
@@ -1540,6 +1622,11 @@ var PackageProcessor = class {
1540
1622
  if (verification.exists && verification.reachable) {
1541
1623
  revisionRange = `${latestTag}..HEAD`;
1542
1624
  } else {
1625
+ if (this.config.strictReachable) {
1626
+ throw new Error(
1627
+ `Cannot generate changelog: tag '${latestTag}' is not reachable from the current commit. When strictReachable is enabled, all tags must be reachable. To allow fallback to all commits, set strictReachable to false.`
1628
+ );
1629
+ }
1543
1630
  log(`Tag ${latestTag} is unreachable (${verification.error}), using all commits for changelog`, "debug");
1544
1631
  revisionRange = "HEAD";
1545
1632
  }
@@ -1547,6 +1634,14 @@ var PackageProcessor = class {
1547
1634
  revisionRange = "HEAD";
1548
1635
  }
1549
1636
  changelogEntries = extractChangelogEntriesFromCommits(pkgPath, revisionRange);
1637
+ const allCommitsWithHash = extractAllChangelogEntriesWithHash(pkgPath, revisionRange);
1638
+ const packageCommitsWithHash = extractChangelogEntriesWithHash(pkgPath, revisionRange);
1639
+ const packageCommitHashes = new Set(packageCommitsWithHash.map((c) => c.hash));
1640
+ const globalCommits = allCommitsWithHash.filter((c) => !packageCommitHashes.has(c.hash)).map((c) => c.entry);
1641
+ if (globalCommits.length > 0) {
1642
+ log(`Adding ${globalCommits.length} global commit(s) to ${name} changelog`, "debug");
1643
+ changelogEntries = [...globalCommits, ...changelogEntries];
1644
+ }
1550
1645
  if (changelogEntries.length === 0) {
1551
1646
  changelogEntries = [
1552
1647
  {
@@ -1855,6 +1950,11 @@ function createSyncStrategy(config) {
1855
1950
  });
1856
1951
  revisionRange = `${latestTag}..HEAD`;
1857
1952
  } catch {
1953
+ if (config.strictReachable) {
1954
+ throw new Error(
1955
+ `Cannot generate changelog: tag '${latestTag}' is not reachable from the current commit. When strictReachable is enabled, all tags must be reachable. To allow fallback to all commits, set strictReachable to false.`
1956
+ );
1957
+ }
1858
1958
  log(`Tag ${latestTag} doesn't exist, using all commits for changelog`, "debug");
1859
1959
  revisionRange = "HEAD";
1860
1960
  }
@@ -1974,6 +2074,11 @@ function createSingleStrategy(config) {
1974
2074
  });
1975
2075
  revisionRange = `${latestTag}..HEAD`;
1976
2076
  } catch {
2077
+ if (config.strictReachable) {
2078
+ throw new Error(
2079
+ `Cannot generate changelog: tag '${latestTag}' is not reachable from the current commit. When strictReachable is enabled, all tags must be reachable. To allow fallback to all commits, set strictReachable to false.`
2080
+ );
2081
+ }
1977
2082
  log(`Tag ${latestTag} doesn't exist, using all commits for changelog`, "debug");
1978
2083
  revisionRange = "HEAD";
1979
2084
  }
package/dist/cli.js CHANGED
@@ -5,8 +5,9 @@ import {
5
5
  loadConfig,
6
6
  log,
7
7
  printJsonOutput
8
- } from "./chunk-3JG7UWIT.js";
8
+ } from "./chunk-DXDB5P7S.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, {
@@ -100,24 +130,7 @@ var import_semver3 = __toESM(require("semver"), 1);
100
130
  // src/git/repository.ts
101
131
  var import_node_fs = require("fs");
102
132
  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
133
+ init_commandExecutor();
121
134
  function isGitRepository(directory) {
122
135
  const gitDir = (0, import_node_path.join)(directory, ".git");
123
136
  if (!(0, import_node_fs.existsSync)(gitDir)) {
@@ -280,6 +293,7 @@ To fix this:
280
293
  }
281
294
 
282
295
  // src/git/tagsAndBranches.ts
296
+ init_commandExecutor();
283
297
  function getCommitsLength(pkgRoot, sinceTag) {
284
298
  try {
285
299
  let amount;
@@ -357,28 +371,37 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
357
371
  `Looking for tags for package ${packageName} with prefix ${versionPrefix || "none"}, packageSpecificTags: ${packageSpecificTags}`,
358
372
  "debug"
359
373
  );
360
- const allTags = await (0, import_git_semver_tags.getSemverTags)({
361
- tagPrefix: versionPrefix
362
- });
363
- log(`Retrieved ${allTags.length} tags: ${allTags.join(", ")}`, "debug");
374
+ let allTags = [];
375
+ try {
376
+ const { execSync: execSync2 } = await Promise.resolve().then(() => (init_commandExecutor(), commandExecutor_exports));
377
+ const tagsOutput = execSync2("git", ["tag", "-l"], { cwd: process.cwd() });
378
+ allTags = tagsOutput.toString().trim().split("\n").filter((tag) => tag.length > 0);
379
+ } catch (err) {
380
+ log(`Error getting tags: ${err instanceof Error ? err.message : String(err)}`, "error");
381
+ }
382
+ log(`Retrieved ${allTags.length} tags`, "debug");
364
383
  if (packageSpecificTags) {
365
384
  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
385
  log(`Using package tag pattern: ${packageTagPattern}`, "debug");
367
386
  const packageTagRegex = new RegExp(`^${packageTagPattern}$`);
368
387
  let packageTags = allTags.filter((tag) => packageTagRegex.test(tag));
388
+ log(`Found ${packageTags.length} matching tags for ${packageName}`, "debug");
369
389
  if (packageTags.length > 0) {
370
390
  const chronologicalFirst = packageTags[0];
391
+ void chronologicalFirst;
371
392
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
372
393
  let versionA = "";
373
394
  let versionB = "";
374
395
  if (a.includes("@")) {
375
- const afterAt = a.split("@")[1] || "";
396
+ const parts = a.split("@");
397
+ const afterAt = parts[parts.length - 1] || "";
376
398
  versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
377
399
  } else {
378
400
  versionA = a.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
379
401
  }
380
402
  if (b.includes("@")) {
381
- const afterAtB = b.split("@")[1] || "";
403
+ const parts = b.split("@");
404
+ const afterAtB = parts[parts.length - 1] || "";
382
405
  versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
383
406
  } else {
384
407
  versionB = b.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
@@ -389,12 +412,6 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
389
412
  });
390
413
  log(`Found ${packageTags.length} package tags using configured pattern`, "debug");
391
414
  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
415
  return sortedPackageTags2[0];
399
416
  }
400
417
  if (versionPrefix) {
@@ -402,9 +419,11 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
402
419
  packageTags = allTags.filter((tag) => pattern1.test(tag));
403
420
  if (packageTags.length > 0) {
404
421
  const sortedPackageTags2 = [...packageTags].sort((a, b) => {
405
- const afterAt = a.split("@")[1] || "";
422
+ const aParts = a.split("@");
423
+ const afterAt = aParts[aParts.length - 1] || "";
406
424
  const versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
407
- const afterAtB = b.split("@")[1] || "";
425
+ const bParts = b.split("@");
426
+ const afterAtB = bParts[bParts.length - 1] || "";
408
427
  const versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
409
428
  const cleanVersionA = import_semver.default.clean(versionA) || "0.0.0";
410
429
  const cleanVersionB = import_semver.default.clean(versionB) || "0.0.0";
@@ -420,8 +439,10 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
420
439
  packageTags = allTags.filter((tag) => pattern2.test(tag));
421
440
  if (packageTags.length > 0) {
422
441
  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";
442
+ const aParts = a.split("@");
443
+ const versionA = import_semver.default.clean(aParts[aParts.length - 1] || "") || "0.0.0";
444
+ const bParts = b.split("@");
445
+ const versionB = import_semver.default.clean(bParts[bParts.length - 1] || "") || "0.0.0";
425
446
  return import_semver.default.rcompare(versionA, versionB);
426
447
  });
427
448
  log(`Found ${packageTags.length} package tags using pattern: ${versionPrefix}packageName@...`, "debug");
@@ -441,8 +462,10 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
441
462
  return "";
442
463
  }
443
464
  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";
465
+ const aParts = a.split("@");
466
+ const versionA = import_semver.default.clean(aParts[aParts.length - 1] || "") || "0.0.0";
467
+ const bParts = b.split("@");
468
+ const versionB = import_semver.default.clean(bParts[bParts.length - 1] || "") || "0.0.0";
446
469
  return import_semver.default.rcompare(versionA, versionB);
447
470
  });
448
471
  log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
@@ -578,6 +601,7 @@ var import_config3 = require("@releasekit/config");
578
601
  var import_semver2 = __toESM(require("semver"), 1);
579
602
 
580
603
  // src/git/tagVerification.ts
604
+ init_commandExecutor();
581
605
  function verifyTag(tagName, cwd4) {
582
606
  if (!tagName || tagName.trim() === "") {
583
607
  return { exists: false, reachable: false, error: "Empty tag name" };
@@ -681,12 +705,17 @@ var VersionMismatchError = class extends Error {
681
705
  this.name = "VersionMismatchError";
682
706
  }
683
707
  };
684
- async function getBestVersionSource(tagName, packageVersion, cwd4, mismatchStrategy = "error") {
708
+ async function getBestVersionSource(tagName, packageVersion, cwd4, mismatchStrategy = "error", strictReachable = false) {
685
709
  if (!tagName?.trim()) {
686
710
  return packageVersion ? { source: "package", version: packageVersion, reason: "No git tag provided" } : { source: "initial", version: "0.1.0", reason: "No git tag or package version available" };
687
711
  }
688
712
  const verification = verifyTag(tagName, cwd4);
689
713
  if (!verification.exists || !verification.reachable) {
714
+ if (strictReachable) {
715
+ throw new Error(
716
+ `Git tag '${tagName}' is not reachable from the current commit. The tag exists but cannot be reached from HEAD, which usually means you're on a different branch or the tag is orphaned. To allow fallback to package version, set strictReachable to false in your configuration.`
717
+ );
718
+ }
690
719
  if (packageVersion) {
691
720
  log(
692
721
  `Git tag '${tagName}' unreachable (${verification.error}), using package version: ${packageVersion}`,
@@ -792,7 +821,8 @@ async function calculateVersion(config, options) {
792
821
  prereleaseIdentifier: configPrereleaseIdentifier,
793
822
  branchPattern,
794
823
  baseBranch,
795
- mismatchStrategy
824
+ mismatchStrategy,
825
+ strictReachable
796
826
  } = config;
797
827
  const {
798
828
  latestTag,
@@ -837,7 +867,13 @@ async function calculateVersion(config, options) {
837
867
  const packageDir = pkgPath || (0, import_node_process.cwd)();
838
868
  const manifestResult = getVersionFromManifests(packageDir);
839
869
  const packageVersion = manifestResult.manifestFound && manifestResult.version ? manifestResult.version : void 0;
840
- versionSource = await getBestVersionSource(latestTag, packageVersion, packageDir, mismatchStrategy);
870
+ versionSource = await getBestVersionSource(
871
+ latestTag,
872
+ packageVersion,
873
+ packageDir,
874
+ mismatchStrategy,
875
+ strictReachable
876
+ );
841
877
  log(`Using version source: ${versionSource.source} (${versionSource.reason})`, "info");
842
878
  }
843
879
  const specifiedType = type;
@@ -1129,14 +1165,59 @@ var import_node_fs6 = __toESM(require("fs"), 1);
1129
1165
  var path6 = __toESM(require("path"), 1);
1130
1166
 
1131
1167
  // src/changelog/commitParser.ts
1168
+ init_commandExecutor();
1132
1169
  var CONVENTIONAL_COMMIT_REGEX = /^(\w+)(?:\(([^)]+)\))?(!)?: (.+)(?:\n\n([\s\S]*))?/;
1133
1170
  var BREAKING_CHANGE_REGEX = /BREAKING CHANGE: ([\s\S]+?)(?:\n\n|$)/;
1171
+ function extractChangelogEntriesWithHash(projectDir, revisionRange) {
1172
+ try {
1173
+ const args = ["log", revisionRange, "--pretty=format:%H|||%B---COMMIT_DELIMITER---", "--no-merges", "--", "."];
1174
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
1175
+ const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
1176
+ return commits.map((commit) => {
1177
+ const [hash, ...messageParts] = commit.split("|||");
1178
+ const message = messageParts.join("|||").trim();
1179
+ const entry = parseCommitMessage(message);
1180
+ if (entry && hash) {
1181
+ return { hash: hash.trim(), entry };
1182
+ }
1183
+ return null;
1184
+ }).filter((item) => item !== null);
1185
+ } catch (error) {
1186
+ const errorMessage = error instanceof Error ? error.message : String(error);
1187
+ log(`Error extracting commits with hash: ${errorMessage}`, "error");
1188
+ return [];
1189
+ }
1190
+ }
1191
+ function extractAllChangelogEntriesWithHash(projectDir, revisionRange) {
1192
+ try {
1193
+ const args = ["log", revisionRange, "--pretty=format:%H|||%B---COMMIT_DELIMITER---", "--no-merges"];
1194
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
1195
+ const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
1196
+ return commits.map((commit) => {
1197
+ const [hash, ...messageParts] = commit.split("|||");
1198
+ const message = messageParts.join("|||").trim();
1199
+ const entry = parseCommitMessage(message);
1200
+ if (entry && hash) {
1201
+ return { hash: hash.trim(), entry };
1202
+ }
1203
+ return null;
1204
+ }).filter((item) => item !== null);
1205
+ } catch (error) {
1206
+ const errorMessage = error instanceof Error ? error.message : String(error);
1207
+ log(`Error extracting all commits with hash: ${errorMessage}`, "error");
1208
+ return [];
1209
+ }
1210
+ }
1134
1211
  function extractChangelogEntriesFromCommits(projectDir, revisionRange) {
1212
+ return extractCommitsFromGitLog(projectDir, revisionRange, true);
1213
+ }
1214
+ function extractCommitsFromGitLog(projectDir, revisionRange, filterToPath) {
1135
1215
  try {
1136
- const output = execSync("git", ["log", revisionRange, "--pretty=format:%B---COMMIT_DELIMITER---", "--no-merges"], {
1137
- cwd: projectDir,
1138
- encoding: "utf8"
1139
- }).toString();
1216
+ const args = ["log", revisionRange, "--pretty=format:%B---COMMIT_DELIMITER---", "--no-merges"];
1217
+ if (filterToPath) {
1218
+ args.push("--", ".");
1219
+ }
1220
+ const output = execSync("git", args, { cwd: projectDir, encoding: "utf8" }).toString();
1140
1221
  const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
1141
1222
  return commits.map((commit) => parseCommitMessage(commit)).filter((entry) => entry !== null);
1142
1223
  } catch (error) {
@@ -1229,8 +1310,12 @@ function extractIssueIds(body) {
1229
1310
  return issueIds;
1230
1311
  }
1231
1312
 
1313
+ // src/core/versionStrategies.ts
1314
+ init_commandExecutor();
1315
+
1232
1316
  // src/git/commands.ts
1233
1317
  var import_node_process2 = require("process");
1318
+ init_commandExecutor();
1234
1319
  async function gitAdd(files) {
1235
1320
  return execAsync("git", ["add", ...files]);
1236
1321
  }
@@ -1535,6 +1620,11 @@ var PackageProcessor = class {
1535
1620
  if (verification.exists && verification.reachable) {
1536
1621
  revisionRange = `${latestTag}..HEAD`;
1537
1622
  } else {
1623
+ if (this.config.strictReachable) {
1624
+ throw new Error(
1625
+ `Cannot generate changelog: tag '${latestTag}' is not reachable from the current commit. When strictReachable is enabled, all tags must be reachable. To allow fallback to all commits, set strictReachable to false.`
1626
+ );
1627
+ }
1538
1628
  log(`Tag ${latestTag} is unreachable (${verification.error}), using all commits for changelog`, "debug");
1539
1629
  revisionRange = "HEAD";
1540
1630
  }
@@ -1542,6 +1632,14 @@ var PackageProcessor = class {
1542
1632
  revisionRange = "HEAD";
1543
1633
  }
1544
1634
  changelogEntries = extractChangelogEntriesFromCommits(pkgPath, revisionRange);
1635
+ const allCommitsWithHash = extractAllChangelogEntriesWithHash(pkgPath, revisionRange);
1636
+ const packageCommitsWithHash = extractChangelogEntriesWithHash(pkgPath, revisionRange);
1637
+ const packageCommitHashes = new Set(packageCommitsWithHash.map((c) => c.hash));
1638
+ const globalCommits = allCommitsWithHash.filter((c) => !packageCommitHashes.has(c.hash)).map((c) => c.entry);
1639
+ if (globalCommits.length > 0) {
1640
+ log(`Adding ${globalCommits.length} global commit(s) to ${name} changelog`, "debug");
1641
+ changelogEntries = [...globalCommits, ...changelogEntries];
1642
+ }
1545
1643
  if (changelogEntries.length === 0) {
1546
1644
  changelogEntries = [
1547
1645
  {
@@ -1850,6 +1948,11 @@ function createSyncStrategy(config) {
1850
1948
  });
1851
1949
  revisionRange = `${latestTag}..HEAD`;
1852
1950
  } catch {
1951
+ if (config.strictReachable) {
1952
+ throw new Error(
1953
+ `Cannot generate changelog: tag '${latestTag}' is not reachable from the current commit. When strictReachable is enabled, all tags must be reachable. To allow fallback to all commits, set strictReachable to false.`
1954
+ );
1955
+ }
1853
1956
  log(`Tag ${latestTag} doesn't exist, using all commits for changelog`, "debug");
1854
1957
  revisionRange = "HEAD";
1855
1958
  }
@@ -1969,6 +2072,11 @@ function createSingleStrategy(config) {
1969
2072
  });
1970
2073
  revisionRange = `${latestTag}..HEAD`;
1971
2074
  } catch {
2075
+ if (config.strictReachable) {
2076
+ throw new Error(
2077
+ `Cannot generate changelog: tag '${latestTag}' is not reachable from the current commit. When strictReachable is enabled, all tags must be reachable. To allow fallback to all commits, set strictReachable to false.`
2078
+ );
2079
+ }
1972
2080
  log(`Tag ${latestTag} doesn't exist, using all commits for changelog`, "debug");
1973
2081
  revisionRange = "HEAD";
1974
2082
  }
package/dist/index.d.cts CHANGED
@@ -11,6 +11,7 @@ interface VersionConfigBase {
11
11
  baseBranch?: string;
12
12
  path?: string;
13
13
  name?: string;
14
+ strictReachable?: boolean;
14
15
  }
15
16
  interface Config extends VersionConfigBase {
16
17
  tagTemplate: string;
@@ -30,6 +31,7 @@ interface Config extends VersionConfigBase {
30
31
  latestTag?: string;
31
32
  isPrerelease?: boolean;
32
33
  mismatchStrategy?: 'error' | 'warn' | 'ignore' | 'prefer-package' | 'prefer-git';
34
+ strictReachable?: boolean;
33
35
  cargo?: {
34
36
  enabled?: boolean;
35
37
  paths?: string[];
package/dist/index.d.ts CHANGED
@@ -11,6 +11,7 @@ interface VersionConfigBase {
11
11
  baseBranch?: string;
12
12
  path?: string;
13
13
  name?: string;
14
+ strictReachable?: boolean;
14
15
  }
15
16
  interface Config extends VersionConfigBase {
16
17
  tagTemplate: string;
@@ -30,6 +31,7 @@ interface Config extends VersionConfigBase {
30
31
  latestTag?: string;
31
32
  isPrerelease?: boolean;
32
33
  mismatchStrategy?: 'error' | 'warn' | 'ignore' | 'prefer-package' | 'prefer-git';
34
+ strictReachable?: boolean;
33
35
  cargo?: {
34
36
  enabled?: boolean;
35
37
  paths?: string[];
package/dist/index.js CHANGED
@@ -10,10 +10,11 @@ import {
10
10
  enableJsonOutput,
11
11
  getJsonData,
12
12
  loadConfig
13
- } from "./chunk-3JG7UWIT.js";
13
+ } from "./chunk-DXDB5P7S.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.1",
3
+ "version": "0.2.0-next.10",
4
4
  "description": "Semantic versioning based on Git history and conventional commits",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",