package-versioner 0.7.2 → 0.8.1

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/README.md CHANGED
@@ -58,6 +58,9 @@ npx package-versioner -t @scope/package-a,@scope/package-b
58
58
  # Perform a dry run: calculates version, logs actions, but makes no file changes or Git commits/tags
59
59
  npx package-versioner --dry-run
60
60
 
61
+ # Only use reachable tags (Git-semantic mode, no fallback to unreachable tags)
62
+ npx package-versioner --strict-reachable
63
+
61
64
  # Output results as JSON (useful for CI/CD scripts)
62
65
  npx package-versioner --json
63
66
 
@@ -67,6 +70,26 @@ npx package-versioner --dry-run --json
67
70
 
68
71
  **Note on Targeting:** Using the `-t` flag creates package-specific tags (e.g., `@scope/package-a@1.2.0`) but *not* a global tag (like `v1.2.0`). If needed, create the global tag manually in your CI/CD script after this command.
69
72
 
73
+ ### Git Tag Reachability
74
+
75
+ By default, `package-versioner` intelligently handles Git tag reachability to provide the best user experience:
76
+
77
+ - **Default behavior**: Uses reachable tags when available, but falls back to the latest repository tag if needed (common in feature branches)
78
+ - **Strict mode (`--strict-reachable`)**: Only uses tags reachable from the current commit, following strict Git semantics
79
+
80
+ This is particularly useful when working on feature branches that have diverged from the main branch where newer tags exist. The tool will automatically detect the Git context and provide helpful guidance:
81
+
82
+ ```bash
83
+ # On a feature branch with unreachable tags
84
+ npx package-versioner --dry-run
85
+ # Output: "No tags reachable from current branch 'feature-x'. Using latest repository tag v1.2.3 as version base."
86
+ # Tip: Consider 'git merge main' or 'git rebase main' to include tag history in your branch.
87
+
88
+ # Force strict Git semantics
89
+ npx package-versioner --dry-run --strict-reachable
90
+ # Output: Uses only reachable tags, may result in "No reachable tags found"
91
+ ```
92
+
70
93
  ## JSON Output
71
94
 
72
95
  When using the `--json` flag, normal console output is suppressed and the tool outputs a structured JSON object that includes information about the versioning operation.
@@ -103,6 +126,7 @@ Customize behaviour by creating a `version.config.json` file in your project roo
103
126
  "commitMessage": "chore: release ${packageName}@${version} [skip ci]",
104
127
  "updateChangelog": true,
105
128
  "changelogFormat": "keep-a-changelog",
129
+ "strictReachable": false,
106
130
  "synced": true,
