package-versioner 0.8.4 → 0.8.6
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 +3 -0
- package/dist/index.cjs +185 -161
- package/dist/index.js +185 -161
- package/docs/CI_CD_INTEGRATION.md +2 -1
- package/docs/changelogs.md +7 -0
- package/docs/versioning.md +114 -78
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -55,6 +55,9 @@ npx package-versioner --bump patch --prerelease alpha
|
|
|
55
55
|
# Target specific packages (only in async/independent mode, comma-separated)
|
|
56
56
|
npx package-versioner -t @scope/package-a,@scope/package-b
|
|
57
57
|
|
|
58
|
+
# Run from a different directory
|
|
59
|
+
npx package-versioner --project-dir /path/to/project
|
|
60
|
+
|
|
58
61
|
# Perform a dry run: calculates version, logs actions, but makes no file changes or Git commits/tags
|
|
59
62
|
npx package-versioner --dry-run
|
|
60
63
|
|
package/dist/index.cjs
CHANGED
|
@@ -472,7 +472,7 @@ function getAllVersionTags(since, versionPrefix = "v") {
|
|
|
472
472
|
const allTags = tagOutput.split("\n").filter((tag) => !!tag);
|
|
473
473
|
let filteredTags = allTags;
|
|
474
474
|
if (since) {
|
|
475
|
-
const sinceIndex = allTags.
|
|
475
|
+
const sinceIndex = allTags.indexOf(since);
|
|
476
476
|
if (sinceIndex >= 0) {
|
|
477
477
|
filteredTags = allTags.slice(sinceIndex);
|
|
478
478
|
} else {
|
|
@@ -556,7 +556,7 @@ async function regenerateChangelog(options) {
|
|
|
556
556
|
const allTagsCmd = `git tag --list "${versionPrefix}*" --sort=creatordate`;
|
|
557
557
|
const allTagsOutput = (0, import_node_child_process2.execSync)(allTagsCmd, { encoding: "utf8" }).trim();
|
|
558
558
|
const allTags = allTagsOutput.split("\n").filter((tag) => !!tag);
|
|
559
|
-
const sinceIndex = allTags.
|
|
559
|
+
const sinceIndex = allTags.indexOf(since);
|
|
560
560
|
const actualPreviousTag = sinceIndex > 0 ? allTags[sinceIndex - 1] : null;
|
|
561
561
|
if (actualPreviousTag) {
|
|
562
562
|
tagRange = `${actualPreviousTag}..${currentTag.tag}`;
|
|
@@ -694,8 +694,12 @@ function filterPackagesByConfig(packages, configTargets, workspaceRoot) {
|
|
|
694
694
|
for (const target of configTargets) {
|
|
695
695
|
const dirMatches = filterByDirectoryPattern(packages, target, workspaceRoot);
|
|
696
696
|
const nameMatches = filterByPackageNamePattern(packages, target);
|
|
697
|
-
|
|
698
|
-
|
|
697
|
+
for (const pkg of dirMatches) {
|
|
698
|
+
matchedPackages.add(pkg);
|
|
699
|
+
}
|
|
700
|
+
for (const pkg of nameMatches) {
|
|
701
|
+
matchedPackages.add(pkg);
|
|
702
|
+
}
|
|
699
703
|
}
|
|
700
704
|
return Array.from(matchedPackages);
|
|
701
705
|
}
|
|
@@ -759,7 +763,7 @@ function matchesPackageNamePattern(packageName, pattern) {
|
|
|
759
763
|
}
|
|
760
764
|
|
|
761
765
|
// src/core/versionStrategies.ts
|
|
762
|
-
var
|
|
766
|
+
var import_node_child_process4 = require("child_process");
|
|
763
767
|
var import_node_fs7 = __toESM(require("fs"), 1);
|
|
764
768
|
var path8 = __toESM(require("path"), 1);
|
|
765
769
|
|
|
@@ -1041,9 +1045,14 @@ function formatCommitMessage(template, version, packageName, additionalContext)
|
|
|
1041
1045
|
}
|
|
1042
1046
|
|
|
1043
1047
|
// src/git/tagsAndBranches.ts
|
|
1044
|
-
function getCommitsLength(pkgRoot) {
|
|
1048
|
+
function getCommitsLength(pkgRoot, sinceTag) {
|
|
1045
1049
|
try {
|
|
1046
|
-
|
|
1050
|
+
let gitCommand;
|
|
1051
|
+
if (sinceTag && sinceTag.trim() !== "") {
|
|
1052
|
+
gitCommand = `git rev-list --count ${sinceTag}..HEAD ${pkgRoot}`;
|
|
1053
|
+
} else {
|
|
1054
|
+
gitCommand = `git rev-list --count HEAD ^$(git describe --tags --abbrev=0) ${pkgRoot}`;
|
|
1055
|
+
}
|
|
1047
1056
|
const amount = execSync3(gitCommand).toString().trim();
|
|
1048
1057
|
return Number(amount);
|
|
1049
1058
|
} catch (error) {
|
|
@@ -1317,7 +1326,6 @@ function updatePackageVersion(packagePath, version) {
|
|
|
1317
1326
|
}
|
|
1318
1327
|
|
|
1319
1328
|
// src/package/packageProcessor.ts
|
|
1320
|
-
var import_node_child_process4 = require("child_process");
|
|
1321
1329
|
var fs8 = __toESM(require("fs"), 1);
|
|
1322
1330
|
var import_node_path7 = __toESM(require("path"), 1);
|
|
1323
1331
|
var import_node_process4 = require("process");
|
|
@@ -1376,18 +1384,41 @@ function getVersionFromManifests(packageDir) {
|
|
|
1376
1384
|
manifestType: null
|
|
1377
1385
|
};
|
|
1378
1386
|
}
|
|
1379
|
-
function throwIfNoManifestsFound(packageDir) {
|
|
1380
|
-
const packageJsonPath = import_node_path6.default.join(packageDir, "package.json");
|
|
1381
|
-
const cargoTomlPath = import_node_path6.default.join(packageDir, "Cargo.toml");
|
|
1382
|
-
throw new Error(
|
|
1383
|
-
`Neither package.json nor Cargo.toml found at ${packageDir}. Checked paths: ${packageJsonPath}, ${cargoTomlPath}. Cannot determine version.`
|
|
1384
|
-
);
|
|
1385
|
-
}
|
|
1386
1387
|
|
|
1387
1388
|
// src/utils/versionUtils.ts
|
|
1388
1389
|
var import_node_fs6 = __toESM(require("fs"), 1);
|
|
1389
1390
|
var import_semver2 = __toESM(require("semver"), 1);
|
|
1390
1391
|
var TOML2 = __toESM(require("smol-toml"), 1);
|
|
1392
|
+
|
|
1393
|
+
// src/git/tagVerification.ts
|
|
1394
|
+
function verifyTag(tagName, cwd5) {
|
|
1395
|
+
if (!tagName || tagName.trim() === "") {
|
|
1396
|
+
return { exists: false, reachable: false, error: "Empty tag name" };
|
|
1397
|
+
}
|
|
1398
|
+
try {
|
|
1399
|
+
execSync3(`git rev-parse --verify "${tagName}"`, {
|
|
1400
|
+
cwd: cwd5,
|
|
1401
|
+
stdio: "ignore"
|
|
1402
|
+
});
|
|
1403
|
+
return { exists: true, reachable: true };
|
|
1404
|
+
} catch (error) {
|
|
1405
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1406
|
+
if (errorMessage.includes("unknown revision") || errorMessage.includes("bad revision") || errorMessage.includes("No such ref")) {
|
|
1407
|
+
return {
|
|
1408
|
+
exists: false,
|
|
1409
|
+
reachable: false,
|
|
1410
|
+
error: `Tag '${tagName}' not found in repository`
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
return {
|
|
1414
|
+
exists: false,
|
|
1415
|
+
reachable: false,
|
|
1416
|
+
error: `Git error: ${errorMessage}`
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// src/utils/versionUtils.ts
|
|
1391
1422
|
var STANDARD_BUMP_TYPES = ["major", "minor", "patch"];
|
|
1392
1423
|
function normalizePrereleaseIdentifier(prereleaseIdentifier, config) {
|
|
1393
1424
|
if (prereleaseIdentifier === true) {
|
|
@@ -1421,6 +1452,59 @@ function bumpVersion(currentVersion, bumpType, prereleaseIdentifier) {
|
|
|
1421
1452
|
}
|
|
1422
1453
|
return import_semver2.default.inc(currentVersion, bumpType, prereleaseIdentifier) || "";
|
|
1423
1454
|
}
|
|
1455
|
+
async function getBestVersionSource(tagName, packageVersion, cwd5) {
|
|
1456
|
+
if (!(tagName == null ? void 0 : tagName.trim())) {
|
|
1457
|
+
return packageVersion ? { source: "package", version: packageVersion, reason: "No git tag provided" } : { source: "initial", version: "0.1.0", reason: "No git tag or package version available" };
|
|
1458
|
+
}
|
|
1459
|
+
const verification = verifyTag(tagName, cwd5);
|
|
1460
|
+
if (!verification.exists || !verification.reachable) {
|
|
1461
|
+
if (packageVersion) {
|
|
1462
|
+
log(
|
|
1463
|
+
`Git tag '${tagName}' unreachable (${verification.error}), using package version: ${packageVersion}`,
|
|
1464
|
+
"warning"
|
|
1465
|
+
);
|
|
1466
|
+
return { source: "package", version: packageVersion, reason: "Git tag unreachable" };
|
|
1467
|
+
}
|
|
1468
|
+
log(
|
|
1469
|
+
`Git tag '${tagName}' unreachable and no package version available, using initial version`,
|
|
1470
|
+
"warning"
|
|
1471
|
+
);
|
|
1472
|
+
return {
|
|
1473
|
+
source: "initial",
|
|
1474
|
+
version: "0.1.0",
|
|
1475
|
+
reason: "Git tag unreachable, no package version"
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
if (!packageVersion) {
|
|
1479
|
+
return {
|
|
1480
|
+
source: "git",
|
|
1481
|
+
version: tagName,
|
|
1482
|
+
reason: "Git tag exists, no package version to compare"
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
1485
|
+
try {
|
|
1486
|
+
const cleanTagVersion = tagName.replace(/^.*?([0-9])/, "$1");
|
|
1487
|
+
const cleanPackageVersion = packageVersion;
|
|
1488
|
+
if (import_semver2.default.gt(cleanPackageVersion, cleanTagVersion)) {
|
|
1489
|
+
log(
|
|
1490
|
+
`Package version ${packageVersion} is newer than git tag ${tagName}, using package version`,
|
|
1491
|
+
"info"
|
|
1492
|
+
);
|
|
1493
|
+
return { source: "package", version: packageVersion, reason: "Package version is newer" };
|
|
1494
|
+
}
|
|
1495
|
+
if (import_semver2.default.gt(cleanTagVersion, cleanPackageVersion)) {
|
|
1496
|
+
log(
|
|
1497
|
+
`Git tag ${tagName} is newer than package version ${packageVersion}, using git tag`,
|
|
1498
|
+
"info"
|
|
1499
|
+
);
|
|
1500
|
+
return { source: "git", version: tagName, reason: "Git tag is newer" };
|
|
1501
|
+
}
|
|
1502
|
+
return { source: "git", version: tagName, reason: "Versions equal, using git tag" };
|
|
1503
|
+
} catch (error) {
|
|
1504
|
+
log(`Failed to compare versions, defaulting to git tag: ${error}`, "warning");
|
|
1505
|
+
return { source: "git", version: tagName, reason: "Version comparison failed" };
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1424
1508
|
|
|
1425
1509
|
// src/core/versionCalculator.ts
|
|
1426
1510
|
async function calculateVersion(config, options) {
|
|
@@ -1452,84 +1536,50 @@ async function calculateVersion(config, options) {
|
|
|
1452
1536
|
return `${packageName}@${prefix}`;
|
|
1453
1537
|
}, escapeRegExp3 = function(string) {
|
|
1454
1538
|
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1539
|
+
}, getCurrentVersionFromSource2 = function() {
|
|
1540
|
+
if (!versionSource) {
|
|
1541
|
+
if (hasNoTags) {
|
|
1542
|
+
return initialVersion;
|
|
1543
|
+
}
|
|
1544
|
+
const cleanedTag = import_semver3.default.clean(latestTag) || latestTag;
|
|
1545
|
+
return import_semver3.default.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
|
|
1546
|
+
}
|
|
1547
|
+
if (versionSource.source === "git") {
|
|
1548
|
+
const cleanedTag = import_semver3.default.clean(versionSource.version) || versionSource.version;
|
|
1549
|
+
return import_semver3.default.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
|
|
1550
|
+
}
|
|
1551
|
+
return versionSource.version;
|
|
1455
1552
|
};
|
|
1456
|
-
var determineTagSearchPattern = determineTagSearchPattern2, escapeRegExp2 = escapeRegExp3;
|
|
1553
|
+
var determineTagSearchPattern = determineTagSearchPattern2, escapeRegExp2 = escapeRegExp3, getCurrentVersionFromSource = getCurrentVersionFromSource2;
|
|
1457
1554
|
const originalPrefix = versionPrefix || "";
|
|
1458
1555
|
const tagSearchPattern = determineTagSearchPattern2(name, originalPrefix);
|
|
1459
1556
|
const escapedTagPattern = escapeRegExp3(tagSearchPattern);
|
|
1460
|
-
|
|
1557
|
+
let versionSource;
|
|
1558
|
+
if (pkgPath) {
|
|
1461
1559
|
const packageDir = pkgPath || (0, import_node_process3.cwd)();
|
|
1462
1560
|
const manifestResult = getVersionFromManifests(packageDir);
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
const packageVersion = manifestResult.version;
|
|
1467
|
-
if (import_semver3.default.gt(packageVersion, tagVersion)) {
|
|
1468
|
-
log(
|
|
1469
|
-
`Warning: Version mismatch detected!
|
|
1470
|
-
\u2022 ${manifestResult.manifestType} version: ${packageVersion}
|
|
1471
|
-
\u2022 Latest Git tag version: ${tagVersion} (from ${latestTag})
|
|
1472
|
-
\u2022 Package version is AHEAD of Git tags
|
|
1473
|
-
|
|
1474
|
-
This usually happens when:
|
|
1475
|
-
\u2022 A version was released but the tag wasn't pushed to the remote repository
|
|
1476
|
-
\u2022 The ${manifestResult.manifestType} was manually updated without creating a corresponding tag
|
|
1477
|
-
\u2022 You're running in CI and the latest tag isn't available yet
|
|
1478
|
-
|
|
1479
|
-
The tool will use the Git tag version (${tagVersion}) as the base for calculation.
|
|
1480
|
-
Expected next version will be based on ${tagVersion}, not ${packageVersion}.
|
|
1481
|
-
|
|
1482
|
-
To fix this mismatch:
|
|
1483
|
-
\u2022 Push missing tags: git push origin --tags
|
|
1484
|
-
\u2022 Or use package version as base by ensuring tags are up to date`,
|
|
1485
|
-
"warning"
|
|
1486
|
-
);
|
|
1487
|
-
} else if (import_semver3.default.gt(tagVersion, packageVersion)) {
|
|
1488
|
-
log(
|
|
1489
|
-
`Warning: Version mismatch detected!
|
|
1490
|
-
\u2022 ${manifestResult.manifestType} version: ${packageVersion}
|
|
1491
|
-
\u2022 Latest Git tag version: ${tagVersion} (from ${latestTag})
|
|
1492
|
-
\u2022 Git tag version is AHEAD of package version
|
|
1493
|
-
|
|
1494
|
-
This usually happens when:
|
|
1495
|
-
\u2022 A release was tagged but the ${manifestResult.manifestType} wasn't updated
|
|
1496
|
-
\u2022 You're on an older branch that hasn't been updated with the latest version
|
|
1497
|
-
\u2022 Automated release process created tags but didn't update manifest files
|
|
1498
|
-
\u2022 You pulled tags but not the corresponding commits that update the package version
|
|
1499
|
-
|
|
1500
|
-
The tool will use the Git tag version (${tagVersion}) as the base for calculation.
|
|
1501
|
-
This will likely result in a version that's already been released.
|
|
1502
|
-
|
|
1503
|
-
To fix this mismatch:
|
|
1504
|
-
\u2022 Update ${manifestResult.manifestType}: Set version to ${tagVersion} or higher
|
|
1505
|
-
\u2022 Or checkout the branch/commit that corresponds to the tag
|
|
1506
|
-
\u2022 Or ensure your branch is up to date with the latest changes`,
|
|
1507
|
-
"warning"
|
|
1508
|
-
);
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1561
|
+
const packageVersion = manifestResult.manifestFound && manifestResult.version ? manifestResult.version : void 0;
|
|
1562
|
+
versionSource = await getBestVersionSource(latestTag, packageVersion, packageDir);
|
|
1563
|
+
log(`Using version source: ${versionSource.source} (${versionSource.reason})`, "info");
|
|
1511
1564
|
}
|
|
1512
1565
|
const specifiedType = type;
|
|
1513
1566
|
if (specifiedType) {
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
normalizedPrereleaseId,
|
|
1520
|
-
initialVersion
|
|
1521
|
-
);
|
|
1522
|
-
}
|
|
1523
|
-
const cleanedTag = import_semver3.default.clean(latestTag) || latestTag;
|
|
1524
|
-
const currentVersion = import_semver3.default.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
|
|
1525
|
-
if (STANDARD_BUMP_TYPES.includes(specifiedType) && (import_semver3.default.prerelease(currentVersion) || normalizedPrereleaseId)) {
|
|
1567
|
+
const currentVersion = getCurrentVersionFromSource2();
|
|
1568
|
+
const isCurrentPrerelease = import_semver3.default.prerelease(currentVersion);
|
|
1569
|
+
const explicitlyRequestedPrerelease = config.isPrerelease;
|
|
1570
|
+
if (STANDARD_BUMP_TYPES.includes(specifiedType) && (isCurrentPrerelease || explicitlyRequestedPrerelease)) {
|
|
1571
|
+
const prereleaseId2 = explicitlyRequestedPrerelease || isCurrentPrerelease ? normalizedPrereleaseId : void 0;
|
|
1526
1572
|
log(
|
|
1527
|
-
|
|
1573
|
+
explicitlyRequestedPrerelease ? `Creating prerelease version with identifier '${prereleaseId2}' using ${specifiedType}` : `Cleaning prerelease identifier from ${currentVersion} for ${specifiedType} bump`,
|
|
1528
1574
|
"debug"
|
|
1529
1575
|
);
|
|
1530
|
-
return bumpVersion(currentVersion, specifiedType,
|
|
1576
|
+
return bumpVersion(currentVersion, specifiedType, prereleaseId2);
|
|
1531
1577
|
}
|
|
1532
|
-
|
|
1578
|
+
const isPrereleaseBumpType = ["prerelease", "premajor", "preminor", "prepatch"].includes(
|
|
1579
|
+
specifiedType
|
|
1580
|
+
);
|
|
1581
|
+
const prereleaseId = config.isPrerelease || isPrereleaseBumpType ? normalizedPrereleaseId : void 0;
|
|
1582
|
+
return bumpVersion(currentVersion, specifiedType, prereleaseId);
|
|
1533
1583
|
}
|
|
1534
1584
|
if (branchPattern && branchPattern.length > 0) {
|
|
1535
1585
|
const currentBranch = getCurrentBranch();
|
|
@@ -1551,19 +1601,13 @@ To fix this mismatch:
|
|
|
1551
1601
|
}
|
|
1552
1602
|
}
|
|
1553
1603
|
if (branchVersionType) {
|
|
1554
|
-
|
|
1555
|
-
return getPackageVersionFallback(
|
|
1556
|
-
pkgPath,
|
|
1557
|
-
name,
|
|
1558
|
-
branchVersionType,
|
|
1559
|
-
normalizedPrereleaseId,
|
|
1560
|
-
initialVersion
|
|
1561
|
-
);
|
|
1562
|
-
}
|
|
1563
|
-
const cleanedTag = import_semver3.default.clean(latestTag) || latestTag;
|
|
1564
|
-
const currentVersion = import_semver3.default.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
|
|
1604
|
+
const currentVersion = getCurrentVersionFromSource2();
|
|
1565
1605
|
log(`Applying ${branchVersionType} bump based on branch pattern`, "debug");
|
|
1566
|
-
|
|
1606
|
+
const isPrereleaseBumpType = ["prerelease", "premajor", "preminor", "prepatch"].includes(
|
|
1607
|
+
branchVersionType
|
|
1608
|
+
);
|
|
1609
|
+
const prereleaseId = config.isPrerelease || isPrereleaseBumpType ? normalizedPrereleaseId : void 0;
|
|
1610
|
+
return bumpVersion(currentVersion, branchVersionType, prereleaseId);
|
|
1567
1611
|
}
|
|
1568
1612
|
}
|
|
1569
1613
|
try {
|
|
@@ -1571,36 +1615,39 @@ To fix this mismatch:
|
|
|
1571
1615
|
bumper.loadPreset(preset);
|
|
1572
1616
|
const recommendedBump = await bumper.bump();
|
|
1573
1617
|
const releaseTypeFromCommits = recommendedBump && "releaseType" in recommendedBump ? recommendedBump.releaseType : void 0;
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1618
|
+
const currentVersion = getCurrentVersionFromSource2();
|
|
1619
|
+
if (versionSource && versionSource.source === "git") {
|
|
1620
|
+
const checkPath = pkgPath || (0, import_node_process3.cwd)();
|
|
1621
|
+
const commitsLength = getCommitsLength(checkPath, versionSource.version);
|
|
1622
|
+
if (commitsLength === 0) {
|
|
1623
|
+
log(
|
|
1624
|
+
`No new commits found for ${name || "project"} since ${versionSource.version}, skipping version bump`,
|
|
1625
|
+
"info"
|
|
1582
1626
|
);
|
|
1627
|
+
return "";
|
|
1583
1628
|
}
|
|
1584
|
-
|
|
1585
|
-
}
|
|
1586
|
-
const checkPath = pkgPath || (0, import_node_process3.cwd)();
|
|
1587
|
-
const commitsLength = getCommitsLength(checkPath);
|
|
1588
|
-
if (commitsLength === 0) {
|
|
1629
|
+
} else if (versionSource && versionSource.source === "package") {
|
|
1589
1630
|
log(
|
|
1590
|
-
`
|
|
1591
|
-
"
|
|
1631
|
+
`Using package version ${versionSource.version} as base, letting conventional commits determine bump necessity`,
|
|
1632
|
+
"debug"
|
|
1592
1633
|
);
|
|
1593
|
-
return "";
|
|
1594
1634
|
}
|
|
1595
1635
|
if (!releaseTypeFromCommits) {
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1636
|
+
if (latestTag && latestTag.trim() !== "") {
|
|
1637
|
+
log(
|
|
1638
|
+
`No relevant commits found for ${name || "project"} since ${latestTag}, skipping version bump`,
|
|
1639
|
+
"info"
|
|
1640
|
+
);
|
|
1641
|
+
} else {
|
|
1642
|
+
log(`No relevant commits found for ${name || "project"}, skipping version bump`, "info");
|
|
1643
|
+
}
|
|
1600
1644
|
return "";
|
|
1601
1645
|
}
|
|
1602
|
-
const
|
|
1603
|
-
|
|
1646
|
+
const isPrereleaseBumpType = ["prerelease", "premajor", "preminor", "prepatch"].includes(
|
|
1647
|
+
releaseTypeFromCommits
|
|
1648
|
+
);
|
|
1649
|
+
const prereleaseId = config.isPrerelease || isPrereleaseBumpType ? normalizedPrereleaseId : void 0;
|
|
1650
|
+
return bumpVersion(currentVersion, releaseTypeFromCommits, prereleaseId);
|
|
1604
1651
|
} catch (error) {
|
|
1605
1652
|
log(`Failed to calculate version for ${name || "project"}`, "error");
|
|
1606
1653
|
console.error(error);
|
|
@@ -1620,40 +1667,6 @@ To fix this mismatch:
|
|
|
1620
1667
|
throw error;
|
|
1621
1668
|
}
|
|
1622
1669
|
}
|
|
1623
|
-
function getPackageVersionFallback(pkgPath, name, releaseType, prereleaseIdentifier, initialVersion) {
|
|
1624
|
-
const packageDir = pkgPath || (0, import_node_process3.cwd)();
|
|
1625
|
-
const manifestResult = getVersionFromManifests(packageDir);
|
|
1626
|
-
if (manifestResult.manifestFound && manifestResult.version) {
|
|
1627
|
-
log(
|
|
1628
|
-
`No tags found for ${name || "package"}, using ${manifestResult.manifestType} version: ${manifestResult.version} as base`,
|
|
1629
|
-
"info"
|
|
1630
|
-
);
|
|
1631
|
-
return calculateNextVersion(
|
|
1632
|
-
manifestResult.version,
|
|
1633
|
-
manifestResult.manifestType || "manifest",
|
|
1634
|
-
name,
|
|
1635
|
-
releaseType,
|
|
1636
|
-
prereleaseIdentifier,
|
|
1637
|
-
initialVersion
|
|
1638
|
-
);
|
|
1639
|
-
}
|
|
1640
|
-
throwIfNoManifestsFound(packageDir);
|
|
1641
|
-
}
|
|
1642
|
-
function calculateNextVersion(version, manifestType, name, releaseType, prereleaseIdentifier, initialVersion) {
|
|
1643
|
-
log(
|
|
1644
|
-
`No tags found for ${name || "package"}, using ${manifestType} version: ${version} as base`,
|
|
1645
|
-
"info"
|
|
1646
|
-
);
|
|
1647
|
-
if (STANDARD_BUMP_TYPES.includes(releaseType) && (import_semver3.default.prerelease(version) || prereleaseIdentifier)) {
|
|
1648
|
-
log(
|
|
1649
|
-
prereleaseIdentifier ? `Creating prerelease version with identifier '${prereleaseIdentifier}' using ${releaseType}` : `Cleaning prerelease identifier from ${version} for ${releaseType} bump`,
|
|
1650
|
-
"debug"
|
|
1651
|
-
);
|
|
1652
|
-
return bumpVersion(version, releaseType, prereleaseIdentifier);
|
|
1653
|
-
}
|
|
1654
|
-
const result = bumpVersion(version, releaseType, prereleaseIdentifier);
|
|
1655
|
-
return result || initialVersion;
|
|
1656
|
-
}
|
|
1657
1670
|
|
|
1658
1671
|
// src/utils/packageMatching.ts
|
|
1659
1672
|
var import_micromatch2 = __toESM(require("micromatch"), 1);
|
|
@@ -1803,14 +1816,14 @@ var PackageProcessor = class {
|
|
|
1803
1816
|
try {
|
|
1804
1817
|
let revisionRange;
|
|
1805
1818
|
if (latestTag) {
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
cwd: pkgPath,
|
|
1809
|
-
stdio: "ignore"
|
|
1810
|
-
});
|
|
1819
|
+
const verification = verifyTag(latestTag, pkgPath);
|
|
1820
|
+
if (verification.exists && verification.reachable) {
|
|
1811
1821
|
revisionRange = `${latestTag}..HEAD`;
|
|
1812
|
-
}
|
|
1813
|
-
log(
|
|
1822
|
+
} else {
|
|
1823
|
+
log(
|
|
1824
|
+
`Tag ${latestTag} is unreachable (${verification.error}), using all commits for changelog`,
|
|
1825
|
+
"debug"
|
|
1826
|
+
);
|
|
1814
1827
|
revisionRange = "HEAD";
|
|
1815
1828
|
}
|
|
1816
1829
|
} else {
|
|
@@ -2179,7 +2192,7 @@ function createSingleStrategy(config) {
|
|
|
2179
2192
|
let revisionRange;
|
|
2180
2193
|
if (latestTag) {
|
|
2181
2194
|
try {
|
|
2182
|
-
(0,
|
|
2195
|
+
(0, import_node_child_process4.execSync)(`git rev-parse --verify "${latestTag}"`, {
|
|
2183
2196
|
cwd: pkgPath,
|
|
2184
2197
|
stdio: "ignore"
|
|
2185
2198
|
});
|
|
@@ -2337,11 +2350,10 @@ function createStrategyMap(config) {
|
|
|
2337
2350
|
// src/core/versionEngine.ts
|
|
2338
2351
|
var VersionEngine = class {
|
|
2339
2352
|
config;
|
|
2340
|
-
jsonMode;
|
|
2341
2353
|
workspaceCache = null;
|
|
2342
2354
|
strategies;
|
|
2343
2355
|
currentStrategy;
|
|
2344
|
-
constructor(config,
|
|
2356
|
+
constructor(config, _jsonMode = false) {
|
|
2345
2357
|
if (!config) {
|
|
2346
2358
|
throw createVersionError("CONFIG_REQUIRED" /* CONFIG_REQUIRED */);
|
|
2347
2359
|
}
|
|
@@ -2350,7 +2362,6 @@ var VersionEngine = class {
|
|
|
2350
2362
|
log("No preset specified, using default: conventional-commits", "warning");
|
|
2351
2363
|
}
|
|
2352
2364
|
this.config = config;
|
|
2353
|
-
this.jsonMode = jsonMode;
|
|
2354
2365
|
this.strategies = createStrategyMap(config);
|
|
2355
2366
|
this.currentStrategy = createStrategy(config);
|
|
2356
2367
|
}
|
|
@@ -2466,18 +2477,31 @@ async function run() {
|
|
|
2466
2477
|
program.command("version", { isDefault: true }).description("Version a package or packages based on configuration").option(
|
|
2467
2478
|
"-c, --config <path>",
|
|
2468
2479
|
"Path to config file (defaults to version.config.json in current directory)"
|
|
2469
|
-
).option("-d, --dry-run", "Dry run (no changes made)", false).option("-b, --bump <type>", "Specify bump type (patch|minor|major)").option("-p, --prerelease [identifier]", "Create prerelease version").option("-s, --synced", "Use synchronized versioning across all packages").option("-j, --json", "Output results as JSON", false).option("-t, --target <packages>", "Comma-delimited list of package names to target").action(async (options) => {
|
|
2480
|
+
).option("-d, --dry-run", "Dry run (no changes made)", false).option("-b, --bump <type>", "Specify bump type (patch|minor|major)").option("-p, --prerelease [identifier]", "Create prerelease version").option("-s, --synced", "Use synchronized versioning across all packages").option("-j, --json", "Output results as JSON", false).option("-t, --target <packages>", "Comma-delimited list of package names to target").option("--project-dir <path>", "Project directory to run commands in", process.cwd()).action(async (options) => {
|
|
2470
2481
|
if (options.json) {
|
|
2471
2482
|
enableJsonOutput(options.dryRun);
|
|
2472
2483
|
}
|
|
2473
2484
|
try {
|
|
2485
|
+
const originalCwd = process.cwd();
|
|
2486
|
+
if (options.projectDir && options.projectDir !== originalCwd) {
|
|
2487
|
+
try {
|
|
2488
|
+
process.chdir(options.projectDir);
|
|
2489
|
+
log(`Changed working directory to: ${options.projectDir}`, "debug");
|
|
2490
|
+
} catch (error) {
|
|
2491
|
+
throw new Error(
|
|
2492
|
+
`Failed to change to directory "${options.projectDir}": ${error instanceof Error ? error.message : String(error)}`
|
|
2493
|
+
);
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2474
2496
|
const config = await loadConfig(options.config);
|
|
2475
2497
|
log(`Loaded configuration from ${options.config || "version.config.json"}`, "info");
|
|
2476
2498
|
if (options.dryRun) config.dryRun = true;
|
|
2477
2499
|
if (options.synced) config.synced = true;
|
|
2478
2500
|
if (options.bump) config.type = options.bump;
|
|
2479
|
-
if (options.prerelease)
|
|
2501
|
+
if (options.prerelease) {
|
|
2480
2502
|
config.prereleaseIdentifier = options.prerelease === true ? "next" : options.prerelease;
|
|
2503
|
+
config.isPrerelease = true;
|
|
2504
|
+
}
|
|
2481
2505
|
const cliTargets = options.target ? options.target.split(",").map((t) => t.trim()) : [];
|
|
2482
2506
|
const engine = new VersionEngine(config, !!options.json);
|
|
2483
2507
|
const pkgsResult = await engine.getWorkspacePackages();
|
package/dist/index.js
CHANGED
|
@@ -439,7 +439,7 @@ function getAllVersionTags(since, versionPrefix = "v") {
|
|
|
439
439
|
const allTags = tagOutput.split("\n").filter((tag) => !!tag);
|
|
440
440
|
let filteredTags = allTags;
|
|
441
441
|
if (since) {
|
|
442
|
-
const sinceIndex = allTags.
|
|
442
|
+
const sinceIndex = allTags.indexOf(since);
|
|
443
443
|
if (sinceIndex >= 0) {
|
|
444
444
|
filteredTags = allTags.slice(sinceIndex);
|
|
445
445
|
} else {
|
|
@@ -523,7 +523,7 @@ async function regenerateChangelog(options) {
|
|
|
523
523
|
const allTagsCmd = `git tag --list "${versionPrefix}*" --sort=creatordate`;
|
|
524
524
|
const allTagsOutput = execSync2(allTagsCmd, { encoding: "utf8" }).trim();
|
|
525
525
|
const allTags = allTagsOutput.split("\n").filter((tag) => !!tag);
|
|
526
|
-
const sinceIndex = allTags.
|
|
526
|
+
const sinceIndex = allTags.indexOf(since);
|
|
527
527
|
const actualPreviousTag = sinceIndex > 0 ? allTags[sinceIndex - 1] : null;
|
|
528
528
|
if (actualPreviousTag) {
|
|
529
529
|
tagRange = `${actualPreviousTag}..${currentTag.tag}`;
|
|
@@ -661,8 +661,12 @@ function filterPackagesByConfig(packages, configTargets, workspaceRoot) {
|
|
|
661
661
|
for (const target of configTargets) {
|
|
662
662
|
const dirMatches = filterByDirectoryPattern(packages, target, workspaceRoot);
|
|
663
663
|
const nameMatches = filterByPackageNamePattern(packages, target);
|
|
664
|
-
|
|
665
|
-
|
|
664
|
+
for (const pkg of dirMatches) {
|
|
665
|
+
matchedPackages.add(pkg);
|
|
666
|
+
}
|
|
667
|
+
for (const pkg of nameMatches) {
|
|
668
|
+
matchedPackages.add(pkg);
|
|
669
|
+
}
|
|
666
670
|
}
|
|
667
671
|
return Array.from(matchedPackages);
|
|
668
672
|
}
|
|
@@ -726,7 +730,7 @@ function matchesPackageNamePattern(packageName, pattern) {
|
|
|
726
730
|
}
|
|
727
731
|
|
|
728
732
|
// src/core/versionStrategies.ts
|
|
729
|
-
import { execSync as
|
|
733
|
+
import { execSync as execSync4 } from "child_process";
|
|
730
734
|
import fs9 from "fs";
|
|
731
735
|
import * as path8 from "path";
|
|
732
736
|
|
|
@@ -1008,9 +1012,14 @@ function formatCommitMessage(template, version, packageName, additionalContext)
|
|
|
1008
1012
|
}
|
|
1009
1013
|
|
|
1010
1014
|
// src/git/tagsAndBranches.ts
|
|
1011
|
-
function getCommitsLength(pkgRoot) {
|
|
1015
|
+
function getCommitsLength(pkgRoot, sinceTag) {
|
|
1012
1016
|
try {
|
|
1013
|
-
|
|
1017
|
+
let gitCommand;
|
|
1018
|
+
if (sinceTag && sinceTag.trim() !== "") {
|
|
1019
|
+
gitCommand = `git rev-list --count ${sinceTag}..HEAD ${pkgRoot}`;
|
|
1020
|
+
} else {
|
|
1021
|
+
gitCommand = `git rev-list --count HEAD ^$(git describe --tags --abbrev=0) ${pkgRoot}`;
|
|
1022
|
+
}
|
|
1014
1023
|
const amount = execSync3(gitCommand).toString().trim();
|
|
1015
1024
|
return Number(amount);
|
|
1016
1025
|
} catch (error) {
|
|
@@ -1284,7 +1293,6 @@ function updatePackageVersion(packagePath, version) {
|
|
|
1284
1293
|
}
|
|
1285
1294
|
|
|
1286
1295
|
// src/package/packageProcessor.ts
|
|
1287
|
-
import { execSync as execSync4 } from "child_process";
|
|
1288
1296
|
import * as fs8 from "fs";
|
|
1289
1297
|
import path7 from "path";
|
|
1290
1298
|
import { exit } from "process";
|
|
@@ -1343,18 +1351,41 @@ function getVersionFromManifests(packageDir) {
|
|
|
1343
1351
|
manifestType: null
|
|
1344
1352
|
};
|
|
1345
1353
|
}
|
|
1346
|
-
function throwIfNoManifestsFound(packageDir) {
|
|
1347
|
-
const packageJsonPath = path6.join(packageDir, "package.json");
|
|
1348
|
-
const cargoTomlPath = path6.join(packageDir, "Cargo.toml");
|
|
1349
|
-
throw new Error(
|
|
1350
|
-
`Neither package.json nor Cargo.toml found at ${packageDir}. Checked paths: ${packageJsonPath}, ${cargoTomlPath}. Cannot determine version.`
|
|
1351
|
-
);
|
|
1352
|
-
}
|
|
1353
1354
|
|
|
1354
1355
|
// src/utils/versionUtils.ts
|
|
1355
1356
|
import fs7 from "fs";
|
|
1356
1357
|
import semver2 from "semver";
|
|
1357
1358
|
import * as TOML2 from "smol-toml";
|
|
1359
|
+
|
|
1360
|
+
// src/git/tagVerification.ts
|
|
1361
|
+
function verifyTag(tagName, cwd5) {
|
|
1362
|
+
if (!tagName || tagName.trim() === "") {
|
|
1363
|
+
return { exists: false, reachable: false, error: "Empty tag name" };
|
|
1364
|
+
}
|
|
1365
|
+
try {
|
|
1366
|
+
execSync3(`git rev-parse --verify "${tagName}"`, {
|
|
1367
|
+
cwd: cwd5,
|
|
1368
|
+
stdio: "ignore"
|
|
1369
|
+
});
|
|
1370
|
+
return { exists: true, reachable: true };
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1373
|
+
if (errorMessage.includes("unknown revision") || errorMessage.includes("bad revision") || errorMessage.includes("No such ref")) {
|
|
1374
|
+
return {
|
|
1375
|
+
exists: false,
|
|
1376
|
+
reachable: false,
|
|
1377
|
+
error: `Tag '${tagName}' not found in repository`
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
return {
|
|
1381
|
+
exists: false,
|
|
1382
|
+
reachable: false,
|
|
1383
|
+
error: `Git error: ${errorMessage}`
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// src/utils/versionUtils.ts
|
|
1358
1389
|
var STANDARD_BUMP_TYPES = ["major", "minor", "patch"];
|
|
1359
1390
|
function normalizePrereleaseIdentifier(prereleaseIdentifier, config) {
|
|
1360
1391
|
if (prereleaseIdentifier === true) {
|
|
@@ -1388,6 +1419,59 @@ function bumpVersion(currentVersion, bumpType, prereleaseIdentifier) {
|
|
|
1388
1419
|
}
|
|
1389
1420
|
return semver2.inc(currentVersion, bumpType, prereleaseIdentifier) || "";
|
|
1390
1421
|
}
|
|
1422
|
+
async function getBestVersionSource(tagName, packageVersion, cwd5) {
|
|
1423
|
+
if (!(tagName == null ? void 0 : tagName.trim())) {
|
|
1424
|
+
return packageVersion ? { source: "package", version: packageVersion, reason: "No git tag provided" } : { source: "initial", version: "0.1.0", reason: "No git tag or package version available" };
|
|
1425
|
+
}
|
|
1426
|
+
const verification = verifyTag(tagName, cwd5);
|
|
1427
|
+
if (!verification.exists || !verification.reachable) {
|
|
1428
|
+
if (packageVersion) {
|
|
1429
|
+
log(
|
|
1430
|
+
`Git tag '${tagName}' unreachable (${verification.error}), using package version: ${packageVersion}`,
|
|
1431
|
+
"warning"
|
|
1432
|
+
);
|
|
1433
|
+
return { source: "package", version: packageVersion, reason: "Git tag unreachable" };
|
|
1434
|
+
}
|
|
1435
|
+
log(
|
|
1436
|
+
`Git tag '${tagName}' unreachable and no package version available, using initial version`,
|
|
1437
|
+
"warning"
|
|
1438
|
+
);
|
|
1439
|
+
return {
|
|
1440
|
+
source: "initial",
|
|
1441
|
+
version: "0.1.0",
|
|
1442
|
+
reason: "Git tag unreachable, no package version"
|
|
1443
|
+
};
|
|
1444
|
+
}
|
|
1445
|
+
if (!packageVersion) {
|
|
1446
|
+
return {
|
|
1447
|
+
source: "git",
|
|
1448
|
+
version: tagName,
|
|
1449
|
+
reason: "Git tag exists, no package version to compare"
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
try {
|
|
1453
|
+
const cleanTagVersion = tagName.replace(/^.*?([0-9])/, "$1");
|
|
1454
|
+
const cleanPackageVersion = packageVersion;
|
|
1455
|
+
if (semver2.gt(cleanPackageVersion, cleanTagVersion)) {
|
|
1456
|
+
log(
|
|
1457
|
+
`Package version ${packageVersion} is newer than git tag ${tagName}, using package version`,
|
|
1458
|
+
"info"
|
|
1459
|
+
);
|
|
1460
|
+
return { source: "package", version: packageVersion, reason: "Package version is newer" };
|
|
1461
|
+
}
|
|
1462
|
+
if (semver2.gt(cleanTagVersion, cleanPackageVersion)) {
|
|
1463
|
+
log(
|
|
1464
|
+
`Git tag ${tagName} is newer than package version ${packageVersion}, using git tag`,
|
|
1465
|
+
"info"
|
|
1466
|
+
);
|
|
1467
|
+
return { source: "git", version: tagName, reason: "Git tag is newer" };
|
|
1468
|
+
}
|
|
1469
|
+
return { source: "git", version: tagName, reason: "Versions equal, using git tag" };
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
log(`Failed to compare versions, defaulting to git tag: ${error}`, "warning");
|
|
1472
|
+
return { source: "git", version: tagName, reason: "Version comparison failed" };
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1391
1475
|
|
|
1392
1476
|
// src/core/versionCalculator.ts
|
|
1393
1477
|
async function calculateVersion(config, options) {
|
|
@@ -1419,84 +1503,50 @@ async function calculateVersion(config, options) {
|
|
|
1419
1503
|
return `${packageName}@${prefix}`;
|
|
1420
1504
|
}, escapeRegExp3 = function(string) {
|
|
1421
1505
|
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1506
|
+
}, getCurrentVersionFromSource2 = function() {
|
|
1507
|
+
if (!versionSource) {
|
|
1508
|
+
if (hasNoTags) {
|
|
1509
|
+
return initialVersion;
|
|
1510
|
+
}
|
|
1511
|
+
const cleanedTag = semver3.clean(latestTag) || latestTag;
|
|
1512
|
+
return semver3.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
|
|
1513
|
+
}
|
|
1514
|
+
if (versionSource.source === "git") {
|
|
1515
|
+
const cleanedTag = semver3.clean(versionSource.version) || versionSource.version;
|
|
1516
|
+
return semver3.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
|
|
1517
|
+
}
|
|
1518
|
+
return versionSource.version;
|
|
1422
1519
|
};
|
|
1423
|
-
var determineTagSearchPattern = determineTagSearchPattern2, escapeRegExp2 = escapeRegExp3;
|
|
1520
|
+
var determineTagSearchPattern = determineTagSearchPattern2, escapeRegExp2 = escapeRegExp3, getCurrentVersionFromSource = getCurrentVersionFromSource2;
|
|
1424
1521
|
const originalPrefix = versionPrefix || "";
|
|
1425
1522
|
const tagSearchPattern = determineTagSearchPattern2(name, originalPrefix);
|
|
1426
1523
|
const escapedTagPattern = escapeRegExp3(tagSearchPattern);
|
|
1427
|
-
|
|
1524
|
+
let versionSource;
|
|
1525
|
+
if (pkgPath) {
|
|
1428
1526
|
const packageDir = pkgPath || cwd3();
|
|
1429
1527
|
const manifestResult = getVersionFromManifests(packageDir);
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
const packageVersion = manifestResult.version;
|
|
1434
|
-
if (semver3.gt(packageVersion, tagVersion)) {
|
|
1435
|
-
log(
|
|
1436
|
-
`Warning: Version mismatch detected!
|
|
1437
|
-
\u2022 ${manifestResult.manifestType} version: ${packageVersion}
|
|
1438
|
-
\u2022 Latest Git tag version: ${tagVersion} (from ${latestTag})
|
|
1439
|
-
\u2022 Package version is AHEAD of Git tags
|
|
1440
|
-
|
|
1441
|
-
This usually happens when:
|
|
1442
|
-
\u2022 A version was released but the tag wasn't pushed to the remote repository
|
|
1443
|
-
\u2022 The ${manifestResult.manifestType} was manually updated without creating a corresponding tag
|
|
1444
|
-
\u2022 You're running in CI and the latest tag isn't available yet
|
|
1445
|
-
|
|
1446
|
-
The tool will use the Git tag version (${tagVersion}) as the base for calculation.
|
|
1447
|
-
Expected next version will be based on ${tagVersion}, not ${packageVersion}.
|
|
1448
|
-
|
|
1449
|
-
To fix this mismatch:
|
|
1450
|
-
\u2022 Push missing tags: git push origin --tags
|
|
1451
|
-
\u2022 Or use package version as base by ensuring tags are up to date`,
|
|
1452
|
-
"warning"
|
|
1453
|
-
);
|
|
1454
|
-
} else if (semver3.gt(tagVersion, packageVersion)) {
|
|
1455
|
-
log(
|
|
1456
|
-
`Warning: Version mismatch detected!
|
|
1457
|
-
\u2022 ${manifestResult.manifestType} version: ${packageVersion}
|
|
1458
|
-
\u2022 Latest Git tag version: ${tagVersion} (from ${latestTag})
|
|
1459
|
-
\u2022 Git tag version is AHEAD of package version
|
|
1460
|
-
|
|
1461
|
-
This usually happens when:
|
|
1462
|
-
\u2022 A release was tagged but the ${manifestResult.manifestType} wasn't updated
|
|
1463
|
-
\u2022 You're on an older branch that hasn't been updated with the latest version
|
|
1464
|
-
\u2022 Automated release process created tags but didn't update manifest files
|
|
1465
|
-
\u2022 You pulled tags but not the corresponding commits that update the package version
|
|
1466
|
-
|
|
1467
|
-
The tool will use the Git tag version (${tagVersion}) as the base for calculation.
|
|
1468
|
-
This will likely result in a version that's already been released.
|
|
1469
|
-
|
|
1470
|
-
To fix this mismatch:
|
|
1471
|
-
\u2022 Update ${manifestResult.manifestType}: Set version to ${tagVersion} or higher
|
|
1472
|
-
\u2022 Or checkout the branch/commit that corresponds to the tag
|
|
1473
|
-
\u2022 Or ensure your branch is up to date with the latest changes`,
|
|
1474
|
-
"warning"
|
|
1475
|
-
);
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1528
|
+
const packageVersion = manifestResult.manifestFound && manifestResult.version ? manifestResult.version : void 0;
|
|
1529
|
+
versionSource = await getBestVersionSource(latestTag, packageVersion, packageDir);
|
|
1530
|
+
log(`Using version source: ${versionSource.source} (${versionSource.reason})`, "info");
|
|
1478
1531
|
}
|
|
1479
1532
|
const specifiedType = type;
|
|
1480
1533
|
if (specifiedType) {
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
normalizedPrereleaseId,
|
|
1487
|
-
initialVersion
|
|
1488
|
-
);
|
|
1489
|
-
}
|
|
1490
|
-
const cleanedTag = semver3.clean(latestTag) || latestTag;
|
|
1491
|
-
const currentVersion = semver3.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
|
|
1492
|
-
if (STANDARD_BUMP_TYPES.includes(specifiedType) && (semver3.prerelease(currentVersion) || normalizedPrereleaseId)) {
|
|
1534
|
+
const currentVersion = getCurrentVersionFromSource2();
|
|
1535
|
+
const isCurrentPrerelease = semver3.prerelease(currentVersion);
|
|
1536
|
+
const explicitlyRequestedPrerelease = config.isPrerelease;
|
|
1537
|
+
if (STANDARD_BUMP_TYPES.includes(specifiedType) && (isCurrentPrerelease || explicitlyRequestedPrerelease)) {
|
|
1538
|
+
const prereleaseId2 = explicitlyRequestedPrerelease || isCurrentPrerelease ? normalizedPrereleaseId : void 0;
|
|
1493
1539
|
log(
|
|
1494
|
-
|
|
1540
|
+
explicitlyRequestedPrerelease ? `Creating prerelease version with identifier '${prereleaseId2}' using ${specifiedType}` : `Cleaning prerelease identifier from ${currentVersion} for ${specifiedType} bump`,
|
|
1495
1541
|
"debug"
|
|
1496
1542
|
);
|
|
1497
|
-
return bumpVersion(currentVersion, specifiedType,
|
|
1543
|
+
return bumpVersion(currentVersion, specifiedType, prereleaseId2);
|
|
1498
1544
|
}
|
|
1499
|
-
|
|
1545
|
+
const isPrereleaseBumpType = ["prerelease", "premajor", "preminor", "prepatch"].includes(
|
|
1546
|
+
specifiedType
|
|
1547
|
+
);
|
|
1548
|
+
const prereleaseId = config.isPrerelease || isPrereleaseBumpType ? normalizedPrereleaseId : void 0;
|
|
1549
|
+
return bumpVersion(currentVersion, specifiedType, prereleaseId);
|
|
1500
1550
|
}
|
|
1501
1551
|
if (branchPattern && branchPattern.length > 0) {
|
|
1502
1552
|
const currentBranch = getCurrentBranch();
|
|
@@ -1518,19 +1568,13 @@ To fix this mismatch:
|
|
|
1518
1568
|
}
|
|
1519
1569
|
}
|
|
1520
1570
|
if (branchVersionType) {
|
|
1521
|
-
|
|
1522
|
-
return getPackageVersionFallback(
|
|
1523
|
-
pkgPath,
|
|
1524
|
-
name,
|
|
1525
|
-
branchVersionType,
|
|
1526
|
-
normalizedPrereleaseId,
|
|
1527
|
-
initialVersion
|
|
1528
|
-
);
|
|
1529
|
-
}
|
|
1530
|
-
const cleanedTag = semver3.clean(latestTag) || latestTag;
|
|
1531
|
-
const currentVersion = semver3.clean(cleanedTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
|
|
1571
|
+
const currentVersion = getCurrentVersionFromSource2();
|
|
1532
1572
|
log(`Applying ${branchVersionType} bump based on branch pattern`, "debug");
|
|
1533
|
-
|
|
1573
|
+
const isPrereleaseBumpType = ["prerelease", "premajor", "preminor", "prepatch"].includes(
|
|
1574
|
+
branchVersionType
|
|
1575
|
+
);
|
|
1576
|
+
const prereleaseId = config.isPrerelease || isPrereleaseBumpType ? normalizedPrereleaseId : void 0;
|
|
1577
|
+
return bumpVersion(currentVersion, branchVersionType, prereleaseId);
|
|
1534
1578
|
}
|
|
1535
1579
|
}
|
|
1536
1580
|
try {
|
|
@@ -1538,36 +1582,39 @@ To fix this mismatch:
|
|
|
1538
1582
|
bumper.loadPreset(preset);
|
|
1539
1583
|
const recommendedBump = await bumper.bump();
|
|
1540
1584
|
const releaseTypeFromCommits = recommendedBump && "releaseType" in recommendedBump ? recommendedBump.releaseType : void 0;
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1585
|
+
const currentVersion = getCurrentVersionFromSource2();
|
|
1586
|
+
if (versionSource && versionSource.source === "git") {
|
|
1587
|
+
const checkPath = pkgPath || cwd3();
|
|
1588
|
+
const commitsLength = getCommitsLength(checkPath, versionSource.version);
|
|
1589
|
+
if (commitsLength === 0) {
|
|
1590
|
+
log(
|
|
1591
|
+
`No new commits found for ${name || "project"} since ${versionSource.version}, skipping version bump`,
|
|
1592
|
+
"info"
|
|
1549
1593
|
);
|
|
1594
|
+
return "";
|
|
1550
1595
|
}
|
|
1551
|
-
|
|
1552
|
-
}
|
|
1553
|
-
const checkPath = pkgPath || cwd3();
|
|
1554
|
-
const commitsLength = getCommitsLength(checkPath);
|
|
1555
|
-
if (commitsLength === 0) {
|
|
1596
|
+
} else if (versionSource && versionSource.source === "package") {
|
|
1556
1597
|
log(
|
|
1557
|
-
`
|
|
1558
|
-
"
|
|
1598
|
+
`Using package version ${versionSource.version} as base, letting conventional commits determine bump necessity`,
|
|
1599
|
+
"debug"
|
|
1559
1600
|
);
|
|
1560
|
-
return "";
|
|
1561
1601
|
}
|
|
1562
1602
|
if (!releaseTypeFromCommits) {
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1603
|
+
if (latestTag && latestTag.trim() !== "") {
|
|
1604
|
+
log(
|
|
1605
|
+
`No relevant commits found for ${name || "project"} since ${latestTag}, skipping version bump`,
|
|
1606
|
+
"info"
|
|
1607
|
+
);
|
|
1608
|
+
} else {
|
|
1609
|
+
log(`No relevant commits found for ${name || "project"}, skipping version bump`, "info");
|
|
1610
|
+
}
|
|
1567
1611
|
return "";
|
|
1568
1612
|
}
|
|
1569
|
-
const
|
|
1570
|
-
|
|
1613
|
+
const isPrereleaseBumpType = ["prerelease", "premajor", "preminor", "prepatch"].includes(
|
|
1614
|
+
releaseTypeFromCommits
|
|
1615
|
+
);
|
|
1616
|
+
const prereleaseId = config.isPrerelease || isPrereleaseBumpType ? normalizedPrereleaseId : void 0;
|
|
1617
|
+
return bumpVersion(currentVersion, releaseTypeFromCommits, prereleaseId);
|
|
1571
1618
|
} catch (error) {
|
|
1572
1619
|
log(`Failed to calculate version for ${name || "project"}`, "error");
|
|
1573
1620
|
console.error(error);
|
|
@@ -1587,40 +1634,6 @@ To fix this mismatch:
|
|
|
1587
1634
|
throw error;
|
|
1588
1635
|
}
|
|
1589
1636
|
}
|
|
1590
|
-
function getPackageVersionFallback(pkgPath, name, releaseType, prereleaseIdentifier, initialVersion) {
|
|
1591
|
-
const packageDir = pkgPath || cwd3();
|
|
1592
|
-
const manifestResult = getVersionFromManifests(packageDir);
|
|
1593
|
-
if (manifestResult.manifestFound && manifestResult.version) {
|
|
1594
|
-
log(
|
|
1595
|
-
`No tags found for ${name || "package"}, using ${manifestResult.manifestType} version: ${manifestResult.version} as base`,
|
|
1596
|
-
"info"
|
|
1597
|
-
);
|
|
1598
|
-
return calculateNextVersion(
|
|
1599
|
-
manifestResult.version,
|
|
1600
|
-
manifestResult.manifestType || "manifest",
|
|
1601
|
-
name,
|
|
1602
|
-
releaseType,
|
|
1603
|
-
prereleaseIdentifier,
|
|
1604
|
-
initialVersion
|
|
1605
|
-
);
|
|
1606
|
-
}
|
|
1607
|
-
throwIfNoManifestsFound(packageDir);
|
|
1608
|
-
}
|
|
1609
|
-
function calculateNextVersion(version, manifestType, name, releaseType, prereleaseIdentifier, initialVersion) {
|
|
1610
|
-
log(
|
|
1611
|
-
`No tags found for ${name || "package"}, using ${manifestType} version: ${version} as base`,
|
|
1612
|
-
"info"
|
|
1613
|
-
);
|
|
1614
|
-
if (STANDARD_BUMP_TYPES.includes(releaseType) && (semver3.prerelease(version) || prereleaseIdentifier)) {
|
|
1615
|
-
log(
|
|
1616
|
-
prereleaseIdentifier ? `Creating prerelease version with identifier '${prereleaseIdentifier}' using ${releaseType}` : `Cleaning prerelease identifier from ${version} for ${releaseType} bump`,
|
|
1617
|
-
"debug"
|
|
1618
|
-
);
|
|
1619
|
-
return bumpVersion(version, releaseType, prereleaseIdentifier);
|
|
1620
|
-
}
|
|
1621
|
-
const result = bumpVersion(version, releaseType, prereleaseIdentifier);
|
|
1622
|
-
return result || initialVersion;
|
|
1623
|
-
}
|
|
1624
1637
|
|
|
1625
1638
|
// src/utils/packageMatching.ts
|
|
1626
1639
|
import micromatch2 from "micromatch";
|
|
@@ -1770,14 +1783,14 @@ var PackageProcessor = class {
|
|
|
1770
1783
|
try {
|
|
1771
1784
|
let revisionRange;
|
|
1772
1785
|
if (latestTag) {
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
cwd: pkgPath,
|
|
1776
|
-
stdio: "ignore"
|
|
1777
|
-
});
|
|
1786
|
+
const verification = verifyTag(latestTag, pkgPath);
|
|
1787
|
+
if (verification.exists && verification.reachable) {
|
|
1778
1788
|
revisionRange = `${latestTag}..HEAD`;
|
|
1779
|
-
}
|
|
1780
|
-
log(
|
|
1789
|
+
} else {
|
|
1790
|
+
log(
|
|
1791
|
+
`Tag ${latestTag} is unreachable (${verification.error}), using all commits for changelog`,
|
|
1792
|
+
"debug"
|
|
1793
|
+
);
|
|
1781
1794
|
revisionRange = "HEAD";
|
|
1782
1795
|
}
|
|
1783
1796
|
} else {
|
|
@@ -2146,7 +2159,7 @@ function createSingleStrategy(config) {
|
|
|
2146
2159
|
let revisionRange;
|
|
2147
2160
|
if (latestTag) {
|
|
2148
2161
|
try {
|
|
2149
|
-
|
|
2162
|
+
execSync4(`git rev-parse --verify "${latestTag}"`, {
|
|
2150
2163
|
cwd: pkgPath,
|
|
2151
2164
|
stdio: "ignore"
|
|
2152
2165
|
});
|
|
@@ -2304,11 +2317,10 @@ function createStrategyMap(config) {
|
|
|
2304
2317
|
// src/core/versionEngine.ts
|
|
2305
2318
|
var VersionEngine = class {
|
|
2306
2319
|
config;
|
|
2307
|
-
jsonMode;
|
|
2308
2320
|
workspaceCache = null;
|
|
2309
2321
|
strategies;
|
|
2310
2322
|
currentStrategy;
|
|
2311
|
-
constructor(config,
|
|
2323
|
+
constructor(config, _jsonMode = false) {
|
|
2312
2324
|
if (!config) {
|
|
2313
2325
|
throw createVersionError("CONFIG_REQUIRED" /* CONFIG_REQUIRED */);
|
|
2314
2326
|
}
|
|
@@ -2317,7 +2329,6 @@ var VersionEngine = class {
|
|
|
2317
2329
|
log("No preset specified, using default: conventional-commits", "warning");
|
|
2318
2330
|
}
|
|
2319
2331
|
this.config = config;
|
|
2320
|
-
this.jsonMode = jsonMode;
|
|
2321
2332
|
this.strategies = createStrategyMap(config);
|
|
2322
2333
|
this.currentStrategy = createStrategy(config);
|
|
2323
2334
|
}
|
|
@@ -2432,18 +2443,31 @@ async function run() {
|
|
|
2432
2443
|
program.command("version", { isDefault: true }).description("Version a package or packages based on configuration").option(
|
|
2433
2444
|
"-c, --config <path>",
|
|
2434
2445
|
"Path to config file (defaults to version.config.json in current directory)"
|
|
2435
|
-
).option("-d, --dry-run", "Dry run (no changes made)", false).option("-b, --bump <type>", "Specify bump type (patch|minor|major)").option("-p, --prerelease [identifier]", "Create prerelease version").option("-s, --synced", "Use synchronized versioning across all packages").option("-j, --json", "Output results as JSON", false).option("-t, --target <packages>", "Comma-delimited list of package names to target").action(async (options) => {
|
|
2446
|
+
).option("-d, --dry-run", "Dry run (no changes made)", false).option("-b, --bump <type>", "Specify bump type (patch|minor|major)").option("-p, --prerelease [identifier]", "Create prerelease version").option("-s, --synced", "Use synchronized versioning across all packages").option("-j, --json", "Output results as JSON", false).option("-t, --target <packages>", "Comma-delimited list of package names to target").option("--project-dir <path>", "Project directory to run commands in", process.cwd()).action(async (options) => {
|
|
2436
2447
|
if (options.json) {
|
|
2437
2448
|
enableJsonOutput(options.dryRun);
|
|
2438
2449
|
}
|
|
2439
2450
|
try {
|
|
2451
|
+
const originalCwd = process.cwd();
|
|
2452
|
+
if (options.projectDir && options.projectDir !== originalCwd) {
|
|
2453
|
+
try {
|
|
2454
|
+
process.chdir(options.projectDir);
|
|
2455
|
+
log(`Changed working directory to: ${options.projectDir}`, "debug");
|
|
2456
|
+
} catch (error) {
|
|
2457
|
+
throw new Error(
|
|
2458
|
+
`Failed to change to directory "${options.projectDir}": ${error instanceof Error ? error.message : String(error)}`
|
|
2459
|
+
);
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2440
2462
|
const config = await loadConfig(options.config);
|
|
2441
2463
|
log(`Loaded configuration from ${options.config || "version.config.json"}`, "info");
|
|
2442
2464
|
if (options.dryRun) config.dryRun = true;
|
|
2443
2465
|
if (options.synced) config.synced = true;
|
|
2444
2466
|
if (options.bump) config.type = options.bump;
|
|
2445
|
-
if (options.prerelease)
|
|
2467
|
+
if (options.prerelease) {
|
|
2446
2468
|
config.prereleaseIdentifier = options.prerelease === true ? "next" : options.prerelease;
|
|
2469
|
+
config.isPrerelease = true;
|
|
2470
|
+
}
|
|
2447
2471
|
const cliTargets = options.target ? options.target.split(",").map((t) => t.trim()) : [];
|
|
2448
2472
|
const engine = new VersionEngine(config, !!options.json);
|
|
2449
2473
|
const pkgsResult = await engine.getWorkspacePackages();
|
|
@@ -180,4 +180,5 @@ Each CI system might have slightly different syntax, so check your CI provider's
|
|
|
180
180
|
2. **Use the `fetch-depth: 0`** option in GitHub Actions (or equivalent in other CIs) to ensure access to the full Git history
|
|
181
181
|
3. **Store the JSON output** as a build artifact for debugging and auditing
|
|
182
182
|
4. **Consider dry runs** in your preview/staging branches to validate version changes before they're applied
|
|
183
|
-
5. **
|
|
183
|
+
5. **Use `--project-dir`** when running from a different directory than your project root
|
|
184
|
+
6. **Be mindful of Git credentials** - ensure your CI has proper permissions for creating commits and tags
|
package/docs/changelogs.md
CHANGED
|
@@ -52,7 +52,14 @@ You can configure the preferred format in your `version.config.json`:
|
|
|
52
52
|
For projects with existing history, you can regenerate a complete changelog from scratch using the CLI:
|
|
53
53
|
|
|
54
54
|
```bash
|
|
55
|
+
# Generate changelog in current directory
|
|
55
56
|
npx package-versioner changelog --regenerate
|
|
57
|
+
|
|
58
|
+
# Generate changelog in a specific directory
|
|
59
|
+
npx package-versioner changelog --regenerate --project-dir /path/to/project
|
|
60
|
+
|
|
61
|
+
# Customize output path and format
|
|
62
|
+
npx package-versioner changelog --regenerate --output CHANGELOG.md --format keep-a-changelog
|
|
56
63
|
```
|
|
57
64
|
|
|
58
65
|
This will scan your entire git history and create a comprehensive changelog based on all version tags found in your repository.
|
package/docs/versioning.md
CHANGED
|
@@ -65,78 +65,6 @@ You define patterns in the `branchPattern` array in `version.config.json`. Each
|
|
|
65
65
|
|
|
66
66
|
This allows you to enforce version bumps based on your branching workflow (e.g., all branches starting with `feature/` result in a minor bump).
|
|
67
67
|
|
|
68
|
-
## Monorepo Versioning Modes
|
|
69
|
-
|
|
70
|
-
While primarily used for single packages now, `package-versioner` retains options for monorepo workflows, controlled mainly by the `synced` flag in `version.config.json`.
|
|
71
|
-
|
|
72
|
-
### Synced Mode (`synced: true`)
|
|
73
|
-
|
|
74
|
-
This is the default if the `synced` flag is present and true.
|
|
75
|
-
|
|
76
|
-
- **Behaviour:** The tool calculates **one** version bump based on the overall history (or branch pattern). This single new version is applied to **all** packages within the repository (or just the root `package.json` if not a structured monorepo). A single Git tag is created.
|
|
77
|
-
- **Tag Behaviour:**
|
|
78
|
-
- In **multi-package monorepos**: Creates global tags like `v1.2.3` regardless of `packageSpecificTags` setting
|
|
79
|
-
- In **single-package repositories**: Respects the `packageSpecificTags` setting - can create either `v1.2.3` or `package-name@v1.2.3`
|
|
80
|
-
- **Use Case:** Suitable for monorepos where all packages are tightly coupled and released together with the same version number. Also the effective mode for single-package repositories.
|
|
81
|
-
|
|
82
|
-
### Async Mode (`synced: false`)
|
|
83
|
-
|
|
84
|
-
*(Note: This mode relies heavily on monorepo tooling and structure, like `pnpm workspaces` and correctly configured package dependencies.)*
|
|
85
|
-
|
|
86
|
-
- **Behaviour (Default - No `-t` flag):** The tool analyzes commits to determine which specific packages within the monorepo have changed since the last relevant commit/tag.
|
|
87
|
-
- It calculates an appropriate version bump **independently for each changed package** based on the commits affecting that package.
|
|
88
|
-
- Only the `package.json` files of the changed packages are updated.
|
|
89
|
-
- A **single commit** is created grouping all the version bumps, using the commit message template. **No Git tags are created** in this mode.
|
|
90
|
-
- **Use Case:** Suitable for monorepos where packages are versioned independently, but a single commit represents the batch of updates for traceability.
|
|
91
|
-
|
|
92
|
-
- **Behaviour (Targeted - With `-t` flag):** When using the `-t, --target <targets>` flag:
|
|
93
|
-
- Only the specified packages (respecting the `skip` list) are considered for versioning.
|
|
94
|
-
- It calculates an appropriate version bump **independently for each targeted package** based on its commit history.
|
|
95
|
-
- The `package.json` file of each successfully updated targeted package is modified.
|
|
96
|
-
- An **individual Git tag** (e.g., `packageName@1.2.3`) is created **for each successfully updated package** immediately after its version is bumped.
|
|
97
|
-
- Finally, a **single commit** is created including all the updated `package.json` files, using a summary commit message (e.g., `chore(release): pkg-a, pkg-b 1.2.3 [skip-ci]`).
|
|
98
|
-
- **Important:** Only package-specific tags are created. The global tag (e.g., `v1.2.3`) is **not** automatically generated in this mode. If your release process (like GitHub Releases) depends on a global tag, you'll need to create it manually in your CI/CD script *after* `package-versioner` completes.
|
|
99
|
-
- **Use Case:** Releasing specific packages independently while still tagging each released package individually.
|
|
100
|
-
|
|
101
|
-
## Prerelease Handling
|
|
102
|
-
|
|
103
|
-
`package-versioner` provides flexible handling for prerelease versions, allowing both creation of prereleases and promotion to stable releases.
|
|
104
|
-
|
|
105
|
-
### Creating Prereleases
|
|
106
|
-
|
|
107
|
-
Use the `--prerelease` flag with an identifier to create a prerelease version:
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
# Create a beta prerelease
|
|
111
|
-
npx package-versioner --bump minor --prerelease beta
|
|
112
|
-
# Result: 1.0.0 -> 1.1.0-beta.0
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
You can also set a default prerelease identifier in your `version.config.json`:
|
|
116
|
-
|
|
117
|
-
```json
|
|
118
|
-
{
|
|
119
|
-
"prereleaseIdentifier": "beta"
|
|
120
|
-
}
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
### Promoting Prereleases to Stable Releases
|
|
124
|
-
|
|
125
|
-
When using standard bump types (`major`, `minor`, `patch`) with the `--bump` flag on a prerelease version, `package-versioner` will automatically clean the prerelease identifier:
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
# Starting from version 1.0.0-beta.1
|
|
129
|
-
npx package-versioner --bump major
|
|
130
|
-
# Result: 1.0.0-beta.1 -> 2.0.0 (not 2.0.0-beta.0)
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
This intuitive behaviour means you don't need to use an empty prerelease identifier (`--prerelease ""`) to promote a prerelease to a stable version. Simply specify the standard bump type and the tool will automatically produce a clean version number.
|
|
134
|
-
|
|
135
|
-
This applies to all standard bump types:
|
|
136
|
-
- `--bump major`: 1.0.0-beta.1 -> 2.0.0
|
|
137
|
-
- `--bump minor`: 1.0.0-beta.1 -> 1.1.0
|
|
138
|
-
- `--bump patch`: 1.0.0-beta.1 -> 1.0.1
|
|
139
|
-
|
|
140
68
|
## Package Type Support
|
|
141
69
|
|
|
142
70
|
`package-versioner` supports both JavaScript/TypeScript projects using `package.json` and Rust projects using `Cargo.toml`:
|
|
@@ -149,11 +77,6 @@ For JavaScript/TypeScript projects, the tool looks for and updates the `version`
|
|
|
149
77
|
|
|
150
78
|
For Rust projects, the tool looks for and updates the `package.version` field in `Cargo.toml` files using the same versioning strategies.
|
|
151
79
|
|
|
152
|
-
When no tags are found for a project, `package-versioner` will:
|
|
153
|
-
1. Look for the `version` in `package.json` if it exists
|
|
154
|
-
2. Look for the `package.version` in `Cargo.toml` if it exists
|
|
155
|
-
3. Fall back to the configured `initialVersion` (default: "0.1.0")
|
|
156
|
-
|
|
157
80
|
### Mixed Projects with Both Manifests
|
|
158
81
|
|
|
159
82
|
When both `package.json` and `Cargo.toml` exist in the same directory, `package-versioner` will:
|
|
@@ -164,7 +87,48 @@ When both `package.json` and `Cargo.toml` exist in the same directory, `package-
|
|
|
164
87
|
|
|
165
88
|
This allows you to maintain consistent versioning across JavaScript and Rust components in the same package.
|
|
166
89
|
|
|
167
|
-
|
|
90
|
+
## Version Source Selection
|
|
91
|
+
|
|
92
|
+
`package-versioner` uses a smart version source selection strategy to determine the base version for calculating the next version:
|
|
93
|
+
|
|
94
|
+
1. First, it checks for Git tags:
|
|
95
|
+
- In normal mode: Uses the latest reachable tag, falling back to unreachable tags if needed
|
|
96
|
+
- In strict mode (`--strict-reachable`): Only uses reachable tags
|
|
97
|
+
|
|
98
|
+
2. Then, it checks manifest files (package.json, Cargo.toml):
|
|
99
|
+
- Reads version from package.json if it exists
|
|
100
|
+
- Falls back to Cargo.toml if package.json doesn't exist or has no version
|
|
101
|
+
|
|
102
|
+
3. Finally, it compares the versions:
|
|
103
|
+
- If both Git tag and manifest versions exist, it uses the newer version
|
|
104
|
+
- If the versions are equal, it prefers the Git tag for better history tracking
|
|
105
|
+
- If only one source has a version, it uses that
|
|
106
|
+
- If no version is found, it uses the default initial version (0.1.0)
|
|
107
|
+
|
|
108
|
+
This strategy ensures that:
|
|
109
|
+
- Version numbers never go backwards
|
|
110
|
+
- Git history is respected when possible
|
|
111
|
+
- Manifest files are considered as valid version sources
|
|
112
|
+
- The tool always has a valid base version to work from
|
|
113
|
+
|
|
114
|
+
For example:
|
|
115
|
+
```
|
|
116
|
+
Scenario 1:
|
|
117
|
+
- Git tag: v1.0.0
|
|
118
|
+
- package.json: 1.1.0
|
|
119
|
+
Result: Uses 1.1.0 as base (package.json is newer)
|
|
120
|
+
|
|
121
|
+
Scenario 2:
|
|
122
|
+
- Git tag: v1.0.0
|
|
123
|
+
- package.json: 1.0.0
|
|
124
|
+
Result: Uses v1.0.0 as base (versions equal, prefer Git)
|
|
125
|
+
|
|
126
|
+
Scenario 3:
|
|
127
|
+
- Git tag: unreachable v2.0.0
|
|
128
|
+
- package.json: 1.0.0
|
|
129
|
+
Result: Uses 2.0.0 as base in normal mode (unreachable tag is newer)
|
|
130
|
+
Uses 1.0.0 as base in strict mode (unreachable tag ignored)
|
|
131
|
+
```
|
|
168
132
|
|
|
169
133
|
## Package Targeting in Monorepos
|
|
170
134
|
|
|
@@ -430,3 +394,75 @@ For global commit messages, use templates without `${packageName}`:
|
|
|
430
394
|
"commitMessage": "chore: release ${version}"
|
|
431
395
|
}
|
|
432
396
|
```
|
|
397
|
+
|
|
398
|
+
## Monorepo Versioning Modes
|
|
399
|
+
|
|
400
|
+
While primarily used for single packages now, `package-versioner` retains options for monorepo workflows, controlled mainly by the `synced` flag in `version.config.json`.
|
|
401
|
+
|
|
402
|
+
### Synced Mode (`synced: true`)
|
|
403
|
+
|
|
404
|
+
This is the default if the `synced` flag is present and true.
|
|
405
|
+
|
|
406
|
+
- **Behaviour:** The tool calculates **one** version bump based on the overall history (or branch pattern). This single new version is applied to **all** packages within the repository (or just the root `package.json` if not a structured monorepo). A single Git tag is created.
|
|
407
|
+
- **Tag Behaviour:**
|
|
408
|
+
- In **multi-package monorepos**: Creates global tags like `v1.2.3` regardless of `packageSpecificTags` setting
|
|
409
|
+
- In **single-package repositories**: Respects the `packageSpecificTags` setting - can create either `v1.2.3` or `package-name@v1.2.3`
|
|
410
|
+
- **Use Case:** Suitable for monorepos where all packages are tightly coupled and released together with the same version number. Also the effective mode for single-package repositories.
|
|
411
|
+
|
|
412
|
+
### Async Mode (`synced: false`)
|
|
413
|
+
|
|
414
|
+
*(Note: This mode relies heavily on monorepo tooling and structure, like `pnpm workspaces` and correctly configured package dependencies.)*
|
|
415
|
+
|
|
416
|
+
- **Behaviour (Default - No `-t` flag):** The tool analyzes commits to determine which specific packages within the monorepo have changed since the last relevant commit/tag.
|
|
417
|
+
- It calculates an appropriate version bump **independently for each changed package** based on the commits affecting that package.
|
|
418
|
+
- Only the `package.json` files of the changed packages are updated.
|
|
419
|
+
- A **single commit** is created grouping all the version bumps, using the commit message template. **No Git tags are created** in this mode.
|
|
420
|
+
- **Use Case:** Suitable for monorepos where packages are versioned independently, but a single commit represents the batch of updates for traceability.
|
|
421
|
+
|
|
422
|
+
- **Behaviour (Targeted - With `-t` flag):** When using the `-t, --target <targets>` flag:
|
|
423
|
+
- Only the specified packages (respecting the `skip` list) are considered for versioning.
|
|
424
|
+
- It calculates an appropriate version bump **independently for each targeted package** based on its commit history.
|
|
425
|
+
- The `package.json` file of each successfully updated targeted package is modified.
|
|
426
|
+
- An **individual Git tag** (e.g., `packageName@1.2.3`) is created **for each successfully updated package** immediately after its version is bumped.
|
|
427
|
+
- Finally, a **single commit** is created including all the updated `package.json` files, using a summary commit message (e.g., `chore(release): pkg-a, pkg-b 1.2.3 [skip-ci]`).
|
|
428
|
+
- **Important:** Only package-specific tags are created. The global tag (e.g., `v1.2.3`) is **not** automatically generated in this mode. If your release process (like GitHub Releases) depends on a global tag, you'll need to create it manually in your CI/CD script *after* `package-versioner` completes.
|
|
429
|
+
- **Use Case:** Releasing specific packages independently while still tagging each released package individually.
|
|
430
|
+
|
|
431
|
+
## Prerelease Handling
|
|
432
|
+
|
|
433
|
+
`package-versioner` provides flexible handling for prerelease versions, allowing both creation of prereleases and promotion to stable releases.
|
|
434
|
+
|
|
435
|
+
### Creating Prereleases
|
|
436
|
+
|
|
437
|
+
Use the `--prerelease` flag with an identifier to create a prerelease version:
|
|
438
|
+
|
|
439
|
+
```bash
|
|
440
|
+
# Create a beta prerelease
|
|
441
|
+
npx package-versioner --bump minor --prerelease beta
|
|
442
|
+
# Result: 1.0.0 -> 1.1.0-beta.0
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
You can also set a default prerelease identifier in your `version.config.json`:
|
|
446
|
+
|
|
447
|
+
```json
|
|
448
|
+
{
|
|
449
|
+
"prereleaseIdentifier": "beta"
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
### Promoting Prereleases to Stable Releases
|
|
454
|
+
|
|
455
|
+
When using standard bump types (`major`, `minor`, `patch`) with the `--bump` flag on a prerelease version, `package-versioner` will automatically clean the prerelease identifier:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
# Starting from version 1.0.0-beta.1
|
|
459
|
+
npx package-versioner --bump major
|
|
460
|
+
# Result: 1.0.0-beta.1 -> 2.0.0 (not 2.0.0-beta.0)
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
This intuitive behaviour means you don't need to use an empty prerelease identifier (`--prerelease ""`) to promote a prerelease to a stable version. Simply specify the standard bump type and the tool will automatically produce a clean version number.
|
|
464
|
+
|
|
465
|
+
This applies to all standard bump types:
|
|
466
|
+
- `--bump major`: 1.0.0-beta.1 -> 2.0.0
|
|
467
|
+
- `--bump minor`: 1.0.0-beta.1 -> 1.1.0
|
|
468
|
+
- `--bump patch`: 1.0.0-beta.1 -> 1.0.1
|
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.8.
|
|
4
|
+
"version": "0.8.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
@@ -37,34 +37,34 @@
|
|
|
37
37
|
]
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@biomejs/biome": "^2.
|
|
40
|
+
"@biomejs/biome": "^2.2.2",
|
|
41
41
|
"@types/figlet": "^1.5.5",
|
|
42
|
-
"@types/node": "^24.0
|
|
42
|
+
"@types/node": "^24.3.0",
|
|
43
43
|
"@types/semver": "^7.3.13",
|
|
44
44
|
"@vitest/coverage-v8": "^3.2.4",
|
|
45
|
-
"cross-env": "^
|
|
45
|
+
"cross-env": "^10.0.0",
|
|
46
46
|
"husky": "^9.1.7",
|
|
47
|
-
"lint-staged": "^16.1.
|
|
47
|
+
"lint-staged": "^16.1.5",
|
|
48
48
|
"tsup": "^8.5.0",
|
|
49
|
-
"tsx": "^4.20.
|
|
50
|
-
"typescript": "^5.
|
|
49
|
+
"tsx": "^4.20.5",
|
|
50
|
+
"typescript": "^5.9.2",
|
|
51
51
|
"vitest": "^3.2.4"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@manypkg/get-packages": "^3.
|
|
54
|
+
"@manypkg/get-packages": "^3.1.0",
|
|
55
55
|
"@types/micromatch": "^4.0.9",
|
|
56
|
-
"chalk": "^5.
|
|
56
|
+
"chalk": "^5.6.0",
|
|
57
57
|
"commander": "^14.0.0",
|
|
58
58
|
"conventional-changelog-angular": "^8.0.0",
|
|
59
59
|
"conventional-changelog-conventional-commits": "npm:conventional-changelog-conventionalcommits@^9.0.0",
|
|
60
|
-
"conventional-changelog-conventionalcommits": "^9.
|
|
60
|
+
"conventional-changelog-conventionalcommits": "^9.1.0",
|
|
61
61
|
"conventional-commits-filter": "^5.0.0",
|
|
62
62
|
"conventional-recommended-bump": "^11.2.0",
|
|
63
|
-
"figlet": "^1.8.
|
|
63
|
+
"figlet": "^1.8.2",
|
|
64
64
|
"git-semver-tags": "^8.0.0",
|
|
65
65
|
"micromatch": "^4.0.8",
|
|
66
66
|
"semver": "^7.7.2",
|
|
67
|
-
"smol-toml": "^1.4.
|
|
67
|
+
"smol-toml": "^1.4.2"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"build": "tsup src/index.ts --format esm,cjs --dts",
|