@aklinker1/check 2.0.0 → 2.1.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/README.md CHANGED
@@ -13,7 +13,7 @@ pnpm check --fix
13
13
  To enable checks for any of the following modules, just install them:
14
14
 
15
15
  ```sh
16
- pnpm i -D typescript oxlint prettier publint eslint
16
+ pnpm i -D typescript oxlint prettier publint eslint markdownlint-cli
17
17
  ```
18
18
 
19
19
  ## Contributing
@@ -30,10 +30,14 @@ bun run build
30
30
  # Run checks
31
31
  bun check --help
32
32
  bun check
33
+
34
+ # Debug commands used
35
+ DEBUG=1 bun check
33
36
  ```
34
37
 
35
38
  ### Adding Tools
36
39
 
37
- I've added everything I use, so if you want to add support for another tool, feel free.
38
-
39
- Just copy `src/tools/prettier.ts` and `src/tools/prettier.test.ts`, update the implementations (yes, tests are required), and add your new tool to `src/tools/index.ts`'s `ALL_TOOLS` export.
40
+ 1. Copy and rename `src/tools/prettier.ts` and `src/tools/prettier.test.ts` accordingly
41
+ 2. Implement and update tests for your new tool
42
+ 3. Add your tool to the `ALL_TOOLS` array in `src/tools/index.ts`
43
+ 4. Add the tool's NPM package to the first section of this README
package/bin/check.mjs CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import "../dist/cli.mjs";
2
+ import "../dist/cli.js";
package/dist/cli.d.ts CHANGED
@@ -1 +1 @@
1
- export {};
1
+ export { };
package/dist/cli.js ADDED
@@ -0,0 +1,37 @@
1
+ import { i as ALL_TOOLS, t as check } from "./src-D3TrACF9.js";
2
+ import { isCI } from "ci-info";
3
+
4
+ //#region package.json
5
+ var version = "2.1.1";
6
+
7
+ //#endregion
8
+ //#region src/cli.ts
9
+ const help = `\x1b[34m\x1b[1mcheck\x1b[0m runs all your project checks at once, standardizing and combining the output. \x1b[2m(${version})\x1b[0m
10
+
11
+ \x1b[1mSupported Tools:\x1b[0m
12
+ ${ALL_TOOLS.map((tool) => `\x1b[32m${tool.name}\x1b[0m`).sort().join(", ")}
13
+
14
+ \x1b[1mUsage:\x1b[0m
15
+ \x1b[1mcheck\x1b[0m \x1b[1m\x1b[36m[flags]\x1b[0m [root]
16
+
17
+ \x1b[1mArguments:\x1b[0m
18
+ root Directory to run commands from (default: .)
19
+
20
+ \x1b[1mFlags\x1b[0m:
21
+ \x1b[36m-f\x1b[0m, \x1b[36m--fix\x1b[0m Fix problems when possible (default: false in CI, true everywhere else)
22
+ \x1b[36m-d\x1b[0m, \x1b[36m--debug\x1b[0m Enable debug logs
23
+ \x1b[36m-h\x1b[0m, \x1b[36m--help\x1b[0m Show this help message and exit`;
24
+ const args = process.argv.slice(2);
25
+ const boolArg = (arg) => args.includes(arg) || void 0;
26
+ if (boolArg("-h") || boolArg("--help")) {
27
+ console.log(help);
28
+ process.exit(0);
29
+ }
30
+ await check({
31
+ fix: boolArg("-f") ?? boolArg("--fix") ?? !isCI,
32
+ debug: boolArg("-d") ?? boolArg("--debug") ?? false,
33
+ root: args.find((arg) => !arg.startsWith("-")) ?? "."
34
+ });
35
+
36
+ //#endregion
37
+ export { };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,63 @@
1
- import type { CheckOptions, Problem } from "./types";
2
- export type * from "./types";
3
- export declare function check(options?: CheckOptions): Promise<void>;
4
- export declare function renderProblemGroup(problems: Problem[]): string;
5
- export declare function renderProblem(problem: Problem): string;
1
+ //#region src/types.d.ts
2
+ interface CheckOptions {
3
+ /**
4
+ * Set to true to fix problems that can be automatically fixed.
5
+ *
6
+ * Defaults to `true` outside CI, and `false` inside CI.
7
+ */
8
+ fix?: boolean;
9
+ /**
10
+ * Directory to run commands in.
11
+ */
12
+ root?: string;
13
+ /**
14
+ * Set to true to enable debug logs.
15
+ */
16
+ debug?: boolean;
17
+ }
18
+ type ToolDefinition = (opts: {
19
+ root: string;
20
+ packageJson: any;
21
+ }) => Promise<Tool> | Tool;
22
+ interface Tool {
23
+ /**
24
+ * Name of tool shown in the console output.
25
+ */
26
+ name: string;
27
+ /**
28
+ * The name of the package in your `package.json`. If present, this tool will be ran.
29
+ */
30
+ packageName: string;
31
+ /**
32
+ * Run the tool, only checking for problems.
33
+ */
34
+ check: () => Promise<Problem[]>;
35
+ /**
36
+ * Run the tool, but fix problems if possible. If the tool doesn't support fixing problems, `check` will be called instead.
37
+ */
38
+ fix?: () => Promise<Problem[]>;
39
+ }
40
+ interface Problem {
41
+ location?: CodeLocation;
42
+ message: string;
43
+ file: string;
44
+ kind: ProblemKind;
45
+ rule?: string;
46
+ }
47
+ interface CodeLocation {
48
+ line: number;
49
+ column: number;
50
+ }
51
+ type ProblemKind = "warning" | "error";
52
+ type OutputParser = (data: {
53
+ code: number;
54
+ stdout: string;
55
+ stderr: string;
56
+ }) => Problem[];
57
+ //#endregion
58
+ //#region src/index.d.ts
59
+ declare function check(options?: CheckOptions): Promise<never>;
60
+ declare function renderProblemGroup(problems: Problem[]): string;
61
+ declare function renderProblem(problem: Problem): string;
62
+ //#endregion
63
+ export { CheckOptions, CodeLocation, OutputParser, Problem, ProblemKind, Tool, ToolDefinition, check, renderProblem, renderProblemGroup };
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ import { n as renderProblem, r as renderProblemGroup, t as check } from "./src-D3TrACF9.js";
2
+
3
+ export { check, renderProblem, renderProblemGroup };
@@ -0,0 +1,490 @@
1
+ import { isCI } from "ci-info";
2
+ import { spawn } from "node:child_process";
3
+ import readline from "node:readline";
4
+ import { join, relative, resolve, sep } from "node:path";
5
+ import { readFile } from "node:fs/promises";
6
+
7
+ //#region src/utils.ts
8
+ function exec(cmd, args, opts) {
9
+ return new Promise((resolve$1, reject) => {
10
+ const child = spawn(cmd, args, {
11
+ ...opts,
12
+ shell: true
13
+ });
14
+ let stderr = "";
15
+ let stdout = "";
16
+ child.stdout.on("data", (data) => {
17
+ stdout += data.toString();
18
+ });
19
+ child.stderr.on("data", (data) => {
20
+ stderr += data.toString();
21
+ });
22
+ child.on("error", (error) => {
23
+ reject(error);
24
+ });
25
+ child.on("close", (exitCode) => {
26
+ resolve$1({
27
+ stderr,
28
+ stdout,
29
+ exitCode
30
+ });
31
+ });
32
+ });
33
+ }
34
+ async function execAndParse(bin, args, cwd, parser) {
35
+ const res = await exec(bin, args, { cwd });
36
+ if (res.exitCode == null) throw Error("Exit code was null");
37
+ if (isDebug()) console.debug({
38
+ bin,
39
+ args,
40
+ cwd,
41
+ ...res
42
+ });
43
+ return parser({
44
+ code: res.exitCode,
45
+ stderr: res.stderr,
46
+ stdout: res.stdout
47
+ });
48
+ }
49
+ function isDebug() {
50
+ return process.env.DEBUG === "true" || process.env.DEBUG === "1";
51
+ }
52
+ const bold = (str) => `\x1b[1m${str}\x1b[0m`;
53
+ const dim = (str) => `\x1b[2m${str}\x1b[0m`;
54
+ const red = (str) => `\x1b[31m${str}\x1b[0m`;
55
+ const green = (str) => `\x1b[32m${str}\x1b[0m`;
56
+ const yellow = (str) => `\x1b[33m${str}\x1b[0m`;
57
+ const cyan = (str) => `\x1b[36m${str}\x1b[0m`;
58
+ function humanMs(ms) {
59
+ const minutes = Math.floor(ms / 6e4);
60
+ const seconds = (ms % 6e4 / 1e3).toFixed(minutes > 0 ? 0 : 3);
61
+ return `${minutes > 0 ? `${minutes}m ` : ""}${seconds}s`;
62
+ }
63
+ function debug(message) {
64
+ if (isDebug()) console.debug(dim("⚙ " + message));
65
+ }
66
+
67
+ //#endregion
68
+ //#region src/tools/eslint.ts
69
+ const eslint = ({ root }) => {
70
+ const bin = "eslint";
71
+ const checkArgs = [
72
+ ".",
73
+ "--ext",
74
+ ".js,.ts,.jsx,.tsx,.mjs,.mts,.cjs,.cts,.vue",
75
+ "--format",
76
+ "compact",
77
+ "--max-warnings",
78
+ "0"
79
+ ];
80
+ const fixArgs = [...checkArgs, "--fix"];
81
+ return {
82
+ name: "ESLint",
83
+ packageName: "eslint",
84
+ check: () => execAndParse(bin, checkArgs, root, parseOutput$5),
85
+ fix: () => execAndParse(bin, fixArgs, root, parseOutput$5)
86
+ };
87
+ };
88
+ const parseOutput$5 = ({ stdout, stderr }) => {
89
+ return `${stdout}\n${stderr}`.split(/\r?\n/).reduce((acc, line) => {
90
+ const groups = /^(?<file>.*?): line (?<line>[0-9]+), col (?<column>[0-9]+), (?<kind>\S+) - (?<message>.*?) \((?<rule>\S*?)\)$/.exec(line)?.groups;
91
+ if (groups) acc.push({
92
+ file: groups.file,
93
+ kind: groups.kind === "Warning" ? "warning" : "error",
94
+ message: groups.message,
95
+ location: {
96
+ line: parseInt(groups.line, 10),
97
+ column: parseInt(groups.column, 10)
98
+ },
99
+ rule: groups.rule
100
+ });
101
+ return acc;
102
+ }, []);
103
+ };
104
+
105
+ //#endregion
106
+ //#region src/tools/markdownlint.ts
107
+ const markdownlint = ({ root }) => {
108
+ const bin = "markdownlint";
109
+ const checkArgs = [
110
+ ".",
111
+ "--json",
112
+ "--ignore='**/dist/**'",
113
+ "--ignore='**/node_modules/**'",
114
+ "--ignore='**/.output/**'",
115
+ "--ignore='**/coverage/**'"
116
+ ];
117
+ const fixArgs = [...checkArgs, "--fix"];
118
+ return {
119
+ name: "Markdownlint",
120
+ packageName: "markdownlint-cli",
121
+ check: () => execAndParse(bin, checkArgs, root, parseOutput$4),
122
+ fix: () => execAndParse(bin, fixArgs, root, parseOutput$4)
123
+ };
124
+ };
125
+ const parseOutput$4 = ({ stderr: _stderr }) => {
126
+ const stderr = _stderr.trim();
127
+ if (!stderr) return [];
128
+ return JSON.parse(stderr).map((warning) => ({
129
+ location: {
130
+ line: warning.lineNumber,
131
+ column: warning.errorRange?.[0] ?? 0
132
+ },
133
+ message: warning.ruleDescription,
134
+ file: warning.fileName,
135
+ kind: "warning",
136
+ rule: warning.ruleNames[0]
137
+ }));
138
+ };
139
+
140
+ //#endregion
141
+ //#region src/tools/oxlint.ts
142
+ const oxlint = ({ root }) => {
143
+ const bin = "oxlint";
144
+ const checkArgs = [
145
+ "--format=unix",
146
+ "--deny-warnings",
147
+ "--ignore-path=.oxlintignore",
148
+ "--ignore-pattern='**/dist/**'",
149
+ "--ignore-pattern='**/node_modules/**'",
150
+ "--ignore-pattern='**/.output/**'",
151
+ "--ignore-pattern='**/coverage/**'"
152
+ ];
153
+ const fixArgs = [...checkArgs, "--fix"];
154
+ return {
155
+ name: "Oxlint",
156
+ packageName: "oxlint",
157
+ check: () => execAndParse(bin, checkArgs, root, parseOutput$3),
158
+ fix: () => execAndParse(bin, fixArgs, root, parseOutput$3)
159
+ };
160
+ };
161
+ const parseOutput$3 = ({ stdout }) => {
162
+ if (stdout.trim()) return stdout.split(/\r?\n/).reduce((acc, line) => {
163
+ const groups = /^(?<file>.+?):(?<line>[0-9]+):(?<column>[0-9]+):\s?(?<message>.*?)\s?\[(?<kind>Warning|Error)\/?(?<rule>.*?)\]\s?$/.exec(line)?.groups;
164
+ if (groups) acc.push({
165
+ file: groups.file,
166
+ kind: groups.kind === "Error" ? "error" : "warning",
167
+ message: groups.message,
168
+ rule: groups.rule || void 0,
169
+ location: {
170
+ line: parseInt(groups.line, 10),
171
+ column: parseInt(groups.column, 10)
172
+ }
173
+ });
174
+ return acc;
175
+ }, []);
176
+ return stdout.trim().split(/\r?\n/).map((line) => line.trim()).filter((line) => !!line && !line.includes(" ")).map((line) => ({
177
+ file: line.trim(),
178
+ kind: "warning",
179
+ message: "Not formatted."
180
+ }));
181
+ };
182
+
183
+ //#endregion
184
+ //#region src/tools/prettier.ts
185
+ const prettier = ({ root }) => {
186
+ const bin = "prettier";
187
+ const checkArgs = [".", "--list-different"];
188
+ const fixArgs = [".", "-w"];
189
+ return {
190
+ name: "Prettier",
191
+ packageName: "prettier",
192
+ check: () => execAndParse(bin, checkArgs, root, parseOutput$2),
193
+ fix: () => execAndParse(bin, fixArgs, root, parseOutput$2)
194
+ };
195
+ };
196
+ const parseOutput$2 = ({ stdout, stderr }) => {
197
+ if (stderr.trim()) return stderr.split(/\r?\n/).reduce((acc, line) => {
198
+ const groups = /^\[(?<kind>.+?)\]\s?(?<file>.+?):\s?(?<message>.*?)\s?\((?<line>[0-9]+):(?<column>[0-9]+)\)$/.exec(line)?.groups;
199
+ if (groups) acc.push({
200
+ file: groups.file,
201
+ kind: groups.kind === "error" ? "error" : "warning",
202
+ message: groups.message,
203
+ location: {
204
+ line: parseInt(groups.line, 10),
205
+ column: parseInt(groups.column, 10)
206
+ }
207
+ });
208
+ return acc;
209
+ }, []);
210
+ return stdout.trim().split(/\r?\n/).map((line) => line.trim()).filter((line) => !!line && !line.includes(" ")).map((line) => ({
211
+ file: line.trim(),
212
+ kind: "warning",
213
+ message: "Not formatted."
214
+ }));
215
+ };
216
+
217
+ //#endregion
218
+ //#region src/tools/publint.ts
219
+ const publint = ({ root }) => {
220
+ const bin = "publint";
221
+ const args = [];
222
+ return {
223
+ name: "Publint",
224
+ packageName: "publint",
225
+ check: () => execAndParse(bin, args, root, parseOutput$1)
226
+ };
227
+ };
228
+ const parseOutput$1 = ({ stdout }) => {
229
+ let kind = "warning";
230
+ return stdout.split(/\r?\n/).reduce((acc, line) => {
231
+ if (line.includes("Errors:")) {
232
+ kind = "error";
233
+ return acc;
234
+ }
235
+ const groups = /^[0-9]+\.\s?(?<message>.*)$/.exec(line)?.groups;
236
+ if (groups == null) return acc;
237
+ acc.push({
238
+ kind,
239
+ message: groups.message,
240
+ file: "package.json"
241
+ });
242
+ return acc;
243
+ }, []);
244
+ };
245
+
246
+ //#endregion
247
+ //#region src/tools/typescript.ts
248
+ const typescript = async ({ root, packageJson }) => {
249
+ const tsc = {
250
+ name: "TypeScript",
251
+ bin: "tsc",
252
+ packageName: "typescript",
253
+ args: [
254
+ "--noEmit",
255
+ "--pretty",
256
+ "false"
257
+ ]
258
+ };
259
+ const vueTsc = {
260
+ name: "TypeScript (Vue)",
261
+ bin: "vue-tsc",
262
+ packageName: "vue-tsc",
263
+ args: [
264
+ "--noEmit",
265
+ "--pretty",
266
+ "false"
267
+ ]
268
+ };
269
+ const isVueTsc = packageJson.devDependencies?.["vue-tsc"] !== void 0;
270
+ debug("TypeScript: Is vue-tsc installed? " + isVueTsc);
271
+ const cmd = isVueTsc ? vueTsc : tsc;
272
+ return {
273
+ name: cmd.name,
274
+ packageName: cmd.packageName,
275
+ check: async () => execAndParse(cmd.bin, cmd.args, root, parseOutput)
276
+ };
277
+ };
278
+ const parseOutput = ({ stdout }) => {
279
+ return stdout.split(/\r?\n/).reduce((acc, line) => {
280
+ const groups = /^(?<file>\S+?)\((?<line>[0-9]+),(?<column>[0-9]+)\): \w+? (?<rule>TS[0-9]+): (?<message>.*)$/.exec(line)?.groups;
281
+ if (groups) acc.push({
282
+ file: groups.file,
283
+ kind: "error",
284
+ message: groups.message,
285
+ location: {
286
+ line: parseInt(groups.line, 10),
287
+ column: parseInt(groups.column, 10)
288
+ },
289
+ rule: groups.rule
290
+ });
291
+ return acc;
292
+ }, []);
293
+ };
294
+
295
+ //#endregion
296
+ //#region src/tools/index.ts
297
+ const ALL_TOOLS = [
298
+ eslint,
299
+ markdownlint,
300
+ oxlint,
301
+ prettier,
302
+ publint,
303
+ typescript
304
+ ];
305
+
306
+ //#endregion
307
+ //#region src/tasklist/index.ts
308
+ async function createTaskList(inputs, run) {
309
+ const states = inputs.map((item) => ({
310
+ title: item.name,
311
+ state: "pending"
312
+ }));
313
+ const isTty = process.stderr.isTTY;
314
+ let tick = 0;
315
+ const render = (opts) => {
316
+ if (isTty && !opts?.firstRender) readline.moveCursor(process.stderr, 0, -1 * states.length);
317
+ if (isTty || opts?.firstRender || opts?.lastRender) states.forEach(({ state, title }) => {
318
+ readline.clearLine(process.stderr, 0);
319
+ const frames = SPINNER_FRAMES[state];
320
+ process.stderr.write(`${frames[tick % frames.length]} ${title}\n`);
321
+ });
322
+ tick++;
323
+ };
324
+ render({ firstRender: true });
325
+ const renderInterval = setInterval(render, SPINNER_INTERVAL_MS);
326
+ try {
327
+ const result = await Promise.all(inputs.map(async (input, i) => {
328
+ const succeed = (title) => {
329
+ if (title != null) states[i].title = title;
330
+ states[i].state = "success";
331
+ render();
332
+ };
333
+ const warn = (title) => {
334
+ if (title != null) states[i].title = title;
335
+ states[i].state = "warning";
336
+ render();
337
+ };
338
+ const fail = (title) => {
339
+ if (title != null) states[i].title = title;
340
+ states[i].state = "error";
341
+ render();
342
+ };
343
+ try {
344
+ states[i].state = "in-progress";
345
+ render();
346
+ const res = await run({
347
+ input,
348
+ succeed,
349
+ warn,
350
+ fail
351
+ });
352
+ if (states[i].state === "in-progress") states[i].state = "success";
353
+ render();
354
+ return res;
355
+ } catch (err) {
356
+ if (err instanceof Error) fail(err.message);
357
+ else fail(String(err));
358
+ render();
359
+ throw err;
360
+ }
361
+ }));
362
+ render({ lastRender: true });
363
+ return result;
364
+ } finally {
365
+ clearInterval(renderInterval);
366
+ }
367
+ }
368
+ const SPINNER_INTERVAL_MS = 80;
369
+ const SPINNER_FRAMES = {
370
+ pending: [dim("□")],
371
+ "in-progress": [
372
+ "⠋",
373
+ "⠙",
374
+ "⠹",
375
+ "⠸",
376
+ "⠼",
377
+ "⠴",
378
+ "⠦",
379
+ "⠧",
380
+ "⠇",
381
+ "⠏"
382
+ ].map(cyan),
383
+ success: [green("✔")],
384
+ warning: [yellow("⚠")],
385
+ error: [red("✗")]
386
+ };
387
+
388
+ //#endregion
389
+ //#region src/index.ts
390
+ async function check(options = {}) {
391
+ const { debug: debug$1, fix = !isCI, root = process.cwd() } = options;
392
+ const packageJson = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
393
+ if (debug$1) process.env.DEBUG = "true";
394
+ console.log();
395
+ debug("Options:" + JSON.stringify(options));
396
+ debug("Resolved options:" + JSON.stringify({
397
+ debug: debug$1,
398
+ fix,
399
+ root,
400
+ packageJson
401
+ }));
402
+ const tools = await findInstalledTools({
403
+ root,
404
+ packageJson
405
+ });
406
+ if (tools.length === 0) {
407
+ if (isDebug()) console.log("No tools detected!");
408
+ else console.log("No tools detected! Run with --debug for more info");
409
+ console.log();
410
+ process.exit(1);
411
+ }
412
+ const problems = (await createTaskList(tools, async ({ input: tool, fail, succeed, warn }) => {
413
+ const startTime = performance.now();
414
+ const problems$1 = await (fix ? tool.fix ?? tool.check : tool.check)();
415
+ problems$1.forEach((problem) => {
416
+ problem.file = resolve(root ?? process.cwd(), problem.file);
417
+ });
418
+ const duration = humanMs(performance.now() - startTime);
419
+ const title = `${tool.name} ${dim(`(${duration})`)}`;
420
+ if (problems$1.filter((p) => p.kind === "error").length > 0) fail(title);
421
+ else if (problems$1.length > 0) warn(title);
422
+ else succeed(title);
423
+ return problems$1;
424
+ })).flat();
425
+ console.log();
426
+ if (problems.length === 0) process.exit(0);
427
+ console.log(plural(problems.length, "Problem:", "Problems:"));
428
+ problems.sort((l, r) => {
429
+ const nameCompare = l.file.localeCompare(r.file);
430
+ if (nameCompare !== 0) return nameCompare;
431
+ const lineCompare = (l.location?.line ?? 0) - (r.location?.line ?? 0);
432
+ if (lineCompare !== 0) return lineCompare;
433
+ return (l.location?.column ?? 0) - (r.location?.column ?? 0);
434
+ });
435
+ const groupedProblems = problems.reduce((acc, problem) => {
436
+ const locationHash = `${problem.file}:${problem.location?.line}:${problem.location?.column}`;
437
+ const list = acc.get(locationHash) ?? [];
438
+ list.push(problem);
439
+ acc.set(locationHash, list);
440
+ return acc;
441
+ }, /* @__PURE__ */ new Map());
442
+ console.log([...groupedProblems.values()].map(renderProblemGroup).join("\n"));
443
+ const files = Object.entries(problems.reduce((acc, problem) => {
444
+ const file = "." + sep + relative(process.cwd(), problem.file);
445
+ acc[file] ??= 0;
446
+ acc[file]++;
447
+ return acc;
448
+ }, {}));
449
+ const maxLength = files.reduce((prev, [file]) => Math.max(prev, file.length), 0);
450
+ console.log();
451
+ console.log("Across " + plural(files.length, "File:", "Files:"));
452
+ files.forEach(([file, count]) => {
453
+ console.log(`${cyan(file.padEnd(maxLength, " "))} ${count}`);
454
+ });
455
+ console.log();
456
+ process.exit(problems.length);
457
+ }
458
+ async function findInstalledTools(opts) {
459
+ const status = await Promise.all(ALL_TOOLS.map(async (def) => {
460
+ const tool = await def(opts);
461
+ return {
462
+ tool,
463
+ isInstalled: !!opts.packageJson.devDependencies?.[tool.packageName]
464
+ };
465
+ }));
466
+ if (isDebug()) {
467
+ const getTools = (isInstalled) => status.filter((item) => item.isInstalled === isInstalled).map((item) => item.tool.name).join(", ");
468
+ debug(`Installed: ${getTools(true) || "(none)"}`);
469
+ debug(`Skipping: ${getTools(false) || "(none)"} `);
470
+ }
471
+ return status.filter(({ isInstalled }) => isInstalled).map((item) => item.tool);
472
+ }
473
+ function plural(count, singular, plural$1) {
474
+ return `${count} ${count === 1 ? singular : plural$1} `;
475
+ }
476
+ function renderProblemGroup(problems) {
477
+ const renderedProblems = problems.map(renderProblem);
478
+ const problem = problems[0];
479
+ const path = relative(process.cwd(), problem.file);
480
+ const link = dim(`→ .${sep}${problem.location ? `${path}:${problem.location.line}:${problem.location.column}` : path}`);
481
+ return `${renderedProblems.join("\n")}\n ${link}`;
482
+ }
483
+ function renderProblem(problem) {
484
+ const icon = problem.kind === "warning" ? bold(yellow("⚠")) : bold(red("✗"));
485
+ const source = problem.rule ? dim(` (${problem.rule})`) : "";
486
+ return `${icon} ${problem.message}${source}`;
487
+ }
488
+
489
+ //#endregion
490
+ export { ALL_TOOLS as i, renderProblem as n, renderProblemGroup as r, check as t };