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