package-versioner 0.0.1 → 0.1.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # package-versioner
2
2
 
3
+
4
+ <a href="https://www.npmjs.com/package/package-versioner" alt="NPM Version">
5
+ <img src="https://img.shields.io/npm/v/package-versioner" /></a>
6
+ <a href="https://www.npmjs.com/package/package-versioner" alt="NPM Downloads">
7
+ <img src="https://img.shields.io/npm/dw/package-versioner" /></a>
8
+
3
9
  A powerful CLI tool for automated semantic versioning based on Git history and conventional commits. Simplifies version management in JavaScript/TypeScript projects.
4
10
 
5
11
  ## Features
@@ -32,6 +38,9 @@ npx package-versioner --bump minor
32
38
  # Create a prerelease (e.g., alpha)
33
39
  npx package-versioner --bump patch --prerelease alpha
34
40
 
41
+ # Target specific packages (only in async/independent mode, comma-separated)
42
+ npx package-versioner -t @scope/package-a,@scope/package-b
43
+
35
44
  # Perform a dry run: calculates version, logs actions, but makes no file changes or Git commits/tags
36
45
  npx package-versioner --dry-run
37
46
  ```
@@ -82,7 +91,7 @@ npx package-versioner --help
82
91
 
83
92
  ## Acknowledgements
84
93
 
85
- This project was originally forked from and inspired by `jucian0/turbo-version` ([https://github.com/jucian0/turbo-version](https://github.com/jucian0/turbo-version)). We appreciate the foundational work done by the original authors.
94
+ This project was originally forked from and inspired by [`jucian0/turbo-version`](https://github.com/jucian0/turbo-version). We appreciate the foundational work done by the original authors.
86
95
 
87
96
  ## License
88
97
 
package/dist/index.cjs CHANGED
@@ -15,21 +15,50 @@ var __copyProps = (to, from, except, desc) => {
15
15
  return to;
16
16
  };
17
17
  var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
18
22
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
19
23
  mod
20
24
  ));
21
25
 
22
26
  // src/index.ts
23
- var import_node_process4 = require("process");
24
- var import_chalk2 = __toESM(require("chalk"), 1);
25
27
  var import_commander = require("commander");
28
+
29
+ // src/config.ts
30
+ var fs = __toESM(require("fs"), 1);
31
+ var import_node_process = require("process");
32
+ function loadConfig(configPath) {
33
+ const localProcess = (0, import_node_process.cwd)();
34
+ const filePath = configPath || `${localProcess}/version.config.json`;
35
+ return new Promise((resolve, reject) => {
36
+ fs.readFile(filePath, "utf-8", (err, data) => {
37
+ if (err) {
38
+ reject(new Error(`Could not locate the config file at ${filePath}: ${err.message}`));
39
+ return;
40
+ }
41
+ try {
42
+ const config = JSON.parse(data);
43
+ resolve(config);
44
+ } catch (err2) {
45
+ const errorMessage = err2 instanceof Error ? err2.message : String(err2);
46
+ reject(new Error(`Failed to parse config file ${filePath}: ${errorMessage}`));
47
+ }
48
+ });
49
+ });
50
+ }
51
+
52
+ // src/utils.ts
53
+ var import_node_fs2 = __toESM(require("fs"), 1);
54
+ var import_chalk = __toESM(require("chalk"), 1);
26
55
  var import_figlet = __toESM(require("figlet"), 1);
27
56
 
28
57
  // package.json
29
58
  var package_default = {
30
59
  name: "package-versioner",
31
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.",
32
- version: "0.0.1",
61
+ version: "0.0.2",
33
62
  type: "module",
34
63
  main: "./dist/index.js",
35
64
  module: "./dist/index.mjs",
@@ -43,9 +72,17 @@ var package_default = {
43
72
  url: "https://github.com/goosewobbler/package-versioner",
44
73
  homepage: "https://github.com/goosewobbler/package-versioner"
45
74
  },
46
- keywords: ["version", "semver", "git", "package"],
75
+ keywords: [
76
+ "version",
77
+ "semver",
78
+ "git",
79
+ "package"
80
+ ],
47
81
  license: "MIT",
48
- files: ["dist/**", "package-versioner.schema.json"],
82
+ files: [
83
+ "dist/**",
84
+ "package-versioner.schema.json"
85
+ ],
49
86
  bin: {
50
87
  "package-versioner": "./dist/index.js"
51
88
  },
@@ -63,17 +100,20 @@ var package_default = {
63
100
  prepare: "husky"
64
101
  },
65
102
  "lint-staged": {
66
- "*.{js,ts,jsx,tsx}": ["biome check --apply", "biome format --write"]
103
+ "*.{js,ts,jsx,tsx}": [
104
+ "biome check --apply",
105
+ "biome format --write"
106
+ ]
67
107
  },
68
108
  devDependencies: {
69
109
  "@biomejs/biome": "^1.9.4",
70
110
  "@types/figlet": "^1.5.5",
71
- "@types/node": "^18.14.0",
111
+ "@types/node": "^22.14.0",
72
112
  "@types/semver": "^7.3.13",
73
113
  "@vitest/coverage-v8": "^3.1.1",
74
114
  husky: "^9.1.7",
75
115
  "lint-staged": "^15.5.0",
76
- tsup: "^5.10.1",
116
+ tsup: "^8.4.0",
77
117
  typescript: "^5.8.3",
78
118
  vitest: "^3.1.1"
79
119
  },
@@ -90,32 +130,7 @@ var package_default = {
90
130
  packageManager: "pnpm@10.8.0+sha512.0e82714d1b5b43c74610193cb20734897c1d00de89d0e18420aebc5977fa13d780a9cb05734624e81ebd81cc876cd464794850641c48b9544326b5622ca29971"
91
131
  };
92
132
 
93
- // src/config.ts
94
- var fs = __toESM(require("fs"), 1);
95
- var import_node_process = require("process");
96
- function loadConfig(configPath) {
97
- const localProcess = (0, import_node_process.cwd)();
98
- const filePath = configPath || `${localProcess}/version.config.json`;
99
- return new Promise((resolve, reject) => {
100
- fs.readFile(filePath, "utf-8", (err, data) => {
101
- if (err) {
102
- reject(new Error(`Could not locate the config file at ${filePath}: ${err.message}`));
103
- return;
104
- }
105
- try {
106
- const config = JSON.parse(data);
107
- resolve(config);
108
- } catch (err2) {
109
- const errorMessage = err2 instanceof Error ? err2.message : String(err2);
110
- reject(new Error(`Failed to parse config file ${filePath}: ${errorMessage}`));
111
- }
112
- });
113
- });
114
- }
115
-
116
133
  // src/utils.ts
117
- var import_node_fs2 = __toESM(require("fs"), 1);
118
- var import_chalk = __toESM(require("chalk"), 1);
119
134
  var import_git_semver_tags = require("git-semver-tags");
120
135
 
121
136
  // src/git.ts
@@ -202,7 +217,7 @@ async function gitProcess({ files, nextTag, commitMessage, skipHooks, dryRun })
202
217
  message: commitMessage,
203
218
  skipHooks
204
219
  });
205
- const tagMessage = `New Version ${nextTag} generated at ${new Date().toISOString()}`;
220
+ const tagMessage = `New Version ${nextTag} generated at ${(/* @__PURE__ */ new Date()).toISOString()}`;
206
221
  await createGitTag({
207
222
  tag: nextTag,
208
223
  message: tagMessage
@@ -241,6 +256,25 @@ function getCurrentBranch() {
241
256
  }
242
257
 
243
258
  // src/utils.ts
259
+ function printFiglet() {
260
+ const font = "Standard";
261
+ import_figlet.default.text(package_default.name, { font }, (err, data) => {
262
+ if (err) {
263
+ log("warning", "Could not print figlet banner: Figlet error");
264
+ console.error(err);
265
+ return;
266
+ }
267
+ if (data) {
268
+ const figletText = data;
269
+ const versionText = `v${package_default.version}`;
270
+ process.stdout.write(`${import_chalk.default.hex("#FF1F57")(figletText)}
271
+ `);
272
+ process.stdout.write(`${import_chalk.default.hex("#0096FF")(versionText)}
273
+
274
+ `);
275
+ }
276
+ });
277
+ }
244
278
  function log(status, message) {
245
279
  const statusColors = {
246
280
  info: import_chalk.default.blue("\u2139"),
@@ -265,10 +299,10 @@ async function getLatestTag() {
265
299
  }
266
300
  }
267
301
  function formatTag(options, props) {
268
- const { name: name2, synced, tagPrefix } = options;
302
+ const { name, synced, tagPrefix } = options;
269
303
  const { version } = props;
270
- if (!synced && name2) {
271
- return `${tagPrefix ? tagPrefix : ""}${name2}@${version}`;
304
+ if (!synced && name) {
305
+ return `${tagPrefix ? tagPrefix : ""}${name}@${version}`;
272
306
  }
273
307
  return `${tagPrefix ? tagPrefix : "v"}${version}`;
274
308
  }
@@ -281,7 +315,7 @@ function createTemplateString(template, data) {
281
315
  function formatCommitMessage(template, version) {
282
316
  return createTemplateString(template, { version });
283
317
  }
284
- function updatePackageVersion({ path: path2, version, name: name2, dryRun }) {
318
+ function updatePackageVersion({ path: path2, version, name, dryRun }) {
285
319
  try {
286
320
  const pkgPath = `${path2}/package.json`;
287
321
  const pkg = JSON.parse(import_node_fs2.default.readFileSync(pkgPath, "utf8"));
@@ -289,12 +323,12 @@ function updatePackageVersion({ path: path2, version, name: name2, dryRun }) {
289
323
  if (!dryRun) {
290
324
  import_node_fs2.default.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
291
325
  `);
