@cluerise/tools 5.4.0 → 5.4.1

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.
@@ -1,512 +1,442 @@
1
1
  import * as ActionsCore from "@actions/core";
2
- import { z, ZodError } from "zod";
3
- import ChildProcess from "node:child_process";
4
- import { existsSync } from "node:fs";
2
+ import { ZodError, z } from "zod";
5
3
  import FileSystem from "node:fs/promises";
6
4
  import OS from "node:os";
7
5
  import Path from "node:path";
6
+ import ChildProcess from "node:child_process";
7
+ import { existsSync } from "node:fs";
8
8
  import { Writable } from "node:stream";
9
9
  import semanticRelease from "semantic-release";
10
10
  import "@commitlint/load";
11
11
  import { ESLint } from "eslint";
12
12
  import { glob } from "glob";
13
13
  import * as Prettier from "prettier";
14
- const gitProviderOrigins = {
15
- github: "https://github.com"
14
+ //#region src/modules/core/package-json/git-provider.ts
15
+ var gitProviderOrigins = { github: "https://github.com" };
16
+ var GitProvider = class {
17
+ static #origins = gitProviderOrigins;
18
+ static #names = Object.keys(this.#origins);
19
+ /**
20
+ * Check if the provided name is a valid Git provider name.
21
+ *
22
+ * @param name The name to check.
23
+ * @returns True if the name is valid, otherwise false.
24
+ */
25
+ static isValidName(name) {
26
+ return this.#names.includes(name);
27
+ }
28
+ /**
29
+ * Get the origin URL for the given Git provider name.
30
+ *
31
+ * @param name The Git provider name.
32
+ * @returns The origin URL.
33
+ */
34
+ static getOrigin(name) {
35
+ return this.#origins[name];
36
+ }
16
37
  };
17
- class GitProvider {
18
- static #origins = gitProviderOrigins;
19
- static #names = Object.keys(this.#origins);
20
- /**
21
- * Check if the provided name is a valid Git provider name.
22
- *
23
- * @param name The name to check.
24
- * @returns True if the name is valid, otherwise false.
25
- */
26
- static isValidName(name) {
27
- return this.#names.includes(name);
28
- }
29
- /**
30
- * Get the origin URL for the given Git provider name.
31
- *
32
- * @param name The Git provider name.
33
- * @returns The origin URL.
34
- */
35
- static getOrigin(name) {
36
- return this.#origins[name];
37
- }
38
- }
39
- const enginesSchema = z.object({
40
- node: z.string()
41
- });
42
- const repositoryObjectSchema = z.object({
43
- type: z.string(),
44
- url: z.string(),
45
- directory: z.string().optional()
38
+ //#endregion
39
+ //#region src/modules/core/package-json/package-json-data.ts
40
+ var enginesSchema = z.object({ node: z.string() });
41
+ var repositoryObjectSchema = z.object({
42
+ type: z.string(),
43
+ url: z.string(),
44
+ directory: z.string().optional()
46
45
  });
47
- const repositorySchema = z.union([z.string(), repositoryObjectSchema]);
48
- const packageJsonDataSchema = z.object({
49
- name: z.string(),
50
- version: z.string().optional(),
51
- description: z.string().optional(),
52
- engines: enginesSchema.optional(),
53
- repository: repositorySchema.optional()
46
+ var repositorySchema = z.union([z.string(), repositoryObjectSchema]);
47
+ var packageJsonDataSchema = z.object({
48
+ name: z.string(),
49
+ version: z.string().optional(),
50
+ description: z.string().optional(),
51
+ engines: enginesSchema.optional(),
52
+ repository: repositorySchema.optional()
54
53
  });
55
- class PackageJson {
56
- #data;
57
- constructor(data) {
58
- this.#data = data;
59
- }
60
- /**
61
- * Load and parse the `package.json` file, returning a `PackageJson` instance.
62
- *
63
- * Throws an error if the file is invalid or cannot be parsed.
64
- *
65
- * @returns A new `PackageJson` instance with parsed data.
66
- */
67
- static async init() {
68
- const content = await FileSystem.readFile("package.json", { encoding: "utf8" });
69
- const data = JsonUtils.parse(content);
70
- const parseResult = packageJsonDataSchema.safeParse(data);
71
- if (!parseResult.success) {
72
- throw new Error("Invalid package.json", {
73
- cause: parseResult.error
74
- });
75
- }
76
- return new PackageJson(data);
77
- }
78
- /**
79
- * Get the package name from `package.json`.
80
- *
81
- * @returns The package name.
82
- */
83
- get name() {
84
- return this.#data.name;
85
- }
86
- /**
87
- * Get the package version from `package.json`.
88
- *
89
- * @returns The package version.
90
- */
91
- get version() {
92
- return this.#data.version;
93
- }
94
- /**
95
- * Get the engines field from `package.json`, if present.
96
- *
97
- * @returns The engines object or undefined.
98
- */
99
- get engines() {
100
- return this.#data.engines;
101
- }
102
- /**
103
- * Set the engines field in `package.json`.
104
- *
105
- * @param engines The engines object to set.
106
- */
107
- set engines(engines) {
108
- this.#data.engines = engines;
109
- }
110
- /**
111
- * Get the repository field from `package.json`, if present.
112
- *
113
- * @returns The repository object or string, or undefined.
114
- */
115
- get repository() {
116
- return this.#data.repository;
117
- }
118
- #parseGitRepository(urlString) {
119
- if (!urlString) {
120
- return null;
121
- }
122
- const urlValue = urlString.includes(":") ? urlString : `https://${urlString}`;
123
- const url = new URL(urlValue);
124
- const scheme = url.protocol.slice(0, -1);
125
- if (GitProvider.isValidName(scheme)) {
126
- const [owner, repositoryName] = url.pathname.split("/");
127
- if (!owner || !repositoryName) {
128
- throw new Error("Unknown owner or repositoryName");
129
- }
130
- return {
131
- origin: GitProvider.getOrigin(scheme),
132
- owner,
133
- repositoryName
134
- };
135
- }
136
- if (scheme === "https") {
137
- const [, owner, repositoryName] = url.pathname.split("/");
138
- if (!owner || !repositoryName) {
139
- throw new Error("Unknown owner or repositoryName");
140
- }
141
- return {
142
- origin: url.origin,
143
- owner,
144
- repositoryName: repositoryName.endsWith(".git") ? repositoryName.slice(0, -4) : repositoryName
145
- };
146
- }
147
- throw new Error("Unsupported repository URL");
148
- }
149
- /**
150
- * Parse the repository information from `package.json` and return a `GitRepository` object or null.
151
- *
152
- * Returns null if no repository is defined or if parsing fails.
153
- *
154
- * @returns The parsed `GitRepository`, or null if not available.
155
- */
156
- parseGitRepository() {
157
- if (!this.repository) {
158
- return null;
159
- }
160
- if (typeof this.repository === "string") {
161
- return this.#parseGitRepository(this.repository);
162
- }
163
- return this.#parseGitRepository(this.repository.url);
164
- }
165
- /**
166
- * Save the current `package.json` data to disk, formatting it as prettified JSON.
167
- */
168
- async save() {
169
- const content = JsonUtils.prettify(this.#data) + "\n";
170
- await FileSystem.writeFile("package.json", content, { encoding: "utf8" });
171
- }
172
- }
173
- const runMain = (main2) => {
174
- Promise.resolve().then(() => main2(process.argv.slice(2))).then((exitCode) => {
175
- process.exit(exitCode);
176
- }).catch((error) => {
177
- console.error("Error:", error);
178
- process.exit(1);
179
- });
54
+ //#endregion
55
+ //#region src/modules/core/package-json/package-json.ts
56
+ var PackageJson = class PackageJson {
57
+ #data;
58
+ constructor(data) {
59
+ this.#data = data;
60
+ }
61
+ /**
62
+ * Load and parse the `package.json` file, returning a `PackageJson` instance.
63
+ *
64
+ * Throws an error if the file is invalid or cannot be parsed.
65
+ *
66
+ * @returns A new `PackageJson` instance with parsed data.
67
+ */
68
+ static async init() {
69
+ const content = await FileSystem.readFile("package.json", { encoding: "utf8" });
70
+ const data = JsonUtils.parse(content);
71
+ const parseResult = packageJsonDataSchema.safeParse(data);
72
+ if (!parseResult.success) throw new Error("Invalid package.json", { cause: parseResult.error });
73
+ return new PackageJson(data);
74
+ }
75
+ /**
76
+ * Get the package name from `package.json`.
77
+ *
78
+ * @returns The package name.
79
+ */
80
+ get name() {
81
+ return this.#data.name;
82
+ }
83
+ /**
84
+ * Get the package version from `package.json`.
85
+ *
86
+ * @returns The package version.
87
+ */
88
+ get version() {
89
+ return this.#data.version;
90
+ }
91
+ /**
92
+ * Get the engines field from `package.json`, if present.
93
+ *
94
+ * @returns The engines object or undefined.
95
+ */
96
+ get engines() {
97
+ return this.#data.engines;
98
+ }
99
+ /**
100
+ * Set the engines field in `package.json`.
101
+ *
102
+ * @param engines The engines object to set.
103
+ */
104
+ set engines(engines) {
105
+ this.#data.engines = engines;
106
+ }
107
+ /**
108
+ * Get the repository field from `package.json`, if present.
109
+ *
110
+ * @returns The repository object or string, or undefined.
111
+ */
112
+ get repository() {
113
+ return this.#data.repository;
114
+ }
115
+ #parseGitRepository(urlString) {
116
+ if (!urlString) return null;
117
+ const urlValue = urlString.includes(":") ? urlString : `https://${urlString}`;
118
+ const url = new URL(urlValue);
119
+ const scheme = url.protocol.slice(0, -1);
120
+ if (GitProvider.isValidName(scheme)) {
121
+ const [owner, repositoryName] = url.pathname.split("/");
122
+ if (!owner || !repositoryName) throw new Error("Unknown owner or repositoryName");
123
+ return {
124
+ origin: GitProvider.getOrigin(scheme),
125
+ owner,
126
+ repositoryName
127
+ };
128
+ }
129
+ if (scheme === "https") {
130
+ const [, owner, repositoryName] = url.pathname.split("/");
131
+ if (!owner || !repositoryName) throw new Error("Unknown owner or repositoryName");
132
+ return {
133
+ origin: url.origin,
134
+ owner,
135
+ repositoryName: repositoryName.endsWith(".git") ? repositoryName.slice(0, -4) : repositoryName
136
+ };
137
+ }
138
+ throw new Error("Unsupported repository URL");
139
+ }
140
+ /**
141
+ * Parse the repository information from `package.json` and return a `GitRepository` object or null.
142
+ *
143
+ * Returns null if no repository is defined or if parsing fails.
144
+ *
145
+ * @returns The parsed `GitRepository`, or null if not available.
146
+ */
147
+ parseGitRepository() {
148
+ if (!this.repository) return null;
149
+ if (typeof this.repository === "string") return this.#parseGitRepository(this.repository);
150
+ return this.#parseGitRepository(this.repository.url);
151
+ }
152
+ /**
153
+ * Save the current `package.json` data to disk, formatting it as prettified JSON.
154
+ */
155
+ async save() {
156
+ const content = JsonUtils.prettify(this.#data) + "\n";
157
+ await FileSystem.writeFile("package.json", content, { encoding: "utf8" });
158
+ }
159
+ };
160
+ //#endregion
161
+ //#region src/modules/core/run-main.ts
162
+ /**
163
+ * Execute the provided main function and handle any errors that occur.
164
+ *
165
+ * If the main function resolves, the process exits with the returned code.
166
+ * If an error is thrown, it logs the error and exits with code 1.
167
+ *
168
+ * @param main The main function to execute.
169
+ */
170
+ var runMain = (main) => {
171
+ Promise.resolve().then(() => main(process.argv.slice(2))).then((exitCode) => {
172
+ process.exit(exitCode);
173
+ }).catch((error) => {
174
+ console.error("Error:", error);
175
+ process.exit(1);
176
+ });
177
+ };
178
+ //#endregion
179
+ //#region src/modules/core/utils/json-utils.ts
180
+ var JsonUtils = class {
181
+ static stringify(value) {
182
+ return JSON.stringify(value);
183
+ }
184
+ static prettify(value) {
185
+ return JSON.stringify(value, null, 2);
186
+ }
187
+ static parse(value) {
188
+ return JSON.parse(value);
189
+ }
190
+ };
191
+ //#endregion
192
+ //#region src/modules/lint/file-linter.ts
193
+ var FileLinter = class FileLinter {
194
+ #options;
195
+ #eslint;
196
+ #eslintFixer = null;
197
+ constructor(options) {
198
+ this.#options = options ?? null;
199
+ this.#eslint = new ESLint({ overrideConfigFile: options?.configPath });
200
+ if (options?.fix) this.#eslintFixer = new ESLint({
201
+ fix: options.fix,
202
+ overrideConfigFile: options?.configPath
203
+ });
204
+ }
205
+ /**
206
+ * Lint files using `lint-staged` with the provided arguments.
207
+ *
208
+ * @param args An array of arguments to pass to `lint-staged`.
209
+ * @returns The exit status of the `lint-staged` command.
210
+ */
211
+ static lintStaged(args) {
212
+ const { status } = ChildProcess.spawnSync("lint-staged", args, { stdio: "inherit" });
213
+ return status ?? 0;
214
+ }
215
+ static #preparePrettierLintResult(results) {
216
+ if (results.length === 0) return { status: "success" };
217
+ const title = `Linting with Prettier failed with ${results.length} problem${results.length === 1 ? "" : "s"}\n`;
218
+ let message = "Prettier failed with the following files:\n";
219
+ message += results.map((result) => `- ${result.path}`).join("\n");
220
+ message += OS.EOL;
221
+ return {
222
+ status: "failure",
223
+ title,
224
+ message,
225
+ problemCount: results.length
226
+ };
227
+ }
228
+ async #isIgnoredByPrettier(path) {
229
+ if (path.endsWith(".sh")) return false;
230
+ return this.#eslint.isPathIgnored(path);
231
+ }
232
+ async #prettierLint(patterns) {
233
+ const paths = await glob(patterns, {
234
+ nodir: true,
235
+ ignore: "node_modules/**"
236
+ });
237
+ return (await Promise.all(paths.map(async (path) => {
238
+ const config = await Prettier.resolveConfig(path);
239
+ if (!config) return null;
240
+ if (await this.#isIgnoredByPrettier(path)) return null;
241
+ const content = (await FileSystem.readFile(path)).toString();
242
+ if (this.#options?.fix) {
243
+ const nextContent = await Prettier.format(content, {
244
+ ...config,
245
+ filepath: path
246
+ });
247
+ await FileSystem.writeFile(path, nextContent);
248
+ return null;
249
+ }
250
+ return await Prettier.check(content, {
251
+ ...config,
252
+ filepath: path
253
+ }) ? null : { path };
254
+ }))).filter((result) => result !== null);
255
+ }
256
+ async #prepareESLintLintResult(results) {
257
+ const problemCount = results.reduce((sum, result) => sum + result.errorCount + result.warningCount + (this.#options?.fix ? -(result.fixableErrorCount + result.fixableWarningCount) : 0), 0);
258
+ if (problemCount === 0) return { status: "success" };
259
+ return {
260
+ status: "failure",
261
+ title: `ESLint failed with ${problemCount} problem${problemCount === 1 ? "" : "s"}`,
262
+ message: await (await this.#eslint.loadFormatter("stylish")).format(results),
263
+ problemCount
264
+ };
265
+ }
266
+ async #eslintLint(patterns) {
267
+ const results = await this.#eslint.lintFiles(patterns);
268
+ const errorResults = ESLint.getErrorResults(results);
269
+ if (this.#eslintFixer && errorResults.length > 0) await ESLint.outputFixes(await this.#eslintFixer.lintFiles(patterns));
270
+ return results;
271
+ }
272
+ /**
273
+ * Lint files matching the provided patterns using ESLint and Prettier.
274
+ *
275
+ * @param patterns An array of glob patterns to match files against. Defaults to ['**'] which matches all files.
276
+ * @returns A promise that resolves to a LintFilesResult indicating the success or failure of the linting process.
277
+ */
278
+ async lint(patterns = ["**"]) {
279
+ const prettierResults = await this.#prettierLint(patterns);
280
+ const eslintResults = await this.#eslintLint(patterns);
281
+ const eslintResult = await this.#prepareESLintLintResult(eslintResults);
282
+ if (eslintResult.status === "failure") return eslintResult;
283
+ const prettierResult = FileLinter.#preparePrettierLintResult(prettierResults);
284
+ if (prettierResult.status === "failure") return prettierResult;
285
+ return { status: "success" };
286
+ }
287
+ };
288
+ //#endregion
289
+ //#region src/modules/release/releaser.ts
290
+ var Releaser = class Releaser {
291
+ static #releaseSeparator = /^## /gm;
292
+ #config;
293
+ #changelogPath;
294
+ #silent;
295
+ constructor(config, changelogPath, silent) {
296
+ this.#config = config;
297
+ this.#changelogPath = changelogPath;
298
+ this.#silent = silent;
299
+ }
300
+ /**
301
+ * Initialize a new instance of the Releaser class with the provided configuration.
302
+ *
303
+ * @param options Options for initializing the releaser, including paths for the config and changelog.
304
+ * @returns A new instance of Releaser.
305
+ */
306
+ static async init({ configPath = "release.config.js", changelogPath = "CHANGELOG.md", silent = false } = {}) {
307
+ const { default: config } = await import(`file://${Path.join(process.cwd(), configPath)}`);
308
+ return new Releaser(config, changelogPath, silent);
309
+ }
310
+ /**
311
+ * Create a new release based on the semantic-release configuration.
312
+ *
313
+ * It updates the changelog with the new release notes and runs linting on the changelog file.
314
+ *
315
+ * @returns The next release information or null if no release was created.
316
+ */
317
+ async createRelease() {
318
+ const silentStream = new Writable({ write(_chunk, _encoding, callback) {
319
+ callback();
320
+ } });
321
+ const result = await semanticRelease(this.#config, {
322
+ stderr: this.#silent ? silentStream : process.stderr,
323
+ stdout: this.#silent ? silentStream : process.stdout
324
+ });
325
+ silentStream.destroy();
326
+ if (!result) return null;
327
+ const { lastRelease, nextRelease } = result;
328
+ const oldChangelog = (await FileSystem.readFile(this.#changelogPath, { flag: FileSystem.constants.O_CREAT })).toString();
329
+ const newChangelog = Releaser.#prependReleaseNotes(oldChangelog, lastRelease, nextRelease);
330
+ await FileSystem.writeFile(this.#changelogPath, newChangelog);
331
+ await new FileLinter({ fix: true }).lint([this.#changelogPath]);
332
+ ChildProcess.execFileSync("pnpm", [
333
+ "version",
334
+ nextRelease.version,
335
+ "--allow-same-version",
336
+ "--no-git-tag-version",
337
+ this.#silent ? "--silent" : null
338
+ ].filter((arg) => arg !== null));
339
+ return {
340
+ name: (await PackageJson.init()).name,
341
+ tag: nextRelease.gitTag,
342
+ type: nextRelease.type,
343
+ versions: {
344
+ from: lastRelease.version,
345
+ to: nextRelease.version
346
+ }
347
+ };
348
+ }
349
+ /**
350
+ * Get the latest release information.
351
+ *
352
+ * @returns The latest release information including name, version, tag, and changelog.
353
+ * @throws If no release is found or if the header is not found in the changelog.
354
+ */
355
+ async getLatestReleaseInfo() {
356
+ const { name, version } = await PackageJson.init();
357
+ if (!version) throw new Error("Package version is not defined in package.json");
358
+ const tagFormat = this.#config.tagFormat ?? "v${version}";
359
+ const tag = tagFormat.replace("${version}", version);
360
+ const branchFormat = this.#config.branchFormat === void 0 ? tagFormat : this.#config.branchFormat;
361
+ return {
362
+ name,
363
+ version,
364
+ tag,
365
+ latestBranch: branchFormat === null ? null : branchFormat.replace("v${version}", "latest"),
366
+ changelog: await this.#getLatestReleaseChangelog()
367
+ };
368
+ }
369
+ async #getLatestReleaseChangelog() {
370
+ if (!existsSync(this.#changelogPath)) return null;
371
+ const [, latestReleaseChangelog] = (await FileSystem.readFile(this.#changelogPath)).toString().split(Releaser.#releaseSeparator);
372
+ if (!latestReleaseChangelog) return null;
373
+ const indexOfFirstEOL = latestReleaseChangelog.indexOf(OS.EOL);
374
+ if (indexOfFirstEOL === -1) throw new Error("Header not found");
375
+ return latestReleaseChangelog.slice(indexOfFirstEOL).trim();
376
+ }
377
+ static #findLastReleaseTitle(changelog, lastRelease) {
378
+ const lastReleaseTitleLink = `## [${lastRelease.version}](`;
379
+ if (changelog.includes(lastReleaseTitleLink)) return lastReleaseTitleLink;
380
+ const lastReleaseTitle = `## ${lastRelease.version}`;
381
+ if (changelog.includes(lastReleaseTitle)) return lastReleaseTitle;
382
+ return null;
383
+ }
384
+ static #prependReleaseNotes(changelog, lastRelease, nextRelease) {
385
+ const nextReleaseNotes = nextRelease.notes?.trim() ?? "";
386
+ if (changelog.length === 0) return nextReleaseNotes;
387
+ const lastReleaseTitle = this.#findLastReleaseTitle(changelog, lastRelease);
388
+ if (!lastReleaseTitle) return nextReleaseNotes;
389
+ return changelog.replace(lastReleaseTitle, `${nextReleaseNotes}\n\n${lastReleaseTitle}`);
390
+ }
180
391
  };
181
- class JsonUtils {
182
- static stringify(value) {
183
- return JSON.stringify(value);
184
- }
185
- static prettify(value) {
186
- return JSON.stringify(value, null, 2);
187
- }
188
- static parse(value) {
189
- return JSON.parse(value);
190
- }
191
- }
192
- class FileLinter {
193
- #options;
194
- #eslint;
195
- #eslintFixer = null;
196
- constructor(options) {
197
- this.#options = options ?? null;
198
- this.#eslint = new ESLint({
199
- overrideConfigFile: options?.configPath
200
- });
201
- if (options?.fix) {
202
- this.#eslintFixer = new ESLint({
203
- fix: options.fix,
204
- overrideConfigFile: options?.configPath
205
- });
206
- }
207
- }
208
- /**
209
- * Lint files using `lint-staged` with the provided arguments.
210
- *
211
- * @param args An array of arguments to pass to `lint-staged`.
212
- * @returns The exit status of the `lint-staged` command.
213
- */
214
- static lintStaged(args) {
215
- const { status } = ChildProcess.spawnSync("lint-staged", args, { stdio: "inherit" });
216
- return status ?? 0;
217
- }
218
- static #preparePrettierLintResult(results) {
219
- if (results.length === 0) {
220
- return { status: "success" };
221
- }
222
- const title = `Linting with Prettier failed with ${results.length} problem${results.length === 1 ? "" : "s"}
223
- `;
224
- let message = "Prettier failed with the following files:\n";
225
- message += results.map((result) => `- ${result.path}`).join("\n");
226
- message += OS.EOL;
227
- return {
228
- status: "failure",
229
- title,
230
- message,
231
- problemCount: results.length
232
- };
233
- }
234
- async #isIgnoredByPrettier(path) {
235
- if (path.endsWith(".sh")) {
236
- return false;
237
- }
238
- return this.#eslint.isPathIgnored(path);
239
- }
240
- async #prettierLint(patterns) {
241
- const paths = await glob(patterns, {
242
- nodir: true,
243
- ignore: "node_modules/**"
244
- });
245
- const results = await Promise.all(
246
- paths.map(async (path) => {
247
- const config = await Prettier.resolveConfig(path);
248
- if (!config) {
249
- return null;
250
- }
251
- if (await this.#isIgnoredByPrettier(path)) {
252
- return null;
253
- }
254
- const contentBuffer = await FileSystem.readFile(path);
255
- const content = contentBuffer.toString();
256
- if (this.#options?.fix) {
257
- const nextContent = await Prettier.format(content, {
258
- ...config,
259
- filepath: path
260
- });
261
- await FileSystem.writeFile(path, nextContent);
262
- return null;
263
- }
264
- const result = await Prettier.check(content, {
265
- ...config,
266
- filepath: path
267
- });
268
- return result ? null : { path };
269
- })
270
- );
271
- return results.filter((result) => result !== null);
272
- }
273
- async #prepareESLintLintResult(results) {
274
- const problemCount = results.reduce(
275
- (sum, result) => sum + result.errorCount + result.warningCount + (this.#options?.fix ? -(result.fixableErrorCount + result.fixableWarningCount) : 0),
276
- 0
277
- );
278
- if (problemCount === 0) {
279
- return { status: "success" };
280
- }
281
- const title = `ESLint failed with ${problemCount} problem${problemCount === 1 ? "" : "s"}`;
282
- const formatter = await this.#eslint.loadFormatter("stylish");
283
- const message = await formatter.format(results);
284
- return {
285
- status: "failure",
286
- title,
287
- message,
288
- problemCount
289
- };
290
- }
291
- async #eslintLint(patterns) {
292
- const results = await this.#eslint.lintFiles(patterns);
293
- const errorResults = ESLint.getErrorResults(results);
294
- if (this.#eslintFixer && errorResults.length > 0) {
295
- await ESLint.outputFixes(await this.#eslintFixer.lintFiles(patterns));
296
- }
297
- return results;
298
- }
299
- /**
300
- * Lint files matching the provided patterns using ESLint and Prettier.
301
- *
302
- * @param patterns An array of glob patterns to match files against. Defaults to ['**'] which matches all files.
303
- * @returns A promise that resolves to a LintFilesResult indicating the success or failure of the linting process.
304
- */
305
- async lint(patterns = ["**"]) {
306
- const prettierResults = await this.#prettierLint(patterns);
307
- const eslintResults = await this.#eslintLint(patterns);
308
- const eslintResult = await this.#prepareESLintLintResult(eslintResults);
309
- if (eslintResult.status === "failure") {
310
- return eslintResult;
311
- }
312
- const prettierResult = FileLinter.#preparePrettierLintResult(prettierResults);
313
- if (prettierResult.status === "failure") {
314
- return prettierResult;
315
- }
316
- return {
317
- status: "success"
318
- };
319
- }
320
- }
321
- class Releaser {
322
- static #releaseSeparator = /^## /gm;
323
- #config;
324
- #changelogPath;
325
- #silent;
326
- constructor(config, changelogPath, silent) {
327
- this.#config = config;
328
- this.#changelogPath = changelogPath;
329
- this.#silent = silent;
330
- }
331
- /**
332
- * Initialize a new instance of the Releaser class with the provided configuration.
333
- *
334
- * @param options Options for initializing the releaser, including paths for the config and changelog.
335
- * @returns A new instance of Releaser.
336
- */
337
- static async init({
338
- configPath = "release.config.js",
339
- changelogPath = "CHANGELOG.md",
340
- silent = false
341
- } = {}) {
342
- const configAbsolutePath = Path.join(process.cwd(), configPath);
343
- const { default: config } = await import(`file://${configAbsolutePath}`);
344
- return new Releaser(config, changelogPath, silent);
345
- }
346
- /**
347
- * Create a new release based on the semantic-release configuration.
348
- *
349
- * It updates the changelog with the new release notes and runs linting on the changelog file.
350
- *
351
- * @returns The next release information or null if no release was created.
352
- */
353
- async createRelease() {
354
- const silentStream = new Writable({
355
- write(_chunk, _encoding, callback) {
356
- callback();
357
- }
358
- });
359
- const result = await semanticRelease(this.#config, {
360
- stderr: this.#silent ? silentStream : process.stderr,
361
- stdout: this.#silent ? silentStream : process.stdout
362
- });
363
- silentStream.destroy();
364
- if (!result) {
365
- return null;
366
- }
367
- const { lastRelease, nextRelease } = result;
368
- const oldChangelogBuffer = await FileSystem.readFile(this.#changelogPath, { flag: FileSystem.constants.O_CREAT });
369
- const oldChangelog = oldChangelogBuffer.toString();
370
- const newChangelog = Releaser.#prependReleaseNotes(oldChangelog, lastRelease, nextRelease);
371
- await FileSystem.writeFile(this.#changelogPath, newChangelog);
372
- const linter = new FileLinter({ fix: true });
373
- await linter.lint([this.#changelogPath]);
374
- ChildProcess.execFileSync(
375
- "pnpm",
376
- [
377
- "version",
378
- nextRelease.version,
379
- "--allow-same-version",
380
- "--no-git-tag-version",
381
- this.#silent ? "--silent" : null
382
- ].filter((arg) => arg !== null)
383
- );
384
- const packageJson = await PackageJson.init();
385
- return {
386
- name: packageJson.name,
387
- tag: nextRelease.gitTag,
388
- type: nextRelease.type,
389
- versions: {
390
- from: lastRelease.version,
391
- to: nextRelease.version
392
- }
393
- };
394
- }
395
- /**
396
- * Get the latest release information.
397
- *
398
- * @returns The latest release information including name, version, tag, and changelog.
399
- * @throws If no release is found or if the header is not found in the changelog.
400
- */
401
- async getLatestReleaseInfo() {
402
- const { name, version } = await PackageJson.init();
403
- if (!version) {
404
- throw new Error("Package version is not defined in package.json");
405
- }
406
- const tagFormat = this.#config.tagFormat ?? "v${version}";
407
- const tag = tagFormat.replace("${version}", version);
408
- const branchFormat = this.#config.branchFormat === void 0 ? tagFormat : this.#config.branchFormat;
409
- const latestBranch = branchFormat === null ? null : branchFormat.replace("v${version}", "latest");
410
- const changelog = await this.#getLatestReleaseChangelog();
411
- return {
412
- name,
413
- version,
414
- tag,
415
- latestBranch,
416
- changelog
417
- };
418
- }
419
- async #getLatestReleaseChangelog() {
420
- if (!existsSync(this.#changelogPath)) {
421
- return null;
422
- }
423
- const contentBuffer = await FileSystem.readFile(this.#changelogPath);
424
- const content = contentBuffer.toString();
425
- const [, latestReleaseChangelog] = content.split(Releaser.#releaseSeparator);
426
- if (!latestReleaseChangelog) {
427
- return null;
428
- }
429
- const indexOfFirstEOL = latestReleaseChangelog.indexOf(OS.EOL);
430
- if (indexOfFirstEOL === -1) {
431
- throw new Error("Header not found");
432
- }
433
- return latestReleaseChangelog.slice(indexOfFirstEOL).trim();
434
- }
435
- static #findLastReleaseTitle(changelog, lastRelease) {
436
- const lastReleaseTitleLink = `## [${lastRelease.version}](`;
437
- if (changelog.includes(lastReleaseTitleLink)) {
438
- return lastReleaseTitleLink;
439
- }
440
- const lastReleaseTitle = `## ${lastRelease.version}`;
441
- if (changelog.includes(lastReleaseTitle)) {
442
- return lastReleaseTitle;
443
- }
444
- return null;
445
- }
446
- static #prependReleaseNotes(changelog, lastRelease, nextRelease) {
447
- const nextReleaseNotes = nextRelease.notes?.trim() ?? "";
448
- if (changelog.length === 0) {
449
- return nextReleaseNotes;
450
- }
451
- const lastReleaseTitle = this.#findLastReleaseTitle(changelog, lastRelease);
452
- if (!lastReleaseTitle) {
453
- return nextReleaseNotes;
454
- }
455
- return changelog.replace(lastReleaseTitle, `${nextReleaseNotes}
456
-
457
- ${lastReleaseTitle}`);
458
- }
459
- }
460
- const commandNameArgSchema = z.union([z.literal("create"), z.literal("info")]);
461
- const parseReleaseArgs = ([commandArg, silentArg]) => {
462
- const command = commandNameArgSchema.parse(commandArg);
463
- const silent = silentArg === "--silent";
464
- return {
465
- command,
466
- silent
467
- };
392
+ //#endregion
393
+ //#region src/app/scripts/release/args.ts
394
+ var commandNameArgSchema = z.union([z.literal("create"), z.literal("info")]);
395
+ var parseReleaseArgs = ([commandArg, silentArg]) => {
396
+ return {
397
+ command: commandNameArgSchema.parse(commandArg),
398
+ silent: silentArg === "--silent"
399
+ };
468
400
  };
469
- const main = async (args) => {
470
- try {
471
- const { command, silent } = parseReleaseArgs(args);
472
- const releaser = await Releaser.init({ silent });
473
- switch (command) {
474
- case "create": {
475
- const release = await releaser.createRelease();
476
- if (!release) {
477
- return 0;
478
- }
479
- if (process.env.GITHUB_ACTIONS === "true") {
480
- ActionsCore.setOutput("release", release);
481
- ActionsCore.setOutput(`release.${release.name}`, release);
482
- }
483
- console.log(JsonUtils.prettify(release));
484
- return 0;
485
- }
486
- case "info": {
487
- const info = await releaser.getLatestReleaseInfo();
488
- if (process.env.GITHUB_ACTIONS === "true") {
489
- ActionsCore.setOutput("info", info);
490
- ActionsCore.setOutput(`info.${info.name}`, info);
491
- }
492
- console.log(JsonUtils.prettify(info));
493
- return 0;
494
- }
495
- }
496
- } catch (error) {
497
- if (error instanceof ZodError) {
498
- const firstIssue = error.issues[0];
499
- if (firstIssue?.code === "invalid_union") {
500
- console.error("Error: Invalid command name");
501
- return 1;
502
- }
503
- }
504
- if (error instanceof Error) {
505
- console.error("Error:", error.message);
506
- } else {
507
- console.error("Error:", error);
508
- }
509
- return 1;
510
- }
401
+ //#endregion
402
+ //#region src/app/scripts/release/main.ts
403
+ var main = async (args) => {
404
+ try {
405
+ const { command, silent } = parseReleaseArgs(args);
406
+ const releaser = await Releaser.init({ silent });
407
+ switch (command) {
408
+ case "create": {
409
+ const release = await releaser.createRelease();
410
+ if (!release) return 0;
411
+ if (process.env.GITHUB_ACTIONS === "true") {
412
+ ActionsCore.setOutput("release", release);
413
+ ActionsCore.setOutput(`release.${release.name}`, release);
414
+ }
415
+ console.log(JsonUtils.prettify(release));
416
+ return 0;
417
+ }
418
+ case "info": {
419
+ const info = await releaser.getLatestReleaseInfo();
420
+ if (process.env.GITHUB_ACTIONS === "true") {
421
+ ActionsCore.setOutput("info", info);
422
+ ActionsCore.setOutput(`info.${info.name}`, info);
423
+ }
424
+ console.log(JsonUtils.prettify(info));
425
+ return 0;
426
+ }
427
+ }
428
+ } catch (error) {
429
+ if (error instanceof ZodError) {
430
+ if (error.issues[0]?.code === "invalid_union") {
431
+ console.error("Error: Invalid command name");
432
+ return 1;
433
+ }
434
+ }
435
+ if (error instanceof Error) console.error("Error:", error.message);
436
+ else console.error("Error:", error);
437
+ return 1;
438
+ }
511
439
  };
512
440
  runMain(main);
441
+ //#endregion
442
+ export {};