@eimerreis/linting 0.1.0 → 0.2.0
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 +17 -4
- package/bin/init.mjs +183 -29
- package/package.json +8 -5
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Personal linting and formatting defaults for new JavaScript/TypeScript projects.
|
|
4
4
|
|
|
5
|
+
Requires Node `^20.19.0 || >=22.12.0`.
|
|
6
|
+
|
|
5
7
|
Built on:
|
|
6
8
|
|
|
7
9
|
- `oxlint` for linting
|
|
@@ -20,6 +22,12 @@ Default focus:
|
|
|
20
22
|
npm add -D @eimerreis/linting oxlint oxfmt
|
|
21
23
|
```
|
|
22
24
|
|
|
25
|
+
`lint` also executes `react-doctor` via:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx react-doctor -y .
|
|
29
|
+
```
|
|
30
|
+
|
|
23
31
|
## Quick Start
|
|
24
32
|
|
|
25
33
|
From your project root:
|
|
@@ -39,7 +47,7 @@ This creates:
|
|
|
39
47
|
- `.oxlintrc.json`
|
|
40
48
|
- `.oxfmtrc.json`
|
|
41
49
|
|
|
42
|
-
And updates `package.json` scripts (if present):
|
|
50
|
+
And updates `package.json` scripts (if present) so linting is executed through this package:
|
|
43
51
|
|
|
44
52
|
- `lint`
|
|
45
53
|
- `lint:fix`
|
|
@@ -49,11 +57,16 @@ And updates `package.json` scripts (if present):
|
|
|
49
57
|
### CLI options
|
|
50
58
|
|
|
51
59
|
```bash
|
|
52
|
-
eimerreis-linting
|
|
60
|
+
eimerreis-linting init [targetDir] [--force]
|
|
61
|
+
eimerreis-linting lint [targetDir] [--fix]
|
|
62
|
+
eimerreis-linting format [targetDir] [--check]
|
|
53
63
|
```
|
|
54
64
|
|
|
55
|
-
- `
|
|
56
|
-
-
|
|
65
|
+
- `init --force`: overwrite existing `.oxlintrc.json` / `.oxfmtrc.json` and script values
|
|
66
|
+
- `lint --fix`: run `oxlint --fix .` and then `npx react-doctor -y .`
|
|
67
|
+
- `format --check`: run `oxfmt --check .`
|
|
68
|
+
|
|
69
|
+
`react-doctor` runs only when the target package has `react`, `react-dom`, or `next` in dependencies/devDependencies/peerDependencies.
|
|
57
70
|
|
|
58
71
|
## Manual Setup
|
|
59
72
|
|
package/bin/init.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
3
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
5
|
import { fileURLToPath } from "node:url";
|
|
5
6
|
import { dirname, resolve } from "node:path";
|
|
@@ -8,22 +9,6 @@ import process from "node:process";
|
|
|
8
9
|
const packageName = "@eimerreis/linting";
|
|
9
10
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
11
|
|
|
11
|
-
const rawArgs = process.argv.slice(2);
|
|
12
|
-
|
|
13
|
-
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
14
|
-
console.log("Usage: eimerreis-linting [init] [targetDir] [--force]");
|
|
15
|
-
process.exit(0);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const args = rawArgs[0] === "init" ? rawArgs.slice(1) : rawArgs;
|
|
19
|
-
const force = args.includes("--force");
|
|
20
|
-
const targetArg = args.find((arg) => !arg.startsWith("-"));
|
|
21
|
-
const targetDir = resolve(process.cwd(), targetArg ?? ".");
|
|
22
|
-
|
|
23
|
-
const targetPackageJsonPath = resolve(targetDir, "package.json");
|
|
24
|
-
const oxlintPath = resolve(targetDir, ".oxlintrc.json");
|
|
25
|
-
const oxfmtPath = resolve(targetDir, ".oxfmtrc.json");
|
|
26
|
-
|
|
27
12
|
function ensureDirectory(filePath) {
|
|
28
13
|
const dir = dirname(filePath);
|
|
29
14
|
if (!existsSync(dir)) {
|
|
@@ -40,7 +25,94 @@ function readJson(filePath) {
|
|
|
40
25
|
return JSON.parse(readFileSync(filePath, "utf8"));
|
|
41
26
|
}
|
|
42
27
|
|
|
43
|
-
function
|
|
28
|
+
function printUsage() {
|
|
29
|
+
console.log("Usage:");
|
|
30
|
+
console.log(" eimerreis-linting init [targetDir] [--force]");
|
|
31
|
+
console.log(" eimerreis-linting lint [targetDir] [--fix]");
|
|
32
|
+
console.log(" eimerreis-linting format [targetDir] [--check]");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parseCommand(rawArgs) {
|
|
36
|
+
const firstArg = rawArgs[0];
|
|
37
|
+
if (!firstArg || firstArg === "init" || firstArg.startsWith("-")) {
|
|
38
|
+
return {
|
|
39
|
+
command: "init",
|
|
40
|
+
args: firstArg === "init" ? rawArgs.slice(1) : rawArgs,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
command: firstArg,
|
|
46
|
+
args: rawArgs.slice(1),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parsePathAndFlags(args, allowedFlags) {
|
|
51
|
+
let targetDirArg;
|
|
52
|
+
const flags = new Set();
|
|
53
|
+
|
|
54
|
+
for (const arg of args) {
|
|
55
|
+
if (arg.startsWith("-")) {
|
|
56
|
+
if (!allowedFlags.includes(arg)) {
|
|
57
|
+
throw new Error(`Unknown flag: ${arg}`);
|
|
58
|
+
}
|
|
59
|
+
flags.add(arg);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (targetDirArg) {
|
|
64
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
targetDirArg = arg;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
targetDir: resolve(process.cwd(), targetDirArg ?? "."),
|
|
72
|
+
flags,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function runCommand(command, commandArgs, cwd) {
|
|
77
|
+
return new Promise((resolvePromise) => {
|
|
78
|
+
const child = spawn(command, commandArgs, {
|
|
79
|
+
cwd,
|
|
80
|
+
stdio: "inherit",
|
|
81
|
+
shell: process.platform === "win32",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
child.on("error", (error) => {
|
|
85
|
+
console.error(`failed to run ${command}: ${error.message}`);
|
|
86
|
+
resolvePromise(1);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
child.on("close", (code) => {
|
|
90
|
+
resolvePromise(code ?? 1);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function hasReactProject(cwd) {
|
|
96
|
+
const packageJsonPath = resolve(cwd, "package.json");
|
|
97
|
+
|
|
98
|
+
if (!existsSync(packageJsonPath)) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const packageJson = readJson(packageJsonPath);
|
|
104
|
+
const dependencyFields = ["dependencies", "devDependencies", "peerDependencies"];
|
|
105
|
+
|
|
106
|
+
return dependencyFields.some((field) => {
|
|
107
|
+
const deps = packageJson[field];
|
|
108
|
+
return Boolean(deps?.react || deps?.["react-dom"] || deps?.next);
|
|
109
|
+
});
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function upsertScript(packageJson, name, command, force) {
|
|
44
116
|
if (!packageJson.scripts || typeof packageJson.scripts !== "object") {
|
|
45
117
|
packageJson.scripts = {};
|
|
46
118
|
}
|
|
@@ -49,7 +121,7 @@ function upsertScript(packageJson, name, command) {
|
|
|
49
121
|
}
|
|
50
122
|
}
|
|
51
123
|
|
|
52
|
-
function createExtendsConfig(targetPath, exportPath) {
|
|
124
|
+
function createExtendsConfig(targetPath, exportPath, force) {
|
|
53
125
|
if (existsSync(targetPath) && !force) {
|
|
54
126
|
console.log(`skip ${targetPath} (already exists)`);
|
|
55
127
|
return;
|
|
@@ -61,7 +133,7 @@ function createExtendsConfig(targetPath, exportPath) {
|
|
|
61
133
|
console.log(`write ${targetPath}`);
|
|
62
134
|
}
|
|
63
135
|
|
|
64
|
-
function maybeUpdatePackageJson() {
|
|
136
|
+
function maybeUpdatePackageJson(targetPackageJsonPath, force) {
|
|
65
137
|
if (!existsSync(targetPackageJsonPath)) {
|
|
66
138
|
console.log("skip package.json (not found)");
|
|
67
139
|
return;
|
|
@@ -69,16 +141,16 @@ function maybeUpdatePackageJson() {
|
|
|
69
141
|
|
|
70
142
|
const packageJson = readJson(targetPackageJsonPath);
|
|
71
143
|
|
|
72
|
-
upsertScript(packageJson, "lint", "
|
|
73
|
-
upsertScript(packageJson, "lint:fix", "
|
|
74
|
-
upsertScript(packageJson, "format", "
|
|
75
|
-
upsertScript(packageJson, "format:check", "
|
|
144
|
+
upsertScript(packageJson, "lint", "eimerreis-linting lint", force);
|
|
145
|
+
upsertScript(packageJson, "lint:fix", "eimerreis-linting lint --fix", force);
|
|
146
|
+
upsertScript(packageJson, "format", "eimerreis-linting format", force);
|
|
147
|
+
upsertScript(packageJson, "format:check", "eimerreis-linting format --check", force);
|
|
76
148
|
|
|
77
149
|
writeJson(targetPackageJsonPath, packageJson);
|
|
78
150
|
console.log(`update ${targetPackageJsonPath}`);
|
|
79
151
|
}
|
|
80
152
|
|
|
81
|
-
function printNextSteps() {
|
|
153
|
+
function printNextSteps(targetDir) {
|
|
82
154
|
const relativePath = targetDir === process.cwd() ? "." : targetDir;
|
|
83
155
|
console.log("done");
|
|
84
156
|
console.log("next steps:");
|
|
@@ -87,16 +159,98 @@ function printNextSteps() {
|
|
|
87
159
|
console.log("3) npm run lint && npm run format:check");
|
|
88
160
|
}
|
|
89
161
|
|
|
90
|
-
function
|
|
162
|
+
function runInit(args) {
|
|
163
|
+
const { targetDir, flags } = parsePathAndFlags(args, ["--force"]);
|
|
164
|
+
const force = flags.has("--force");
|
|
165
|
+
const targetPackageJsonPath = resolve(targetDir, "package.json");
|
|
166
|
+
const oxlintPath = resolve(targetDir, ".oxlintrc.json");
|
|
167
|
+
const oxfmtPath = resolve(targetDir, ".oxfmtrc.json");
|
|
168
|
+
|
|
91
169
|
if (!existsSync(packageRoot)) {
|
|
92
170
|
console.error("init failed: package root not found");
|
|
93
171
|
process.exit(1);
|
|
94
172
|
}
|
|
95
173
|
|
|
96
|
-
createExtendsConfig(oxlintPath, "oxlint.config.json");
|
|
97
|
-
createExtendsConfig(oxfmtPath, "oxfmt.config.json");
|
|
98
|
-
maybeUpdatePackageJson();
|
|
99
|
-
printNextSteps();
|
|
174
|
+
createExtendsConfig(oxlintPath, "oxlint.config.json", force);
|
|
175
|
+
createExtendsConfig(oxfmtPath, "oxfmt.config.json", force);
|
|
176
|
+
maybeUpdatePackageJson(targetPackageJsonPath, force);
|
|
177
|
+
printNextSteps(targetDir);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function runLint(args) {
|
|
181
|
+
const { targetDir, flags } = parsePathAndFlags(args, ["--fix"]);
|
|
182
|
+
const lintArgs = ["--no-install", "oxlint"];
|
|
183
|
+
|
|
184
|
+
if (flags.has("--fix")) {
|
|
185
|
+
lintArgs.push("--fix");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
lintArgs.push(".");
|
|
189
|
+
|
|
190
|
+
const lintExitCode = await runCommand("npx", lintArgs, targetDir);
|
|
191
|
+
if (lintExitCode !== 0) {
|
|
192
|
+
process.exit(lintExitCode);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (!hasReactProject(targetDir)) {
|
|
196
|
+
console.log("skip react-doctor (no react/next dependency found)");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const doctorExitCode = await runCommand("npx", ["react-doctor", "-y", "."], targetDir);
|
|
201
|
+
if (doctorExitCode !== 0) {
|
|
202
|
+
process.exit(doctorExitCode);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function runFormat(args) {
|
|
207
|
+
const { targetDir, flags } = parsePathAndFlags(args, ["--check"]);
|
|
208
|
+
const formatArgs = ["--no-install", "oxfmt"];
|
|
209
|
+
|
|
210
|
+
if (flags.has("--check")) {
|
|
211
|
+
formatArgs.push("--check");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
formatArgs.push(".");
|
|
215
|
+
|
|
216
|
+
const formatExitCode = await runCommand("npx", formatArgs, targetDir);
|
|
217
|
+
if (formatExitCode !== 0) {
|
|
218
|
+
process.exit(formatExitCode);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function main() {
|
|
223
|
+
try {
|
|
224
|
+
const rawArgs = process.argv.slice(2);
|
|
225
|
+
if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
|
|
226
|
+
printUsage();
|
|
227
|
+
process.exit(0);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const { command, args } = parseCommand(rawArgs);
|
|
231
|
+
|
|
232
|
+
if (command === "init") {
|
|
233
|
+
runInit(args);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (command === "lint") {
|
|
238
|
+
await runLint(args);
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (command === "format") {
|
|
243
|
+
await runFormat(args);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
console.error(`Unknown command: ${command}`);
|
|
248
|
+
printUsage();
|
|
249
|
+
process.exit(1);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
100
254
|
}
|
|
101
255
|
|
|
102
256
|
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eimerreis/linting",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Personal OXC linting and formatting defaults",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"format",
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"typescript"
|
|
15
15
|
],
|
|
16
16
|
"license": "MIT",
|
|
17
|
+
"repository": {
|
|
18
|
+
"url": "https://github.com/eimerreis/linting"
|
|
19
|
+
},
|
|
17
20
|
"bin": {
|
|
18
21
|
"eimerreis-linting": "bin/init.mjs"
|
|
19
22
|
},
|
|
@@ -35,10 +38,10 @@
|
|
|
35
38
|
},
|
|
36
39
|
"scripts": {
|
|
37
40
|
"changeset": "changeset",
|
|
38
|
-
"lint": "
|
|
39
|
-
"lint:fix": "
|
|
40
|
-
"format": "
|
|
41
|
-
"format:check": "
|
|
41
|
+
"lint": "node ./bin/init.mjs lint",
|
|
42
|
+
"lint:fix": "node ./bin/init.mjs lint --fix",
|
|
43
|
+
"format": "node ./bin/init.mjs format",
|
|
44
|
+
"format:check": "node ./bin/init.mjs format --check",
|
|
42
45
|
"version-packages": "changeset version",
|
|
43
46
|
"release": "changeset publish"
|
|
44
47
|
},
|