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