package-versioner 0.2.0 → 0.4.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/dist/index.js CHANGED
@@ -26,90 +26,136 @@ function loadConfig(configPath) {
26
26
  });
27
27
  }
28
28
 
29
- // src/utils.ts
30
- import fs2 from "node:fs";
29
+ // src/core/versionEngine.ts
30
+ import { cwd as cwd4 } from "node:process";
31
+ import { getPackagesSync } from "@manypkg/get-packages";
32
+
33
+ // src/errors/gitError.ts
34
+ var GitError = class extends Error {
35
+ constructor(message, code) {
36
+ super(message);
37
+ this.code = code;
38
+ this.name = "GitError";
39
+ }
40
+ };
41
+ function createGitError(code, details) {
42
+ const messages = {
43
+ ["NOT_GIT_REPO" /* NOT_GIT_REPO */]: "Not a git repository",
44
+ ["GIT_PROCESS_ERROR" /* GIT_PROCESS_ERROR */]: "Failed to create new version",
45
+ ["NO_FILES" /* NO_FILES */]: "No files specified for commit",
46
+ ["NO_COMMIT_MESSAGE" /* NO_COMMIT_MESSAGE */]: "Commit message is required",
47
+ ["GIT_ERROR" /* GIT_ERROR */]: "Git operation failed"
48
+ };
49
+ const baseMessage = messages[code];
50
+ const fullMessage = details ? `${baseMessage}: ${details}` : baseMessage;
51
+ return new GitError(fullMessage, code);
52
+ }
53
+
54
+ // src/errors/versionError.ts
55
+ var VersionError = class extends Error {
56
+ constructor(message, code) {
57
+ super(message);
58
+ this.code = code;
59
+ this.name = "VersionError";
60
+ }
61
+ };
62
+ function createVersionError(code, details) {
63
+ const messages = {
64
+ ["CONFIG_REQUIRED" /* CONFIG_REQUIRED */]: "Configuration is required",
65
+ ["PACKAGES_NOT_FOUND" /* PACKAGES_NOT_FOUND */]: "Failed to get packages information",
66
+ ["WORKSPACE_ERROR" /* WORKSPACE_ERROR */]: "Failed to get workspace packages",
67
+ ["INVALID_CONFIG" /* INVALID_CONFIG */]: "Invalid configuration",
68
+ ["PACKAGE_NOT_FOUND" /* PACKAGE_NOT_FOUND */]: "Package not found",
69
+ ["VERSION_CALCULATION_ERROR" /* VERSION_CALCULATION_ERROR */]: "Failed to calculate version"
70
+ };
71
+ const baseMessage = messages[code];
72
+ const fullMessage = details ? `${baseMessage}: ${details}` : baseMessage;
73
+ return new VersionError(fullMessage, code);
74
+ }
75
+
76
+ // src/utils/logging.ts
31
77
  import chalk from "chalk";
32
78
  import figlet from "figlet";
33
79
 
