@ucdjs/release-scripts 0.1.0-beta.24 → 0.1.0-beta.25

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.
@@ -0,0 +1,481 @@
1
+ import * as fs from "node:fs";
2
+ import * as path$2 from "node:path";
3
+
4
+ //#region node_modules/.pnpm/eta@4.5.1/node_modules/eta/dist/index.mjs
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
+ err.cause = originalError;
57
+ throw err;
58
+ }
59
+ function readFile(path$1) {
60
+ let res = "";
61
+ try {
62
+ res = fs.readFileSync(path$1, "utf8");
63
+ } catch (err) {
64
+ if (err?.code === "ENOENT") throw new EtaFileResolutionError(`Could not find template: ${path$1}`);
65
+ else throw err;
66
+ }
67
+ return res;
68
+ }
69
+ function resolvePath(templatePath, options) {
70
+ let resolvedFilePath = "";
71
+ const views = this.config.views;
72
+ if (!views) throw new EtaFileResolutionError("Views directory is not defined");
73
+ const baseFilePath = options?.filepath;
74
+ const defaultExtension = this.config.defaultExtension === void 0 ? ".eta" : this.config.defaultExtension;
75
+ const cacheIndex = JSON.stringify({
76
+ filename: baseFilePath,
77
+ path: templatePath,
78
+ views: this.config.views
79
+ });
80
+ templatePath += path$2.extname(templatePath) ? "" : defaultExtension;
81
+ if (baseFilePath) {
82
+ if (this.config.cacheFilepaths && this.filepathCache[cacheIndex]) return this.filepathCache[cacheIndex];
83
+ if (absolutePathRegExp.exec(templatePath)?.length) {
84
+ const formattedPath = templatePath.replace(/^\/*|^\\*/, "");
85
+ resolvedFilePath = path$2.join(views, formattedPath);
86
+ } else resolvedFilePath = path$2.join(path$2.dirname(baseFilePath), templatePath);
87
+ } else resolvedFilePath = path$2.join(views, templatePath);
88
+ if (dirIsChild(views, resolvedFilePath)) {
89
+ if (baseFilePath && this.config.cacheFilepaths) this.filepathCache[cacheIndex] = resolvedFilePath;
90
+ return resolvedFilePath;
91
+ } else throw new EtaFileResolutionError(`Template '${templatePath}' is not in the views directory`);
92
+ }
93
+ function dirIsChild(parent, dir) {
94
+ const relative = path$2.relative(parent, dir);
95
+ return relative && !relative.startsWith("..") && !path$2.isAbsolute(relative);
96
+ }
97
+ const absolutePathRegExp = /^\\|^\//;
98
+ /* istanbul ignore next */
99
+ const AsyncFunction = (async () => {}).constructor;
100
+ /**
101
+ * Takes a template string and returns a template function that can be called with (data, config)
102
+ *
103
+ * @param str - The template string
104
+ * @param config - A custom configuration object (optional)
105
+ */
106
+ function compile(str, options) {
107
+ const config = this.config;
108
+ const ctor = options?.async ? AsyncFunction : Function;
109
+ try {
110
+ return new ctor(config.varName, "options", this.compileToString.call(this, str, options));
111
+ } catch (e) {
112
+ 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");
113
+ else throw e;
114
+ }
115
+ }
116
+ /**
117
+ * 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
118
+ */
119
+ function compileToString(str, options) {
120
+ const config = this.config;
121
+ const isAsync = options?.async;
122
+ const compileBody$1 = this.compileBody;
123
+ const buffer = this.parse.call(this, str);
124
+ let res = `${config.functionHeader}
125
+ let include = (__eta_t, __eta_d) => this.render(__eta_t, {...${config.varName}, ...(__eta_d ?? {})}, options);
126
+ let includeAsync = (__eta_t, __eta_d) => this.renderAsync(__eta_t, {...${config.varName}, ...(__eta_d ?? {})}, options);
127
+
128
+ 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") + "\"" : ""}};
129
+
130
+ function layout(path, data) {
131
+ __eta.layout = path;
132
+ __eta.layoutData = data;
133
+ }${config.debug ? "try {" : ""}${config.useWith ? "with(" + config.varName + "||{}){" : ""}
134
+
135
+ function ${config.outputFunctionName}(s){__eta.res+=s;}
136
+
137
+ ${compileBody$1.call(this, buffer)}
138
+ if (__eta.layout) {
139
+ __eta.res = ${isAsync ? "await includeAsync" : "include"} (__eta.layout, {...${config.varName}, body: __eta.res, ...__eta.layoutData});
140
+ }
141
+ ${config.useWith ? "}" : ""}${config.debug ? "} catch (e) { this.RuntimeErr(e, __eta.templateStr, __eta.line, options.filepath) }" : ""}
142
+ return __eta.res;
143
+ `;
144
+ if (config.plugins) for (let i = 0; i < config.plugins.length; i++) {
145
+ const plugin = config.plugins[i];
146
+ if (plugin.processFnString) res = plugin.processFnString(res, config);
147
+ }
148
+ return res;
149
+ }
150
+ /**
151
+ * Loops through the AST generated by `parse` and transform each item into JS calls
152
+ *
153
+ * **Example**
154
+ *
155
+ * ```js
156
+ * let templateAST = ['Hi ', { val: 'it.name', t: 'i' }]
157
+ * compileBody.call(Eta, templateAST)
158
+ * // => "__eta.res+='Hi '\n__eta.res+=__eta.e(it.name)\n"
159
+ * ```
160
+ */
161
+ function compileBody(buff) {
162
+ const config = this.config;
163
+ let i = 0;
164
+ const buffLength = buff.length;
165
+ let returnStr = "";
166
+ for (; i < buffLength; i++) {
167
+ const currentBlock = buff[i];
168
+ if (typeof currentBlock === "string") returnStr += "__eta.res+='" + currentBlock + "';\n";
169
+ else {
170
+ const type = currentBlock.t;
171
+ let content = currentBlock.val || "";
172
+ if (config.debug) returnStr += "__eta.line=" + currentBlock.lineNo + "\n";
173
+ if (type === "r") {
174
+ if (config.autoFilter) content = "__eta.f(" + content + ")";
175
+ returnStr += "__eta.res+=" + content + ";\n";
176
+ } else if (type === "i") {
177
+ if (config.autoFilter) content = "__eta.f(" + content + ")";
178
+ if (config.autoEscape) content = "__eta.e(" + content + ")";
179
+ returnStr += "__eta.res+=" + content + ";\n";
180
+ } else if (type === "e") returnStr += content + "\n";
181
+ }
182
+ }
183
+ return returnStr;
184
+ }
185
+ /**
186
+ * Takes a string within a template and trims it, based on the preceding tag's whitespace control and `config.autoTrim`
187
+ */
188
+ function trimWS(str, config, wsLeft, wsRight) {
189
+ let leftTrim;
190
+ let rightTrim;
191
+ if (Array.isArray(config.autoTrim)) {
192
+ leftTrim = config.autoTrim[1];
193
+ rightTrim = config.autoTrim[0];
194
+ } else leftTrim = rightTrim = config.autoTrim;
195
+ if (wsLeft || wsLeft === false) leftTrim = wsLeft;
196
+ if (wsRight || wsRight === false) rightTrim = wsRight;
197
+ if (!rightTrim && !leftTrim) return str;
198
+ if (leftTrim === "slurp" && rightTrim === "slurp") return str.trim();
199
+ if (leftTrim === "_" || leftTrim === "slurp") str = str.trimStart();
200
+ else if (leftTrim === "-" || leftTrim === "nl") str = str.replace(/^(?:\r\n|\n|\r)/, "");
201
+ if (rightTrim === "_" || rightTrim === "slurp") str = str.trimEnd();
202
+ else if (rightTrim === "-" || rightTrim === "nl") str = str.replace(/(?:\r\n|\n|\r)$/, "");
203
+ return str;
204
+ }
205
+ /**
206
+ * A map of special HTML characters to their XML-escaped equivalents
207
+ */
208
+ const escMap = {
209
+ "&": "&amp;",
210
+ "<": "&lt;",
211
+ ">": "&gt;",
212
+ "\"": "&quot;",
213
+ "'": "&#39;"
214
+ };
215
+ function replaceChar(s) {
216
+ return escMap[s];
217
+ }
218
+ /**
219
+ * XML-escapes an input value after converting it to a string
220
+ *
221
+ * @param str - Input value (usually a string)
222
+ * @returns XML-escaped string
223
+ */
224
+ function XMLEscape(str) {
225
+ const newStr = String(str);
226
+ if (/[&<>"']/.test(newStr)) return newStr.replace(/[&<>"']/g, replaceChar);
227
+ else return newStr;
228
+ }
229
+ /** Eta's base (global) configuration */
230
+ const defaultConfig = {
231
+ autoEscape: true,
232
+ autoFilter: false,
233
+ autoTrim: [false, "nl"],
234
+ cache: false,
235
+ cacheFilepaths: true,
236
+ debug: false,
237
+ escapeFunction: XMLEscape,
238
+ filterFunction: (val) => String(val),
239
+ outputFunctionName: "output",
240
+ functionHeader: "",
241
+ parse: {
242
+ exec: "",
243
+ interpolate: "=",
244
+ raw: "~"
245
+ },
246
+ plugins: [],
247
+ rmWhitespace: false,
248
+ tags: ["<%", "%>"],
249
+ useWith: false,
250
+ varName: "it",
251
+ defaultExtension: ".eta"
252
+ };
253
+ const templateLitReg = /`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})*}|(?!\${)[^\\`])*`/g;
254
+ const singleQuoteReg = /'(?:\\[\s\w"'\\`]|[^\n\r'\\])*?'/g;
255
+ const doubleQuoteReg = /"(?:\\[\s\w"'\\`]|[^\n\r"\\])*?"/g;
256
+ /** Escape special regular expression characters inside a string */
257
+ function escapeRegExp(string) {
258
+ return string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&");
259
+ }
260
+ function getLineNo(str, index) {
261
+ return str.slice(0, index).split("\n").length;
262
+ }
263
+ function parse(str) {
264
+ const config = this.config;
265
+ let buffer = [];
266
+ let trimLeftOfNextStr = false;
267
+ let lastIndex = 0;
268
+ const parseOptions = config.parse;
269
+ if (config.plugins) for (let i = 0; i < config.plugins.length; i++) {
270
+ const plugin = config.plugins[i];
271
+ if (plugin.processTemplate) str = plugin.processTemplate(str, config);
272
+ }
273
+ if (config.rmWhitespace) str = str.replace(/[\r\n]+/g, "\n").replace(/^\s+|\s+$/gm, "");
274
+ templateLitReg.lastIndex = 0;
275
+ singleQuoteReg.lastIndex = 0;
276
+ doubleQuoteReg.lastIndex = 0;
277
+ function pushString(strng, shouldTrimRightOfString) {
278
+ if (strng) {
279
+ strng = trimWS(strng, config, trimLeftOfNextStr, shouldTrimRightOfString);
280
+ if (strng) {
281
+ strng = strng.replace(/\\|'/g, "\\$&").replace(/\r\n|\n|\r/g, "\\n");
282
+ buffer.push(strng);
283
+ }
284
+ }
285
+ }
286
+ const prefixes = [
287
+ parseOptions.exec,
288
+ parseOptions.interpolate,
289
+ parseOptions.raw
290
+ ].reduce((accumulator, prefix) => {
291
+ if (accumulator && prefix) return accumulator + "|" + escapeRegExp(prefix);
292
+ else if (prefix) return escapeRegExp(prefix);
293
+ else return accumulator;
294
+ }, "");
295
+ const parseOpenReg = new RegExp(escapeRegExp(config.tags[0]) + "(-|_)?\\s*(" + prefixes + ")?\\s*", "g");
296
+ const parseCloseReg = new RegExp("'|\"|`|\\/\\*|(\\s*(-|_)?" + escapeRegExp(config.tags[1]) + ")", "g");
297
+ let m;
298
+ while (m = parseOpenReg.exec(str)) {
299
+ const precedingString = str.slice(lastIndex, m.index);
300
+ lastIndex = m[0].length + m.index;
301
+ const wsLeft = m[1];
302
+ const prefix = m[2] || "";
303
+ pushString(precedingString, wsLeft);
304
+ parseCloseReg.lastIndex = lastIndex;
305
+ let closeTag;
306
+ let currentObj = false;
307
+ while (closeTag = parseCloseReg.exec(str)) if (closeTag[1]) {
308
+ const content = str.slice(lastIndex, closeTag.index);
309
+ parseOpenReg.lastIndex = lastIndex = parseCloseReg.lastIndex;
310
+ trimLeftOfNextStr = closeTag[2];
311
+ currentObj = {
312
+ t: prefix === parseOptions.exec ? "e" : prefix === parseOptions.raw ? "r" : prefix === parseOptions.interpolate ? "i" : "",
313
+ val: content
314
+ };
315
+ break;
316
+ } else {
317
+ const char = closeTag[0];
318
+ if (char === "/*") {
319
+ const commentCloseInd = str.indexOf("*/", parseCloseReg.lastIndex);
320
+ if (commentCloseInd === -1) ParseErr("unclosed comment", str, closeTag.index);
321
+ parseCloseReg.lastIndex = commentCloseInd;
322
+ } else if (char === "'") {
323
+ singleQuoteReg.lastIndex = closeTag.index;
324
+ if (singleQuoteReg.exec(str)) parseCloseReg.lastIndex = singleQuoteReg.lastIndex;
325
+ else ParseErr("unclosed string", str, closeTag.index);
326
+ } else if (char === "\"") {
327
+ doubleQuoteReg.lastIndex = closeTag.index;
328
+ if (doubleQuoteReg.exec(str)) parseCloseReg.lastIndex = doubleQuoteReg.lastIndex;
329
+ else ParseErr("unclosed string", str, closeTag.index);
330
+ } else if (char === "`") {
331
+ templateLitReg.lastIndex = closeTag.index;
332
+ if (templateLitReg.exec(str)) parseCloseReg.lastIndex = templateLitReg.lastIndex;
333
+ else ParseErr("unclosed string", str, closeTag.index);
334
+ }
335
+ }
336
+ if (currentObj) {
337
+ if (config.debug) currentObj.lineNo = getLineNo(str, m.index);
338
+ buffer.push(currentObj);
339
+ } else ParseErr("unclosed tag", str, m.index);
340
+ }
341
+ pushString(str.slice(lastIndex, str.length), false);
342
+ if (config.plugins) for (let i = 0; i < config.plugins.length; i++) {
343
+ const plugin = config.plugins[i];
344
+ if (plugin.processAST) buffer = plugin.processAST(buffer, config);
345
+ }
346
+ return buffer;
347
+ }
348
+ function handleCache(template, options) {
349
+ const templateStore = options?.async ? this.templatesAsync : this.templatesSync;
350
+ if (this.resolvePath && this.readFile && !template.startsWith("@")) {
351
+ const templatePath = options.filepath;
352
+ const cachedTemplate = templateStore.get(templatePath);
353
+ if (this.config.cache && cachedTemplate) return cachedTemplate;
354
+ else {
355
+ const templateString = this.readFile(templatePath);
356
+ const templateFn = this.compile(templateString, options);
357
+ if (this.config.cache) templateStore.define(templatePath, templateFn);
358
+ return templateFn;
359
+ }
360
+ } else {
361
+ const cachedTemplate = templateStore.get(template);
362
+ if (cachedTemplate) return cachedTemplate;
363
+ else throw new EtaNameResolutionError(`Failed to get template '${template}'`);
364
+ }
365
+ }
366
+ function render(template, data, meta) {
367
+ let templateFn;
368
+ const options = {
369
+ ...meta,
370
+ async: false
371
+ };
372
+ if (typeof template === "string") {
373
+ if (this.resolvePath && this.readFile && !template.startsWith("@")) options.filepath = this.resolvePath(template, options);
374
+ templateFn = handleCache.call(this, template, options);
375
+ } else templateFn = template;
376
+ return templateFn.call(this, data, options);
377
+ }
378
+ function renderAsync(template, data, meta) {
379
+ let templateFn;
380
+ const options = {
381
+ ...meta,
382
+ async: true
383
+ };
384
+ if (typeof template === "string") {
385
+ if (this.resolvePath && this.readFile && !template.startsWith("@")) options.filepath = this.resolvePath(template, options);
386
+ templateFn = handleCache.call(this, template, options);
387
+ } else templateFn = template;
388
+ const res = templateFn.call(this, data, options);
389
+ return Promise.resolve(res);
390
+ }
391
+ function renderString(template, data) {
392
+ const templateFn = this.compile(template, { async: false });
393
+ return render.call(this, templateFn, data);
394
+ }
395
+ function renderStringAsync(template, data) {
396
+ const templateFn = this.compile(template, { async: true });
397
+ return renderAsync.call(this, templateFn, data);
398
+ }
399
+ /**
400
+ * Handles storage and accessing of values
401
+ *
402
+ * In this case, we use it to store compiled template functions
403
+ * Indexed by their `name` or `filename`
404
+ */
405
+ var Cacher = class {
406
+ constructor(cache) {
407
+ this.cache = cache;
408
+ }
409
+ define(key, val) {
410
+ this.cache[key] = val;
411
+ }
412
+ get(key) {
413
+ return this.cache[key];
414
+ }
415
+ remove(key) {
416
+ delete this.cache[key];
417
+ }
418
+ reset() {
419
+ this.cache = {};
420
+ }
421
+ load(cacheObj) {
422
+ this.cache = {
423
+ ...this.cache,
424
+ ...cacheObj
425
+ };
426
+ }
427
+ };
428
+ var Eta$1 = class {
429
+ constructor(customConfig) {
430
+ if (customConfig) this.config = {
431
+ ...defaultConfig,
432
+ ...customConfig
433
+ };
434
+ else this.config = { ...defaultConfig };
435
+ }
436
+ config;
437
+ RuntimeErr = RuntimeErr;
438
+ compile = compile;
439
+ compileToString = compileToString;
440
+ compileBody = compileBody;
441
+ parse = parse;
442
+ render = render;
443
+ renderAsync = renderAsync;
444
+ renderString = renderString;
445
+ renderStringAsync = renderStringAsync;
446
+ filepathCache = {};
447
+ templatesSync = new Cacher({});
448
+ templatesAsync = new Cacher({});
449
+ resolvePath = null;
450
+ readFile = null;
451
+ configure(customConfig) {
452
+ this.config = {
453
+ ...this.config,
454
+ ...customConfig
455
+ };
456
+ }
457
+ withConfig(customConfig) {
458
+ return {
459
+ ...this,
460
+ config: {
461
+ ...this.config,
462
+ ...customConfig
463
+ }
464
+ };
465
+ }
466
+ loadTemplate(name, template, options) {
467
+ if (typeof template === "string") (options?.async ? this.templatesAsync : this.templatesSync).define(name, this.compile(template, options));
468
+ else {
469
+ let templates = this.templatesSync;
470
+ if (template.constructor.name === "AsyncFunction" || options?.async) templates = this.templatesAsync;
471
+ templates.define(name, template);
472
+ }
473
+ }
474
+ };
475
+ var Eta = class extends Eta$1 {
476
+ readFile = readFile;
477
+ resolvePath = resolvePath;
478
+ };
479
+
480
+ //#endregion
481
+ export { Eta as t };
package/dist/index.d.mts CHANGED
@@ -24,16 +24,20 @@ interface ReleaseScriptsOptionsInput {
24
24
  };
25
25
  types?: Record<string, {
26
26
  title: string;
27
+ color?: string;
27
28
  }>;
28
29
  changelog?: {
29
30
  enabled?: boolean;
30
31
  template?: string;
31
32
  emojis?: boolean;
32
33
  };
34
+ npm?: {
35
+ otp?: string;
36
+ provenance?: boolean;
37
+ };
33
38
  }
34
39
  //#endregion
35
40
  //#region src/services/workspace.service.d.ts
36
-
37
41
  declare const WorkspacePackageSchema: Schema.Struct<{
38
42
  name: typeof Schema.String;
39
43
  version: typeof Schema.String;
package/dist/index.mjs CHANGED
@@ -1,12 +1,139 @@
1
+ import { t as Eta } from "./eta-BV8TCRDW.mjs";
1
2
  import { Console, Context, Data, Effect, Layer, Schema } from "effect";
3
+ import path from "node:path";
2
4
  import { Command, CommandExecutor } from "@effect/platform";
3
5
  import { NodeCommandExecutor, NodeFileSystem } from "@effect/platform-node";
4
6
  import * as CommitParser from "commit-parser";
5
7
  import process from "node:process";
6
8
  import semver from "semver";
7
9
  import fs from "node:fs/promises";
8
- import path from "node:path";
9
10
 
11
+ //#region src/utils/changelog-formatters.ts
12
+ const eta$1 = new Eta();
13
+ /**
14
+ * Pure function to parse commits into changelog entries
15
+ */
16
+ function parseCommits(commits) {
17
+ return commits.filter((commit) => commit.isConventional).map((commit) => ({
18
+ type: commit.type || "other",
19
+ scope: commit.scope,
20
+ description: commit.description,
21
+ breaking: commit.isBreaking || false,
22
+ hash: commit.hash,
23
+ shortHash: commit.shortHash,
24
+ references: commit.references.map((ref) => ({
25
+ type: ref.type,
26
+ value: ref.value
27
+ }))
28
+ }));
29
+ }
30
+ /**
31
+ * Pure function to group changelog entries by type
32
+ */
33
+ function groupByType(entries) {
34
+ const groups = /* @__PURE__ */ new Map();
35
+ for (const entry of entries) {
36
+ const type = entry.breaking ? "breaking" : entry.type;
37
+ if (!groups.has(type)) groups.set(type, []);
38
+ groups.get(type).push(entry);
39
+ }
40
+ return groups;
41
+ }
42
+ /**
43
+ * Changelog template for Eta rendering
44
+ */
45
+ const CHANGELOG_TEMPLATE = `# <%= it.packageName %> v<%= it.version %>
46
+
47
+ **Previous version**: \`<%= it.previousVersion %>\`
48
+ **New version**: \`<%= it.version %>\`
49
+
50
+ <% if (it.entries.length === 0) { %>
51
+ *No conventional commits found.*
52
+ <% } else { %>
53
+ <% const groups = it.groupedEntries; %>
54
+ <% const typeOrder = ["breaking", "feat", "fix", "perf", "docs", "style", "refactor", "test", "build", "ci", "chore"]; %>
55
+ <% const typeLabels = {
56
+ breaking: "💥 Breaking Changes",
57
+ feat: "✨ Features",
58
+ fix: "🐛 Bug Fixes",
59
+ perf: "⚡ Performance",
60
+ docs: "📝 Documentation",
61
+ style: "💄 Styling",
62
+ refactor: "♻️ Refactoring",
63
+ test: "✅ Tests",
64
+ build: "📦 Build",
65
+ ci: "👷 CI",
66
+ chore: "🔧 Chores"
67
+ }; %>
68
+
69
+ <% for (const type of typeOrder) { %>
70
+ <% const entries = groups.get(type); %>
71
+ <% if (entries && entries.length > 0) { %>
72
+ ## <%= typeLabels[type] || type.charAt(0).toUpperCase() + type.slice(1) %>
73
+
74
+ <% for (const entry of entries) { %>
75
+ - <% if (entry.scope) { %>**<%= entry.scope %>**: <% } %><%= entry.description %><% if (entry.references.length > 0) { %> (<%= entry.references.map(r => "#" + r.value).join(", ") %>)<% } %> (\`<%= entry.shortHash %>\`)
76
+ <% } %>
77
+
78
+ <% } %>
79
+ <% } %>
80
+
81
+ <% for (const [type, entries] of groups) { %>
82
+ <% if (!typeOrder.includes(type)) { %>
83
+ ## <%= type.charAt(0).toUpperCase() + type.slice(1) %>
84
+
85
+ <% for (const entry of entries) { %>
86
+ - <% if (entry.scope) { %>**<%= entry.scope %>**: <% } %><%= entry.description %> (\`<%= entry.shortHash %>\`)
87
+ <% } %>
88
+
89
+ <% } %>
90
+ <% } %>
91
+ <% } %>`;
92
+ /**
93
+ * Pure function to format changelog as markdown
94
+ */
95
+ function formatChangelogMarkdown(changelog) {
96
+ const groups = groupByType(changelog.entries);
97
+ return eta$1.renderString(CHANGELOG_TEMPLATE, {
98
+ packageName: changelog.packageName,
99
+ version: changelog.version,
100
+ previousVersion: changelog.previousVersion,
101
+ entries: changelog.entries,
102
+ groupedEntries: groups
103
+ });
104
+ }
105
+ /**
106
+ * Pure function to create a changelog object
107
+ */
108
+ function createChangelog(packageName, version, previousVersion, commits) {
109
+ return {
110
+ packageName,
111
+ version,
112
+ previousVersion,
113
+ entries: parseCommits(commits)
114
+ };
115
+ }
116
+
117
+ //#endregion
118
+ //#region src/services/changelog.service.ts
119
+ var ChangelogService = class extends Effect.Service()("@ucdjs/release-scripts/ChangelogService", {
120
+ effect: Effect.gen(function* () {
121
+ function generateChangelog(pkg, newVersion, commits) {
122
+ return Effect.gen(function* () {
123
+ const changelog = createChangelog(pkg.name, newVersion, pkg.version, commits);
124
+ return {
125
+ changelog,
126
+ markdown: formatChangelogMarkdown(changelog),
127
+ filePath: `${pkg.path}/CHANGELOG.md`
128
+ };
129
+ });
130
+ }
131
+ return { generateChangelog };
132
+ }),
133
+ dependencies: []
134
+ }) {};
135
+
136
+ //#endregion
10
137
  //#region src/services/dependency-graph.service.ts
11
138
  var DependencyGraphService = class extends Effect.Service()("@ucdjs/release-scripts/DependencyGraphService", {
12
139
  effect: Effect.gen(function* () {
@@ -81,21 +208,42 @@ var WorkspaceError = class extends Data.TaggedError("WorkspaceError") {};
81
208
  var GitHubError = class extends Data.TaggedError("GitHubError") {};
82
209
  var VersionCalculationError = class extends Data.TaggedError("VersionCalculationError") {};
83
210
  var OverridesLoadError = class extends Data.TaggedError("OverridesLoadError") {};
211
+ var NPMError = class extends Data.TaggedError("NPMError") {};
212
+ var PublishError = class extends Data.TaggedError("PublishError") {};
213
+ var TagError = class extends Data.TaggedError("TagError") {};
84
214
 
85
215
  //#endregion
86
216
  //#region src/options.ts
87
217
  const DEFAULT_PR_BODY_TEMPLATE = `## Summary\n\nThis PR contains the following changes:\n\n- Updated package versions\n- Updated changelogs\n\n## Packages\n\nThe following packages will be released:\n\n{{packages}}`;
88
218
  const DEFAULT_CHANGELOG_TEMPLATE = `# Changelog\n\n{{releases}}`;
89
219
  const DEFAULT_TYPES = {
90
- feat: { title: "🚀 Features" },
91
- fix: { title: "🐞 Bug Fixes" },
92
- refactor: { title: "🔧 Code Refactoring" },
93
- perf: { title: "🏎 Performance" },
94
- docs: { title: "📚 Documentation" },
95
- style: { title: "🎨 Styles" }
220
+ feat: {
221
+ title: "🚀 Features",
222
+ color: "green"
223
+ },
224
+ fix: {
225
+ title: "🐞 Bug Fixes",
226
+ color: "red"
227
+ },
228
+ refactor: {
229
+ title: "🔧 Code Refactoring",
230
+ color: "blue"
231
+ },
232
+ perf: {
233
+ title: "🏎 Performance",
234
+ color: "orange"
235
+ },
236
+ docs: {
237
+ title: "📚 Documentation",
238
+ color: "purple"
239
+ },
240
+ style: {
241
+ title: "🎨 Styles",
242
+ color: "pink"
243
+ }
96
244
  };
97
245
  function normalizeReleaseScriptsOptions(options) {
98
- const { workspaceRoot = process.cwd(), githubToken = "", repo: fullRepo, packages = true, branch = {}, globalCommitMode = "dependencies", pullRequest = {}, changelog = {}, types = {}, dryRun = false } = options;
246
+ const { workspaceRoot = process.cwd(), githubToken = "", repo: fullRepo, packages = true, branch = {}, globalCommitMode = "dependencies", pullRequest = {}, changelog = {}, types = {}, dryRun = false, npm = {} } = options;
99
247
  const token = githubToken.trim();
100
248
  if (!token) throw new Error("GitHub token is required. Pass it in via options.");
101
249
  if (!fullRepo || !fullRepo.trim() || !fullRepo.includes("/")) throw new Error("Repository (repo) is required. Specify in 'owner/repo' format (e.g., 'octocat/hello-world').");
@@ -129,7 +277,11 @@ function normalizeReleaseScriptsOptions(options) {
129
277
  types: options.types ? {
130
278
  ...DEFAULT_TYPES,
131
279
  ...types
132
- } : DEFAULT_TYPES
280
+ } : DEFAULT_TYPES,
281
+ npm: {
282
+ otp: npm.otp,
283
+ provenance: npm.provenance ?? true
284
+ }
133
285
  };
134
286
  }
135
287
  var ReleaseScriptsOptions = class extends Context.Tag("@ucdjs/release-scripts/ReleaseScriptsOptions")() {};
@@ -176,6 +328,9 @@ var GitService = class extends Effect.Service()("@ucdjs/release-scripts/GitServi
176
328
  function checkoutBranch(branch) {
177
329
  return execGitCommand(["checkout", branch]);
178
330
  }
331
+ function rebaseBranch(onto) {
332
+ return execGitCommandIfNotDry(["rebase", onto]);
333
+ }
179
334
  function stageChanges(files) {
180
335
  return Effect.gen(function* () {
181
336
  if (files.length === 0) return yield* Effect.fail(/* @__PURE__ */ new Error("No files to stage."));
@@ -196,6 +351,14 @@ var GitService = class extends Effect.Service()("@ucdjs/release-scripts/GitServi
196
351
  branch
197
352
  ]);
198
353
  }
354
+ function forcePushChanges(branch, remote = "origin") {
355
+ return execGitCommandIfNotDry([
356
+ "push",
357
+ "--force-with-lease",
358
+ remote,
359
+ branch
360
+ ]);
361
+ }
199
362
  function readFile(filePath, ref = "HEAD") {
200
363
  return execGitCommand(["show", `${ref}:${filePath}`]);
201
364
  }
@@ -215,6 +378,32 @@ var GitService = class extends Effect.Service()("@ucdjs/release-scripts/GitServi
215
378
  })));
216
379
  }));
217
380
  }
