package-versioner 0.7.1 → 0.7.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
@@ -92,14 +92,14 @@ For detailed examples of how to use this in CI/CD pipelines, see [CI/CD Integrat
92
92
 
93
93
  ## Configuration
94
94
 
95
- Customize behavior by creating a `version.config.json` file in your project root:
95
+ Customize behaviour by creating a `version.config.json` file in your project root:
96
96
 
97
97
  ```json
98
98
  {
99
99
  "preset": "angular",
100
100
  "versionPrefix": "v",
101
- "tagTemplate": "${prefix}${version}",
102
- "packageTagTemplate": "${packageName}@${prefix}${version}",
101
+ "tagTemplate": "${packageName}@${prefix}${version}",
102
+ "packageSpecificTags": true,
103
103
  "commitMessage": "chore: release ${packageName}@${version} [skip ci]",
104
104
  "updateChangelog": true,
105
105
  "changelogFormat": "keep-a-changelog",
@@ -108,7 +108,7 @@ Customize behavior by creating a `version.config.json` file in your project root
108
108
  "docs",
109
109
  "e2e"
110
110
  ],
111
- "packages": ["packages/*"],
111
+ "packages": ["@mycompany/*"],
112
112
  "mainPackage": "primary-package",
113
113
  "cargo": {
114
114
  "enabled": true,
@@ -133,13 +133,92 @@ Customize behavior by creating a `version.config.json` file in your project root
133
133
  #### Monorepo-Specific Options
134
134
  - `synced`: Whether all packages should be versioned together (default: true)
135
135
  - `skip`: Array of package names to exclude from versioning
136
- - `packages`: Glob patterns for package discovery (e.g., ["packages/*"])
136
+ - `packages`: Array of package names or patterns to target for versioning. Supports exact names, scope wildcards, and global wildcards (e.g., ["@scope/package-a", "@scope/*", "*"])
137
137
  - `mainPackage`: Package name whose commit history should drive version determination
138
- - `packageTagTemplate`: Template for package-specific Git tags (default: "${packageName}@${prefix}${version}")
138
+ - `packageSpecificTags`: Whether to enable package-specific tagging behaviour (default: false)
139
139
  - `updateInternalDependencies`: How to update internal dependencies ("patch", "minor", "major", or "inherit")
140
140
 
141
141
  For more details on CI/CD integration and advanced usage, see [CI/CD Integration](./docs/CI_CD_INTEGRATION.md).
142
142
 
143
+ ### Package Targeting
144
+
145
+ The `packages` configuration option allows you to specify which packages should be processed for versioning. It supports several pattern types:
146
+
147
+ #### Exact Package Names
148
+ ```json
149
+ {
150
+ "packages": ["@mycompany/core", "@mycompany/utils", "standalone-package"]
151
+ }
152
+ ```
153
+
154
+ #### Scope Wildcards
155
+ Target all packages within a specific scope:
156
+ ```json
157
+ {
158
+ "packages": ["@mycompany/*"]
159
+ }
160
+ ```
161
+
162
+ #### Global Wildcard
163
+ Target all packages in the workspace:
164
+ ```json
165
+ {
166
+ "packages": ["*"]
167
+ }
168
+ ```
169
+
170
+ #### Mixed Patterns
171
+ Combine different pattern types:
172
+ ```json
173
+ {
174
+ "packages": ["@mycompany/*", "@utils/logger", "legacy-package"]
175
+ }
176
+ ```
177
+
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.
179
+
180
+ ### Package-Specific Tagging
181
+
182
+ The `packageSpecificTags` option controls whether the tool creates and searches for package-specific Git tags:
183
+
184
+ - **When `false` (default)**: Creates global tags like `v1.2.3` and searches for the latest global tag
185
+ - **When `true`**: Creates package-specific tags like `@scope/package-a@v1.2.3` and searches for package-specific tags
186
+
187
+ This option works in conjunction with `tagTemplate` to control tag formatting. The `tagTemplate` is used for all tag creation, with the `packageSpecificTags` boolean controlling whether the `${packageName}` variable is populated:
188
+
189
+ - When `packageSpecificTags` is `false`: The `${packageName}` variable is empty, so templates should use `${prefix}${version}`
190
+ - When `packageSpecificTags` is `true`: The `${packageName}` variable contains the package name
191
+
192
+ **Examples:**
193
+
194
+ For single-package repositories or synced monorepos:
195
+ ```json
196
+ {
197
+ "packageSpecificTags": true,
198
+ "tagTemplate": "${packageName}@${prefix}${version}"
199
+ }
200
+ ```
201
+ Creates tags like `my-package@v1.2.3`
202
+
203
+ For global versioning:
204
+ ```json
205
+ {
206
+ "packageSpecificTags": false,
207
+ "tagTemplate": "${prefix}${version}"
208
+ }
209
+ ```
210
+ Creates tags like `v1.2.3`
211
+
212
+ **Important Notes:**
213
+ - In **synced mode** with a single package, `packageSpecificTags: true` will use the package name even though all packages are versioned together
214
+ - In **synced mode** with multiple packages, package names are not used regardless of the setting
215
+ - In **async mode**, each package gets its own tag when `packageSpecificTags` is enabled
216
+
217
+ With package-specific tagging enabled, the tool will:
218
+ 1. Look for existing tags matching the configured pattern for each package
219
+ 2. Create new tags using the same pattern when releasing
220
+ 3. Fall back to global tag lookup if no package-specific tags are found
221
+
143
222
  ## How Versioning Works
144
223
 
145
224
  `package-versioner` determines the next version based on your configuration (`version.config.json`). The two main approaches are:
package/dist/index.cjs CHANGED
@@ -138,7 +138,23 @@ function extractChangelogEntriesFromCommits(projectDir, revisionRange) {
138
138
  const commits = output.split("---COMMIT_DELIMITER---").filter((commit) => commit.trim() !== "");
139
139
  return commits.map((commit) => parseCommitMessage(commit)).filter((entry) => entry !== null);
140
140
  } catch (error) {
141
- log(`Error extracting commits: ${error}`, "error");
141
+ const errorMessage = error instanceof Error ? error.message : String(error);
142
+ if (errorMessage.includes("ambiguous argument") && errorMessage.includes("unknown revision")) {
143
+ const tagName = revisionRange.split("..")[0] || revisionRange;
144
+ if (tagName.startsWith("v") && !tagName.includes("@")) {
145
+ log(
146
+ `Error: Tag "${tagName}" not found. If you're using package-specific tags (like "package-name@v1.0.0"), you may need to configure "tagTemplate" in your version.config.json to use: \${packageName}@\${prefix}\${version}`,
147
+ "error"
148
+ );
149
+ } else {
150
+ log(
151
+ `Error: Tag or revision "${tagName}" not found in the repository. Please check if this tag exists or if you need to fetch it from the remote.`,
152
+ "error"
153
+ );
154
+ }
155
+ } else {
156
+ log(`Error extracting commits: ${errorMessage}`, "error");
157
+ }
142
158
  return [];
