@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.
Files changed (3) hide show
  1. package/README.md +26 -3
  2. package/bin/init.mjs +168 -23
  3. 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 { dirname, resolve } from "node:path";
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, flags } = parsePathAndFlags(args, ["--fix"]);
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 (flags.has("--fix")) {
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
- const lintExitCode = await runCommand(process.execPath, lintArgs, targetDir);
322
- if (lintExitCode !== 0) {
323
- process.exit(lintExitCode);
324
- }
452
+ try {
453
+ const lintExitCode = await runCommand(process.execPath, lintArgs, targetDir);
454
+ if (lintExitCode !== 0) {
455
+ process.exit(lintExitCode);
456
+ }
325
457
 
326
- if (!hasReactProject(targetDir)) {
327
- console.log("skip react-doctor (no react/next dependency found)");
328
- return;
329
- }
458
+ if (!hasReactProject(targetDir)) {
459
+ console.log("skip react-doctor (no react/next dependency found)");
460
+ return;
461
+ }
330
462
 
331
- const reactDoctorEntryPath = resolvePackageEntry("react-doctor");
332
- const doctorExitCode = await runCommand(process.execPath, [reactDoctorEntryPath, "-y", "."], targetDir);
333
- if (doctorExitCode !== 0) {
334
- process.exit(doctorExitCode);
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, flags } = parsePathAndFlags(args, ["--check"]);
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 (flags.has("--check")) {
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
- const formatExitCode = await runCommand(process.execPath, formatArgs, targetDir);
358
- if (formatExitCode !== 0) {
359
- process.exit(formatExitCode);
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eimerreis/linting",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Personal OXC linting and formatting defaults",
5
5
  "keywords": [
6
6
  "format",