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