package-versioner 0.1.0 → 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 +149 -34
- package/dist/index.js +149 -34
- 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.
|
|
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",
|
|
@@ -217,18 +206,22 @@ async function gitProcess({ files, nextTag, commitMessage, skipHooks, dryRun })
|
|
|
217
206
|
message: commitMessage,
|
|
218
207
|
skipHooks
|
|
219
208
|
});
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
209
|
+
if (nextTag) {
|
|
210
|
+
const tagMessage = `New Version ${nextTag} generated at ${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
211
|
+
await createGitTag({
|
|
212
|
+
tag: nextTag,
|
|
213
|
+
message: tagMessage
|
|
214
|
+
});
|
|
215
|
+
}
|
|
225
216
|
} else {
|
|
226
217
|
log("info", "[DRY RUN] Would add files:");
|
|
227
218
|
for (const file of files) {
|
|
228
219
|
log("info", ` - ${file}`);
|
|
229
220
|
}
|
|
230
221
|
log("info", `[DRY RUN] Would commit with message: "${commitMessage}"`);
|
|
231
|
-
|
|
222
|
+
if (nextTag) {
|
|
223
|
+
log("info", `[DRY RUN] Would create tag: ${nextTag}`);
|
|
224
|
+
}
|
|
232
225
|
}
|
|
233
226
|
} catch (err) {
|
|
234
227
|
console.log(err);
|
|
@@ -421,7 +414,7 @@ var VersionEngine = class {
|
|
|
421
414
|
* Returns a list of package.json file paths that were updated (or would be in dry run).
|
|
422
415
|
*/
|
|
423
416
|
async processPackages(discoveredPackages = [], targets = []) {
|
|
424
|
-
const { tagPrefix, skip } = this.config;
|
|
417
|
+
const { tagPrefix, skip, dryRun } = this.config;
|
|
425
418
|
const files = [];
|
|
426
419
|
const pkgsToConsider = discoveredPackages.filter((pkg) => {
|
|
427
420
|
if (skip == null ? void 0 : skip.includes(pkg.packageJson.name)) {
|
|
@@ -444,16 +437,13 @@ var VersionEngine = class {
|
|
|
444
437
|
const latestTag = await getLatestTag();
|
|
445
438
|
const nextVersion = await this.calculateVersion({
|
|
446
439
|
latestTag,
|
|
447
|
-
// This might need refinement for async based on package-specific tags
|
|
448
440
|
tagPrefix: prefix,
|
|
449
441
|
path: pkgPath,
|
|
450
442
|
name,
|
|
451
|
-
// Pass name for potential package-specific tag lookups
|
|
452
443
|
branchPattern: this.config.branchPattern,
|
|
453
444
|
baseBranch: this.config.baseBranch,
|
|
454
445
|
prereleaseIdentifier: this.config.prereleaseIdentifier,
|
|
455
446
|
type: this.config.forceType
|
|
456
|
-
// Pass forced type if provided
|
|
457
447
|
});
|
|
458
448
|
if (!nextVersion) {
|
|
459
449
|
continue;
|
|
@@ -462,7 +452,7 @@ var VersionEngine = class {
|
|
|
462
452
|
path: pkgPath,
|
|
463
453
|
version: nextVersion,
|
|
464
454
|
name,
|
|
465
|
-
dryRun
|
|
455
|
+
dryRun
|
|
466
456
|
});
|
|
467
457
|
files.push(import_node_path2.default.join(pkgPath, "package.json"));
|
|
468
458
|
}
|
|
@@ -594,13 +584,19 @@ var VersionEngine = class {
|
|
|
594
584
|
const pkgPath = pkg.dir;
|
|
595
585
|
const prefix = formatTagPrefix(tagPrefix);
|
|
596
586
|
const latestTag = await getLatestTag();
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
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 === "") {
|
|
604
600
|
log("info", `No version change needed for ${packageName}`);
|
|
605
601
|
return;
|
|
606
602
|
}
|
|
@@ -626,11 +622,14 @@ var VersionEngine = class {
|
|
|
626
622
|
* Async versioning strategy (each package gets its own version)
|
|
627
623
|
*/
|
|
628
624
|
async asyncStrategy(cliTargets = []) {
|
|
625
|
+
if (cliTargets.length > 0) {
|
|
626
|
+
await this.asyncTargetedStrategy(cliTargets);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
629
|
const {
|
|
630
630
|
commitMessage = "chore(release): ${version}",
|
|
631
631
|
// Align with test expectations
|
|
632
632
|
skipHooks
|
|
633
|
-
// Add skipHooks here
|
|
634
633
|
} = this.config;
|
|
635
634
|
let pkgsResult;
|
|
636
635
|
try {
|
|
@@ -654,6 +653,7 @@ var VersionEngine = class {
|
|
|
654
653
|
await gitProcess({
|
|
655
654
|
files: pkgsToProcess,
|
|
656
655
|
nextTag: "",
|
|
656
|
+
// No tag for default async
|
|
657
657
|
commitMessage: formattedCommitMessage,
|
|
658
658
|
skipHooks,
|
|
659
659
|
dryRun: this.config.dryRun
|
|
@@ -667,6 +667,121 @@ var VersionEngine = class {
|
|
|
667
667
|
(0, import_node_process3.exit)(1);
|
|
668
668
|
}
|
|
669
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
|
+
}
|
|
670
785
|
};
|
|
671
786
|
|
|
672
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.
|
|
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",
|
|
@@ -194,18 +183,22 @@ async function gitProcess({ files, nextTag, commitMessage, skipHooks, dryRun })
|
|
|
194
183
|
message: commitMessage,
|
|
195
184
|
skipHooks
|
|
196
185
|
});
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
186
|
+
if (nextTag) {
|
|
187
|
+
const tagMessage = `New Version ${nextTag} generated at ${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
188
|
+
await createGitTag({
|
|
189
|
+
tag: nextTag,
|
|
190
|
+
message: tagMessage
|
|
191
|
+
});
|
|
192
|
+
}
|
|
202
193
|
} else {
|
|
203
194
|
log("info", "[DRY RUN] Would add files:");
|
|
204
195
|
for (const file of files) {
|
|
205
196
|
log("info", ` - ${file}`);
|
|
206
197
|
}
|
|
207
198
|
log("info", `[DRY RUN] Would commit with message: "${commitMessage}"`);
|
|
208
|
-
|
|
199
|
+
if (nextTag) {
|
|
200
|
+
log("info", `[DRY RUN] Would create tag: ${nextTag}`);
|
|
201
|
+
}
|
|
209
202
|
}
|
|
210
203
|
} catch (err) {
|
|
211
204
|
console.log(err);
|
|
@@ -398,7 +391,7 @@ var VersionEngine = class {
|
|
|
398
391
|
* Returns a list of package.json file paths that were updated (or would be in dry run).
|
|
399
392
|
*/
|
|
400
393
|
async processPackages(discoveredPackages = [], targets = []) {
|
|
401
|
-
const { tagPrefix, skip } = this.config;
|
|
394
|
+
const { tagPrefix, skip, dryRun } = this.config;
|
|
402
395
|
const files = [];
|
|
403
396
|
const pkgsToConsider = discoveredPackages.filter((pkg) => {
|
|
404
397
|
if (skip == null ? void 0 : skip.includes(pkg.packageJson.name)) {
|
|
@@ -421,16 +414,13 @@ var VersionEngine = class {
|
|
|
421
414
|
const latestTag = await getLatestTag();
|
|
422
415
|
const nextVersion = await this.calculateVersion({
|
|
423
416
|
latestTag,
|
|
424
|
-
// This might need refinement for async based on package-specific tags
|
|
425
417
|
tagPrefix: prefix,
|
|
426
418
|
path: pkgPath,
|
|
427
419
|
name,
|
|
428
|
-
// Pass name for potential package-specific tag lookups
|
|
429
420
|
branchPattern: this.config.branchPattern,
|
|
430
421
|
baseBranch: this.config.baseBranch,
|
|
431
422
|
prereleaseIdentifier: this.config.prereleaseIdentifier,
|
|
432
423
|
type: this.config.forceType
|
|
433
|
-
// Pass forced type if provided
|
|
434
424
|
});
|
|
435
425
|
if (!nextVersion) {
|
|
436
426
|
continue;
|
|
@@ -439,7 +429,7 @@ var VersionEngine = class {
|
|
|
439
429
|
path: pkgPath,
|
|
440
430
|
version: nextVersion,
|
|
441
431
|
name,
|
|
442
|
-
dryRun
|
|
432
|
+
dryRun
|
|
443
433
|
});
|
|
444
434
|
files.push(path.join(pkgPath, "package.json"));
|
|
445
435
|
}
|
|
@@ -571,13 +561,19 @@ var VersionEngine = class {
|
|
|
571
561
|
const pkgPath = pkg.dir;
|
|
572
562
|
const prefix = formatTagPrefix(tagPrefix);
|
|
573
563
|
const latestTag = await getLatestTag();
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
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 === "") {
|
|
581
577
|
log("info", `No version change needed for ${packageName}`);
|
|
582
578
|
return;
|
|
583
579
|
}
|
|
@@ -603,11 +599,14 @@ var VersionEngine = class {
|
|
|
603
599
|
* Async versioning strategy (each package gets its own version)
|
|
604
600
|
*/
|
|
605
601
|
async asyncStrategy(cliTargets = []) {
|
|
602
|
+
if (cliTargets.length > 0) {
|
|
603
|
+
await this.asyncTargetedStrategy(cliTargets);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
606
|
const {
|
|
607
607
|
commitMessage = "chore(release): ${version}",
|
|
608
608
|
// Align with test expectations
|
|
609
609
|
skipHooks
|
|
610
|
-
// Add skipHooks here
|
|
611
610
|
} = this.config;
|
|
612
611
|
let pkgsResult;
|
|
613
612
|
try {
|
|
@@ -631,6 +630,7 @@ var VersionEngine = class {
|
|
|
631
630
|
await gitProcess({
|
|
632
631
|
files: pkgsToProcess,
|
|
633
632
|
nextTag: "",
|
|
633
|
+
// No tag for default async
|
|
634
634
|
commitMessage: formattedCommitMessage,
|
|
635
635
|
skipHooks,
|
|
636
636
|
dryRun: this.config.dryRun
|
|
@@ -644,6 +644,121 @@ var VersionEngine = class {
|
|
|
644
644
|
exit(1);
|
|
645
645
|
}
|
|
646
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
|
+
}
|
|
647
762
|
};
|
|
648
763
|
|
|
649
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": {
|