package-versioner 0.3.0 → 0.4.1

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
@@ -39,6 +39,10 @@ npx package-versioner --bump minor
39
39
  # Create a prerelease (e.g., alpha)
40
40
  npx package-versioner --bump patch --prerelease alpha
41
41
 
42
+ # Promote a prerelease to a stable release (automatic cleaning)
43
+ # For example, 1.0.0-beta.1 -> 2.0.0:
44
+ npx package-versioner --bump major
45
+
42
46
  # Target specific packages (only in async/independent mode, comma-separated)
43
47
  npx package-versioner -t @scope/package-a,@scope/package-b
44
48
 
@@ -70,7 +74,7 @@ When using the `--json` flag, normal console output is suppressed and the tool o
70
74
  ],
71
75
  "commitMessage": "chore(release): v1.2.3",
72
76
  "tags": [
73
- "@scope/package-a@v1.2.3"
77
+ "v@scope/package-a@1.2.3"
74
78
  ]
75
79
  }
76
80
  ```
package/dist/index.cjs CHANGED
@@ -410,13 +410,16 @@ var import_node_path3 = __toESM(require("path"), 1);
410
410
  var import_node_process4 = require("process");
411
411
 
412
412
  // src/core/versionCalculator.ts
413
+ var fs3 = __toESM(require("fs"), 1);
414
+ var path2 = __toESM(require("path"), 1);
413
415
  var import_node_process3 = require("process");
414
416
  var import_conventional_recommended_bump = require("conventional-recommended-bump");
415
417
  var import_semver = __toESM(require("semver"), 1);
416
418
  async function calculateVersion(config, options) {
417
- const { latestTag, type, path: path4, name, branchPattern, prereleaseIdentifier } = options;
419
+ const { latestTag, type, path: pkgPath, name, branchPattern, prereleaseIdentifier } = options;
418
420
  const originalPrefix = config.tagPrefix || "v";
419
421
  const initialVersion = prereleaseIdentifier ? `0.0.1-${prereleaseIdentifier}` : "0.0.1";
422
+ const hasNoTags = !latestTag || latestTag === "";
420
423
  function determineTagSearchPattern(packageName, prefix) {
421
424
  if (packageName) {
422
425
  return prefix ? `${prefix}${packageName}@` : `${packageName}@`;
@@ -427,10 +430,24 @@ async function calculateVersion(config, options) {
427
430
  const escapedTagPattern = escapeRegExp(tagSearchPattern);
428
431
  let determinedReleaseType = type || null;
429
432
  if (determinedReleaseType) {
430
- if (!latestTag) {
431
- return initialVersion;
433
+ if (hasNoTags) {
434
+ return getPackageVersionFallback(
435
+ pkgPath,
436
+ name,
437
+ determinedReleaseType,
438
+ prereleaseIdentifier,
439
+ initialVersion
440
+ );
432
441
  }
433
442
  const currentVersion = import_semver.default.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
443
+ const standardBumpTypes = ["major", "minor", "patch"];
444
+ if (standardBumpTypes.includes(determinedReleaseType) && import_semver.default.prerelease(currentVersion)) {
445
+ log(
446
+ `Cleaning prerelease identifier from ${currentVersion} for ${determinedReleaseType} bump`,
447
+ "debug"
448
+ );
449
+ return import_semver.default.inc(currentVersion, determinedReleaseType) || "";
450
+ }
434
451
  return import_semver.default.inc(currentVersion, determinedReleaseType, prereleaseIdentifier) || "";
435
452
  }
436
453
  if (config.versionStrategy === "branchPattern" && (branchPattern == null ? void 0 : branchPattern.length)) {
@@ -445,8 +462,14 @@ async function calculateVersion(config, options) {
445
462
  }
446
463
  }
447
464
  if (determinedReleaseType) {
448
- if (!latestTag) {
449
- return initialVersion;
465
+ if (hasNoTags) {
466
+ return getPackageVersionFallback(
467
+ pkgPath,
468
+ name,
469
+ determinedReleaseType,
470
+ prereleaseIdentifier,
471
+ initialVersion
472
+ );
450
473
  }
451
474
  const currentVersion = import_semver.default.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
452
475
  return import_semver.default.inc(currentVersion, determinedReleaseType, prereleaseIdentifier) || "";
@@ -457,10 +480,19 @@ async function calculateVersion(config, options) {
457
480
  bumper.loadPreset(config.preset);
458
481
  const recommendedBump = await bumper.bump();
459
482
  const releaseTypeFromCommits = recommendedBump.releaseType;
460
- if (!latestTag) {
483
+ if (hasNoTags) {
484
+ if (releaseTypeFromCommits) {
485
+ return getPackageVersionFallback(
486
+ pkgPath,
487
+ name,
488
+ releaseTypeFromCommits,
489
+ prereleaseIdentifier,
490
+ initialVersion
491
+ );
492
+ }
461
493
  return initialVersion;
462
494
  }
463
- const checkPath = path4 || (0, import_node_process3.cwd)();
495
+ const checkPath = pkgPath || (0, import_node_process3.cwd)();
464
496
  const commitsLength = getCommitsLength(checkPath);
465
497
  if (commitsLength === 0) {
466
498
  log(
@@ -488,6 +520,38 @@ async function calculateVersion(config, options) {
488
520
  throw error;
489
521
  }
490
522
  }
523
+ function getPackageVersionFallback(pkgPath, name, releaseType, prereleaseIdentifier, initialVersion) {
524
+ const packageDir = pkgPath || (0, import_node_process3.cwd)();
525
+ const packageJsonPath = path2.join(packageDir, "package.json");
526
+ if (!fs3.existsSync(packageJsonPath)) {
527
+ throw new Error(`package.json not found at ${packageJsonPath}. Cannot determine version.`);
528
+ }
529
+ try {
530
+ const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
531
+ if (!packageJson.version) {
532
+ log(`No version found in package.json. Using initial version ${initialVersion}`, "info");
533
+ return initialVersion;
534
+ }
535
+ log(
536
+ `No tags found for ${name || "package"}, using package.json version: ${packageJson.version} as base`,
537
+ "info"
538
+ );
539
+ const standardBumpTypes = ["major", "minor", "patch"];
540
+ if (standardBumpTypes.includes(releaseType) && import_semver.default.prerelease(packageJson.version)) {
541
+ log(
542
+ `Cleaning prerelease identifier from ${packageJson.version} for ${releaseType} bump`,
543
+ "debug"
544
+ );
545
+ const cleanVersion = import_semver.default.inc(packageJson.version, "patch") || packageJson.version;
546
+ return import_semver.default.inc(cleanVersion, releaseType) || initialVersion;
547
+ }
548
+ return import_semver.default.inc(packageJson.version, releaseType, prereleaseIdentifier) || initialVersion;
549
+ } catch (err) {
550
+ throw new Error(
551
+ `Error reading package.json: ${err instanceof Error ? err.message : String(err)}`
552
+ );
553
+ }
554
+ }
491
555
 
492
556
  // src/package/packageProcessor.ts
493
557
  var PackageProcessor = class {
package/dist/index.js CHANGED
@@ -142,8 +142,8 @@ function log(message, status = "info") {
142
142
  }
143
143
 
144
144
  // src/core/versionStrategies.ts
145
- import fs3 from "node:fs";
146
- import path2 from "node:path";
145
+ import fs4 from "node:fs";
146
+ import path3 from "node:path";
147
147
 
148
148
  // src/git/commands.ts
149
149
  import { cwd as cwd2 } from "node:process";
@@ -382,17 +382,20 @@ function updatePackageVersion(packagePath, version) {
382
382
  }
383
383
 
384
384
  // src/package/packageProcessor.ts
385
- import path from "node:path";
385
+ import path2 from "node:path";
386
386
  import { exit } from "node:process";
387
387
 
388
388
  // src/core/versionCalculator.ts
389
+ import * as fs3 from "node:fs";
390
+ import * as path from "node:path";
389
391
  import { cwd as cwd3 } from "node:process";
390
392
  import { Bumper } from "conventional-recommended-bump";
391
393
  import semver from "semver";
392
394
  async function calculateVersion(config, options) {
393
- const { latestTag, type, path: path3, name, branchPattern, prereleaseIdentifier } = options;
395
+ const { latestTag, type, path: pkgPath, name, branchPattern, prereleaseIdentifier } = options;
394
396
  const originalPrefix = config.tagPrefix || "v";
395
397
  const initialVersion = prereleaseIdentifier ? `0.0.1-${prereleaseIdentifier}` : "0.0.1";
398
+ const hasNoTags = !latestTag || latestTag === "";
396
399
  function determineTagSearchPattern(packageName, prefix) {
397
400
  if (packageName) {
398
401
  return prefix ? `${prefix}${packageName}@` : `${packageName}@`;
@@ -403,10 +406,24 @@ async function calculateVersion(config, options) {
403
406
  const escapedTagPattern = escapeRegExp(tagSearchPattern);
404
407
  let determinedReleaseType = type || null;
405
408
  if (determinedReleaseType) {
406
- if (!latestTag) {
407
- return initialVersion;
409
+ if (hasNoTags) {
410
+ return getPackageVersionFallback(
411
+ pkgPath,
412
+ name,
413
+ determinedReleaseType,
414
+ prereleaseIdentifier,
415
+ initialVersion
416
+ );
408
417
  }
409
418
  const currentVersion = semver.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
419
+ const standardBumpTypes = ["major", "minor", "patch"];
420
+ if (standardBumpTypes.includes(determinedReleaseType) && semver.prerelease(currentVersion)) {
421
+ log(
422
+ `Cleaning prerelease identifier from ${currentVersion} for ${determinedReleaseType} bump`,
423
+ "debug"
424
+ );
425
+ return semver.inc(currentVersion, determinedReleaseType) || "";
426
+ }
410
427
  return semver.inc(currentVersion, determinedReleaseType, prereleaseIdentifier) || "";
411
428
  }
412
429
  if (config.versionStrategy === "branchPattern" && (branchPattern == null ? void 0 : branchPattern.length)) {
@@ -421,8 +438,14 @@ async function calculateVersion(config, options) {
421
438
  }
422
439
  }
423
440
  if (determinedReleaseType) {
424
- if (!latestTag) {
425
- return initialVersion;
441
+ if (hasNoTags) {
442
+ return getPackageVersionFallback(
443
+ pkgPath,
444
+ name,
445
+ determinedReleaseType,
446
+ prereleaseIdentifier,
447
+ initialVersion
448
+ );
426
449
  }
427
450
  const currentVersion = semver.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
428
451
  return semver.inc(currentVersion, determinedReleaseType, prereleaseIdentifier) || "";
@@ -433,10 +456,19 @@ async function calculateVersion(config, options) {
433
456
  bumper.loadPreset(config.preset);
434
457
  const recommendedBump = await bumper.bump();
435
458
  const releaseTypeFromCommits = recommendedBump.releaseType;
436
- if (!latestTag) {
459
+ if (hasNoTags) {
460
+ if (releaseTypeFromCommits) {
461
+ return getPackageVersionFallback(
462
+ pkgPath,
463
+ name,
464
+ releaseTypeFromCommits,
465
+ prereleaseIdentifier,
466
+ initialVersion
467
+ );
468
+ }
437
469
  return initialVersion;
438
470
  }
439
- const checkPath = path3 || cwd3();
471
+ const checkPath = pkgPath || cwd3();
440
472
  const commitsLength = getCommitsLength(checkPath);
441
473
  if (commitsLength === 0) {
442
474
  log(
@@ -464,6 +496,38 @@ async function calculateVersion(config, options) {
464
496
  throw error;
465
497
  }
466
498
  }
499
+ function getPackageVersionFallback(pkgPath, name, releaseType, prereleaseIdentifier, initialVersion) {
500
+ const packageDir = pkgPath || cwd3();
501
+ const packageJsonPath = path.join(packageDir, "package.json");
502
+ if (!fs3.existsSync(packageJsonPath)) {
503
+ throw new Error(`package.json not found at ${packageJsonPath}. Cannot determine version.`);
504
+ }
505
+ try {
506
+ const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
507
+ if (!packageJson.version) {
508
+ log(`No version found in package.json. Using initial version ${initialVersion}`, "info");
509
+ return initialVersion;
510
+ }
511
+ log(
512
+ `No tags found for ${name || "package"}, using package.json version: ${packageJson.version} as base`,
513
+ "info"
514
+ );
515
+ const standardBumpTypes = ["major", "minor", "patch"];
516
+ if (standardBumpTypes.includes(releaseType) && semver.prerelease(packageJson.version)) {
517
+ log(
518
+ `Cleaning prerelease identifier from ${packageJson.version} for ${releaseType} bump`,
519
+ "debug"
520
+ );
521
+ const cleanVersion = semver.inc(packageJson.version, "patch") || packageJson.version;
522
+ return semver.inc(cleanVersion, releaseType) || initialVersion;
523
+ }
524
+ return semver.inc(packageJson.version, releaseType, prereleaseIdentifier) || initialVersion;
525
+ } catch (err) {
526
+ throw new Error(
527
+ `Error reading package.json: ${err instanceof Error ? err.message : String(err)}`
528
+ );
529
+ }
530
+ }
467
531
 
468
532
  // src/package/packageProcessor.ts
469
533
  var PackageProcessor = class {
@@ -546,7 +610,7 @@ var PackageProcessor = class {
546
610
  if (!nextVersion) {
547
611
  continue;
548
612
  }
549
- updatePackageVersion(path.join(pkgPath, "package.json"), nextVersion);
613
+ updatePackageVersion(path2.join(pkgPath, "package.json"), nextVersion);
550
614
  const packageTag = formatTag(nextVersion, tagPrefix);
551
615
  const tagMessage = `chore(release): ${name} ${nextVersion}`;
552
616
  addTag(packageTag);
@@ -571,7 +635,7 @@ var PackageProcessor = class {
571
635
  log("No targeted packages required a version update.", "info");
572
636
  return { updatedPackages: [], tags };
573
637
  }
574
- const filesToCommit = updatedPackagesInfo.map((info) => path.join(info.path, "package.json"));
638
+ const filesToCommit = updatedPackagesInfo.map((info) => path2.join(info.path, "package.json"));
575
639
  const packageNames = updatedPackagesInfo.map((p) => p.name).join(", ");
576
640
  const representativeVersion = ((_a = updatedPackagesInfo[0]) == null ? void 0 : _a.version) || "multiple";
577
641
  let commitMessage = this.commitMessageTemplate || "chore(release): publish packages";
@@ -647,8 +711,8 @@ function createSyncedStrategy(config) {
647
711
  const files = [];
648
712
  const updatedPackages = [];
649
713
  try {
650
- const rootPkgPath = path2.join(packages.root, "package.json");
651
- if (fs3.existsSync(rootPkgPath)) {
714
+ const rootPkgPath = path3.join(packages.root, "package.json");
715
+ if (fs4.existsSync(rootPkgPath)) {
652
716
  updatePackageVersion(rootPkgPath, nextVersion);
653
717
  files.push(rootPkgPath);
654
718
  updatedPackages.push("root");
@@ -660,7 +724,7 @@ function createSyncedStrategy(config) {
660
724
  if (!shouldProcessPackage(pkg, config)) {
661
725
  continue;
662
726
  }
663
- const packageJsonPath = path2.join(pkg.dir, "package.json");
727
+ const packageJsonPath = path3.join(pkg.dir, "package.json");
664
728
  updatePackageVersion(packageJsonPath, nextVersion);
665
729
  files.push(packageJsonPath);
666
730
  updatedPackages.push(pkg.packageJson.name);
@@ -725,7 +789,7 @@ function createSingleStrategy(config) {
725
789
  log(`No version change needed for ${packageName}`, "info");
726
790
  return;
727
791
  }
728
- const packageJsonPath = path2.join(pkgPath, "package.json");
792
+ const packageJsonPath = path3.join(pkgPath, "package.json");
729
793
  updatePackageVersion(packageJsonPath, nextVersion);
730
794
  log(`Updated package ${packageName} to version ${nextVersion}`, "success");
731
795
  const nextTag = formatTag(nextVersion, tagPrefix || "v");
@@ -94,3 +94,42 @@ This is the default if the `synced` flag is present and true.
94
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
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
96
  - **Use Case:** Releasing specific packages independently while still tagging each released package individually.
97
+
98
+ ## Prerelease Handling
99
+
100
+ `package-versioner` provides flexible handling for prerelease versions, allowing both creation of prereleases and promotion to stable releases.
101
+
102
+ ### Creating Prereleases
103
+
104
+ Use the `--prerelease` flag with an identifier to create a prerelease version:
105
+
106
+ ```bash
107
+ # Create a beta prerelease
108
+ npx package-versioner --bump minor --prerelease beta
109
+ # Result: 1.0.0 -> 1.1.0-beta.0
110
+ ```
111
+
112
+ You can also set a default prerelease identifier in your `version.config.json`:
113
+
114
+ ```json
115
+ {
116
+ "prereleaseIdentifier": "beta"
117
+ }
118
+ ```
119
+
120
+ ### Promoting Prereleases to Stable Releases
121
+
122
+ When using standard bump types (`major`, `minor`, `patch`) with the `--bump` flag on a prerelease version, `package-versioner` will automatically clean the prerelease identifier:
123
+
124
+ ```bash
125
+ # Starting from version 1.0.0-beta.1
126
+ npx package-versioner --bump major
127
+ # Result: 1.0.0-beta.1 -> 2.0.0 (not 2.0.0-beta.0)
128
+ ```
129
+
130
+ This intuitive behavior means you don't need to use an empty prerelease identifier (`--prerelease ""`) to promote a prerelease to a stable version. Simply specify the standard bump type and the tool will automatically produce a clean version number.
131
+
132
+ This applies to all standard bump types:
133
+ - `--bump major`: 1.0.0-beta.1 -> 2.0.0
134
+ - `--bump minor`: 1.0.0-beta.1 -> 1.1.0
135
+ - `--bump patch`: 1.0.0-beta.1 -> 1.0.1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "package-versioner",
3
3
  "description": "A lightweight yet powerful CLI tool for automated semantic versioning based on Git history and conventional commits.",
4
- "version": "0.3.0",
4
+ "version": "0.4.1",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.mjs",