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