143
159
  }
144
160
  }
@@ -788,38 +804,42 @@ var import_git_semver_tags = require("git-semver-tags");
788
804
  function escapeRegExp(string) {
789
805
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
790
806
  }
791
- function formatTag(version, versionPrefix, packageName, tagTemplate = "${prefix}${version}", packageTagTemplate = "${packageName}@${prefix}${version}") {
792
- const variables = {
793
- version,
794
- prefix: versionPrefix || "",
795
- packageName: packageName || ""
796
- };
797
- const template = packageName ? packageTagTemplate : tagTemplate;
798
- return createTemplateString(template, variables);
799
- }
800
- function formatVersionPrefix(versionPrefix, scope) {
801
- if (!versionPrefix) return "";
802
- const cleanPrefix = versionPrefix.replace(/\/$/, "");
803
- if (scope) {
804
- return `${cleanPrefix}/${scope}`;
805
- }
806
- return cleanPrefix;
807
- }
808
- function formatCommitMessage(template, version, packageName, scope) {
809
- return createTemplateString(template, {
810
- version,
811
- scope,
812
- packageName: packageName || ""
813
- });
807
+ function formatVersionPrefix(prefix) {
808
+ return prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
814
809
  }
815
- function createTemplateString(template, variables) {
816
- return Object.entries(variables).reduce((result, [key, value]) => {
817
- if (value === void 0) {
818
- return result;
810
+ function formatTag(version, prefix, packageName, template, packageSpecificTags) {
811
+ if ((template == null ? void 0 : template.includes("${packageName}")) && !packageName) {
812
+ log(
813
+ 'Warning: Your tagTemplate contains ${packageName} but no package name is available.\nThis will result in an empty package name in the tag (e.g., "@v1.0.0" instead of "my-package@v1.0.0").\n\nTo fix this:\n\u2022 If using synced mode: Set "packageSpecificTags": true in your config to enable package names in tags\n\u2022 If you want global tags: Remove ${packageName} from your tagTemplate (e.g., use "${prefix}${version}")\n\u2022 If using single/async mode: Ensure your package.json has a valid "name" field',
814
+ "warning"
815
+ );
816
+ }
817
+ if (template) {
818
+ return template.replace(/\$\{version\}/g, version).replace(/\$\{prefix\}/g, prefix).replace(/\$\{packageName\}/g, packageName || "");
819
+ }
820
+ if (packageSpecificTags && packageName) {
821
+ return `${packageName}@${prefix}${version}`;
822
+ }
823
+ return `${prefix}${version}`;
824
+ }
825
+ function formatCommitMessage(template, version, packageName, additionalContext) {
826
+ if (template.includes("${packageName}") && !packageName) {
827
+ log(
828
+ 'Warning: Your commitMessage template contains ${packageName} but no package name is available.\nThis will result in an empty package name in the commit message (e.g., "Release @v1.0.0").\n\nTo fix this:\n\u2022 If using synced mode: Set "packageSpecificTags": true to enable package names in commits\n\u2022 If you want generic commit messages: Remove ${packageName} from your commitMessage template\n\u2022 If using single/async mode: Ensure your package.json has a valid "name" field',
829
+ "warning"
830
+ );
831
+ }
832
+ let result = template.replace(/\$\{version\}/g, version).replace(/\$\{packageName\}/g, packageName || "");
833
+ if (additionalContext) {
834
+ for (const [key, value] of Object.entries(additionalContext)) {
835
+ const placeholder = `\${${key}}`;
836
+ result = result.replace(
837
+ new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g"),
838
+ value
839
+ );
819
840
  }
820
- const regex = new RegExp(`\\$\\{${key}\\}`, "g");
821
- return result.replace(regex, value);
822
- }, template);
841
+ }
842
+ return result;
823
843
  }
824
844
 
825
845
  // src/git/tagsAndBranches.ts
@@ -862,56 +882,71 @@ async function lastMergeBranchName(branches, baseBranch) {
862
882
  return null;
863
883
  }
864
884
  }
865
- async function getLatestTagForPackage(packageName, versionPrefix) {
885
+ async function getLatestTagForPackage(packageName, versionPrefix, options) {
866
886
  try {
887
+ const tagTemplate = (options == null ? void 0 : options.tagTemplate) || "${prefix}${version}";
888
+ const packageSpecificTags = (options == null ? void 0 : options.packageSpecificTags) ?? false;
867
889
  const escapedPackageName = escapeRegExp(packageName);
890
+ const escapedPrefix = versionPrefix ? escapeRegExp(versionPrefix) : "";
868
891
  log(
869
- `Looking for tags for package ${packageName} with prefix ${versionPrefix || "none"}`,
892
+ `Looking for tags for package ${packageName} with prefix ${versionPrefix || "none"}, packageSpecificTags: ${packageSpecificTags}`,
870
893
  "debug"
871
894
  );
872
895
  const allTags = await (0, import_git_semver_tags.getSemverTags)({
873
896
  tagPrefix: versionPrefix
874
897
  });
875
898
  log(`Retrieved ${allTags.length} tags: ${allTags.join(", ")}`, "debug");
876
- let packageTags = [];
877
- if (versionPrefix) {
878
- const pattern1 = new RegExp(`^${escapedPackageName}@${escapeRegExp(versionPrefix)}`);
879
- packageTags = allTags.filter((tag) => pattern1.test(tag));
899
+ if (packageSpecificTags) {
900
+ const packageTagPattern = escapeRegExp(tagTemplate).replace(/\\\$\\\{packageName\\\}/g, `(?:${escapedPackageName})`).replace(/\\\$\\\{prefix\\\}/g, `(?:${escapedPrefix})`).replace(/\\\$\\\{version\\\}/g, "(?:[0-9]+\\.[0-9]+\\.[0-9]+(?:-[a-zA-Z0-9.-]+)?)");
901
+ log(`Using package tag pattern: ${packageTagPattern}`, "debug");
902
+ const packageTagRegex = new RegExp(`^${packageTagPattern}$`);
903
+ let packageTags = allTags.filter((tag) => packageTagRegex.test(tag));
880
904
  if (packageTags.length > 0) {
881
- log(
882
- `Found ${packageTags.length} package tags using pattern: packageName@${versionPrefix}...`,
883
- "debug"
884
- );
905
+ log(`Found ${packageTags.length} package tags using configured pattern`, "debug");
885
906
  log(`Using tag: ${packageTags[0]}`, "debug");
886
907
  return packageTags[0];
887
908
  }
888
- }
889
- if (versionPrefix) {
890
- const pattern2 = new RegExp(`^${escapeRegExp(versionPrefix)}${escapedPackageName}@`);
891
- packageTags = allTags.filter((tag) => pattern2.test(tag));
892
- if (packageTags.length > 0) {
893
- log(
894
- `Found ${packageTags.length} package tags using pattern: ${versionPrefix}packageName@...`,
895
- "debug"
896
- );
897
- log(`Using tag: ${packageTags[0]}`, "debug");
898
- return packageTags[0];
909
+ if (versionPrefix) {
910
+ const pattern1 = new RegExp(`^${escapedPackageName}@${escapeRegExp(versionPrefix)}`);
911
+ packageTags = allTags.filter((tag) => pattern1.test(tag));
912
+ if (packageTags.length > 0) {
913
+ log(
914
+ `Found ${packageTags.length} package tags using pattern: packageName@${versionPrefix}...`,
915
+ "debug"
916
+ );
917
+ log(`Using tag: ${packageTags[0]}`, "debug");
918
+ return packageTags[0];
919
+ }
899
920
  }
900
- }
901
- const pattern3 = new RegExp(`^${escapedPackageName}@`);
902
- packageTags = allTags.filter((tag) => pattern3.test(tag));
903
- log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
904
- if (packageTags.length === 0) {
905
- log("No matching tags found for pattern: packageName@version", "debug");
906
- if (allTags.length > 0) {
907
- log(`Available tags: ${allTags.join(", ")}`, "debug");
921
+ if (versionPrefix) {
922
+ const pattern2 = new RegExp(`^${escapeRegExp(versionPrefix)}${escapedPackageName}@`);
923
+ packageTags = allTags.filter((tag) => pattern2.test(tag));
924
+ if (packageTags.length > 0) {
925
+ log(
926
+ `Found ${packageTags.length} package tags using pattern: ${versionPrefix}packageName@...`,
927
+ "debug"
928
+ );
929
+ log(`Using tag: ${packageTags[0]}`, "debug");
930
+ return packageTags[0];
931
+ }
932
+ }
933
+ const pattern3 = new RegExp(`^${escapedPackageName}@`);
934
+ packageTags = allTags.filter((tag) => pattern3.test(tag));
935
+ log(`Found ${packageTags.length} package tags for ${packageName}`, "debug");
936
+ if (packageTags.length === 0) {
937
+ log("No matching tags found for pattern: packageName@version", "debug");
938
+ if (allTags.length > 0) {
939
+ log(`Available tags: ${allTags.join(", ")}`, "debug");
940
+ } else {
941
+ log("No tags available in the repository", "debug");
942
+ }
908
943
  } else {
909
- log("No tags available in the repository", "debug");
944
+ log(`Using tag: ${packageTags[0]}`, "debug");
910
945
  }
911
- } else {
912
- log(`Using tag: ${packageTags[0]}`, "debug");
946
+ return packageTags[0] || "";
913
947
  }
914
- return packageTags[0] || "";
948
+ log(`Package-specific tags disabled for ${packageName}, falling back to global tags`, "debug");
949
+ return "";
915
950
  } catch (error) {
916
951
  const errorMessage = error instanceof Error ? error.message : String(error);
917
952
  log(`Failed to get latest tag for package ${packageName}: ${errorMessage}`, "error");
@@ -1446,32 +1481,90 @@ function bumpVersion(currentVersion, bumpType, prereleaseIdentifier) {
1446
1481
  // src/core/versionCalculator.ts
1447
1482
  async function calculateVersion(config, options) {
1448
1483
  const {
1449
- latestTag = "",
1450
- type,
1451
- versionPrefix = "",
1484
+ type: configType,
1485
+ preset = "angular",
1486
+ versionPrefix,
1487
+ prereleaseIdentifier: configPrereleaseIdentifier,
1452
1488
  branchPattern,
1453
- baseBranch,
1454
- prereleaseIdentifier,
1489
+ baseBranch
1490
+ } = config;
1491
+ const {
1492
+ latestTag,
1493
+ name,
1455
1494
  path: pkgPath,
1456
- name
1495
+ type: optionsType,
1496
+ prereleaseIdentifier: optionsPrereleaseIdentifier
1457
1497
  } = options;
1458
- const preset = config.preset || "conventional-commits";
1498
+ const type = optionsType || configType;
1499
+ const prereleaseIdentifier = optionsPrereleaseIdentifier || configPrereleaseIdentifier;
1459
1500
  const initialVersion = "0.1.0";
1501
+ const hasNoTags = !latestTag || latestTag.trim() === "";
1460
1502
  const normalizedPrereleaseId = normalizePrereleaseIdentifier(prereleaseIdentifier, config);
1461
1503
  try {
1462
1504
  let determineTagSearchPattern2 = function(packageName, prefix) {
1463
- if (packageName) {
1464
- const escapedPackageName = escapeRegExp(packageName);
1465
- const escapedPrefix = escapeRegExp(prefix);
1466
- return `${escapedPackageName}[@]?${escapedPrefix}`;
1505
+ if (!packageName) {
1506
+ return prefix;
1467
1507
  }
1468
- return escapeRegExp(prefix);
1508
+ return `${packageName}@${prefix}`;
1509
+ }, escapeRegExp3 = function(string) {
1510
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1469
1511
  };
1470
- var determineTagSearchPattern = determineTagSearchPattern2;
1471
- const hasNoTags = !latestTag;
1472
- const originalPrefix = versionPrefix;
1512
+ var determineTagSearchPattern = determineTagSearchPattern2, escapeRegExp2 = escapeRegExp3;
1513
+ const originalPrefix = versionPrefix || "";
1473
1514
  const tagSearchPattern = determineTagSearchPattern2(name, originalPrefix);
1474
- const escapedTagPattern = escapeRegExp(tagSearchPattern);
1515
+ const escapedTagPattern = escapeRegExp3(tagSearchPattern);
1516
+ if (!hasNoTags && pkgPath) {
1517
+ const packageDir = pkgPath || (0, import_node_process3.cwd)();
1518
+ const manifestResult = getVersionFromManifests(packageDir);
1519
+ 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";
1522
+ const packageVersion = manifestResult.version;
1523
+ if (import_semver2.default.gt(packageVersion, tagVersion)) {
1524
+ log(
1525
+ `Warning: Version mismatch detected!
1526
+ \u2022 ${manifestResult.manifestType} version: ${packageVersion}
1527
+ \u2022 Latest Git tag version: ${tagVersion} (from ${latestTag})
1528
+ \u2022 Package version is AHEAD of Git tags
1529
+
1530
+ This usually happens when:
1531
+ \u2022 A version was released but the tag wasn't pushed to the remote repository
1532
+ \u2022 The ${manifestResult.manifestType} was manually updated without creating a corresponding tag
1533
+ \u2022 You're running in CI and the latest tag isn't available yet
1534
+
1535
+ The tool will use the Git tag version (${tagVersion}) as the base for calculation.
1536
+ Expected next version will be based on ${tagVersion}, not ${packageVersion}.
1537
+
1538
+ To fix this mismatch:
1539
+ \u2022 Push missing tags: git push origin --tags
1540
+ \u2022 Or use package version as base by ensuring tags are up to date`,
1541
+ "warning"
1542
+ );
1543
+ } else if (import_semver2.default.gt(tagVersion, packageVersion)) {
1544
+ log(
1545
+ `Warning: Version mismatch detected!
1546
+ \u2022 ${manifestResult.manifestType} version: ${packageVersion}
1547
+ \u2022 Latest Git tag version: ${tagVersion} (from ${latestTag})
1548
+ \u2022 Git tag version is AHEAD of package version
1549
+
1550
+ This usually happens when:
1551
+ \u2022 A release was tagged but the ${manifestResult.manifestType} wasn't updated
1552
+ \u2022 You're on an older branch that hasn't been updated with the latest version
1553
+ \u2022 Automated release process created tags but didn't update manifest files
1554
+ \u2022 You pulled tags but not the corresponding commits that update the package version
1555
+
1556
+ The tool will use the Git tag version (${tagVersion}) as the base for calculation.
1557
+ This will likely result in a version that's already been released.
1558
+
1559
+ To fix this mismatch:
1560
+ \u2022 Update ${manifestResult.manifestType}: Set version to ${tagVersion} or higher
1561
+ \u2022 Or checkout the branch/commit that corresponds to the tag
1562
+ \u2022 Or ensure your branch is up to date with the latest changes`,
1563
+ "warning"
1564
+ );
1565
+ }
1566
+ }
1567
+ }
1475
1568
  const specifiedType = type;
1476
1569
  if (specifiedType) {
1477
1570
  if (hasNoTags) {
@@ -1618,13 +1711,39 @@ function calculateNextVersion(version, manifestType, name, releaseType, prerelea
1618
1711
  return result || initialVersion;
1619
1712
  }
1620
1713
 
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
+
1621
1741
  // src/package/packageProcessor.ts
1622
1742
  var PackageProcessor = class {
1623
1743
  skip;
1624
1744
  targets;
1625
1745
  versionPrefix;
1626
1746
  tagTemplate;
1627
- packageTagTemplate;
1628
1747
  commitMessageTemplate;
1629
1748
  dryRun;
1630
1749
  skipHooks;
@@ -1637,7 +1756,6 @@ var PackageProcessor = class {
1637
1756
  this.targets = options.targets || [];
1638
1757
  this.versionPrefix = options.versionPrefix || "v";
1639
1758
  this.tagTemplate = options.tagTemplate;
1640
- this.packageTagTemplate = options.packageTagTemplate;
1641
1759
  this.commitMessageTemplate = options.commitMessageTemplate || "";
1642
1760
  this.dryRun = options.dryRun || false;
1643
1761
  this.skipHooks = options.skipHooks || false;
@@ -1665,18 +1783,15 @@ var PackageProcessor = class {
1665
1783
  const pkgsToConsider = packages.filter((pkg) => {
1666
1784
  var _a2;
1667
1785
  const pkgName = pkg.packageJson.name;
1668
- if ((_a2 = this.skip) == null ? void 0 : _a2.includes(pkgName)) {
1669
- log(`Skipping package ${pkgName} as it's in the skip list.`, "info");
1670
- return false;
1671
- }
1672
- if (!this.targets || this.targets.length === 0) {
1673
- return true;
1674
- }
1675
- const isTargeted = this.targets.includes(pkgName);
1676
- if (!isTargeted) {
1677
- log(`Package ${pkgName} not in target list, skipping.`, "info");
1786
+ const shouldProcess = shouldProcessPackage(pkgName, this.targets, this.skip);
1787
+ 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
+ }
1678
1793
  }
1679
- return isTargeted;
1794
+ return shouldProcess;
1680
1795
  });
1681
1796
  log(`Found ${pkgsToConsider.length} targeted package(s) to process after filtering.`, "info");
1682
1797
  if (pkgsToConsider.length === 0) {
@@ -1689,7 +1804,10 @@ var PackageProcessor = class {
1689
1804
  const formattedPrefix = formatVersionPrefix(this.versionPrefix);
1690
1805
  let latestTagResult = "";
1691
1806
  try {
1692
- latestTagResult = await getLatestTagForPackage(name, this.versionPrefix);
1807
+ latestTagResult = await getLatestTagForPackage(name, this.versionPrefix, {
1808
+ tagTemplate: this.tagTemplate,
1809
+ packageSpecificTags: this.fullConfig.packageSpecificTags
1810
+ });
1693
1811
  } catch (error) {
1694
1812
  const errorMessage = error instanceof Error ? error.message : String(error);
1695
1813
  log(
@@ -1821,7 +1939,7 @@ var PackageProcessor = class {
1821
1939
  this.versionPrefix,
1822
1940
  name,
1823
1941
  this.tagTemplate,
1824
- this.packageTagTemplate
1942
+ this.fullConfig.packageSpecificTags
1825
1943
  );
1826
1944
  const tagMessage = `chore(release): ${name} ${nextVersion}`;
1827
1945
  addTag(packageTag);
@@ -1907,16 +2025,9 @@ var PackageProcessor = class {
1907
2025
  };
1908
2026
 
1909
2027
  // src/core/versionStrategies.ts
1910
- function shouldProcessPackage(pkg, config, targets = []) {
1911
- var _a;
2028
+ function shouldProcessPackage2(pkg, config, targets = []) {
1912
2029
  const pkgName = pkg.packageJson.name;
1913
- if ((_a = config.skip) == null ? void 0 : _a.includes(pkgName)) {
1914
- return false;
1915
- }
1916
- if (!targets || targets.length === 0) {
1917
- return true;
1918
- }
1919
- return targets.includes(pkgName);
2030
+ return shouldProcessPackage(pkgName, targets, config.skip);
1920
2031
  }
1921
2032
  function createSyncedStrategy(config) {
1922
2033
  return async (packages) => {
@@ -1972,6 +2083,7 @@ function createSyncedStrategy(config) {
1972
2083
  }
1973
2084
  const files = [];
1974
2085
  const updatedPackages = [];
2086
+ const processedPaths = /* @__PURE__ */ new Set();
1975
2087
  try {
1976
2088
  if (packages.root) {
1977
2089
  const rootPkgPath = path7.join(packages.root, "package.json");
@@ -1979,6 +2091,7 @@ function createSyncedStrategy(config) {
1979
2091
  updatePackageVersion(rootPkgPath, nextVersion);
1980
2092
  files.push(rootPkgPath);
1981
2093
  updatedPackages.push("root");
2094
+ processedPaths.add(rootPkgPath);
1982
2095
  }
1983
2096
  } else {
1984
2097
  log("Root package path is undefined, skipping root package.json update", "warning");
@@ -1988,13 +2101,17 @@ function createSyncedStrategy(config) {
1988
2101
  log(`Failed to update root package.json: ${errMessage}`, "error");
1989
2102
  }
1990
2103
  for (const pkg of packages.packages) {
1991
- if (!shouldProcessPackage(pkg, config)) {
2104
+ if (!shouldProcessPackage2(pkg, config)) {
1992
2105
  continue;
1993
2106
  }
1994
2107
  const packageJsonPath = path7.join(pkg.dir, "package.json");
2108
+ if (processedPaths.has(packageJsonPath)) {
2109
+ continue;
2110
+ }
1995
2111
  updatePackageVersion(packageJsonPath, nextVersion);
1996
2112
  files.push(packageJsonPath);
1997
2113
  updatedPackages.push(pkg.packageJson.name);
2114
+ processedPaths.add(packageJsonPath);
1998
2115
  }
1999
2116
  if (updatedPackages.length > 0) {
2000
2117
  log(`Updated ${updatedPackages.length} package(s) to version ${nextVersion}`, "success");
@@ -2002,8 +2119,25 @@ function createSyncedStrategy(config) {
2002
2119
  log("No packages were updated", "warning");
2003
2120
  return;
2004
2121
  }
2005
- const nextTag = formatTag(nextVersion, formattedPrefix, null, tagTemplate);
2006
- const formattedCommitMessage = formatCommitMessage(commitMessage, nextVersion);
2122
+ let tagPackageName = null;
2123
+ let commitPackageName = void 0;
2124
+ if (config.packageSpecificTags && packages.packages.length === 1) {
2125
+ tagPackageName = packages.packages[0].packageJson.name;
2126
+ commitPackageName = packages.packages[0].packageJson.name;
2127
+ }
2128
+ const nextTag = formatTag(
2129
+ nextVersion,
2130
+ formattedPrefix,
2131
+ tagPackageName,
2132
+ tagTemplate,
2133
+ config.packageSpecificTags || false
2134
+ );
2135
+ const formattedCommitMessage = formatCommitMessage(
2136
+ commitMessage,
2137
+ nextVersion,
2138
+ commitPackageName,
2139
+ void 0
2140
+ );
2007
2141
  await createGitCommitAndTag(files, nextTag, formattedCommitMessage, skipHooks, dryRun);
2008
2142
  } catch (error) {
2009
2143
  if (error instanceof VersionError || error instanceof GitError) {
@@ -2024,7 +2158,6 @@ function createSingleStrategy(config) {
2024
2158
  mainPackage,
2025
2159
  versionPrefix,
2026
2160
  tagTemplate,
2027
- packageTagTemplate,
2028
2161
  commitMessage = "chore(release): ${version}",
2029
2162
  dryRun,
2030
2163
  skipHooks
@@ -2046,7 +2179,10 @@ function createSingleStrategy(config) {
2046
2179
  }
2047
2180
  const pkgPath = pkg.dir;
2048
2181
  const formattedPrefix = formatVersionPrefix(versionPrefix || "v");
2049
- let latestTagResult = await getLatestTagForPackage(packageName, formattedPrefix);
2182
+ let latestTagResult = await getLatestTagForPackage(packageName, formattedPrefix, {
2183
+ tagTemplate,
2184
+ packageSpecificTags: config.packageSpecificTags
2185
+ });
2050
2186
  if (!latestTagResult) {
2051
2187
  const globalTagResult = await getLatestTag();
2052
2188
  latestTagResult = globalTagResult || "";
@@ -2077,7 +2213,7 @@ function createSingleStrategy(config) {
2077
2213
  formattedPrefix,
2078
2214
  packageName,
2079
2215
  tagTemplate,
2080
- packageTagTemplate
2216
+ config.packageSpecificTags
2081
2217
  );
2082
2218
  const formattedCommitMessage = formatCommitMessage(commitMessage, nextVersion, packageName);
2083
2219
  await createGitCommitAndTag(
@@ -2110,7 +2246,6 @@ function createAsyncStrategy(config) {
2110
2246
  targets: config.packages || [],
2111
2247
  versionPrefix: config.versionPrefix || "v",
2112
2248
  tagTemplate: config.tagTemplate,
2113
- packageTagTemplate: config.packageTagTemplate,
2114
2249
  commitMessageTemplate: config.commitMessage || "",
2115
2250
  dryRun: config.dryRun || false,
2116
2251
  skipHooks: config.skipHooks || false,