@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 +8 -4
- package/bin/check.mjs +1 -1
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +37 -0
- package/dist/index.d.ts +63 -5
- package/dist/index.js +3 -0
- package/dist/src-D3TrACF9.js +490 -0
- package/package.json +23 -38
- package/dist/cli.mjs +0 -34
- package/dist/index.mjs +0 -135
- package/dist/tasklist/index.d.ts +0 -8
- package/dist/tasklist/index.mjs +0 -78
- package/dist/tools/eslint.d.ts +0 -3
- package/dist/tools/eslint.mjs +0 -41
- package/dist/tools/eslint.test.d.ts +0 -1
- package/dist/tools/eslint.test.mjs +0 -35
- package/dist/tools/index.d.ts +0 -1
- package/dist/tools/index.mjs +0 -6
- package/dist/tools/oxlint.d.ts +0 -3
- package/dist/tools/oxlint.mjs +0 -49
- package/dist/tools/oxlint.test.d.ts +0 -1
- package/dist/tools/oxlint.test.mjs +0 -57
- package/dist/tools/prettier.d.ts +0 -3
- package/dist/tools/prettier.mjs +0 -40
- package/dist/tools/prettier.test.d.ts +0 -1
- package/dist/tools/prettier.test.mjs +0 -78
- package/dist/tools/publint.d.ts +0 -3
- package/dist/tools/publint.mjs +0 -28
- package/dist/tools/publint.test.d.ts +0 -1
- package/dist/tools/publint.test.mjs +0 -33
- package/dist/tools/typescript.d.ts +0 -3
- package/dist/tools/typescript.mjs +0 -47
- package/dist/tools/typescript.test.d.ts +0 -1
- package/dist/tools/typescript.test.mjs +0 -51
- package/dist/types.d.ts +0 -55
- package/dist/types.mjs +0 -0
- package/dist/utils.d.ts +0 -11
- package/dist/utils.mjs +0 -50
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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.
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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,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 };
|