@useinsider/eslint-config 4.0.0-beta.1 → 4.0.0-beta.3
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 +122 -17
- package/dist/builders/scopeBlocks.d.ts +6 -0
- package/dist/cli.cjs +949 -223
- package/dist/cli.cjs.map +1 -1
- package/dist/main.cjs +395 -84
- package/dist/main.cjs.map +1 -1
- package/dist/main.js +396 -85
- package/dist/main.js.map +1 -1
- package/dist/utils/ensureConfigStructure.d.ts +8 -4
- package/dist/utils/ensureOxlintConfig.d.ts +2 -1
- package/dist/utils/layoutDiagnosis.d.ts +63 -0
- package/dist/utils/offerMigration.d.ts +42 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,8 +27,8 @@ export const { useInsiderESLint, useInsiderOXC } = useInsider({
|
|
|
27
27
|
});
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
```
|
|
31
|
-
// eslint.config.
|
|
30
|
+
```ts
|
|
31
|
+
// eslint.config.ts
|
|
32
32
|
import { useInsiderESLint } from './insider.lint.ts';
|
|
33
33
|
|
|
34
34
|
export default useInsiderESLint();
|
|
@@ -52,6 +52,13 @@ Three constraints this layout is not free to break:
|
|
|
52
52
|
- **The `./insider.lint.ts` import keeps its real extension.** Neither
|
|
53
53
|
ESLint's loader nor oxlint's applies TypeScript's `.js` → `.ts` specifier
|
|
54
54
|
rewriting, so `'./insider.lint.js'` does not resolve.
|
|
55
|
+
- **The ESLint config needs a TypeScript extension too** — `eslint.config.ts`
|
|
56
|
+
or `eslint.config.mts`. ESLint routes those through jiti, which resolves the
|
|
57
|
+
TypeScript lint module on every supported Node version. A `.js`/`.mjs`/`.cjs`
|
|
58
|
+
config is loaded by Node directly, so importing `./insider.lint.ts` from one
|
|
59
|
+
works only where Node's type stripping is unflagged (Node >= 22.18) and dies
|
|
60
|
+
with `ERR_UNKNOWN_FILE_EXTENSION` on Node 20. Both CLI commands therefore
|
|
61
|
+
rename an existing JavaScript flat config and delete the original.
|
|
55
62
|
- **The oxlint config must be `oxlint.config.ts` or `oxlint.config.mts`.**
|
|
56
63
|
oxlint silently ignores `oxlint.config.{cts,js,mjs,cjs}` and falls through
|
|
57
64
|
to the nearest ancestor config. In a package with no `"type": "module"`,
|
|
@@ -59,7 +66,11 @@ Three constraints this layout is not free to break:
|
|
|
59
66
|
- **A TypeScript project needs `allowImportingTsExtensions: true`** in the
|
|
60
67
|
`tsconfig.json` that covers the layout files, or `tsc` rejects the import
|
|
61
68
|
with `TS5097`. Both CLI flows offer to set it for you, following `extends`
|
|
62
|
-
first so a shared base that already carries it is left alone.
|
|
69
|
+
first so a shared base that already carries it is left alone. TypeScript only
|
|
70
|
+
accepts the flag on a project with `noEmit`, `emitDeclarationOnly` or
|
|
71
|
+
`rewriteRelativeImportExtensions` — on an emitting project it fails with
|
|
72
|
+
`TS5096` — so a project that emits is never edited; the CLI explains what to
|
|
73
|
+
turn on instead.
|
|
63
74
|
- **`oxlint` runs with `--disable-nested-config`.** The emitted config carries
|
|
64
75
|
options oxlint accepts only in a root config, and a discovered nested config
|
|
65
76
|
carrying them aborts the whole run. Disabling nested discovery also makes
|
|
@@ -83,7 +94,9 @@ is a sign your scope coverage is incomplete — describe the extra files inside
|
|
|
83
94
|
- [Strict and spell-checker knobs](#strict-and-spell-checker-knobs)
|
|
84
95
|
- [API reference](#api-reference)
|
|
85
96
|
- [Unused disable directives](#unused-disable-directives)
|
|
97
|
+
- [Lint scripts](#lint-scripts)
|
|
86
98
|
- [Migrating to v4](#migrating-to-v4)
|
|
99
|
+
- [The guided migration offer](#the-guided-migration-offer)
|
|
87
100
|
- [Migrating from v1.x](#migrating-from-v1x)
|
|
88
101
|
- [First lint after install](#first-lint-after-install)
|
|
89
102
|
- [Troubleshooting](#troubleshooting)
|
|
@@ -105,14 +118,17 @@ npx @useinsider/eslint-config
|
|
|
105
118
|
The CLI walks an interactive prompt (project type, language, framework, test
|
|
106
119
|
runner, jQuery globals, tsconfig layout), installs every required linter and
|
|
107
120
|
plugin pinned to the versions this package was built against, writes the three
|
|
108
|
-
layout files at the project root,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
121
|
+
layout files at the project root, wires oxlint into the script that actually
|
|
122
|
+
invokes ESLint (see [Lint scripts](#lint-scripts)), and offers to add
|
|
123
|
+
`allowImportingTsExtensions: true` to the tsconfig that really governs those
|
|
124
|
+
files. In a solution-style project that is the referenced `tsconfig.node.json`,
|
|
125
|
+
not the root — and if an applicable config already sets the flag, or no tsconfig
|
|
126
|
+
type-checks those files, you are not asked at all. That edit is surgical —
|
|
127
|
+
comments, key order and indentation survive — and declining it prints the exact
|
|
128
|
+
snippet to paste.
|
|
129
|
+
|
|
130
|
+
To migrate an existing `eslint.config.{js,mjs,cjs}` — v3's scope-keyed call or
|
|
131
|
+
v1's `useInsider({ preset, config })` — or a legacy `.eslintrc.{js,cjs}`:
|
|
116
132
|
|
|
117
133
|
```bash
|
|
118
134
|
npx @useinsider/eslint-config migrate [path]
|
|
@@ -121,11 +137,25 @@ npx @useinsider/eslint-config migrate [path]
|
|
|
121
137
|
The migrate flow auto-detects the source format, prints a summary plus any
|
|
122
138
|
dropped custom rules, asks for confirmation, then splits the config into the
|
|
123
139
|
three-file layout: the `useInsider()` call moves into `insider.lint.*` and
|
|
124
|
-
both linter configs become thin re-exports.
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
140
|
+
both linter configs become thin re-exports.
|
|
141
|
+
|
|
142
|
+
A **v3** config takes the shorter route. v4 accepts the same options v3 did,
|
|
143
|
+
so the call is re-housed rather than re-rendered: the file is copied into
|
|
144
|
+
`insider.lint.*` byte-for-byte and only the export statement changes to
|
|
145
|
+
`export const { useInsiderESLint, useInsiderOXC } = useInsider({ ... })`. Your
|
|
146
|
+
comments, `satisfies` clauses, computed values and formatting survive. An
|
|
147
|
+
`await` on the call and an array wrapping it are dropped, since `useInsider()`
|
|
148
|
+
is synchronous in v4 and returns the two renderers. Shapes with nowhere to go
|
|
149
|
+
in a lint module are refused by name rather than half-migrated: more than one
|
|
150
|
+
`useInsider()` call, a default export mixing the call with hand-written
|
|
151
|
+
flat-config blocks, a `defineConfig()` wrapper, and a call reached through a
|
|
152
|
+
variable. An `.eslintignore` beside a v3 config is left alone — flat config
|
|
153
|
+
never read it, and the re-housed call is not rewritten to fold it in.
|
|
154
|
+
|
|
155
|
+
Legacy `.eslintrc.{js,cjs}` is also handled (custom rules and
|
|
156
|
+
non-`@useinsider/eslint-config/*` extends are dropped with a loud warning so
|
|
157
|
+
you can review what changed). Legacy `.eslintrc.{json,yml,yaml}` are not
|
|
158
|
+
supported in this release — convert them to `.eslintrc.{js,cjs}` first.
|
|
129
159
|
|
|
130
160
|
Migrate also rewrites your source: an `// eslint-disable-next-line <rule>`
|
|
131
161
|
naming a rule oxlint has taken over becomes `// oxlint-disable-next-line
|
|
@@ -432,8 +462,51 @@ silenceRules(['no-magic-numbers']);
|
|
|
432
462
|
export const { useInsiderESLint, useInsiderOXC } = useInsider({ /* ... */ });
|
|
433
463
|
```
|
|
434
464
|
|
|
465
|
+
## Lint scripts
|
|
466
|
+
|
|
467
|
+
Both linters have to run, and oxlint has to run *first* — the ESLint side
|
|
468
|
+
turns off every rule oxlint owns, so ESLint alone silently drops those checks.
|
|
469
|
+
The canonical script is:
|
|
470
|
+
|
|
471
|
+
```json
|
|
472
|
+
{ "scripts": { "lint": "oxlint --disable-nested-config && eslint ." } }
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
The CLI never replaces a script it did not author. It finds the **one** script
|
|
476
|
+
that really invokes the `eslint` binary and **prepends** oxlint to it, leaving
|
|
477
|
+
the original command verbatim inside the new value. A composed `lint` that fans
|
|
478
|
+
out to a type-check and a stylelint pass is therefore untouched:
|
|
479
|
+
|
|
480
|
+
```diff
|
|
481
|
+
"lint": "run-p lint:type lint:es lint:style",
|
|
482
|
+
"lint:es": "npm run lint:es-ci -- --cache",
|
|
483
|
+
- "lint:es-ci": "NODE_OPTIONS=--max-old-space-size=6144 eslint .",
|
|
484
|
+
+ "lint:es-ci": "oxlint --disable-nested-config && NODE_OPTIONS=--max-old-space-size=6144 eslint .",
|
|
485
|
+
"lint:style": "npm run lint:style-ci -- --cache",
|
|
486
|
+
"lint:type": "run-p lint:type:app lint:type:test lint:type:node",
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
Prepending rather than appending is deliberate: a `--` passthrough
|
|
490
|
+
(`npm run lint:es-ci -- --cache`) lands on the *last* command, so `--cache`
|
|
491
|
+
still reaches ESLint. A script whose name ends in a `--fix` invocation gets
|
|
492
|
+
`oxlint --disable-nested-config --fix`. Every rewrite is printed with its
|
|
493
|
+
previous value.
|
|
494
|
+
|
|
495
|
+
Exactly one script is rewritten. When several entry points run ESLint —
|
|
496
|
+
`lint`, `lint:fix`, `lint:ci` — prepending to all of them would run oxlint once
|
|
497
|
+
per entry point over the same tree, so the CLI picks the entry point `lint`
|
|
498
|
+
designates (itself, or the leaf it delegates to) and reports the rest with the
|
|
499
|
+
prefix to add if you want oxlint on them too.
|
|
500
|
+
|
|
501
|
+
If no script invokes `eslint` and a `lint` script already exists, nothing is
|
|
502
|
+
written — the conflict is reported with the command to add by hand.
|
|
503
|
+
|
|
435
504
|
## Migrating to v4
|
|
436
505
|
|
|
506
|
+
v4 requires **Node.js 24.19+** (`engines.node` was `>=20` through v3). Check
|
|
507
|
+
your CI images and any repo pinning an older Node before upgrading: npm and
|
|
508
|
+
pnpm warn with `EBADENGINE`, and fail outright under `engine-strict`.
|
|
509
|
+
|
|
437
510
|
v4 splits the lint run across ESLint and oxlint. Run the CLI to do all of
|
|
438
511
|
this automatically:
|
|
439
512
|
|
|
@@ -441,6 +514,37 @@ this automatically:
|
|
|
441
514
|
npx @useinsider/eslint-config migrate
|
|
442
515
|
```
|
|
443
516
|
|
|
517
|
+
### The guided migration offer
|
|
518
|
+
|
|
519
|
+
You do not have to know that command exists. Running lint against a v3 config
|
|
520
|
+
under v4 detects the outdated structure and, in an interactive terminal, offers
|
|
521
|
+
to run the migration there and then:
|
|
522
|
+
|
|
523
|
+
```
|
|
524
|
+
@useinsider/eslint-config: eslint.config.js still uses the v3 single-file layout.
|
|
525
|
+
/repo/eslint.config.js calls `useInsider()` itself, and
|
|
526
|
+
insider.lint.ts and oxlint.config.ts are missing.
|
|
527
|
+
|
|
528
|
+
Starting the guided migration. It shows every change before applying it.
|
|
529
|
+
Skip it with INSIDER_NO_PROMPT=true.
|
|
530
|
+
|
|
531
|
+
◆ eslint.config.js still uses the v3 single-file layout. Migrate it now?
|
|
532
|
+
│ ● Yes / ○ No
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
Accepting runs the normal `migrate` flow — same summary, same confirmations —
|
|
536
|
+
and then exits non-zero asking you to re-run lint, because this process already
|
|
537
|
+
loaded the configuration that was just rewritten.
|
|
538
|
+
|
|
539
|
+
The offer is suppressed, and the failure message leads with the `migrate`
|
|
540
|
+
command instead, whenever prompting would be wrong: under oxlint (whose stdout
|
|
541
|
+
is its report), in CI, without a TTY, with `INSIDER_NO_PROMPT=true`, or with
|
|
542
|
+
`INSIDER_SKIP_CONFIG_VALIDATION=true`.
|
|
543
|
+
|
|
544
|
+
The directive rewrite is reported as its own step, with the file count shown
|
|
545
|
+
*before* anything is written, so you can take it as a separate commit or
|
|
546
|
+
decline it and run `migrate` again later.
|
|
547
|
+
|
|
444
548
|
What changes:
|
|
445
549
|
|
|
446
550
|
- **`eslint-disable` no longer suppresses an oxlint-owned rule. This is the
|
|
@@ -509,7 +613,8 @@ What changes:
|
|
|
509
613
|
nothing here applies TypeScript's `.js` → `.ts` rewriting — and `tsc` fails
|
|
510
614
|
with `TS5097` otherwise. The CLI prompts to add it and resolves the full
|
|
511
615
|
`extends` chain first, so a shared base that already sets it is left alone;
|
|
512
|
-
declining prints the snippet to paste.
|
|
616
|
+
declining prints the snippet to paste. A project that emits is never edited —
|
|
617
|
+
TypeScript rejects the flag there with `TS5096`.
|
|
513
618
|
- **`@typescript-eslint` type-aware rules, `sonarjs`, `@stylistic`, `@cspell`,
|
|
514
619
|
`jsdoc/check-tag-names` and `no-inner-declarations` stay with ESLint.** Two
|
|
515
620
|
of those are permanent, for different reasons:
|
|
@@ -44,5 +44,11 @@ declare const scopeMap: {
|
|
|
44
44
|
};
|
|
45
45
|
export type ScopeName = keyof typeof scopeMap;
|
|
46
46
|
export declare function isScopeName(key: string): key is ScopeName;
|
|
47
|
+
/**
|
|
48
|
+
* Every scope name `useInsider()` accepts, for error messages that have to spell the options out.
|
|
49
|
+
* @example
|
|
50
|
+
* scopeNames().includes('typescriptNode'); // true
|
|
51
|
+
*/
|
|
52
|
+
export declare function scopeNames(): ScopeName[];
|
|
47
53
|
export declare function buildScopeBlocks(scope: ScopeName, entries: readonly ScopeEntry[]): Promise<Linter.Config[]>;
|
|
48
54
|
export {};
|