107
131
  "skip": [
108
132
  "docs",
@@ -126,6 +150,7 @@ Customize behaviour by creating a `version.config.json` file in your project roo
126
150
  - `commitMessage`: Template for commit messages (default: "chore(release): ${version}")
127
151
  - `updateChangelog`: Whether to automatically update changelogs (default: true)
128
152
  - `changelogFormat`: Format for changelogs - "keep-a-changelog" or "angular" (default: "keep-a-changelog")
153
+ - `strictReachable`: Only use reachable tags, no fallback to unreachable tags (default: false)
129
154
  - `cargo`: Options for Rust projects:
130
155
  - `enabled`: Whether to handle Cargo.toml files (default: true)
131
156
  - `paths`: Directories to search for Cargo.toml files (optional)
package/dist/index.cjs CHANGED
@@ -799,6 +799,7 @@ async function createGitCommitAndTag(files, nextTag, commitMessage, skipHooks, d
799
799
 
800
800
  // src/git/tagsAndBranches.ts
801
801
  var import_git_semver_tags = require("git-semver-tags");
802
+ var import_semver = __toESM(require("semver"), 1);
802
803
 
803
804
  // src/utils/formatting.ts
804
805
  function escapeRegExp(string) {
@@ -854,10 +855,29 @@ function getCommitsLength(pkgRoot) {
854
855
  return 0;
855
856
  }
856
857
  }
857
- async function getLatestTag() {
858
+ async function getLatestTag(versionPrefix) {
858
859
  try {
859
- const tags = await (0, import_git_semver_tags.getSemverTags)({});
860
- return tags[0] || "";
860
+ const tags = await (0, import_git_semver_tags.getSemverTags)({
861
+ tagPrefix: versionPrefix
862
+ });
863
+ if (tags.length === 0) {
864
+ return "";
865
+ }
866
+ const chronologicalLatest = tags[0];
867
+ const sortedTags = [...tags].sort((a, b) => {
868
+ const versionA = import_semver.default.clean(a) || "0.0.0";
869
+ const versionB = import_semver.default.clean(b) || "0.0.0";
870
+ return import_semver.default.rcompare(versionA, versionB);
871
+ });
872
+ const semanticLatest = sortedTags[0];
873
+ if (semanticLatest !== chronologicalLatest) {
874
+ log(
875
+ `Tag ordering differs: chronological latest is ${chronologicalLatest}, semantic latest is ${semanticLatest}`,
876
+ "debug"
877
+ );
878
+ log(`Using semantic latest (${semanticLatest}) to handle out-of-order tag creation`, "info");
879
+ }
880
+ return semanticLatest;
861
881
  } catch (error) {
862
882
  const errorMessage = error instanceof Error ? error.message : String(error);
863
883
  log(`Failed to get latest tag: ${errorMessage}`, "error");
@@ -902,37 +922,82 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
902
922
  const packageTagRegex = new RegExp(`^${packageTagPattern}$`);
903
923
  let packageTags = allTags.filter((tag) => packageTagRegex.test(tag));
904
924
  if (packageTags.length > 0) {
925
+ const chronologicalFirst = packageTags[0];
926
+ const sortedPackageTags2 = [...packageTags].sort((a, b) => {
927
+ let versionA = "";
928
+ let versionB = "";
929
+ if (a.includes("@")) {
930
+ const afterAt = a.split("@")[1] || "";
931
+ versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
932
+ } else {
933
+ versionA = a.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
934
+ }
935
+ if (b.includes("@")) {
936
+ const afterAtB = b.split("@")[1] || "";
937
+ versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
938
+ } else {
939
+ versionB = b.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
940
+ }
941
+ const cleanVersionA = import_semver.default.clean(versionA) || "0.0.0";
942
+ const cleanVersionB = import_semver.default.clean(versionB) || "0.0.0";
943
+ return import_semver.default.rcompare(cleanVersionA, cleanVersionB);
944
+ });
905
945
  log(`Found ${packageTags.length} package tags using configured pattern`, "debug");
906
- log(`Using tag: ${packageTags[0]}`, "debug");
907
- return packageTags[0];
946
+ log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
947
+ if (sortedPackageTags2[0] !== chronologicalFirst) {
948
+ log(
949
+ `Package tag ordering differs: chronological first is ${chronologicalFirst}, semantic latest is ${sortedPackageTags2[0]}`,
950
+ "debug"
951
+ );
952
+ }
953
+ return sortedPackageTags2[0];
908
954
  }
909
955
  if (versionPrefix) {
910
956
  const pattern1 = new RegExp(`^${escapedPackageName}@${escapeRegExp(versionPrefix)}`);
911
957
  packageTags = allTags.filter((tag) => pattern1.test(tag));
912
958
  if (packageTags.length > 0) {
959
+ const sortedPackageTags2 = [...packageTags].sort((a, b) => {
960
+ const afterAt = a.split("@")[1] || "";
961
+ const versionA = afterAt.replace(
962
+ new RegExp(`^${escapeRegExp(versionPrefix || "")}`),
963
+ ""
964
+ );
965
+ const afterAtB = b.split("@")[1] || "";
966
+ const versionB = afterAtB.replace(
967
+ new RegExp(`^${escapeRegExp(versionPrefix || "")}`),
968
+ ""
969
+ );
970
+ const cleanVersionA = import_semver.default.clean(versionA) || "0.0.0";
971
+ const cleanVersionB = import_semver.default.clean(versionB) || "0.0.0";
972
+ return import_semver.default.rcompare(cleanVersionA, cleanVersionB);
973
+ });
913
974
  log(
914
975
  `Found ${packageTags.length} package tags using pattern: packageName@${versionPrefix}...`,
915
976
  "debug"
916
977
  );
917
- log(`Using tag: ${packageTags[0]}`, "debug");
918
- return packageTags[0];
978
+ log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
979
+ return sortedPackageTags2[0];
919
980
  }
920
981
  }
921
982
  if (versionPrefix) {
922
983
  const pattern2 = new RegExp(`^${escapeRegExp(versionPrefix)}${escapedPackageName}@`);
923
984
  packageTags = allTags.filter((tag) => pattern2.test(tag));
924
985
  if (packageTags.length > 0) {
986
+ const sortedPackageTags2 = [...packageTags].sort((a, b) => {
987
+ const versionA = import_semver.default.clean(a.split("@")[1] || "") || "0.0.0";
988
+ const versionB = import_semver.default.clean(b.split("@")[1] || "") || "0.0.0";
989
+ return import_semver.default.rcompare(versionA, versionB);
990
+ });
925
991
  log(
926
992
  `Found ${packageTags.length} package tags using pattern: ${versionPrefix}packageName@...`,
927
993
  "debug"
928
994
  );
929
- log(`Using tag: ${packageTags[0]}`, "debug");
930
- return packageTags[0];
995
+ log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
996
+ return sortedPackageTags2[0];
931
997
  }
932
998
  }
933
999
  const pattern3 = new RegExp(`^${escapedPackageName}@`);
934
1000
  packageTags = allTags.filter((tag) => pattern3.test(tag));
935
- log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
936
1001
  if (packageTags.length === 0) {
937
1002
  log("No matching tags found for pattern: packageName@version", "debug");
938
1003
  if (allTags.length > 0) {
@@ -940,10 +1005,16 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
940
1005
  } else {
941
1006
  log("No tags available in the repository", "debug");
942
1007
  }
943
- } else {
944
- log(`Using tag: ${packageTags[0]}`, "debug");
1008
+ return "";
945
1009
  }
946
- return packageTags[0] || "";
1010
+ const sortedPackageTags = [...packageTags].sort((a, b) => {
1011
+ const versionA = import_semver.default.clean(a.split("@")[1] || "") || "0.0.0";
1012
+ const versionB = import_semver.default.clean(b.split("@")[1] || "") || "0.0.0";
1013
+ return import_semver.default.rcompare(versionA, versionB);
1014
+ });
1015
+ log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
1016
+ log(`Using semantically latest tag: ${sortedPackageTags[0]}`, "debug");
1017
+ return sortedPackageTags[0];
947
1018
  }
948
1019
  log(`Package-specific tags disabled for ${packageName}, falling back to global tags`, "debug");
949
1020
  return "";
@@ -1049,6 +1120,7 @@ function updatePackageVersion(packagePath, version) {
1049
1120
  }
1050
1121
 
1051
1122
  // src/package/packageProcessor.ts
1123
+ var import_node_child_process4 = require("child_process");
1052
1124
  var fs8 = __toESM(require("fs"), 1);
1053
1125
  var import_node_path6 = __toESM(require("path"), 1);
1054
1126
  var import_node_process4 = require("process");
@@ -1381,7 +1453,7 @@ function capitalizeFirstLetter(input) {
1381
1453
  // src/core/versionCalculator.ts
1382
1454
  var import_node_process3 = require("process");
1383
1455
  var import_conventional_recommended_bump = require("conventional-recommended-bump");
1384
- var import_semver2 = __toESM(require("semver"), 1);
1456
+ var import_semver3 = __toESM(require("semver"), 1);
1385
1457
 
1386
1458
  // src/utils/manifestHelpers.ts
1387
1459
  var import_node_fs5 = __toESM(require("fs"), 1);
@@ -1442,7 +1514,7 @@ function throwIfNoManifestsFound(packageDir) {
1442
1514
 
1443
1515
  // src/utils/versionUtils.ts
1444
1516
  var import_node_fs6 = __toESM(require("fs"), 1);
1445
- var import_semver = __toESM(require("semver"), 1);
1517
+ var import_semver2 = __toESM(require("semver"), 1);
1446
1518
  var TOML2 = __toESM(require("smol-toml"), 1);
1447
1519
  var STANDARD_BUMP_TYPES = ["major", "minor", "patch"];
1448
1520
  function normalizePrereleaseIdentifier(prereleaseIdentifier, config) {
@@ -1455,27 +1527,27 @@ function normalizePrereleaseIdentifier(prereleaseIdentifier, config) {
1455
1527
  return void 0;
1456
1528
  }
1457
1529
  function bumpVersion(currentVersion, bumpType, prereleaseIdentifier) {
1458
- if (prereleaseIdentifier && STANDARD_BUMP_TYPES.includes(bumpType) && !import_semver.default.prerelease(currentVersion)) {
1530
+ if (prereleaseIdentifier && STANDARD_BUMP_TYPES.includes(bumpType) && !import_semver2.default.prerelease(currentVersion)) {
1459
1531
  const preBumpType = `pre${bumpType}`;
1460
1532
  log(
1461
1533
  `Creating prerelease version with identifier '${prereleaseIdentifier}' using ${preBumpType}`,
1462
1534
  "debug"
1463
1535
  );
1464
- return import_semver.default.inc(currentVersion, preBumpType, prereleaseIdentifier) || "";
1536
+ return import_semver2.default.inc(currentVersion, preBumpType, prereleaseIdentifier) || "";
1465
1537
  }
1466
- if (import_semver.default.prerelease(currentVersion) && STANDARD_BUMP_TYPES.includes(bumpType)) {
1467
- const parsed = import_semver.default.parse(currentVersion);
1538
+ if (import_semver2.default.prerelease(currentVersion) && STANDARD_BUMP_TYPES.includes(bumpType)) {
1539
+ const parsed = import_semver2.default.parse(currentVersion);
1468
1540
  if (!parsed) {
1469
- return import_semver.default.inc(currentVersion, bumpType) || "";
1541
+ return import_semver2.default.inc(currentVersion, bumpType) || "";
1470
1542
  }
1471
1543
  if (bumpType === "major" && parsed.minor === 0 && parsed.patch === 0 || bumpType === "minor" && parsed.patch === 0 || bumpType === "patch") {
1472
1544
  log(`Cleaning prerelease identifier from ${currentVersion} for ${bumpType} bump`, "debug");
1473
1545
  return `${parsed.major}.${parsed.minor}.${parsed.patch}`;
1474
1546
  }
1475
1547
  log(`Standard increment for ${currentVersion} with ${bumpType} bump`, "debug");
1476
- return import_semver.default.inc(currentVersion, bumpType) || "";
1548
+ return import_semver2.default.inc(currentVersion, bumpType) || "";
1477
1549
  }
1478
- return import_semver.default.inc(currentVersion, bumpType, prereleaseIdentifier) || "";
1550
+ return import_semver2.default.inc(currentVersion, bumpType, prereleaseIdentifier) || "";
1479
1551
  }
1480
1552
 
1481
1553
  // src/core/versionCalculator.ts
@@ -1517,10 +1589,10 @@ async function calculateVersion(config, options) {
1517
1589
  const packageDir = pkgPath || (0, import_node_process3.cwd)();
1518
1590
  const manifestResult = getVersionFromManifests(packageDir);
1519
1591
  if (manifestResult.manifestFound && manifestResult.version) {
1520
- const cleanedTag = import_semver2.default.clean(latestTag) || latestTag;
1521
- const tagVersion = import_semver2.default.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1592
+ const cleanedTag = import_semver3.default.clean(latestTag) || latestTag;
1593
+ const tagVersion = import_semver3.default.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1522
1594
  const packageVersion = manifestResult.version;
1523
- if (import_semver2.default.gt(packageVersion, tagVersion)) {
1595
+ if (import_semver3.default.gt(packageVersion, tagVersion)) {
1524
1596
  log(
1525
1597
  `Warning: Version mismatch detected!
1526
1598
  \u2022 ${manifestResult.manifestType} version: ${packageVersion}
@@ -1540,7 +1612,7 @@ To fix this mismatch:
1540
1612
  \u2022 Or use package version as base by ensuring tags are up to date`,
1541
1613
  "warning"
1542
1614
  );
1543
- } else if (import_semver2.default.gt(tagVersion, packageVersion)) {
1615
+ } else if (import_semver3.default.gt(tagVersion, packageVersion)) {
1544
1616
  log(
1545
1617
  `Warning: Version mismatch detected!
1546
1618
  \u2022 ${manifestResult.manifestType} version: ${packageVersion}
@@ -1576,9 +1648,9 @@ To fix this mismatch:
1576
1648
  initialVersion
1577
1649
  );
1578
1650
  }
1579
- const cleanedTag = import_semver2.default.clean(latestTag) || latestTag;
1580
- const currentVersion = import_semver2.default.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1581
- if (STANDARD_BUMP_TYPES.includes(specifiedType) && (import_semver2.default.prerelease(currentVersion) || normalizedPrereleaseId)) {
1651
+ const cleanedTag = import_semver3.default.clean(latestTag) || latestTag;
1652
+ const currentVersion = import_semver3.default.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1653
+ if (STANDARD_BUMP_TYPES.includes(specifiedType) && (import_semver3.default.prerelease(currentVersion) || normalizedPrereleaseId)) {
1582
1654
  log(
1583
1655
  normalizedPrereleaseId ? `Creating prerelease version with identifier '${normalizedPrereleaseId}' using ${specifiedType}` : `Cleaning prerelease identifier from ${currentVersion} for ${specifiedType} bump`,
1584
1656
  "debug"
@@ -1616,8 +1688,8 @@ To fix this mismatch:
1616
1688
  initialVersion
1617
1689
  );
1618
1690
  }
1619
- const cleanedTag = import_semver2.default.clean(latestTag) || latestTag;
1620
- const currentVersion = import_semver2.default.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1691
+ const cleanedTag = import_semver3.default.clean(latestTag) || latestTag;
1692
+ const currentVersion = import_semver3.default.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1621
1693
  log(`Applying ${branchVersionType} bump based on branch pattern`, "debug");
1622
1694
  return bumpVersion(currentVersion, branchVersionType, normalizedPrereleaseId);
1623
1695
  }
@@ -1655,7 +1727,7 @@ To fix this mismatch:
1655
1727
  );
1656
1728
  return "";
1657
1729
  }
1658
- const currentVersion = import_semver2.default.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1730
+ const currentVersion = import_semver3.default.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1659
1731
  return bumpVersion(currentVersion, releaseTypeFromCommits, normalizedPrereleaseId);
1660
1732
  } catch (error) {
1661
1733
  log(`Failed to calculate version for ${name || "project"}`, "error");
@@ -1700,7 +1772,7 @@ function calculateNextVersion(version, manifestType, name, releaseType, prerelea
1700
1772
  `No tags found for ${name || "package"}, using ${manifestType} version: ${version} as base`,
1701
1773
  "info"
1702
1774
  );
1703
- if (STANDARD_BUMP_TYPES.includes(releaseType) && (import_semver2.default.prerelease(version) || prereleaseIdentifier)) {
1775
+ if (STANDARD_BUMP_TYPES.includes(releaseType) && (import_semver3.default.prerelease(version) || prereleaseIdentifier)) {
1704
1776
  log(
1705
1777
  prereleaseIdentifier ? `Creating prerelease version with identifier '${prereleaseIdentifier}' using ${releaseType}` : `Cleaning prerelease identifier from ${version} for ${releaseType} bump`,
1706
1778
  "debug"
@@ -1861,7 +1933,24 @@ var PackageProcessor = class {
1861
1933
  if (this.fullConfig.updateChangelog !== false) {
1862
1934
  let changelogEntries = [];
1863
1935
  try {
1864
- changelogEntries = extractChangelogEntriesFromCommits(pkgPath, latestTag);
1936
+ let revisionRange = latestTag;
1937
+ if (latestTag) {
1938
+ try {
1939
+ (0, import_node_child_process4.execSync)(`git rev-parse --verify "${latestTag}"`, {
1940
+ cwd: pkgPath,
1941
+ stdio: "ignore"
1942
+ });
1943
+ } catch {
1944
+ log(
1945
+ `Tag ${latestTag} doesn't exist, using recent commits from HEAD for changelog`,
1946
+ "debug"
1947
+ );
1948
+ revisionRange = "HEAD~10..HEAD";
1949
+ }
1950
+ } else {
1951
+ revisionRange = "HEAD~10..HEAD";
1952
+ }
1953
+ changelogEntries = extractChangelogEntriesFromCommits(pkgPath, revisionRange);
1865
1954
  if (changelogEntries.length === 0) {
1866
1955
  changelogEntries = [
1867
1956
  {
package/dist/index.js CHANGED
@@ -766,6 +766,7 @@ async function createGitCommitAndTag(files, nextTag, commitMessage, skipHooks, d
766
766
 
767
767
  // src/git/tagsAndBranches.ts
768
768
  import { getSemverTags } from "git-semver-tags";
769
+ import semver from "semver";
769
770
 
770
771
  // src/utils/formatting.ts
771
772
  function escapeRegExp(string) {
@@ -821,10 +822,29 @@ function getCommitsLength(pkgRoot) {
821
822
  return 0;
822
823
  }
823
824
  }
824
- async function getLatestTag() {
825
+ async function getLatestTag(versionPrefix) {
825
826
  try {
826
- const tags = await getSemverTags({});
827
- return tags[0] || "";
827
+ const tags = await getSemverTags({
828
+ tagPrefix: versionPrefix
829
+ });
830
+ if (tags.length === 0) {
831
+ return "";
832
+ }
833
+ const chronologicalLatest = tags[0];
834
+ const sortedTags = [...tags].sort((a, b) => {
835
+ const versionA = semver.clean(a) || "0.0.0";
836
+ const versionB = semver.clean(b) || "0.0.0";
837
+ return semver.rcompare(versionA, versionB);
838
+ });
839
+ const semanticLatest = sortedTags[0];
840
+ if (semanticLatest !== chronologicalLatest) {
841
+ log(
842
+ `Tag ordering differs: chronological latest is ${chronologicalLatest}, semantic latest is ${semanticLatest}`,
843
+ "debug"
844
+ );
845
+ log(`Using semantic latest (${semanticLatest}) to handle out-of-order tag creation`, "info");
846
+ }
847
+ return semanticLatest;
828
848
  } catch (error) {
829
849
  const errorMessage = error instanceof Error ? error.message : String(error);
830
850
  log(`Failed to get latest tag: ${errorMessage}`, "error");
@@ -869,37 +889,82 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
869
889
  const packageTagRegex = new RegExp(`^${packageTagPattern}$`);
870
890
  let packageTags = allTags.filter((tag) => packageTagRegex.test(tag));
871
891
  if (packageTags.length > 0) {
892
+ const chronologicalFirst = packageTags[0];
893
+ const sortedPackageTags2 = [...packageTags].sort((a, b) => {
894
+ let versionA = "";
895
+ let versionB = "";
896
+ if (a.includes("@")) {
897
+ const afterAt = a.split("@")[1] || "";
898
+ versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
899
+ } else {
900
+ versionA = a.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
901
+ }
902
+ if (b.includes("@")) {
903
+ const afterAtB = b.split("@")[1] || "";
904
+ versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
905
+ } else {
906
+ versionB = b.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
907
+ }
908
+ const cleanVersionA = semver.clean(versionA) || "0.0.0";
909
+ const cleanVersionB = semver.clean(versionB) || "0.0.0";
910
+ return semver.rcompare(cleanVersionA, cleanVersionB);
911
+ });
872
912
  log(`Found ${packageTags.length} package tags using configured pattern`, "debug");
873
- log(`Using tag: ${packageTags[0]}`, "debug");
874
- return packageTags[0];
913
+ log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
914
+ if (sortedPackageTags2[0] !== chronologicalFirst) {
915
+ log(
916
+ `Package tag ordering differs: chronological first is ${chronologicalFirst}, semantic latest is ${sortedPackageTags2[0]}`,
917
+ "debug"
918
+ );
919
+ }
920
+ return sortedPackageTags2[0];
875
921
  }
876
922
  if (versionPrefix) {
877
923
  const pattern1 = new RegExp(`^${escapedPackageName}@${escapeRegExp(versionPrefix)}`);
878
924
  packageTags = allTags.filter((tag) => pattern1.test(tag));
879
925
  if (packageTags.length > 0) {
926
+ const sortedPackageTags2 = [...packageTags].sort((a, b) => {
927
+ const afterAt = a.split("@")[1] || "";
928
+ const versionA = afterAt.replace(
929
+ new RegExp(`^${escapeRegExp(versionPrefix || "")}`),
930
+ ""
931
+ );
932
+ const afterAtB = b.split("@")[1] || "";
933
+ const versionB = afterAtB.replace(
934
+ new RegExp(`^${escapeRegExp(versionPrefix || "")}`),
935
+ ""
936
+ );
937
+ const cleanVersionA = semver.clean(versionA) || "0.0.0";
938
+ const cleanVersionB = semver.clean(versionB) || "0.0.0";
939
+ return semver.rcompare(cleanVersionA, cleanVersionB);
940
+ });
880
941
  log(
881
942
  `Found ${packageTags.length} package tags using pattern: packageName@${versionPrefix}...`,
882
943
  "debug"
883
944
  );
884
- log(`Using tag: ${packageTags[0]}`, "debug");
885
- return packageTags[0];
945
+ log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
946
+ return sortedPackageTags2[0];
886
947
  }
887
948
  }
888
949
  if (versionPrefix) {
889
950
  const pattern2 = new RegExp(`^${escapeRegExp(versionPrefix)}${escapedPackageName}@`);
890
951
  packageTags = allTags.filter((tag) => pattern2.test(tag));
891
952
  if (packageTags.length > 0) {
953
+ const sortedPackageTags2 = [...packageTags].sort((a, b) => {
954
+ const versionA = semver.clean(a.split("@")[1] || "") || "0.0.0";
955
+ const versionB = semver.clean(b.split("@")[1] || "") || "0.0.0";
956
+ return semver.rcompare(versionA, versionB);
957
+ });
892
958
  log(
893
959
  `Found ${packageTags.length} package tags using pattern: ${versionPrefix}packageName@...`,
894
960
  "debug"
895
961
  );
896
- log(`Using tag: ${packageTags[0]}`, "debug");
897
- return packageTags[0];
962
+ log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
963
+ return sortedPackageTags2[0];
898
964
  }
899
965
  }
900
966
  const pattern3 = new RegExp(`^${escapedPackageName}@`);
901
967
  packageTags = allTags.filter((tag) => pattern3.test(tag));
902
- log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
903
968
  if (packageTags.length === 0) {
904
969
  log("No matching tags found for pattern: packageName@version", "debug");
905
970
  if (allTags.length > 0) {
@@ -907,10 +972,16 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
907
972
  } else {
908
973
  log("No tags available in the repository", "debug");
909
974
  }
910
- } else {
911
- log(`Using tag: ${packageTags[0]}`, "debug");
975
+ return "";
912
976
  }
913
- return packageTags[0] || "";
977
+ const sortedPackageTags = [...packageTags].sort((a, b) => {
978
+ const versionA = semver.clean(a.split("@")[1] || "") || "0.0.0";
979
+ const versionB = semver.clean(b.split("@")[1] || "") || "0.0.0";
980
+ return semver.rcompare(versionA, versionB);
981
+ });
982
+ log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
983
+ log(`Using semantically latest tag: ${sortedPackageTags[0]}`, "debug");
984
+ return sortedPackageTags[0];
914
985
  }
915
986
  log(`Package-specific tags disabled for ${packageName}, falling back to global tags`, "debug");
916
987
  return "";
@@ -1016,6 +1087,7 @@ function updatePackageVersion(packagePath, version) {
1016
1087
  }
1017
1088
 
1018
1089
  // src/package/packageProcessor.ts
1090
+ import { execSync as execSync4 } from "child_process";
1019
1091
  import * as fs8 from "fs";
1020
1092
  import path6 from "path";
1021
1093
  import { exit } from "process";
@@ -1348,7 +1420,7 @@ function capitalizeFirstLetter(input) {
1348
1420
  // src/core/versionCalculator.ts
1349
1421
  import { cwd as cwd3 } from "process";
1350
1422
  import { Bumper } from "conventional-recommended-bump";
1351
- import semver2 from "semver";
1423
+ import semver3 from "semver";
1352
1424
 
1353
1425
  // src/utils/manifestHelpers.ts
1354
1426
  import fs6 from "fs";
@@ -1409,7 +1481,7 @@ function throwIfNoManifestsFound(packageDir) {
1409
1481
 
1410
1482
  // src/utils/versionUtils.ts
1411
1483
  import fs7 from "fs";
1412
- import semver from "semver";
1484
+ import semver2 from "semver";
1413
1485
  import * as TOML2 from "smol-toml";
1414
1486
  var STANDARD_BUMP_TYPES = ["major", "minor", "patch"];
1415
1487
  function normalizePrereleaseIdentifier(prereleaseIdentifier, config) {
@@ -1422,27 +1494,27 @@ function normalizePrereleaseIdentifier(prereleaseIdentifier, config) {
1422
1494
  return void 0;
1423
1495
  }
1424
1496
  function bumpVersion(currentVersion, bumpType, prereleaseIdentifier) {
1425
- if (prereleaseIdentifier && STANDARD_BUMP_TYPES.includes(bumpType) && !semver.prerelease(currentVersion)) {
1497
+ if (prereleaseIdentifier && STANDARD_BUMP_TYPES.includes(bumpType) && !semver2.prerelease(currentVersion)) {
1426
1498
  const preBumpType = `pre${bumpType}`;
1427
1499
  log(
1428
1500
  `Creating prerelease version with identifier '${prereleaseIdentifier}' using ${preBumpType}`,
1429
1501
  "debug"
1430
1502
  );
1431
- return semver.inc(currentVersion, preBumpType, prereleaseIdentifier) || "";
1503
+ return semver2.inc(currentVersion, preBumpType, prereleaseIdentifier) || "";
1432
1504
  }
1433
- if (semver.prerelease(currentVersion) && STANDARD_BUMP_TYPES.includes(bumpType)) {
1434
- const parsed = semver.parse(currentVersion);
1505
+ if (semver2.prerelease(currentVersion) && STANDARD_BUMP_TYPES.includes(bumpType)) {
1506
+ const parsed = semver2.parse(currentVersion);
1435
1507
  if (!parsed) {
1436
- return semver.inc(currentVersion, bumpType) || "";
1508
+ return semver2.inc(currentVersion, bumpType) || "";
1437
1509
  }
1438
1510
  if (bumpType === "major" && parsed.minor === 0 && parsed.patch === 0 || bumpType === "minor" && parsed.patch === 0 || bumpType === "patch") {
1439
1511
  log(`Cleaning prerelease identifier from ${currentVersion} for ${bumpType} bump`, "debug");
1440
1512
  return `${parsed.major}.${parsed.minor}.${parsed.patch}`;
1441
1513
  }
1442
1514
  log(`Standard increment for ${currentVersion} with ${bumpType} bump`, "debug");
1443
- return semver.inc(currentVersion, bumpType) || "";
1515
+ return semver2.inc(currentVersion, bumpType) || "";
1444
1516
  }
1445
- return semver.inc(currentVersion, bumpType, prereleaseIdentifier) || "";
1517
+ return semver2.inc(currentVersion, bumpType, prereleaseIdentifier) || "";
1446
1518
  }
1447
1519
 
1448
1520
  // src/core/versionCalculator.ts
@@ -1484,10 +1556,10 @@ async function calculateVersion(config, options) {
1484
1556
  const packageDir = pkgPath || cwd3();
1485
1557
  const manifestResult = getVersionFromManifests(packageDir);
1486
1558
  if (manifestResult.manifestFound && manifestResult.version) {
1487
- const cleanedTag = semver2.clean(latestTag) || latestTag;
1488
- const tagVersion = semver2.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1559
+ const cleanedTag = semver3.clean(latestTag) || latestTag;
1560
+ const tagVersion = semver3.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1489
1561
  const packageVersion = manifestResult.version;
1490
- if (semver2.gt(packageVersion, tagVersion)) {
1562
+ if (semver3.gt(packageVersion, tagVersion)) {
1491
1563
  log(
1492
1564
  `Warning: Version mismatch detected!
1493
1565
  \u2022 ${manifestResult.manifestType} version: ${packageVersion}
@@ -1507,7 +1579,7 @@ To fix this mismatch:
1507
1579
  \u2022 Or use package version as base by ensuring tags are up to date`,
1508
1580
  "warning"
1509
1581
  );
1510
- } else if (semver2.gt(tagVersion, packageVersion)) {
1582
+ } else if (semver3.gt(tagVersion, packageVersion)) {
1511
1583
  log(
1512
1584
  `Warning: Version mismatch detected!
1513
1585
  \u2022 ${manifestResult.manifestType} version: ${packageVersion}
@@ -1543,9 +1615,9 @@ To fix this mismatch:
1543
1615
  initialVersion
1544
1616
  );
1545
1617
  }
1546
- const cleanedTag = semver2.clean(latestTag) || latestTag;
1547
- const currentVersion = semver2.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1548
- if (STANDARD_BUMP_TYPES.includes(specifiedType) && (semver2.prerelease(currentVersion) || normalizedPrereleaseId)) {
1618
+ const cleanedTag = semver3.clean(latestTag) || latestTag;
1619
+ const currentVersion = semver3.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1620
+ if (STANDARD_BUMP_TYPES.includes(specifiedType) && (semver3.prerelease(currentVersion) || normalizedPrereleaseId)) {
1549
1621
  log(
1550
1622
  normalizedPrereleaseId ? `Creating prerelease version with identifier '${normalizedPrereleaseId}' using ${specifiedType}` : `Cleaning prerelease identifier from ${currentVersion} for ${specifiedType} bump`,
1551
1623
  "debug"
@@ -1583,8 +1655,8 @@ To fix this mismatch:
1583
1655
  initialVersion
1584
1656
  );
1585
1657
  }
1586
- const cleanedTag = semver2.clean(latestTag) || latestTag;
1587
- const currentVersion = semver2.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1658
+ const cleanedTag = semver3.clean(latestTag) || latestTag;
1659
+ const currentVersion = semver3.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1588
1660
  log(`Applying ${branchVersionType} bump based on branch pattern`, "debug");
1589
1661
  return bumpVersion(currentVersion, branchVersionType, normalizedPrereleaseId);
1590
1662
  }
@@ -1622,7 +1694,7 @@ To fix this mismatch:
1622
1694
  );
1623
1695
  return "";
1624
1696
  }
1625
- const currentVersion = semver2.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1697
+ const currentVersion = semver3.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1626
1698
  return bumpVersion(currentVersion, releaseTypeFromCommits, normalizedPrereleaseId);
1627
1699
  } catch (error) {
1628
1700
  log(`Failed to calculate version for ${name || "project"}`, "error");
@@ -1667,7 +1739,7 @@ function calculateNextVersion(version, manifestType, name, releaseType, prerelea
1667
1739
  `No tags found for ${name || "package"}, using ${manifestType} version: ${version} as base`,
1668
1740
  "info"
1669
1741
  );
1670
- if (STANDARD_BUMP_TYPES.includes(releaseType) && (semver2.prerelease(version) || prereleaseIdentifier)) {
1742
+ if (STANDARD_BUMP_TYPES.includes(releaseType) && (semver3.prerelease(version) || prereleaseIdentifier)) {
1671
1743
  log(
1672
1744
  prereleaseIdentifier ? `Creating prerelease version with identifier '${prereleaseIdentifier}' using ${releaseType}` : `Cleaning prerelease identifier from ${version} for ${releaseType} bump`,
1673
1745
  "debug"
@@ -1828,7 +1900,24 @@ var PackageProcessor = class {
1828
1900
  if (this.fullConfig.updateChangelog !== false) {
1829
1901
  let changelogEntries = [];
1830
1902
  try {
1831
- changelogEntries = extractChangelogEntriesFromCommits(pkgPath, latestTag);
1903
+ let revisionRange = latestTag;
1904
+ if (latestTag) {
1905
+ try {
1906
+ execSync4(`git rev-parse --verify "${latestTag}"`, {
1907
+ cwd: pkgPath,
1908
+ stdio: "ignore"
1909
+ });
1910
+ } catch {
1911
+ log(
1912
+ `Tag ${latestTag} doesn't exist, using recent commits from HEAD for changelog`,
1913
+ "debug"
1914
+ );
1915
+ revisionRange = "HEAD~10..HEAD";
1916
+ }
1917
+ } else {
1918
+ revisionRange = "HEAD~10..HEAD";
1919
+ }
1920
+ changelogEntries = extractChangelogEntriesFromCommits(pkgPath, revisionRange);
1832
1921
  if (changelogEntries.length === 0) {
1833
1922
  changelogEntries = [
1834
1923
  {
@@ -111,6 +111,11 @@
111
111
  "default": true,
112
112
  "description": "Whether to automatically generate and update changelogs"
113
113
  },
114
+ "strictReachable": {
115
+ "type": "boolean",
116
+ "default": false,
117
+ "description": "Only use reachable tags (no fallback to unreachable tags)"
118
+ },
114
119
  "cargo": {
115
120
  "type": "object",
116
121
  "properties": {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "package-versioner",
3
3
  "description": "A lightweight yet powerful CLI tool for automated semantic versioning based on Git history and conventional commits.",
4
- "version": "0.7.2",
4
+ "version": "0.8.1",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",