381
+ function createTag(name, message) {
382
+ return execGitCommandIfNotDry(message ? [
383
+ "tag",
384
+ "-a",
385
+ name,
386
+ "-m",
387
+ message
388
+ ] : ["tag", name]).pipe(Effect.mapError((err) => new TagError({
389
+ message: `Failed to create tag "${name}"`,
390
+ tagName: name,
391
+ operation: "create",
392
+ cause: err
393
+ })));
394
+ }
395
+ function pushTag(name, remote = "origin") {
396
+ return execGitCommandIfNotDry([
397
+ "push",
398
+ remote,
399
+ name
400
+ ]).pipe(Effect.mapError((err) => new TagError({
401
+ message: `Failed to push tag "${name}" to ${remote}`,
402
+ tagName: name,
403
+ operation: "push",
404
+ cause: err
405
+ })));
406
+ }
218
407
  function getCommits(options) {
219
408
  return Effect.tryPromise({
220
409
  try: async () => CommitParser.getCommits({
@@ -264,16 +453,22 @@ var GitService = class extends Effect.Service()("@ucdjs/release-scripts/GitServi
264
453
  exists: doesBranchExist,
265
454
  create: createBranch,
266
455
  checkout: checkoutBranch,
456
+ rebase: rebaseBranch,
267
457
  get: getBranch
268
458
  },
269
459
  commits: {
270
460
  stage: stageChanges,
271
461
  write: writeCommit,
272
462
  push: pushChanges,
463
+ forcePush: forcePushChanges,
273
464
  get: getCommits,
274
465
  filesChangesBetweenRefs
275
466
  },
276
- tags: { mostRecentForPackage: getMostRecentPackageTag },
467
+ tags: {
468
+ mostRecentForPackage: getMostRecentPackageTag,
469
+ create: createTag,
470
+ push: pushTag
471
+ },
277
472
  workspace: {
278
473
  readFile,
279
474
  isWithinRepository,
@@ -287,6 +482,7 @@ var GitService = class extends Effect.Service()("@ucdjs/release-scripts/GitServi
287
482
 
288
483
  //#endregion
289
484
  //#region src/services/github.service.ts
485
+ const eta = new Eta();
290
486
  const PullRequestSchema = Schema.Struct({
291
487
  number: Schema.Number,
292
488
  title: Schema.String,
@@ -370,7 +566,128 @@ var GitHubService = class extends Effect.Service()("@ucdjs/release-scripts/GitHu
370
566
  cause: e.cause
371
567
  })));
372
568
  }
373
- return { getPullRequestByBranch };
569
+ function setCommitStatus(sha, status) {
570
+ return makeRequest(`statuses/${sha}`, Schema.Unknown, {
571
+ method: "POST",
572
+ body: JSON.stringify(status)
573
+ }).pipe(Effect.map(() => status), Effect.catchAll((e) => Effect.fail(new GitHubError({
574
+ message: e.message,
575
+ operation: "setCommitStatus",
576
+ cause: e.cause
577
+ }))));
578
+ }
579
+ function updatePullRequest(number, options) {
580
+ return makeRequest(`pulls/${number}`, PullRequestSchema, {
581
+ method: "PATCH",
582
+ body: JSON.stringify(options)
583
+ }).pipe(Effect.mapError((e) => new GitHubError({
584
+ message: e.message,
585
+ operation: "updatePullRequest",
586
+ cause: e.cause
587
+ })));
588
+ }
589
+ const prBodyTemplate = `## Release Summary
590
+
591
+ This PR prepares the release of <%= it.count %> package<%= it.count === 1 ? "" : "s" %>:
592
+
593
+ <% for (const release of it.releases) { %>
594
+ - **<%= release.packageName %>**: \`<%= release.previousVersion %>\` → \`<%= release.version %>\`
595
+ <% } %>
596
+
597
+ ## Changes
598
+
599
+ See individual package changelogs for details.
600
+ `;
601
+ function generateReleasePRBody(releases) {
602
+ return Effect.gen(function* () {
603
+ return eta.renderString(prBodyTemplate, {
604
+ count: releases.length,
605
+ releases
606
+ });
607
+ });
608
+ }
609
+ return {
610
+ getPullRequestByBranch,
611
+ setCommitStatus,
612
+ updatePullRequest,
613
+ generateReleasePRBody
614
+ };
615
+ }),
616
+ dependencies: []
617
+ }) {};
618
+
619
+ //#endregion
620
+ //#region src/services/npm.service.ts
621
+ const PackumentSchema = Schema.Struct({
622
+ "name": Schema.String,
623
+ "dist-tags": Schema.Record({
624
+ key: Schema.String,
625
+ value: Schema.String
626
+ }),
627
+ "versions": Schema.Record({
628
+ key: Schema.String,
629
+ value: Schema.Struct({
630
+ name: Schema.String,
631
+ version: Schema.String,
632
+ description: Schema.optional(Schema.String),
633
+ dist: Schema.Struct({
634
+ tarball: Schema.String,
635
+ shasum: Schema.String,
636
+ integrity: Schema.optional(Schema.String)
637
+ })
638
+ })
639
+ })
640
+ });
641
+ var NPMService = class extends Effect.Service()("@ucdjs/release-scripts/NPMService", {
642
+ effect: Effect.gen(function* () {
643
+ const executor = yield* CommandExecutor.CommandExecutor;
644
+ const config = yield* ReleaseScriptsOptions;
645
+ const fetchPackument = (packageName) => Effect.tryPromise({
646
+ try: async () => {
647
+ const response = await fetch(`https://registry.npmjs.org/${packageName}`);
648
+ if (response.status === 404) return null;
649
+ if (!response.ok) throw new Error(`Failed to fetch packument: ${response.statusText}`);
650
+ return await response.json();
651
+ },
652
+ catch: (error) => {
653
+ return new NPMError({
654
+ message: error instanceof Error ? error.message : String(error),
655
+ operation: "fetchPackument"
656
+ });
657
+ }
658
+ }).pipe(Effect.flatMap((data) => {
659
+ if (data === null) return Effect.succeed(null);
660
+ return Schema.decodeUnknown(PackumentSchema)(data).pipe(Effect.mapError((error) => new NPMError({
661
+ message: `Failed to parse packument: ${error}`,
662
+ operation: "fetchPackument"
663
+ })));
664
+ }));
665
+ const versionExists = (packageName, version) => fetchPackument(packageName).pipe(Effect.map((packument) => {
666
+ if (!packument) return false;
667
+ return version in packument.versions;
668
+ }));
669
+ const getLatestVersion = (packageName) => fetchPackument(packageName).pipe(Effect.map((packument) => {
670
+ if (!packument) return null;
671
+ return packument["dist-tags"].latest || null;
672
+ }));
673
+ const publish = (options) => Effect.gen(function* () {
674
+ const args = ["publish"];
675
+ if (options.tagName) args.push("--tag", options.tagName);
676
+ if (options.otp) args.push("--otp", options.otp);
677
+ if (options.provenance !== false) args.push("--provenance");
678
+ if (options.dryRun ?? config.dryRun) args.push("--dry-run");
679
+ const command = Command.make("pnpm", ...args).pipe(Command.workingDirectory(options.packagePath));
680
+ return (yield* executor.string(command).pipe(Effect.mapError((err) => new PublishError({
681
+ message: `Failed to publish package at ${options.packagePath}: ${err.message}`,
682
+ cause: err
683
+ })))).trim();
684
+ });
685
+ return {
686
+ fetchPackument,
687
+ versionExists,
688
+ getLatestVersion,
689
+ publish
690
+ };
374
691
  }),
375
692
  dependencies: []
376
693
  }) {};
@@ -839,8 +1156,257 @@ function findCommitRange(packages) {
839
1156
  return [oldestCommit.hash, newestCommit.hash];
840
1157
  }
841
1158
 
1159
+ //#endregion
1160
+ //#region src/prepare.ts
1161
+ function constructPrepareProgram(config) {
1162
+ return Effect.gen(function* () {
1163
+ const changelog = yield* ChangelogService;
1164
+ const git = yield* GitService;
1165
+ const github = yield* GitHubService;
1166
+ const dependencyGraph = yield* DependencyGraphService;
1167
+ const packageUpdater = yield* PackageUpdaterService;
1168
+ const versionCalculator = yield* VersionCalculatorService;
1169
+ const workspace = yield* WorkspaceService;
1170
+ yield* git.workspace.assertWorkspaceReady;
1171
+ const releasePullRequest = yield* github.getPullRequestByBranch(config.branch.release);
1172
+ if (!releasePullRequest || !releasePullRequest.head) return yield* Effect.fail(/* @__PURE__ */ new Error(`Release pull request for branch "${config.branch.release}" does not exist.`));
1173
+ yield* Console.log(`✅ Release pull request #${releasePullRequest.number} exists.`);
1174
+ if ((yield* git.branches.get) !== config.branch.release) {
1175
+ yield* git.branches.checkout(config.branch.release);
1176
+ yield* Console.log(`✅ Checked out to release branch "${config.branch.release}".`);
1177
+ }
1178
+ yield* Console.log(`🔄 Rebasing "${config.branch.release}" onto "${config.branch.default}"...`);
1179
+ yield* git.branches.rebase(config.branch.default);
1180
+ yield* Console.log(`✅ Rebase complete.`);
1181
+ const overrides = yield* loadOverrides({
1182
+ sha: config.branch.default,
1183
+ overridesPath: ".github/ucdjs-release.overrides.json"
1184
+ });
1185
+ if (Object.keys(overrides).length > 0) yield* Console.log("📋 Loaded version overrides:", overrides);
1186
+ const originalBranch = yield* git.branches.get;
1187
+ yield* git.branches.checkout(config.branch.default);
1188
+ const packages = yield* workspace.discoverWorkspacePackages.pipe(Effect.flatMap(mergePackageCommitsIntoPackages), Effect.flatMap((pkgs) => mergeCommitsAffectingGloballyIntoPackage(pkgs, config.globalCommitMode)));
1189
+ yield* Console.log(`📦 Discovered ${packages.length} packages with commits.`);
1190
+ const releases = yield* versionCalculator.calculateBumps(packages, overrides);
1191
+ yield* dependencyGraph.topologicalOrder(packages);
1192
+ const releasesCount = releases.length;
1193
+ yield* Console.log(`📊 ${releasesCount} package${releasesCount === 1 ? "" : "s"} will be released.`);
1194
+ yield* git.branches.checkout(originalBranch);
1195
+ yield* Console.log("✏️ Updating package.json files...");
1196
+ yield* packageUpdater.applyReleases(packages, releases);
1197
+ yield* Console.log("✅ package.json files updated.");
1198
+ yield* Console.log("📝 Generating changelogs...");
1199
+ const changelogFiles = [];
1200
+ for (const release of releases) {
1201
+ const pkg = packages.find((p) => p.name === release.package.name);
1202
+ if (!pkg || !pkg.commits) continue;
1203
+ const result = yield* changelog.generateChangelog(pkg, release.newVersion, pkg.commits);
1204
+ yield* Effect.tryPromise({
1205
+ try: async () => {
1206
+ await (await import("node:fs/promises")).writeFile(result.filePath, result.markdown, "utf-8");
1207
+ },
1208
+ catch: (e) => /* @__PURE__ */ new Error(`Failed to write changelog: ${String(e)}`)
1209
+ });
1210
+ changelogFiles.push(result.filePath);
1211
+ }
1212
+ yield* Console.log(`✅ Generated ${changelogFiles.length} changelog file${changelogFiles.length === 1 ? "" : "s"}.`);
1213
+ const filesToStage = [...releases.map((r) => `${r.package.path}/package.json`), ...changelogFiles];
1214
+ yield* Console.log(`📌 Staging ${filesToStage.length} file${filesToStage.length === 1 ? "" : "s"}...`);
1215
+ yield* git.commits.stage(filesToStage);
1216
+ const commitMessage = `chore(release): prepare release
1217
+
1218
+ ${releasesCount} package${releasesCount === 1 ? "" : "s"} updated:
1219
+ ${releases.map((r) => ` - ${r.package.name}@${r.newVersion}`).join("\n")}`;
1220
+ yield* Console.log("💾 Creating commit...");
1221
+ yield* git.commits.write(commitMessage);
1222
+ yield* Console.log("✅ Commit created.");
1223
+ yield* Console.log(`⬆️ Force pushing to "${config.branch.release}"...`);
1224
+ yield* git.commits.forcePush(config.branch.release);
1225
+ yield* Console.log("✅ Force push complete.");
1226
+ yield* Console.log("📄 Updating pull request...");
1227
+ const prBody = yield* github.generateReleasePRBody(releases.map((r) => ({
1228
+ packageName: r.package.name,
1229
+ version: r.newVersion,
1230
+ previousVersion: r.package.version
1231
+ })));
1232
+ yield* github.updatePullRequest(releasePullRequest.number, { body: prBody });
1233
+ yield* Console.log("✅ Pull request updated.");
1234
+ yield* Console.log(`\n🎉 Release preparation complete! View PR: #${releasePullRequest.number}`);
1235
+ });
1236
+ }
1237
+
1238
+ //#endregion
1239
+ //#region src/publish.ts
1240
+ function isPrerelease(version) {
1241
+ const parsed = semver.parse(version);
1242
+ return parsed !== null && parsed.prerelease.length > 0;
1243
+ }
1244
+ function getDistTag(version) {
1245
+ return isPrerelease(version) ? "next" : "latest";
1246
+ }
1247
+ function buildPackage(packagePath) {
1248
+ return Effect.gen(function* () {
1249
+ const executor = yield* CommandExecutor.CommandExecutor;
1250
+ const command = Command.make("pnpm", "run", "build").pipe(Command.workingDirectory(packagePath));
1251
+ return (yield* executor.string(command).pipe(Effect.mapError((err) => /* @__PURE__ */ new Error(`Failed to build package at ${packagePath}: ${err.message}`)))).trim();
1252
+ });
1253
+ }
1254
+ function constructPublishProgram(config) {
1255
+ return Effect.gen(function* () {
1256
+ const git = yield* GitService;
1257
+ const npm = yield* NPMService;
1258
+ const workspace = yield* WorkspaceService;
1259
+ const dependencyGraph = yield* DependencyGraphService;
1260
+ yield* git.workspace.assertWorkspaceReady;
1261
+ const currentBranch = yield* git.branches.get;
1262
+ if (currentBranch !== config.branch.default) return yield* Effect.fail(/* @__PURE__ */ new Error(`Publish must be run on the default branch "${config.branch.default}". Current branch: "${currentBranch}"`));
1263
+ yield* Console.log(`✅ On default branch "${config.branch.default}".`);
1264
+ const publicPackages = (yield* workspace.discoverWorkspacePackages).filter((pkg) => !pkg.packageJson.private);
1265
+ yield* Console.log(`📦 Found ${publicPackages.length} public package${publicPackages.length === 1 ? "" : "s"} to check.`);
1266
+ const orderedPackages = yield* dependencyGraph.topologicalOrder(publicPackages);
1267
+ const results = [];
1268
+ for (const updateOrder of orderedPackages) {
1269
+ const pkg = updateOrder.package;
1270
+ const version = pkg.version;
1271
+ const tagName = `${pkg.name}@${version}`;
1272
+ if (yield* npm.versionExists(pkg.name, version)) {
1273
+ yield* Console.log(`⏭️ Skipping ${pkg.name}@${version} - already published.`);
1274
+ results.push({
1275
+ packageName: pkg.name,
1276
+ version,
1277
+ status: "skipped",
1278
+ reason: "Already published to npm"
1279
+ });
1280
+ continue;
1281
+ }
1282
+ yield* Console.log(`🔨 Building ${pkg.name}...`);
1283
+ yield* buildPackage(pkg.path);
1284
+ yield* Console.log(`✅ Build complete for ${pkg.name}.`);
1285
+ const distTag = getDistTag(version);
1286
+ yield* Console.log(`🚀 Publishing ${pkg.name}@${version} with tag "${distTag}"...`);
1287
+ const publishResult = yield* npm.publish({
1288
+ packagePath: pkg.path,
1289
+ tagName: distTag,
1290
+ otp: config.npm.otp,
1291
+ provenance: config.npm.provenance,
1292
+ dryRun: config.dryRun
1293
+ }).pipe(Effect.map(() => ({ success: true })), Effect.catchAll((err) => Effect.succeed({
1294
+ success: false,
1295
+ error: err
1296
+ })));
1297
+ if (publishResult.success) {
1298
+ yield* Console.log(`✅ Published ${pkg.name}@${version}.`);
1299
+ if (!config.dryRun) {
1300
+ yield* Console.log(`🏷️ Creating tag ${tagName}...`);
1301
+ yield* git.tags.create(tagName, `Release ${tagName}`);
1302
+ yield* git.tags.push(tagName);
1303
+ yield* Console.log(`✅ Tag ${tagName} created and pushed.`);
1304
+ } else yield* Console.log(`🏷️ [Dry Run] Would create and push tag ${tagName}.`);
1305
+ results.push({
1306
+ packageName: pkg.name,
1307
+ version,
1308
+ status: "published"
1309
+ });
1310
+ } else {
1311
+ const error = publishResult.error;
1312
+ yield* Console.log(`❌ Failed to publish ${pkg.name}@${version}: ${error.message}`);
1313
+ results.push({
1314
+ packageName: pkg.name,
1315
+ version,
1316
+ status: "failed",
1317
+ reason: error.message
1318
+ });
1319
+ }
1320
+ }
1321
+ const published = results.filter((r) => r.status === "published");
1322
+ const skipped = results.filter((r) => r.status === "skipped");
1323
+ const failed = results.filter((r) => r.status === "failed");
1324
+ yield* Console.log("\n📊 Publish Summary:");
1325
+ yield* Console.log(` Published: ${published.length}`);
1326
+ yield* Console.log(` Skipped: ${skipped.length}`);
1327
+ yield* Console.log(` Failed: ${failed.length}`);
1328
+ if (failed.length > 0) {
1329
+ yield* Console.log("\n❌ Failed packages:");
1330
+ for (const f of failed) yield* Console.log(` - ${f.packageName}@${f.version}: ${f.reason}`);
1331
+ return yield* Effect.fail(/* @__PURE__ */ new Error("Some packages failed to publish."));
1332
+ }
1333
+ if (published.length === 0 && skipped.length > 0) yield* Console.log("\n✅ All packages were already published.");
1334
+ else if (published.length > 0) yield* Console.log("\n🎉 Publish complete!");
1335
+ });
1336
+ }
1337
+
842
1338
  //#endregion
843
1339
  //#region src/verify.ts
1340
+ function satisfiesRange(range, version) {
1341
+ return semver.satisfies(version, range, { includePrerelease: true });
1342
+ }
1343
+ function snapshotPackageJson(pkg, ref) {
1344
+ return Effect.gen(function* () {
1345
+ return yield* (yield* GitService).workspace.readFile(`${pkg.path}/package.json`, ref).pipe(Effect.flatMap((content) => Effect.try({
1346
+ try: () => JSON.parse(content),
1347
+ catch: (e) => /* @__PURE__ */ new Error(`Failed to parse package.json for ${pkg.name} at ${ref}: ${String(e)}`)
1348
+ })));
1349
+ });
1350
+ }
1351
+ function findDrift(packages, releases, branchSnapshots) {
1352
+ const releaseVersionByName = /* @__PURE__ */ new Map();
1353
+ for (const rel of releases) releaseVersionByName.set(rel.package.name, rel.newVersion);
1354
+ const reasons = [];
1355
+ for (const pkg of packages) {
1356
+ const snapshot = branchSnapshots.get(pkg.name);
1357
+ if (snapshot == null) {
1358
+ reasons.push({
1359
+ packageName: pkg.name,
1360
+ reason: "package.json missing on release branch"
1361
+ });
1362
+ continue;
1363
+ }
1364
+ if (snapshot instanceof Error) {
1365
+ reasons.push({
1366
+ packageName: pkg.name,
1367
+ reason: snapshot.message
1368
+ });
1369
+ continue;
1370
+ }
1371
+ const expectedVersion = releaseVersionByName.get(pkg.name) ?? pkg.version;
1372
+ const branchVersion = typeof snapshot.version === "string" ? snapshot.version : void 0;
1373
+ if (!branchVersion) {
1374
+ reasons.push({
1375
+ packageName: pkg.name,
1376
+ reason: "package.json on release branch lacks version"
1377
+ });
1378
+ continue;
1379
+ }
1380
+ if (branchVersion !== expectedVersion) reasons.push({
1381
+ packageName: pkg.name,
1382
+ reason: `version mismatch: expected ${expectedVersion}, found ${branchVersion}`
1383
+ });
1384
+ for (const section of [
1385
+ "dependencies",
1386
+ "devDependencies",
1387
+ "peerDependencies"
1388
+ ]) {
1389
+ const deps = snapshot[section];
1390
+ if (!deps || typeof deps !== "object") continue;
1391
+ for (const [depName, range] of Object.entries(deps)) {
1392
+ const bumpedVersion = releaseVersionByName.get(depName);
1393
+ if (!bumpedVersion) continue;
1394
+ if (typeof range !== "string") {
1395
+ reasons.push({
1396
+ packageName: pkg.name,
1397
+ reason: `${section}.${depName} is not a string range`
1398
+ });
1399
+ continue;
1400
+ }
1401
+ if (!satisfiesRange(range, bumpedVersion)) reasons.push({
1402
+ packageName: pkg.name,
1403
+ reason: `${section}.${depName} does not include ${bumpedVersion}`
1404
+ });
1405
+ }
1406
+ }
1407
+ }
1408
+ return reasons;
1409
+ }
844
1410
  function constructVerifyProgram(config) {
845
1411
  return Effect.gen(function* () {
846
1412
  const git = yield* GitService;
@@ -867,6 +1433,26 @@ function constructVerifyProgram(config) {
867
1433
  const ordered = yield* dependencyGraph.topologicalOrder(packages);
868
1434
  yield* Console.log("Calculated releases:", releases);
869
1435
  yield* Console.log("Release order:", ordered);
1436
+ const releaseHeadSha = releasePullRequest.head.sha;
1437
+ const branchSnapshots = /* @__PURE__ */ new Map();
1438
+ for (const pkg of packages) {
1439
+ const snapshot = yield* snapshotPackageJson(pkg, releaseHeadSha).pipe(Effect.catchAll((err) => Effect.succeed(err instanceof Error ? err : new Error(String(err)))));
1440
+ branchSnapshots.set(pkg.name, snapshot);
1441
+ }
1442
+ const drift = findDrift(packages, releases, branchSnapshots);
1443
+ if (drift.length === 0) yield* Console.log("✅ Release branch is in sync with expected releases.");
1444
+ else yield* Console.log("❌ Release branch is out of sync:", drift);
1445
+ const status = drift.length === 0 ? {
1446
+ state: "success",
1447
+ description: "Release artifacts in sync",
1448
+ context: "release/verify"
1449
+ } : {
1450
+ state: "failure",
1451
+ description: "Release branch out of sync",
1452
+ context: "release/verify"
1453
+ };
1454
+ yield* github.setCommitStatus(releaseHeadSha, status);
1455
+ if (drift.length > 0) return yield* Effect.fail(/* @__PURE__ */ new Error("Release branch is out of sync."));
870
1456
  });
871
1457
  }
872
1458
 
@@ -874,7 +1460,7 @@ function constructVerifyProgram(config) {
874
1460
  //#region src/index.ts
875
1461
  async function createReleaseScripts(options) {
876
1462
  const config = normalizeReleaseScriptsOptions(options);
877
- const AppLayer = Layer.succeed(ReleaseScriptsOptions, config).pipe(Layer.provide(NodeCommandExecutor.layer), Layer.provide(NodeFileSystem.layer), Layer.provide(GitService.Default), Layer.provide(GitHubService.Default), Layer.provide(DependencyGraphService.Default), Layer.provide(PackageUpdaterService.Default), Layer.provide(VersionCalculatorService.Default), Layer.provide(WorkspaceService.Default));
1463
+ const AppLayer = Layer.succeed(ReleaseScriptsOptions, config).pipe(Layer.provide(NodeCommandExecutor.layer), Layer.provide(NodeFileSystem.layer), Layer.provide(ChangelogService.Default), Layer.provide(GitService.Default), Layer.provide(GitHubService.Default), Layer.provide(DependencyGraphService.Default), Layer.provide(NPMService.Default), Layer.provide(PackageUpdaterService.Default), Layer.provide(VersionCalculatorService.Default), Layer.provide(WorkspaceService.Default));
878
1464
  const runProgram = (program) => {
879
1465
  const provided = program.pipe(Effect.provide(AppLayer));
880
1466
  return Effect.runPromise(provided);
@@ -894,39 +1480,10 @@ async function createReleaseScripts(options) {
894
1480
  return runProgram(constructVerifyProgram(config));
895
1481
  },
896
1482
  async prepare() {
897
- return runProgram(Effect.gen(function* () {
898
- const git = yield* GitService;
899
- const github = yield* GitHubService;
900
- const dependencyGraph = yield* DependencyGraphService;
901
- const packageUpdater = yield* PackageUpdaterService;
902
- const versionCalculator = yield* VersionCalculatorService;
903
- const workspace = yield* WorkspaceService;
904
- yield* safeguardProgram;
905
- const releasePullRequest = yield* github.getPullRequestByBranch(config.branch.release);
906
- if (!releasePullRequest || !releasePullRequest.head) return yield* Effect.fail(/* @__PURE__ */ new Error(`Release pull request for branch "${config.branch.release}" does not exist.`));
907
- yield* Console.log(`✅ Release pull request #${releasePullRequest.number} exists.`);
908
- if ((yield* git.branches.get) !== config.branch.default) {
909
- yield* git.branches.checkout(config.branch.default);
910
- yield* Console.log(`✅ Checked out to default branch "${config.branch.default}".`);
911
- }
912
- const overrides = yield* loadOverrides({
913
- sha: releasePullRequest.head.sha,
914
- overridesPath: ".github/ucdjs-release.overrides.json"
915
- });
916
- yield* Console.log("Loaded overrides:", overrides);
917
- const packages = yield* workspace.discoverWorkspacePackages.pipe(Effect.flatMap(mergePackageCommitsIntoPackages), Effect.flatMap((pkgs) => mergeCommitsAffectingGloballyIntoPackage(pkgs, config.globalCommitMode)));
918
- yield* Console.log("Discovered packages with commits and global commits:", packages);
919
- const releases = yield* versionCalculator.calculateBumps(packages, overrides);
920
- const ordered = yield* dependencyGraph.topologicalOrder(packages);
921
- yield* Console.log("Calculated releases:", releases);
922
- yield* Console.log("Release order:", ordered);
923
- yield* packageUpdater.applyReleases(packages, releases);
924
- }));
1483
+ return runProgram(constructPrepareProgram(config));
925
1484
  },
926
1485
  async publish() {
927
- return runProgram(Effect.gen(function* () {
928
- return yield* Effect.fail(/* @__PURE__ */ new Error("Not implemented yet."));
929
- }));
1486
+ return runProgram(constructPublishProgram(config));
930
1487
  },
931
1488
  packages: {
932
1489
  async list() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ucdjs/release-scripts",
3
- "version": "0.1.0-beta.24",
3
+ "version": "0.1.0-beta.25",
4
4
  "description": "@ucdjs release scripts",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -15,37 +15,35 @@
15
15
  ".": "./dist/index.mjs",
16
16
  "./package.json": "./package.json"
17
17
  },
18
- "main": "./dist/index.mjs",
19
- "module": "./dist/index.mjs",
20
18
  "types": "./dist/index.d.mts",
21
19
  "files": [
22
20
  "dist"
23
21
  ],
24
22
  "dependencies": {
25
- "@effect/platform": "0.93.6",
26
- "@effect/platform-node": "0.103.0",
27
- "@luxass/utils": "2.7.2",
23
+ "@effect/platform": "0.94.4",
24
+ "@effect/platform-node": "0.104.1",
25
+ "@luxass/utils": "2.7.3",
28
26
  "commit-parser": "1.3.0",
29
- "effect": "3.19.9",
27
+ "effect": "3.19.16",
30
28
  "farver": "1.0.0-beta.1",
31
29
  "mri": "1.2.0",
32
30
  "prompts": "2.4.2",
33
- "semver": "7.7.3",
31
+ "semver": "7.7.4",
34
32
  "tinyexec": "1.0.2"
35
33
  },
36
34
  "devDependencies": {
37
- "@effect/language-service": "^0.60.0",
35
+ "@effect/language-service": "^0.73.1",
38
36
  "@effect/vitest": "0.27.0",
39
- "@luxass/eslint-config": "6.0.3",
37
+ "@luxass/eslint-config": "7.2.0",
40
38
  "@types/node": "22.18.12",
41
39
  "@types/prompts": "2.4.9",
42
40
  "@types/semver": "7.7.1",
43
- "eslint": "9.39.1",
44
- "eta": "4.4.1",
45
- "tsdown": "0.17.0",
41
+ "eslint": "10.0.0",
42
+ "eta": "4.5.1",
43
+ "tsdown": "0.20.3",
46
44
  "typescript": "5.9.3",
47
- "vitest": "4.0.15",
48
- "vitest-testdirs": "4.3.0"
45
+ "vitest": "4.0.18",
46
+ "vitest-testdirs": "4.4.2"
49
47
  },
50
48
  "scripts": {
51
49
  "build": "tsdown",