@ucdjs/release-scripts 0.1.0-beta.4 → 0.1.0-beta.6
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/eta-Boh7yPZi.mjs +477 -0
- package/dist/index.d.mts +50 -36
- package/dist/index.mjs +244 -287
- package/package.json +2 -2
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
//#region node_modules/.pnpm/eta@4.0.1/node_modules/eta/dist/index.js
|
|
5
|
+
var EtaError = class extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "Eta Error";
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var EtaParseError = class extends EtaError {
|
|
12
|
+
constructor(message) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = "EtaParser Error";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
var EtaRuntimeError = class extends EtaError {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "EtaRuntime Error";
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
var EtaFileResolutionError = class extends EtaError {
|
|
24
|
+
constructor(message) {
|
|
25
|
+
super(message);
|
|
26
|
+
this.name = "EtaFileResolution Error";
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
var EtaNameResolutionError = class extends EtaError {
|
|
30
|
+
constructor(message) {
|
|
31
|
+
super(message);
|
|
32
|
+
this.name = "EtaNameResolution Error";
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Throws an EtaError with a nicely formatted error and message showing where in the template the error occurred.
|
|
37
|
+
*/
|
|
38
|
+
function ParseErr(message, str, indx) {
|
|
39
|
+
const whitespace = str.slice(0, indx).split(/\n/);
|
|
40
|
+
const lineNo = whitespace.length;
|
|
41
|
+
const colNo = whitespace[lineNo - 1].length + 1;
|
|
42
|
+
message += " at line " + lineNo + " col " + colNo + ":\n\n " + str.split(/\n/)[lineNo - 1] + "\n " + Array(colNo).join(" ") + "^";
|
|
43
|
+
throw new EtaParseError(message);
|
|
44
|
+
}
|
|
45
|
+
function RuntimeErr(originalError, str, lineNo, path$1) {
|
|
46
|
+
const lines = str.split("\n");
|
|
47
|
+
const start = Math.max(lineNo - 3, 0);
|
|
48
|
+
const end = Math.min(lines.length, lineNo + 3);
|
|
49
|
+
const filename = path$1;
|
|
50
|
+
const context = lines.slice(start, end).map((line, i) => {
|
|
51
|
+
const curr = i + start + 1;
|
|
52
|
+
return (curr === lineNo ? " >> " : " ") + curr + "| " + line;
|
|
53
|
+
}).join("\n");
|
|
54
|
+
const err = new EtaRuntimeError((filename ? filename + ":" + lineNo + "\n" : "line " + lineNo + "\n") + context + "\n\n" + originalError.message);
|
|
55
|
+
err.name = originalError.name;
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
function readFile(path$1) {
|
|
59
|
+
let res = "";
|
|
60
|
+
try {
|
|
61
|
+
res = fs.readFileSync(path$1, "utf8");
|
|
62
|
+
} catch (err) {
|
|
63
|
+
if (err?.code === "ENOENT") throw new EtaFileResolutionError(`Could not find template: ${path$1}`);
|
|
64
|
+
else throw err;
|
|
65
|
+
}
|
|
66
|
+
return res;
|
|
67
|
+
}
|
|
68
|
+
function resolvePath(templatePath, options) {
|
|
69
|
+
let resolvedFilePath = "";
|
|
70
|
+
const views = this.config.views;
|
|
71
|
+
if (!views) throw new EtaFileResolutionError("Views directory is not defined");
|
|
72
|
+
const baseFilePath = options?.filepath;
|
|
73
|
+
const defaultExtension = this.config.defaultExtension === void 0 ? ".eta" : this.config.defaultExtension;
|
|
74
|
+
const cacheIndex = JSON.stringify({
|
|
75
|
+
filename: baseFilePath,
|
|
76
|
+
path: templatePath,
|
|
77
|
+
views: this.config.views
|
|
78
|
+
});
|
|
79
|
+
templatePath += path.extname(templatePath) ? "" : defaultExtension;
|
|
80
|
+
if (baseFilePath) {
|
|
81
|
+
if (this.config.cacheFilepaths && this.filepathCache[cacheIndex]) return this.filepathCache[cacheIndex];
|
|
82
|
+
if (absolutePathRegExp.exec(templatePath)?.length) {
|
|
83
|
+
const formattedPath = templatePath.replace(/^\/*|^\\*/, "");
|
|
84
|
+
resolvedFilePath = path.join(views, formattedPath);
|
|
85
|
+
} else resolvedFilePath = path.join(path.dirname(baseFilePath), templatePath);
|
|
86
|
+
} else resolvedFilePath = path.join(views, templatePath);
|
|
87
|
+
if (dirIsChild(views, resolvedFilePath)) {
|
|
88
|
+
if (baseFilePath && this.config.cacheFilepaths) this.filepathCache[cacheIndex] = resolvedFilePath;
|
|
89
|
+
return resolvedFilePath;
|
|
90
|
+
} else throw new EtaFileResolutionError(`Template '${templatePath}' is not in the views directory`);
|
|
91
|
+
}
|
|
92
|
+
function dirIsChild(parent, dir) {
|
|
93
|
+
const relative = path.relative(parent, dir);
|
|
94
|
+
return relative && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
95
|
+
}
|
|
96
|
+
const absolutePathRegExp = /^\\|^\//;
|
|
97
|
+
/* istanbul ignore next */
|
|
98
|
+
const AsyncFunction = (async () => {}).constructor;
|
|
99
|
+
/**
|
|
100
|
+
* Takes a template string and returns a template function that can be called with (data, config)
|
|
101
|
+
*
|
|
102
|
+
* @param str - The template string
|
|
103
|
+
* @param config - A custom configuration object (optional)
|
|
104
|
+
*/
|
|
105
|
+
function compile(str, options) {
|
|
106
|
+
const config = this.config;
|
|
107
|
+
const ctor = options?.async ? AsyncFunction : Function;
|
|
108
|
+
try {
|
|
109
|
+
return new ctor(config.varName, "options", this.compileToString.call(this, str, options));
|
|
110
|
+
} catch (e) {
|
|
111
|
+
if (e instanceof SyntaxError) throw new EtaParseError("Bad template syntax\n\n" + e.message + "\n" + Array(e.message.length + 1).join("=") + "\n" + this.compileToString.call(this, str, options) + "\n");
|
|
112
|
+
else throw e;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Compiles a template string to a function string. Most often users just use `compile()`, which calls `compileToString` and creates a new function using the result
|
|
117
|
+
*/
|
|
118
|
+
function compileToString(str, options) {
|
|
119
|
+
const config = this.config;
|
|
120
|
+
const isAsync = options?.async;
|
|
121
|
+
const compileBody$1 = this.compileBody;
|
|
122
|
+
const buffer = this.parse.call(this, str);
|
|
123
|
+
let res = `${config.functionHeader}
|
|
124
|
+
let include = (template, data) => this.render(template, data, options);
|
|
125
|
+
let includeAsync = (template, data) => this.renderAsync(template, data, options);
|
|
126
|
+
|
|
127
|
+
let __eta = {res: "", e: this.config.escapeFunction, f: this.config.filterFunction${config.debug ? ", line: 1, templateStr: \"" + str.replace(/\\|"/g, "\\$&").replace(/\r\n|\n|\r/g, "\\n") + "\"" : ""}};
|
|
128
|
+
|
|
129
|
+
function layout(path, data) {
|
|
130
|
+
__eta.layout = path;
|
|
131
|
+
__eta.layoutData = data;
|
|
132
|
+
}${config.debug ? "try {" : ""}${config.useWith ? "with(" + config.varName + "||{}){" : ""}
|
|
133
|
+
|
|
134
|
+
${compileBody$1.call(this, buffer)}
|
|
135
|
+
if (__eta.layout) {
|
|
136
|
+
__eta.res = ${isAsync ? "await includeAsync" : "include"} (__eta.layout, {...${config.varName}, body: __eta.res, ...__eta.layoutData});
|
|
137
|
+
}
|
|
138
|
+
${config.useWith ? "}" : ""}${config.debug ? "} catch (e) { this.RuntimeErr(e, __eta.templateStr, __eta.line, options.filepath) }" : ""}
|
|
139
|
+
return __eta.res;
|
|
140
|
+
`;
|
|
141
|
+
if (config.plugins) for (let i = 0; i < config.plugins.length; i++) {
|
|
142
|
+
const plugin = config.plugins[i];
|
|
143
|
+
if (plugin.processFnString) res = plugin.processFnString(res, config);
|
|
144
|
+
}
|
|
145
|
+
return res;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Loops through the AST generated by `parse` and transform each item into JS calls
|
|
149
|
+
*
|
|
150
|
+
* **Example**
|
|
151
|
+
*
|
|
152
|
+
* ```js
|
|
153
|
+
* let templateAST = ['Hi ', { val: 'it.name', t: 'i' }]
|
|
154
|
+
* compileBody.call(Eta, templateAST)
|
|
155
|
+
* // => "__eta.res+='Hi '\n__eta.res+=__eta.e(it.name)\n"
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
function compileBody(buff) {
|
|
159
|
+
const config = this.config;
|
|
160
|
+
let i = 0;
|
|
161
|
+
const buffLength = buff.length;
|
|
162
|
+
let returnStr = "";
|
|
163
|
+
for (; i < buffLength; i++) {
|
|
164
|
+
const currentBlock = buff[i];
|
|
165
|
+
if (typeof currentBlock === "string") returnStr += "__eta.res+='" + currentBlock + "'\n";
|
|
166
|
+
else {
|
|
167
|
+
const type = currentBlock.t;
|
|
168
|
+
let content = currentBlock.val || "";
|
|
169
|
+
if (config.debug) returnStr += "__eta.line=" + currentBlock.lineNo + "\n";
|
|
170
|
+
if (type === "r") {
|
|
171
|
+
if (config.autoFilter) content = "__eta.f(" + content + ")";
|
|
172
|
+
returnStr += "__eta.res+=" + content + "\n";
|
|
173
|
+
} else if (type === "i") {
|
|
174
|
+
if (config.autoFilter) content = "__eta.f(" + content + ")";
|
|
175
|
+
if (config.autoEscape) content = "__eta.e(" + content + ")";
|
|
176
|
+
returnStr += "__eta.res+=" + content + "\n";
|
|
177
|
+
} else if (type === "e") returnStr += content + "\n";
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return returnStr;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Takes a string within a template and trims it, based on the preceding tag's whitespace control and `config.autoTrim`
|
|
184
|
+
*/
|
|
185
|
+
function trimWS(str, config, wsLeft, wsRight) {
|
|
186
|
+
let leftTrim;
|
|
187
|
+
let rightTrim;
|
|
188
|
+
if (Array.isArray(config.autoTrim)) {
|
|
189
|
+
leftTrim = config.autoTrim[1];
|
|
190
|
+
rightTrim = config.autoTrim[0];
|
|
191
|
+
} else leftTrim = rightTrim = config.autoTrim;
|
|
192
|
+
if (wsLeft || wsLeft === false) leftTrim = wsLeft;
|
|
193
|
+
if (wsRight || wsRight === false) rightTrim = wsRight;
|
|
194
|
+
if (!rightTrim && !leftTrim) return str;
|
|
195
|
+
if (leftTrim === "slurp" && rightTrim === "slurp") return str.trim();
|
|
196
|
+
if (leftTrim === "_" || leftTrim === "slurp") str = str.trimStart();
|
|
197
|
+
else if (leftTrim === "-" || leftTrim === "nl") str = str.replace(/^(?:\r\n|\n|\r)/, "");
|
|
198
|
+
if (rightTrim === "_" || rightTrim === "slurp") str = str.trimEnd();
|
|
199
|
+
else if (rightTrim === "-" || rightTrim === "nl") str = str.replace(/(?:\r\n|\n|\r)$/, "");
|
|
200
|
+
return str;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* A map of special HTML characters to their XML-escaped equivalents
|
|
204
|
+
*/
|
|
205
|
+
const escMap = {
|
|
206
|
+
"&": "&",
|
|
207
|
+
"<": "<",
|
|
208
|
+
">": ">",
|
|
209
|
+
"\"": """,
|
|
210
|
+
"'": "'"
|
|
211
|
+
};
|
|
212
|
+
function replaceChar(s) {
|
|
213
|
+
return escMap[s];
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* XML-escapes an input value after converting it to a string
|
|
217
|
+
*
|
|
218
|
+
* @param str - Input value (usually a string)
|
|
219
|
+
* @returns XML-escaped string
|
|
220
|
+
*/
|
|
221
|
+
function XMLEscape(str) {
|
|
222
|
+
const newStr = String(str);
|
|
223
|
+
if (/[&<>"']/.test(newStr)) return newStr.replace(/[&<>"']/g, replaceChar);
|
|
224
|
+
else return newStr;
|
|
225
|
+
}
|
|
226
|
+
/** Eta's base (global) configuration */
|
|
227
|
+
const defaultConfig = {
|
|
228
|
+
autoEscape: true,
|
|
229
|
+
autoFilter: false,
|
|
230
|
+
autoTrim: [false, "nl"],
|
|
231
|
+
cache: false,
|
|
232
|
+
cacheFilepaths: true,
|
|
233
|
+
debug: false,
|
|
234
|
+
escapeFunction: XMLEscape,
|
|
235
|
+
filterFunction: (val) => String(val),
|
|
236
|
+
functionHeader: "",
|
|
237
|
+
parse: {
|
|
238
|
+
exec: "",
|
|
239
|
+
interpolate: "=",
|
|
240
|
+
raw: "~"
|
|
241
|
+
},
|
|
242
|
+
plugins: [],
|
|
243
|
+
rmWhitespace: false,
|
|
244
|
+
tags: ["<%", "%>"],
|
|
245
|
+
useWith: false,
|
|
246
|
+
varName: "it",
|
|
247
|
+
defaultExtension: ".eta"
|
|
248
|
+
};
|
|
249
|
+
const templateLitReg = /`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})*}|(?!\${)[^\\`])*`/g;
|
|
250
|
+
const singleQuoteReg = /'(?:\\[\s\w"'\\`]|[^\n\r'\\])*?'/g;
|
|
251
|
+
const doubleQuoteReg = /"(?:\\[\s\w"'\\`]|[^\n\r"\\])*?"/g;
|
|
252
|
+
/** Escape special regular expression characters inside a string */
|
|
253
|
+
function escapeRegExp(string) {
|
|
254
|
+
return string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");
|
|
255
|
+
}
|
|
256
|
+
function getLineNo(str, index) {
|
|
257
|
+
return str.slice(0, index).split("\n").length;
|
|
258
|
+
}
|
|
259
|
+
function parse(str) {
|
|
260
|
+
const config = this.config;
|
|
261
|
+
let buffer = [];
|
|
262
|
+
let trimLeftOfNextStr = false;
|
|
263
|
+
let lastIndex = 0;
|
|
264
|
+
const parseOptions = config.parse;
|
|
265
|
+
if (config.plugins) for (let i = 0; i < config.plugins.length; i++) {
|
|
266
|
+
const plugin = config.plugins[i];
|
|
267
|
+
if (plugin.processTemplate) str = plugin.processTemplate(str, config);
|
|
268
|
+
}
|
|
269
|
+
if (config.rmWhitespace) str = str.replace(/[\r\n]+/g, "\n").replace(/^\s+|\s+$/gm, "");
|
|
270
|
+
templateLitReg.lastIndex = 0;
|
|
271
|
+
singleQuoteReg.lastIndex = 0;
|
|
272
|
+
doubleQuoteReg.lastIndex = 0;
|
|
273
|
+
function pushString(strng, shouldTrimRightOfString) {
|
|
274
|
+
if (strng) {
|
|
275
|
+
strng = trimWS(strng, config, trimLeftOfNextStr, shouldTrimRightOfString);
|
|
276
|
+
if (strng) {
|
|
277
|
+
strng = strng.replace(/\\|'/g, "\\$&").replace(/\r\n|\n|\r/g, "\\n");
|
|
278
|
+
buffer.push(strng);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const prefixes = [
|
|
283
|
+
parseOptions.exec,
|
|
284
|
+
parseOptions.interpolate,
|
|
285
|
+
parseOptions.raw
|
|
286
|
+
].reduce((accumulator, prefix) => {
|
|
287
|
+
if (accumulator && prefix) return accumulator + "|" + escapeRegExp(prefix);
|
|
288
|
+
else if (prefix) return escapeRegExp(prefix);
|
|
289
|
+
else return accumulator;
|
|
290
|
+
}, "");
|
|
291
|
+
const parseOpenReg = new RegExp(escapeRegExp(config.tags[0]) + "(-|_)?\\s*(" + prefixes + ")?\\s*", "g");
|
|
292
|
+
const parseCloseReg = new RegExp("'|\"|`|\\/\\*|(\\s*(-|_)?" + escapeRegExp(config.tags[1]) + ")", "g");
|
|
293
|
+
let m;
|
|
294
|
+
while (m = parseOpenReg.exec(str)) {
|
|
295
|
+
const precedingString = str.slice(lastIndex, m.index);
|
|
296
|
+
lastIndex = m[0].length + m.index;
|
|
297
|
+
const wsLeft = m[1];
|
|
298
|
+
const prefix = m[2] || "";
|
|
299
|
+
pushString(precedingString, wsLeft);
|
|
300
|
+
parseCloseReg.lastIndex = lastIndex;
|
|
301
|
+
let closeTag;
|
|
302
|
+
let currentObj = false;
|
|
303
|
+
while (closeTag = parseCloseReg.exec(str)) if (closeTag[1]) {
|
|
304
|
+
const content = str.slice(lastIndex, closeTag.index);
|
|
305
|
+
parseOpenReg.lastIndex = lastIndex = parseCloseReg.lastIndex;
|
|
306
|
+
trimLeftOfNextStr = closeTag[2];
|
|
307
|
+
currentObj = {
|
|
308
|
+
t: prefix === parseOptions.exec ? "e" : prefix === parseOptions.raw ? "r" : prefix === parseOptions.interpolate ? "i" : "",
|
|
309
|
+
val: content
|
|
310
|
+
};
|
|
311
|
+
break;
|
|
312
|
+
} else {
|
|
313
|
+
const char = closeTag[0];
|
|
314
|
+
if (char === "/*") {
|
|
315
|
+
const commentCloseInd = str.indexOf("*/", parseCloseReg.lastIndex);
|
|
316
|
+
if (commentCloseInd === -1) ParseErr("unclosed comment", str, closeTag.index);
|
|
317
|
+
parseCloseReg.lastIndex = commentCloseInd;
|
|
318
|
+
} else if (char === "'") {
|
|
319
|
+
singleQuoteReg.lastIndex = closeTag.index;
|
|
320
|
+
if (singleQuoteReg.exec(str)) parseCloseReg.lastIndex = singleQuoteReg.lastIndex;
|
|
321
|
+
else ParseErr("unclosed string", str, closeTag.index);
|
|
322
|
+
} else if (char === "\"") {
|
|
323
|
+
doubleQuoteReg.lastIndex = closeTag.index;
|
|
324
|
+
if (doubleQuoteReg.exec(str)) parseCloseReg.lastIndex = doubleQuoteReg.lastIndex;
|
|
325
|
+
else ParseErr("unclosed string", str, closeTag.index);
|
|
326
|
+
} else if (char === "`") {
|
|
327
|
+
templateLitReg.lastIndex = closeTag.index;
|
|
328
|
+
if (templateLitReg.exec(str)) parseCloseReg.lastIndex = templateLitReg.lastIndex;
|
|
329
|
+
else ParseErr("unclosed string", str, closeTag.index);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (currentObj) {
|
|
333
|
+
if (config.debug) currentObj.lineNo = getLineNo(str, m.index);
|
|
334
|
+
buffer.push(currentObj);
|
|
335
|
+
} else ParseErr("unclosed tag", str, m.index);
|
|
336
|
+
}
|
|
337
|
+
pushString(str.slice(lastIndex, str.length), false);
|
|
338
|
+
if (config.plugins) for (let i = 0; i < config.plugins.length; i++) {
|
|
339
|
+
const plugin = config.plugins[i];
|
|
340
|
+
if (plugin.processAST) buffer = plugin.processAST(buffer, config);
|
|
341
|
+
}
|
|
342
|
+
return buffer;
|
|
343
|
+
}
|
|
344
|
+
function handleCache(template, options) {
|
|
345
|
+
const templateStore = options?.async ? this.templatesAsync : this.templatesSync;
|
|
346
|
+
if (this.resolvePath && this.readFile && !template.startsWith("@")) {
|
|
347
|
+
const templatePath = options.filepath;
|
|
348
|
+
const cachedTemplate = templateStore.get(templatePath);
|
|
349
|
+
if (this.config.cache && cachedTemplate) return cachedTemplate;
|
|
350
|
+
else {
|
|
351
|
+
const templateString = this.readFile(templatePath);
|
|
352
|
+
const templateFn = this.compile(templateString, options);
|
|
353
|
+
if (this.config.cache) templateStore.define(templatePath, templateFn);
|
|
354
|
+
return templateFn;
|
|
355
|
+
}
|
|
356
|
+
} else {
|
|
357
|
+
const cachedTemplate = templateStore.get(template);
|
|
358
|
+
if (cachedTemplate) return cachedTemplate;
|
|
359
|
+
else throw new EtaNameResolutionError(`Failed to get template '${template}'`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function render(template, data, meta) {
|
|
363
|
+
let templateFn;
|
|
364
|
+
const options = {
|
|
365
|
+
...meta,
|
|
366
|
+
async: false
|
|
367
|
+
};
|
|
368
|
+
if (typeof template === "string") {
|
|
369
|
+
if (this.resolvePath && this.readFile && !template.startsWith("@")) options.filepath = this.resolvePath(template, options);
|
|
370
|
+
templateFn = handleCache.call(this, template, options);
|
|
371
|
+
} else templateFn = template;
|
|
372
|
+
return templateFn.call(this, data, options);
|
|
373
|
+
}
|
|
374
|
+
function renderAsync(template, data, meta) {
|
|
375
|
+
let templateFn;
|
|
376
|
+
const options = {
|
|
377
|
+
...meta,
|
|
378
|
+
async: true
|
|
379
|
+
};
|
|
380
|
+
if (typeof template === "string") {
|
|
381
|
+
if (this.resolvePath && this.readFile && !template.startsWith("@")) options.filepath = this.resolvePath(template, options);
|
|
382
|
+
templateFn = handleCache.call(this, template, options);
|
|
383
|
+
} else templateFn = template;
|
|
384
|
+
const res = templateFn.call(this, data, options);
|
|
385
|
+
return Promise.resolve(res);
|
|
386
|
+
}
|
|
387
|
+
function renderString(template, data) {
|
|
388
|
+
const templateFn = this.compile(template, { async: false });
|
|
389
|
+
return render.call(this, templateFn, data);
|
|
390
|
+
}
|
|
391
|
+
function renderStringAsync(template, data) {
|
|
392
|
+
const templateFn = this.compile(template, { async: true });
|
|
393
|
+
return renderAsync.call(this, templateFn, data);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Handles storage and accessing of values
|
|
397
|
+
*
|
|
398
|
+
* In this case, we use it to store compiled template functions
|
|
399
|
+
* Indexed by their `name` or `filename`
|
|
400
|
+
*/
|
|
401
|
+
var Cacher = class {
|
|
402
|
+
constructor(cache) {
|
|
403
|
+
this.cache = cache;
|
|
404
|
+
}
|
|
405
|
+
define(key, val) {
|
|
406
|
+
this.cache[key] = val;
|
|
407
|
+
}
|
|
408
|
+
get(key) {
|
|
409
|
+
return this.cache[key];
|
|
410
|
+
}
|
|
411
|
+
remove(key) {
|
|
412
|
+
delete this.cache[key];
|
|
413
|
+
}
|
|
414
|
+
reset() {
|
|
415
|
+
this.cache = {};
|
|
416
|
+
}
|
|
417
|
+
load(cacheObj) {
|
|
418
|
+
this.cache = {
|
|
419
|
+
...this.cache,
|
|
420
|
+
...cacheObj
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
var Eta$1 = class {
|
|
425
|
+
constructor(customConfig) {
|
|
426
|
+
if (customConfig) this.config = {
|
|
427
|
+
...defaultConfig,
|
|
428
|
+
...customConfig
|
|
429
|
+
};
|
|
430
|
+
else this.config = { ...defaultConfig };
|
|
431
|
+
}
|
|
432
|
+
config;
|
|
433
|
+
RuntimeErr = RuntimeErr;
|
|
434
|
+
compile = compile;
|
|
435
|
+
compileToString = compileToString;
|
|
436
|
+
compileBody = compileBody;
|
|
437
|
+
parse = parse;
|
|
438
|
+
render = render;
|
|
439
|
+
renderAsync = renderAsync;
|
|
440
|
+
renderString = renderString;
|
|
441
|
+
renderStringAsync = renderStringAsync;
|
|
442
|
+
filepathCache = {};
|
|
443
|
+
templatesSync = new Cacher({});
|
|
444
|
+
templatesAsync = new Cacher({});
|
|
445
|
+
resolvePath = null;
|
|
446
|
+
readFile = null;
|
|
447
|
+
configure(customConfig) {
|
|
448
|
+
this.config = {
|
|
449
|
+
...this.config,
|
|
450
|
+
...customConfig
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
withConfig(customConfig) {
|
|
454
|
+
return {
|
|
455
|
+
...this,
|
|
456
|
+
config: {
|
|
457
|
+
...this.config,
|
|
458
|
+
...customConfig
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
loadTemplate(name, template, options) {
|
|
463
|
+
if (typeof template === "string") (options?.async ? this.templatesAsync : this.templatesSync).define(name, this.compile(template, options));
|
|
464
|
+
else {
|
|
465
|
+
let templates = this.templatesSync;
|
|
466
|
+
if (template.constructor.name === "AsyncFunction" || options?.async) templates = this.templatesAsync;
|
|
467
|
+
templates.define(name, template);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
var Eta = class extends Eta$1 {
|
|
472
|
+
readFile = readFile;
|
|
473
|
+
resolvePath = resolvePath;
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
//#endregion
|
|
477
|
+
export { Eta as t };
|
package/dist/index.d.mts
CHANGED
|
@@ -10,6 +10,45 @@ interface WorkspacePackage {
|
|
|
10
10
|
//#endregion
|
|
11
11
|
//#region src/types.d.ts
|
|
12
12
|
type BumpKind = "none" | "patch" | "minor" | "major";
|
|
13
|
+
interface SharedOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Repository identifier (e.g., "owner/repo")
|
|
16
|
+
*/
|
|
17
|
+
repo: string;
|
|
18
|
+
/**
|
|
19
|
+
* Root directory of the workspace (defaults to process.cwd())
|
|
20
|
+
*/
|
|
21
|
+
workspaceRoot?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Specific packages to prepare for release.
|
|
24
|
+
* - true: discover all packages
|
|
25
|
+
* - FindWorkspacePackagesOptions: discover with filters
|
|
26
|
+
* - string[]: specific package names
|
|
27
|
+
*/
|
|
28
|
+
packages?: true | FindWorkspacePackagesOptions | string[];
|
|
29
|
+
/**
|
|
30
|
+
* Whether to enable verbose logging
|
|
31
|
+
* @default false
|
|
32
|
+
*/
|
|
33
|
+
verbose?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* GitHub token for authentication
|
|
36
|
+
*/
|
|
37
|
+
githubToken: string;
|
|
38
|
+
/**
|
|
39
|
+
* Interactive prompt configuration
|
|
40
|
+
*/
|
|
41
|
+
prompts?: {
|
|
42
|
+
/**
|
|
43
|
+
* Enable package selection prompt (defaults to true when not in CI)
|
|
44
|
+
*/
|
|
45
|
+
packages?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Enable version override prompt (defaults to true when not in CI)
|
|
48
|
+
*/
|
|
49
|
+
versions?: boolean;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
13
52
|
interface PackageJson {
|
|
14
53
|
name: string;
|
|
15
54
|
version: string;
|
|
@@ -23,11 +62,11 @@ interface FindWorkspacePackagesOptions {
|
|
|
23
62
|
/**
|
|
24
63
|
* Package names to exclude
|
|
25
64
|
*/
|
|
26
|
-
|
|
65
|
+
exclude?: string[];
|
|
27
66
|
/**
|
|
28
67
|
* Only include these packages (if specified, all others are excluded)
|
|
29
68
|
*/
|
|
30
|
-
|
|
69
|
+
include?: string[];
|
|
31
70
|
/**
|
|
32
71
|
* Whether to exclude private packages (default: false)
|
|
33
72
|
*/
|
|
@@ -55,39 +94,17 @@ interface VersionUpdate {
|
|
|
55
94
|
*/
|
|
56
95
|
hasDirectChanges: boolean;
|
|
57
96
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
*/
|
|
66
|
-
workspaceRoot?: string;
|
|
67
|
-
/**
|
|
68
|
-
* Specific packages to prepare for release.
|
|
69
|
-
* - true: discover all packages
|
|
70
|
-
* - FindWorkspacePackagesOptions: discover with filters
|
|
71
|
-
* - string[]: specific package names
|
|
72
|
-
*/
|
|
73
|
-
packages?: true | FindWorkspacePackagesOptions | string[];
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/publish.d.ts
|
|
99
|
+
interface PublishOptions extends SharedOptions {}
|
|
100
|
+
declare function publish(_options: PublishOptions): void;
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/release.d.ts
|
|
103
|
+
interface ReleaseOptions extends SharedOptions {
|
|
74
104
|
/**
|
|
75
105
|
* Branch name for the release PR (defaults to "release/next")
|
|
76
106
|
*/
|
|
77
107
|
releaseBranch?: string;
|
|
78
|
-
/**
|
|
79
|
-
* Interactive prompt configuration
|
|
80
|
-
*/
|
|
81
|
-
prompts?: {
|
|
82
|
-
/**
|
|
83
|
-
* Enable package selection prompt (defaults to true when not in CI)
|
|
84
|
-
*/
|
|
85
|
-
packages?: boolean;
|
|
86
|
-
/**
|
|
87
|
-
* Enable version override prompt (defaults to true when not in CI)
|
|
88
|
-
*/
|
|
89
|
-
versions?: boolean;
|
|
90
|
-
};
|
|
91
108
|
/**
|
|
92
109
|
* Whether to perform a dry run (no changes pushed or PR created)
|
|
93
110
|
* @default false
|
|
@@ -99,9 +116,8 @@ interface ReleaseOptions {
|
|
|
99
116
|
*/
|
|
100
117
|
safeguards?: boolean;
|
|
101
118
|
/**
|
|
102
|
-
*
|
|
119
|
+
* Pull request configuration
|
|
103
120
|
*/
|
|
104
|
-
githubToken: string;
|
|
105
121
|
pullRequest?: {
|
|
106
122
|
/**
|
|
107
123
|
* Title for the release pull request
|
|
@@ -132,8 +148,6 @@ interface ReleaseResult {
|
|
|
132
148
|
*/
|
|
133
149
|
created: boolean;
|
|
134
150
|
}
|
|
135
|
-
//#endregion
|
|
136
|
-
//#region src/release.d.ts
|
|
137
151
|
declare function release(options: ReleaseOptions): Promise<ReleaseResult | null>;
|
|
138
152
|
//#endregion
|
|
139
|
-
export { release };
|
|
153
|
+
export { type PublishOptions, type ReleaseOptions, type ReleaseResult, publish, release };
|
package/dist/index.mjs
CHANGED
|
@@ -1,16 +1,41 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { t as Eta } from "./eta-Boh7yPZi.mjs";
|
|
2
2
|
import { getCommits } from "commit-parser";
|
|
3
|
+
import process from "node:process";
|
|
3
4
|
import farver from "farver";
|
|
4
5
|
import { exec } from "tinyexec";
|
|
5
6
|
import { dedent } from "@luxass/utils";
|
|
6
|
-
import { Eta } from "eta";
|
|
7
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
8
7
|
import { join } from "node:path";
|
|
8
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
9
9
|
import prompts from "prompts";
|
|
10
10
|
|
|
11
|
+
//#region src/publish.ts
|
|
12
|
+
function publish(_options) {}
|
|
13
|
+
|
|
14
|
+
//#endregion
|
|
11
15
|
//#region src/utils.ts
|
|
12
|
-
const globalOptions = {
|
|
16
|
+
const globalOptions = {
|
|
17
|
+
dryRun: false,
|
|
18
|
+
verbose: false
|
|
19
|
+
};
|
|
13
20
|
const isCI = typeof process.env.CI === "string" && process.env.CI !== "" && process.env.CI.toLowerCase() !== "false";
|
|
21
|
+
const logger = {
|
|
22
|
+
info: (...args) => {
|
|
23
|
+
console.info(farver.cyan("[info]:"), ...args);
|
|
24
|
+
},
|
|
25
|
+
debug: (...args) => {
|
|
26
|
+
console.debug(farver.gray("[debug]:"), ...args);
|
|
27
|
+
},
|
|
28
|
+
warn: (...args) => {
|
|
29
|
+
console.warn(farver.yellow("[warn]:"), ...args);
|
|
30
|
+
},
|
|
31
|
+
error: (...args) => {
|
|
32
|
+
console.error(farver.red("[error]:"), ...args);
|
|
33
|
+
},
|
|
34
|
+
log: (...args) => {
|
|
35
|
+
if (!globalOptions.verbose) return;
|
|
36
|
+
console.log(...args);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
14
39
|
async function run(bin, args, opts = {}) {
|
|
15
40
|
return exec(bin, args, {
|
|
16
41
|
throwOnError: true,
|
|
@@ -22,18 +47,49 @@ async function run(bin, args, opts = {}) {
|
|
|
22
47
|
});
|
|
23
48
|
}
|
|
24
49
|
async function dryRun(bin, args, opts) {
|
|
25
|
-
return
|
|
50
|
+
return logger.log(farver.blue(`[dryrun] ${bin} ${args.join(" ")}`), opts || "");
|
|
26
51
|
}
|
|
27
52
|
const runIfNotDry = globalOptions.dryRun ? dryRun : run;
|
|
53
|
+
function exitWithError(message, hint) {
|
|
54
|
+
logger.error(farver.bold(message));
|
|
55
|
+
if (hint) console.error(farver.gray(` ${hint}`));
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
function normalizeSharedOptions(options) {
|
|
59
|
+
const { workspaceRoot = process.cwd(), githubToken = "", verbose = false, repo, packages = true, prompts: prompts$1 = {
|
|
60
|
+
packages: true,
|
|
61
|
+
versions: true
|
|
62
|
+
},...rest } = options;
|
|
63
|
+
globalOptions.verbose = verbose;
|
|
64
|
+
if (!githubToken.trim()) exitWithError("GitHub token is required", "Set GITHUB_TOKEN environment variable or pass it in options");
|
|
65
|
+
if (!repo || !repo.trim() || !repo.includes("/")) exitWithError("Repository (repo) is required", "Specify the repository in 'owner/repo' format (e.g., 'octocat/hello-world')");
|
|
66
|
+
const [owner, name] = options.repo.split("/");
|
|
67
|
+
if (!owner || !name) exitWithError(`Invalid repo format: "${options.repo}"`, "Expected format: \"owner/repo\" (e.g., \"octocat/hello-world\")");
|
|
68
|
+
return {
|
|
69
|
+
...rest,
|
|
70
|
+
packages,
|
|
71
|
+
prompts: prompts$1,
|
|
72
|
+
workspaceRoot,
|
|
73
|
+
githubToken,
|
|
74
|
+
owner,
|
|
75
|
+
repo,
|
|
76
|
+
verbose
|
|
77
|
+
};
|
|
78
|
+
}
|
|
28
79
|
|
|
29
80
|
//#endregion
|
|
30
81
|
//#region src/commits.ts
|
|
31
82
|
async function getLastPackageTag(packageName, workspaceRoot) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
83
|
+
try {
|
|
84
|
+
const { stdout } = await run("git", ["tag", "--list"], { nodeOptions: {
|
|
85
|
+
cwd: workspaceRoot,
|
|
86
|
+
stdio: "pipe"
|
|
87
|
+
} });
|
|
88
|
+
return stdout.split("\n").map((tag) => tag.trim()).filter(Boolean).reverse().find((tag) => tag.startsWith(`${packageName}@`));
|
|
89
|
+
} catch (err) {
|
|
90
|
+
logger.warn(`Failed to get tags for package ${packageName}: ${err.message}`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
37
93
|
}
|
|
38
94
|
function determineHighestBump(commits) {
|
|
39
95
|
if (commits.length === 0) return "none";
|
|
@@ -46,35 +102,42 @@ function determineHighestBump(commits) {
|
|
|
46
102
|
}
|
|
47
103
|
return highestBump;
|
|
48
104
|
}
|
|
49
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Retrieves commits that affect a specific workspace package since its last tag.
|
|
107
|
+
*
|
|
108
|
+
* @param {string} workspaceRoot - The root directory of the workspace.
|
|
109
|
+
* @param {WorkspacePackage} pkg - The workspace package to analyze.
|
|
110
|
+
* @returns {Promise<GitCommit[]>} A promise that resolves to an array of GitCommit objects affecting the package.
|
|
111
|
+
*/
|
|
112
|
+
async function getCommitsForWorkspacePackage(workspaceRoot, pkg) {
|
|
50
113
|
const lastTag = await getLastPackageTag(pkg.name, workspaceRoot);
|
|
51
114
|
const allCommits = getCommits({
|
|
52
115
|
from: lastTag,
|
|
53
|
-
to: "HEAD"
|
|
116
|
+
to: "HEAD",
|
|
117
|
+
cwd: workspaceRoot
|
|
118
|
+
});
|
|
119
|
+
logger.log(`Found ${allCommits.length} commits for ${pkg.name} since ${lastTag || "beginning"}`);
|
|
120
|
+
const touchedCommitHashes = getCommits({
|
|
121
|
+
from: lastTag,
|
|
122
|
+
to: "HEAD",
|
|
123
|
+
cwd: workspaceRoot,
|
|
124
|
+
folder: pkg.path
|
|
54
125
|
});
|
|
55
|
-
console.log(`Found ${allCommits.length} commits for ${pkg.name} since ${lastTag || "beginning"}`);
|
|
56
|
-
const touchedCommitHashes = await getCommitsTouchingPackage(lastTag || "HEAD", "HEAD", pkg.path, workspaceRoot);
|
|
57
126
|
const touchedSet = new Set(touchedCommitHashes);
|
|
58
|
-
const packageCommits = allCommits.filter((commit) => touchedSet.has(commit
|
|
59
|
-
|
|
127
|
+
const packageCommits = allCommits.filter((commit) => touchedSet.has(commit));
|
|
128
|
+
logger.log(`${packageCommits.length} commits affect ${pkg.name}`);
|
|
60
129
|
return packageCommits;
|
|
61
130
|
}
|
|
62
|
-
async function
|
|
63
|
-
return determineHighestBump(await getPackageCommits(pkg, workspaceRoot));
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Analyze commits for multiple packages to determine version bumps
|
|
67
|
-
*
|
|
68
|
-
* @param packages - Packages to analyze
|
|
69
|
-
* @param workspaceRoot - Root directory of the workspace
|
|
70
|
-
* @returns Map of package names to their bump types
|
|
71
|
-
*/
|
|
72
|
-
async function analyzeCommits(packages, workspaceRoot) {
|
|
131
|
+
async function getWorkspacePackageCommits(workspaceRoot, packages) {
|
|
73
132
|
const changedPackages = /* @__PURE__ */ new Map();
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
133
|
+
const promises = packages.map(async (pkg) => {
|
|
134
|
+
return {
|
|
135
|
+
pkgName: pkg.name,
|
|
136
|
+
commits: await getCommitsForWorkspacePackage(workspaceRoot, pkg)
|
|
137
|
+
};
|
|
138
|
+
});
|
|
139
|
+
const results = await Promise.all(promises);
|
|
140
|
+
for (const { pkgName, commits } of results) changedPackages.set(pkgName, commits);
|
|
78
141
|
return changedPackages;
|
|
79
142
|
}
|
|
80
143
|
function determineBumpType(commit) {
|
|
@@ -95,24 +158,6 @@ function determineBumpType(commit) {
|
|
|
95
158
|
default: return "none";
|
|
96
159
|
}
|
|
97
160
|
}
|
|
98
|
-
async function getCommitsTouchingPackage(from, to, packagePath, workspaceRoot) {
|
|
99
|
-
try {
|
|
100
|
-
const { stdout } = await run("git", [
|
|
101
|
-
"log",
|
|
102
|
-
"--pretty=format:%h",
|
|
103
|
-
from === "HEAD" ? "HEAD" : `${from}...${to}`,
|
|
104
|
-
"--",
|
|
105
|
-
packagePath
|
|
106
|
-
], { nodeOptions: {
|
|
107
|
-
cwd: workspaceRoot,
|
|
108
|
-
stdio: "pipe"
|
|
109
|
-
} });
|
|
110
|
-
return stdout.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
111
|
-
} catch (error) {
|
|
112
|
-
console.error(`Error getting commits touching package: ${error}`);
|
|
113
|
-
return [];
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
161
|
|
|
117
162
|
//#endregion
|
|
118
163
|
//#region src/git.ts
|
|
@@ -129,7 +174,7 @@ async function isWorkingDirectoryClean(workspaceRoot) {
|
|
|
129
174
|
} })).stdout.trim() !== "") return false;
|
|
130
175
|
return true;
|
|
131
176
|
} catch (err) {
|
|
132
|
-
|
|
177
|
+
logger.error("Error checking git status:", err);
|
|
133
178
|
return false;
|
|
134
179
|
}
|
|
135
180
|
}
|
|
@@ -313,10 +358,10 @@ async function getExistingPullRequest({ owner, repo, branch, githubToken }) {
|
|
|
313
358
|
draft: firstPullRequest.draft,
|
|
314
359
|
html_url: firstPullRequest.html_url
|
|
315
360
|
};
|
|
316
|
-
|
|
361
|
+
logger.info(`Found existing pull request: ${farver.yellow(`#${pullRequest.number}`)}`);
|
|
317
362
|
return pullRequest;
|
|
318
363
|
} catch (err) {
|
|
319
|
-
|
|
364
|
+
logger.error("Error fetching pull request:", err);
|
|
320
365
|
return null;
|
|
321
366
|
}
|
|
322
367
|
}
|
|
@@ -346,7 +391,7 @@ async function upsertPullRequest({ owner, repo, title, body, head, base, pullNum
|
|
|
346
391
|
const pr = await res.json();
|
|
347
392
|
if (typeof pr !== "object" || pr === null || !("number" in pr) || typeof pr.number !== "number" || !("title" in pr) || typeof pr.title !== "string" || !("body" in pr) || typeof pr.body !== "string" || !("draft" in pr) || typeof pr.draft !== "boolean" || !("html_url" in pr) || typeof pr.html_url !== "string") throw new TypeError("Pull request data validation failed");
|
|
348
393
|
const action = isUpdate ? "Updated" : "Created";
|
|
349
|
-
|
|
394
|
+
logger.info(`${action} pull request: ${farver.yellow(`#${pr.number}`)}`);
|
|
350
395
|
return {
|
|
351
396
|
number: pr.number,
|
|
352
397
|
title: pr.title,
|
|
@@ -355,7 +400,7 @@ async function upsertPullRequest({ owner, repo, title, body, head, base, pullNum
|
|
|
355
400
|
html_url: pr.html_url
|
|
356
401
|
};
|
|
357
402
|
} catch (err) {
|
|
358
|
-
|
|
403
|
+
logger.error(`Error upserting pull request:`, err);
|
|
359
404
|
throw err;
|
|
360
405
|
}
|
|
361
406
|
}
|
|
@@ -392,6 +437,65 @@ function generatePullRequestBody(updates, body) {
|
|
|
392
437
|
})) });
|
|
393
438
|
}
|
|
394
439
|
|
|
440
|
+
//#endregion
|
|
441
|
+
//#region src/prompts.ts
|
|
442
|
+
async function selectPackagePrompt(packages) {
|
|
443
|
+
const response = await prompts({
|
|
444
|
+
type: "multiselect",
|
|
445
|
+
name: "selectedPackages",
|
|
446
|
+
message: "Select packages to release",
|
|
447
|
+
choices: packages.map((pkg) => ({
|
|
448
|
+
title: `${pkg.name} (${farver.bold(pkg.version)})`,
|
|
449
|
+
value: pkg.name,
|
|
450
|
+
selected: true
|
|
451
|
+
})),
|
|
452
|
+
min: 1,
|
|
453
|
+
hint: "Space to select/deselect. Return to submit.",
|
|
454
|
+
instructions: false
|
|
455
|
+
});
|
|
456
|
+
if (!response.selectedPackages || response.selectedPackages.length === 0) return [];
|
|
457
|
+
return response.selectedPackages;
|
|
458
|
+
}
|
|
459
|
+
async function promptVersionOverride(pkg, workspaceRoot, currentVersion, suggestedVersion, suggestedBumpType) {
|
|
460
|
+
const choices = [{
|
|
461
|
+
title: `Use suggested: ${suggestedVersion} (${suggestedBumpType})`,
|
|
462
|
+
value: "suggested"
|
|
463
|
+
}];
|
|
464
|
+
for (const bumpType of [
|
|
465
|
+
"patch",
|
|
466
|
+
"minor",
|
|
467
|
+
"major"
|
|
468
|
+
]) if (bumpType !== suggestedBumpType) {
|
|
469
|
+
const version = getNextVersion(currentVersion, bumpType);
|
|
470
|
+
choices.push({
|
|
471
|
+
title: `${bumpType}: ${version}`,
|
|
472
|
+
value: bumpType
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
choices.push({
|
|
476
|
+
title: "Custom version",
|
|
477
|
+
value: "custom"
|
|
478
|
+
});
|
|
479
|
+
const response = await prompts([{
|
|
480
|
+
type: "select",
|
|
481
|
+
name: "choice",
|
|
482
|
+
message: `${pkg.name} (${currentVersion}):`,
|
|
483
|
+
choices,
|
|
484
|
+
initial: 0
|
|
485
|
+
}, {
|
|
486
|
+
type: (prev) => prev === "custom" ? "text" : null,
|
|
487
|
+
name: "customVersion",
|
|
488
|
+
message: "Enter custom version:",
|
|
489
|
+
initial: suggestedVersion,
|
|
490
|
+
validate: (value) => {
|
|
491
|
+
return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(value) || "Invalid semver version (e.g., 1.0.0)";
|
|
492
|
+
}
|
|
493
|
+
}]);
|
|
494
|
+
if (response.choice === "suggested") return suggestedVersion;
|
|
495
|
+
else if (response.choice === "custom") return response.customVersion;
|
|
496
|
+
else return getNextVersion(currentVersion, response.choice);
|
|
497
|
+
}
|
|
498
|
+
|
|
395
499
|
//#endregion
|
|
396
500
|
//#region src/version.ts
|
|
397
501
|
function isValidSemver(version) {
|
|
@@ -400,11 +504,7 @@ function isValidSemver(version) {
|
|
|
400
504
|
function validateSemver(version) {
|
|
401
505
|
if (!isValidSemver(version)) throw new Error(`Invalid semver version: ${version}`);
|
|
402
506
|
}
|
|
403
|
-
|
|
404
|
-
* Calculate the new version based on current version and bump type
|
|
405
|
-
* Pure function - no side effects, easily testable
|
|
406
|
-
*/
|
|
407
|
-
function calculateNewVersion(currentVersion, bump) {
|
|
507
|
+
function getNextVersion(currentVersion, bump) {
|
|
408
508
|
if (bump === "none") return currentVersion;
|
|
409
509
|
validateSemver(currentVersion);
|
|
410
510
|
const match = currentVersion.match(/^(\d+)\.(\d+)\.(\d+)(.*)$/);
|
|
@@ -433,7 +533,7 @@ function calculateNewVersion(currentVersion, bump) {
|
|
|
433
533
|
* Create a version update object
|
|
434
534
|
*/
|
|
435
535
|
function createVersionUpdate(pkg, bump, hasDirectChanges) {
|
|
436
|
-
const newVersion =
|
|
536
|
+
const newVersion = getNextVersion(pkg.version, bump);
|
|
437
537
|
return {
|
|
438
538
|
package: pkg,
|
|
439
539
|
currentVersion: pkg.version,
|
|
@@ -443,8 +543,37 @@ function createVersionUpdate(pkg, bump, hasDirectChanges) {
|
|
|
443
543
|
};
|
|
444
544
|
}
|
|
445
545
|
/**
|
|
446
|
-
*
|
|
546
|
+
* Infer version updates from package commits with optional interactive overrides
|
|
547
|
+
*
|
|
548
|
+
* @param workspacePackages - All workspace packages
|
|
549
|
+
* @param packageCommits - Map of package names to their commits
|
|
550
|
+
* @param workspaceRoot - Root directory for prompts
|
|
551
|
+
* @param showPrompt - Whether to show prompts for version overrides
|
|
552
|
+
* @returns Version updates for packages with changes
|
|
447
553
|
*/
|
|
554
|
+
async function inferVersionUpdates(workspacePackages, packageCommits, workspaceRoot, showPrompt) {
|
|
555
|
+
const versionUpdates = [];
|
|
556
|
+
for (const [pkgName, commits] of packageCommits) {
|
|
557
|
+
if (commits.length === 0) continue;
|
|
558
|
+
const pkg = workspacePackages.find((p) => p.name === pkgName);
|
|
559
|
+
if (!pkg) continue;
|
|
560
|
+
const bump = determineHighestBump(commits);
|
|
561
|
+
if (bump === "none") {
|
|
562
|
+
logger.info(`No version bump needed for package ${pkg.name}`);
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
let newVersion = getNextVersion(pkg.version, bump);
|
|
566
|
+
if (!isCI && showPrompt) newVersion = await promptVersionOverride(pkg, workspaceRoot, pkg.version, newVersion, bump);
|
|
567
|
+
versionUpdates.push({
|
|
568
|
+
package: pkg,
|
|
569
|
+
currentVersion: pkg.version,
|
|
570
|
+
newVersion,
|
|
571
|
+
bumpType: bump,
|
|
572
|
+
hasDirectChanges: true
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
return versionUpdates;
|
|
576
|
+
}
|
|
448
577
|
async function updatePackageJson(pkg, newVersion, dependencyUpdates) {
|
|
449
578
|
const packageJsonPath = join(pkg.path, "package.json");
|
|
450
579
|
const content = await readFile(packageJsonPath, "utf-8");
|
|
@@ -569,148 +698,6 @@ async function updateAllPackageJsonFiles(updates) {
|
|
|
569
698
|
}));
|
|
570
699
|
}
|
|
571
700
|
|
|
572
|
-
//#endregion
|
|
573
|
-
//#region src/prompts.ts
|
|
574
|
-
/**
|
|
575
|
-
* Get commits for a package grouped by conventional commit type
|
|
576
|
-
*
|
|
577
|
-
* @param pkg - The workspace package
|
|
578
|
-
* @param workspaceRoot - Root directory of the workspace
|
|
579
|
-
* @param limit - Maximum number of commits to return (default: 10)
|
|
580
|
-
* @returns Commits grouped by type
|
|
581
|
-
*/
|
|
582
|
-
async function getCommitsForPackage(pkg, workspaceRoot, limit = 10) {
|
|
583
|
-
const limitedCommits = (await getPackageCommits(pkg, workspaceRoot)).slice(0, limit);
|
|
584
|
-
const grouped = {
|
|
585
|
-
feat: [],
|
|
586
|
-
fix: [],
|
|
587
|
-
perf: [],
|
|
588
|
-
chore: [],
|
|
589
|
-
docs: [],
|
|
590
|
-
style: [],
|
|
591
|
-
refactor: [],
|
|
592
|
-
test: [],
|
|
593
|
-
build: [],
|
|
594
|
-
ci: [],
|
|
595
|
-
revert: [],
|
|
596
|
-
other: []
|
|
597
|
-
};
|
|
598
|
-
for (const commit of limitedCommits) if (commit.type && commit.type in grouped) grouped[commit.type].push(commit);
|
|
599
|
-
else grouped.other.push(commit);
|
|
600
|
-
return grouped;
|
|
601
|
-
}
|
|
602
|
-
/**
|
|
603
|
-
* Format grouped commits into a readable string
|
|
604
|
-
*/
|
|
605
|
-
function formatCommitGroups(grouped) {
|
|
606
|
-
const lines = [];
|
|
607
|
-
const typeLabels = {
|
|
608
|
-
feat: "Features",
|
|
609
|
-
fix: "Bug Fixes",
|
|
610
|
-
perf: "Performance",
|
|
611
|
-
chore: "Chores",
|
|
612
|
-
docs: "Documentation",
|
|
613
|
-
style: "Styling",
|
|
614
|
-
refactor: "Refactoring",
|
|
615
|
-
test: "Tests",
|
|
616
|
-
build: "Build",
|
|
617
|
-
ci: "CI",
|
|
618
|
-
revert: "Reverts",
|
|
619
|
-
other: "Other"
|
|
620
|
-
};
|
|
621
|
-
for (const type of [
|
|
622
|
-
"feat",
|
|
623
|
-
"fix",
|
|
624
|
-
"perf",
|
|
625
|
-
"refactor",
|
|
626
|
-
"test",
|
|
627
|
-
"docs",
|
|
628
|
-
"style",
|
|
629
|
-
"build",
|
|
630
|
-
"ci",
|
|
631
|
-
"chore",
|
|
632
|
-
"revert",
|
|
633
|
-
"other"
|
|
634
|
-
]) {
|
|
635
|
-
const commits = grouped[type];
|
|
636
|
-
if (commits.length > 0) {
|
|
637
|
-
lines.push(`\n${typeLabels[type]}:`);
|
|
638
|
-
for (const commit of commits) {
|
|
639
|
-
const scope = commit.scope ? `(${commit.scope})` : "";
|
|
640
|
-
const breaking = commit.isBreaking ? " ⚠️ BREAKING" : "";
|
|
641
|
-
lines.push(` • ${commit.type}${scope}: ${commit.message}${breaking}`);
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
return lines.join("\n");
|
|
646
|
-
}
|
|
647
|
-
async function selectPackagePrompt(packages) {
|
|
648
|
-
const response = await prompts({
|
|
649
|
-
type: "multiselect",
|
|
650
|
-
name: "selectedPackages",
|
|
651
|
-
message: "Select packages to release",
|
|
652
|
-
choices: packages.map((pkg) => ({
|
|
653
|
-
title: `${pkg.name} (${farver.bold(pkg.version)})`,
|
|
654
|
-
value: pkg.name,
|
|
655
|
-
selected: true
|
|
656
|
-
})),
|
|
657
|
-
min: 1,
|
|
658
|
-
hint: "Space to select/deselect. Return to submit.",
|
|
659
|
-
instructions: false
|
|
660
|
-
});
|
|
661
|
-
if (!response.selectedPackages || response.selectedPackages.length === 0) return [];
|
|
662
|
-
return response.selectedPackages;
|
|
663
|
-
}
|
|
664
|
-
async function promptVersionOverride(pkg, workspaceRoot, currentVersion, suggestedVersion, suggestedBumpType) {
|
|
665
|
-
const commitSummary = formatCommitGroups(await getCommitsForPackage(pkg, workspaceRoot));
|
|
666
|
-
if (commitSummary.trim()) console.log(`\nRecent changes in ${pkg.name}:${commitSummary}\n`);
|
|
667
|
-
const choices = [{
|
|
668
|
-
title: `Use suggested: ${suggestedVersion} (${suggestedBumpType})`,
|
|
669
|
-
value: "suggested"
|
|
670
|
-
}];
|
|
671
|
-
for (const bumpType of [
|
|
672
|
-
"patch",
|
|
673
|
-
"minor",
|
|
674
|
-
"major"
|
|
675
|
-
]) if (bumpType !== suggestedBumpType) {
|
|
676
|
-
const version = calculateNewVersion(currentVersion, bumpType);
|
|
677
|
-
choices.push({
|
|
678
|
-
title: `${bumpType}: ${version}`,
|
|
679
|
-
value: bumpType
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
choices.push({
|
|
683
|
-
title: "Custom version",
|
|
684
|
-
value: "custom"
|
|
685
|
-
});
|
|
686
|
-
const response = await prompts([{
|
|
687
|
-
type: "select",
|
|
688
|
-
name: "choice",
|
|
689
|
-
message: `${pkg.name} (${currentVersion}):`,
|
|
690
|
-
choices,
|
|
691
|
-
initial: 0
|
|
692
|
-
}, {
|
|
693
|
-
type: (prev) => prev === "custom" ? "text" : null,
|
|
694
|
-
name: "customVersion",
|
|
695
|
-
message: "Enter custom version:",
|
|
696
|
-
initial: suggestedVersion,
|
|
697
|
-
validate: (value) => {
|
|
698
|
-
return /^\d+\.\d+\.\d+(?:[-+].+)?$/.test(value) || "Invalid semver version (e.g., 1.0.0)";
|
|
699
|
-
}
|
|
700
|
-
}]);
|
|
701
|
-
if (response.choice === "suggested") return suggestedVersion;
|
|
702
|
-
else if (response.choice === "custom") return response.customVersion;
|
|
703
|
-
else return calculateNewVersion(currentVersion, response.choice);
|
|
704
|
-
}
|
|
705
|
-
async function promptVersionOverrides(packages, workspaceRoot) {
|
|
706
|
-
const overrides = /* @__PURE__ */ new Map();
|
|
707
|
-
for (const item of packages) {
|
|
708
|
-
const newVersion = await promptVersionOverride(item.package, workspaceRoot, item.currentVersion, item.suggestedVersion, item.bumpType);
|
|
709
|
-
overrides.set(item.package.name, newVersion);
|
|
710
|
-
}
|
|
711
|
-
return overrides;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
701
|
//#endregion
|
|
715
702
|
//#region src/workspace.ts
|
|
716
703
|
async function discoverWorkspacePackages(workspaceRoot, options) {
|
|
@@ -720,29 +707,25 @@ async function discoverWorkspacePackages(workspaceRoot, options) {
|
|
|
720
707
|
else if (Array.isArray(options.packages)) {
|
|
721
708
|
workspaceOptions = {
|
|
722
709
|
excludePrivate: false,
|
|
723
|
-
|
|
710
|
+
include: options.packages
|
|
724
711
|
};
|
|
725
712
|
explicitPackages = options.packages;
|
|
726
713
|
} else {
|
|
727
714
|
workspaceOptions = options.packages;
|
|
728
|
-
if (options.packages.
|
|
715
|
+
if (options.packages.include) explicitPackages = options.packages.include;
|
|
729
716
|
}
|
|
730
|
-
|
|
717
|
+
let workspacePackages = await findWorkspacePackages(workspaceRoot, workspaceOptions);
|
|
731
718
|
if (explicitPackages) {
|
|
732
719
|
const foundNames = new Set(workspacePackages.map((p) => p.name));
|
|
733
720
|
const missing = explicitPackages.filter((p) => !foundNames.has(p));
|
|
734
|
-
if (missing.length > 0)
|
|
721
|
+
if (missing.length > 0) exitWithError(`Package${missing.length > 1 ? "s" : ""} not found in workspace: ${missing.join(", ")}`, "Check your package names or run 'pnpm ls' to see available packages");
|
|
735
722
|
}
|
|
736
|
-
let packagesToAnalyze = workspacePackages;
|
|
737
723
|
const isPackagePromptEnabled = options.prompts?.packages !== false;
|
|
738
724
|
if (!isCI && isPackagePromptEnabled && !explicitPackages) {
|
|
739
725
|
const selectedNames = await selectPackagePrompt(workspacePackages);
|
|
740
|
-
|
|
726
|
+
workspacePackages = workspacePackages.filter((pkg) => selectedNames.includes(pkg.name));
|
|
741
727
|
}
|
|
742
|
-
return
|
|
743
|
-
workspacePackages,
|
|
744
|
-
packagesToAnalyze
|
|
745
|
-
};
|
|
728
|
+
return workspacePackages;
|
|
746
729
|
}
|
|
747
730
|
async function findWorkspacePackages(workspaceRoot, options) {
|
|
748
731
|
try {
|
|
@@ -769,131 +752,105 @@ async function findWorkspacePackages(workspaceRoot, options) {
|
|
|
769
752
|
version: rawProject.version,
|
|
770
753
|
path: rawProject.path,
|
|
771
754
|
packageJson,
|
|
772
|
-
workspaceDependencies:
|
|
773
|
-
|
|
755
|
+
workspaceDependencies: Object.keys(rawProject.dependencies || []).filter((dep) => {
|
|
756
|
+
return allPackageNames.has(dep);
|
|
757
|
+
}),
|
|
758
|
+
workspaceDevDependencies: Object.keys(rawProject.devDependencies || []).filter((dep) => {
|
|
759
|
+
return allPackageNames.has(dep);
|
|
760
|
+
})
|
|
774
761
|
};
|
|
775
762
|
});
|
|
776
763
|
const packages = await Promise.all(promises);
|
|
777
|
-
if (excludedPackages.size > 0)
|
|
764
|
+
if (excludedPackages.size > 0) logger.info(`Excluded packages: ${farver.green(Array.from(excludedPackages).join(", "))}`);
|
|
778
765
|
return packages.filter((pkg) => pkg !== null);
|
|
779
766
|
} catch (err) {
|
|
780
|
-
|
|
767
|
+
logger.error("Error discovering workspace packages:", err);
|
|
781
768
|
throw err;
|
|
782
769
|
}
|
|
783
770
|
}
|
|
784
771
|
function shouldIncludePackage(pkg, options) {
|
|
785
772
|
if (!options) return true;
|
|
786
773
|
if (options.excludePrivate && pkg.private) return false;
|
|
787
|
-
if (options.
|
|
788
|
-
if (!options.
|
|
774
|
+
if (options.include && options.include.length > 0) {
|
|
775
|
+
if (!options.include.includes(pkg.name)) return false;
|
|
789
776
|
}
|
|
790
|
-
if (options.
|
|
777
|
+
if (options.exclude?.includes(pkg.name)) return false;
|
|
791
778
|
return true;
|
|
792
779
|
}
|
|
793
|
-
function extractWorkspaceDependencies(dependencies, workspacePackages) {
|
|
794
|
-
if (!dependencies) return [];
|
|
795
|
-
return Object.keys(dependencies).filter((dep) => {
|
|
796
|
-
return workspacePackages.has(dep);
|
|
797
|
-
});
|
|
798
|
-
}
|
|
799
780
|
|
|
800
781
|
//#endregion
|
|
801
782
|
//#region src/release.ts
|
|
802
783
|
async function release(options) {
|
|
803
|
-
const
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
if (packagesToAnalyze.length === 0) {
|
|
814
|
-
console.log("No packages found to analyze for release.");
|
|
784
|
+
const normalizedOptions = normalizeSharedOptions(options);
|
|
785
|
+
normalizedOptions.dryRun ??= false;
|
|
786
|
+
normalizedOptions.releaseBranch ??= "release/next";
|
|
787
|
+
normalizedOptions.safeguards ??= true;
|
|
788
|
+
globalOptions.dryRun = normalizedOptions.dryRun;
|
|
789
|
+
const workspaceRoot = normalizedOptions.workspaceRoot;
|
|
790
|
+
if (normalizedOptions.safeguards && !await isWorkingDirectoryClean(workspaceRoot)) exitWithError("Working directory is not clean. Please commit or stash your changes before proceeding.");
|
|
791
|
+
const workspacePackages = await discoverWorkspacePackages(workspaceRoot, options);
|
|
792
|
+
if (workspacePackages.length === 0) {
|
|
793
|
+
logger.log("No packages found to analyze for release.");
|
|
815
794
|
return null;
|
|
816
795
|
}
|
|
817
|
-
const
|
|
818
|
-
if (
|
|
819
|
-
let versionUpdates = [];
|
|
820
|
-
for (const [pkgName, bump] of changedPackages) {
|
|
821
|
-
const pkg = workspacePackages.find((p) => p.name === pkgName);
|
|
822
|
-
if (pkg) versionUpdates.push(createVersionUpdate(pkg, bump, true));
|
|
823
|
-
}
|
|
824
|
-
const isVersionPromptEnabled = options.prompts?.versions !== false;
|
|
825
|
-
if (!isCI && isVersionPromptEnabled) {
|
|
826
|
-
const versionOverrides = await promptVersionOverrides(versionUpdates.map((u) => ({
|
|
827
|
-
package: u.package,
|
|
828
|
-
currentVersion: u.currentVersion,
|
|
829
|
-
suggestedVersion: u.newVersion,
|
|
830
|
-
bumpType: u.bumpType
|
|
831
|
-
})), workspaceRoot);
|
|
832
|
-
versionUpdates = versionUpdates.map((update) => {
|
|
833
|
-
const overriddenVersion = versionOverrides.get(update.package.name);
|
|
834
|
-
if (overriddenVersion && overriddenVersion !== update.newVersion) return {
|
|
835
|
-
...update,
|
|
836
|
-
newVersion: overriddenVersion
|
|
837
|
-
};
|
|
838
|
-
return update;
|
|
839
|
-
});
|
|
840
|
-
}
|
|
796
|
+
const versionUpdates = await inferVersionUpdates(workspacePackages, await getWorkspacePackageCommits(workspaceRoot, workspacePackages), workspaceRoot, options.prompts?.versions !== false);
|
|
797
|
+
if (versionUpdates.length === 0) exitWithError("No packages have changes requiring a release", "Make sure you have commits since the last release");
|
|
841
798
|
const allUpdates = createDependentUpdates(buildPackageDependencyGraph(workspacePackages), workspacePackages, versionUpdates);
|
|
842
799
|
const currentBranch = await getCurrentBranch(workspaceRoot);
|
|
843
800
|
const existingPullRequest = await getExistingPullRequest({
|
|
844
|
-
owner,
|
|
845
|
-
repo,
|
|
846
|
-
branch: releaseBranch,
|
|
847
|
-
githubToken
|
|
801
|
+
owner: normalizedOptions.owner,
|
|
802
|
+
repo: normalizedOptions.repo,
|
|
803
|
+
branch: normalizedOptions.releaseBranch,
|
|
804
|
+
githubToken: normalizedOptions.githubToken
|
|
848
805
|
});
|
|
849
806
|
const prExists = !!existingPullRequest;
|
|
850
|
-
if (prExists)
|
|
851
|
-
else
|
|
852
|
-
const branchExists = await doesBranchExist(releaseBranch, workspaceRoot);
|
|
807
|
+
if (prExists) logger.log("Existing pull request found:", existingPullRequest.html_url);
|
|
808
|
+
else logger.log("No existing pull request found, will create new one");
|
|
809
|
+
const branchExists = await doesBranchExist(normalizedOptions.releaseBranch, workspaceRoot);
|
|
853
810
|
if (!branchExists) {
|
|
854
|
-
|
|
855
|
-
await createBranch(releaseBranch, currentBranch, workspaceRoot);
|
|
811
|
+
logger.log("Creating release branch:", normalizedOptions.releaseBranch);
|
|
812
|
+
await createBranch(normalizedOptions.releaseBranch, currentBranch, workspaceRoot);
|
|
856
813
|
}
|
|
857
|
-
if (!await checkoutBranch(releaseBranch, workspaceRoot)) throw new Error(`Failed to checkout branch: ${releaseBranch}`);
|
|
814
|
+
if (!await checkoutBranch(normalizedOptions.releaseBranch, workspaceRoot)) throw new Error(`Failed to checkout branch: ${normalizedOptions.releaseBranch}`);
|
|
858
815
|
if (branchExists) {
|
|
859
|
-
|
|
860
|
-
if (!await pullLatestChanges(releaseBranch, workspaceRoot))
|
|
816
|
+
logger.log("Pulling latest changes from remote");
|
|
817
|
+
if (!await pullLatestChanges(normalizedOptions.releaseBranch, workspaceRoot)) logger.log("Warning: Failed to pull latest changes, continuing anyway");
|
|
861
818
|
}
|
|
862
|
-
|
|
819
|
+
logger.log("Rebasing release branch onto", currentBranch);
|
|
863
820
|
await rebaseBranch(currentBranch, workspaceRoot);
|
|
864
821
|
await updateAllPackageJsonFiles(allUpdates);
|
|
865
822
|
const hasCommitted = await commitChanges("chore: update release versions", workspaceRoot);
|
|
866
|
-
const isBranchAhead = await isBranchAheadOfRemote(releaseBranch, workspaceRoot);
|
|
823
|
+
const isBranchAhead = await isBranchAheadOfRemote(normalizedOptions.releaseBranch, workspaceRoot);
|
|
867
824
|
if (!hasCommitted && !isBranchAhead) {
|
|
868
|
-
|
|
825
|
+
logger.log("No changes to commit and branch is in sync with remote");
|
|
869
826
|
await checkoutBranch(currentBranch, workspaceRoot);
|
|
870
827
|
if (prExists) {
|
|
871
|
-
|
|
828
|
+
logger.log("No updates needed, PR is already up to date");
|
|
872
829
|
return {
|
|
873
830
|
updates: allUpdates,
|
|
874
831
|
prUrl: existingPullRequest.html_url,
|
|
875
832
|
created: false
|
|
876
833
|
};
|
|
877
834
|
} else {
|
|
878
|
-
|
|
835
|
+
logger.error("No changes to commit, and no existing PR. Nothing to do.");
|
|
879
836
|
return null;
|
|
880
837
|
}
|
|
881
838
|
}
|
|
882
|
-
|
|
883
|
-
await pushBranch(releaseBranch, workspaceRoot, { forceWithLease: true });
|
|
839
|
+
logger.log("Pushing changes to remote");
|
|
840
|
+
await pushBranch(normalizedOptions.releaseBranch, workspaceRoot, { forceWithLease: true });
|
|
884
841
|
const prTitle = existingPullRequest?.title || options.pullRequest?.title || "chore: update package versions";
|
|
885
842
|
const prBody = generatePullRequestBody(allUpdates, options.pullRequest?.body);
|
|
886
843
|
const pullRequest = await upsertPullRequest({
|
|
887
|
-
owner,
|
|
888
|
-
repo,
|
|
844
|
+
owner: normalizedOptions.owner,
|
|
845
|
+
repo: normalizedOptions.repo,
|
|
889
846
|
pullNumber: existingPullRequest?.number,
|
|
890
847
|
title: prTitle,
|
|
891
848
|
body: prBody,
|
|
892
|
-
head: releaseBranch,
|
|
849
|
+
head: normalizedOptions.releaseBranch,
|
|
893
850
|
base: currentBranch,
|
|
894
|
-
githubToken
|
|
851
|
+
githubToken: normalizedOptions.githubToken
|
|
895
852
|
});
|
|
896
|
-
|
|
853
|
+
logger.log(prExists ? "Updated pull request:" : "Created pull request:", pullRequest?.html_url);
|
|
897
854
|
await checkoutBranch(currentBranch, workspaceRoot);
|
|
898
855
|
return {
|
|
899
856
|
updates: allUpdates,
|
|
@@ -903,4 +860,4 @@ async function release(options) {
|
|
|
903
860
|
}
|
|
904
861
|
|
|
905
862
|
//#endregion
|
|
906
|
-
export { release };
|
|
863
|
+
export { publish, release };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ucdjs/release-scripts",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.6",
|
|
4
4
|
"description": "@ucdjs release scripts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@luxass/utils": "2.7.2",
|
|
23
23
|
"commit-parser": "0.4.5",
|
|
24
|
-
"eta": "4.0.1",
|
|
25
24
|
"farver": "1.0.0-beta.1",
|
|
26
25
|
"prompts": "2.4.2",
|
|
27
26
|
"tinyexec": "1.0.2"
|
|
@@ -31,6 +30,7 @@
|
|
|
31
30
|
"@types/node": "22.18.12",
|
|
32
31
|
"@types/prompts": "2.4.9",
|
|
33
32
|
"eslint": "9.39.1",
|
|
33
|
+
"eta": "4.0.1",
|
|
34
34
|
"tsdown": "0.16.0",
|
|
35
35
|
"typescript": "5.9.3",
|
|
36
36
|
"vitest": "4.0.4"
|