292
- log("success", `${name2}: ${version}`);
326
+ log("success", `${name}: ${version}`);
293
327
  } else {
294
- log("info", `[DRY RUN] Would update ${name2} package.json to version ${version}`);
328
+ log("info", `[DRY RUN] Would update ${name} package.json to version ${version}`);
295
329
  }
296
330
  } catch (error) {
297
- log("error", `Failed to update ${name2} to version ${version}`);
331
+ log("error", `Failed to update ${name} to version ${version}`);
298
332
  console.error(error);
299
333
  }
300
334
  }
@@ -307,14 +341,18 @@ var import_get_packages = require("@manypkg/get-packages");
307
341
  var import_conventional_recommended_bump = require("conventional-recommended-bump");
308
342
  var import_semver = __toESM(require("semver"), 1);
309
343
  var VersionEngine = class {
344
+ config;
310
345
  constructor(config) {
311
346
  this.config = config;
312
347
  }
348
+ /**
349
+ * Calculate the next version based on options
350
+ */
313
351
  async calculateVersion(options) {
314
- const { latestTag, type, path: path2, name: name2, branchPattern, prereleaseIdentifier } = options;
352
+ const { latestTag, type, path: path2, name, branchPattern, prereleaseIdentifier } = options;
315
353
  const originalPrefix = this.config.tagPrefix || "";
316
354
  const initialVersion = prereleaseIdentifier ? `0.0.1-${prereleaseIdentifier}` : "0.0.1";
317
- const tagSearchPattern = name2 ? originalPrefix ? `${originalPrefix}${name2}@` : `${name2}@` : originalPrefix ? `${originalPrefix}v` : "v";
355
+ const tagSearchPattern = name ? originalPrefix ? `${originalPrefix}${name}@` : `${name}@` : originalPrefix ? `${originalPrefix}v` : "v";
318
356
  let determinedReleaseType = type || null;
319
357
  if (determinedReleaseType) {
320
358
  if (!latestTag) {
@@ -355,21 +393,21 @@ var VersionEngine = class {
355
393
  if (commitsLength === 0) {
356
394
  log(
357
395
  "info",
358
- `No new commits found for ${name2 || "project"} since ${latestTag}, skipping version bump`
396
+ `No new commits found for ${name || "project"} since ${latestTag}, skipping version bump`
359
397
  );
360
398
  return "";
361
399
  }
362
400
  if (!releaseTypeFromCommits) {
363
401
  log(
364
402
  "info",
365
- `No relevant commits found for ${name2 || "project"} since ${latestTag}, skipping version bump`
403
+ `No relevant commits found for ${name || "project"} since ${latestTag}, skipping version bump`
366
404
  );
367
405
  return "";
368
406
  }
369
407
  const currentVersion = import_semver.default.clean(latestTag.replace(tagSearchPattern, "")) || "0.0.0";
370
408
  return import_semver.default.inc(currentVersion, releaseTypeFromCommits, prereleaseIdentifier) || "";
371
409
  } catch (error) {
372
- log("error", `Failed to calculate version for ${name2 || "project"}`);
410
+ log("error", `Failed to calculate version for ${name || "project"}`);
373
411
  console.error(error);
374
412
  if (error instanceof Error && error.message.includes("No names found")) {
375
413
  log("info", "No tags found, proceeding with initial version calculation (if applicable).");
@@ -378,30 +416,44 @@ var VersionEngine = class {
378
416
  return "";
379
417
  }
380
418
  }
381
- async processPackages(packages = [], configPackages = []) {
382
- const { tagPrefix } = this.config;
383
- const pkgsResult = packages.length ? { packages } : (0, import_get_packages.getPackagesSync)((0, import_node_process3.cwd)());
419
+ /**
420
+ * Process packages based on discovery, skip list, and optional target list.
421
+ * Returns a list of package.json file paths that were updated (or would be in dry run).
422
+ */
423
+ async processPackages(discoveredPackages = [], targets = []) {
424
+ const { tagPrefix, skip } = this.config;
384
425
  const files = [];
385
- const selectedPackages = pkgsResult.packages.filter((pkg) => {
386
- var _a;
387
- if ((_a = this.config.skip) == null ? void 0 : _a.includes(pkg.packageJson.name)) {
426
+ const pkgsToConsider = discoveredPackages.filter((pkg) => {
427
+ if (skip == null ? void 0 : skip.includes(pkg.packageJson.name)) {
428
+ log("info", `Skipping package ${pkg.packageJson.name} based on config skip list.`);
388
429
  return false;
389
430
  }
390
- return configPackages.length === 0 || configPackages.includes(pkg.packageJson.name);
431
+ if (targets.length > 0) {
432
+ const isTargeted = targets.includes(pkg.packageJson.name);
433
+ if (!isTargeted) {
434
+ }
435
+ return isTargeted;
436
+ }
437
+ return true;
391
438
  });
392
- for (const pkg of selectedPackages) {
393
- const name2 = pkg.packageJson.name;
439
+ log("info", `Found ${pkgsToConsider.length} package(s) to process after filtering.`);
440
+ for (const pkg of pkgsToConsider) {
441
+ const name = pkg.packageJson.name;
394
442
  const pkgPath = pkg.dir;
395
443
  const prefix = formatTagPrefix(tagPrefix);
396
444
  const latestTag = await getLatestTag();
397
445
  const nextVersion = await this.calculateVersion({
398
446
  latestTag,
447
+ // This might need refinement for async based on package-specific tags
399
448
  tagPrefix: prefix,
400
449
  path: pkgPath,
401
- name: name2,
450
+ name,
451
+ // Pass name for potential package-specific tag lookups
402
452
  branchPattern: this.config.branchPattern,
403
453
  baseBranch: this.config.baseBranch,
404
- prereleaseIdentifier: this.config.prereleaseIdentifier
454
+ prereleaseIdentifier: this.config.prereleaseIdentifier,
455
+ type: this.config.forceType
456
+ // Pass forced type if provided
405
457
  });
406
458
  if (!nextVersion) {
407
459
  continue;
@@ -409,13 +461,16 @@ var VersionEngine = class {
409
461
  updatePackageVersion({
410
462
  path: pkgPath,
411
463
  version: nextVersion,
412
- name: name2,
464
+ name,
413
465
  dryRun: this.config.dryRun
414
466
  });
415
467
  files.push(import_node_path2.default.join(pkgPath, "package.json"));
416
468
  }
417
469
  return files;
418
470
  }
471
+ /**
472
+ * Create git commit and tag
473
+ */
419
474
  async createGitCommitAndTag(files, nextTag, commitMessage, dryRun) {
420
475
  try {
421
476
  await gitProcess({
@@ -434,6 +489,9 @@ var VersionEngine = class {
434
489
  (0, import_node_process3.exit)(1);
435
490
  }
436
491
  }
492
+ /**
493
+ * Synced versioning strategy (all packages get the same version)
494
+ */
437
495
  async syncedStrategy() {
438
496
  var _a;
439
497
  const {
@@ -502,6 +560,9 @@ var VersionEngine = class {
502
560
  const formattedCommitMessage = formatCommitMessage(commitMessage, nextVersion);
503
561
  await this.createGitCommitAndTag(files, nextTag, formattedCommitMessage, this.config.dryRun);
504
562
  }
563
+ /**
564
+ * Single package versioning strategy
565
+ */
505
566
  async singleStrategy() {
506
567
  const {
507
568
  packages: configPackages,
@@ -561,11 +622,15 @@ var VersionEngine = class {
561
622
  this.config.dryRun
562
623
  );
563
624
  }
564
- async asyncStrategy() {
625
+ /**
626
+ * Async versioning strategy (each package gets its own version)
627
+ */
628
+ async asyncStrategy(cliTargets = []) {
565
629
  const {
566
- packages: configPackages,
567
630
  commitMessage = "chore(release): ${version}",
631
+ // Align with test expectations
568
632
  skipHooks
633
+ // Add skipHooks here
569
634
  } = this.config;
570
635
  let pkgsResult;
571
636
  try {
@@ -579,9 +644,9 @@ var VersionEngine = class {
579
644
  (0, import_node_process3.exit)(1);
580
645
  return;
581
646
  }
582
- const pkgsToProcess = await this.processPackages(pkgsResult.packages, configPackages);
647
+ const pkgsToProcess = await this.processPackages(pkgsResult.packages, cliTargets);
583
648
  if (pkgsToProcess.length === 0) {
584
- log("info", "No packages to process");
649
+ log("info", "No packages to process based on changes and targets");
585
650
  return;
586
651
  }
587
652
  const formattedCommitMessage = commitMessage;
@@ -594,7 +659,7 @@ var VersionEngine = class {
594
659
  dryRun: this.config.dryRun
595
660
  });
596
661
  if (!this.config.dryRun) {
597
- log("success", "Created version commit");
662
+ log("success", `Created version commit for ${pkgsToProcess.length} package(s)`);
598
663
  }
599
664
  } catch (error) {
600
665
  log("error", "Failed to create version commit");
@@ -605,63 +670,49 @@ var VersionEngine = class {
605
670
  };
606
671
 
607
672
  // src/index.ts
608
- var name = "package-versioner";
609
- var program = new import_commander.Command();
610
- program.name("package-versioner").description("Manages package versions using Git context.").version(package_default.version);
611
- program.option("-t, --target <project>", "specific package to update").option("-b, --bump <version>", "type of version bump to perform", (value) => {
612
- const validBumps = [
613
- "patch",
614
- "minor",
615
- "major",
616
- "premajor",
617
- "preminor",
618
- "prepatch",
619
- "prerelease"
620
- ];
621
- if (!validBumps.includes(value)) {
622
- log("error", `Invalid bump type '${value}'. Valid options are: ${validBumps.join(", ")}`);
623
- process.exit(1);
624
- }
625
- return value;
626
- }).option("--base-branch <branch>", "override the base branch for this operation").option("--synced", "force synced versioning mode").option("--no-synced", "force async versioning mode").option(
627
- "--skip <packages>",
628
- "comma-separated list of packages to skip",
629
- (value) => value.split(",")
630
- ).option("--prerelease <identifier>", "set prerelease identifier (e.g., alpha, beta)").option("--skip-hooks", "skip Git hooks for this operation").option("--config <path>", "specify a custom config file path").option("--dry-run", "Calculate version and log actions without changing files or Git state");
631
- program.description("Version packages based on Git context and conventional commits").action(async (options) => {
632
- const figletText = import_figlet.default.textSync(name);
633
- const versionText = `v${package_default.version}`;
634
- process.stdout.write(`${import_chalk2.default.hex("#FF1F57")(figletText)}
635
- `);
636
- process.stdout.write(`${import_chalk2.default.hex("#0096FF")(versionText)}
637
-
638
- `);
673
+ async function run() {
674
+ printFiglet();
675
+ const program = new import_commander.Command();
676
+ program.name("package-versioner").description(
677
+ "Automated semantic versioning based on Git history and conventional commits. Supports monorepos with synchronized or independent package versioning strategies."
678
+ ).version("0.0.2").option("--config <path>", "Path to the configuration file").option("--dry-run", "Simulate the versioning process without making changes").option("--synced", "Force synced versioning strategy (overrides config)").option("--bump <type>", "Force a specific release type (patch, minor, major)").option(
679
+ "--prerelease <identifier>",
680
+ "Create a prerelease version with the specified identifier"
681
+ ).option(
682
+ "-t, --target <targets>",
683
+ "Comma-separated list of package names to target (only for async strategy)"
684
+ ).parse(process.argv);
685
+ const options = program.opts();
639
686
  try {
640
- const configPath = options.config || void 0;
641
- const config = await loadConfig(configPath);
642
- if (options.baseBranch)
643
- config.baseBranch = options.baseBranch;
644
- if (options.synced !== void 0)
645
- config.synced = options.synced;
646
- if (options.skip)
647
- config.skip = options.skip;
687
+ const config = await loadConfig(options.config);
688
+ log("info", `Loaded configuration from ${options.config || "version.config.json"}`);
689
+ if (options.dryRun) config.dryRun = true;
690
+ if (options.synced) config.synced = true;
691
+ if (options.bump) config.forceType = options.bump;
648
692
  if (options.prerelease)
649
- config.prereleaseIdentifier = options.prerelease;
650
- if (options.skipHooks !== void 0)
651
- config.skipHooks = options.skipHooks;
652
- if (options.dryRun !== void 0)
653
- config.dryRun = options.dryRun;
693
+ config.prereleaseIdentifier = options.prerelease === true ? "rc" : options.prerelease;
694
+ const cliTargets = options.target ? options.target.split(",").map((t) => t.trim()) : [];
654
695
  const engine = new VersionEngine(config);
655
696
  if (config.synced) {
697
+ log("info", "Using synced versioning strategy.");
656
698
  await engine.syncedStrategy();
657
- } else if (options.bump && options.target) {
699
+ } else if (config.packages && config.packages.length === 1) {
700
+ log("info", "Using single package versioning strategy.");
701
+ if (cliTargets.length > 0) {
702
+ log("warning", "--target flag is ignored for single package strategy.");
703
+ }
658
704
  await engine.singleStrategy();
659
705
  } else {
660
- await engine.asyncStrategy();
706
+ log("info", "Using async versioning strategy.");
707
+ if (cliTargets.length > 0) {
708
+ log("info", `Targeting specific packages: ${cliTargets.join(", ")}`);
709
+ }
710
+ await engine.asyncStrategy(cliTargets);
661
711
  }
662
- } catch (err) {
663
- log("error", `${err instanceof Error ? err.message : String(err)}`);
664
- (0, import_node_process4.exit)(1);
712
+ log("success", "Versioning process completed.");
713
+ } catch (error) {
714
+ log("error", error instanceof Error ? error.message : String(error));
715
+ process.exit(1);
665
716
  }
666
- });
667
- program.parse();
717
+ }
718
+ run();
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js CHANGED
@@ -1,16 +1,41 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { exit as exit2 } from "process";
5
- import chalk2 from "chalk";
6
4
  import { Command } from "commander";
5
+
6
+ // src/config.ts
7
+ import * as fs from "node:fs";
8
+ import { cwd } from "node:process";
9
+ function loadConfig(configPath) {
10
+ const localProcess = cwd();
11
+ const filePath = configPath || `${localProcess}/version.config.json`;
12
+ return new Promise((resolve, reject) => {
13
+ fs.readFile(filePath, "utf-8", (err, data) => {
14
+ if (err) {
15
+ reject(new Error(`Could not locate the config file at ${filePath}: ${err.message}`));
16
+ return;
17
+ }
18
+ try {
19
+ const config = JSON.parse(data);
20
+ resolve(config);
21
+ } catch (err2) {
22
+ const errorMessage = err2 instanceof Error ? err2.message : String(err2);
23
+ reject(new Error(`Failed to parse config file ${filePath}: ${errorMessage}`));
24
+ }
25
+ });
26
+ });
27
+ }
28
+
29
+ // src/utils.ts
30
+ import fs2 from "node:fs";
31
+ import chalk from "chalk";
7
32
  import figlet from "figlet";
8
33
 
9
34
  // package.json
10
35
  var package_default = {
11
36
  name: "package-versioner",
12
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.",
13
- version: "0.0.1",
38
+ version: "0.0.2",
14
39
  type: "module",
15
40
  main: "./dist/index.js",
16
41
  module: "./dist/index.mjs",
@@ -24,9 +49,17 @@ var package_default = {
24
49
  url: "https://github.com/goosewobbler/package-versioner",
25
50
  homepage: "https://github.com/goosewobbler/package-versioner"
26
51
  },
27
- keywords: ["version", "semver", "git", "package"],
52
+ keywords: [
53
+ "version",
54
+ "semver",
55
+ "git",
56
+ "package"
57
+ ],
28
58
  license: "MIT",
29
- files: ["dist/**", "package-versioner.schema.json"],
59
+ files: [
60
+ "dist/**",
61
+ "package-versioner.schema.json"
62
+ ],
30
63
  bin: {
31
64
  "package-versioner": "./dist/index.js"
32
65
  },
@@ -44,17 +77,20 @@ var package_default = {
44
77
  prepare: "husky"
45
78
  },
46
79
  "lint-staged": {
47
- "*.{js,ts,jsx,tsx}": ["biome check --apply", "biome format --write"]
80
+ "*.{js,ts,jsx,tsx}": [
81
+ "biome check --apply",
82
+ "biome format --write"
83
+ ]
48
84
  },
49
85
  devDependencies: {
50
86
  "@biomejs/biome": "^1.9.4",
51
87
  "@types/figlet": "^1.5.5",
52
- "@types/node": "^18.14.0",
88
+ "@types/node": "^22.14.0",
53
89
  "@types/semver": "^7.3.13",
54
90
  "@vitest/coverage-v8": "^3.1.1",
55
91
  husky: "^9.1.7",
56
92
  "lint-staged": "^15.5.0",
57
- tsup: "^5.10.1",
93
+ tsup: "^8.4.0",
58
94
  typescript: "^5.8.3",
59
95
  vitest: "^3.1.1"
60
96
  },
@@ -71,39 +107,14 @@ var package_default = {
71
107
  packageManager: "pnpm@10.8.0+sha512.0e82714d1b5b43c74610193cb20734897c1d00de89d0e18420aebc5977fa13d780a9cb05734624e81ebd81cc876cd464794850641c48b9544326b5622ca29971"
72
108
  };
73
109
 
74
- // src/config.ts
75
- import * as fs from "fs";
76
- import { cwd } from "process";
77
- function loadConfig(configPath) {
78
- const localProcess = cwd();
79
- const filePath = configPath || `${localProcess}/version.config.json`;
80
- return new Promise((resolve, reject) => {
81
- fs.readFile(filePath, "utf-8", (err, data) => {
82
- if (err) {
83
- reject(new Error(`Could not locate the config file at ${filePath}: ${err.message}`));
84
- return;
85
- }
86
- try {
87
- const config = JSON.parse(data);
88
- resolve(config);
89
- } catch (err2) {
90
- const errorMessage = err2 instanceof Error ? err2.message : String(err2);
91
- reject(new Error(`Failed to parse config file ${filePath}: ${errorMessage}`));
92
- }
93
- });
94
- });
95
- }
96
-
97
110
  // src/utils.ts
98
- import fs2 from "fs";
99
- import chalk from "chalk";
100
111
  import { getSemverTags } from "git-semver-tags";
101
112
 
102
113
  // src/git.ts
103
- import { exec, execSync as syncExec } from "child_process";
104
- import { existsSync, statSync } from "fs";
105
- import { join } from "path";
106
- import { cwd as cwd2 } from "process";
114
+ import { exec, execSync as syncExec } from "node:child_process";
115
+ import { existsSync, statSync } from "node:fs";
116
+ import { join } from "node:path";
117
+ import { cwd as cwd2 } from "node:process";
107
118
  var execAsync = (command) => {
108
119
  return new Promise((resolve, reject) => {
109
120
  const options = { maxBuffer: 1024 * 1024 * 10 };
@@ -183,7 +194,7 @@ async function gitProcess({ files, nextTag, commitMessage, skipHooks, dryRun })
183
194
  message: commitMessage,
184
195
  skipHooks
185
196
  });
186
- const tagMessage = `New Version ${nextTag} generated at ${new Date().toISOString()}`;
197
+ const tagMessage = `New Version ${nextTag} generated at ${(/* @__PURE__ */ new Date()).toISOString()}`;
187
198
  await createGitTag({
188
199
  tag: nextTag,
189
200
  message: tagMessage
@@ -222,6 +233,25 @@ function getCurrentBranch() {
222
233
  }
223
234
 
224
235
  // src/utils.ts
236
+ function printFiglet() {
237
+ const font = "Standard";
238
+ figlet.text(package_default.name, { font }, (err, data) => {
239
+ if (err) {
240
+ log("warning", "Could not print figlet banner: Figlet error");
241
+ console.error(err);
242
+ return;
243
+ }
244
+ if (data) {
245
+ const figletText = data;
246
+ const versionText = `v${package_default.version}`;
247
+ process.stdout.write(`${chalk.hex("#FF1F57")(figletText)}
248
+ `);
249
+ process.stdout.write(`${chalk.hex("#0096FF")(versionText)}
250
+
251
+ `);
252
+ }
253
+ });
254
+ }
225
255
  function log(status, message) {
226
256
  const statusColors = {
227
257
  info: chalk.blue("\u2139"),
@@ -246,10 +276,10 @@ async function getLatestTag() {
246
276
  }
247
277
  }
248
278
  function formatTag(options, props) {
249
- const { name: name2, synced, tagPrefix } = options;
279
+ const { name, synced, tagPrefix } = options;
250
280
  const { version } = props;
251
- if (!synced && name2) {
252
- return `${tagPrefix ? tagPrefix : ""}${name2}@${version}`;
281
+ if (!synced && name) {
282
+ return `${tagPrefix ? tagPrefix : ""}${name}@${version}`;
253
283
  }
254
284
  return `${tagPrefix ? tagPrefix : "v"}${version}`;
255
285
  }
@@ -262,7 +292,7 @@ function createTemplateString(template, data) {
262
292
  function formatCommitMessage(template, version) {
263
293
  return createTemplateString(template, { version });
264
294
  }
265
- function updatePackageVersion({ path: path2, version, name: name2, dryRun }) {
295
+ function updatePackageVersion({ path: path2, version, name, dryRun }) {
266
296
  try {
267
297
  const pkgPath = `${path2}/package.json`;
268
298
  const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
@@ -270,32 +300,36 @@ function updatePackageVersion({ path: path2, version, name: name2, dryRun }) {
270
300
  if (!dryRun) {
271
301
  fs2.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
272
302
  `);
273
- log("success", `${name2}: ${version}`);
303
+ log("success", `${name}: ${version}`);
274
304
  } else {
275
- log("info", `[DRY RUN] Would update ${name2} package.json to version ${version}`);
305
+ log("info", `[DRY RUN] Would update ${name} package.json to version ${version}`);
276
306
  }
277
307
  } catch (error) {
278
- log("error", `Failed to update ${name2} to version ${version}`);
308
+ log("error", `Failed to update ${name} to version ${version}`);
279
309
  console.error(error);
280
310
  }
281
311
  }
282
312
 
283
313
  // src/versionEngine.ts
284
- import fs3 from "fs";
285
- import path from "path";
286
- import { cwd as cwd3, exit } from "process";
314
+ import fs3 from "node:fs";
315
+ import path from "node:path";
316
+ import { cwd as cwd3, exit } from "node:process";
287
317
  import { getPackagesSync } from "@manypkg/get-packages";
288
318
  import { Bumper } from "conventional-recommended-bump";
289
319
  import semver from "semver";
290
320
  var VersionEngine = class {
321
+ config;
291
322
  constructor(config) {
292
323
  this.config = config;
293
324
  }
325
+ /**
326
+ * Calculate the next version based on options
327
+ */
294
328
  async calculateVersion(options) {
295
- const { latestTag, type, path: path2, name: name2, branchPattern, prereleaseIdentifier } = options;
329
+ const { latestTag, type, path: path2, name, branchPattern, prereleaseIdentifier } = options;
296
330
  const originalPrefix = this.config.tagPrefix || "";
297
331
  const initialVersion = prereleaseIdentifier ? `0.0.1-${prereleaseIdentifier}` : "0.0.1";
298
- const tagSearchPattern = name2 ? originalPrefix ? `${originalPrefix}${name2}@` : `${name2}@` : originalPrefix ? `${originalPrefix}v` : "v";
332
+ const tagSearchPattern = name ? originalPrefix ? `${originalPrefix}${name}@` : `${name}@` : originalPrefix ? `${originalPrefix}v` : "v";
299
333
  let determinedReleaseType = type || null;
300
334
  if (determinedReleaseType) {
301
335
  if (!latestTag) {
@@ -336,21 +370,21 @@ var VersionEngine = class {
336
370
  if (commitsLength === 0) {
337
371
  log(
338
372
  "info",
339
- `No new commits found for ${name2 || "project"} since ${latestTag}, skipping version bump`
373
+ `No new commits found for ${name || "project"} since ${latestTag}, skipping version bump`
340
374
  );
341
375
  return "";
342
376
  }
343
377
  if (!releaseTypeFromCommits) {
344
378
  log(
345
379
  "info",
346
- `No relevant commits found for ${name2 || "project"} since ${latestTag}, skipping version bump`
380
+ `No relevant commits found for ${name || "project"} since ${latestTag}, skipping version bump`
347
381
  );
348
382
  return "";
349
383
  }
350
384
  const currentVersion = semver.clean(latestTag.replace(tagSearchPattern, "")) || "0.0.0";
351
385
  return semver.inc(currentVersion, releaseTypeFromCommits, prereleaseIdentifier) || "";
352
386
  } catch (error) {
353
- log("error", `Failed to calculate version for ${name2 || "project"}`);
387
+ log("error", `Failed to calculate version for ${name || "project"}`);
354
388
  console.error(error);
355
389
  if (error instanceof Error && error.message.includes("No names found")) {
356
390
  log("info", "No tags found, proceeding with initial version calculation (if applicable).");
@@ -359,30 +393,44 @@ var VersionEngine = class {
359
393
  return "";
360
394
  }
361
395
  }
362
- async processPackages(packages = [], configPackages = []) {
363
- const { tagPrefix } = this.config;
364
- const pkgsResult = packages.length ? { packages } : getPackagesSync(cwd3());
396
+ /**
397
+ * Process packages based on discovery, skip list, and optional target list.
398
+ * Returns a list of package.json file paths that were updated (or would be in dry run).
399
+ */
400
+ async processPackages(discoveredPackages = [], targets = []) {
401
+ const { tagPrefix, skip } = this.config;
365
402
  const files = [];
366
- const selectedPackages = pkgsResult.packages.filter((pkg) => {
367
- var _a;
368
- if ((_a = this.config.skip) == null ? void 0 : _a.includes(pkg.packageJson.name)) {
403
+ const pkgsToConsider = discoveredPackages.filter((pkg) => {
404
+ if (skip == null ? void 0 : skip.includes(pkg.packageJson.name)) {
405
+ log("info", `Skipping package ${pkg.packageJson.name} based on config skip list.`);
369
406
  return false;
370
407
  }
371
- return configPackages.length === 0 || configPackages.includes(pkg.packageJson.name);
408
+ if (targets.length > 0) {
409
+ const isTargeted = targets.includes(pkg.packageJson.name);
410
+ if (!isTargeted) {
411
+ }
412
+ return isTargeted;
413
+ }
414
+ return true;
372
415
  });
373
- for (const pkg of selectedPackages) {
374
- const name2 = pkg.packageJson.name;
416
+ log("info", `Found ${pkgsToConsider.length} package(s) to process after filtering.`);
417
+ for (const pkg of pkgsToConsider) {
418
+ const name = pkg.packageJson.name;
375
419
  const pkgPath = pkg.dir;
376
420
  const prefix = formatTagPrefix(tagPrefix);
377
421
  const latestTag = await getLatestTag();
378
422
  const nextVersion = await this.calculateVersion({
379
423
  latestTag,
424
+ // This might need refinement for async based on package-specific tags
380
425
  tagPrefix: prefix,
381
426
  path: pkgPath,
382
- name: name2,
427
+ name,
428
+ // Pass name for potential package-specific tag lookups
383
429
  branchPattern: this.config.branchPattern,
384
430
  baseBranch: this.config.baseBranch,
385
- prereleaseIdentifier: this.config.prereleaseIdentifier
431
+ prereleaseIdentifier: this.config.prereleaseIdentifier,
432
+ type: this.config.forceType
433
+ // Pass forced type if provided
386
434
  });
387
435
  if (!nextVersion) {
388
436
  continue;
@@ -390,13 +438,16 @@ var VersionEngine = class {
390
438
  updatePackageVersion({
391
439
  path: pkgPath,
392
440
  version: nextVersion,
393
- name: name2,
441
+ name,
394
442
  dryRun: this.config.dryRun
395
443
  });
396
444
  files.push(path.join(pkgPath, "package.json"));
397
445
  }
398
446
  return files;
399
447
  }
448
+ /**
449
+ * Create git commit and tag
450
+ */
400
451
  async createGitCommitAndTag(files, nextTag, commitMessage, dryRun) {
401
452
  try {
402
453
  await gitProcess({
@@ -415,6 +466,9 @@ var VersionEngine = class {
415
466
  exit(1);
416
467
  }
417
468
  }
469
+ /**
470
+ * Synced versioning strategy (all packages get the same version)
471
+ */
418
472
  async syncedStrategy() {
419
473
  var _a;
420
474
  const {
@@ -483,6 +537,9 @@ var VersionEngine = class {
483
537
  const formattedCommitMessage = formatCommitMessage(commitMessage, nextVersion);
484
538
  await this.createGitCommitAndTag(files, nextTag, formattedCommitMessage, this.config.dryRun);
485
539
  }
540
+ /**
541
+ * Single package versioning strategy
542
+ */
486
543
  async singleStrategy() {
487
544
  const {
488
545
  packages: configPackages,
@@ -542,11 +599,15 @@ var VersionEngine = class {
542
599
  this.config.dryRun
543
600
  );
544
601
  }
545
- async asyncStrategy() {
602
+ /**
603
+ * Async versioning strategy (each package gets its own version)
604
+ */
605
+ async asyncStrategy(cliTargets = []) {
546
606
  const {
547
- packages: configPackages,
548
607
  commitMessage = "chore(release): ${version}",
608
+ // Align with test expectations
549
609
  skipHooks
610
+ // Add skipHooks here
550
611
  } = this.config;
551
612
  let pkgsResult;
552
613
  try {
@@ -560,9 +621,9 @@ var VersionEngine = class {
560
621
  exit(1);
561
622
  return;
562
623
  }
563
- const pkgsToProcess = await this.processPackages(pkgsResult.packages, configPackages);
624
+ const pkgsToProcess = await this.processPackages(pkgsResult.packages, cliTargets);
564
625
  if (pkgsToProcess.length === 0) {
565
- log("info", "No packages to process");
626
+ log("info", "No packages to process based on changes and targets");
566
627
  return;
567
628
  }
568
629
  const formattedCommitMessage = commitMessage;
@@ -575,7 +636,7 @@ var VersionEngine = class {
575
636
  dryRun: this.config.dryRun
576
637
  });
577
638
  if (!this.config.dryRun) {
578
- log("success", "Created version commit");
639
+ log("success", `Created version commit for ${pkgsToProcess.length} package(s)`);
579
640
  }
580
641
  } catch (error) {
581
642
  log("error", "Failed to create version commit");
@@ -586,63 +647,49 @@ var VersionEngine = class {
586
647
  };
587
648
 
588
649
  // src/index.ts
589
- var name = "package-versioner";
590
- var program = new Command();
591
- program.name("package-versioner").description("Manages package versions using Git context.").version(package_default.version);
592
- program.option("-t, --target <project>", "specific package to update").option("-b, --bump <version>", "type of version bump to perform", (value) => {
593
- const validBumps = [
594
- "patch",
595
- "minor",
596
- "major",
597
- "premajor",
598
- "preminor",
599
- "prepatch",
600
- "prerelease"
601
- ];
602
- if (!validBumps.includes(value)) {
603
- log("error", `Invalid bump type '${value}'. Valid options are: ${validBumps.join(", ")}`);
604
- process.exit(1);
605
- }
606
- return value;
607
- }).option("--base-branch <branch>", "override the base branch for this operation").option("--synced", "force synced versioning mode").option("--no-synced", "force async versioning mode").option(
608
- "--skip <packages>",
609
- "comma-separated list of packages to skip",
610
- (value) => value.split(",")
611
- ).option("--prerelease <identifier>", "set prerelease identifier (e.g., alpha, beta)").option("--skip-hooks", "skip Git hooks for this operation").option("--config <path>", "specify a custom config file path").option("--dry-run", "Calculate version and log actions without changing files or Git state");
612
- program.description("Version packages based on Git context and conventional commits").action(async (options) => {
613
- const figletText = figlet.textSync(name);
614
- const versionText = `v${package_default.version}`;
615
- process.stdout.write(`${chalk2.hex("#FF1F57")(figletText)}
616
- `);
617
- process.stdout.write(`${chalk2.hex("#0096FF")(versionText)}
618
-
619
- `);
650
+ async function run() {
651
+ printFiglet();
652
+ const program = new Command();
653
+ program.name("package-versioner").description(
654
+ "Automated semantic versioning based on Git history and conventional commits. Supports monorepos with synchronized or independent package versioning strategies."
655
+ ).version("0.0.2").option("--config <path>", "Path to the configuration file").option("--dry-run", "Simulate the versioning process without making changes").option("--synced", "Force synced versioning strategy (overrides config)").option("--bump <type>", "Force a specific release type (patch, minor, major)").option(
656
+ "--prerelease <identifier>",
657
+ "Create a prerelease version with the specified identifier"
658
+ ).option(
659
+ "-t, --target <targets>",
660
+ "Comma-separated list of package names to target (only for async strategy)"
661
+ ).parse(process.argv);
662
+ const options = program.opts();
620
663
  try {
621
- const configPath = options.config || void 0;
622
- const config = await loadConfig(configPath);
623
- if (options.baseBranch)
624
- config.baseBranch = options.baseBranch;
625
- if (options.synced !== void 0)
626
- config.synced = options.synced;
627
- if (options.skip)
628
- config.skip = options.skip;
664
+ const config = await loadConfig(options.config);
665
+ log("info", `Loaded configuration from ${options.config || "version.config.json"}`);
666
+ if (options.dryRun) config.dryRun = true;
667
+ if (options.synced) config.synced = true;
668
+ if (options.bump) config.forceType = options.bump;
629
669
  if (options.prerelease)
630
- config.prereleaseIdentifier = options.prerelease;
631
- if (options.skipHooks !== void 0)
632
- config.skipHooks = options.skipHooks;
633
- if (options.dryRun !== void 0)
634
- config.dryRun = options.dryRun;
670
+ config.prereleaseIdentifier = options.prerelease === true ? "rc" : options.prerelease;
671
+ const cliTargets = options.target ? options.target.split(",").map((t) => t.trim()) : [];
635
672
  const engine = new VersionEngine(config);
636
673
  if (config.synced) {
674
+ log("info", "Using synced versioning strategy.");
637
675
  await engine.syncedStrategy();
638
- } else if (options.bump && options.target) {
676
+ } else if (config.packages && config.packages.length === 1) {
677
+ log("info", "Using single package versioning strategy.");
678
+ if (cliTargets.length > 0) {
679
+ log("warning", "--target flag is ignored for single package strategy.");
680
+ }
639
681
  await engine.singleStrategy();
640
682
  } else {
641
- await engine.asyncStrategy();
683
+ log("info", "Using async versioning strategy.");
684
+ if (cliTargets.length > 0) {
685
+ log("info", `Targeting specific packages: ${cliTargets.join(", ")}`);
686
+ }
687
+ await engine.asyncStrategy(cliTargets);
642
688
  }
643
- } catch (err) {
644
- log("error", `${err instanceof Error ? err.message : String(err)}`);
645
- exit2(1);
689
+ log("success", "Versioning process completed.");
690
+ } catch (error) {
691
+ log("error", error instanceof Error ? error.message : String(error));
692
+ process.exit(1);
646
693
  }
647
- });
648
- program.parse();
694
+ }
695
+ run();
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.0.1",
4
+ "version": "0.1.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",
@@ -38,12 +38,12 @@
38
38
  "devDependencies": {
39
39
  "@biomejs/biome": "^1.9.4",
40
40
  "@types/figlet": "^1.5.5",
41
- "@types/node": "^18.14.0",
41
+ "@types/node": "^22.14.0",
42
42
  "@types/semver": "^7.3.13",
43
43
  "@vitest/coverage-v8": "^3.1.1",
44
44
  "husky": "^9.1.7",
45
45
  "lint-staged": "^15.5.0",
46
- "tsup": "^5.10.1",
46
+ "tsup": "^8.4.0",
47
47
  "typescript": "^5.8.3",
48
48
  "vitest": "^3.1.1"
49
49
  },