package-versioner 0.7.2 → 0.8.2

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 behaviour**: 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,8 @@ 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)
154
+ - `prereleaseIdentifier`: Identifier for prerelease versions (e.g., "alpha", "beta", "next") used in versions like "1.2.0-alpha.3"
129
155
  - `cargo`: Options for Rust projects:
130
156
  - `enabled`: Whether to handle Cargo.toml files (default: true)
131
157
  - `paths`: Directories to search for Cargo.toml files (optional)
@@ -142,7 +168,7 @@ For more details on CI/CD integration and advanced usage, see [CI/CD Integration
142
168
 
143
169
  ### Package Targeting
144
170
 
145
- The `packages` configuration option allows you to specify which packages should be processed for versioning. It supports several pattern types:
171
+ The `packages` configuration option controls which packages are processed for versioning. It supports several pattern types:
146
172
 
147
173
  #### Exact Package Names
148
174
  ```json
@@ -175,7 +201,12 @@ Combine different pattern types:
175
201
  }
176
202
  ```
177
203
 
178
- **Note**: Package discovery is handled by your workspace configuration (pnpm-workspace.yaml, package.json workspaces, etc.). The `packages` option only filters which discovered packages to process.
204
+ **Behaviour:**
205
+ - When `packages` is specified, **only** packages matching those patterns will be processed
206
+ - When `packages` is empty or not specified, **all** workspace packages will be processed
207
+ - The `skip` option can exclude specific packages from the selected set
208
+
209
+ **Note**: Your workspace configuration (pnpm-workspace.yaml, package.json workspaces, etc.) determines which packages are available, but the `packages` option directly controls which ones get versioned.
179
210
 
180
211
  ### Package-Specific Tagging
181
212
 
package/dist/index.cjs CHANGED
@@ -639,6 +639,30 @@ function createVersionError(code, details) {
639
639
  return new VersionError(fullMessage, code);
640
640
  }
641
641
 
642
+ // src/utils/packageMatching.ts
643
+ function matchesPackageTarget(packageName, target) {
644
+ if (packageName === target) {
645
+ return true;
646
+ }
647
+ if (target.endsWith("/*")) {
648
+ const scope = target.slice(0, -2);
649
+ if (scope.startsWith("@")) {
650
+ return packageName.startsWith(`${scope}/`);
651
+ }
652
+ return packageName.startsWith(`${scope}/`);
653
+ }
654
+ if (target === "*") {
655
+ return true;
656
+ }
657
+ return false;
658
+ }
659
+ function shouldMatchPackageTargets(packageName, targets) {
660
+ return targets.some((target) => matchesPackageTarget(packageName, target));
661
+ }
662
+ function shouldProcessPackage(packageName, skip = []) {
663
+ return !skip.includes(packageName);
664
+ }
665
+
642
666
  // src/core/versionStrategies.ts
643
667
  var import_node_fs7 = __toESM(require("fs"), 1);
644
668
  var path7 = __toESM(require("path"), 1);
@@ -799,6 +823,7 @@ async function createGitCommitAndTag(files, nextTag, commitMessage, skipHooks, d
799
823
 
800
824
  // src/git/tagsAndBranches.ts
801
825
  var import_git_semver_tags = require("git-semver-tags");
826
+ var import_semver = __toESM(require("semver"), 1);
802
827
 
803
828
  // src/utils/formatting.ts
804
829
  function escapeRegExp(string) {
@@ -854,10 +879,29 @@ function getCommitsLength(pkgRoot) {
854
879
  return 0;
855
880
  }
856
881
  }
857
- async function getLatestTag() {
882
+ async function getLatestTag(versionPrefix) {
858
883
  try {
859
- const tags = await (0, import_git_semver_tags.getSemverTags)({});
860
- return tags[0] || "";
884
+ const tags = await (0, import_git_semver_tags.getSemverTags)({
885
+ tagPrefix: versionPrefix
886
+ });
887
+ if (tags.length === 0) {
888
+ return "";
889
+ }
890
+ const chronologicalLatest = tags[0];
891
+ const sortedTags = [...tags].sort((a, b) => {
892
+ const versionA = import_semver.default.clean(a) || "0.0.0";
893
+ const versionB = import_semver.default.clean(b) || "0.0.0";
894
+ return import_semver.default.rcompare(versionA, versionB);
895
+ });
896
+ const semanticLatest = sortedTags[0];
897
+ if (semanticLatest !== chronologicalLatest) {
898
+ log(
899
+ `Tag ordering differs: chronological latest is ${chronologicalLatest}, semantic latest is ${semanticLatest}`,
900
+ "debug"
901
+ );
902
+ log(`Using semantic latest (${semanticLatest}) to handle out-of-order tag creation`, "info");
903
+ }
904
+ return semanticLatest;
861
905
  } catch (error) {
862
906
  const errorMessage = error instanceof Error ? error.message : String(error);
863
907
  log(`Failed to get latest tag: ${errorMessage}`, "error");
@@ -902,37 +946,82 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
902
946
  const packageTagRegex = new RegExp(`^${packageTagPattern}$`);
903
947
  let packageTags = allTags.filter((tag) => packageTagRegex.test(tag));
904
948
  if (packageTags.length > 0) {
949
+ const chronologicalFirst = packageTags[0];
950
+ const sortedPackageTags2 = [...packageTags].sort((a, b) => {
951
+ let versionA = "";
952
+ let versionB = "";
953
+ if (a.includes("@")) {
954
+ const afterAt = a.split("@")[1] || "";
955
+ versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
956
+ } else {
957
+ versionA = a.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
958
+ }
959
+ if (b.includes("@")) {
960
+ const afterAtB = b.split("@")[1] || "";
961
+ versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
962
+ } else {
963
+ versionB = b.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
964
+ }
965
+ const cleanVersionA = import_semver.default.clean(versionA) || "0.0.0";
966
+ const cleanVersionB = import_semver.default.clean(versionB) || "0.0.0";
967
+ return import_semver.default.rcompare(cleanVersionA, cleanVersionB);
968
+ });
905
969
  log(`Found ${packageTags.length} package tags using configured pattern`, "debug");
906
- log(`Using tag: ${packageTags[0]}`, "debug");
907
- return packageTags[0];
970
+ log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
971
+ if (sortedPackageTags2[0] !== chronologicalFirst) {
972
+ log(
973
+ `Package tag ordering differs: chronological first is ${chronologicalFirst}, semantic latest is ${sortedPackageTags2[0]}`,
974
+ "debug"
975
+ );
976
+ }
977
+ return sortedPackageTags2[0];
908
978
  }
909
979
  if (versionPrefix) {
910
980
  const pattern1 = new RegExp(`^${escapedPackageName}@${escapeRegExp(versionPrefix)}`);
911
981
  packageTags = allTags.filter((tag) => pattern1.test(tag));
912
982
  if (packageTags.length > 0) {
983
+ const sortedPackageTags2 = [...packageTags].sort((a, b) => {
984
+ const afterAt = a.split("@")[1] || "";
985
+ const versionA = afterAt.replace(
986
+ new RegExp(`^${escapeRegExp(versionPrefix || "")}`),
987
+ ""
988
+ );
989
+ const afterAtB = b.split("@")[1] || "";
990
+ const versionB = afterAtB.replace(
991
+ new RegExp(`^${escapeRegExp(versionPrefix || "")}`),
992
+ ""
993
+ );
994
+ const cleanVersionA = import_semver.default.clean(versionA) || "0.0.0";
995
+ const cleanVersionB = import_semver.default.clean(versionB) || "0.0.0";
996
+ return import_semver.default.rcompare(cleanVersionA, cleanVersionB);
997
+ });
913
998
  log(
914
999
  `Found ${packageTags.length} package tags using pattern: packageName@${versionPrefix}...`,
915
1000
  "debug"
916
1001
  );
917
- log(`Using tag: ${packageTags[0]}`, "debug");
918
- return packageTags[0];
1002
+ log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
1003
+ return sortedPackageTags2[0];
919
1004
  }
920
1005
  }
921
1006
  if (versionPrefix) {
922
1007
  const pattern2 = new RegExp(`^${escapeRegExp(versionPrefix)}${escapedPackageName}@`);
923
1008
  packageTags = allTags.filter((tag) => pattern2.test(tag));
924
1009
  if (packageTags.length > 0) {
1010
+ const sortedPackageTags2 = [...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
+ });
925
1015
  log(
926
1016
  `Found ${packageTags.length} package tags using pattern: ${versionPrefix}packageName@...`,
927
1017
  "debug"
928
1018
  );
929
- log(`Using tag: ${packageTags[0]}`, "debug");
930
- return packageTags[0];
1019
+ log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
1020
+ return sortedPackageTags2[0];
931
1021
  }
932
1022
  }
933
1023
  const pattern3 = new RegExp(`^${escapedPackageName}@`);
934
1024
  packageTags = allTags.filter((tag) => pattern3.test(tag));
935
- log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
936
1025
  if (packageTags.length === 0) {
937
1026
  log("No matching tags found for pattern: packageName@version", "debug");
938
1027
  if (allTags.length > 0) {
@@ -940,10 +1029,16 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
940
1029
  } else {
941
1030
  log("No tags available in the repository", "debug");
942
1031
  }
943
- } else {
944
- log(`Using tag: ${packageTags[0]}`, "debug");
1032
+ return "";
945
1033
  }
946
- return packageTags[0] || "";
1034
+ const sortedPackageTags = [...packageTags].sort((a, b) => {
1035
+ const versionA = import_semver.default.clean(a.split("@")[1] || "") || "0.0.0";
1036
+ const versionB = import_semver.default.clean(b.split("@")[1] || "") || "0.0.0";
1037
+ return import_semver.default.rcompare(versionA, versionB);
1038
+ });
1039
+ log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
1040
+ log(`Using semantically latest tag: ${sortedPackageTags[0]}`, "debug");
1041
+ return sortedPackageTags[0];
947
1042
  }
948
1043
  log(`Package-specific tags disabled for ${packageName}, falling back to global tags`, "debug");
949
1044
  return "";
@@ -1049,6 +1144,7 @@ function updatePackageVersion(packagePath, version) {
1049
1144
  }
1050
1145
 
1051
1146
  // src/package/packageProcessor.ts
1147
+ var import_node_child_process4 = require("child_process");
1052
1148
  var fs8 = __toESM(require("fs"), 1);
1053
1149
  var import_node_path6 = __toESM(require("path"), 1);
1054
1150
  var import_node_process4 = require("process");
@@ -1381,7 +1477,7 @@ function capitalizeFirstLetter(input) {
1381
1477
  // src/core/versionCalculator.ts
1382
1478
  var import_node_process3 = require("process");
1383
1479
  var import_conventional_recommended_bump = require("conventional-recommended-bump");
1384
- var import_semver2 = __toESM(require("semver"), 1);
1480
+ var import_semver3 = __toESM(require("semver"), 1);
1385
1481
 
1386
1482
  // src/utils/manifestHelpers.ts
1387
1483
  var import_node_fs5 = __toESM(require("fs"), 1);
@@ -1442,7 +1538,7 @@ function throwIfNoManifestsFound(packageDir) {
1442
1538
 
1443
1539
  // src/utils/versionUtils.ts
1444
1540
  var import_node_fs6 = __toESM(require("fs"), 1);
1445
- var import_semver = __toESM(require("semver"), 1);
1541
+ var import_semver2 = __toESM(require("semver"), 1);
1446
1542
  var TOML2 = __toESM(require("smol-toml"), 1);
1447
1543
  var STANDARD_BUMP_TYPES = ["major", "minor", "patch"];
1448
1544
  function normalizePrereleaseIdentifier(prereleaseIdentifier, config) {
@@ -1455,27 +1551,27 @@ function normalizePrereleaseIdentifier(prereleaseIdentifier, config) {
1455
1551
  return void 0;
1456
1552
  }
1457
1553
  function bumpVersion(currentVersion, bumpType, prereleaseIdentifier) {
1458
- if (prereleaseIdentifier && STANDARD_BUMP_TYPES.includes(bumpType) && !import_semver.default.prerelease(currentVersion)) {
1554
+ if (prereleaseIdentifier && STANDARD_BUMP_TYPES.includes(bumpType) && !import_semver2.default.prerelease(currentVersion)) {
1459
1555
  const preBumpType = `pre${bumpType}`;
1460
1556
  log(
1461
1557
  `Creating prerelease version with identifier '${prereleaseIdentifier}' using ${preBumpType}`,
1462
1558
  "debug"
1463
1559
  );
1464
- return import_semver.default.inc(currentVersion, preBumpType, prereleaseIdentifier) || "";
1560
+ return import_semver2.default.inc(currentVersion, preBumpType, prereleaseIdentifier) || "";
1465
1561
  }
1466
- if (import_semver.default.prerelease(currentVersion) && STANDARD_BUMP_TYPES.includes(bumpType)) {
1467
- const parsed = import_semver.default.parse(currentVersion);
1562
+ if (import_semver2.default.prerelease(currentVersion) && STANDARD_BUMP_TYPES.includes(bumpType)) {
1563
+ const parsed = import_semver2.default.parse(currentVersion);
1468
1564
  if (!parsed) {
1469
- return import_semver.default.inc(currentVersion, bumpType) || "";
1565
+ return import_semver2.default.inc(currentVersion, bumpType) || "";
1470
1566
  }
1471
1567
  if (bumpType === "major" && parsed.minor === 0 && parsed.patch === 0 || bumpType === "minor" && parsed.patch === 0 || bumpType === "patch") {
1472
1568
  log(`Cleaning prerelease identifier from ${currentVersion} for ${bumpType} bump`, "debug");
1473
1569
  return `${parsed.major}.${parsed.minor}.${parsed.patch}`;
1474
1570
  }
1475
1571
  log(`Standard increment for ${currentVersion} with ${bumpType} bump`, "debug");
1476
- return import_semver.default.inc(currentVersion, bumpType) || "";
1572
+ return import_semver2.default.inc(currentVersion, bumpType) || "";
1477
1573
  }
1478
- return import_semver.default.inc(currentVersion, bumpType, prereleaseIdentifier) || "";
1574
+ return import_semver2.default.inc(currentVersion, bumpType, prereleaseIdentifier) || "";
1479
1575
  }
1480
1576
 
1481
1577
  // src/core/versionCalculator.ts
@@ -1517,10 +1613,10 @@ async function calculateVersion(config, options) {
1517
1613
  const packageDir = pkgPath || (0, import_node_process3.cwd)();
1518
1614
  const manifestResult = getVersionFromManifests(packageDir);
1519
1615
  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";
1616
+ const cleanedTag = import_semver3.default.clean(latestTag) || latestTag;
1617
+ const tagVersion = import_semver3.default.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1522
1618
  const packageVersion = manifestResult.version;
1523
- if (import_semver2.default.gt(packageVersion, tagVersion)) {
1619
+ if (import_semver3.default.gt(packageVersion, tagVersion)) {
1524
1620
  log(
1525
1621
  `Warning: Version mismatch detected!
1526
1622
  \u2022 ${manifestResult.manifestType} version: ${packageVersion}
@@ -1540,7 +1636,7 @@ To fix this mismatch:
1540
1636
  \u2022 Or use package version as base by ensuring tags are up to date`,
1541
1637
  "warning"
1542
1638
  );
1543
- } else if (import_semver2.default.gt(tagVersion, packageVersion)) {
1639
+ } else if (import_semver3.default.gt(tagVersion, packageVersion)) {
1544
1640
  log(
1545
1641
  `Warning: Version mismatch detected!
1546
1642
  \u2022 ${manifestResult.manifestType} version: ${packageVersion}
@@ -1576,9 +1672,9 @@ To fix this mismatch:
1576
1672
  initialVersion
1577
1673
  );
1578
1674
  }
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)) {
1675
+ const cleanedTag = import_semver3.default.clean(latestTag) || latestTag;
1676
+ const currentVersion = import_semver3.default.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1677
+ if (STANDARD_BUMP_TYPES.includes(specifiedType) && (import_semver3.default.prerelease(currentVersion) || normalizedPrereleaseId)) {
1582
1678
  log(
1583
1679
  normalizedPrereleaseId ? `Creating prerelease version with identifier '${normalizedPrereleaseId}' using ${specifiedType}` : `Cleaning prerelease identifier from ${currentVersion} for ${specifiedType} bump`,
1584
1680
  "debug"
@@ -1616,8 +1712,8 @@ To fix this mismatch:
1616
1712
  initialVersion
1617
1713
  );
1618
1714
  }
1619
- const cleanedTag = import_semver2.default.clean(latestTag) || latestTag;
1620
- const currentVersion = import_semver2.default.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1715
+ const cleanedTag = import_semver3.default.clean(latestTag) || latestTag;
1716
+ const currentVersion = import_semver3.default.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1621
1717
  log(`Applying ${branchVersionType} bump based on branch pattern`, "debug");
1622
1718
  return bumpVersion(currentVersion, branchVersionType, normalizedPrereleaseId);
1623
1719
  }
@@ -1626,7 +1722,7 @@ To fix this mismatch:
1626
1722
  const bumper = new import_conventional_recommended_bump.Bumper();
1627
1723
  bumper.loadPreset(preset);
1628
1724
  const recommendedBump = await bumper.bump();
1629
- const releaseTypeFromCommits = recommendedBump == null ? void 0 : recommendedBump.releaseType;
1725
+ const releaseTypeFromCommits = recommendedBump && "releaseType" in recommendedBump ? recommendedBump.releaseType : void 0;
1630
1726
  if (hasNoTags) {
1631
1727
  if (releaseTypeFromCommits) {
1632
1728
  return getPackageVersionFallback(
@@ -1655,7 +1751,7 @@ To fix this mismatch:
1655
1751
  );
1656
1752
  return "";
1657
1753
  }
1658
- const currentVersion = import_semver2.default.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1754
+ const currentVersion = import_semver3.default.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1659
1755
  return bumpVersion(currentVersion, releaseTypeFromCommits, normalizedPrereleaseId);
1660
1756
  } catch (error) {
1661
1757
  log(`Failed to calculate version for ${name || "project"}`, "error");
@@ -1700,7 +1796,7 @@ function calculateNextVersion(version, manifestType, name, releaseType, prerelea
1700
1796
  `No tags found for ${name || "package"}, using ${manifestType} version: ${version} as base`,
1701
1797
  "info"
1702
1798
  );
1703
- if (STANDARD_BUMP_TYPES.includes(releaseType) && (import_semver2.default.prerelease(version) || prereleaseIdentifier)) {
1799
+ if (STANDARD_BUMP_TYPES.includes(releaseType) && (import_semver3.default.prerelease(version) || prereleaseIdentifier)) {
1704
1800
  log(
1705
1801
  prereleaseIdentifier ? `Creating prerelease version with identifier '${prereleaseIdentifier}' using ${releaseType}` : `Cleaning prerelease identifier from ${version} for ${releaseType} bump`,
1706
1802
  "debug"
@@ -1711,37 +1807,9 @@ function calculateNextVersion(version, manifestType, name, releaseType, prerelea
1711
1807
  return result || initialVersion;
1712
1808
  }
1713
1809
 
1714
- // src/utils/packageMatching.ts
1715
- function matchesPackageTarget(packageName, target) {
1716
- if (packageName === target) {
1717
- return true;
1718
- }
1719
- if (target.endsWith("/*")) {
1720
- const scope = target.slice(0, -2);
1721
- if (scope.startsWith("@")) {
1722
- return packageName.startsWith(`${scope}/`);
1723
- }
1724
- return packageName.startsWith(`${scope}/`);
1725
- }
1726
- if (target === "*") {
1727
- return true;
1728
- }
1729
- return false;
1730
- }
1731
- function shouldProcessPackage(packageName, targets = [], skip = []) {
1732
- if (skip.includes(packageName)) {
1733
- return false;
1734
- }
1735
- if (targets.length === 0) {
1736
- return true;
1737
- }
1738
- return targets.some((target) => matchesPackageTarget(packageName, target));
1739
- }
1740
-
1741
1810
  // src/package/packageProcessor.ts
1742
1811
  var PackageProcessor = class {
1743
1812
  skip;
1744
- targets;
1745
1813
  versionPrefix;
1746
1814
  tagTemplate;
1747
1815
  commitMessageTemplate;
@@ -1753,7 +1821,6 @@ var PackageProcessor = class {
1753
1821
  fullConfig;
1754
1822
  constructor(options) {
1755
1823
  this.skip = options.skip || [];
1756
- this.targets = options.targets || [];
1757
1824
  this.versionPrefix = options.versionPrefix || "v";
1758
1825
  this.tagTemplate = options.tagTemplate;
1759
1826
  this.commitMessageTemplate = options.commitMessageTemplate || "";
@@ -1764,13 +1831,7 @@ var PackageProcessor = class {
1764
1831
  this.fullConfig = options.fullConfig;
1765
1832
  }
1766
1833
  /**
1767
- * Set package targets to process
1768
- */
1769
- setTargets(targets) {
1770
- this.targets = targets;
1771
- }
1772
- /**
1773
- * Process packages based on targeting criteria
1834
+ * Process packages based on skip list only (targeting handled at discovery time)
1774
1835
  */
1775
1836
  async processPackages(packages) {
1776
1837
  var _a, _b, _c, _d, _e;
@@ -1781,21 +1842,16 @@ var PackageProcessor = class {
1781
1842
  return { updatedPackages: [], tags: [] };
1782
1843
  }
1783
1844
  const pkgsToConsider = packages.filter((pkg) => {
1784
- var _a2;
1785
1845
  const pkgName = pkg.packageJson.name;
1786
- const shouldProcess = shouldProcessPackage(pkgName, this.targets, this.skip);
1846
+ const shouldProcess = shouldProcessPackage(pkgName, this.skip);
1787
1847
  if (!shouldProcess) {
1788
- if ((_a2 = this.skip) == null ? void 0 : _a2.includes(pkgName)) {
1789
- log(`Skipping package ${pkgName} as it's in the skip list.`, "info");
1790
- } else {
1791
- log(`Package ${pkgName} not in target list, skipping.`, "info");
1792
- }
1848
+ log(`Skipping package ${pkgName} as it's in the skip list.`, "info");
1793
1849
  }
1794
1850
  return shouldProcess;
1795
1851
  });
1796
- log(`Found ${pkgsToConsider.length} targeted package(s) to process after filtering.`, "info");
1852
+ log(`Found ${pkgsToConsider.length} package(s) to process after filtering.`, "info");
1797
1853
  if (pkgsToConsider.length === 0) {
1798
- log("No matching targeted packages found to process.", "info");
1854
+ log("No packages found to process.", "info");
1799
1855
  return { updatedPackages: [], tags: [] };
1800
1856
  }
1801
1857
  for (const pkg of pkgsToConsider) {
@@ -1861,7 +1917,24 @@ var PackageProcessor = class {
1861
1917
  if (this.fullConfig.updateChangelog !== false) {
1862
1918
  let changelogEntries = [];
1863
1919
  try {
1864
- changelogEntries = extractChangelogEntriesFromCommits(pkgPath, latestTag);
1920
+ let revisionRange = latestTag;
1921
+ if (latestTag) {
1922
+ try {
1923
+ (0, import_node_child_process4.execSync)(`git rev-parse --verify "${latestTag}"`, {
1924
+ cwd: pkgPath,
1925
+ stdio: "ignore"
1926
+ });
1927
+ } catch {
1928
+ log(
1929
+ `Tag ${latestTag} doesn't exist, using recent commits from HEAD for changelog`,
1930
+ "debug"
1931
+ );
1932
+ revisionRange = "HEAD~10..HEAD";
1933
+ }
1934
+ } else {
1935
+ revisionRange = "HEAD~10..HEAD";
1936
+ }
1937
+ changelogEntries = extractChangelogEntriesFromCommits(pkgPath, revisionRange);
1865
1938
  if (changelogEntries.length === 0) {
1866
1939
  changelogEntries = [
1867
1940
  {
@@ -1961,7 +2034,7 @@ var PackageProcessor = class {
1961
2034
  updatedPackagesInfo.push({ name, version: nextVersion, path: pkgPath });
1962
2035
  }
1963
2036
  if (updatedPackagesInfo.length === 0) {
1964
- log("No targeted packages required a version update.", "info");
2037
+ log("No packages required a version update.", "info");
1965
2038
  return { updatedPackages: [], tags };
1966
2039
  }
1967
2040
  const filesToCommit = [];
@@ -2025,9 +2098,9 @@ var PackageProcessor = class {
2025
2098
  };
2026
2099
 
2027
2100
  // src/core/versionStrategies.ts
2028
- function shouldProcessPackage2(pkg, config, targets = []) {
2101
+ function shouldProcessPackage2(pkg, config) {
2029
2102
  const pkgName = pkg.packageJson.name;
2030
- return shouldProcessPackage(pkgName, targets, config.skip);
2103
+ return shouldProcessPackage(pkgName, config.skip);
2031
2104
  }
2032
2105
  function createSyncedStrategy(config) {
2033
2106
  return async (packages) => {
@@ -2243,7 +2316,6 @@ function createAsyncStrategy(config) {
2243
2316
  };
2244
2317
  const processorOptions = {
2245
2318
  skip: config.skip || [],
2246
- targets: config.packages || [],
2247
2319
  versionPrefix: config.versionPrefix || "v",
2248
2320
  tagTemplate: config.tagTemplate,
2249
2321
  commitMessageTemplate: config.commitMessage || "",
@@ -2260,15 +2332,9 @@ function createAsyncStrategy(config) {
2260
2332
  }
2261
2333
  };
2262
2334
  const packageProcessor = new PackageProcessor(processorOptions);
2263
- return async (packages, targets = []) => {
2335
+ return async (packages, _targets = []) => {
2264
2336
  try {
2265
- const targetPackages = targets.length > 0 ? targets : config.packages || [];
2266
- packageProcessor.setTargets(targetPackages);
2267
- if (targetPackages.length > 0) {
2268
- log(`Processing targeted packages: ${targetPackages.join(", ")}`, "info");
2269
- } else {
2270
- log("No targets specified, processing all non-skipped packages", "info");
2271
- }
2337
+ log(`Processing ${packages.packages.length} pre-filtered packages`, "info");
2272
2338
  const result = await packageProcessor.processPackages(packages.packages);
2273
2339
  if (result.updatedPackages.length === 0) {
2274
2340
  log("No packages required a version update.", "info");
@@ -2350,6 +2416,25 @@ var VersionEngine = class {
2350
2416
  );
2351
2417
  pkgsResult.root = (0, import_node_process5.cwd)();
2352
2418
  }
2419
+ if (this.config.packages && this.config.packages.length > 0) {
2420
+ const originalCount = pkgsResult.packages.length;
2421
+ const filteredPackages = pkgsResult.packages.filter(
2422
+ (pkg) => shouldMatchPackageTargets(pkg.packageJson.name, this.config.packages)
2423
+ );
2424
+ pkgsResult.packages = filteredPackages;
2425
+ log(
2426
+ `Filtered ${originalCount} workspace packages to ${filteredPackages.length} based on packages config`,
2427
+ "info"
2428
+ );
2429
+ if (filteredPackages.length === 0) {
2430
+ log("Warning: No packages matched the specified patterns in config.packages", "warning");
2431
+ }
2432
+ } else {
2433
+ log(
2434
+ `Processing all ${pkgsResult.packages.length} workspace packages (no packages filter specified)`,
2435
+ "info"
2436
+ );
2437
+ }
2353
2438
  this.workspaceCache = pkgsResult;
2354
2439
  return pkgsResult;
2355
2440
  } catch (error) {
package/dist/index.js CHANGED
@@ -606,6 +606,30 @@ function createVersionError(code, details) {
606
606
  return new VersionError(fullMessage, code);
607
607
  }
608
608
 
609
+ // src/utils/packageMatching.ts
610
+ function matchesPackageTarget(packageName, target) {
611
+ if (packageName === target) {
612
+ return true;
613
+ }
614
+ if (target.endsWith("/*")) {
615
+ const scope = target.slice(0, -2);
616
+ if (scope.startsWith("@")) {
617
+ return packageName.startsWith(`${scope}/`);
618
+ }
619
+ return packageName.startsWith(`${scope}/`);
620
+ }
621
+ if (target === "*") {
622
+ return true;
623
+ }
624
+ return false;
625
+ }
626
+ function shouldMatchPackageTargets(packageName, targets) {
627
+ return targets.some((target) => matchesPackageTarget(packageName, target));
628
+ }
629
+ function shouldProcessPackage(packageName, skip = []) {
630
+ return !skip.includes(packageName);
631
+ }
632
+
609
633
  // src/core/versionStrategies.ts
610
634
  import fs9 from "fs";
611
635
  import * as path7 from "path";
@@ -766,6 +790,7 @@ async function createGitCommitAndTag(files, nextTag, commitMessage, skipHooks, d
766
790
 
767
791
  // src/git/tagsAndBranches.ts
768
792
  import { getSemverTags } from "git-semver-tags";
793
+ import semver from "semver";
769
794
 
770
795
  // src/utils/formatting.ts
771
796
  function escapeRegExp(string) {
@@ -821,10 +846,29 @@ function getCommitsLength(pkgRoot) {
821
846
  return 0;
822
847
  }
823
848
  }
824
- async function getLatestTag() {
849
+ async function getLatestTag(versionPrefix) {
825
850
  try {
826
- const tags = await getSemverTags({});
827
- return tags[0] || "";
851
+ const tags = await getSemverTags({
852
+ tagPrefix: versionPrefix
853
+ });
854
+ if (tags.length === 0) {
855
+ return "";
856
+ }
857
+ const chronologicalLatest = tags[0];
858
+ const sortedTags = [...tags].sort((a, b) => {
859
+ const versionA = semver.clean(a) || "0.0.0";
860
+ const versionB = semver.clean(b) || "0.0.0";
861
+ return semver.rcompare(versionA, versionB);
862
+ });
863
+ const semanticLatest = sortedTags[0];
864
+ if (semanticLatest !== chronologicalLatest) {
865
+ log(
866
+ `Tag ordering differs: chronological latest is ${chronologicalLatest}, semantic latest is ${semanticLatest}`,
867
+ "debug"
868
+ );
869
+ log(`Using semantic latest (${semanticLatest}) to handle out-of-order tag creation`, "info");
870
+ }
871
+ return semanticLatest;
828
872
  } catch (error) {
829
873
  const errorMessage = error instanceof Error ? error.message : String(error);
830
874
  log(`Failed to get latest tag: ${errorMessage}`, "error");
@@ -869,37 +913,82 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
869
913
  const packageTagRegex = new RegExp(`^${packageTagPattern}$`);
870
914
  let packageTags = allTags.filter((tag) => packageTagRegex.test(tag));
871
915
  if (packageTags.length > 0) {
916
+ const chronologicalFirst = packageTags[0];
917
+ const sortedPackageTags2 = [...packageTags].sort((a, b) => {
918
+ let versionA = "";
919
+ let versionB = "";
920
+ if (a.includes("@")) {
921
+ const afterAt = a.split("@")[1] || "";
922
+ versionA = afterAt.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
923
+ } else {
924
+ versionA = a.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
925
+ }
926
+ if (b.includes("@")) {
927
+ const afterAtB = b.split("@")[1] || "";
928
+ versionB = afterAtB.replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
929
+ } else {
930
+ versionB = b.replace(new RegExp(`^${escapeRegExp(packageName)}`), "").replace(new RegExp(`^${escapeRegExp(versionPrefix || "")}`), "");
931
+ }
932
+ const cleanVersionA = semver.clean(versionA) || "0.0.0";
933
+ const cleanVersionB = semver.clean(versionB) || "0.0.0";
934
+ return semver.rcompare(cleanVersionA, cleanVersionB);
935
+ });
872
936
  log(`Found ${packageTags.length} package tags using configured pattern`, "debug");
873
- log(`Using tag: ${packageTags[0]}`, "debug");
874
- return packageTags[0];
937
+ log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
938
+ if (sortedPackageTags2[0] !== chronologicalFirst) {
939
+ log(
940
+ `Package tag ordering differs: chronological first is ${chronologicalFirst}, semantic latest is ${sortedPackageTags2[0]}`,
941
+ "debug"
942
+ );
943
+ }
944
+ return sortedPackageTags2[0];
875
945
  }
876
946
  if (versionPrefix) {
877
947
  const pattern1 = new RegExp(`^${escapedPackageName}@${escapeRegExp(versionPrefix)}`);
878
948
  packageTags = allTags.filter((tag) => pattern1.test(tag));
879
949
  if (packageTags.length > 0) {
950
+ const sortedPackageTags2 = [...packageTags].sort((a, b) => {
951
+ const afterAt = a.split("@")[1] || "";
952
+ const versionA = afterAt.replace(
953
+ new RegExp(`^${escapeRegExp(versionPrefix || "")}`),
954
+ ""
955
+ );
956
+ const afterAtB = b.split("@")[1] || "";
957
+ const versionB = afterAtB.replace(
958
+ new RegExp(`^${escapeRegExp(versionPrefix || "")}`),
959
+ ""
960
+ );
961
+ const cleanVersionA = semver.clean(versionA) || "0.0.0";
962
+ const cleanVersionB = semver.clean(versionB) || "0.0.0";
963
+ return semver.rcompare(cleanVersionA, cleanVersionB);
964
+ });
880
965
  log(
881
966
  `Found ${packageTags.length} package tags using pattern: packageName@${versionPrefix}...`,
882
967
  "debug"
883
968
  );
884
- log(`Using tag: ${packageTags[0]}`, "debug");
885
- return packageTags[0];
969
+ log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
970
+ return sortedPackageTags2[0];
886
971
  }
887
972
  }
888
973
  if (versionPrefix) {
889
974
  const pattern2 = new RegExp(`^${escapeRegExp(versionPrefix)}${escapedPackageName}@`);
890
975
  packageTags = allTags.filter((tag) => pattern2.test(tag));
891
976
  if (packageTags.length > 0) {
977
+ const sortedPackageTags2 = [...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
+ });
892
982
  log(
893
983
  `Found ${packageTags.length} package tags using pattern: ${versionPrefix}packageName@...`,
894
984
  "debug"
895
985
  );
896
- log(`Using tag: ${packageTags[0]}`, "debug");
897
- return packageTags[0];
986
+ log(`Using semantically latest tag: ${sortedPackageTags2[0]}`, "debug");
987
+ return sortedPackageTags2[0];
898
988
  }
899
989
  }
900
990
  const pattern3 = new RegExp(`^${escapedPackageName}@`);
901
991
  packageTags = allTags.filter((tag) => pattern3.test(tag));
902
- log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
903
992
  if (packageTags.length === 0) {
904
993
  log("No matching tags found for pattern: packageName@version", "debug");
905
994
  if (allTags.length > 0) {
@@ -907,10 +996,16 @@ async function getLatestTagForPackage(packageName, versionPrefix, options) {
907
996
  } else {
908
997
  log("No tags available in the repository", "debug");
909
998
  }
910
- } else {
911
- log(`Using tag: ${packageTags[0]}`, "debug");
999
+ return "";
912
1000
  }
913
- return packageTags[0] || "";
1001
+ const sortedPackageTags = [...packageTags].sort((a, b) => {
1002
+ const versionA = semver.clean(a.split("@")[1] || "") || "0.0.0";
1003
+ const versionB = semver.clean(b.split("@")[1] || "") || "0.0.0";
1004
+ return semver.rcompare(versionA, versionB);
1005
+ });
1006
+ log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
1007
+ log(`Using semantically latest tag: ${sortedPackageTags[0]}`, "debug");
1008
+ return sortedPackageTags[0];
914
1009
  }
915
1010
  log(`Package-specific tags disabled for ${packageName}, falling back to global tags`, "debug");
916
1011
  return "";
@@ -1016,6 +1111,7 @@ function updatePackageVersion(packagePath, version) {
1016
1111
  }
1017
1112
 
1018
1113
  // src/package/packageProcessor.ts
1114
+ import { execSync as execSync4 } from "child_process";
1019
1115
  import * as fs8 from "fs";
1020
1116
  import path6 from "path";
1021
1117
  import { exit } from "process";
@@ -1348,7 +1444,7 @@ function capitalizeFirstLetter(input) {
1348
1444
  // src/core/versionCalculator.ts
1349
1445
  import { cwd as cwd3 } from "process";
1350
1446
  import { Bumper } from "conventional-recommended-bump";
1351
- import semver2 from "semver";
1447
+ import semver3 from "semver";
1352
1448
 
1353
1449
  // src/utils/manifestHelpers.ts
1354
1450
  import fs6 from "fs";
@@ -1409,7 +1505,7 @@ function throwIfNoManifestsFound(packageDir) {
1409
1505
 
1410
1506
  // src/utils/versionUtils.ts
1411
1507
  import fs7 from "fs";
1412
- import semver from "semver";
1508
+ import semver2 from "semver";
1413
1509
  import * as TOML2 from "smol-toml";
1414
1510
  var STANDARD_BUMP_TYPES = ["major", "minor", "patch"];
1415
1511
  function normalizePrereleaseIdentifier(prereleaseIdentifier, config) {
@@ -1422,27 +1518,27 @@ function normalizePrereleaseIdentifier(prereleaseIdentifier, config) {
1422
1518
  return void 0;
1423
1519
  }
1424
1520
  function bumpVersion(currentVersion, bumpType, prereleaseIdentifier) {
1425
- if (prereleaseIdentifier && STANDARD_BUMP_TYPES.includes(bumpType) && !semver.prerelease(currentVersion)) {
1521
+ if (prereleaseIdentifier && STANDARD_BUMP_TYPES.includes(bumpType) && !semver2.prerelease(currentVersion)) {
1426
1522
  const preBumpType = `pre${bumpType}`;
1427
1523
  log(
1428
1524
  `Creating prerelease version with identifier '${prereleaseIdentifier}' using ${preBumpType}`,
1429
1525
  "debug"
1430
1526
  );
1431
- return semver.inc(currentVersion, preBumpType, prereleaseIdentifier) || "";
1527
+ return semver2.inc(currentVersion, preBumpType, prereleaseIdentifier) || "";
1432
1528
  }
1433
- if (semver.prerelease(currentVersion) && STANDARD_BUMP_TYPES.includes(bumpType)) {
1434
- const parsed = semver.parse(currentVersion);
1529
+ if (semver2.prerelease(currentVersion) && STANDARD_BUMP_TYPES.includes(bumpType)) {
1530
+ const parsed = semver2.parse(currentVersion);
1435
1531
  if (!parsed) {
1436
- return semver.inc(currentVersion, bumpType) || "";
1532
+ return semver2.inc(currentVersion, bumpType) || "";
1437
1533
  }
1438
1534
  if (bumpType === "major" && parsed.minor === 0 && parsed.patch === 0 || bumpType === "minor" && parsed.patch === 0 || bumpType === "patch") {
1439
1535
  log(`Cleaning prerelease identifier from ${currentVersion} for ${bumpType} bump`, "debug");
1440
1536
  return `${parsed.major}.${parsed.minor}.${parsed.patch}`;
1441
1537
  }
1442
1538
  log(`Standard increment for ${currentVersion} with ${bumpType} bump`, "debug");
1443
- return semver.inc(currentVersion, bumpType) || "";
1539
+ return semver2.inc(currentVersion, bumpType) || "";
1444
1540
  }
1445
- return semver.inc(currentVersion, bumpType, prereleaseIdentifier) || "";
1541
+ return semver2.inc(currentVersion, bumpType, prereleaseIdentifier) || "";
1446
1542
  }
1447
1543
 
1448
1544
  // src/core/versionCalculator.ts
@@ -1484,10 +1580,10 @@ async function calculateVersion(config, options) {
1484
1580
  const packageDir = pkgPath || cwd3();
1485
1581
  const manifestResult = getVersionFromManifests(packageDir);
1486
1582
  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";
1583
+ const cleanedTag = semver3.clean(latestTag) || latestTag;
1584
+ const tagVersion = semver3.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1489
1585
  const packageVersion = manifestResult.version;
1490
- if (semver2.gt(packageVersion, tagVersion)) {
1586
+ if (semver3.gt(packageVersion, tagVersion)) {
1491
1587
  log(
1492
1588
  `Warning: Version mismatch detected!
1493
1589
  \u2022 ${manifestResult.manifestType} version: ${packageVersion}
@@ -1507,7 +1603,7 @@ To fix this mismatch:
1507
1603
  \u2022 Or use package version as base by ensuring tags are up to date`,
1508
1604
  "warning"
1509
1605
  );
1510
- } else if (semver2.gt(tagVersion, packageVersion)) {
1606
+ } else if (semver3.gt(tagVersion, packageVersion)) {
1511
1607
  log(
1512
1608
  `Warning: Version mismatch detected!
1513
1609
  \u2022 ${manifestResult.manifestType} version: ${packageVersion}
@@ -1543,9 +1639,9 @@ To fix this mismatch:
1543
1639
  initialVersion
1544
1640
  );
1545
1641
  }
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)) {
1642
+ const cleanedTag = semver3.clean(latestTag) || latestTag;
1643
+ const currentVersion = semver3.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1644
+ if (STANDARD_BUMP_TYPES.includes(specifiedType) && (semver3.prerelease(currentVersion) || normalizedPrereleaseId)) {
1549
1645
  log(
1550
1646
  normalizedPrereleaseId ? `Creating prerelease version with identifier '${normalizedPrereleaseId}' using ${specifiedType}` : `Cleaning prerelease identifier from ${currentVersion} for ${specifiedType} bump`,
1551
1647
  "debug"
@@ -1583,8 +1679,8 @@ To fix this mismatch:
1583
1679
  initialVersion
1584
1680
  );
1585
1681
  }
1586
- const cleanedTag = semver2.clean(latestTag) || latestTag;
1587
- const currentVersion = semver2.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1682
+ const cleanedTag = semver3.clean(latestTag) || latestTag;
1683
+ const currentVersion = semver3.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1588
1684
  log(`Applying ${branchVersionType} bump based on branch pattern`, "debug");
1589
1685
  return bumpVersion(currentVersion, branchVersionType, normalizedPrereleaseId);
1590
1686
  }
@@ -1593,7 +1689,7 @@ To fix this mismatch:
1593
1689
  const bumper = new Bumper();
1594
1690
  bumper.loadPreset(preset);
1595
1691
  const recommendedBump = await bumper.bump();
1596
- const releaseTypeFromCommits = recommendedBump == null ? void 0 : recommendedBump.releaseType;
1692
+ const releaseTypeFromCommits = recommendedBump && "releaseType" in recommendedBump ? recommendedBump.releaseType : void 0;
1597
1693
  if (hasNoTags) {
1598
1694
  if (releaseTypeFromCommits) {
1599
1695
  return getPackageVersionFallback(
@@ -1622,7 +1718,7 @@ To fix this mismatch:
1622
1718
  );
1623
1719
  return "";
1624
1720
  }
1625
- const currentVersion = semver2.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1721
+ const currentVersion = semver3.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
1626
1722
  return bumpVersion(currentVersion, releaseTypeFromCommits, normalizedPrereleaseId);
1627
1723
  } catch (error) {
1628
1724
  log(`Failed to calculate version for ${name || "project"}`, "error");
@@ -1667,7 +1763,7 @@ function calculateNextVersion(version, manifestType, name, releaseType, prerelea
1667
1763
  `No tags found for ${name || "package"}, using ${manifestType} version: ${version} as base`,
1668
1764
  "info"
1669
1765
  );
1670
- if (STANDARD_BUMP_TYPES.includes(releaseType) && (semver2.prerelease(version) || prereleaseIdentifier)) {
1766
+ if (STANDARD_BUMP_TYPES.includes(releaseType) && (semver3.prerelease(version) || prereleaseIdentifier)) {
1671
1767
  log(
1672
1768
  prereleaseIdentifier ? `Creating prerelease version with identifier '${prereleaseIdentifier}' using ${releaseType}` : `Cleaning prerelease identifier from ${version} for ${releaseType} bump`,
1673
1769
  "debug"
@@ -1678,37 +1774,9 @@ function calculateNextVersion(version, manifestType, name, releaseType, prerelea
1678
1774
  return result || initialVersion;
1679
1775
  }
1680
1776
 
1681
- // src/utils/packageMatching.ts
1682
- function matchesPackageTarget(packageName, target) {
1683
- if (packageName === target) {
1684
- return true;
1685
- }
1686
- if (target.endsWith("/*")) {
1687
- const scope = target.slice(0, -2);
1688
- if (scope.startsWith("@")) {
1689
- return packageName.startsWith(`${scope}/`);
1690
- }
1691
- return packageName.startsWith(`${scope}/`);
1692
- }
1693
- if (target === "*") {
1694
- return true;
1695
- }
1696
- return false;
1697
- }
1698
- function shouldProcessPackage(packageName, targets = [], skip = []) {
1699
- if (skip.includes(packageName)) {
1700
- return false;
1701
- }
1702
- if (targets.length === 0) {
1703
- return true;
1704
- }
1705
- return targets.some((target) => matchesPackageTarget(packageName, target));
1706
- }
1707
-
1708
1777
  // src/package/packageProcessor.ts
1709
1778
  var PackageProcessor = class {
1710
1779
  skip;
1711
- targets;
1712
1780
  versionPrefix;
1713
1781
  tagTemplate;
1714
1782
  commitMessageTemplate;
@@ -1720,7 +1788,6 @@ var PackageProcessor = class {
1720
1788
  fullConfig;
1721
1789
  constructor(options) {
1722
1790
  this.skip = options.skip || [];
1723
- this.targets = options.targets || [];
1724
1791
  this.versionPrefix = options.versionPrefix || "v";
1725
1792
  this.tagTemplate = options.tagTemplate;
1726
1793
  this.commitMessageTemplate = options.commitMessageTemplate || "";
@@ -1731,13 +1798,7 @@ var PackageProcessor = class {
1731
1798
  this.fullConfig = options.fullConfig;
1732
1799
  }
1733
1800
  /**
1734
- * Set package targets to process
1735
- */
1736
- setTargets(targets) {
1737
- this.targets = targets;
1738
- }
1739
- /**
1740
- * Process packages based on targeting criteria
1801
+ * Process packages based on skip list only (targeting handled at discovery time)
1741
1802
  */
1742
1803
  async processPackages(packages) {
1743
1804
  var _a, _b, _c, _d, _e;
@@ -1748,21 +1809,16 @@ var PackageProcessor = class {
1748
1809
  return { updatedPackages: [], tags: [] };
1749
1810
  }
1750
1811
  const pkgsToConsider = packages.filter((pkg) => {
1751
- var _a2;
1752
1812
  const pkgName = pkg.packageJson.name;
1753
- const shouldProcess = shouldProcessPackage(pkgName, this.targets, this.skip);
1813
+ const shouldProcess = shouldProcessPackage(pkgName, this.skip);
1754
1814
  if (!shouldProcess) {
1755
- if ((_a2 = this.skip) == null ? void 0 : _a2.includes(pkgName)) {
1756
- log(`Skipping package ${pkgName} as it's in the skip list.`, "info");
1757
- } else {
1758
- log(`Package ${pkgName} not in target list, skipping.`, "info");
1759
- }
1815
+ log(`Skipping package ${pkgName} as it's in the skip list.`, "info");
1760
1816
  }
1761
1817
  return shouldProcess;
1762
1818
  });
1763
- log(`Found ${pkgsToConsider.length} targeted package(s) to process after filtering.`, "info");
1819
+ log(`Found ${pkgsToConsider.length} package(s) to process after filtering.`, "info");
1764
1820
  if (pkgsToConsider.length === 0) {
1765
- log("No matching targeted packages found to process.", "info");
1821
+ log("No packages found to process.", "info");
1766
1822
  return { updatedPackages: [], tags: [] };
1767
1823
  }
1768
1824
  for (const pkg of pkgsToConsider) {
@@ -1828,7 +1884,24 @@ var PackageProcessor = class {
1828
1884
  if (this.fullConfig.updateChangelog !== false) {
1829
1885
  let changelogEntries = [];
1830
1886
  try {
1831
- changelogEntries = extractChangelogEntriesFromCommits(pkgPath, latestTag);
1887
+ let revisionRange = latestTag;
1888
+ if (latestTag) {
1889
+ try {
1890
+ execSync4(`git rev-parse --verify "${latestTag}"`, {
1891
+ cwd: pkgPath,
1892
+ stdio: "ignore"
1893
+ });
1894
+ } catch {
1895
+ log(
1896
+ `Tag ${latestTag} doesn't exist, using recent commits from HEAD for changelog`,
1897
+ "debug"
1898
+ );
1899
+ revisionRange = "HEAD~10..HEAD";
1900
+ }
1901
+ } else {
1902
+ revisionRange = "HEAD~10..HEAD";
1903
+ }
1904
+ changelogEntries = extractChangelogEntriesFromCommits(pkgPath, revisionRange);
1832
1905
  if (changelogEntries.length === 0) {
1833
1906
  changelogEntries = [
1834
1907
  {
@@ -1928,7 +2001,7 @@ var PackageProcessor = class {
1928
2001
  updatedPackagesInfo.push({ name, version: nextVersion, path: pkgPath });
1929
2002
  }
1930
2003
  if (updatedPackagesInfo.length === 0) {
1931
- log("No targeted packages required a version update.", "info");
2004
+ log("No packages required a version update.", "info");
1932
2005
  return { updatedPackages: [], tags };
1933
2006
  }
1934
2007
  const filesToCommit = [];
@@ -1992,9 +2065,9 @@ var PackageProcessor = class {
1992
2065
  };
1993
2066
 
1994
2067
  // src/core/versionStrategies.ts
1995
- function shouldProcessPackage2(pkg, config, targets = []) {
2068
+ function shouldProcessPackage2(pkg, config) {
1996
2069
  const pkgName = pkg.packageJson.name;
1997
- return shouldProcessPackage(pkgName, targets, config.skip);
2070
+ return shouldProcessPackage(pkgName, config.skip);
1998
2071
  }
1999
2072
  function createSyncedStrategy(config) {
2000
2073
  return async (packages) => {
@@ -2210,7 +2283,6 @@ function createAsyncStrategy(config) {
2210
2283
  };
2211
2284
  const processorOptions = {
2212
2285
  skip: config.skip || [],
2213
- targets: config.packages || [],
2214
2286
  versionPrefix: config.versionPrefix || "v",
2215
2287
  tagTemplate: config.tagTemplate,
2216
2288
  commitMessageTemplate: config.commitMessage || "",
@@ -2227,15 +2299,9 @@ function createAsyncStrategy(config) {
2227
2299
  }
2228
2300
  };
2229
2301
  const packageProcessor = new PackageProcessor(processorOptions);
2230
- return async (packages, targets = []) => {
2302
+ return async (packages, _targets = []) => {
2231
2303
  try {
2232
- const targetPackages = targets.length > 0 ? targets : config.packages || [];
2233
- packageProcessor.setTargets(targetPackages);
2234
- if (targetPackages.length > 0) {
2235
- log(`Processing targeted packages: ${targetPackages.join(", ")}`, "info");
2236
- } else {
2237
- log("No targets specified, processing all non-skipped packages", "info");
2238
- }
2304
+ log(`Processing ${packages.packages.length} pre-filtered packages`, "info");
2239
2305
  const result = await packageProcessor.processPackages(packages.packages);
2240
2306
  if (result.updatedPackages.length === 0) {
2241
2307
  log("No packages required a version update.", "info");
@@ -2317,6 +2383,25 @@ var VersionEngine = class {
2317
2383
  );
2318
2384
  pkgsResult.root = cwd4();
2319
2385
  }
2386
+ if (this.config.packages && this.config.packages.length > 0) {
2387
+ const originalCount = pkgsResult.packages.length;
2388
+ const filteredPackages = pkgsResult.packages.filter(
2389
+ (pkg) => shouldMatchPackageTargets(pkg.packageJson.name, this.config.packages)
2390
+ );
2391
+ pkgsResult.packages = filteredPackages;
2392
+ log(
2393
+ `Filtered ${originalCount} workspace packages to ${filteredPackages.length} based on packages config`,
2394
+ "info"
2395
+ );
2396
+ if (filteredPackages.length === 0) {
2397
+ log("Warning: No packages matched the specified patterns in config.packages", "warning");
2398
+ }
2399
+ } else {
2400
+ log(
2401
+ `Processing all ${pkgsResult.packages.length} workspace packages (no packages filter specified)`,
2402
+ "info"
2403
+ );
2404
+ }
2320
2405
  this.workspaceCache = pkgsResult;
2321
2406
  return pkgsResult;
2322
2407
  } catch (error) {
@@ -154,7 +154,7 @@ git push origin "v$NEW_VERSION"
154
154
  `package-versioner` respects the following environment variables:
155
155
 
156
156
  - `NO_COLOR=1`: Disables colored output in logs (automatically detected in CI environments)
157
- - `CI=true`: Most CI environments set this automatically, which helps the tool adjust its output behavior
157
+ - `CI=true`: Most CI environments set this automatically, which helps the tool adjust its output behaviour
158
158
 
159
159
  ## Skipping CI for Version Commits
160
160
 
@@ -170,13 +170,6 @@ This dual support makes `package-versioner` suitable for both JavaScript/TypeScr
170
170
 
171
171
  When working with monorepos, you can control which packages are processed for versioning using the `packages` configuration option. This provides flexible targeting with support for various pattern types.
172
172
 
173
- ### Package Discovery vs. Targeting
174
-
175
- It's important to understand the distinction:
176
-
177
- - **Package Discovery**: Handled by your workspace configuration (pnpm-workspace.yaml, package.json workspaces, etc.)
178
- - **Package Targeting**: Controlled by the `packages` option in version.config.json to filter which discovered packages to process
179
-
180
173
  ### Targeting Patterns
181
174
 
182
175
  #### Exact Package Names
@@ -212,6 +205,12 @@ Combine different pattern types for flexible targeting:
212
205
  }
213
206
  ```
214
207
 
208
+ ### Behaviour
209
+
210
+ - **When `packages` is specified**: Only packages matching those patterns will be processed for versioning
211
+ - **When `packages` is empty or not specified**: All workspace packages will be processed
212
+ - **Error handling**: If no packages match the specified patterns, a warning is displayed
213
+
215
214
  ### Excluding Packages
216
215
 
217
216
  Use the `skip` option to exclude specific packages from processing:
@@ -224,6 +223,8 @@ Use the `skip` option to exclude specific packages from processing:
224
223
 
225
224
  This configuration will process all packages in the `@mycompany` scope except for `@mycompany/deprecated-package`.
226
225
 
226
+ **Note**: Your workspace configuration (pnpm-workspace.yaml, package.json workspaces, etc.) determines which packages are available in your workspace, but the `packages` option directly controls which ones get versioned.
227
+
227
228
  ## Tag Templates and Configuration
228
229
 
229
230
  `package-versioner` provides flexible configuration for how Git tags are formatted, allowing you to customize the tag structure for both single package repositories and monorepos.
@@ -21,7 +21,7 @@
21
21
  "packageSpecificTags": {
22
22
  "type": "boolean",
23
23
  "default": false,
24
- "description": "Whether to enable package-specific tagging behavior"
24
+ "description": "Whether to enable package-specific tagging behaviour"
25
25
  },
26
26
  "preset": {
27
27
  "type": "string",
@@ -53,7 +53,7 @@
53
53
  "minLength": 1
54
54
  },
55
55
  "default": [],
56
- "description": "Array of package names or patterns to target for versioning. Supports exact names (e.g., '@scope/package-a'), scope wildcards (e.g., '@scope/*'), and global wildcards (e.g., '*')"
56
+ "description": "Array of package names or patterns that determines which packages will be processed for versioning. When specified, only packages matching these patterns will be versioned. When empty or not specified, all workspace packages will be processed. Supports exact names (e.g., '@scope/package-a'), scope wildcards (e.g., '@scope/*'), and global wildcards (e.g., '*')"
57
57
  },
58
58
  "mainPackage": {
59
59
  "type": "string",
@@ -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.2",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -37,17 +37,17 @@
37
37
  ]
38
38
  },
39
39
  "devDependencies": {
40
- "@biomejs/biome": "^1.9.4",
40
+ "@biomejs/biome": "^2.0.6",
41
41
  "@types/figlet": "^1.5.5",
42
- "@types/node": "^24.0.3",
42
+ "@types/node": "^24.0.10",
43
43
  "@types/semver": "^7.3.13",
44
- "@vitest/coverage-v8": "^3.2.3",
44
+ "@vitest/coverage-v8": "^3.2.4",
45
45
  "husky": "^9.1.7",
46
46
  "lint-staged": "^16.1.2",
47
47
  "tsup": "^8.5.0",
48
48
  "tsx": "^4.20.3",
49
49
  "typescript": "^5.8.3",
50
- "vitest": "^3.2.3"
50
+ "vitest": "^3.2.4"
51
51
  },
52
52
  "dependencies": {
53
53
  "@manypkg/get-packages": "^3.0.0",
@@ -59,7 +59,7 @@
59
59
  "figlet": "^1.8.1",
60
60
  "git-semver-tags": "^8.0.0",
61
61
  "semver": "^7.7.2",
62
- "smol-toml": "^1.3.4"
62
+ "smol-toml": "^1.4.1"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "tsup src/index.ts --format esm,cjs --dts",