@eimerreis/linting 0.3.0 → 0.4.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 +26 -3
- package/bin/init.mjs +168 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,16 +60,37 @@ And updates `package.json` scripts (if present) so linting is executed through t
|
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
62
|
eimerreis-linting init [targetDir] [--force]
|
|
63
|
-
eimerreis-linting lint [targetDir] [--fix]
|
|
64
|
-
eimerreis-linting format [targetDir] [--check]
|
|
63
|
+
eimerreis-linting lint [targetDir] [--fix] [--ignore-path <path>] [--ignore-pattern <pattern>]
|
|
64
|
+
eimerreis-linting format [targetDir] [--check] [--ignore-path <path>] [--ignore-pattern <pattern>]
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
- `init --force`: overwrite existing `.oxlintrc.json` / `.oxfmtrc.json` and script values
|
|
68
68
|
- `lint --fix`: run `oxlint --fix .` and then run react-doctor
|
|
69
69
|
- `format --check`: run `oxfmt --check .`
|
|
70
|
+
- `--ignore-path`: add one ignore file (repeatable) for lint/format
|
|
71
|
+
- `--ignore-pattern`: add one glob pattern (repeatable) for lint/format
|
|
70
72
|
|
|
71
73
|
`react-doctor` runs only when the target package has `react`, `react-dom`, or `next` in dependencies/devDependencies/peerDependencies.
|
|
72
74
|
|
|
75
|
+
### Ignoring files
|
|
76
|
+
|
|
77
|
+
Supported options:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
eimerreis-linting lint --ignore-path .gitignore
|
|
81
|
+
eimerreis-linting format --check --ignore-path .gitignore --ignore-path .prettierignore
|
|
82
|
+
eimerreis-linting lint --ignore-pattern "dist/**" --ignore-pattern "coverage/**"
|
|
83
|
+
eimerreis-linting format --check --ignore-path .gitignore --ignore-pattern "**/*.generated.ts"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
You can also add a dedicated ignore file at project root:
|
|
87
|
+
|
|
88
|
+
```text
|
|
89
|
+
.eimerreis-lintingignore
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
If present, it is picked up automatically by both lint and format.
|
|
93
|
+
|
|
73
94
|
### First-time one-shot usage
|
|
74
95
|
|
|
75
96
|
You do not need to run `init` first.
|
|
@@ -114,7 +135,9 @@ Generated project scripts:
|
|
|
114
135
|
"lint": "eimerreis-linting lint",
|
|
115
136
|
"lint:fix": "eimerreis-linting lint --fix",
|
|
116
137
|
"format": "eimerreis-linting format",
|
|
117
|
-
"format:check": "eimerreis-linting format --check"
|
|
138
|
+
"format:check": "eimerreis-linting format --check",
|
|
139
|
+
"lint:ignore": "eimerreis-linting lint --ignore-path .eimerreis-lintingignore",
|
|
140
|
+
"format:check:ignore": "eimerreis-linting format --check --ignore-path .eimerreis-lintingignore"
|
|
118
141
|
}
|
|
119
142
|
}
|
|
120
143
|
```
|
package/bin/init.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
|
-
import {
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
8
|
import process from "node:process";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
9
10
|
|
|
@@ -140,8 +141,8 @@ function resolveFormatConfigPath(targetDir) {
|
|
|
140
141
|
function printUsage() {
|
|
141
142
|
console.log("Usage:");
|
|
142
143
|
console.log(" eimerreis-linting init [targetDir] [--force]");
|
|
143
|
-
console.log(" eimerreis-linting lint [targetDir] [--fix]");
|
|
144
|
-
console.log(" eimerreis-linting format [targetDir] [--check]");
|
|
144
|
+
console.log(" eimerreis-linting lint [targetDir] [--fix] [--ignore-path <path>] [--ignore-pattern <pattern>]");
|
|
145
|
+
console.log(" eimerreis-linting format [targetDir] [--check] [--ignore-path <path>] [--ignore-pattern <pattern>]");
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
function parseCommand(rawArgs) {
|
|
@@ -185,6 +186,77 @@ function parsePathAndFlags(args, allowedFlags) {
|
|
|
185
186
|
};
|
|
186
187
|
}
|
|
187
188
|
|
|
189
|
+
function parseRunArgs(args, primaryFlag) {
|
|
190
|
+
let targetDirArg;
|
|
191
|
+
const ignorePaths = [];
|
|
192
|
+
const ignorePatterns = [];
|
|
193
|
+
let primaryEnabled = false;
|
|
194
|
+
|
|
195
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
196
|
+
const arg = args[index];
|
|
197
|
+
|
|
198
|
+
if (arg === primaryFlag) {
|
|
199
|
+
primaryEnabled = true;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (arg === "--ignore-path") {
|
|
204
|
+
const ignorePath = args[index + 1];
|
|
205
|
+
if (!ignorePath || ignorePath.startsWith("-")) {
|
|
206
|
+
throw new Error("Missing value for --ignore-path");
|
|
207
|
+
}
|
|
208
|
+
ignorePaths.push(ignorePath);
|
|
209
|
+
index += 1;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (arg.startsWith("--ignore-path=")) {
|
|
214
|
+
const ignorePath = arg.slice("--ignore-path=".length);
|
|
215
|
+
if (!ignorePath) {
|
|
216
|
+
throw new Error("Missing value for --ignore-path");
|
|
217
|
+
}
|
|
218
|
+
ignorePaths.push(ignorePath);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (arg === "--ignore-pattern") {
|
|
223
|
+
const ignorePattern = args[index + 1];
|
|
224
|
+
if (!ignorePattern || ignorePattern.startsWith("-")) {
|
|
225
|
+
throw new Error("Missing value for --ignore-pattern");
|
|
226
|
+
}
|
|
227
|
+
ignorePatterns.push(ignorePattern);
|
|
228
|
+
index += 1;
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (arg.startsWith("--ignore-pattern=")) {
|
|
233
|
+
const ignorePattern = arg.slice("--ignore-pattern=".length);
|
|
234
|
+
if (!ignorePattern) {
|
|
235
|
+
throw new Error("Missing value for --ignore-pattern");
|
|
236
|
+
}
|
|
237
|
+
ignorePatterns.push(ignorePattern);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (arg.startsWith("-")) {
|
|
242
|
+
throw new Error(`Unknown flag: ${arg}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (targetDirArg) {
|
|
246
|
+
throw new Error(`Unexpected argument: ${arg}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
targetDirArg = arg;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
targetDir: resolve(process.cwd(), targetDirArg ?? "."),
|
|
254
|
+
primaryEnabled,
|
|
255
|
+
ignorePaths,
|
|
256
|
+
ignorePatterns,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
188
260
|
function runCommand(command, commandArgs, cwd) {
|
|
189
261
|
return new Promise((resolvePromise) => {
|
|
190
262
|
const child = spawn(command, commandArgs, {
|
|
@@ -266,6 +338,13 @@ function maybeUpdatePackageJson(targetPackageJsonPath, force) {
|
|
|
266
338
|
upsertScript(packageJson, "lint:fix", "eimerreis-linting lint --fix", force);
|
|
267
339
|
upsertScript(packageJson, "format", "eimerreis-linting format", force);
|
|
268
340
|
upsertScript(packageJson, "format:check", "eimerreis-linting format --check", force);
|
|
341
|
+
upsertScript(packageJson, "lint:ignore", "eimerreis-linting lint --ignore-path .eimerreis-lintingignore", force);
|
|
342
|
+
upsertScript(
|
|
343
|
+
packageJson,
|
|
344
|
+
"format:check:ignore",
|
|
345
|
+
"eimerreis-linting format --check --ignore-path .eimerreis-lintingignore",
|
|
346
|
+
force
|
|
347
|
+
);
|
|
269
348
|
|
|
270
349
|
writeJson(targetPackageJsonPath, packageJson);
|
|
271
350
|
console.log(`update ${targetPackageJsonPath}`);
|
|
@@ -280,6 +359,52 @@ function printNextSteps(targetDir) {
|
|
|
280
359
|
console.log("3) npm run lint && npm run format:check");
|
|
281
360
|
}
|
|
282
361
|
|
|
362
|
+
function resolveIgnorePaths(targetDir, rawIgnorePaths) {
|
|
363
|
+
const collectedPaths = [];
|
|
364
|
+
const defaultIgnorePath = resolve(targetDir, ".eimerreis-lintingignore");
|
|
365
|
+
|
|
366
|
+
if (existsSync(defaultIgnorePath)) {
|
|
367
|
+
collectedPaths.push(defaultIgnorePath);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
for (const rawIgnorePath of rawIgnorePaths) {
|
|
371
|
+
const resolvedPath = resolve(targetDir, rawIgnorePath);
|
|
372
|
+
if (!existsSync(resolvedPath)) {
|
|
373
|
+
throw new Error(`Ignore file not found: ${resolvedPath}`);
|
|
374
|
+
}
|
|
375
|
+
collectedPaths.push(resolvedPath);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return [...new Set(collectedPaths)];
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function createMergedIgnoreFile(ignorePaths, ignorePatterns) {
|
|
382
|
+
if (ignorePaths.length === 0 && ignorePatterns.length === 0) {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const fileEntries = ignorePaths
|
|
387
|
+
.map((ignorePath) => readFileSync(ignorePath, "utf8").trim())
|
|
388
|
+
.filter((entry) => entry.length > 0);
|
|
389
|
+
|
|
390
|
+
const patternEntries = ignorePatterns.map((pattern) => pattern.trim()).filter((pattern) => pattern.length > 0);
|
|
391
|
+
|
|
392
|
+
const mergedEntries = [...fileEntries, ...patternEntries];
|
|
393
|
+
|
|
394
|
+
if (mergedEntries.length === 0) {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const tempDir = mkdtempSync(join(tmpdir(), "eimerreis-linting-"));
|
|
399
|
+
const mergedIgnorePath = resolve(tempDir, ".ignore");
|
|
400
|
+
writeFileSync(mergedIgnorePath, `${mergedEntries.join("\n")}\n`, "utf8");
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
path: mergedIgnorePath,
|
|
404
|
+
cleanup: () => rmSync(tempDir, { recursive: true, force: true }),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
283
408
|
async function runInit(args) {
|
|
284
409
|
const { targetDir, flags } = parsePathAndFlags(args, ["--force"]);
|
|
285
410
|
const force = flags.has("--force");
|
|
@@ -305,33 +430,43 @@ async function runLint(args) {
|
|
|
305
430
|
process.exit(1);
|
|
306
431
|
}
|
|
307
432
|
|
|
308
|
-
const { targetDir,
|
|
433
|
+
const { targetDir, primaryEnabled, ignorePaths: rawIgnorePaths, ignorePatterns } = parseRunArgs(args, "--fix");
|
|
434
|
+
const ignorePaths = resolveIgnorePaths(targetDir, rawIgnorePaths);
|
|
435
|
+
const mergedIgnoreFile = createMergedIgnoreFile(ignorePaths, ignorePatterns);
|
|
309
436
|
const oxlintBinPath = resolvePackageBin("oxlint", "bin/oxlint");
|
|
310
437
|
const lintArgs = [oxlintBinPath];
|
|
311
438
|
const lintConfigPath = resolveLintConfigPath(targetDir);
|
|
312
439
|
|
|
313
440
|
lintArgs.push("-c", lintConfigPath);
|
|
314
441
|
|
|
315
|
-
if (
|
|
442
|
+
if (mergedIgnoreFile) {
|
|
443
|
+
lintArgs.push(`--ignore-path=${mergedIgnoreFile.path}`);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
if (primaryEnabled) {
|
|
316
447
|
lintArgs.push("--fix");
|
|
317
448
|
}
|
|
318
449
|
|
|
319
450
|
lintArgs.push(".");
|
|
320
451
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
452
|
+
try {
|
|
453
|
+
const lintExitCode = await runCommand(process.execPath, lintArgs, targetDir);
|
|
454
|
+
if (lintExitCode !== 0) {
|
|
455
|
+
process.exit(lintExitCode);
|
|
456
|
+
}
|
|
325
457
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
458
|
+
if (!hasReactProject(targetDir)) {
|
|
459
|
+
console.log("skip react-doctor (no react/next dependency found)");
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
330
462
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
463
|
+
const reactDoctorEntryPath = resolvePackageEntry("react-doctor");
|
|
464
|
+
const doctorExitCode = await runCommand(process.execPath, [reactDoctorEntryPath, "-y", "."], targetDir);
|
|
465
|
+
if (doctorExitCode !== 0) {
|
|
466
|
+
process.exit(doctorExitCode);
|
|
467
|
+
}
|
|
468
|
+
} finally {
|
|
469
|
+
mergedIgnoreFile?.cleanup();
|
|
335
470
|
}
|
|
336
471
|
}
|
|
337
472
|
|
|
@@ -341,22 +476,32 @@ async function runFormat(args) {
|
|
|
341
476
|
process.exit(1);
|
|
342
477
|
}
|
|
343
478
|
|
|
344
|
-
const { targetDir,
|
|
479
|
+
const { targetDir, primaryEnabled, ignorePaths: rawIgnorePaths, ignorePatterns } = parseRunArgs(args, "--check");
|
|
480
|
+
const ignorePaths = resolveIgnorePaths(targetDir, rawIgnorePaths);
|
|
481
|
+
const mergedIgnoreFile = createMergedIgnoreFile(ignorePaths, ignorePatterns);
|
|
345
482
|
const oxfmtBinPath = resolvePackageBin("oxfmt", "bin/oxfmt");
|
|
346
483
|
const formatArgs = [oxfmtBinPath];
|
|
347
484
|
const formatConfigPath = resolveFormatConfigPath(targetDir);
|
|
348
485
|
|
|
349
486
|
formatArgs.push("-c", formatConfigPath);
|
|
350
487
|
|
|
351
|
-
if (
|
|
488
|
+
if (mergedIgnoreFile) {
|
|
489
|
+
formatArgs.push(`--ignore-path=${mergedIgnoreFile.path}`);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (primaryEnabled) {
|
|
352
493
|
formatArgs.push("--check");
|
|
353
494
|
}
|
|
354
495
|
|
|
355
496
|
formatArgs.push(".");
|
|
356
497
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
498
|
+
try {
|
|
499
|
+
const formatExitCode = await runCommand(process.execPath, formatArgs, targetDir);
|
|
500
|
+
if (formatExitCode !== 0) {
|
|
501
|
+
process.exit(formatExitCode);
|
|
502
|
+
}
|
|
503
|
+
} finally {
|
|
504
|
+
mergedIgnoreFile?.cleanup();
|
|
360
505
|
}
|
|
361
506
|
}
|
|
362
507
|
|