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