package-versioner 0.1.1 → 0.2.0
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 +1 -1
- package/dist/index.cjs +139 -28
- package/dist/index.js +139 -28
- package/docs/VERSIONING_STRATEGIES.md +96 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ npx package-versioner -t @scope/package-a,@scope/package-b
|
|
|
45
45
|
npx package-versioner --dry-run
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
**Note on Targeting:** Using the `-t` flag creates package-specific tags (e.g., `@scope/package-a@1.2.0`) but *not* a global tag (like `v1.2.0`). If needed, create the global tag manually in your CI/CD script after this command.
|
|
49
49
|
|
|
50
50
|
## Configuration
|
|
51
51
|
|
package/dist/index.cjs
CHANGED
|
@@ -58,7 +58,7 @@ var import_figlet = __toESM(require("figlet"), 1);
|
|
|
58
58
|
var package_default = {
|
|
59
59
|
name: "package-versioner",
|
|
60
60
|
description: "A powerful CLI tool for automated semantic versioning based on Git history and conventional commits. Supports monorepos with synchronized or independent package versioning strategies.",
|
|
61
|
-
version: "0.1.
|
|
61
|
+
version: "0.1.1",
|
|
62
62
|
type: "module",
|
|
63
63
|
main: "./dist/index.js",
|
|
64
64
|
module: "./dist/index.mjs",
|
|
@@ -72,17 +72,9 @@ var package_default = {
|
|
|
72
72
|
url: "https://github.com/goosewobbler/package-versioner",
|
|
73
73
|
homepage: "https://github.com/goosewobbler/package-versioner"
|
|
74
74
|
},
|
|
75
|
-
keywords: [
|
|
76
|
-
"version",
|
|
77
|
-
"semver",
|
|
78
|
-
"git",
|
|
79
|
-
"package"
|
|
80
|
-
],
|
|
75
|
+
keywords: ["version", "semver", "git", "package"],
|
|
81
76
|
license: "MIT",
|
|
82
|
-
files: [
|
|
83
|
-
"dist/**",
|
|
84
|
-
"package-versioner.schema.json"
|
|
85
|
-
],
|
|
77
|
+
files: ["dist/**", "docs/**", "package-versioner.schema.json"],
|
|
86
78
|
bin: {
|
|
87
79
|
"package-versioner": "./dist/index.js"
|
|
88
80
|
},
|
|
@@ -100,10 +92,7 @@ var package_default = {
|
|
|
100
92
|
prepare: "husky"
|
|
101
93
|
},
|
|
102
94
|
"lint-staged": {
|
|
103
|
-
"*.{js,ts,jsx,tsx}": [
|
|
104
|
-
"biome check --apply",
|
|
105
|
-
"biome format --write"
|
|
106
|
-
]
|
|
95
|
+
"*.{js,ts,jsx,tsx}": ["biome check --apply", "biome format --write"]
|
|
107
96
|
},
|
|
108
97
|
devDependencies: {
|
|
109
98
|
"@biomejs/biome": "^1.9.4",
|
|
@@ -425,7 +414,7 @@ var VersionEngine = class {
|
|
|
425
414
|
* Returns a list of package.json file paths that were updated (or would be in dry run).
|
|
426
415
|
*/
|
|
427
416
|
async processPackages(discoveredPackages = [], targets = []) {
|
|
428
|
-
const { tagPrefix, skip } = this.config;
|
|
417
|
+
const { tagPrefix, skip, dryRun } = this.config;
|
|
429
418
|
const files = [];
|
|
430
419
|
const pkgsToConsider = discoveredPackages.filter((pkg) => {
|
|
431
420
|
if (skip == null ? void 0 : skip.includes(pkg.packageJson.name)) {
|
|
@@ -448,16 +437,13 @@ var VersionEngine = class {
|
|
|
448
437
|
const latestTag = await getLatestTag();
|
|
449
438
|
const nextVersion = await this.calculateVersion({
|
|
450
439
|
latestTag,
|
|
451
|
-
// This might need refinement for async based on package-specific tags
|
|
452
440
|
tagPrefix: prefix,
|
|
453
441
|
path: pkgPath,
|
|
454
442
|
name,
|
|
455
|
-
// Pass name for potential package-specific tag lookups
|
|
456
443
|
branchPattern: this.config.branchPattern,
|
|
457
444
|
baseBranch: this.config.baseBranch,
|
|
458
445
|
prereleaseIdentifier: this.config.prereleaseIdentifier,
|
|
459
446
|
type: this.config.forceType
|
|
460
|
-
// Pass forced type if provided
|
|
461
447
|
});
|
|
462
448
|
if (!nextVersion) {
|
|
463
449
|
continue;
|
|
@@ -466,7 +452,7 @@ var VersionEngine = class {
|
|
|
466
452
|
path: pkgPath,
|
|
467
453
|
version: nextVersion,
|
|
468
454
|
name,
|
|
469
|
-
dryRun
|
|
455
|
+
dryRun
|
|
470
456
|
});
|
|
471
457
|
files.push(import_node_path2.default.join(pkgPath, "package.json"));
|
|
472
458
|
}
|
|
@@ -598,13 +584,19 @@ var VersionEngine = class {
|
|
|
598
584
|
const pkgPath = pkg.dir;
|
|
599
585
|
const prefix = formatTagPrefix(tagPrefix);
|
|
600
586
|
const latestTag = await getLatestTag();
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
587
|
+
let nextVersion = void 0;
|
|
588
|
+
try {
|
|
589
|
+
nextVersion = await this.calculateVersion({
|
|
590
|
+
latestTag,
|
|
591
|
+
tagPrefix: prefix,
|
|
592
|
+
path: pkgPath,
|
|
593
|
+
name: packageName
|
|
594
|
+
});
|
|
595
|
+
} catch (error) {
|
|
596
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
597
|
+
log("error", `Failed to calculate version for ${packageName}: ${errorMessage}`);
|
|
598
|
+
}
|
|
599
|
+
if (nextVersion === void 0 || nextVersion === "") {
|
|
608
600
|
log("info", `No version change needed for ${packageName}`);
|
|
609
601
|
return;
|
|
610
602
|
}
|
|
@@ -630,11 +622,14 @@ var VersionEngine = class {
|
|
|
630
622
|
* Async versioning strategy (each package gets its own version)
|
|
631
623
|
*/
|
|
632
624
|
async asyncStrategy(cliTargets = []) {
|
|
625
|
+
if (cliTargets.length > 0) {
|
|
626
|
+
await this.asyncTargetedStrategy(cliTargets);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
633
629
|
const {
|
|
634
630
|
commitMessage = "chore(release): ${version}",
|
|
635
631
|
// Align with test expectations
|
|
636
632
|
skipHooks
|
|
637
|
-
// Add skipHooks here
|
|
638
633
|
} = this.config;
|
|
639
634
|
let pkgsResult;
|
|
640
635
|
try {
|
|
@@ -658,6 +653,7 @@ var VersionEngine = class {
|
|
|
658
653
|
await gitProcess({
|
|
659
654
|
files: pkgsToProcess,
|
|
660
655
|
nextTag: "",
|
|
656
|
+
// No tag for default async
|
|
661
657
|
commitMessage: formattedCommitMessage,
|
|
662
658
|
skipHooks,
|
|
663
659
|
dryRun: this.config.dryRun
|
|
@@ -671,6 +667,121 @@ var VersionEngine = class {
|
|
|
671
667
|
(0, import_node_process3.exit)(1);
|
|
672
668
|
}
|
|
673
669
|
}
|
|
670
|
+
// --- NEW METHOD for Async + Targeted ---
|
|
671
|
+
async asyncTargetedStrategy(cliTargets) {
|
|
672
|
+
var _a;
|
|
673
|
+
const {
|
|
674
|
+
tagPrefix,
|
|
675
|
+
skip,
|
|
676
|
+
dryRun,
|
|
677
|
+
skipHooks,
|
|
678
|
+
commitMessage: commitMessageTemplate
|
|
679
|
+
} = this.config;
|
|
680
|
+
const updatedPackagesInfo = [];
|
|
681
|
+
log("info", `Processing targeted packages: ${cliTargets.join(", ")}`);
|
|
682
|
+
let pkgsResult;
|
|
683
|
+
try {
|
|
684
|
+
pkgsResult = (0, import_get_packages.getPackagesSync)((0, import_node_process3.cwd)());
|
|
685
|
+
if (!pkgsResult || !pkgsResult.packages) {
|
|
686
|
+
throw new Error("Failed to get packages information");
|
|
687
|
+
}
|
|
688
|
+
} catch (error) {
|
|
689
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
690
|
+
log("error", `Failed to get packages information: ${errorMessage}`);
|
|
691
|
+
(0, import_node_process3.exit)(1);
|
|
692
|
+
}
|
|
693
|
+
const pkgsToConsider = pkgsResult.packages.filter((pkg) => {
|
|
694
|
+
if (skip == null ? void 0 : skip.includes(pkg.packageJson.name)) {
|
|
695
|
+
log("info", `Skipping package ${pkg.packageJson.name} based on config skip list.`);
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
const isTargeted = cliTargets.includes(pkg.packageJson.name);
|
|
699
|
+
if (!isTargeted) {
|
|
700
|
+
}
|
|
701
|
+
return isTargeted;
|
|
702
|
+
});
|
|
703
|
+
log("info", `Found ${pkgsToConsider.length} targeted package(s) to process after filtering.`);
|
|
704
|
+
if (pkgsToConsider.length === 0) {
|
|
705
|
+
log("info", "No matching targeted packages found to process.");
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
for (const pkg of pkgsToConsider) {
|
|
709
|
+
const name = pkg.packageJson.name;
|
|
710
|
+
const pkgPath = pkg.dir;
|
|
711
|
+
const prefix = formatTagPrefix(tagPrefix);
|
|
712
|
+
const latestTag = await getLatestTag();
|
|
713
|
+
const nextVersion = await this.calculateVersion({
|
|
714
|
+
latestTag,
|
|
715
|
+
tagPrefix: prefix,
|
|
716
|
+
path: pkgPath,
|
|
717
|
+
name,
|
|
718
|
+
branchPattern: this.config.branchPattern,
|
|
719
|
+
baseBranch: this.config.baseBranch,
|
|
720
|
+
prereleaseIdentifier: this.config.prereleaseIdentifier,
|
|
721
|
+
type: this.config.forceType
|
|
722
|
+
});
|
|
723
|
+
if (!nextVersion) {
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
updatePackageVersion({
|
|
727
|
+
path: pkgPath,
|
|
728
|
+
version: nextVersion,
|
|
729
|
+
name,
|
|
730
|
+
dryRun
|
|
731
|
+
});
|
|
732
|
+
const packageTag = formatTag(
|
|
733
|
+
{ synced: false, name, tagPrefix },
|
|
734
|
+
{ version: nextVersion, tagPrefix }
|
|
735
|
+
);
|
|
736
|
+
const tagMessage = `chore(release): ${name} ${nextVersion}`;
|
|
737
|
+
if (!dryRun) {
|
|
738
|
+
try {
|
|
739
|
+
await createGitTag({ tag: packageTag, message: tagMessage });
|
|
740
|
+
log("success", `Created tag: ${packageTag}`);
|
|
741
|
+
} catch (tagError) {
|
|
742
|
+
log(
|
|
743
|
+
"error",
|
|
744
|
+
`Failed to create tag ${packageTag} for ${name}: ${tagError.message}`
|
|
745
|
+
);
|
|
746
|
+
log("error", tagError.stack || "No stack trace available");
|
|
747
|
+
}
|
|
748
|
+
} else {
|
|
749
|
+
log("info", `[DRY RUN] Would create tag: ${packageTag}`);
|
|
750
|
+
}
|
|
751
|
+
updatedPackagesInfo.push({ name, version: nextVersion, path: pkgPath });
|
|
752
|
+
}
|
|
753
|
+
if (updatedPackagesInfo.length === 0) {
|
|
754
|
+
log("info", "No targeted packages required a version update.");
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const filesToCommit = updatedPackagesInfo.map((info) => import_node_path2.default.join(info.path, "package.json"));
|
|
758
|
+
const packageNames = updatedPackagesInfo.map((p) => p.name).join(", ");
|
|
759
|
+
const representativeVersion = ((_a = updatedPackagesInfo[0]) == null ? void 0 : _a.version) || "multiple";
|
|
760
|
+
let commitMessage = commitMessageTemplate || "chore(release): publish packages";
|
|
761
|
+
if (updatedPackagesInfo.length === 1 && commitMessage.includes("${version}")) {
|
|
762
|
+
commitMessage = formatCommitMessage(commitMessage, representativeVersion);
|
|
763
|
+
} else {
|
|
764
|
+
commitMessage = `chore(release): ${packageNames} ${representativeVersion}`;
|
|
765
|
+
}
|
|
766
|
+
commitMessage += " [skip-ci]";
|
|
767
|
+
if (!dryRun) {
|
|
768
|
+
try {
|
|
769
|
+
await gitAdd(filesToCommit);
|
|
770
|
+
await gitCommit({ message: commitMessage, skipHooks });
|
|
771
|
+
log("success", `Created commit for targeted release: ${packageNames}`);
|
|
772
|
+
} catch (commitError) {
|
|
773
|
+
log("error", "Failed to create commit for targeted release.");
|
|
774
|
+
console.error(commitError);
|
|
775
|
+
(0, import_node_process3.exit)(1);
|
|
776
|
+
}
|
|
777
|
+
} else {
|
|
778
|
+
log("info", "[DRY RUN] Would add files:");
|
|
779
|
+
for (const file of filesToCommit) {
|
|
780
|
+
log("info", ` - ${file}`);
|
|
781
|
+
}
|
|
782
|
+
log("info", `[DRY RUN] Would commit with message: "${commitMessage}"`);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
674
785
|
};
|
|
675
786
|
|
|
676
787
|
// src/index.ts
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ import figlet from "figlet";
|
|
|
35
35
|
var package_default = {
|
|
36
36
|
name: "package-versioner",
|
|
37
37
|
description: "A powerful CLI tool for automated semantic versioning based on Git history and conventional commits. Supports monorepos with synchronized or independent package versioning strategies.",
|
|
38
|
-
version: "0.1.
|
|
38
|
+
version: "0.1.1",
|
|
39
39
|
type: "module",
|
|
40
40
|
main: "./dist/index.js",
|
|
41
41
|
module: "./dist/index.mjs",
|
|
@@ -49,17 +49,9 @@ var package_default = {
|
|
|
49
49
|
url: "https://github.com/goosewobbler/package-versioner",
|
|
50
50
|
homepage: "https://github.com/goosewobbler/package-versioner"
|
|
51
51
|
},
|
|
52
|
-
keywords: [
|
|
53
|
-
"version",
|
|
54
|
-
"semver",
|
|
55
|
-
"git",
|
|
56
|
-
"package"
|
|
57
|
-
],
|
|
52
|
+
keywords: ["version", "semver", "git", "package"],
|
|
58
53
|
license: "MIT",
|
|
59
|
-
files: [
|
|
60
|
-
"dist/**",
|
|
61
|
-
"package-versioner.schema.json"
|
|
62
|
-
],
|
|
54
|
+
files: ["dist/**", "docs/**", "package-versioner.schema.json"],
|
|
63
55
|
bin: {
|
|
64
56
|
"package-versioner": "./dist/index.js"
|
|
65
57
|
},
|
|
@@ -77,10 +69,7 @@ var package_default = {
|
|
|
77
69
|
prepare: "husky"
|
|
78
70
|
},
|
|
79
71
|
"lint-staged": {
|
|
80
|
-
"*.{js,ts,jsx,tsx}": [
|
|
81
|
-
"biome check --apply",
|
|
82
|
-
"biome format --write"
|
|
83
|
-
]
|
|
72
|
+
"*.{js,ts,jsx,tsx}": ["biome check --apply", "biome format --write"]
|
|
84
73
|
},
|
|
85
74
|
devDependencies: {
|
|
86
75
|
"@biomejs/biome": "^1.9.4",
|
|
@@ -402,7 +391,7 @@ var VersionEngine = class {
|
|
|
402
391
|
* Returns a list of package.json file paths that were updated (or would be in dry run).
|
|
403
392
|
*/
|
|
404
393
|
async processPackages(discoveredPackages = [], targets = []) {
|
|
405
|
-
const { tagPrefix, skip } = this.config;
|
|
394
|
+
const { tagPrefix, skip, dryRun } = this.config;
|
|
406
395
|
const files = [];
|
|
407
396
|
const pkgsToConsider = discoveredPackages.filter((pkg) => {
|
|
408
397
|
if (skip == null ? void 0 : skip.includes(pkg.packageJson.name)) {
|
|
@@ -425,16 +414,13 @@ var VersionEngine = class {
|
|
|
425
414
|
const latestTag = await getLatestTag();
|
|
426
415
|
const nextVersion = await this.calculateVersion({
|
|
427
416
|
latestTag,
|
|
428
|
-
// This might need refinement for async based on package-specific tags
|
|
429
417
|
tagPrefix: prefix,
|
|
430
418
|
path: pkgPath,
|
|
431
419
|
name,
|
|
432
|
-
// Pass name for potential package-specific tag lookups
|
|
433
420
|
branchPattern: this.config.branchPattern,
|
|
434
421
|
baseBranch: this.config.baseBranch,
|
|
435
422
|
prereleaseIdentifier: this.config.prereleaseIdentifier,
|
|
436
423
|
type: this.config.forceType
|
|
437
|
-
// Pass forced type if provided
|
|
438
424
|
});
|
|
439
425
|
if (!nextVersion) {
|
|
440
426
|
continue;
|
|
@@ -443,7 +429,7 @@ var VersionEngine = class {
|
|
|
443
429
|
path: pkgPath,
|
|
444
430
|
version: nextVersion,
|
|
445
431
|
name,
|
|
446
|
-
dryRun
|
|
432
|
+
dryRun
|
|
447
433
|
});
|
|
448
434
|
files.push(path.join(pkgPath, "package.json"));
|
|
449
435
|
}
|
|
@@ -575,13 +561,19 @@ var VersionEngine = class {
|
|
|
575
561
|
const pkgPath = pkg.dir;
|
|
576
562
|
const prefix = formatTagPrefix(tagPrefix);
|
|
577
563
|
const latestTag = await getLatestTag();
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
564
|
+
let nextVersion = void 0;
|
|
565
|
+
try {
|
|
566
|
+
nextVersion = await this.calculateVersion({
|
|
567
|
+
latestTag,
|
|
568
|
+
tagPrefix: prefix,
|
|
569
|
+
path: pkgPath,
|
|
570
|
+
name: packageName
|
|
571
|
+
});
|
|
572
|
+
} catch (error) {
|
|
573
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
574
|
+
log("error", `Failed to calculate version for ${packageName}: ${errorMessage}`);
|
|
575
|
+
}
|
|
576
|
+
if (nextVersion === void 0 || nextVersion === "") {
|
|
585
577
|
log("info", `No version change needed for ${packageName}`);
|
|
586
578
|
return;
|
|
587
579
|
}
|
|
@@ -607,11 +599,14 @@ var VersionEngine = class {
|
|
|
607
599
|
* Async versioning strategy (each package gets its own version)
|
|
608
600
|
*/
|
|
609
601
|
async asyncStrategy(cliTargets = []) {
|
|
602
|
+
if (cliTargets.length > 0) {
|
|
603
|
+
await this.asyncTargetedStrategy(cliTargets);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
610
606
|
const {
|
|
611
607
|
commitMessage = "chore(release): ${version}",
|
|
612
608
|
// Align with test expectations
|
|
613
609
|
skipHooks
|
|
614
|
-
// Add skipHooks here
|
|
615
610
|
} = this.config;
|
|
616
611
|
let pkgsResult;
|
|
617
612
|
try {
|
|
@@ -635,6 +630,7 @@ var VersionEngine = class {
|
|
|
635
630
|
await gitProcess({
|
|
636
631
|
files: pkgsToProcess,
|
|
637
632
|
nextTag: "",
|
|
633
|
+
// No tag for default async
|
|
638
634
|
commitMessage: formattedCommitMessage,
|
|
639
635
|
skipHooks,
|
|
640
636
|
dryRun: this.config.dryRun
|
|
@@ -648,6 +644,121 @@ var VersionEngine = class {
|
|
|
648
644
|
exit(1);
|
|
649
645
|
}
|
|
650
646
|
}
|
|
647
|
+
// --- NEW METHOD for Async + Targeted ---
|
|
648
|
+
async asyncTargetedStrategy(cliTargets) {
|
|
649
|
+
var _a;
|
|
650
|
+
const {
|
|
651
|
+
tagPrefix,
|
|
652
|
+
skip,
|
|
653
|
+
dryRun,
|
|
654
|
+
skipHooks,
|
|
655
|
+
commitMessage: commitMessageTemplate
|
|
656
|
+
} = this.config;
|
|
657
|
+
const updatedPackagesInfo = [];
|
|
658
|
+
log("info", `Processing targeted packages: ${cliTargets.join(", ")}`);
|
|
659
|
+
let pkgsResult;
|
|
660
|
+
try {
|
|
661
|
+
pkgsResult = getPackagesSync(cwd3());
|
|
662
|
+
if (!pkgsResult || !pkgsResult.packages) {
|
|
663
|
+
throw new Error("Failed to get packages information");
|
|
664
|
+
}
|
|
665
|
+
} catch (error) {
|
|
666
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
667
|
+
log("error", `Failed to get packages information: ${errorMessage}`);
|
|
668
|
+
exit(1);
|
|
669
|
+
}
|
|
670
|
+
const pkgsToConsider = pkgsResult.packages.filter((pkg) => {
|
|
671
|
+
if (skip == null ? void 0 : skip.includes(pkg.packageJson.name)) {
|
|
672
|
+
log("info", `Skipping package ${pkg.packageJson.name} based on config skip list.`);
|
|
673
|
+
return false;
|
|
674
|
+
}
|
|
675
|
+
const isTargeted = cliTargets.includes(pkg.packageJson.name);
|
|
676
|
+
if (!isTargeted) {
|
|
677
|
+
}
|
|
678
|
+
return isTargeted;
|
|
679
|
+
});
|
|
680
|
+
log("info", `Found ${pkgsToConsider.length} targeted package(s) to process after filtering.`);
|
|
681
|
+
if (pkgsToConsider.length === 0) {
|
|
682
|
+
log("info", "No matching targeted packages found to process.");
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
for (const pkg of pkgsToConsider) {
|
|
686
|
+
const name = pkg.packageJson.name;
|
|
687
|
+
const pkgPath = pkg.dir;
|
|
688
|
+
const prefix = formatTagPrefix(tagPrefix);
|
|
689
|
+
const latestTag = await getLatestTag();
|
|
690
|
+
const nextVersion = await this.calculateVersion({
|
|
691
|
+
latestTag,
|
|
692
|
+
tagPrefix: prefix,
|
|
693
|
+
path: pkgPath,
|
|
694
|
+
name,
|
|
695
|
+
branchPattern: this.config.branchPattern,
|
|
696
|
+
baseBranch: this.config.baseBranch,
|
|
697
|
+
prereleaseIdentifier: this.config.prereleaseIdentifier,
|
|
698
|
+
type: this.config.forceType
|
|
699
|
+
});
|
|
700
|
+
if (!nextVersion) {
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
updatePackageVersion({
|
|
704
|
+
path: pkgPath,
|
|
705
|
+
version: nextVersion,
|
|
706
|
+
name,
|
|
707
|
+
dryRun
|
|
708
|
+
});
|
|
709
|
+
const packageTag = formatTag(
|
|
710
|
+
{ synced: false, name, tagPrefix },
|
|
711
|
+
{ version: nextVersion, tagPrefix }
|
|
712
|
+
);
|
|
713
|
+
const tagMessage = `chore(release): ${name} ${nextVersion}`;
|
|
714
|
+
if (!dryRun) {
|
|
715
|
+
try {
|
|
716
|
+
await createGitTag({ tag: packageTag, message: tagMessage });
|
|
717
|
+
log("success", `Created tag: ${packageTag}`);
|
|
718
|
+
} catch (tagError) {
|
|
719
|
+
log(
|
|
720
|
+
"error",
|
|
721
|
+
`Failed to create tag ${packageTag} for ${name}: ${tagError.message}`
|
|
722
|
+
);
|
|
723
|
+
log("error", tagError.stack || "No stack trace available");
|
|
724
|
+
}
|
|
725
|
+
} else {
|
|
726
|
+
log("info", `[DRY RUN] Would create tag: ${packageTag}`);
|
|
727
|
+
}
|
|
728
|
+
updatedPackagesInfo.push({ name, version: nextVersion, path: pkgPath });
|
|
729
|
+
}
|
|
730
|
+
if (updatedPackagesInfo.length === 0) {
|
|
731
|
+
log("info", "No targeted packages required a version update.");
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
const filesToCommit = updatedPackagesInfo.map((info) => path.join(info.path, "package.json"));
|
|
735
|
+
const packageNames = updatedPackagesInfo.map((p) => p.name).join(", ");
|
|
736
|
+
const representativeVersion = ((_a = updatedPackagesInfo[0]) == null ? void 0 : _a.version) || "multiple";
|
|
737
|
+
let commitMessage = commitMessageTemplate || "chore(release): publish packages";
|
|
738
|
+
if (updatedPackagesInfo.length === 1 && commitMessage.includes("${version}")) {
|
|
739
|
+
commitMessage = formatCommitMessage(commitMessage, representativeVersion);
|
|
740
|
+
} else {
|
|
741
|
+
commitMessage = `chore(release): ${packageNames} ${representativeVersion}`;
|
|
742
|
+
}
|
|
743
|
+
commitMessage += " [skip-ci]";
|
|
744
|
+
if (!dryRun) {
|
|
745
|
+
try {
|
|
746
|
+
await gitAdd(filesToCommit);
|
|
747
|
+
await gitCommit({ message: commitMessage, skipHooks });
|
|
748
|
+
log("success", `Created commit for targeted release: ${packageNames}`);
|
|
749
|
+
} catch (commitError) {
|
|
750
|
+
log("error", "Failed to create commit for targeted release.");
|
|
751
|
+
console.error(commitError);
|
|
752
|
+
exit(1);
|
|
753
|
+
}
|
|
754
|
+
} else {
|
|
755
|
+
log("info", "[DRY RUN] Would add files:");
|
|
756
|
+
for (const file of filesToCommit) {
|
|
757
|
+
log("info", ` - ${file}`);
|
|
758
|
+
}
|
|
759
|
+
log("info", `[DRY RUN] Would commit with message: "${commitMessage}"`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
651
762
|
};
|
|
652
763
|
|
|
653
764
|
// src/index.ts
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Versioning Strategies and Concepts
|
|
2
|
+
|
|
3
|
+
`package-versioner` offers flexible ways to determine the next version for your project based on its history and your configuration.
|
|
4
|
+
|
|
5
|
+
## How the Next Version is Calculated
|
|
6
|
+
|
|
7
|
+
There are two primary methods the tool uses to decide the version bump (e.g., patch, minor, major), configured via the `versionStrategy` option in `version.config.json`:
|
|
8
|
+
|
|
9
|
+
### 1. Conventional Commits (`versionStrategy: "conventional"`)
|
|
10
|
+
|
|
11
|
+
This is the default strategy. `package-versioner` analyzes Git commit messages since the last Git tag that follows semver patterns. It uses the [conventional-commits](https://www.conventionalcommits.org/) specification to determine the bump:
|
|
12
|
+
|
|
13
|
+
- **Patch Bump (e.g., 1.2.3 -> 1.2.4):** Triggered by `fix:` commit types.
|
|
14
|
+
- **Minor Bump (e.g., 1.2.3 -> 1.3.0):** Triggered by `feat:` commit types.
|
|
15
|
+
- **Major Bump (e.g., 1.2.3 -> 2.0.0):** Triggered by commits with `BREAKING CHANGE:` in the footer or `feat!:`, `fix!:` etc. in the header.
|
|
16
|
+
|
|
17
|
+
The specific preset used for analysis (e.g., "angular", "conventional") can be set using the `preset` option in `version.config.json`.
|
|
18
|
+
|
|
19
|
+
**Format:** `<type>(<scope>): <subject>`
|
|
20
|
+
|
|
21
|
+
`<scope>` is optional.
|
|
22
|
+
|
|
23
|
+
**Example Commit Types:**
|
|
24
|
+
|
|
25
|
+
- `feat:` (new feature for the user)
|
|
26
|
+
- `fix:` (bug fix for the user)
|
|
27
|
+
- `docs:` (changes to the documentation)
|
|
28
|
+
- `style:` (formatting, missing semi-colons, etc; no production code change)
|
|
29
|
+
- `refactor:` (refactoring production code, e.g. renaming a variable)
|
|
30
|
+
- `test:` (adding missing tests, refactoring tests; no production code change)
|
|
31
|
+
- `chore:` (updating build tasks etc; no production code change)
|
|
32
|
+
|
|
33
|
+
**References:**
|
|
34
|
+
|
|
35
|
+
- [https://www.conventionalcommits.org/](https://www.conventionalcommits.org/)
|
|
36
|
+
- [https://github.com/conventional-changelog/conventional-changelog](https://github.com/conventional-changelog/conventional-changelog)
|
|
37
|
+
|
|
38
|
+
### 2. Branch Pattern (`versionStrategy: "branchPattern"`)
|
|
39
|
+
|
|
40
|
+
This strategy uses the name of the current Git branch (or the most recently merged branch matching a pattern, if applicable) to determine the version bump.
|
|
41
|
+
|
|
42
|
+
You define patterns in the `branchPattern` array in `version.config.json`. Each pattern is a string like `"prefix:bumptype"`.
|
|
43
|
+
|
|
44
|
+
**Example `version.config.json`:**
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"versionStrategy": "branchPattern",
|
|
49
|
+
"branchPattern": [
|
|
50
|
+
"feature:minor",
|
|
51
|
+
"hotfix:patch",
|
|
52
|
+
"fix:patch",
|
|
53
|
+
"release:major"
|
|
54
|
+
],
|
|
55
|
+
"baseBranch": "main"
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**How it works:**
|
|
60
|
+
|
|
61
|
+
1. The tool checks the current branch name.
|
|
62
|
+
2. It might also look for the most recently merged branch into `baseBranch` that matches any pattern in `branchPattern`.
|
|
63
|
+
3. It compares the relevant branch name (current or last merged) against the prefixes in `branchPattern`.
|
|
64
|
+
4. If a match is found (e.g., current branch is `feature/add-login`), it applies the corresponding bump type (`minor` in this case).
|
|
65
|
+
|
|
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
|
+
|
|
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
|
+
- **Behavior:** 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 (e.g., `v1.2.3`).
|
|
77
|
+
- **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.
|
|
78
|
+
|
|
79
|
+
### Async Mode (`synced: false`)
|
|
80
|
+
|
|
81
|
+
*(Note: This mode relies heavily on monorepo tooling and structure, like `pnpm workspaces` and correctly configured package dependencies.)*
|
|
82
|
+
|
|
83
|
+
- **Behavior (Default - No `-t` flag):** The tool analyzes commits to determine which specific packages within the monorepo have changed since the last relevant commit/tag.
|
|
84
|
+
- It calculates an appropriate version bump **independently for each changed package** based on the commits affecting that package.
|
|
85
|
+
- Only the `package.json` files of the changed packages are updated.
|
|
86
|
+
- A **single commit** is created grouping all the version bumps, using the commit message template. **No Git tags are created** in this mode.
|
|
87
|
+
- **Use Case:** Suitable for monorepos where packages are versioned independently, but a single commit represents the batch of updates for traceability.
|
|
88
|
+
|
|
89
|
+
- **Behavior (Targeted - With `-t` flag):** When using the `-t, --target <targets>` flag:
|
|
90
|
+
- Only the specified packages (respecting the `skip` list) are considered for versioning.
|
|
91
|
+
- It calculates an appropriate version bump **independently for each targeted package** based on its commit history.
|
|
92
|
+
- The `package.json` file of each successfully updated targeted package is modified.
|
|
93
|
+
- An **individual Git tag** (e.g., `packageName@1.2.3`) is created **for each successfully updated package** immediately after its version is bumped.
|
|
94
|
+
- 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]`).
|
|
95
|
+
- **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.
|
|
96
|
+
- **Use Case:** Releasing specific packages independently while still tagging each released package individually.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "package-versioner",
|
|
3
3
|
"description": "A powerful CLI tool for automated semantic versioning based on Git history and conventional commits. Supports monorepos with synchronized or independent package versioning strategies.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.mjs",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"license": "MIT",
|
|
25
25
|
"files": [
|
|
26
26
|
"dist/**",
|
|
27
|
+
"docs/**",
|
|
27
28
|
"package-versioner.schema.json"
|
|
28
29
|
],
|
|
29
30
|
"bin": {
|