@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,445 +1,446 @@
|
|
|
1
1
|
import Path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
import ChildProcess from "node:child_process";
|
|
4
|
-
import loadCommitlintConfig from "@commitlint/load";
|
|
2
|
+
import { ZodError, z } from "zod";
|
|
5
3
|
import FileSystem from "node:fs/promises";
|
|
6
4
|
import OS from "node:os";
|
|
5
|
+
import ChildProcess from "node:child_process";
|
|
6
|
+
import loadCommitlintConfig from "@commitlint/load";
|
|
7
7
|
import { ESLint } from "eslint";
|
|
8
8
|
import { glob } from "glob";
|
|
9
9
|
import * as Prettier from "prettier";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
10
|
+
//#region src/modules/core/config.ts
|
|
11
|
+
var CoreConfig = class {
|
|
12
|
+
static #packageName = "@cluerise/tools";
|
|
13
|
+
/**
|
|
14
|
+
* Determine if the application is running in production mode.
|
|
15
|
+
*
|
|
16
|
+
* This checks the environment variable `import.meta.env.PROD` to determine the mode.
|
|
17
|
+
*
|
|
18
|
+
* @returns True if running in production mode, otherwise false.
|
|
19
|
+
*/
|
|
20
|
+
static get isProd() {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get the path to package configuration files in production.
|
|
25
|
+
*
|
|
26
|
+
* Used to load configuration from the package when running in production.
|
|
27
|
+
*
|
|
28
|
+
* @returns The path to the configuration files in the package.
|
|
29
|
+
*/
|
|
30
|
+
static get configPackage() {
|
|
31
|
+
return `${this.#packageName}/dist/configs`;
|
|
32
|
+
}
|
|
33
|
+
static get #rootDirectory() {
|
|
34
|
+
return this.isProd ? `./node_modules/${this.#packageName}` : ".";
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Get the directory path where configuration files are stored.
|
|
38
|
+
*
|
|
39
|
+
* In production, this points to `dist/configs` in the package; in development, it points to the root directory.
|
|
40
|
+
*
|
|
41
|
+
* @returns The path to the configuration directory.
|
|
42
|
+
*/
|
|
43
|
+
static get configDirectory() {
|
|
44
|
+
return this.isProd ? `${this.#rootDirectory}/dist/configs` : this.#rootDirectory;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/modules/core/console-status-logger.ts
|
|
49
|
+
/**
|
|
50
|
+
* Utility class for logging command status messages to the console.
|
|
51
|
+
*
|
|
52
|
+
* Provides methods to log the start and end of command execution, including the result.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const logger = new ConsoleStatusLogger('Linting');
|
|
56
|
+
* logger.begin('src/index.ts'); // Logs: "Linting src/index.ts..."
|
|
57
|
+
* // ... command execution ...
|
|
58
|
+
* logger.end('src/index.ts'); // Logs: "Linting src/index.ts... Done"
|
|
59
|
+
* logger.end('src/index.ts', 0); // Logs: "Linting src/index.ts... OK"
|
|
60
|
+
* logger.end('src/index.ts', 1); // Logs: "Linting src/index.ts... Failed"
|
|
61
|
+
*/
|
|
62
|
+
var ConsoleStatusLogger = class ConsoleStatusLogger {
|
|
63
|
+
#command;
|
|
64
|
+
/**
|
|
65
|
+
* Create a new logger for a specific command.
|
|
66
|
+
*
|
|
67
|
+
* @param command The command name to log.
|
|
68
|
+
*/
|
|
69
|
+
constructor(command) {
|
|
70
|
+
this.#command = command;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Log the beginning of a command with the provided argument.
|
|
74
|
+
*
|
|
75
|
+
* @param argument The argument for the command.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* const logger = new ConsoleStatusLogger('Linting');
|
|
79
|
+
* logger.begin('src/index.ts'); // Logs: "Linting src/index.ts..."
|
|
80
|
+
*/
|
|
81
|
+
begin(argument) {
|
|
82
|
+
console.info(`${this.#command} ${argument}...`);
|
|
83
|
+
}
|
|
84
|
+
static #createResultMessage(exitCode) {
|
|
85
|
+
if (exitCode === null) return "Done";
|
|
86
|
+
return exitCode === 0 ? "OK" : "Failed";
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Log the end of a command with the provided argument and exit code.
|
|
90
|
+
*
|
|
91
|
+
* @param argument The argument for the command.
|
|
92
|
+
* @param exitCode The exit code of the command execution. If null, it indicates completion without an error.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* const logger = new ConsoleStatusLogger('Linting');
|
|
96
|
+
* logger.end('src/index.ts'); // Logs: "Linting src/index.ts... Done"
|
|
97
|
+
* logger.end('src/index.ts', 0); // Logs: "Linting src/index.ts... OK"
|
|
98
|
+
* logger.end('src/index.ts', 1); // Logs: "Linting src/index.ts... Failed"
|
|
99
|
+
*/
|
|
100
|
+
end(argument, exitCode = null) {
|
|
101
|
+
console.info(`${this.#command} ${argument}... ${ConsoleStatusLogger.#createResultMessage(exitCode)}`);
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/modules/core/package-json/git-provider.ts
|
|
106
|
+
var gitProviderOrigins = { github: "https://github.com" };
|
|
107
|
+
(class {
|
|
108
|
+
static #origins = gitProviderOrigins;
|
|
109
|
+
static #names = Object.keys(this.#origins);
|
|
110
|
+
/**
|
|
111
|
+
* Check if the provided name is a valid Git provider name.
|
|
112
|
+
*
|
|
113
|
+
* @param name The name to check.
|
|
114
|
+
* @returns True if the name is valid, otherwise false.
|
|
115
|
+
*/
|
|
116
|
+
static isValidName(name) {
|
|
117
|
+
return this.#names.includes(name);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get the origin URL for the given Git provider name.
|
|
121
|
+
*
|
|
122
|
+
* @param name The Git provider name.
|
|
123
|
+
* @returns The origin URL.
|
|
124
|
+
*/
|
|
125
|
+
static getOrigin(name) {
|
|
126
|
+
return this.#origins[name];
|
|
127
|
+
}
|
|
92
128
|
});
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/modules/core/package-json/package-json-data.ts
|
|
131
|
+
var enginesSchema = z.object({ node: z.string() });
|
|
132
|
+
var repositoryObjectSchema = z.object({
|
|
133
|
+
type: z.string(),
|
|
134
|
+
url: z.string(),
|
|
135
|
+
directory: z.string().optional()
|
|
97
136
|
});
|
|
98
|
-
|
|
137
|
+
var repositorySchema = z.union([z.string(), repositoryObjectSchema]);
|
|
99
138
|
z.object({
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
139
|
+
name: z.string(),
|
|
140
|
+
version: z.string().optional(),
|
|
141
|
+
description: z.string().optional(),
|
|
142
|
+
engines: enginesSchema.optional(),
|
|
143
|
+
repository: repositorySchema.optional()
|
|
105
144
|
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/modules/core/run-main.ts
|
|
147
|
+
/**
|
|
148
|
+
* Execute the provided main function and handle any errors that occur.
|
|
149
|
+
*
|
|
150
|
+
* If the main function resolves, the process exits with the returned code.
|
|
151
|
+
* If an error is thrown, it logs the error and exits with code 1.
|
|
152
|
+
*
|
|
153
|
+
* @param main The main function to execute.
|
|
154
|
+
*/
|
|
155
|
+
var runMain = (main) => {
|
|
156
|
+
Promise.resolve().then(() => main(process.argv.slice(2))).then((exitCode) => {
|
|
157
|
+
process.exit(exitCode);
|
|
158
|
+
}).catch((error) => {
|
|
159
|
+
console.error("Error:", error);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/modules/core/utils/string-utils.ts
|
|
165
|
+
var StringUtils = class {
|
|
166
|
+
/**
|
|
167
|
+
* Capitalize the first letter of a given string.
|
|
168
|
+
*
|
|
169
|
+
* If the string is empty, it is returned unchanged.
|
|
170
|
+
*
|
|
171
|
+
* @param value The string to capitalize.
|
|
172
|
+
* @returns The string with the first letter capitalized, or the original string if empty.
|
|
173
|
+
*/
|
|
174
|
+
static capitalize(value) {
|
|
175
|
+
const [firstLetter] = value;
|
|
176
|
+
if (!firstLetter) return value;
|
|
177
|
+
return `${firstLetter.toUpperCase()}${value.slice(1)}`;
|
|
178
|
+
}
|
|
179
|
+
};
|
|
180
|
+
//#endregion
|
|
181
|
+
//#region src/modules/lint/commit-linter.ts
|
|
182
|
+
var CommitLinter = class CommitLinter {
|
|
183
|
+
#config;
|
|
184
|
+
constructor(config) {
|
|
185
|
+
this.#config = config;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Initialize the CommitLinter with the loaded commitlint configuration.
|
|
189
|
+
*
|
|
190
|
+
* @returns A Promise that resolves to an instance of CommitLinter.
|
|
191
|
+
*/
|
|
192
|
+
static async init() {
|
|
193
|
+
return new CommitLinter(await loadCommitlintConfig());
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Lint commit messages using commitlint.
|
|
197
|
+
*
|
|
198
|
+
* @param args An array of arguments to pass to commitlint.
|
|
199
|
+
* @returns The exit status of the commitlint command.
|
|
200
|
+
*/
|
|
201
|
+
static lint(args) {
|
|
202
|
+
const { status } = ChildProcess.spawnSync("commitlint", args, { stdio: "inherit" });
|
|
203
|
+
return status ?? 0;
|
|
204
|
+
}
|
|
205
|
+
#getRuleValues(ruleName) {
|
|
206
|
+
const rule = this.#config.rules[ruleName];
|
|
207
|
+
if (!rule) return null;
|
|
208
|
+
const [_severity, _condition, values] = rule;
|
|
209
|
+
return values ?? null;
|
|
210
|
+
}
|
|
211
|
+
#isValidType(type) {
|
|
212
|
+
const values = this.#getRuleValues("type-enum");
|
|
213
|
+
if (!values) return true;
|
|
214
|
+
return values.includes(type);
|
|
215
|
+
}
|
|
216
|
+
#isValidScope(scope) {
|
|
217
|
+
const values = this.#getRuleValues("scope-enum");
|
|
218
|
+
if (!values) return true;
|
|
219
|
+
if ("scopes" in values) return values.scopes.includes(scope);
|
|
220
|
+
return values.includes(scope);
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Parse a semantic branch name into its type and scope.
|
|
224
|
+
*
|
|
225
|
+
* @param name The branch name to parse.
|
|
226
|
+
* @returns An object containing the type and scope of the commit message.
|
|
227
|
+
* @throws An error if the branch name is invalid.
|
|
228
|
+
*/
|
|
229
|
+
parseSemanticBranchName(name) {
|
|
230
|
+
const [typeValue, scopeValue] = name.split("-");
|
|
231
|
+
if (!typeValue || !this.#isValidType(typeValue)) throw new Error("Invalid commit type in branch name");
|
|
232
|
+
return {
|
|
233
|
+
type: typeValue.toLowerCase(),
|
|
234
|
+
scope: scopeValue && this.#isValidScope(scopeValue) ? scopeValue.toLowerCase() : null
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get the prefix of a semantic commit message as a string.
|
|
239
|
+
*
|
|
240
|
+
* This prefix consists of the type and scope of the commit message.
|
|
241
|
+
* If the scope is not provided, it will only return the type.
|
|
242
|
+
*
|
|
243
|
+
* @param prefix An object containing the type and scope of the commit message.
|
|
244
|
+
* @returns The stringified prefix.
|
|
245
|
+
*/
|
|
246
|
+
static stringifySemanticCommitMessagePrefix({ type, scope }) {
|
|
247
|
+
return `${type}${scope ? `(${scope})` : ""}`;
|
|
248
|
+
}
|
|
249
|
+
static #parseSemanticCommitMessage(message) {
|
|
250
|
+
const firstColonPosition = message.search(":");
|
|
251
|
+
return {
|
|
252
|
+
prefix: message.slice(0, firstColonPosition).trim(),
|
|
253
|
+
content: message.slice(firstColonPosition + 1).trim()
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Format a semantic commit message by capitalizing the content after the prefix.
|
|
258
|
+
*
|
|
259
|
+
* @param message The commit message to format.
|
|
260
|
+
* @returns The formatted commit message.
|
|
261
|
+
*/
|
|
262
|
+
static formatSemanticCommitMessage(message) {
|
|
263
|
+
const { prefix, content } = this.#parseSemanticCommitMessage(message);
|
|
264
|
+
if (!prefix || !content) return message;
|
|
265
|
+
return `${prefix}: ${StringUtils.capitalize(content)}`;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Create a semantic commit message from a prefix and a message.
|
|
269
|
+
*
|
|
270
|
+
* @param prefix The prefix of the commit message, containing type and scope.
|
|
271
|
+
* @param message The content of the commit message.
|
|
272
|
+
* @returns The formatted semantic commit message.
|
|
273
|
+
*/
|
|
274
|
+
static createSemanticCommitMessage(prefix, message) {
|
|
275
|
+
const [subject, ...comments] = message.split("\n#");
|
|
276
|
+
let commitMessage = this.stringifySemanticCommitMessagePrefix(prefix) + ": ";
|
|
277
|
+
if (subject) commitMessage += StringUtils.capitalize(subject.trim());
|
|
278
|
+
if (comments.length > 0) commitMessage += "\n\n#" + comments.map((comment) => comment.trim()).join("\n#");
|
|
279
|
+
return commitMessage;
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
//#endregion
|
|
283
|
+
//#region src/modules/lint/file-linter.ts
|
|
284
|
+
var FileLinter = class FileLinter {
|
|
285
|
+
#options;
|
|
286
|
+
#eslint;
|
|
287
|
+
#eslintFixer = null;
|
|
288
|
+
constructor(options) {
|
|
289
|
+
this.#options = options ?? null;
|
|
290
|
+
this.#eslint = new ESLint({ overrideConfigFile: options?.configPath });
|
|
291
|
+
if (options?.fix) this.#eslintFixer = new ESLint({
|
|
292
|
+
fix: options.fix,
|
|
293
|
+
overrideConfigFile: options?.configPath
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Lint files using `lint-staged` with the provided arguments.
|
|
298
|
+
*
|
|
299
|
+
* @param args An array of arguments to pass to `lint-staged`.
|
|
300
|
+
* @returns The exit status of the `lint-staged` command.
|
|
301
|
+
*/
|
|
302
|
+
static lintStaged(args) {
|
|
303
|
+
const { status } = ChildProcess.spawnSync("lint-staged", args, { stdio: "inherit" });
|
|
304
|
+
return status ?? 0;
|
|
305
|
+
}
|
|
306
|
+
static #preparePrettierLintResult(results) {
|
|
307
|
+
if (results.length === 0) return { status: "success" };
|
|
308
|
+
const title = `Linting with Prettier failed with ${results.length} problem${results.length === 1 ? "" : "s"}\n`;
|
|
309
|
+
let message = "Prettier failed with the following files:\n";
|
|
310
|
+
message += results.map((result) => `- ${result.path}`).join("\n");
|
|
311
|
+
message += OS.EOL;
|
|
312
|
+
return {
|
|
313
|
+
status: "failure",
|
|
314
|
+
title,
|
|
315
|
+
message,
|
|
316
|
+
problemCount: results.length
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
async #isIgnoredByPrettier(path) {
|
|
320
|
+
if (path.endsWith(".sh")) return false;
|
|
321
|
+
return this.#eslint.isPathIgnored(path);
|
|
322
|
+
}
|
|
323
|
+
async #prettierLint(patterns) {
|
|
324
|
+
const paths = await glob(patterns, {
|
|
325
|
+
nodir: true,
|
|
326
|
+
ignore: "node_modules/**"
|
|
327
|
+
});
|
|
328
|
+
return (await Promise.all(paths.map(async (path) => {
|
|
329
|
+
const config = await Prettier.resolveConfig(path);
|
|
330
|
+
if (!config) return null;
|
|
331
|
+
if (await this.#isIgnoredByPrettier(path)) return null;
|
|
332
|
+
const content = (await FileSystem.readFile(path)).toString();
|
|
333
|
+
if (this.#options?.fix) {
|
|
334
|
+
const nextContent = await Prettier.format(content, {
|
|
335
|
+
...config,
|
|
336
|
+
filepath: path
|
|
337
|
+
});
|
|
338
|
+
await FileSystem.writeFile(path, nextContent);
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
return await Prettier.check(content, {
|
|
342
|
+
...config,
|
|
343
|
+
filepath: path
|
|
344
|
+
}) ? null : { path };
|
|
345
|
+
}))).filter((result) => result !== null);
|
|
346
|
+
}
|
|
347
|
+
async #prepareESLintLintResult(results) {
|
|
348
|
+
const problemCount = results.reduce((sum, result) => sum + result.errorCount + result.warningCount + (this.#options?.fix ? -(result.fixableErrorCount + result.fixableWarningCount) : 0), 0);
|
|
349
|
+
if (problemCount === 0) return { status: "success" };
|
|
350
|
+
return {
|
|
351
|
+
status: "failure",
|
|
352
|
+
title: `ESLint failed with ${problemCount} problem${problemCount === 1 ? "" : "s"}`,
|
|
353
|
+
message: await (await this.#eslint.loadFormatter("stylish")).format(results),
|
|
354
|
+
problemCount
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
async #eslintLint(patterns) {
|
|
358
|
+
const results = await this.#eslint.lintFiles(patterns);
|
|
359
|
+
const errorResults = ESLint.getErrorResults(results);
|
|
360
|
+
if (this.#eslintFixer && errorResults.length > 0) await ESLint.outputFixes(await this.#eslintFixer.lintFiles(patterns));
|
|
361
|
+
return results;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Lint files matching the provided patterns using ESLint and Prettier.
|
|
365
|
+
*
|
|
366
|
+
* @param patterns An array of glob patterns to match files against. Defaults to ['**'] which matches all files.
|
|
367
|
+
* @returns A promise that resolves to a LintFilesResult indicating the success or failure of the linting process.
|
|
368
|
+
*/
|
|
369
|
+
async lint(patterns = ["**"]) {
|
|
370
|
+
const prettierResults = await this.#prettierLint(patterns);
|
|
371
|
+
const eslintResults = await this.#eslintLint(patterns);
|
|
372
|
+
const eslintResult = await this.#prepareESLintLintResult(eslintResults);
|
|
373
|
+
if (eslintResult.status === "failure") return eslintResult;
|
|
374
|
+
const prettierResult = FileLinter.#preparePrettierLintResult(prettierResults);
|
|
375
|
+
if (prettierResult.status === "failure") return prettierResult;
|
|
376
|
+
return { status: "success" };
|
|
377
|
+
}
|
|
113
378
|
};
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const [firstLetter] = value;
|
|
125
|
-
if (!firstLetter) {
|
|
126
|
-
return value;
|
|
127
|
-
}
|
|
128
|
-
return `${firstLetter.toUpperCase()}${value.slice(1)}`;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
class CommitLinter {
|
|
132
|
-
#config;
|
|
133
|
-
constructor(config) {
|
|
134
|
-
this.#config = config;
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Initialize the CommitLinter with the loaded commitlint configuration.
|
|
138
|
-
*
|
|
139
|
-
* @returns A Promise that resolves to an instance of CommitLinter.
|
|
140
|
-
*/
|
|
141
|
-
static async init() {
|
|
142
|
-
const config = await loadCommitlintConfig();
|
|
143
|
-
return new CommitLinter(config);
|
|
144
|
-
}
|
|
145
|
-
/**
|
|
146
|
-
* Lint commit messages using commitlint.
|
|
147
|
-
*
|
|
148
|
-
* @param args An array of arguments to pass to commitlint.
|
|
149
|
-
* @returns The exit status of the commitlint command.
|
|
150
|
-
*/
|
|
151
|
-
static lint(args) {
|
|
152
|
-
const { status } = ChildProcess.spawnSync("commitlint", args, { stdio: "inherit" });
|
|
153
|
-
return status ?? 0;
|
|
154
|
-
}
|
|
155
|
-
#getRuleValues(ruleName) {
|
|
156
|
-
const rule = this.#config.rules[ruleName];
|
|
157
|
-
if (!rule) {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
const [_severity, _condition, values] = rule;
|
|
161
|
-
return values ?? null;
|
|
162
|
-
}
|
|
163
|
-
#isValidType(type) {
|
|
164
|
-
const values = this.#getRuleValues("type-enum");
|
|
165
|
-
if (!values) {
|
|
166
|
-
return true;
|
|
167
|
-
}
|
|
168
|
-
return values.includes(type);
|
|
169
|
-
}
|
|
170
|
-
#isValidScope(scope) {
|
|
171
|
-
const values = this.#getRuleValues("scope-enum");
|
|
172
|
-
if (!values) {
|
|
173
|
-
return true;
|
|
174
|
-
}
|
|
175
|
-
if ("scopes" in values) {
|
|
176
|
-
return values.scopes.includes(scope);
|
|
177
|
-
}
|
|
178
|
-
return values.includes(scope);
|
|
179
|
-
}
|
|
180
|
-
/**
|
|
181
|
-
* Parse a semantic branch name into its type and scope.
|
|
182
|
-
*
|
|
183
|
-
* @param name The branch name to parse.
|
|
184
|
-
* @returns An object containing the type and scope of the commit message.
|
|
185
|
-
* @throws An error if the branch name is invalid.
|
|
186
|
-
*/
|
|
187
|
-
parseSemanticBranchName(name) {
|
|
188
|
-
const [typeValue, scopeValue] = name.split("-");
|
|
189
|
-
if (!typeValue || !this.#isValidType(typeValue)) {
|
|
190
|
-
throw new Error("Invalid commit type in branch name");
|
|
191
|
-
}
|
|
192
|
-
const type = typeValue.toLowerCase();
|
|
193
|
-
const scope = scopeValue && this.#isValidScope(scopeValue) ? scopeValue.toLowerCase() : null;
|
|
194
|
-
return {
|
|
195
|
-
type,
|
|
196
|
-
scope
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Get the prefix of a semantic commit message as a string.
|
|
201
|
-
*
|
|
202
|
-
* This prefix consists of the type and scope of the commit message.
|
|
203
|
-
* If the scope is not provided, it will only return the type.
|
|
204
|
-
*
|
|
205
|
-
* @param prefix An object containing the type and scope of the commit message.
|
|
206
|
-
* @returns The stringified prefix.
|
|
207
|
-
*/
|
|
208
|
-
static stringifySemanticCommitMessagePrefix({ type, scope }) {
|
|
209
|
-
return `${type}${scope ? `(${scope})` : ""}`;
|
|
210
|
-
}
|
|
211
|
-
static #parseSemanticCommitMessage(message) {
|
|
212
|
-
const firstColonPosition = message.search(":");
|
|
213
|
-
const prefix = message.slice(0, firstColonPosition).trim();
|
|
214
|
-
const content = message.slice(firstColonPosition + 1).trim();
|
|
215
|
-
return { prefix, content };
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Format a semantic commit message by capitalizing the content after the prefix.
|
|
219
|
-
*
|
|
220
|
-
* @param message The commit message to format.
|
|
221
|
-
* @returns The formatted commit message.
|
|
222
|
-
*/
|
|
223
|
-
static formatSemanticCommitMessage(message) {
|
|
224
|
-
const { prefix, content } = this.#parseSemanticCommitMessage(message);
|
|
225
|
-
if (!prefix || !content) {
|
|
226
|
-
return message;
|
|
227
|
-
}
|
|
228
|
-
return `${prefix}: ${StringUtils.capitalize(content)}`;
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Create a semantic commit message from a prefix and a message.
|
|
232
|
-
*
|
|
233
|
-
* @param prefix The prefix of the commit message, containing type and scope.
|
|
234
|
-
* @param message The content of the commit message.
|
|
235
|
-
* @returns The formatted semantic commit message.
|
|
236
|
-
*/
|
|
237
|
-
static createSemanticCommitMessage(prefix, message) {
|
|
238
|
-
const [subject, ...comments] = message.split("\n#");
|
|
239
|
-
let commitMessage = this.stringifySemanticCommitMessagePrefix(prefix) + ": ";
|
|
240
|
-
if (subject) {
|
|
241
|
-
commitMessage += StringUtils.capitalize(subject.trim());
|
|
242
|
-
}
|
|
243
|
-
if (comments.length > 0) {
|
|
244
|
-
commitMessage += "\n\n#" + comments.map((comment) => comment.trim()).join("\n#");
|
|
245
|
-
}
|
|
246
|
-
return commitMessage;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
class FileLinter {
|
|
250
|
-
#options;
|
|
251
|
-
#eslint;
|
|
252
|
-
#eslintFixer = null;
|
|
253
|
-
constructor(options) {
|
|
254
|
-
this.#options = options ?? null;
|
|
255
|
-
this.#eslint = new ESLint({
|
|
256
|
-
overrideConfigFile: options?.configPath
|
|
257
|
-
});
|
|
258
|
-
if (options?.fix) {
|
|
259
|
-
this.#eslintFixer = new ESLint({
|
|
260
|
-
fix: options.fix,
|
|
261
|
-
overrideConfigFile: options?.configPath
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Lint files using `lint-staged` with the provided arguments.
|
|
267
|
-
*
|
|
268
|
-
* @param args An array of arguments to pass to `lint-staged`.
|
|
269
|
-
* @returns The exit status of the `lint-staged` command.
|
|
270
|
-
*/
|
|
271
|
-
static lintStaged(args) {
|
|
272
|
-
const { status } = ChildProcess.spawnSync("lint-staged", args, { stdio: "inherit" });
|
|
273
|
-
return status ?? 0;
|
|
274
|
-
}
|
|
275
|
-
static #preparePrettierLintResult(results) {
|
|
276
|
-
if (results.length === 0) {
|
|
277
|
-
return { status: "success" };
|
|
278
|
-
}
|
|
279
|
-
const title = `Linting with Prettier failed with ${results.length} problem${results.length === 1 ? "" : "s"}
|
|
280
|
-
`;
|
|
281
|
-
let message = "Prettier failed with the following files:\n";
|
|
282
|
-
message += results.map((result) => `- ${result.path}`).join("\n");
|
|
283
|
-
message += OS.EOL;
|
|
284
|
-
return {
|
|
285
|
-
status: "failure",
|
|
286
|
-
title,
|
|
287
|
-
message,
|
|
288
|
-
problemCount: results.length
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
async #isIgnoredByPrettier(path) {
|
|
292
|
-
if (path.endsWith(".sh")) {
|
|
293
|
-
return false;
|
|
294
|
-
}
|
|
295
|
-
return this.#eslint.isPathIgnored(path);
|
|
296
|
-
}
|
|
297
|
-
async #prettierLint(patterns) {
|
|
298
|
-
const paths = await glob(patterns, {
|
|
299
|
-
nodir: true,
|
|
300
|
-
ignore: "node_modules/**"
|
|
301
|
-
});
|
|
302
|
-
const results = await Promise.all(
|
|
303
|
-
paths.map(async (path) => {
|
|
304
|
-
const config = await Prettier.resolveConfig(path);
|
|
305
|
-
if (!config) {
|
|
306
|
-
return null;
|
|
307
|
-
}
|
|
308
|
-
if (await this.#isIgnoredByPrettier(path)) {
|
|
309
|
-
return null;
|
|
310
|
-
}
|
|
311
|
-
const contentBuffer = await FileSystem.readFile(path);
|
|
312
|
-
const content = contentBuffer.toString();
|
|
313
|
-
if (this.#options?.fix) {
|
|
314
|
-
const nextContent = await Prettier.format(content, {
|
|
315
|
-
...config,
|
|
316
|
-
filepath: path
|
|
317
|
-
});
|
|
318
|
-
await FileSystem.writeFile(path, nextContent);
|
|
319
|
-
return null;
|
|
320
|
-
}
|
|
321
|
-
const result = await Prettier.check(content, {
|
|
322
|
-
...config,
|
|
323
|
-
filepath: path
|
|
324
|
-
});
|
|
325
|
-
return result ? null : { path };
|
|
326
|
-
})
|
|
327
|
-
);
|
|
328
|
-
return results.filter((result) => result !== null);
|
|
329
|
-
}
|
|
330
|
-
async #prepareESLintLintResult(results) {
|
|
331
|
-
const problemCount = results.reduce(
|
|
332
|
-
(sum, result) => sum + result.errorCount + result.warningCount + (this.#options?.fix ? -(result.fixableErrorCount + result.fixableWarningCount) : 0),
|
|
333
|
-
0
|
|
334
|
-
);
|
|
335
|
-
if (problemCount === 0) {
|
|
336
|
-
return { status: "success" };
|
|
337
|
-
}
|
|
338
|
-
const title = `ESLint failed with ${problemCount} problem${problemCount === 1 ? "" : "s"}`;
|
|
339
|
-
const formatter = await this.#eslint.loadFormatter("stylish");
|
|
340
|
-
const message = await formatter.format(results);
|
|
341
|
-
return {
|
|
342
|
-
status: "failure",
|
|
343
|
-
title,
|
|
344
|
-
message,
|
|
345
|
-
problemCount
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
async #eslintLint(patterns) {
|
|
349
|
-
const results = await this.#eslint.lintFiles(patterns);
|
|
350
|
-
const errorResults = ESLint.getErrorResults(results);
|
|
351
|
-
if (this.#eslintFixer && errorResults.length > 0) {
|
|
352
|
-
await ESLint.outputFixes(await this.#eslintFixer.lintFiles(patterns));
|
|
353
|
-
}
|
|
354
|
-
return results;
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Lint files matching the provided patterns using ESLint and Prettier.
|
|
358
|
-
*
|
|
359
|
-
* @param patterns An array of glob patterns to match files against. Defaults to ['**'] which matches all files.
|
|
360
|
-
* @returns A promise that resolves to a LintFilesResult indicating the success or failure of the linting process.
|
|
361
|
-
*/
|
|
362
|
-
async lint(patterns = ["**"]) {
|
|
363
|
-
const prettierResults = await this.#prettierLint(patterns);
|
|
364
|
-
const eslintResults = await this.#eslintLint(patterns);
|
|
365
|
-
const eslintResult = await this.#prepareESLintLintResult(eslintResults);
|
|
366
|
-
if (eslintResult.status === "failure") {
|
|
367
|
-
return eslintResult;
|
|
368
|
-
}
|
|
369
|
-
const prettierResult = FileLinter.#preparePrettierLintResult(prettierResults);
|
|
370
|
-
if (prettierResult.status === "failure") {
|
|
371
|
-
return prettierResult;
|
|
372
|
-
}
|
|
373
|
-
return {
|
|
374
|
-
status: "success"
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
const lintScopeArgSchema = z.union([z.literal("all"), z.literal("fix"), z.literal("commit"), z.literal("staged")]);
|
|
379
|
-
const parseLintArgs = ([scopeArg]) => {
|
|
380
|
-
const scope = lintScopeArgSchema.parse(scopeArg);
|
|
381
|
-
return {
|
|
382
|
-
scope
|
|
383
|
-
};
|
|
379
|
+
//#endregion
|
|
380
|
+
//#region src/app/scripts/lint/args.ts
|
|
381
|
+
var lintScopeArgSchema = z.union([
|
|
382
|
+
z.literal("all"),
|
|
383
|
+
z.literal("fix"),
|
|
384
|
+
z.literal("commit"),
|
|
385
|
+
z.literal("staged")
|
|
386
|
+
]);
|
|
387
|
+
var parseLintArgs = ([scopeArg]) => {
|
|
388
|
+
return { scope: lintScopeArgSchema.parse(scopeArg) };
|
|
384
389
|
};
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
console.error("Error:", error.message);
|
|
439
|
-
} else {
|
|
440
|
-
console.error("Error:", error);
|
|
441
|
-
}
|
|
442
|
-
return 1;
|
|
443
|
-
}
|
|
390
|
+
//#endregion
|
|
391
|
+
//#region src/app/scripts/lint/main.ts
|
|
392
|
+
var main = async (args) => {
|
|
393
|
+
try {
|
|
394
|
+
const { scope } = parseLintArgs(args);
|
|
395
|
+
const statusLogger = new ConsoleStatusLogger("Linting");
|
|
396
|
+
statusLogger.begin(scope);
|
|
397
|
+
if (scope === "commit") {
|
|
398
|
+
const exitCode = CommitLinter.lint(args.slice(1));
|
|
399
|
+
statusLogger.end(scope, exitCode);
|
|
400
|
+
return exitCode;
|
|
401
|
+
}
|
|
402
|
+
if (scope === "staged") {
|
|
403
|
+
const exitCode = FileLinter.lintStaged(args.slice(1));
|
|
404
|
+
statusLogger.end(scope, exitCode);
|
|
405
|
+
return exitCode;
|
|
406
|
+
}
|
|
407
|
+
const fix = scope === "fix";
|
|
408
|
+
const lintStatus = await new FileLinter({ fix }).lint();
|
|
409
|
+
if (lintStatus.status === "failure") {
|
|
410
|
+
console.error(lintStatus.title);
|
|
411
|
+
console.error(lintStatus.message);
|
|
412
|
+
statusLogger.end(scope, 1);
|
|
413
|
+
return 1;
|
|
414
|
+
}
|
|
415
|
+
const markdownLinter = new FileLinter({
|
|
416
|
+
configPath: Path.join(CoreConfig.configDirectory, "eslint-markdown.config.js"),
|
|
417
|
+
fix
|
|
418
|
+
});
|
|
419
|
+
try {
|
|
420
|
+
const markdownLintStatus = await markdownLinter.lint(["**/*.md"]);
|
|
421
|
+
if (markdownLintStatus.status === "failure") {
|
|
422
|
+
console.error(markdownLintStatus.title);
|
|
423
|
+
console.error(markdownLintStatus.message);
|
|
424
|
+
statusLogger.end(scope, 1);
|
|
425
|
+
return 1;
|
|
426
|
+
}
|
|
427
|
+
} catch (error) {
|
|
428
|
+
if (!(error instanceof Error && "messageTemplate" in error && error.messageTemplate === "all-matched-files-ignored")) throw error;
|
|
429
|
+
}
|
|
430
|
+
statusLogger.end(scope);
|
|
431
|
+
return 0;
|
|
432
|
+
} catch (error) {
|
|
433
|
+
if (error instanceof ZodError) {
|
|
434
|
+
if (error.issues[0]?.code === "invalid_union") {
|
|
435
|
+
console.error("Error: Invalid scope");
|
|
436
|
+
return 1;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
if (error instanceof Error) console.error("Error:", error.message);
|
|
440
|
+
else console.error("Error:", error);
|
|
441
|
+
return 1;
|
|
442
|
+
}
|
|
444
443
|
};
|
|
445
444
|
runMain(main);
|
|
445
|
+
//#endregion
|
|
446
|
+
export {};
|