34
- // package.json
35
- var package_default = {
36
- name: "package-versioner",
37
- description: "A powerful CLI tool for automated semantic versioning based on Git history and conventional commits. Supports monorepos with synchronized or independent package versioning strategies.",
38
- version: "0.1.1",
39
- type: "module",
40
- main: "./dist/index.js",
41
- module: "./dist/index.mjs",
42
- types: "./dist/index.d.ts",
43
- author: {
44
- name: "Sam Maister",
45
- email: "goosewobbler@protonmail.com"
46
- },
47
- repository: {
48
- type: "git",
49
- url: "https://github.com/goosewobbler/package-versioner",
50
- homepage: "https://github.com/goosewobbler/package-versioner"
51
- },
52
- keywords: ["version", "semver", "git", "package"],
53
- license: "MIT",
54
- files: ["dist/**", "docs/**", "package-versioner.schema.json"],
55
- bin: {
56
- "package-versioner": "./dist/index.js"
57
- },
58
- scripts: {
59
- build: "tsup src/index.ts --format esm,cjs --dts",
60
- dev: "tsup src/index.ts --format esm,cjs --watch --dts",
61
- clean: "rm -rf node_modules && rm -rf dist",
62
- test: "vitest run --coverage",
63
- "test:watch": "vitest --coverage",
64
- lint: "biome check .",
65
- "lint:fix": "biome check --apply .",
66
- format: "biome format --write .",
67
- "format:check": "biome format .",
68
- fix: "pnpm run lint:fix && pnpm run format",
69
- prepare: "husky"
70
- },
71
- "lint-staged": {
72
- "*.{js,ts,jsx,tsx}": ["biome check --apply", "biome format --write"]
73
- },
74
- devDependencies: {
75
- "@biomejs/biome": "^1.9.4",
76
- "@types/figlet": "^1.5.5",
77
- "@types/node": "^22.14.0",
78
- "@types/semver": "^7.3.13",
79
- "@vitest/coverage-v8": "^3.1.1",
80
- husky: "^9.1.7",
81
- "lint-staged": "^15.5.0",
82
- tsup: "^8.4.0",
83
- typescript: "^5.8.3",
84
- vitest: "^3.1.1"
85
- },
86
- dependencies: {
87
- "@manypkg/get-packages": "^2.2.2",
88
- chalk: "^5.4.1",
89
- commander: "^13.1.0",
90
- "conventional-changelog-angular": "^8.0.0",
91
- "conventional-recommended-bump": "^11.0.0",
92
- figlet: "^1.8.0",
93
- "git-semver-tags": "^8.0.0",
94
- semver: "^7.7.1"
95
- },
96
- packageManager: "pnpm@10.8.0+sha512.0e82714d1b5b43c74610193cb20734897c1d00de89d0e18420aebc5977fa13d780a9cb05734624e81ebd81cc876cd464794850641c48b9544326b5622ca29971"
80
+ // src/utils/jsonOutput.ts
81
+ var _jsonOutputMode = false;
82
+ var _jsonData = {
83
+ dryRun: false,
84
+ updates: [],
85
+ tags: []
97
86
  };
87
+ function enableJsonOutput(dryRun = false) {
88
+ _jsonOutputMode = true;
89
+ _jsonData.dryRun = dryRun;
90
+ _jsonData.updates = [];
91
+ _jsonData.tags = [];
92
+ _jsonData.commitMessage = void 0;
93
+ }
94
+ function isJsonOutputMode() {
95
+ return _jsonOutputMode;
96
+ }
97
+ function addPackageUpdate(packageName, newVersion, filePath) {
98
+ if (!_jsonOutputMode) return;
99
+ _jsonData.updates.push({
100
+ packageName,
101
+ newVersion,
102
+ filePath
103
+ });
104
+ }
105
+ function addTag(tag) {
106
+ if (!_jsonOutputMode) return;
107
+ _jsonData.tags.push(tag);
108
+ }
109
+ function setCommitMessage(message) {
110
+ if (!_jsonOutputMode) return;
111
+ _jsonData.commitMessage = message;
112
+ }
113
+ function printJsonOutput() {
114
+ if (_jsonOutputMode) {
115
+ console.log(JSON.stringify(_jsonData, null, 2));
116
+ }
117
+ }
98
118
 
99
- // src/utils.ts
100
- import { getSemverTags } from "git-semver-tags";
119
+ // src/utils/logging.ts
120
+ function log(message, status = "info") {
121
+ if (isJsonOutputMode() && status !== "error") {
122
+ return;
123
+ }
124
+ let chalkFn;
125
+ switch (status) {
126
+ case "success":
127
+ chalkFn = chalk.green;
128
+ break;
129
+ case "warning":
130
+ chalkFn = chalk.yellow;
131
+ break;
132
+ case "error":
133
+ chalkFn = chalk.red;
134
+ break;
135
+ case "debug":
136
+ chalkFn = chalk.gray;
137
+ break;
138
+ default:
139
+ chalkFn = chalk.blue;
140
+ }
141
+ console.log(chalkFn(message));
142
+ }
101
143
 
102
- // src/git.ts
103
- import { exec, execSync as syncExec } from "node:child_process";
104
- import { existsSync, statSync } from "node:fs";
105
- import { join } from "node:path";
144
+ // src/core/versionStrategies.ts
145
+ import fs3 from "node:fs";
146
+ import path2 from "node:path";
147
+
148
+ // src/git/commands.ts
106
149
  import { cwd as cwd2 } from "node:process";
107
- var execAsync = (command) => {
150
+
151
+ // src/git/commandExecutor.ts
152
+ import { exec, execSync as nativeExecSync } from "node:child_process";
153
+ var execAsync = (command, options) => {
154
+ const defaultOptions = { maxBuffer: 1024 * 1024 * 10, ...options };
108
155
  return new Promise((resolve, reject) => {
109
- const options = { maxBuffer: 1024 * 1024 * 10 };
110
156
  exec(
111
157
  command,
112
- options,
158
+ defaultOptions,
113
159
  (error, stdout, stderr) => {
114
160
  if (error) {
115
161
  reject(error);
@@ -120,7 +166,33 @@ var execAsync = (command) => {
120
166
  );
121
167
  });
122
168
  };
123
- var execSync = (command, args) => syncExec(command, { maxBuffer: 1024 * 1024 * 10, ...args });
169
+ var execSync = (command, args) => nativeExecSync(command, { maxBuffer: 1024 * 1024 * 10, ...args });
170
+
171
+ // src/git/repository.ts
172
+ import { existsSync, statSync } from "node:fs";
173
+ import { join } from "node:path";
174
+ function isGitRepository(directory) {
175
+ const gitDir = join(directory, ".git");
176
+ if (!existsSync(gitDir)) {
177
+ return false;
178
+ }
179
+ const stats = statSync(gitDir);
180
+ if (!stats.isDirectory()) {
181
+ return false;
182
+ }
183
+ try {
184
+ execSync("git rev-parse --is-inside-work-tree", { cwd: directory });
185
+ return true;
186
+ } catch (_error) {
187
+ return false;
188
+ }
189
+ }
190
+ function getCurrentBranch() {
191
+ const result = execSync("git rev-parse --abbrev-ref HEAD");
192
+ return result.toString().trim();
193
+ }
194
+
195
+ // src/git/commands.ts
124
196
  async function gitAdd(files) {
125
197
  const command = `git add ${files.join(" ")}`;
126
198
  return execAsync(command);
@@ -147,36 +219,12 @@ async function createGitTag(options) {
147
219
  const command = `git tag -a -m "${message}" ${tag} ${args}`;
148
220
  return execAsync(command);
149
221
  }
150
- function getCommitsLength(pkgRoot) {
151
- try {
152
- const gitCommand = `git rev-list --count HEAD ^$(git describe --tags --abbrev=0) ${pkgRoot}`;
153
- const amount = execSync(gitCommand).toString().trim();
154
- return Number(amount);
155
- } catch {
156
- return 0;
157
- }
158
- }
159
- function isGitRepository(directory) {
160
- const gitDir = join(directory, ".git");
161
- if (!existsSync(gitDir)) {
162
- return false;
163
- }
164
- const stats = statSync(gitDir);
165
- if (!stats.isDirectory()) {
166
- return false;
167
- }
168
- try {
169
- execSync("git rev-parse --is-inside-work-tree", { cwd: directory });
170
- return true;
171
- } catch (_error) {
172
- return false;
222
+ async function gitProcess(options) {
223
+ const { files, nextTag, commitMessage, skipHooks, dryRun } = options;
224
+ if (!isGitRepository(cwd2())) {
225
+ throw createGitError("NOT_GIT_REPO" /* NOT_GIT_REPO */);
173
226
  }
174
- }
175
- async function gitProcess({ files, nextTag, commitMessage, skipHooks, dryRun }) {
176
227
  try {
177
- if (!isGitRepository(cwd2())) {
178
- throw new Error("Not a git repository (or any parent up to mount point /)");
179
- }
180
228
  if (!dryRun) {
181
229
  await gitAdd(files);
182
230
  await gitCommit({
@@ -191,503 +239,309 @@ async function gitProcess({ files, nextTag, commitMessage, skipHooks, dryRun })
191
239
  });
192
240
  }
193
241
  } else {
194
- log("info", "[DRY RUN] Would add files:");
242
+ log("[DRY RUN] Would add files:", "info");
195
243
  for (const file of files) {
196
- log("info", ` - ${file}`);
244
+ log(` - ${file}`, "info");
197
245
  }
198
- log("info", `[DRY RUN] Would commit with message: "${commitMessage}"`);
246
+ log(`[DRY RUN] Would commit with message: "${commitMessage}"`, "info");
199
247
  if (nextTag) {
200
- log("info", `[DRY RUN] Would create tag: ${nextTag}`);
248
+ log(`[DRY RUN] Would create tag: ${nextTag}`, "info");
201
249
  }
202
250
  }
203
251
  } catch (err) {
204
- console.log(err);
205
252
  const errorMessage = err instanceof Error ? err.message : String(err);
206
- throw new Error(`Failed to create new version: ${errorMessage}`);
253
+ throw createGitError("GIT_PROCESS_ERROR" /* GIT_PROCESS_ERROR */, errorMessage);
207
254
  }
208
255
  }
209
- async function lastMergeBranchName(branches, baseBranch) {
256
+ async function createGitCommitAndTag(files, nextTag, commitMessage, skipHooks, dryRun) {
210
257
  try {
211
- const branchesRegex = `${branches.join("/(.*)|")}/(.*)`;
212
- const command = `git for-each-ref --sort=-committerdate --format='%(refname:short)' refs/heads --merged ${baseBranch} | grep -o -i -E "${branchesRegex}" | awk -F'[ ]' '{print $1}' | head -n 1`;
213
- const { stdout } = await execAsync(command);
214
- return stdout.trim();
258
+ if (!files || files.length === 0) {
259
+ throw createGitError("NO_FILES" /* NO_FILES */);
260
+ }
261
+ if (!commitMessage) {
262
+ throw createGitError("NO_COMMIT_MESSAGE" /* NO_COMMIT_MESSAGE */);
263
+ }
264
+ setCommitMessage(commitMessage);
265
+ if (nextTag) {
266
+ addTag(nextTag);
267
+ }
268
+ await gitProcess({
269
+ files,
270
+ nextTag,
271
+ commitMessage,
272
+ skipHooks,
273
+ dryRun
274
+ });
275
+ if (!dryRun) {
276
+ log(`Created tag: ${nextTag}`, "success");
277
+ }
215
278
  } catch (error) {
216
- console.error(
217
- "Error while getting the last branch name:",
218
- error instanceof Error ? error.message : String(error)
219
- );
220
- return null;
279
+ const errorMessage = error instanceof Error ? error.message : String(error);
280
+ log(`Failed to create git commit and tag: ${errorMessage}`, "error");
281
+ if (error instanceof Error) {
282
+ console.error(error.stack || error.message);
283
+ } else {
284
+ console.error(error);
285
+ }
286
+ throw new GitError(`Git operation failed: ${errorMessage}`, "GIT_ERROR" /* GIT_ERROR */);
221
287
  }
222
288
  }
223
- function getCurrentBranch() {
224
- const result = execSync("git rev-parse --abbrev-ref HEAD");
225
- return result.toString().trim();
226
- }
227
289
 
228
- // src/utils.ts
229
- function printFiglet() {
230
- const font = "Standard";
231
- figlet.text(package_default.name, { font }, (err, data) => {
232
- if (err) {
233
- log("warning", "Could not print figlet banner: Figlet error");
234
- console.error(err);
235
- return;
236
- }
237
- if (data) {
238
- const figletText = data;
239
- const versionText = `v${package_default.version}`;
240
- process.stdout.write(`${chalk.hex("#FF1F57")(figletText)}
241
- `);
242
- process.stdout.write(`${chalk.hex("#0096FF")(versionText)}
290
+ // src/git/tagsAndBranches.ts
291
+ import { getSemverTags } from "git-semver-tags";
243
292
 
244
- `);
293
+ // src/utils/formatting.ts
294
+ function escapeRegExp(string) {
295
+ return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
296
+ }
297
+ function formatTag(version, tagPrefix) {
298
+ if (!tagPrefix) return version;
299
+ return tagPrefix.endsWith("/") ? `${tagPrefix}${version}` : `${tagPrefix}/${version}`;
300
+ }
301
+ function formatTagPrefix(tagPrefix, scope) {
302
+ if (!tagPrefix) return "";
303
+ const prefix = tagPrefix.replace(/\/$/, "");
304
+ if (scope) {
305
+ return `${prefix}/${scope}`;
306
+ }
307
+ return prefix;
308
+ }
309
+ function formatCommitMessage(template, version, scope) {
310
+ return createTemplateString(template, { version, scope });
311
+ }
312
+ function createTemplateString(template, variables) {
313
+ return Object.entries(variables).reduce((result, [key, value]) => {
314
+ if (value === void 0) {
315
+ return result;
245
316
  }
246
- });
317
+ const regex = new RegExp(`\\$\\{${key}\\}`, "g");
318
+ return result.replace(regex, value);
319
+ }, template);
247
320
  }
248
- function log(status, message) {
249
- const statusColors = {
250
- info: chalk.blue("\u2139"),
251
- success: chalk.green("\u2713"),
252
- error: chalk.red("\u2717"),
253
- warning: chalk.yellow("\u26A0")
254
- };
255
- process.stdout.write(`${statusColors[status]} ${message}
256
- `);
321
+
322
+ // src/git/tagsAndBranches.ts
323
+ function getCommitsLength(pkgRoot) {
324
+ try {
325
+ const gitCommand = `git rev-list --count HEAD ^$(git describe --tags --abbrev=0) ${pkgRoot}`;
326
+ const amount = execSync(gitCommand).toString().trim();
327
+ return Number(amount);
328
+ } catch (error) {
329
+ const errorMessage = error instanceof Error ? error.message : String(error);
330
+ log(`Failed to get number of commits since last tag: ${errorMessage}`, "error");
331
+ return 0;
332
+ }
257
333
  }
258
334
  async function getLatestTag() {
259
335
  try {
260
336
  const tags = await getSemverTags({});
261
337
  return tags[0] || "";
262
338
  } catch (error) {
263
- log("error", "Failed to get latest tag");
264
- console.error(error);
339
+ const errorMessage = error instanceof Error ? error.message : String(error);
340
+ log(`Failed to get latest tag: ${errorMessage}`, "error");
265
341
  if (error instanceof Error && error.message.includes("No names found")) {
266
- log("info", "No tags found in the repository.");
342
+ log("No tags found in the repository.", "info");
267
343
  }
268
344
  return "";
269
345
  }
270
346
  }
271
- function formatTag(options, props) {
272
- const { name, synced, tagPrefix } = options;
273
- const { version } = props;
274
- if (!synced && name) {
275
- return `${tagPrefix ? tagPrefix : ""}${name}@${version}`;
347
+ async function lastMergeBranchName(branches, baseBranch) {
348
+ try {
349
+ const escapedBranches = branches.map((branch) => escapeRegExp(branch));
350
+ const branchesRegex = `${escapedBranches.join("/(.*)|")}/(.*)`;
351
+ const command = `git for-each-ref --sort=-committerdate --format='%(refname:short)' refs/heads --merged ${baseBranch} | grep -o -i -E "${branchesRegex}" | awk -F'[ ]' '{print $1}' | head -n 1`;
352
+ const { stdout } = await execAsync(command);
353
+ return stdout.trim();
354
+ } catch (error) {
355
+ console.error(
356
+ "Error while getting the last branch name:",
357
+ error instanceof Error ? error.message : String(error)
358
+ );
359
+ return null;
276
360
  }
277
- return `${tagPrefix ? tagPrefix : "v"}${version}`;
278
- }
279
- function formatTagPrefix(tagPrefix) {
280
- return tagPrefix ? `${tagPrefix}@` : "";
281
361
  }
282
- function createTemplateString(template, data) {
283
- return template.replace(/\$\{([^}]+)\}/g, (_, key) => data[key] || "");
284
- }
285
- function formatCommitMessage(template, version) {
286
- return createTemplateString(template, { version });
287
- }
288
- function updatePackageVersion({ path: path2, version, name, dryRun }) {
362
+
363
+ // src/package/packageManagement.ts
364
+ import fs2 from "node:fs";
365
+ function updatePackageVersion(packagePath, version) {
289
366
  try {
290
- const pkgPath = `${path2}/package.json`;
291
- const pkg = JSON.parse(fs2.readFileSync(pkgPath, "utf8"));
292
- pkg.version = version;
293
- if (!dryRun) {
294
- fs2.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}
367
+ const packageContent = fs2.readFileSync(packagePath, "utf8");
368
+ const packageJson = JSON.parse(packageContent);
369
+ const packageName = packageJson.name;
370
+ packageJson.version = version;
371
+ fs2.writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}
295
372
  `);
296
- log("success", `${name}: ${version}`);
297
- } else {
298
- log("info", `[DRY RUN] Would update ${name} package.json to version ${version}`);
299
- }
373
+ addPackageUpdate(packageName, version, packagePath);
374
+ log(`Updated package.json at ${packagePath} to version ${version}`, "success");
300
375
  } catch (error) {
301
- log("error", `Failed to update ${name} to version ${version}`);
302
- console.error(error);
376
+ log(`Failed to update package.json at ${packagePath}`, "error");
377
+ if (error instanceof Error) {
378
+ log(error.message, "error");
379
+ }
380
+ throw error;
303
381
  }
304
382
  }
305
383
 
306
- // src/versionEngine.ts
307
- import fs3 from "node:fs";
384
+ // src/package/packageProcessor.ts
308
385
  import path from "node:path";
309
- import { cwd as cwd3, exit } from "node:process";
310
- import { getPackagesSync } from "@manypkg/get-packages";
386
+ import { exit } from "node:process";
387
+
388
+ // src/core/versionCalculator.ts
389
+ import { cwd as cwd3 } from "node:process";
311
390
  import { Bumper } from "conventional-recommended-bump";
312
391
  import semver from "semver";
313
- var VersionEngine = class {
314
- config;
315
- constructor(config) {
316
- this.config = config;
317
- }
318
- /**
319
- * Calculate the next version based on options
320
- */
321
- async calculateVersion(options) {
322
- const { latestTag, type, path: path2, name, branchPattern, prereleaseIdentifier } = options;
323
- const originalPrefix = this.config.tagPrefix || "";
324
- const initialVersion = prereleaseIdentifier ? `0.0.1-${prereleaseIdentifier}` : "0.0.1";
325
- const tagSearchPattern = name ? originalPrefix ? `${originalPrefix}${name}@` : `${name}@` : originalPrefix ? `${originalPrefix}v` : "v";
326
- let determinedReleaseType = type || null;
327
- if (determinedReleaseType) {
328
- if (!latestTag) {
329
- return initialVersion;
330
- }
331
- const currentVersion = semver.clean(latestTag.replace(tagSearchPattern, "")) || "0.0.0";
332
- return semver.inc(currentVersion, determinedReleaseType, prereleaseIdentifier) || "";
392
+ async function calculateVersion(config, options) {
393
+ const { latestTag, type, path: path3, name, branchPattern, prereleaseIdentifier } = options;
394
+ const originalPrefix = config.tagPrefix || "v";
395
+ const initialVersion = prereleaseIdentifier ? `0.0.1-${prereleaseIdentifier}` : "0.0.1";
396
+ function determineTagSearchPattern(packageName, prefix) {
397
+ if (packageName) {
398
+ return prefix ? `${prefix}${packageName}@` : `${packageName}@`;
333
399
  }
334
- if (this.config.versionStrategy === "branchPattern" && (branchPattern == null ? void 0 : branchPattern.length)) {
335
- const currentBranch = await getCurrentBranch();
336
- const mergeBranch = await lastMergeBranchName(branchPattern, this.config.baseBranch);
337
- const branch = mergeBranch || currentBranch;
338
- for (const pattern of branchPattern) {
339
- const [match, releaseType] = pattern.split(":");
340
- if (branch.includes(match) && releaseType) {
341
- determinedReleaseType = releaseType;
342
- break;
343
- }
344
- }
345
- if (determinedReleaseType) {
346
- if (!latestTag) {
347
- return initialVersion;
348
- }
349
- const currentVersion = semver.clean(latestTag.replace(tagSearchPattern, "")) || "0.0.0";
350
- return semver.inc(currentVersion, determinedReleaseType, prereleaseIdentifier) || "";
351
- }
400
+ return prefix ? `${prefix}v` : "v";
401
+ }
402
+ const tagSearchPattern = determineTagSearchPattern(name, originalPrefix);
403
+ const escapedTagPattern = escapeRegExp(tagSearchPattern);
404
+ let determinedReleaseType = type || null;
405
+ if (determinedReleaseType) {
406
+ if (!latestTag) {
407
+ return initialVersion;
352
408
  }
353
- try {
354
- const bumper = new Bumper();
355
- bumper.loadPreset(this.config.preset);
356
- const recommendedBump = await bumper.bump();
357
- const releaseTypeFromCommits = recommendedBump.releaseType;
358
- if (!latestTag) {
359
- return initialVersion;
360
- }
361
- const checkPath = path2 || cwd3();
362
- const commitsLength = await getCommitsLength(checkPath);
363
- if (commitsLength === 0) {
364
- log(
365
- "info",
366
- `No new commits found for ${name || "project"} since ${latestTag}, skipping version bump`
367
- );
368
- return "";
369
- }
370
- if (!releaseTypeFromCommits) {
371
- log(
372
- "info",
373
- `No relevant commits found for ${name || "project"} since ${latestTag}, skipping version bump`
374
- );
375
- return "";
376
- }
377
- const currentVersion = semver.clean(latestTag.replace(tagSearchPattern, "")) || "0.0.0";
378
- return semver.inc(currentVersion, releaseTypeFromCommits, prereleaseIdentifier) || "";
379
- } catch (error) {
380
- log("error", `Failed to calculate version for ${name || "project"}`);
381
- console.error(error);
382
- if (error instanceof Error && error.message.includes("No names found")) {
383
- log("info", "No tags found, proceeding with initial version calculation (if applicable).");
384
- return initialVersion;
385
- }
386
- return "";
409
+ const currentVersion = semver.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
410
+ const standardBumpTypes = ["major", "minor", "patch"];
411
+ if (standardBumpTypes.includes(determinedReleaseType) && semver.prerelease(currentVersion)) {
412
+ log(
413
+ `Cleaning prerelease identifier from ${currentVersion} for ${determinedReleaseType} bump`,
414
+ "debug"
415
+ );
416
+ return semver.inc(currentVersion, determinedReleaseType) || "";
387
417
  }
418
+ return semver.inc(currentVersion, determinedReleaseType, prereleaseIdentifier) || "";
388
419
  }
389
- /**
390
- * Process packages based on discovery, skip list, and optional target list.
391
- * Returns a list of package.json file paths that were updated (or would be in dry run).
392
- */
393
- async processPackages(discoveredPackages = [], targets = []) {
394
- const { tagPrefix, skip, dryRun } = this.config;
395
- const files = [];
396
- const pkgsToConsider = discoveredPackages.filter((pkg) => {
397
- if (skip == null ? void 0 : skip.includes(pkg.packageJson.name)) {
398
- log("info", `Skipping package ${pkg.packageJson.name} based on config skip list.`);
399
- return false;
420
+ if (config.versionStrategy === "branchPattern" && (branchPattern == null ? void 0 : branchPattern.length)) {
421
+ const currentBranch = await getCurrentBranch();
422
+ const mergeBranch = await lastMergeBranchName(branchPattern, config.baseBranch);
423
+ const branch = mergeBranch || currentBranch;
424
+ for (const pattern of branchPattern) {
425
+ const [match, releaseType] = pattern.split(":");
426
+ if (branch.includes(match) && releaseType) {
427
+ determinedReleaseType = releaseType;
428
+ break;
400
429
  }
401
- if (targets.length > 0) {
402
- const isTargeted = targets.includes(pkg.packageJson.name);
403
- if (!isTargeted) {
404
- }
405
- return isTargeted;
406
- }
407
- return true;
408
- });
409
- log("info", `Found ${pkgsToConsider.length} package(s) to process after filtering.`);
410
- for (const pkg of pkgsToConsider) {
411
- const name = pkg.packageJson.name;
412
- const pkgPath = pkg.dir;
413
- const prefix = formatTagPrefix(tagPrefix);
414
- const latestTag = await getLatestTag();
415
- const nextVersion = await this.calculateVersion({
416
- latestTag,
417
- tagPrefix: prefix,
418
- path: pkgPath,
419
- name,
420
- branchPattern: this.config.branchPattern,
421
- baseBranch: this.config.baseBranch,
422
- prereleaseIdentifier: this.config.prereleaseIdentifier,
423
- type: this.config.forceType
424
- });
425
- if (!nextVersion) {
426
- continue;
427
- }
428
- updatePackageVersion({
429
- path: pkgPath,
430
- version: nextVersion,
431
- name,
432
- dryRun
433
- });
434
- files.push(path.join(pkgPath, "package.json"));
435
430
  }
436
- return files;
437
- }
438
- /**
439
- * Create git commit and tag
440
- */
441
- async createGitCommitAndTag(files, nextTag, commitMessage, dryRun) {
442
- try {
443
- await gitProcess({
444
- files,
445
- nextTag,
446
- commitMessage,
447
- skipHooks: this.config.skipHooks,
448
- dryRun
449
- });
450
- if (!dryRun) {
451
- log("success", `Created tag: ${nextTag}`);
431
+ if (determinedReleaseType) {
432
+ if (!latestTag) {
433
+ return initialVersion;
452
434
  }
453
- } catch (error) {
454
- log("error", "Failed to create git commit and tag");
455
- console.error(error);
456
- exit(1);
435
+ const currentVersion = semver.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
436
+ return semver.inc(currentVersion, determinedReleaseType, prereleaseIdentifier) || "";
457
437
  }
458
438
  }
459
- /**
460
- * Synced versioning strategy (all packages get the same version)
461
- */
462
- async syncedStrategy() {
463
- var _a;
464
- const {
465
- tagPrefix,
466
- baseBranch,
467
- branchPattern,
468
- commitMessage = "chore(release): v${version}",
469
- prereleaseIdentifier
470
- } = this.config;
471
- const prefix = formatTagPrefix(tagPrefix);
472
- const latestTag = await getLatestTag();
473
- const nextVersion = await this.calculateVersion({
474
- latestTag,
475
- tagPrefix: prefix,
476
- branchPattern,
477
- baseBranch,
478
- prereleaseIdentifier
479
- });
480
- if (!nextVersion) {
481
- log("info", "No version change needed");
482
- return;
439
+ try {
440
+ const bumper = new Bumper();
441
+ bumper.loadPreset(config.preset);
442
+ const recommendedBump = await bumper.bump();
443
+ const releaseTypeFromCommits = recommendedBump.releaseType;
444
+ if (!latestTag) {
445
+ return initialVersion;
483
446
  }
484
- let pkgsResult;
485
- try {
486
- pkgsResult = getPackagesSync(cwd3());
487
- if (!pkgsResult || !pkgsResult.packages) {
488
- throw new Error("Failed to get packages information");
489
- }
490
- } catch (error) {
491
- log("error", "Failed to get packages information");
492
- console.error(error);
493
- exit(1);
494
- return;
447
+ const checkPath = path3 || cwd3();
448
+ const commitsLength = getCommitsLength(checkPath);
449
+ if (commitsLength === 0) {
450
+ log(
451
+ `No new commits found for ${name || "project"} since ${latestTag}, skipping version bump`,
452
+ "info"
453
+ );
454
+ return "";
495
455
  }
496
- const files = [];
497
- try {
498
- const rootPkgPath = path.join(pkgsResult.root, "package.json");
499
- if (fs3.existsSync(rootPkgPath)) {
500
- updatePackageVersion({
501
- path: pkgsResult.root,
502
- version: nextVersion,
503
- name: "root",
504
- dryRun: this.config.dryRun
505
- });
506
- files.push(rootPkgPath);
507
- }
508
- } catch (_error) {
509
- log("error", "Failed to update root package.json");
456
+ if (!releaseTypeFromCommits) {
457
+ log(
458
+ `No relevant commits found for ${name || "project"} since ${latestTag}, skipping version bump`,
459
+ "info"
460
+ );
461
+ return "";
510
462
  }
511
- for (const pkg of pkgsResult.packages) {
512
- if ((_a = this.config.skip) == null ? void 0 : _a.includes(pkg.packageJson.name)) {
513
- continue;
514
- }
515
- updatePackageVersion({
516
- path: pkg.dir,
517
- version: nextVersion,
518
- name: pkg.packageJson.name,
519
- dryRun: this.config.dryRun
520
- });
521
- files.push(path.join(pkg.dir, "package.json"));
463
+ const currentVersion = semver.clean(latestTag.replace(new RegExp(`^${escapedTagPattern}`), "")) || "0.0.0";
464
+ return semver.inc(currentVersion, releaseTypeFromCommits, prereleaseIdentifier) || "";
465
+ } catch (error) {
466
+ log(`Failed to calculate version for ${name || "project"}`, "error");
467
+ console.error(error);
468
+ if (error instanceof Error && error.message.includes("No names found")) {
469
+ log("No tags found, proceeding with initial version calculation (if applicable).", "info");
470
+ return initialVersion;
522
471
  }
523
- const nextTag = formatTag(
524
- { synced: true, tagPrefix },
525
- { tagPrefix: prefix, version: nextVersion }
526
- );
527
- const formattedCommitMessage = formatCommitMessage(commitMessage, nextVersion);
528
- await this.createGitCommitAndTag(files, nextTag, formattedCommitMessage, this.config.dryRun);
472
+ throw error;
473
+ }
474
+ }
475
+
476
+ // src/package/packageProcessor.ts
477
+ var PackageProcessor = class {
478
+ skip;
479
+ targets;
480
+ tagPrefix;
481
+ commitMessageTemplate;
482
+ dryRun;
483
+ skipHooks;
484
+ getLatestTag;
485
+ config;
486
+ // Config for version calculation
487
+ fullConfig;
488
+ constructor(options) {
489
+ this.skip = options.skip || [];
490
+ this.targets = options.targets || [];
491
+ this.tagPrefix = options.tagPrefix || "v";
492
+ this.commitMessageTemplate = options.commitMessageTemplate || "";
493
+ this.dryRun = options.dryRun || false;
494
+ this.skipHooks = options.skipHooks || false;
495
+ this.getLatestTag = options.getLatestTag;
496
+ this.config = options.config;
497
+ this.fullConfig = options.fullConfig;
529
498
  }
530
499
  /**
531
- * Single package versioning strategy
500
+ * Set package targets to process
532
501
  */
533
- async singleStrategy() {
534
- const {
535
- packages: configPackages,
536
- tagPrefix,
537
- commitMessage = "chore(release): ${version}"
538
- } = this.config;
539
- if (configPackages.length !== 1) {
540
- log("error", "Single mode requires exactly one package name");
541
- exit(1);
542
- }
543
- const packageName = configPackages[0];
544
- let pkgsResult;
545
- try {
546
- pkgsResult = getPackagesSync(cwd3());
547
- if (!pkgsResult || !pkgsResult.packages) {
548
- throw new Error("Failed to get packages information");
549
- }
550
- } catch (error) {
551
- log("error", "Failed to get packages information");
552
- console.error(error);
553
- exit(1);
554
- return;
555
- }
556
- const pkg = pkgsResult.packages.find((p) => p.packageJson.name === packageName);
557
- if (!pkg) {
558
- log("error", `Package ${packageName} not found`);
559
- exit(1);
560
- }
561
- const pkgPath = pkg.dir;
562
- const prefix = formatTagPrefix(tagPrefix);
563
- const latestTag = await getLatestTag();
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 === "") {
577
- log("info", `No version change needed for ${packageName}`);
578
- return;
579
- }
580
- updatePackageVersion({
581
- path: pkgPath,
582
- version: nextVersion,
583
- name: packageName,
584
- dryRun: this.config.dryRun
585
- });
586
- const nextTag = formatTag(
587
- { tagPrefix, name: packageName, synced: false },
588
- { tagPrefix: prefix, version: nextVersion }
589
- );
590
- const formattedCommitMessage = formatCommitMessage(commitMessage, nextVersion);
591
- await this.createGitCommitAndTag(
592
- [path.join(pkgPath, "package.json")],
593
- nextTag,
594
- formattedCommitMessage,
595
- this.config.dryRun
596
- );
502
+ setTargets(targets) {
503
+ this.targets = targets;
597
504
  }
598
505
  /**
599
- * Async versioning strategy (each package gets its own version)
506
+ * Process packages based on targeting criteria
600
507
  */
601
- async asyncStrategy(cliTargets = []) {
602
- if (cliTargets.length > 0) {
603
- await this.asyncTargetedStrategy(cliTargets);
604
- return;
605
- }
606
- const {
607
- commitMessage = "chore(release): ${version}",
608
- // Align with test expectations
609
- skipHooks
610
- } = this.config;
611
- let pkgsResult;
612
- try {
613
- pkgsResult = getPackagesSync(cwd3());
614
- if (!pkgsResult || !pkgsResult.packages) {
615
- throw new Error("Failed to get packages information");
616
- }
617
- } catch (error) {
618
- log("error", "Failed to get packages information");
619
- console.error(error);
620
- exit(1);
621
- return;
622
- }
623
- const pkgsToProcess = await this.processPackages(pkgsResult.packages, cliTargets);
624
- if (pkgsToProcess.length === 0) {
625
- log("info", "No packages to process based on changes and targets");
626
- return;
627
- }
628
- const formattedCommitMessage = commitMessage;
629
- try {
630
- await gitProcess({
631
- files: pkgsToProcess,
632
- nextTag: "",
633
- // No tag for default async
634
- commitMessage: formattedCommitMessage,
635
- skipHooks,
636
- dryRun: this.config.dryRun
637
- });
638
- if (!this.config.dryRun) {
639
- log("success", `Created version commit for ${pkgsToProcess.length} package(s)`);
640
- }
641
- } catch (error) {
642
- log("error", "Failed to create version commit");
643
- console.error(error);
644
- exit(1);
645
- }
646
- }
647
- // --- NEW METHOD for Async + Targeted ---
648
- async asyncTargetedStrategy(cliTargets) {
508
+ async processPackages(packages) {
649
509
  var _a;
650
- const {
651
- tagPrefix,
652
- skip,
653
- dryRun,
654
- skipHooks,
655
- commitMessage: commitMessageTemplate
656
- } = this.config;
510
+ const tags = [];
657
511
  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);
512
+ const tagPrefix = this.tagPrefix;
513
+ if (!packages || !Array.isArray(packages)) {
514
+ log("Invalid packages data provided. Expected array of packages.", "error");
515
+ return { updatedPackages: [], tags: [] };
669
516
  }
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.`);
517
+ const pkgsToConsider = packages.filter((pkg) => {
518
+ var _a2;
519
+ const pkgName = pkg.packageJson.name;
520
+ if ((_a2 = this.skip) == null ? void 0 : _a2.includes(pkgName)) {
521
+ log(`Skipping package ${pkgName} as it's in the skip list.`, "info");
673
522
  return false;
674
523
  }
675
- const isTargeted = cliTargets.includes(pkg.packageJson.name);
524
+ if (!this.targets || this.targets.length === 0) {
525
+ return true;
526
+ }
527
+ const isTargeted = this.targets.includes(pkgName);
676
528
  if (!isTargeted) {
529
+ log(`Package ${pkgName} not in target list, skipping.`, "info");
677
530
  }
678
531
  return isTargeted;
679
532
  });
680
- log("info", `Found ${pkgsToConsider.length} targeted package(s) to process after filtering.`);
533
+ log(`Found ${pkgsToConsider.length} targeted package(s) to process after filtering.`, "info");
681
534
  if (pkgsToConsider.length === 0) {
682
- log("info", "No matching targeted packages found to process.");
683
- return;
535
+ log("No matching targeted packages found to process.", "info");
536
+ return { updatedPackages: [], tags: [] };
684
537
  }
685
538
  for (const pkg of pkgsToConsider) {
686
539
  const name = pkg.packageJson.name;
687
540
  const pkgPath = pkg.dir;
688
541
  const prefix = formatTagPrefix(tagPrefix);
689
- const latestTag = await getLatestTag();
690
- const nextVersion = await this.calculateVersion({
542
+ const latestTagResult = await this.getLatestTag();
543
+ const latestTag = latestTagResult || "";
544
+ const nextVersion = await calculateVersion(this.fullConfig, {
691
545
  latestTag,
692
546
  tagPrefix: prefix,
693
547
  path: pkgPath,
@@ -700,111 +554,401 @@ var VersionEngine = class {
700
554
  if (!nextVersion) {
701
555
  continue;
702
556
  }
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
- );
557
+ updatePackageVersion(path.join(pkgPath, "package.json"), nextVersion);
558
+ const packageTag = formatTag(nextVersion, tagPrefix);
713
559
  const tagMessage = `chore(release): ${name} ${nextVersion}`;
714
- if (!dryRun) {
560
+ addTag(packageTag);
561
+ tags.push(packageTag);
562
+ if (!this.dryRun) {
715
563
  try {
716
564
  await createGitTag({ tag: packageTag, message: tagMessage });
717
- log("success", `Created tag: ${packageTag}`);
565
+ log(`Created tag: ${packageTag}`, "success");
718
566
  } catch (tagError) {
719
567
  log(
720
- "error",
721
- `Failed to create tag ${packageTag} for ${name}: ${tagError.message}`
568
+ `Failed to create tag ${packageTag} for ${name}: ${tagError.message}`,
569
+ "error"
722
570
  );
723
- log("error", tagError.stack || "No stack trace available");
571
+ log(tagError.stack || "No stack trace available", "error");
724
572
  }
725
573
  } else {
726
- log("info", `[DRY RUN] Would create tag: ${packageTag}`);
574
+ log(`[DRY RUN] Would create tag: ${packageTag}`, "info");
727
575
  }
728
576
  updatedPackagesInfo.push({ name, version: nextVersion, path: pkgPath });
729
577
  }
730
578
  if (updatedPackagesInfo.length === 0) {
731
- log("info", "No targeted packages required a version update.");
732
- return;
579
+ log("No targeted packages required a version update.", "info");
580
+ return { updatedPackages: [], tags };
733
581
  }
734
582
  const filesToCommit = updatedPackagesInfo.map((info) => path.join(info.path, "package.json"));
735
583
  const packageNames = updatedPackagesInfo.map((p) => p.name).join(", ");
736
584
  const representativeVersion = ((_a = updatedPackagesInfo[0]) == null ? void 0 : _a.version) || "multiple";
737
- let commitMessage = commitMessageTemplate || "chore(release): publish packages";
585
+ let commitMessage = this.commitMessageTemplate || "chore(release): publish packages";
738
586
  if (updatedPackagesInfo.length === 1 && commitMessage.includes("${version}")) {
739
587
  commitMessage = formatCommitMessage(commitMessage, representativeVersion);
740
588
  } else {
741
589
  commitMessage = `chore(release): ${packageNames} ${representativeVersion}`;
742
590
  }
743
591
  commitMessage += " [skip-ci]";
744
- if (!dryRun) {
592
+ setCommitMessage(commitMessage);
593
+ if (!this.dryRun) {
745
594
  try {
746
595
  await gitAdd(filesToCommit);
747
- await gitCommit({ message: commitMessage, skipHooks });
748
- log("success", `Created commit for targeted release: ${packageNames}`);
596
+ await gitCommit({ message: commitMessage, skipHooks: this.skipHooks });
597
+ log(`Created commit for targeted release: ${packageNames}`, "success");
749
598
  } catch (commitError) {
750
- log("error", "Failed to create commit for targeted release.");
599
+ log("Failed to create commit for targeted release.", "error");
751
600
  console.error(commitError);
752
601
  exit(1);
753
602
  }
754
603
  } else {
755
- log("info", "[DRY RUN] Would add files:");
604
+ log("[DRY RUN] Would add files:", "info");
756
605
  for (const file of filesToCommit) {
757
- log("info", ` - ${file}`);
606
+ log(` - ${file}`, "info");
758
607
  }
759
- log("info", `[DRY RUN] Would commit with message: "${commitMessage}"`);
608
+ log(`[DRY RUN] Would commit with message: "${commitMessage}"`, "info");
760
609
  }
610
+ return {
611
+ updatedPackages: updatedPackagesInfo,
612
+ commitMessage,
613
+ tags
614
+ };
615
+ }
616
+ };
617
+
618
+ // src/core/versionStrategies.ts
619
+ function shouldProcessPackage(pkg, config, targets = []) {
620
+ var _a;
621
+ const pkgName = pkg.packageJson.name;
622
+ if ((_a = config.skip) == null ? void 0 : _a.includes(pkgName)) {
623
+ return false;
624
+ }
625
+ if (!targets || targets.length === 0) {
626
+ return true;
627
+ }
628
+ return targets.includes(pkgName);
629
+ }
630
+ function createSyncedStrategy(config) {
631
+ return async (packages) => {
632
+ try {
633
+ const {
634
+ tagPrefix,
635
+ baseBranch,
636
+ branchPattern,
637
+ commitMessage = "chore(release): v${version}",
638
+ prereleaseIdentifier,
639
+ dryRun,
640
+ skipHooks
641
+ } = config;
642
+ const prefix = formatTagPrefix(tagPrefix || "v");
643
+ const latestTag = await getLatestTag();
644
+ const nextVersion = await calculateVersion(config, {
645
+ latestTag,
646
+ tagPrefix: prefix,
647
+ branchPattern,
648
+ baseBranch,
649
+ prereleaseIdentifier
650
+ });
651
+ if (!nextVersion) {
652
+ log("No version change needed", "info");
653
+ return;
654
+ }
655
+ const files = [];
656
+ const updatedPackages = [];
657
+ try {
658
+ const rootPkgPath = path2.join(packages.root, "package.json");
659
+ if (fs3.existsSync(rootPkgPath)) {
660
+ updatePackageVersion(rootPkgPath, nextVersion);
661
+ files.push(rootPkgPath);
662
+ updatedPackages.push("root");
663
+ }
664
+ } catch (_error) {
665
+ log("Failed to update root package.json", "error");
666
+ }
667
+ for (const pkg of packages.packages) {
668
+ if (!shouldProcessPackage(pkg, config)) {
669
+ continue;
670
+ }
671
+ const packageJsonPath = path2.join(pkg.dir, "package.json");
672
+ updatePackageVersion(packageJsonPath, nextVersion);
673
+ files.push(packageJsonPath);
674
+ updatedPackages.push(pkg.packageJson.name);
675
+ }
676
+ if (updatedPackages.length > 0) {
677
+ log(`Updated ${updatedPackages.length} package(s) to version ${nextVersion}`, "success");
678
+ } else {
679
+ log("No packages were updated", "warning");
680
+ return;
681
+ }
682
+ const nextTag = formatTag(nextVersion, tagPrefix || "v");
683
+ const formattedCommitMessage = formatCommitMessage(commitMessage, nextVersion);
684
+ await createGitCommitAndTag(files, nextTag, formattedCommitMessage, skipHooks, dryRun);
685
+ } catch (error) {
686
+ if (error instanceof VersionError || error instanceof GitError) {
687
+ log(`Synced Strategy failed: ${error.message} (${error.code || "UNKNOWN"})`, "error");
688
+ } else {
689
+ const errorMessage = error instanceof Error ? error.message : String(error);
690
+ log(`Synced Strategy failed: ${errorMessage}`, "error");
691
+ }
692
+ throw error;
693
+ }
694
+ };
695
+ }
696
+ function createSingleStrategy(config) {
697
+ return async (packages) => {
698
+ try {
699
+ const {
700
+ packages: configPackages,
701
+ tagPrefix,
702
+ commitMessage = "chore(release): ${version}",
703
+ dryRun,
704
+ skipHooks
705
+ } = config;
706
+ if (!configPackages || configPackages.length !== 1) {
707
+ throw createVersionError(
708
+ "INVALID_CONFIG" /* INVALID_CONFIG */,
709
+ "Single mode requires exactly one package name"
710
+ );
711
+ }
712
+ const packageName = configPackages[0];
713
+ const pkg = packages.packages.find((p) => p.packageJson.name === packageName);
714
+ if (!pkg) {
715
+ throw createVersionError("PACKAGE_NOT_FOUND" /* PACKAGE_NOT_FOUND */, packageName);
716
+ }
717
+ const pkgPath = pkg.dir;
718
+ const prefix = formatTagPrefix(tagPrefix || "v");
719
+ const latestTag = await getLatestTag();
720
+ let nextVersion = void 0;
721
+ try {
722
+ nextVersion = await calculateVersion(config, {
723
+ latestTag,
724
+ tagPrefix: prefix,
725
+ path: pkgPath,
726
+ name: packageName
727
+ });
728
+ } catch (error) {
729
+ const errorMessage = error instanceof Error ? error.message : String(error);
730
+ throw createVersionError("VERSION_CALCULATION_ERROR" /* VERSION_CALCULATION_ERROR */, errorMessage);
731
+ }
732
+ if (nextVersion === void 0 || nextVersion === "") {
733
+ log(`No version change needed for ${packageName}`, "info");
734
+ return;
735
+ }
736
+ const packageJsonPath = path2.join(pkgPath, "package.json");
737
+ updatePackageVersion(packageJsonPath, nextVersion);
738
+ log(`Updated package ${packageName} to version ${nextVersion}`, "success");
739
+ const nextTag = formatTag(nextVersion, tagPrefix || "v");
740
+ const formattedCommitMessage = formatCommitMessage(commitMessage, nextVersion);
741
+ await createGitCommitAndTag(
742
+ [packageJsonPath],
743
+ nextTag,
744
+ formattedCommitMessage,
745
+ skipHooks,
746
+ dryRun
747
+ );
748
+ } catch (error) {
749
+ if (error instanceof VersionError || error instanceof GitError) {
750
+ log(
751
+ `Single Package Strategy failed: ${error.message} (${error.code || "UNKNOWN"})`,
752
+ "error"
753
+ );
754
+ } else {
755
+ const errorMessage = error instanceof Error ? error.message : String(error);
756
+ log(`Single Package Strategy failed: ${errorMessage}`, "error");
757
+ }
758
+ throw error;
759
+ }
760
+ };
761
+ }
762
+ function createAsyncStrategy(config) {
763
+ const dependencies = {
764
+ getLatestTag
765
+ };
766
+ const processorOptions = {
767
+ skip: config.skip || [],
768
+ targets: config.packages || [],
769
+ tagPrefix: config.tagPrefix || "v",
770
+ commitMessageTemplate: config.commitMessage || "",
771
+ dryRun: config.dryRun || false,
772
+ skipHooks: config.skipHooks || false,
773
+ getLatestTag: dependencies.getLatestTag,
774
+ fullConfig: config,
775
+ config: {
776
+ branchPattern: config.branchPattern || [],
777
+ baseBranch: config.baseBranch || "main",
778
+ prereleaseIdentifier: config.prereleaseIdentifier,
779
+ forceType: config.forceType
780
+ }
781
+ };
782
+ const packageProcessor = new PackageProcessor(processorOptions);
783
+ return async (packages, targets = []) => {
784
+ try {
785
+ const targetPackages = targets.length > 0 ? targets : config.packages || [];
786
+ packageProcessor.setTargets(targetPackages);
787
+ if (targetPackages.length > 0) {
788
+ log(`Processing targeted packages: ${targetPackages.join(", ")}`, "info");
789
+ } else {
790
+ log("No targets specified, processing all non-skipped packages", "info");
791
+ }
792
+ const result = await packageProcessor.processPackages(packages.packages);
793
+ if (result.updatedPackages.length === 0) {
794
+ log("No packages required a version update.", "info");
795
+ } else {
796
+ const packageNames = result.updatedPackages.map((p) => p.name).join(", ");
797
+ log(`Updated ${result.updatedPackages.length} package(s): ${packageNames}`, "success");
798
+ if (result.tags.length > 0) {
799
+ log(`Created ${result.tags.length} tag(s): ${result.tags.join(", ")}`, "success");
800
+ }
801
+ if (result.commitMessage) {
802
+ log(`Created commit with message: "${result.commitMessage}"`, "success");
803
+ }
804
+ }
805
+ } catch (error) {
806
+ if (error instanceof VersionError || error instanceof GitError) {
807
+ log(`Async Strategy failed: ${error.message} (${error.code || "UNKNOWN"})`, "error");
808
+ } else {
809
+ const errorMessage = error instanceof Error ? error.message : String(error);
810
+ log(`Async Strategy failed: ${errorMessage}`, "error");
811
+ }
812
+ throw error;
813
+ }
814
+ };
815
+ }
816
+ function createStrategy(config) {
817
+ var _a;
818
+ if (config.synced) {
819
+ return createSyncedStrategy(config);
820
+ }
821
+ if (((_a = config.packages) == null ? void 0 : _a.length) === 1) {
822
+ return createSingleStrategy(config);
823
+ }
824
+ return createAsyncStrategy(config);
825
+ }
826
+ function createStrategyMap(config) {
827
+ return {
828
+ synced: createSyncedStrategy(config),
829
+ single: createSingleStrategy(config),
830
+ async: createAsyncStrategy(config)
831
+ };
832
+ }
833
+
834
+ // src/core/versionEngine.ts
835
+ var VersionEngine = class {
836
+ config;
837
+ jsonMode;
838
+ workspaceCache = null;
839
+ strategies;
840
+ currentStrategy;
841
+ constructor(config, jsonMode = false) {
842
+ if (!config) {
843
+ throw createVersionError("CONFIG_REQUIRED" /* CONFIG_REQUIRED */);
844
+ }
845
+ if (!config.preset) {
846
+ config.preset = "conventional-commits";
847
+ log("No preset specified, using default: conventional-commits", "warning");
848
+ }
849
+ this.config = config;
850
+ this.jsonMode = jsonMode;
851
+ this.strategies = createStrategyMap(config);
852
+ this.currentStrategy = createStrategy(config);
853
+ }
854
+ /**
855
+ * Get workspace packages information - with caching for performance
856
+ */
857
+ async getWorkspacePackages() {
858
+ try {
859
+ if (this.workspaceCache) {
860
+ return this.workspaceCache;
861
+ }
862
+ const pkgsResult = getPackagesSync(cwd4());
863
+ if (!pkgsResult || !pkgsResult.packages) {
864
+ throw createVersionError("PACKAGES_NOT_FOUND" /* PACKAGES_NOT_FOUND */);
865
+ }
866
+ this.workspaceCache = pkgsResult;
867
+ return pkgsResult;
868
+ } catch (error) {
869
+ const errorMessage = error instanceof Error ? error.message : String(error);
870
+ log(`Failed to get packages information: ${errorMessage}`, "error");
871
+ console.error(error);
872
+ throw createVersionError("WORKSPACE_ERROR" /* WORKSPACE_ERROR */, errorMessage);
873
+ }
874
+ }
875
+ /**
876
+ * Run the current strategy
877
+ * @param targets Optional package targets to process (only used by async strategy)
878
+ */
879
+ async run(targets = []) {
880
+ try {
881
+ const packages = await this.getWorkspacePackages();
882
+ return this.currentStrategy(packages, targets);
883
+ } catch (error) {
884
+ if (error instanceof VersionError || error instanceof GitError) {
885
+ log(`Version engine failed: ${error.message} (${error.code || "UNKNOWN"})`, "error");
886
+ } else {
887
+ const errorMessage = error instanceof Error ? error.message : String(error);
888
+ log(`Version engine failed: ${errorMessage}`, "error");
889
+ }
890
+ throw error;
891
+ }
892
+ }
893
+ /**
894
+ * Change the current strategy
895
+ * @param strategyType The strategy type to use: 'synced', 'single', or 'async'
896
+ */
897
+ setStrategy(strategyType) {
898
+ this.currentStrategy = this.strategies[strategyType];
761
899
  }
762
900
  };
763
901
 
764
902
  // src/index.ts
765
903
  async function run() {
766
- printFiglet();
767
904
  const program = new Command();
768
905
  program.name("package-versioner").description(
769
- "Automated semantic versioning based on Git history and conventional commits. Supports monorepos with synchronized or independent package versioning strategies."
770
- ).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(
771
- "--prerelease <identifier>",
772
- "Create a prerelease version with the specified identifier"
773
- ).option(
774
- "-t, --target <targets>",
775
- "Comma-separated list of package names to target (only for async strategy)"
776
- ).parse(process.argv);
906
+ "A lightweight yet powerful CLI tool for automated semantic versioning based on Git history and conventional commits."
907
+ ).version(process.env.npm_package_version || "0.0.0").option(
908
+ "-c, --config <path>",
909
+ "Path to config file (defaults to version.config.json in current directory)"
910
+ ).option("-d, --dry-run", "Dry run (no changes made)", false).option("-b, --bump <type>", "Force specific bump type (patch|minor|major)").option("-p, --prerelease [identifier]", "Create prerelease version").option("-s, --synced", "Force synchronized versioning across all packages").option("-j, --json", "Output results as JSON", false).option("-t, --target <packages>", "Comma-delimited list of package names to target").parse(process.argv);
777
911
  const options = program.opts();
912
+ if (options.json) {
913
+ enableJsonOutput(options.dryRun);
914
+ }
778
915
  try {
779
916
  const config = await loadConfig(options.config);
780
- log("info", `Loaded configuration from ${options.config || "version.config.json"}`);
917
+ log(`Loaded configuration from ${options.config || "version.config.json"}`, "info");
781
918
  if (options.dryRun) config.dryRun = true;
782
919
  if (options.synced) config.synced = true;
783
920
  if (options.bump) config.forceType = options.bump;
784
921
  if (options.prerelease)
785
922
  config.prereleaseIdentifier = options.prerelease === true ? "rc" : options.prerelease;
786
923
  const cliTargets = options.target ? options.target.split(",").map((t) => t.trim()) : [];
787
- const engine = new VersionEngine(config);
924
+ const engine = new VersionEngine(config, !!options.json);
788
925
  if (config.synced) {
789
- log("info", "Using synced versioning strategy.");
790
- await engine.syncedStrategy();
926
+ log("Using synced versioning strategy.", "info");
927
+ engine.setStrategy("synced");
928
+ await engine.run();
791
929
  } else if (config.packages && config.packages.length === 1) {
792
- log("info", "Using single package versioning strategy.");
930
+ log("Using single package versioning strategy.", "info");
793
931
  if (cliTargets.length > 0) {
794
- log("warning", "--target flag is ignored for single package strategy.");
932
+ log("--target flag is ignored for single package strategy.", "warning");
795
933
  }
796
- await engine.singleStrategy();
934
+ engine.setStrategy("single");
935
+ await engine.run();
797
936
  } else {
798
- log("info", "Using async versioning strategy.");
937
+ log("Using async versioning strategy.", "info");
799
938
  if (cliTargets.length > 0) {
800
- log("info", `Targeting specific packages: ${cliTargets.join(", ")}`);
939
+ log(`Targeting specific packages: ${cliTargets.join(", ")}`, "info");
801
940
  }
802
- await engine.asyncStrategy(cliTargets);
941
+ engine.setStrategy("async");
942
+ await engine.run(cliTargets);
803
943
  }
804
- log("success", "Versioning process completed.");
944
+ log("Versioning process completed.", "success");
945
+ printJsonOutput();
805
946
  } catch (error) {
806
- log("error", error instanceof Error ? error.message : String(error));
947
+ log(error instanceof Error ? error.message : String(error), "error");
807
948
  process.exit(1);
808
949
  }
809
950
  }
810
- run();
951
+ run().catch((error) => {
952
+ console.error("Fatal error:", error);
953
+ process.exit(1);
954
+ });