@cluerise/tools 5.4.0 → 5.4.2

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,445 +1,446 @@
1
1
  import Path from "node:path";
2
- import { z, ZodError } from "zod";
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
- class CoreConfig {
11
- static #packageName = "@cluerise/tools";
12
- /**
13
- * Determine if the application is running in production mode.
14
- *
15
- * This checks the environment variable `import.meta.env.PROD` to determine the mode.
16
- *
17
- * @returns True if running in production mode, otherwise false.
18
- */
19
- static get isProd() {
20
- return true;
21
- }
22
- /**
23
- * Get the path to package configuration files in production.
24
- *
25
- * Used to load configuration from the package when running in production.
26
- *
27
- * @returns The path to the configuration files in the package.
28
- */
29
- static get configPackage() {
30
- return `${this.#packageName}/dist/configs`;
31
- }
32
- static get #rootDirectory() {
33
- return this.isProd ? `./node_modules/${this.#packageName}` : ".";
34
- }
35
- /**
36
- * Get the directory path where configuration files are stored.
37
- *
38
- * In production, this points to `dist/configs` in the package; in development, it points to the root directory.
39
- *
40
- * @returns The path to the configuration directory.
41
- */
42
- static get configDirectory() {
43
- return this.isProd ? `${this.#rootDirectory}/dist/configs` : this.#rootDirectory;
44
- }
45
- }
46
- class ConsoleStatusLogger {
47
- #command;
48
- /**
49
- * Create a new logger for a specific command.
50
- *
51
- * @param command The command name to log.
52
- */
53
- constructor(command) {
54
- this.#command = command;
55
- }
56
- /**
57
- * Log the beginning of a command with the provided argument.
58
- *
59
- * @param argument The argument for the command.
60
- *
61
- * @example
62
- * const logger = new ConsoleStatusLogger('Linting');
63
- * logger.begin('src/index.ts'); // Logs: "Linting src/index.ts..."
64
- */
65
- begin(argument) {
66
- console.info(`${this.#command} ${argument}...`);
67
- }
68
- static #createResultMessage(exitCode) {
69
- if (exitCode === null) {
70
- return "Done";
71
- }
72
- return exitCode === 0 ? "OK" : "Failed";
73
- }
74
- /**
75
- * Log the end of a command with the provided argument and exit code.
76
- *
77
- * @param argument The argument for the command.
78
- * @param exitCode The exit code of the command execution. If null, it indicates completion without an error.
79
- *
80
- * @example
81
- * const logger = new ConsoleStatusLogger('Linting');
82
- * logger.end('src/index.ts'); // Logs: "Linting src/index.ts... Done"
83
- * logger.end('src/index.ts', 0); // Logs: "Linting src/index.ts... OK"
84
- * logger.end('src/index.ts', 1); // Logs: "Linting src/index.ts... Failed"
85
- */
86
- end(argument, exitCode = null) {
87
- console.info(`${this.#command} ${argument}... ${ConsoleStatusLogger.#createResultMessage(exitCode)}`);
88
- }
89
- }
90
- const enginesSchema = z.object({
91
- node: z.string()
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
- const repositoryObjectSchema = z.object({
94
- type: z.string(),
95
- url: z.string(),
96
- directory: z.string().optional()
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
- const repositorySchema = z.union([z.string(), repositoryObjectSchema]);
137
+ var repositorySchema = z.union([z.string(), repositoryObjectSchema]);
99
138
  z.object({
100
- name: z.string(),
101
- version: z.string().optional(),
102
- description: z.string().optional(),
103
- engines: enginesSchema.optional(),
104
- repository: repositorySchema.optional()
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
- const runMain = (main2) => {
107
- Promise.resolve().then(() => main2(process.argv.slice(2))).then((exitCode) => {
108
- process.exit(exitCode);
109
- }).catch((error) => {
110
- console.error("Error:", error);
111
- process.exit(1);
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
- class StringUtils {
115
- /**
116
- * Capitalize the first letter of a given string.
117
- *
118
- * If the string is empty, it is returned unchanged.
119
- *
120
- * @param value The string to capitalize.
121
- * @returns The string with the first letter capitalized, or the original string if empty.
122
- */
123
- static capitalize(value) {
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
- const main = async (args) => {
386
- try {
387
- const { scope } = parseLintArgs(args);
388
- const statusLogger = new ConsoleStatusLogger("Linting");
389
- statusLogger.begin(scope);
390
- if (scope === "commit") {
391
- const exitCode = CommitLinter.lint(args.slice(1));
392
- statusLogger.end(scope, exitCode);
393
- return exitCode;
394
- }
395
- if (scope === "staged") {
396
- const exitCode = FileLinter.lintStaged(args.slice(1));
397
- statusLogger.end(scope, exitCode);
398
- return exitCode;
399
- }
400
- const fix = scope === "fix";
401
- const fileLinter = new FileLinter({ fix });
402
- const lintStatus = await fileLinter.lint();
403
- if (lintStatus.status === "failure") {
404
- console.error(lintStatus.title);
405
- console.error(lintStatus.message);
406
- statusLogger.end(scope, 1);
407
- return 1;
408
- }
409
- const markdownLinter = new FileLinter({
410
- configPath: Path.join(CoreConfig.configDirectory, "eslint-markdown.config.js"),
411
- fix
412
- });
413
- try {
414
- const markdownLintStatus = await markdownLinter.lint(["**/*.md"]);
415
- if (markdownLintStatus.status === "failure") {
416
- console.error(markdownLintStatus.title);
417
- console.error(markdownLintStatus.message);
418
- statusLogger.end(scope, 1);
419
- return 1;
420
- }
421
- } catch (error) {
422
- const allFilesIgnoredError = error instanceof Error && "messageTemplate" in error && error.messageTemplate === "all-matched-files-ignored";
423
- if (!allFilesIgnoredError) {
424
- throw error;
425
- }
426
- }
427
- statusLogger.end(scope);
428
- return 0;
429
- } catch (error) {
430
- if (error instanceof ZodError) {
431
- const firstIssue = error.issues[0];
432
- if (firstIssue?.code === "invalid_union") {
433
- console.error("Error: Invalid scope");
434
- return 1;
435
- }
436
- }
437
- if (error instanceof Error) {
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 {};