@codluv/versionguard 0.2.0 → 0.4.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/calver.d.ts +65 -22
- package/dist/calver.d.ts.map +1 -1
- package/dist/chunks/{index-BwE_OaV3.js → index-B3R60bYJ.js} +913 -138
- package/dist/chunks/index-B3R60bYJ.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +258 -22
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/feedback/index.d.ts +1 -1
- package/dist/feedback/index.d.ts.map +1 -1
- package/dist/fix/index.d.ts +7 -2
- package/dist/fix/index.d.ts.map +1 -1
- package/dist/guard.d.ts +45 -1
- package/dist/guard.d.ts.map +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -23
- package/dist/init-wizard.d.ts +49 -0
- package/dist/init-wizard.d.ts.map +1 -0
- package/dist/project.d.ts +54 -10
- package/dist/project.d.ts.map +1 -1
- package/dist/sources/git-tag.d.ts +52 -0
- package/dist/sources/git-tag.d.ts.map +1 -0
- package/dist/sources/index.d.ts +15 -0
- package/dist/sources/index.d.ts.map +1 -0
- package/dist/sources/json.d.ts +53 -0
- package/dist/sources/json.d.ts.map +1 -0
- package/dist/sources/provider.d.ts +25 -0
- package/dist/sources/provider.d.ts.map +1 -0
- package/dist/sources/regex.d.ts +56 -0
- package/dist/sources/regex.d.ts.map +1 -0
- package/dist/sources/resolve.d.ts +54 -0
- package/dist/sources/resolve.d.ts.map +1 -0
- package/dist/sources/toml.d.ts +60 -0
- package/dist/sources/toml.d.ts.map +1 -0
- package/dist/sources/utils.d.ts +73 -0
- package/dist/sources/utils.d.ts.map +1 -0
- package/dist/sources/version-file.d.ts +51 -0
- package/dist/sources/version-file.d.ts.map +1 -0
- package/dist/sources/yaml.d.ts +53 -0
- package/dist/sources/yaml.d.ts.map +1 -0
- package/dist/tag/index.d.ts.map +1 -1
- package/dist/types.d.ts +138 -5
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -2
- package/dist/chunks/index-BwE_OaV3.js.map +0 -1
|
@@ -1,42 +1,88 @@
|
|
|
1
1
|
import * as childProcess from "node:child_process";
|
|
2
|
-
import { execSync } from "node:child_process";
|
|
2
|
+
import { execFileSync, execSync } from "node:child_process";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
|
+
import { parse as parse$2 } from "smol-toml";
|
|
6
|
+
import * as yaml from "js-yaml";
|
|
5
7
|
import { globSync } from "glob";
|
|
6
8
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
|
|
9
|
+
const VALID_TOKENS = /* @__PURE__ */ new Set([
|
|
10
|
+
"YYYY",
|
|
11
|
+
"YY",
|
|
12
|
+
"0Y",
|
|
13
|
+
"MM",
|
|
14
|
+
"M",
|
|
15
|
+
"0M",
|
|
16
|
+
"WW",
|
|
17
|
+
"0W",
|
|
18
|
+
"DD",
|
|
19
|
+
"D",
|
|
20
|
+
"0D",
|
|
21
|
+
"MICRO",
|
|
22
|
+
"PATCH"
|
|
23
|
+
]);
|
|
24
|
+
const YEAR_TOKENS = /* @__PURE__ */ new Set(["YYYY", "YY", "0Y"]);
|
|
25
|
+
const MONTH_TOKENS = /* @__PURE__ */ new Set(["MM", "M", "0M"]);
|
|
26
|
+
const WEEK_TOKENS = /* @__PURE__ */ new Set(["WW", "0W"]);
|
|
27
|
+
const DAY_TOKENS = /* @__PURE__ */ new Set(["DD", "D", "0D"]);
|
|
28
|
+
const COUNTER_TOKENS = /* @__PURE__ */ new Set(["MICRO", "PATCH"]);
|
|
29
|
+
function isValidCalVerFormat(formatStr) {
|
|
30
|
+
const tokens = formatStr.split(".");
|
|
31
|
+
if (tokens.length < 2) return false;
|
|
32
|
+
if (!tokens.every((t) => VALID_TOKENS.has(t))) return false;
|
|
33
|
+
if (!YEAR_TOKENS.has(tokens[0])) return false;
|
|
34
|
+
const hasWeek = tokens.some((t) => WEEK_TOKENS.has(t));
|
|
35
|
+
const hasMonthOrDay = tokens.some((t) => MONTH_TOKENS.has(t) || DAY_TOKENS.has(t));
|
|
36
|
+
if (hasWeek && hasMonthOrDay) return false;
|
|
37
|
+
const counterIndex = tokens.findIndex((t) => COUNTER_TOKENS.has(t));
|
|
38
|
+
if (counterIndex !== -1 && counterIndex !== tokens.length - 1) return false;
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
8
41
|
function parseFormat(calverFormat) {
|
|
9
|
-
const
|
|
42
|
+
const tokens = calverFormat.split(".");
|
|
10
43
|
const result = {
|
|
11
|
-
year:
|
|
12
|
-
month: parts[1]
|
|
44
|
+
year: tokens[0]
|
|
13
45
|
};
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
46
|
+
for (let i = 1; i < tokens.length; i++) {
|
|
47
|
+
const token = tokens[i];
|
|
48
|
+
if (MONTH_TOKENS.has(token)) {
|
|
49
|
+
result.month = token;
|
|
50
|
+
} else if (WEEK_TOKENS.has(token)) {
|
|
51
|
+
result.week = token;
|
|
52
|
+
} else if (DAY_TOKENS.has(token)) {
|
|
53
|
+
result.day = token;
|
|
54
|
+
} else if (COUNTER_TOKENS.has(token)) {
|
|
55
|
+
result.counter = token;
|
|
56
|
+
}
|
|
21
57
|
}
|
|
22
58
|
return result;
|
|
23
59
|
}
|
|
60
|
+
const MODIFIER_PATTERN = "(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?";
|
|
24
61
|
function tokenPattern(token) {
|
|
25
62
|
switch (token) {
|
|
26
63
|
case "YYYY":
|
|
27
|
-
return "(\\d{
|
|
64
|
+
return "([1-9]\\d{3})";
|
|
28
65
|
case "YY":
|
|
29
|
-
return "(\\d{
|
|
30
|
-
case "
|
|
31
|
-
|
|
32
|
-
return "(\\d{2})";
|
|
66
|
+
return "(\\d{1,3})";
|
|
67
|
+
case "0Y":
|
|
68
|
+
return "(\\d{2,3})";
|
|
33
69
|
case "MM":
|
|
34
|
-
case "DD":
|
|
35
70
|
case "M":
|
|
71
|
+
return "([1-9]|1[0-2])";
|
|
72
|
+
case "0M":
|
|
73
|
+
return "(0[1-9]|1[0-2])";
|
|
74
|
+
case "WW":
|
|
75
|
+
return "([1-9]|[1-4]\\d|5[0-3])";
|
|
76
|
+
case "0W":
|
|
77
|
+
return "(0[1-9]|[1-4]\\d|5[0-3])";
|
|
78
|
+
case "DD":
|
|
36
79
|
case "D":
|
|
37
|
-
return "(\\d
|
|
80
|
+
return "([1-9]|[12]\\d|3[01])";
|
|
81
|
+
case "0D":
|
|
82
|
+
return "(0[1-9]|[12]\\d|3[01])";
|
|
83
|
+
case "MICRO":
|
|
38
84
|
case "PATCH":
|
|
39
|
-
return "(\\d
|
|
85
|
+
return "(0|[1-9]\\d*)";
|
|
40
86
|
default:
|
|
41
87
|
throw new Error(`Unsupported CalVer token: ${token}`);
|
|
42
88
|
}
|
|
@@ -44,7 +90,7 @@ function tokenPattern(token) {
|
|
|
44
90
|
function getRegexForFormat(calverFormat) {
|
|
45
91
|
const tokens = calverFormat.split(".");
|
|
46
92
|
const pattern = tokens.map(tokenPattern).join("\\.");
|
|
47
|
-
return new RegExp(`^${pattern}$`);
|
|
93
|
+
return new RegExp(`^${pattern}${MODIFIER_PATTERN}$`);
|
|
48
94
|
}
|
|
49
95
|
function parse$1(version, calverFormat) {
|
|
50
96
|
const match = version.match(getRegexForFormat(calverFormat));
|
|
@@ -52,28 +98,44 @@ function parse$1(version, calverFormat) {
|
|
|
52
98
|
return null;
|
|
53
99
|
}
|
|
54
100
|
const definition = parseFormat(calverFormat);
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
101
|
+
const yearToken = definition.year;
|
|
102
|
+
let year = Number.parseInt(match[1], 10);
|
|
103
|
+
if (yearToken === "YY" || yearToken === "0Y") {
|
|
104
|
+
year = 2e3 + year;
|
|
105
|
+
}
|
|
106
|
+
let cursor = 2;
|
|
107
|
+
let month;
|
|
58
108
|
let day;
|
|
59
109
|
let patch;
|
|
110
|
+
if (definition.month) {
|
|
111
|
+
month = Number.parseInt(match[cursor], 10);
|
|
112
|
+
cursor += 1;
|
|
113
|
+
}
|
|
114
|
+
if (definition.week) {
|
|
115
|
+
month = Number.parseInt(match[cursor], 10);
|
|
116
|
+
cursor += 1;
|
|
117
|
+
}
|
|
60
118
|
if (definition.day) {
|
|
61
119
|
day = Number.parseInt(match[cursor], 10);
|
|
62
120
|
cursor += 1;
|
|
63
121
|
}
|
|
64
|
-
if (definition.
|
|
122
|
+
if (definition.counter) {
|
|
65
123
|
patch = Number.parseInt(match[cursor], 10);
|
|
124
|
+
cursor += 1;
|
|
66
125
|
}
|
|
126
|
+
const modifierGroup = match[cursor];
|
|
127
|
+
const modifier = modifierGroup || void 0;
|
|
67
128
|
return {
|
|
68
129
|
year,
|
|
69
|
-
month,
|
|
130
|
+
month: month ?? 1,
|
|
70
131
|
day,
|
|
71
132
|
patch,
|
|
133
|
+
modifier,
|
|
72
134
|
format: calverFormat,
|
|
73
135
|
raw: version
|
|
74
136
|
};
|
|
75
137
|
}
|
|
76
|
-
function validate$2(version, calverFormat, preventFutureDates = true) {
|
|
138
|
+
function validate$2(version, calverFormat, preventFutureDates = true, schemeRules) {
|
|
77
139
|
const errors = [];
|
|
78
140
|
const parsed = parse$1(version, calverFormat);
|
|
79
141
|
if (!parsed) {
|
|
@@ -87,19 +149,26 @@ function validate$2(version, calverFormat, preventFutureDates = true) {
|
|
|
87
149
|
]
|
|
88
150
|
};
|
|
89
151
|
}
|
|
90
|
-
|
|
152
|
+
const definition = parseFormat(calverFormat);
|
|
153
|
+
if (definition.month && (parsed.month < 1 || parsed.month > 12)) {
|
|
91
154
|
errors.push({
|
|
92
155
|
message: `Invalid month: ${parsed.month}. Must be between 1 and 12.`,
|
|
93
156
|
severity: "error"
|
|
94
157
|
});
|
|
95
158
|
}
|
|
159
|
+
if (definition.week && (parsed.month < 1 || parsed.month > 53)) {
|
|
160
|
+
errors.push({
|
|
161
|
+
message: `Invalid week: ${parsed.month}. Must be between 1 and 53.`,
|
|
162
|
+
severity: "error"
|
|
163
|
+
});
|
|
164
|
+
}
|
|
96
165
|
if (parsed.day !== void 0) {
|
|
97
166
|
if (parsed.day < 1 || parsed.day > 31) {
|
|
98
167
|
errors.push({
|
|
99
168
|
message: `Invalid day: ${parsed.day}. Must be between 1 and 31.`,
|
|
100
169
|
severity: "error"
|
|
101
170
|
});
|
|
102
|
-
} else {
|
|
171
|
+
} else if (definition.month) {
|
|
103
172
|
const daysInMonth = new Date(parsed.year, parsed.month, 0).getDate();
|
|
104
173
|
if (parsed.day > daysInMonth) {
|
|
105
174
|
errors.push({
|
|
@@ -119,56 +188,83 @@ function validate$2(version, calverFormat, preventFutureDates = true) {
|
|
|
119
188
|
message: `Future year not allowed: ${parsed.year}. Current year is ${currentYear}.`,
|
|
120
189
|
severity: "error"
|
|
121
190
|
});
|
|
122
|
-
} else if (parsed.year === currentYear && parsed.month > currentMonth) {
|
|
191
|
+
} else if (definition.month && parsed.year === currentYear && parsed.month > currentMonth) {
|
|
123
192
|
errors.push({
|
|
124
193
|
message: `Future month not allowed: ${parsed.year}.${parsed.month}. Current month is ${currentMonth}.`,
|
|
125
194
|
severity: "error"
|
|
126
195
|
});
|
|
127
|
-
} else if (parsed.year === currentYear && parsed.month === currentMonth && parsed.day !== void 0 && parsed.day > currentDay) {
|
|
196
|
+
} else if (definition.month && parsed.year === currentYear && parsed.month === currentMonth && parsed.day !== void 0 && parsed.day > currentDay) {
|
|
128
197
|
errors.push({
|
|
129
198
|
message: `Future day not allowed: ${parsed.year}.${parsed.month}.${parsed.day}. Current day is ${currentDay}.`,
|
|
130
199
|
severity: "error"
|
|
131
200
|
});
|
|
132
201
|
}
|
|
133
202
|
}
|
|
203
|
+
if (parsed.modifier && schemeRules?.allowedModifiers) {
|
|
204
|
+
const baseModifier = parsed.modifier.replace(/[\d.]+$/, "") || parsed.modifier;
|
|
205
|
+
if (!schemeRules.allowedModifiers.includes(baseModifier)) {
|
|
206
|
+
errors.push({
|
|
207
|
+
message: `Modifier "${parsed.modifier}" is not allowed. Allowed: ${schemeRules.allowedModifiers.join(", ")}`,
|
|
208
|
+
severity: "error"
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (schemeRules?.maxNumericSegments) {
|
|
213
|
+
const segmentCount = calverFormat.split(".").length;
|
|
214
|
+
if (segmentCount > schemeRules.maxNumericSegments) {
|
|
215
|
+
errors.push({
|
|
216
|
+
message: `Format has ${segmentCount} segments, convention recommends ${schemeRules.maxNumericSegments} or fewer`,
|
|
217
|
+
severity: "warning"
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
134
221
|
return {
|
|
135
|
-
valid: errors.length === 0,
|
|
222
|
+
valid: errors.filter((e) => e.severity === "error").length === 0,
|
|
136
223
|
errors,
|
|
137
224
|
version: { type: "calver", version: parsed }
|
|
138
225
|
};
|
|
139
226
|
}
|
|
140
227
|
function formatToken(token, value) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
228
|
+
switch (token) {
|
|
229
|
+
case "0M":
|
|
230
|
+
case "0D":
|
|
231
|
+
case "0W":
|
|
232
|
+
case "0Y":
|
|
233
|
+
return String(token === "0Y" ? value % 100 : value).padStart(2, "0");
|
|
234
|
+
case "YY":
|
|
235
|
+
return String(value % 100).padStart(2, "0");
|
|
236
|
+
default:
|
|
237
|
+
return String(value);
|
|
146
238
|
}
|
|
147
|
-
return String(value);
|
|
148
239
|
}
|
|
149
240
|
function format$1(version) {
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
|
|
241
|
+
const definition = parseFormat(version.format);
|
|
242
|
+
const parts = [formatToken(definition.year, version.year)];
|
|
243
|
+
if (definition.month) {
|
|
244
|
+
parts.push(formatToken(definition.month, version.month));
|
|
245
|
+
}
|
|
246
|
+
if (definition.week) {
|
|
247
|
+
parts.push(formatToken(definition.week, version.month));
|
|
154
248
|
}
|
|
155
|
-
if (
|
|
156
|
-
|
|
249
|
+
if (definition.day) {
|
|
250
|
+
parts.push(formatToken(definition.day, version.day ?? 1));
|
|
251
|
+
}
|
|
252
|
+
if (definition.counter) {
|
|
253
|
+
parts.push(formatToken(definition.counter, version.patch ?? 0));
|
|
157
254
|
}
|
|
158
|
-
|
|
255
|
+
const base = parts.join(".");
|
|
256
|
+
return version.modifier ? `${base}-${version.modifier}` : base;
|
|
159
257
|
}
|
|
160
258
|
function getCurrentVersion(calverFormat, now = /* @__PURE__ */ new Date()) {
|
|
161
259
|
const definition = parseFormat(calverFormat);
|
|
162
|
-
const currentDay = now.getDate();
|
|
163
260
|
const base = {
|
|
164
261
|
year: now.getFullYear(),
|
|
165
262
|
month: now.getMonth() + 1,
|
|
166
|
-
day: definition.day ?
|
|
167
|
-
patch: definition.
|
|
263
|
+
day: definition.day ? now.getDate() : void 0,
|
|
264
|
+
patch: definition.counter ? 0 : void 0,
|
|
265
|
+
format: calverFormat
|
|
168
266
|
};
|
|
169
|
-
|
|
170
|
-
const patch = base.patch ?? 0;
|
|
171
|
-
return formatToken(definition.year, base.year).concat(`.${formatToken(definition.month, base.month)}`).concat(definition.day ? `.${formatToken(definition.day, day)}` : "").concat(definition.patch ? `.${patch}` : "");
|
|
267
|
+
return format$1(base);
|
|
172
268
|
}
|
|
173
269
|
function compare$1(a, b, calverFormat) {
|
|
174
270
|
const left = parse$1(a, calverFormat);
|
|
@@ -194,12 +290,11 @@ function increment$1(version, calverFormat) {
|
|
|
194
290
|
const next = {
|
|
195
291
|
...parsed
|
|
196
292
|
};
|
|
197
|
-
if (definition.
|
|
198
|
-
|
|
199
|
-
next.patch = patch + 1;
|
|
293
|
+
if (definition.counter) {
|
|
294
|
+
next.patch = (parsed.patch ?? 0) + 1;
|
|
200
295
|
} else {
|
|
201
296
|
next.patch = 0;
|
|
202
|
-
next.format = `${calverFormat}.
|
|
297
|
+
next.format = `${calverFormat}.MICRO`;
|
|
203
298
|
}
|
|
204
299
|
return format$1(next);
|
|
205
300
|
}
|
|
@@ -214,6 +309,7 @@ const calver = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
|
|
|
214
309
|
getNextVersions,
|
|
215
310
|
getRegexForFormat,
|
|
216
311
|
increment: increment$1,
|
|
312
|
+
isValidCalVerFormat,
|
|
217
313
|
parse: parse$1,
|
|
218
314
|
parseFormat,
|
|
219
315
|
validate: validate$2
|
|
@@ -245,7 +341,7 @@ function validateChangelog(changelogPath, version, strict = true, requireEntry =
|
|
|
245
341
|
errors.push("Changelog should include compare links at the bottom");
|
|
246
342
|
}
|
|
247
343
|
const versionHeaderMatch = content.match(
|
|
248
|
-
new RegExp(`## \\[${escapeRegExp(version)}\\] - ([^\r
|
|
344
|
+
new RegExp(`## \\[${escapeRegExp$1(version)}\\] - ([^\r
|
|
249
345
|
]+)`)
|
|
250
346
|
);
|
|
251
347
|
if (requireEntry && hasEntryForVersion) {
|
|
@@ -285,7 +381,7 @@ function addVersionEntry(changelogPath, version, date = (/* @__PURE__ */ new Dat
|
|
|
285
381
|
const updated = `${content.slice(0, insertIndex)}${block}${content.slice(insertIndex)}`;
|
|
286
382
|
fs.writeFileSync(changelogPath, updated, "utf-8");
|
|
287
383
|
}
|
|
288
|
-
function escapeRegExp(value) {
|
|
384
|
+
function escapeRegExp$1(value) {
|
|
289
385
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
290
386
|
}
|
|
291
387
|
const HOOK_NAMES$1 = ["pre-commit", "pre-push", "post-tag"];
|
|
@@ -342,6 +438,7 @@ function areHooksInstalled(cwd = process.cwd()) {
|
|
|
342
438
|
}
|
|
343
439
|
function generateHookScript(hookName) {
|
|
344
440
|
return `#!/bin/sh
|
|
441
|
+
# versionguard
|
|
345
442
|
# VersionGuard ${hookName} hook
|
|
346
443
|
# --no-install prevents accidentally downloading an unscoped package
|
|
347
444
|
# if @codluv/versionguard is not installed locally
|
|
@@ -353,6 +450,632 @@ if [ $status -ne 0 ]; then
|
|
|
353
450
|
fi
|
|
354
451
|
`;
|
|
355
452
|
}
|
|
453
|
+
class GitTagSource {
|
|
454
|
+
/** Human-readable provider name. */
|
|
455
|
+
name = "git-tag";
|
|
456
|
+
/** Empty string since git-tag has no manifest file. */
|
|
457
|
+
manifestFile = "";
|
|
458
|
+
/**
|
|
459
|
+
* Returns `true` when `cwd` is inside a Git repository.
|
|
460
|
+
*
|
|
461
|
+
* @param cwd - Project directory to check.
|
|
462
|
+
* @returns Whether a Git repository is found.
|
|
463
|
+
*/
|
|
464
|
+
exists(cwd) {
|
|
465
|
+
try {
|
|
466
|
+
execFileSync("git", ["rev-parse", "--git-dir"], {
|
|
467
|
+
cwd,
|
|
468
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
469
|
+
});
|
|
470
|
+
return true;
|
|
471
|
+
} catch {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Reads the version string from the latest Git tag.
|
|
477
|
+
*
|
|
478
|
+
* @param cwd - Project directory containing the Git repository.
|
|
479
|
+
* @returns The version string extracted from the latest version tag.
|
|
480
|
+
*/
|
|
481
|
+
getVersion(cwd) {
|
|
482
|
+
try {
|
|
483
|
+
const tag = this.describeVersionTag(cwd);
|
|
484
|
+
return tag.replace(/^v/, "");
|
|
485
|
+
} catch {
|
|
486
|
+
throw new Error("No version tags found. Create a tag first (e.g., git tag v0.1.0)");
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Creates a new annotated Git tag for the given version.
|
|
491
|
+
*
|
|
492
|
+
* @param version - Version string to tag.
|
|
493
|
+
* @param cwd - Project directory containing the Git repository.
|
|
494
|
+
*/
|
|
495
|
+
setVersion(version, cwd) {
|
|
496
|
+
const prefix = this.detectPrefix(cwd);
|
|
497
|
+
const tagName = `${prefix}${version}`;
|
|
498
|
+
execFileSync("git", ["tag", "-a", tagName, "-m", `Release ${version}`], {
|
|
499
|
+
cwd,
|
|
500
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
/** Try version-like tag patterns, fall back to any tag. */
|
|
504
|
+
describeVersionTag(cwd) {
|
|
505
|
+
try {
|
|
506
|
+
return execFileSync("git", ["describe", "--tags", "--abbrev=0", "--match", "v[0-9]*"], {
|
|
507
|
+
cwd,
|
|
508
|
+
encoding: "utf-8",
|
|
509
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
510
|
+
}).trim();
|
|
511
|
+
} catch {
|
|
512
|
+
}
|
|
513
|
+
try {
|
|
514
|
+
return execFileSync("git", ["describe", "--tags", "--abbrev=0", "--match", "[0-9]*"], {
|
|
515
|
+
cwd,
|
|
516
|
+
encoding: "utf-8",
|
|
517
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
518
|
+
}).trim();
|
|
519
|
+
} catch {
|
|
520
|
+
throw new Error("No version tags found");
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/** Detect whether existing tags use a `v` prefix or not. */
|
|
524
|
+
detectPrefix(cwd) {
|
|
525
|
+
try {
|
|
526
|
+
const tag = this.describeVersionTag(cwd);
|
|
527
|
+
return tag.startsWith("v") ? "v" : "";
|
|
528
|
+
} catch {
|
|
529
|
+
return "v";
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
function getNestedValue(obj, dotPath) {
|
|
534
|
+
let current = obj;
|
|
535
|
+
for (const key of dotPath.split(".")) {
|
|
536
|
+
if (current === null || typeof current !== "object") {
|
|
537
|
+
return void 0;
|
|
538
|
+
}
|
|
539
|
+
current = current[key];
|
|
540
|
+
}
|
|
541
|
+
return current;
|
|
542
|
+
}
|
|
543
|
+
function setNestedValue(obj, dotPath, value) {
|
|
544
|
+
const keys = dotPath.split(".");
|
|
545
|
+
let current = obj;
|
|
546
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
547
|
+
const next = current[keys[i]];
|
|
548
|
+
if (typeof next !== "object" || next === null) {
|
|
549
|
+
throw new Error(`Missing intermediate key '${keys.slice(0, i + 1).join(".")}' in manifest`);
|
|
550
|
+
}
|
|
551
|
+
current = next;
|
|
552
|
+
}
|
|
553
|
+
current[keys[keys.length - 1]] = value;
|
|
554
|
+
}
|
|
555
|
+
function escapeRegExp(value) {
|
|
556
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
557
|
+
}
|
|
558
|
+
class JsonVersionSource {
|
|
559
|
+
/** Human-readable provider name. */
|
|
560
|
+
name;
|
|
561
|
+
/** Filename of the JSON manifest (e.g. `'package.json'`). */
|
|
562
|
+
manifestFile;
|
|
563
|
+
/** Dotted key path to the version field within the JSON document. */
|
|
564
|
+
versionPath;
|
|
565
|
+
/**
|
|
566
|
+
* Creates a new JSON version source.
|
|
567
|
+
*
|
|
568
|
+
* @param manifestFile - JSON manifest filename.
|
|
569
|
+
* @param versionPath - Dotted key path to the version field.
|
|
570
|
+
*/
|
|
571
|
+
constructor(manifestFile = "package.json", versionPath = "version") {
|
|
572
|
+
this.name = manifestFile;
|
|
573
|
+
this.manifestFile = manifestFile;
|
|
574
|
+
this.versionPath = versionPath;
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Returns `true` when the manifest file exists in `cwd`.
|
|
578
|
+
*
|
|
579
|
+
* @param cwd - Project directory to check.
|
|
580
|
+
* @returns Whether the manifest file exists.
|
|
581
|
+
*/
|
|
582
|
+
exists(cwd) {
|
|
583
|
+
return fs.existsSync(path.join(cwd, this.manifestFile));
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Reads the version string from the JSON manifest.
|
|
587
|
+
*
|
|
588
|
+
* @param cwd - Project directory containing the manifest.
|
|
589
|
+
* @returns The version string extracted from the manifest.
|
|
590
|
+
*/
|
|
591
|
+
getVersion(cwd) {
|
|
592
|
+
const filePath = path.join(cwd, this.manifestFile);
|
|
593
|
+
if (!fs.existsSync(filePath)) {
|
|
594
|
+
throw new Error(`${this.manifestFile} not found in ${cwd}`);
|
|
595
|
+
}
|
|
596
|
+
const content = JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
597
|
+
const version = getNestedValue(content, this.versionPath);
|
|
598
|
+
if (typeof version !== "string" || version.length === 0) {
|
|
599
|
+
throw new Error(`No version field in ${this.manifestFile}`);
|
|
600
|
+
}
|
|
601
|
+
return version;
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Writes a version string to the JSON manifest, preserving indentation.
|
|
605
|
+
*
|
|
606
|
+
* @param version - Version string to write.
|
|
607
|
+
* @param cwd - Project directory containing the manifest.
|
|
608
|
+
*/
|
|
609
|
+
setVersion(version, cwd) {
|
|
610
|
+
const filePath = path.join(cwd, this.manifestFile);
|
|
611
|
+
if (!fs.existsSync(filePath)) {
|
|
612
|
+
throw new Error(`${this.manifestFile} not found in ${cwd}`);
|
|
613
|
+
}
|
|
614
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
615
|
+
const indentMatch = raw.match(/^(\s+)"/m);
|
|
616
|
+
const indent = indentMatch?.[1]?.length ?? 2;
|
|
617
|
+
const content = JSON.parse(raw);
|
|
618
|
+
setNestedValue(content, this.versionPath, version);
|
|
619
|
+
fs.writeFileSync(filePath, `${JSON.stringify(content, null, indent)}
|
|
620
|
+
`, "utf-8");
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
class RegexVersionSource {
|
|
624
|
+
/** Human-readable provider name. */
|
|
625
|
+
name;
|
|
626
|
+
/** Filename of the source manifest (e.g. `'setup.py'`). */
|
|
627
|
+
manifestFile;
|
|
628
|
+
/** Compiled regex used to locate the version string. */
|
|
629
|
+
versionRegex;
|
|
630
|
+
/**
|
|
631
|
+
* Creates a new regex version source.
|
|
632
|
+
*
|
|
633
|
+
* @param manifestFile - Source manifest filename.
|
|
634
|
+
* @param versionRegex - Regex string with at least one capture group for the version.
|
|
635
|
+
*/
|
|
636
|
+
constructor(manifestFile, versionRegex) {
|
|
637
|
+
this.name = manifestFile;
|
|
638
|
+
this.manifestFile = manifestFile;
|
|
639
|
+
try {
|
|
640
|
+
this.versionRegex = new RegExp(versionRegex, "m");
|
|
641
|
+
} catch (err) {
|
|
642
|
+
throw new Error(`Invalid version regex for ${manifestFile}: ${err.message}`);
|
|
643
|
+
}
|
|
644
|
+
if (!/\((?!\?)/.test(versionRegex)) {
|
|
645
|
+
throw new Error(`Version regex for ${manifestFile} must contain at least one capture group`);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Returns `true` when the manifest file exists in `cwd`.
|
|
650
|
+
*
|
|
651
|
+
* @param cwd - Project directory to check.
|
|
652
|
+
* @returns Whether the manifest file exists.
|
|
653
|
+
*/
|
|
654
|
+
exists(cwd) {
|
|
655
|
+
return fs.existsSync(path.join(cwd, this.manifestFile));
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Reads the version string from the source manifest using regex extraction.
|
|
659
|
+
*
|
|
660
|
+
* @param cwd - Project directory containing the manifest.
|
|
661
|
+
* @returns The version string captured by group 1 of the regex.
|
|
662
|
+
*/
|
|
663
|
+
getVersion(cwd) {
|
|
664
|
+
const filePath = path.join(cwd, this.manifestFile);
|
|
665
|
+
if (!fs.existsSync(filePath)) {
|
|
666
|
+
throw new Error(`${this.manifestFile} not found in ${cwd}`);
|
|
667
|
+
}
|
|
668
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
669
|
+
const match = content.match(this.versionRegex);
|
|
670
|
+
if (!match?.[1]) {
|
|
671
|
+
throw new Error(`No version match found in ${this.manifestFile}`);
|
|
672
|
+
}
|
|
673
|
+
return match[1];
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Writes a version string to the source manifest using position-based replacement.
|
|
677
|
+
*
|
|
678
|
+
* @param version - Version string to write.
|
|
679
|
+
* @param cwd - Project directory containing the manifest.
|
|
680
|
+
*/
|
|
681
|
+
setVersion(version, cwd) {
|
|
682
|
+
const filePath = path.join(cwd, this.manifestFile);
|
|
683
|
+
if (!fs.existsSync(filePath)) {
|
|
684
|
+
throw new Error(`${this.manifestFile} not found in ${cwd}`);
|
|
685
|
+
}
|
|
686
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
687
|
+
const match = this.versionRegex.exec(content);
|
|
688
|
+
if (!match || match.index === void 0) {
|
|
689
|
+
throw new Error(`No version match found in ${this.manifestFile}`);
|
|
690
|
+
}
|
|
691
|
+
const captureStart = match.index + match[0].indexOf(match[1]);
|
|
692
|
+
const captureEnd = captureStart + match[1].length;
|
|
693
|
+
const updated = content.slice(0, captureStart) + version + content.slice(captureEnd);
|
|
694
|
+
fs.writeFileSync(filePath, updated, "utf-8");
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
class TomlVersionSource {
|
|
698
|
+
/** Human-readable provider name. */
|
|
699
|
+
name;
|
|
700
|
+
/** Filename of the TOML manifest (e.g. `'Cargo.toml'`). */
|
|
701
|
+
manifestFile;
|
|
702
|
+
/** Dotted key path to the version field within the TOML document. */
|
|
703
|
+
versionPath;
|
|
704
|
+
/**
|
|
705
|
+
* Creates a new TOML version source.
|
|
706
|
+
*
|
|
707
|
+
* @param manifestFile - TOML manifest filename.
|
|
708
|
+
* @param versionPath - Dotted key path to the version field.
|
|
709
|
+
*/
|
|
710
|
+
constructor(manifestFile = "Cargo.toml", versionPath = "package.version") {
|
|
711
|
+
this.name = manifestFile;
|
|
712
|
+
this.manifestFile = manifestFile;
|
|
713
|
+
this.versionPath = versionPath;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Returns `true` when the manifest file exists in `cwd`.
|
|
717
|
+
*
|
|
718
|
+
* @param cwd - Project directory to check.
|
|
719
|
+
* @returns Whether the manifest file exists.
|
|
720
|
+
*/
|
|
721
|
+
exists(cwd) {
|
|
722
|
+
return fs.existsSync(path.join(cwd, this.manifestFile));
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Reads the version string from the TOML manifest.
|
|
726
|
+
*
|
|
727
|
+
* @param cwd - Project directory containing the manifest.
|
|
728
|
+
* @returns The version string extracted from the manifest.
|
|
729
|
+
*/
|
|
730
|
+
getVersion(cwd) {
|
|
731
|
+
const filePath = path.join(cwd, this.manifestFile);
|
|
732
|
+
if (!fs.existsSync(filePath)) {
|
|
733
|
+
throw new Error(`${this.manifestFile} not found in ${cwd}`);
|
|
734
|
+
}
|
|
735
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
736
|
+
const parsed = parse$2(content);
|
|
737
|
+
const version = getNestedValue(parsed, this.versionPath);
|
|
738
|
+
if (typeof version !== "string" || version.length === 0) {
|
|
739
|
+
throw new Error(`No version field at '${this.versionPath}' in ${this.manifestFile}`);
|
|
740
|
+
}
|
|
741
|
+
return version;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Writes a version string to the TOML manifest, preserving formatting.
|
|
745
|
+
*
|
|
746
|
+
* @param version - Version string to write.
|
|
747
|
+
* @param cwd - Project directory containing the manifest.
|
|
748
|
+
*/
|
|
749
|
+
setVersion(version, cwd) {
|
|
750
|
+
const filePath = path.join(cwd, this.manifestFile);
|
|
751
|
+
if (!fs.existsSync(filePath)) {
|
|
752
|
+
throw new Error(`${this.manifestFile} not found in ${cwd}`);
|
|
753
|
+
}
|
|
754
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
755
|
+
const sectionKey = this.getSectionKey();
|
|
756
|
+
const updated = replaceTomlVersion(content, sectionKey, version);
|
|
757
|
+
if (updated === content) {
|
|
758
|
+
throw new Error(`Could not find version field to update in ${this.manifestFile}`);
|
|
759
|
+
}
|
|
760
|
+
fs.writeFileSync(filePath, updated, "utf-8");
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Splits the dotted version path into a TOML section name and key name.
|
|
764
|
+
*
|
|
765
|
+
* @returns An object with `section` and `key` components.
|
|
766
|
+
*/
|
|
767
|
+
getSectionKey() {
|
|
768
|
+
const parts = this.versionPath.split(".");
|
|
769
|
+
if (parts.length === 1) {
|
|
770
|
+
return { section: "", key: parts[0] };
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
section: parts.slice(0, -1).join("."),
|
|
774
|
+
key: parts[parts.length - 1]
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function replaceTomlVersion(content, target, newVersion) {
|
|
779
|
+
const result = replaceInSection(content, target, newVersion);
|
|
780
|
+
if (result !== content) return result;
|
|
781
|
+
if (target.section) {
|
|
782
|
+
const dottedRegex = new RegExp(
|
|
783
|
+
`^(\\s*${escapeRegExp(target.section)}\\.${escapeRegExp(target.key)}\\s*=\\s*)(["'])([^"']*)(\\2)`,
|
|
784
|
+
"m"
|
|
785
|
+
);
|
|
786
|
+
const dottedResult = content.replace(dottedRegex, `$1$2${newVersion}$4`);
|
|
787
|
+
if (dottedResult !== content) return dottedResult;
|
|
788
|
+
}
|
|
789
|
+
if (target.section) {
|
|
790
|
+
const inlineRegex = new RegExp(
|
|
791
|
+
`^(\\s*${escapeRegExp(target.section)}\\s*=\\s*\\{[^}]*${escapeRegExp(target.key)}\\s*=\\s*)(["'])([^"']*)(\\2)`,
|
|
792
|
+
"m"
|
|
793
|
+
);
|
|
794
|
+
const inlineResult = content.replace(inlineRegex, `$1$2${newVersion}$4`);
|
|
795
|
+
if (inlineResult !== content) return inlineResult;
|
|
796
|
+
}
|
|
797
|
+
return content;
|
|
798
|
+
}
|
|
799
|
+
function replaceInSection(content, target, newVersion) {
|
|
800
|
+
const lines = content.split("\n");
|
|
801
|
+
const sectionHeader = target.section ? `[${target.section}]` : null;
|
|
802
|
+
let inSection = sectionHeader === null;
|
|
803
|
+
const versionRegex = new RegExp(`^(\\s*${escapeRegExp(target.key)}\\s*=\\s*)(["'])([^"']*)(\\2)`);
|
|
804
|
+
for (let i = 0; i < lines.length; i++) {
|
|
805
|
+
const trimmed = lines[i].trim();
|
|
806
|
+
if (sectionHeader !== null) {
|
|
807
|
+
if (trimmed === sectionHeader) {
|
|
808
|
+
inSection = true;
|
|
809
|
+
continue;
|
|
810
|
+
}
|
|
811
|
+
if (inSection && trimmed.startsWith("[") && trimmed !== sectionHeader) {
|
|
812
|
+
inSection = false;
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
if (inSection) {
|
|
817
|
+
const match = lines[i].match(versionRegex);
|
|
818
|
+
if (match) {
|
|
819
|
+
lines[i] = lines[i].replace(versionRegex, `$1$2${newVersion}$4`);
|
|
820
|
+
return lines.join("\n");
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return content;
|
|
825
|
+
}
|
|
826
|
+
class VersionFileSource {
|
|
827
|
+
/** Human-readable provider name. */
|
|
828
|
+
name;
|
|
829
|
+
/** Filename of the version file (e.g. `'VERSION'`). */
|
|
830
|
+
manifestFile;
|
|
831
|
+
/**
|
|
832
|
+
* Creates a new plain text version file source.
|
|
833
|
+
*
|
|
834
|
+
* @param manifestFile - Version filename.
|
|
835
|
+
*/
|
|
836
|
+
constructor(manifestFile = "VERSION") {
|
|
837
|
+
this.name = manifestFile;
|
|
838
|
+
this.manifestFile = manifestFile;
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* Returns `true` when the version file exists in `cwd`.
|
|
842
|
+
*
|
|
843
|
+
* @param cwd - Project directory to check.
|
|
844
|
+
* @returns Whether the version file exists.
|
|
845
|
+
*/
|
|
846
|
+
exists(cwd) {
|
|
847
|
+
return fs.existsSync(path.join(cwd, this.manifestFile));
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* Reads the version string from the plain text version file.
|
|
851
|
+
*
|
|
852
|
+
* @param cwd - Project directory containing the version file.
|
|
853
|
+
* @returns The version string from the first line of the file.
|
|
854
|
+
*/
|
|
855
|
+
getVersion(cwd) {
|
|
856
|
+
const filePath = path.join(cwd, this.manifestFile);
|
|
857
|
+
if (!fs.existsSync(filePath)) {
|
|
858
|
+
throw new Error(`${this.manifestFile} not found in ${cwd}`);
|
|
859
|
+
}
|
|
860
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
861
|
+
if (raw.includes("\0")) {
|
|
862
|
+
throw new Error(`${this.manifestFile} appears to be a binary file`);
|
|
863
|
+
}
|
|
864
|
+
const version = raw.split("\n")[0].trim();
|
|
865
|
+
if (version.length === 0) {
|
|
866
|
+
throw new Error(`${this.manifestFile} is empty`);
|
|
867
|
+
}
|
|
868
|
+
return version;
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Writes a version string to the plain text version file.
|
|
872
|
+
*
|
|
873
|
+
* @param version - Version string to write.
|
|
874
|
+
* @param cwd - Project directory containing the version file.
|
|
875
|
+
*/
|
|
876
|
+
setVersion(version, cwd) {
|
|
877
|
+
const filePath = path.join(cwd, this.manifestFile);
|
|
878
|
+
if (!fs.existsSync(filePath)) {
|
|
879
|
+
throw new Error(`${this.manifestFile} not found in ${cwd}`);
|
|
880
|
+
}
|
|
881
|
+
fs.writeFileSync(filePath, `${version}
|
|
882
|
+
`, "utf-8");
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
class YamlVersionSource {
|
|
886
|
+
/** Human-readable provider name. */
|
|
887
|
+
name;
|
|
888
|
+
/** Filename of the YAML manifest (e.g. `'pubspec.yaml'`). */
|
|
889
|
+
manifestFile;
|
|
890
|
+
/** Dotted key path to the version field within the YAML document. */
|
|
891
|
+
versionKey;
|
|
892
|
+
/**
|
|
893
|
+
* Creates a new YAML version source.
|
|
894
|
+
*
|
|
895
|
+
* @param manifestFile - YAML manifest filename.
|
|
896
|
+
* @param versionKey - Dotted key path to the version field.
|
|
897
|
+
*/
|
|
898
|
+
constructor(manifestFile = "pubspec.yaml", versionKey = "version") {
|
|
899
|
+
this.name = manifestFile;
|
|
900
|
+
this.manifestFile = manifestFile;
|
|
901
|
+
this.versionKey = versionKey;
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Returns `true` when the manifest file exists in `cwd`.
|
|
905
|
+
*
|
|
906
|
+
* @param cwd - Project directory to check.
|
|
907
|
+
* @returns Whether the manifest file exists.
|
|
908
|
+
*/
|
|
909
|
+
exists(cwd) {
|
|
910
|
+
return fs.existsSync(path.join(cwd, this.manifestFile));
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Reads the version string from the YAML manifest.
|
|
914
|
+
*
|
|
915
|
+
* @param cwd - Project directory containing the manifest.
|
|
916
|
+
* @returns The version string extracted from the manifest.
|
|
917
|
+
*/
|
|
918
|
+
getVersion(cwd) {
|
|
919
|
+
const filePath = path.join(cwd, this.manifestFile);
|
|
920
|
+
if (!fs.existsSync(filePath)) {
|
|
921
|
+
throw new Error(`${this.manifestFile} not found in ${cwd}`);
|
|
922
|
+
}
|
|
923
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
924
|
+
const parsed = yaml.load(content);
|
|
925
|
+
if (!parsed || typeof parsed !== "object") {
|
|
926
|
+
throw new Error(`Failed to parse ${this.manifestFile}`);
|
|
927
|
+
}
|
|
928
|
+
const version = getNestedValue(parsed, this.versionKey);
|
|
929
|
+
if (typeof version !== "string" || version.length === 0) {
|
|
930
|
+
if (typeof version === "number") {
|
|
931
|
+
return String(version);
|
|
932
|
+
}
|
|
933
|
+
throw new Error(`No version field in ${this.manifestFile}`);
|
|
934
|
+
}
|
|
935
|
+
return version;
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Writes a version string to the YAML manifest, preserving formatting.
|
|
939
|
+
*
|
|
940
|
+
* @param version - Version string to write.
|
|
941
|
+
* @param cwd - Project directory containing the manifest.
|
|
942
|
+
*/
|
|
943
|
+
setVersion(version, cwd) {
|
|
944
|
+
const filePath = path.join(cwd, this.manifestFile);
|
|
945
|
+
if (!fs.existsSync(filePath)) {
|
|
946
|
+
throw new Error(`${this.manifestFile} not found in ${cwd}`);
|
|
947
|
+
}
|
|
948
|
+
const keyParts = this.versionKey.split(".");
|
|
949
|
+
const leafKey = keyParts[keyParts.length - 1];
|
|
950
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
951
|
+
const regex = new RegExp(`^(\\s*${escapeRegExp(leafKey)}:\\s*)(["']?)(.+?)\\2\\s*$`, "m");
|
|
952
|
+
const updated = content.replace(regex, `$1$2${version}$2`);
|
|
953
|
+
if (updated === content) {
|
|
954
|
+
throw new Error(`Could not find version field to update in ${this.manifestFile}`);
|
|
955
|
+
}
|
|
956
|
+
fs.writeFileSync(filePath, updated, "utf-8");
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
const VALID_SOURCES = /* @__PURE__ */ new Set([
|
|
960
|
+
"auto",
|
|
961
|
+
"package.json",
|
|
962
|
+
"composer.json",
|
|
963
|
+
"Cargo.toml",
|
|
964
|
+
"pyproject.toml",
|
|
965
|
+
"pubspec.yaml",
|
|
966
|
+
"pom.xml",
|
|
967
|
+
"VERSION",
|
|
968
|
+
"git-tag",
|
|
969
|
+
"custom"
|
|
970
|
+
]);
|
|
971
|
+
const DETECTION_TABLE = [
|
|
972
|
+
{
|
|
973
|
+
file: "package.json",
|
|
974
|
+
source: "package.json",
|
|
975
|
+
factory: () => new JsonVersionSource("package.json", "version")
|
|
976
|
+
},
|
|
977
|
+
{
|
|
978
|
+
file: "Cargo.toml",
|
|
979
|
+
source: "Cargo.toml",
|
|
980
|
+
factory: () => new TomlVersionSource("Cargo.toml", "package.version")
|
|
981
|
+
},
|
|
982
|
+
{
|
|
983
|
+
file: "pyproject.toml",
|
|
984
|
+
source: "pyproject.toml",
|
|
985
|
+
factory: () => new TomlVersionSource("pyproject.toml", "project.version")
|
|
986
|
+
},
|
|
987
|
+
{
|
|
988
|
+
file: "pubspec.yaml",
|
|
989
|
+
source: "pubspec.yaml",
|
|
990
|
+
factory: () => new YamlVersionSource("pubspec.yaml", "version")
|
|
991
|
+
},
|
|
992
|
+
{
|
|
993
|
+
file: "composer.json",
|
|
994
|
+
source: "composer.json",
|
|
995
|
+
factory: () => new JsonVersionSource("composer.json", "version")
|
|
996
|
+
},
|
|
997
|
+
{
|
|
998
|
+
// H-002: Use regex that skips <parent> blocks for pom.xml
|
|
999
|
+
file: "pom.xml",
|
|
1000
|
+
source: "pom.xml",
|
|
1001
|
+
factory: () => new RegexVersionSource("pom.xml", "<project[^>]*>[\\s\\S]*?<version>([^<]+)</version>")
|
|
1002
|
+
},
|
|
1003
|
+
{ file: "VERSION", source: "VERSION", factory: () => new VersionFileSource("VERSION") }
|
|
1004
|
+
];
|
|
1005
|
+
function assertPathContained(manifestFile, cwd) {
|
|
1006
|
+
const resolved = path.resolve(cwd, manifestFile);
|
|
1007
|
+
const root = path.resolve(cwd);
|
|
1008
|
+
if (!resolved.startsWith(`${root}${path.sep}`) && resolved !== root) {
|
|
1009
|
+
throw new Error(`Manifest path "${manifestFile}" resolves outside the project directory`);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
function createProvider(source, config, cwd) {
|
|
1013
|
+
if (!VALID_SOURCES.has(source)) {
|
|
1014
|
+
throw new Error(
|
|
1015
|
+
`Invalid manifest source "${source}". Valid sources: ${[...VALID_SOURCES].join(", ")}`
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
switch (source) {
|
|
1019
|
+
case "package.json":
|
|
1020
|
+
return new JsonVersionSource("package.json", config.path ?? "version");
|
|
1021
|
+
case "composer.json":
|
|
1022
|
+
return new JsonVersionSource("composer.json", config.path ?? "version");
|
|
1023
|
+
case "Cargo.toml":
|
|
1024
|
+
return new TomlVersionSource("Cargo.toml", config.path ?? "package.version");
|
|
1025
|
+
case "pyproject.toml":
|
|
1026
|
+
return new TomlVersionSource("pyproject.toml", config.path ?? "project.version");
|
|
1027
|
+
case "pubspec.yaml":
|
|
1028
|
+
return new YamlVersionSource("pubspec.yaml", config.path ?? "version");
|
|
1029
|
+
case "pom.xml":
|
|
1030
|
+
return new RegexVersionSource(
|
|
1031
|
+
"pom.xml",
|
|
1032
|
+
config.regex ?? "<project[^>]*>[\\s\\S]*?<version>([^<]+)</version>"
|
|
1033
|
+
);
|
|
1034
|
+
case "VERSION":
|
|
1035
|
+
return new VersionFileSource(config.path ?? "VERSION");
|
|
1036
|
+
case "git-tag":
|
|
1037
|
+
return new GitTagSource();
|
|
1038
|
+
case "custom": {
|
|
1039
|
+
if (!config.regex) {
|
|
1040
|
+
throw new Error("Custom manifest source requires a 'regex' field in manifest config");
|
|
1041
|
+
}
|
|
1042
|
+
if (!config.path) {
|
|
1043
|
+
throw new Error(
|
|
1044
|
+
"Custom manifest source requires a 'path' field (manifest filename) in manifest config"
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
assertPathContained(config.path, cwd);
|
|
1048
|
+
return new RegexVersionSource(config.path, config.regex);
|
|
1049
|
+
}
|
|
1050
|
+
default:
|
|
1051
|
+
throw new Error(`Unknown manifest source: ${source}`);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
function resolveVersionSource(config, cwd = process.cwd()) {
|
|
1055
|
+
if (config.source !== "auto") {
|
|
1056
|
+
return createProvider(config.source, config, cwd);
|
|
1057
|
+
}
|
|
1058
|
+
for (const entry of DETECTION_TABLE) {
|
|
1059
|
+
const provider = entry.factory();
|
|
1060
|
+
if (provider.exists(cwd)) {
|
|
1061
|
+
return provider;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
const supported = DETECTION_TABLE.map((e) => e.file).join(", ");
|
|
1065
|
+
throw new Error(
|
|
1066
|
+
`No supported manifest file found in ${cwd}. Looked for: ${supported}. Set manifest.source explicitly in .versionguard.yml or create a supported manifest file.`
|
|
1067
|
+
);
|
|
1068
|
+
}
|
|
1069
|
+
function detectManifests(cwd = process.cwd()) {
|
|
1070
|
+
const detected = [];
|
|
1071
|
+
for (const entry of DETECTION_TABLE) {
|
|
1072
|
+
const provider = entry.factory();
|
|
1073
|
+
if (provider.exists(cwd)) {
|
|
1074
|
+
detected.push(entry.source);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return detected;
|
|
1078
|
+
}
|
|
356
1079
|
function getPackageJsonPath(cwd = process.cwd()) {
|
|
357
1080
|
return path.join(cwd, "package.json");
|
|
358
1081
|
}
|
|
@@ -367,18 +1090,30 @@ function writePackageJson(pkg, cwd = process.cwd()) {
|
|
|
367
1090
|
fs.writeFileSync(getPackageJsonPath(cwd), `${JSON.stringify(pkg, null, 2)}
|
|
368
1091
|
`, "utf-8");
|
|
369
1092
|
}
|
|
370
|
-
function getPackageVersion(cwd = process.cwd()) {
|
|
1093
|
+
function getPackageVersion(cwd = process.cwd(), manifest) {
|
|
1094
|
+
if (manifest) {
|
|
1095
|
+
const provider = resolveVersionSource(manifest, cwd);
|
|
1096
|
+
return provider.getVersion(cwd);
|
|
1097
|
+
}
|
|
371
1098
|
const pkg = readPackageJson(cwd);
|
|
372
1099
|
if (typeof pkg.version !== "string" || pkg.version.length === 0) {
|
|
373
1100
|
throw new Error("No version field in package.json");
|
|
374
1101
|
}
|
|
375
1102
|
return pkg.version;
|
|
376
1103
|
}
|
|
377
|
-
function setPackageVersion(version, cwd = process.cwd()) {
|
|
1104
|
+
function setPackageVersion(version, cwd = process.cwd(), manifest) {
|
|
1105
|
+
if (manifest) {
|
|
1106
|
+
const provider = resolveVersionSource(manifest, cwd);
|
|
1107
|
+
provider.setVersion(version, cwd);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
378
1110
|
const pkg = readPackageJson(cwd);
|
|
379
1111
|
pkg.version = version;
|
|
380
1112
|
writePackageJson(pkg, cwd);
|
|
381
1113
|
}
|
|
1114
|
+
function getVersionSource(manifest, cwd = process.cwd()) {
|
|
1115
|
+
return resolveVersionSource(manifest, cwd);
|
|
1116
|
+
}
|
|
382
1117
|
const SEMVER_REGEX = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
|
|
383
1118
|
function parse(version) {
|
|
384
1119
|
const match = version.match(SEMVER_REGEX);
|
|
@@ -623,6 +1358,12 @@ function checkHardcodedVersions(expectedVersion, config, ignorePatterns, cwd = p
|
|
|
623
1358
|
}
|
|
624
1359
|
return mismatches;
|
|
625
1360
|
}
|
|
1361
|
+
function getCalVerConfig(config) {
|
|
1362
|
+
if (!config.versioning.calver) {
|
|
1363
|
+
throw new Error('CalVer configuration is required when versioning.type is "calver"');
|
|
1364
|
+
}
|
|
1365
|
+
return config.versioning.calver;
|
|
1366
|
+
}
|
|
626
1367
|
const CONFIG_FILE_NAMES = [
|
|
627
1368
|
".versionguard.yml",
|
|
628
1369
|
".versionguard.yaml",
|
|
@@ -632,11 +1373,19 @@ const CONFIG_FILE_NAMES = [
|
|
|
632
1373
|
const DEFAULT_CONFIG = {
|
|
633
1374
|
versioning: {
|
|
634
1375
|
type: "semver",
|
|
1376
|
+
schemeRules: {
|
|
1377
|
+
maxNumericSegments: 3,
|
|
1378
|
+
allowedModifiers: ["dev", "alpha", "beta", "rc"]
|
|
1379
|
+
},
|
|
635
1380
|
calver: {
|
|
636
1381
|
format: "YYYY.MM.PATCH",
|
|
637
|
-
preventFutureDates: true
|
|
1382
|
+
preventFutureDates: true,
|
|
1383
|
+
strictMutualExclusion: true
|
|
638
1384
|
}
|
|
639
1385
|
},
|
|
1386
|
+
manifest: {
|
|
1387
|
+
source: "auto"
|
|
1388
|
+
},
|
|
640
1389
|
sync: {
|
|
641
1390
|
files: ["README.md", "CHANGELOG.md"],
|
|
642
1391
|
patterns: [
|
|
@@ -757,13 +1506,7 @@ function getVersionFeedback(version, config, previousVersion) {
|
|
|
757
1506
|
if (config.versioning.type === "semver") {
|
|
758
1507
|
return getSemVerFeedback(version, previousVersion);
|
|
759
1508
|
}
|
|
760
|
-
return getCalVerFeedback(version, getCalVerConfig
|
|
761
|
-
}
|
|
762
|
-
function getCalVerConfig$2(config) {
|
|
763
|
-
if (!config.versioning.calver) {
|
|
764
|
-
throw new Error('CalVer configuration is required when versioning.type is "calver"');
|
|
765
|
-
}
|
|
766
|
-
return config.versioning.calver;
|
|
1509
|
+
return getCalVerFeedback(version, getCalVerConfig(config), previousVersion);
|
|
767
1510
|
}
|
|
768
1511
|
function getSemVerFeedback(version, previousVersion) {
|
|
769
1512
|
const errors = [];
|
|
@@ -779,7 +1522,7 @@ function getSemVerFeedback(version, previousVersion) {
|
|
|
779
1522
|
});
|
|
780
1523
|
suggestions.push({
|
|
781
1524
|
message: `Remove the 'v' prefix`,
|
|
782
|
-
fix: `
|
|
1525
|
+
fix: `npx versionguard fix --version ${cleanVersion}`,
|
|
783
1526
|
autoFixable: true
|
|
784
1527
|
});
|
|
785
1528
|
} else if (version.split(".").length === 2) {
|
|
@@ -789,7 +1532,7 @@ function getSemVerFeedback(version, previousVersion) {
|
|
|
789
1532
|
});
|
|
790
1533
|
suggestions.push({
|
|
791
1534
|
message: `Add patch number (e.g., ${version}.0)`,
|
|
792
|
-
fix: `
|
|
1535
|
+
fix: `npx versionguard fix --version ${version}.0`,
|
|
793
1536
|
autoFixable: true
|
|
794
1537
|
});
|
|
795
1538
|
} else if (/^\d+\.\d+\.\d+\.\d+$/.test(version)) {
|
|
@@ -839,7 +1582,7 @@ function getSemVerFeedback(version, previousVersion) {
|
|
|
839
1582
|
});
|
|
840
1583
|
suggestions.push({
|
|
841
1584
|
message: `Version must be greater than ${previousVersion}`,
|
|
842
|
-
fix: `
|
|
1585
|
+
fix: `npx versionguard fix --version ${increment(previousVersion, "patch")}`,
|
|
843
1586
|
autoFixable: true
|
|
844
1587
|
});
|
|
845
1588
|
} else if (comparison === 0) {
|
|
@@ -849,7 +1592,7 @@ function getSemVerFeedback(version, previousVersion) {
|
|
|
849
1592
|
});
|
|
850
1593
|
suggestions.push({
|
|
851
1594
|
message: `Bump the version`,
|
|
852
|
-
fix: `
|
|
1595
|
+
fix: `npx versionguard fix --version ${increment(previousVersion, "patch")}`,
|
|
853
1596
|
autoFixable: true
|
|
854
1597
|
});
|
|
855
1598
|
} else {
|
|
@@ -896,7 +1639,7 @@ function getCalVerFeedback(version, calverConfig, previousVersion) {
|
|
|
896
1639
|
});
|
|
897
1640
|
suggestions.push({
|
|
898
1641
|
message: `Expected format: ${format2}`,
|
|
899
|
-
fix: `Update
|
|
1642
|
+
fix: `Update version to current date: "${getCurrentVersion(format2)}"`,
|
|
900
1643
|
autoFixable: true
|
|
901
1644
|
});
|
|
902
1645
|
return { valid: false, errors, suggestions, canAutoFix: true };
|
|
@@ -919,21 +1662,21 @@ function getCalVerFeedback(version, calverConfig, previousVersion) {
|
|
|
919
1662
|
if (preventFutureDates && parsed.year > now.getFullYear()) {
|
|
920
1663
|
suggestions.push({
|
|
921
1664
|
message: `Use current year (${now.getFullYear()}) or a past year`,
|
|
922
|
-
fix: `
|
|
1665
|
+
fix: `npx versionguard fix --version ${formatCalVerVersion({ ...parsed, year: now.getFullYear() })}`,
|
|
923
1666
|
autoFixable: true
|
|
924
1667
|
});
|
|
925
1668
|
}
|
|
926
1669
|
if (preventFutureDates && parsed.year === now.getFullYear() && parsed.month > now.getMonth() + 1) {
|
|
927
1670
|
suggestions.push({
|
|
928
1671
|
message: `Current month is ${now.getMonth() + 1}`,
|
|
929
|
-
fix: `
|
|
1672
|
+
fix: `npx versionguard fix --version ${formatCalVerVersion({ ...parsed, month: now.getMonth() + 1 })}`,
|
|
930
1673
|
autoFixable: true
|
|
931
1674
|
});
|
|
932
1675
|
}
|
|
933
1676
|
if (preventFutureDates && parsed.year === now.getFullYear() && parsed.month === now.getMonth() + 1 && parsed.day !== void 0 && parsed.day > now.getDate()) {
|
|
934
1677
|
suggestions.push({
|
|
935
1678
|
message: `Current day is ${now.getDate()}`,
|
|
936
|
-
fix: `
|
|
1679
|
+
fix: `npx versionguard fix --version ${formatCalVerVersion({ ...parsed, day: now.getDate() })}`,
|
|
937
1680
|
autoFixable: true
|
|
938
1681
|
});
|
|
939
1682
|
}
|
|
@@ -947,7 +1690,7 @@ function getCalVerFeedback(version, calverConfig, previousVersion) {
|
|
|
947
1690
|
});
|
|
948
1691
|
suggestions.push({
|
|
949
1692
|
message: `CalVer must increase over time`,
|
|
950
|
-
fix: `
|
|
1693
|
+
fix: `npx versionguard fix --version ${increment$1(previousVersion, format2)}`,
|
|
951
1694
|
autoFixable: true
|
|
952
1695
|
});
|
|
953
1696
|
}
|
|
@@ -1003,7 +1746,7 @@ function getChangelogFeedback(hasEntry, version, latestChangelogVersion) {
|
|
|
1003
1746
|
}
|
|
1004
1747
|
if (latestChangelogVersion && latestChangelogVersion !== version) {
|
|
1005
1748
|
suggestions.push({
|
|
1006
|
-
message: `CHANGELOG.md latest entry is ${latestChangelogVersion}, but
|
|
1749
|
+
message: `CHANGELOG.md latest entry is ${latestChangelogVersion}, but manifest version is ${version}`,
|
|
1007
1750
|
fix: `Make sure versions are in sync`,
|
|
1008
1751
|
autoFixable: false
|
|
1009
1752
|
});
|
|
@@ -1014,7 +1757,7 @@ function getTagFeedback(tagVersion, packageVersion, hasUnsyncedFiles) {
|
|
|
1014
1757
|
const suggestions = [];
|
|
1015
1758
|
if (tagVersion !== packageVersion) {
|
|
1016
1759
|
suggestions.push({
|
|
1017
|
-
message: `Git tag "${tagVersion}" doesn't match
|
|
1760
|
+
message: `Git tag "${tagVersion}" doesn't match manifest version "${packageVersion}"`,
|
|
1018
1761
|
fix: `Delete tag and recreate: git tag -d ${tagVersion} && git tag ${packageVersion}`,
|
|
1019
1762
|
autoFixable: false
|
|
1020
1763
|
});
|
|
@@ -1028,25 +1771,43 @@ function getTagFeedback(tagVersion, packageVersion, hasUnsyncedFiles) {
|
|
|
1028
1771
|
}
|
|
1029
1772
|
return suggestions;
|
|
1030
1773
|
}
|
|
1031
|
-
function fixPackageVersion(targetVersion, cwd = process.cwd()) {
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1774
|
+
function fixPackageVersion(targetVersion, cwd = process.cwd(), manifest) {
|
|
1775
|
+
if (!manifest) {
|
|
1776
|
+
const packagePath = path.join(cwd, "package.json");
|
|
1777
|
+
if (!fs.existsSync(packagePath)) {
|
|
1778
|
+
return { fixed: false, message: "package.json not found" };
|
|
1779
|
+
}
|
|
1780
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
|
1781
|
+
const oldVersion2 = typeof pkg.version === "string" ? pkg.version : void 0;
|
|
1782
|
+
if (oldVersion2 === targetVersion) {
|
|
1783
|
+
return { fixed: false, message: `Already at version ${targetVersion}` };
|
|
1784
|
+
}
|
|
1785
|
+
setPackageVersion(targetVersion, cwd);
|
|
1786
|
+
return {
|
|
1787
|
+
fixed: true,
|
|
1788
|
+
message: `Updated package.json from ${oldVersion2} to ${targetVersion}`,
|
|
1789
|
+
file: packagePath
|
|
1790
|
+
};
|
|
1791
|
+
}
|
|
1792
|
+
const provider = getVersionSource(manifest, cwd);
|
|
1793
|
+
let oldVersion;
|
|
1794
|
+
try {
|
|
1795
|
+
oldVersion = provider.getVersion(cwd);
|
|
1796
|
+
} catch {
|
|
1797
|
+
return { fixed: false, message: "Version source not found" };
|
|
1035
1798
|
}
|
|
1036
|
-
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf-8"));
|
|
1037
|
-
const oldVersion = typeof pkg.version === "string" ? pkg.version : void 0;
|
|
1038
1799
|
if (oldVersion === targetVersion) {
|
|
1039
1800
|
return { fixed: false, message: `Already at version ${targetVersion}` };
|
|
1040
1801
|
}
|
|
1041
|
-
|
|
1802
|
+
provider.setVersion(targetVersion, cwd);
|
|
1042
1803
|
return {
|
|
1043
1804
|
fixed: true,
|
|
1044
|
-
message: `Updated
|
|
1045
|
-
file:
|
|
1805
|
+
message: `Updated version from ${oldVersion} to ${targetVersion}`,
|
|
1806
|
+
file: provider.manifestFile ? path.join(cwd, provider.manifestFile) : void 0
|
|
1046
1807
|
};
|
|
1047
1808
|
}
|
|
1048
1809
|
function fixSyncIssues(config, cwd = process.cwd()) {
|
|
1049
|
-
const version = getPackageVersion(cwd);
|
|
1810
|
+
const version = getPackageVersion(cwd, config.manifest);
|
|
1050
1811
|
const results = syncVersion(version, config.sync, cwd).filter((result) => result.updated).map((result) => ({
|
|
1051
1812
|
fixed: true,
|
|
1052
1813
|
message: `Updated ${path.relative(cwd, result.file)} (${result.changes.length} changes)`,
|
|
@@ -1102,9 +1863,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
1102
1863
|
}
|
|
1103
1864
|
function fixAll(config, targetVersion, cwd = process.cwd()) {
|
|
1104
1865
|
const results = [];
|
|
1105
|
-
const version = targetVersion || getPackageVersion(cwd);
|
|
1106
|
-
if (targetVersion && targetVersion !== getPackageVersion(cwd)) {
|
|
1107
|
-
results.push(fixPackageVersion(targetVersion, cwd));
|
|
1866
|
+
const version = targetVersion || getPackageVersion(cwd, config.manifest);
|
|
1867
|
+
if (targetVersion && targetVersion !== getPackageVersion(cwd, config.manifest)) {
|
|
1868
|
+
results.push(fixPackageVersion(targetVersion, cwd, config.manifest));
|
|
1108
1869
|
}
|
|
1109
1870
|
const syncResults = fixSyncIssues(config, cwd);
|
|
1110
1871
|
results.push(...syncResults);
|
|
@@ -1138,7 +1899,7 @@ function suggestNextVersion(currentVersion, config, changeType) {
|
|
|
1138
1899
|
});
|
|
1139
1900
|
}
|
|
1140
1901
|
} else {
|
|
1141
|
-
const format2 = getCalVerConfig
|
|
1902
|
+
const format2 = getCalVerConfig(config).format;
|
|
1142
1903
|
const currentCal = getCurrentVersion(format2);
|
|
1143
1904
|
suggestions.push({
|
|
1144
1905
|
version: currentCal,
|
|
@@ -1151,12 +1912,6 @@ function suggestNextVersion(currentVersion, config, changeType) {
|
|
|
1151
1912
|
}
|
|
1152
1913
|
return suggestions;
|
|
1153
1914
|
}
|
|
1154
|
-
function getCalVerConfig$1(config) {
|
|
1155
|
-
if (!config.versioning.calver) {
|
|
1156
|
-
throw new Error('CalVer configuration is required when versioning.type is "calver"');
|
|
1157
|
-
}
|
|
1158
|
-
return config.versioning.calver;
|
|
1159
|
-
}
|
|
1160
1915
|
const HOOK_NAMES = ["pre-commit", "pre-push", "post-tag"];
|
|
1161
1916
|
function checkHooksPathOverride(cwd) {
|
|
1162
1917
|
try {
|
|
@@ -1165,6 +1920,15 @@ function checkHooksPathOverride(cwd) {
|
|
|
1165
1920
|
encoding: "utf-8"
|
|
1166
1921
|
}).trim();
|
|
1167
1922
|
if (hooksPath) {
|
|
1923
|
+
const resolved = path.resolve(cwd, hooksPath);
|
|
1924
|
+
const huskyDir = path.resolve(cwd, ".husky");
|
|
1925
|
+
if (resolved === huskyDir || resolved.startsWith(`${huskyDir}${path.sep}`)) {
|
|
1926
|
+
return {
|
|
1927
|
+
code: "HOOKS_PATH_HUSKY",
|
|
1928
|
+
severity: "warning",
|
|
1929
|
+
message: `Husky detected — core.hooksPath is set to "${hooksPath}". Hooks in .git/hooks/ are bypassed. Add versionguard validate to your .husky/pre-commit manually or use a tool like forge-ts that manages .husky/ hooks cooperatively.`
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1168
1932
|
return {
|
|
1169
1933
|
code: "HOOKS_PATH_OVERRIDE",
|
|
1170
1934
|
severity: "error",
|
|
@@ -1315,7 +2079,7 @@ function createTag(version, message, autoFix = true, config, cwd = process.cwd()
|
|
|
1315
2079
|
actions
|
|
1316
2080
|
};
|
|
1317
2081
|
}
|
|
1318
|
-
const packageVersion = getPackageVersion(cwd);
|
|
2082
|
+
const packageVersion = getPackageVersion(cwd, config.manifest);
|
|
1319
2083
|
const shouldAutoFix = autoFix;
|
|
1320
2084
|
const preflightError = getTagPreflightError(config, cwd, version, shouldAutoFix);
|
|
1321
2085
|
if (preflightError) {
|
|
@@ -1328,7 +2092,7 @@ function createTag(version, message, autoFix = true, config, cwd = process.cwd()
|
|
|
1328
2092
|
if (version !== packageVersion && !autoFix) {
|
|
1329
2093
|
return {
|
|
1330
2094
|
success: false,
|
|
1331
|
-
message: `Version mismatch:
|
|
2095
|
+
message: `Version mismatch: manifest version is ${packageVersion}, tag is ${version}`,
|
|
1332
2096
|
actions: []
|
|
1333
2097
|
};
|
|
1334
2098
|
}
|
|
@@ -1388,15 +2152,15 @@ function handlePostTag(config, cwd = process.cwd()) {
|
|
|
1388
2152
|
actions
|
|
1389
2153
|
};
|
|
1390
2154
|
}
|
|
1391
|
-
const packageVersion = getPackageVersion(cwd);
|
|
2155
|
+
const packageVersion = getPackageVersion(cwd, config.manifest);
|
|
1392
2156
|
if (tag.version !== packageVersion) {
|
|
1393
2157
|
return {
|
|
1394
2158
|
success: false,
|
|
1395
|
-
message: `Tag version ${tag.version} doesn't match
|
|
2159
|
+
message: `Tag version ${tag.version} doesn't match manifest version ${packageVersion}`,
|
|
1396
2160
|
actions: [
|
|
1397
2161
|
"To fix: delete tag and recreate with correct version",
|
|
1398
2162
|
` git tag -d ${tag.name}`,
|
|
1399
|
-
`
|
|
2163
|
+
` Update manifest to ${tag.version}`,
|
|
1400
2164
|
` git tag ${tag.name}`
|
|
1401
2165
|
]
|
|
1402
2166
|
};
|
|
@@ -1427,11 +2191,12 @@ function getTagPreflightError(config, cwd, expectedVersion, allowAutoFix = false
|
|
|
1427
2191
|
if (hasDirtyWorktree(cwd)) {
|
|
1428
2192
|
return "Working tree must be clean before creating or validating release tags";
|
|
1429
2193
|
}
|
|
1430
|
-
const version = expectedVersion ?? getPackageVersion(cwd);
|
|
2194
|
+
const version = expectedVersion ?? getPackageVersion(cwd, config.manifest);
|
|
1431
2195
|
const versionResult = config.versioning.type === "semver" ? validate$1(version) : validate$2(
|
|
1432
2196
|
version,
|
|
1433
2197
|
config.versioning.calver?.format ?? "YYYY.MM.PATCH",
|
|
1434
|
-
config.versioning.calver?.preventFutureDates ?? true
|
|
2198
|
+
config.versioning.calver?.preventFutureDates ?? true,
|
|
2199
|
+
config.versioning.schemeRules
|
|
1435
2200
|
);
|
|
1436
2201
|
if (!versionResult.valid) {
|
|
1437
2202
|
return versionResult.errors[0]?.message ?? `Invalid version: ${version}`;
|
|
@@ -1515,13 +2280,18 @@ function validateVersion(version, config) {
|
|
|
1515
2280
|
return validate$1(version);
|
|
1516
2281
|
}
|
|
1517
2282
|
const calverConfig = getCalVerConfig(config);
|
|
1518
|
-
return validate$2(
|
|
2283
|
+
return validate$2(
|
|
2284
|
+
version,
|
|
2285
|
+
calverConfig.format,
|
|
2286
|
+
calverConfig.preventFutureDates,
|
|
2287
|
+
config.versioning.schemeRules
|
|
2288
|
+
);
|
|
1519
2289
|
}
|
|
1520
2290
|
function validate(config, cwd = process.cwd()) {
|
|
1521
2291
|
const errors = [];
|
|
1522
2292
|
let version;
|
|
1523
2293
|
try {
|
|
1524
|
-
version = getPackageVersion(cwd);
|
|
2294
|
+
version = getPackageVersion(cwd, config.manifest);
|
|
1525
2295
|
} catch (err) {
|
|
1526
2296
|
return {
|
|
1527
2297
|
valid: false,
|
|
@@ -1592,7 +2362,7 @@ function doctor(config, cwd = process.cwd()) {
|
|
|
1592
2362
|
};
|
|
1593
2363
|
}
|
|
1594
2364
|
function sync(config, cwd = process.cwd()) {
|
|
1595
|
-
const version = getPackageVersion(cwd);
|
|
2365
|
+
const version = getPackageVersion(cwd, config.manifest);
|
|
1596
2366
|
syncVersion(version, config.sync, cwd);
|
|
1597
2367
|
}
|
|
1598
2368
|
function canBump(currentVersion, newVersion, config) {
|
|
@@ -1627,12 +2397,6 @@ function canBump(currentVersion, newVersion, config) {
|
|
|
1627
2397
|
}
|
|
1628
2398
|
return { canBump: true };
|
|
1629
2399
|
}
|
|
1630
|
-
function getCalVerConfig(config) {
|
|
1631
|
-
if (!config.versioning.calver) {
|
|
1632
|
-
throw new Error('CalVer configuration is required when versioning.type is "calver"');
|
|
1633
|
-
}
|
|
1634
|
-
return config.versioning.calver;
|
|
1635
|
-
}
|
|
1636
2400
|
function isWorktreeClean(cwd) {
|
|
1637
2401
|
try {
|
|
1638
2402
|
return execSync("git status --porcelain", { cwd, encoding: "utf-8" }).trim().length === 0;
|
|
@@ -1641,30 +2405,41 @@ function isWorktreeClean(cwd) {
|
|
|
1641
2405
|
}
|
|
1642
2406
|
}
|
|
1643
2407
|
export {
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
2408
|
+
fixChangelog as A,
|
|
2409
|
+
fixPackageVersion as B,
|
|
2410
|
+
getAllTags as C,
|
|
2411
|
+
getCalVerConfig as D,
|
|
2412
|
+
getLatestTag as E,
|
|
2413
|
+
getTagFeedback as F,
|
|
2414
|
+
GitTagSource as G,
|
|
2415
|
+
getVersionSource as H,
|
|
2416
|
+
initConfig as I,
|
|
2417
|
+
JsonVersionSource as J,
|
|
2418
|
+
resolveVersionSource as K,
|
|
2419
|
+
semver as L,
|
|
2420
|
+
suggestTagMessage as M,
|
|
2421
|
+
sync as N,
|
|
2422
|
+
syncVersion as O,
|
|
2423
|
+
validateChangelog as P,
|
|
2424
|
+
validateTagForPush as Q,
|
|
2425
|
+
RegexVersionSource as R,
|
|
2426
|
+
validateVersion as S,
|
|
2427
|
+
TomlVersionSource as T,
|
|
2428
|
+
VersionFileSource as V,
|
|
2429
|
+
YamlVersionSource as Y,
|
|
2430
|
+
installHooks as a,
|
|
2431
|
+
getPackageVersion as b,
|
|
2432
|
+
getVersionFeedback as c,
|
|
2433
|
+
getSyncFeedback as d,
|
|
2434
|
+
getChangelogFeedback as e,
|
|
2435
|
+
doctor as f,
|
|
1661
2436
|
getConfig as g,
|
|
1662
2437
|
handlePostTag as h,
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
2438
|
+
isValidCalVerFormat as i,
|
|
2439
|
+
fixAll as j,
|
|
2440
|
+
fixSyncIssues as k,
|
|
2441
|
+
setPackageVersion as l,
|
|
2442
|
+
createTag as m,
|
|
1668
2443
|
areHooksInstalled as n,
|
|
1669
2444
|
calver as o,
|
|
1670
2445
|
canBump as p,
|
|
@@ -1677,6 +2452,6 @@ export {
|
|
|
1677
2452
|
checkHookIntegrity as w,
|
|
1678
2453
|
checkHooksPathOverride as x,
|
|
1679
2454
|
checkHuskyBypass as y,
|
|
1680
|
-
|
|
2455
|
+
detectManifests as z
|
|
1681
2456
|
};
|
|
1682
|
-
//# sourceMappingURL=index-
|
|
2457
|
+
//# sourceMappingURL=index-B3R60bYJ.js.map
|