@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.
- package/dist/configs/.nvmrc +1 -1
- package/dist/scripts/check-heroku-node-version/main.js +227 -228
- package/dist/scripts/create-commit-message/main.js +227 -213
- package/dist/scripts/format-commit-message/main.js +194 -181
- package/dist/scripts/init/main.js +557 -601
- package/dist/scripts/lint/main.js +432 -431
- package/dist/scripts/release/main.js +425 -495
- package/dist/scripts/update-node-versions/main.js +243 -259
- package/dist/scripts/update-xcode-version/main.js +202 -204
- package/package.json +17 -17
|
@@ -1,512 +1,442 @@
|
|
|
1
1
|
import * as ActionsCore from "@actions/core";
|
|
2
|
-
import {
|
|
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
|
-
|
|
15
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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 {};
|