@useinsider/eslint-config

A collection of ESLint configurations provided by @useinsider, offering support for various environments including Vanilla JS, TypeScript, and framework-specific setups such as Vue 3.
19,782
Install
npm install @useinsider/eslint-config
Latest Version:4.1.0
Vue:3
License:MIT
Last Updated:Oct 12, 2026
Links: npm
Maintainer: useinsider-dev

@useinsider/eslint-config

Shared lint configuration for Insider projects, split across two linters. useInsider() takes a single scope-keyed config object and returns two renderers: useInsiderESLint() for ESLint 9 flat config and useInsiderOXC() for oxlint. Every rule oxlint implements natively is turned off on the ESLint side and enabled on the oxlint side from the same description, so the two configs cannot drift. There is no opaque preset list, no freeform config escape hatch, and no expectation that you spread either result into your own array.

Because ESLint alone would now silently skip every rule oxlint owns, both linters must run. useInsider() refuses to start if the oxlint config is missing.

The three-file layout

useInsider() is called from exactly one module, and each linter's config is a thin re-export of its renderer:

// insider.lint.ts
import { useInsider } from '@useinsider/eslint-config';

export const { useInsiderESLint, useInsiderOXC } = useInsider({
    // scopes go here
});
// eslint.config.ts
import { useInsiderESLint } from './insider.lint.ts';

export default useInsiderESLint();
// oxlint.config.ts
import { useInsiderOXC } from './insider.lint.ts';

export default await useInsiderOXC();

Then run both:

oxlint --disable-nested-config && eslint .

Three constraints this layout is not free to break:

  • The ./insider.lint.ts import keeps its real extension. Neither ESLint's loader nor oxlint's applies TypeScript's .js → .ts specifier rewriting, so './insider.lint.js' does not resolve.
  • The ESLint config needs a TypeScript extension too — eslint.config.ts or eslint.config.mts. ESLint routes those through jiti, which resolves the TypeScript lint module on every supported Node version. A .js/.mjs/.cjs config is loaded by Node directly, so importing ./insider.lint.ts from one works only where Node's type stripping is unflagged (Node >= 22.18) and dies with ERR_UNKNOWN_FILE_EXTENSION on Node 20. Both CLI commands therefore rename an existing JavaScript flat config and delete the original.
  • The oxlint config must be oxlint.config.ts or oxlint.config.mts. oxlint silently ignores oxlint.config.{cts,js,mjs,cjs} and falls through to the nearest ancestor config. In a package with no "type": "module", use .mts for both generated files to avoid Node's module-type warning.
  • A TypeScript project needs allowImportingTsExtensions: true in the tsconfig.json that covers the layout files, or tsc rejects the import with TS5097. Both CLI flows offer to set it for you, following extends first so a shared base that already carries it is left alone. TypeScript only accepts the flag on a project with noEmit, emitDeclarationOnly or rewriteRelativeImportExtensions — on an emitting project it fails with TS5096 — so a project that emits is never edited; the CLI explains what to turn on instead.
  • oxlint runs with --disable-nested-config. The emitted config carries options oxlint accepts only in a root config, and a discovered nested config carrying them aborts the whole run. Disabling nested discovery also makes the config's own ignorePatterns authoritative. In a monorepo each package runs its own lint script, so every config is the root of its own run.

Do not await useInsiderESLint(). Do not spread either renderer. Do not wrap them in an array. If you find yourself wanting to append a block, that is a sign your scope coverage is incomplete — describe the extra files inside useInsider({...}) instead.

Table of Contents

Prerequisites

  • Node.js 24.19+
  • ESLint 9+
  • oxlint 1.87+
  • pnpm, npm, or yarn

Quick start

npx @useinsider/eslint-config

The CLI walks an interactive prompt (project type, language, framework, test runner, jQuery globals, tsconfig layout), installs every required linter and plugin pinned to the versions this package was built against, writes the three layout files at the project root, wires oxlint into the script that actually invokes ESLint (see Lint scripts), and offers to add allowImportingTsExtensions: true to the tsconfig that really governs those files. In a solution-style project that is the referenced tsconfig.node.json, not the root — and if an applicable config already sets the flag, or no tsconfig type-checks those files, you are not asked at all. That edit is surgical — comments, key order and indentation survive — and declining it prints the exact snippet to paste.

To migrate an existing eslint.config.{js,mjs,cjs} — v3's scope-keyed call or v1's useInsider({ preset, config }) — or a legacy .eslintrc.{js,cjs}:

npx @useinsider/eslint-config migrate [path]

The migrate flow auto-detects the source format, prints a summary plus any dropped custom rules, asks for confirmation, then splits the config into the three-file layout: the useInsider() call moves into insider.lint.* and both linter configs become thin re-exports.

A v3 config takes the shorter route. v4 accepts the same options v3 did, so the call is re-housed rather than re-rendered: the file is copied into insider.lint.* byte-for-byte and only the export statement changes to export const { useInsiderESLint, useInsiderOXC } = useInsider({ ... }). Your comments, satisfies clauses, computed values and formatting survive. An await on the call and an array wrapping it are dropped, since useInsider() is synchronous in v4 and returns the two renderers. Shapes with nowhere to go in a lint module are refused by name rather than half-migrated: more than one useInsider() call, a default export mixing the call with hand-written flat-config blocks, a defineConfig() wrapper, and a call reached through a variable. An .eslintignore beside a v3 config is left alone — flat config never read it, and the re-housed call is not rewritten to fold it in.

Legacy .eslintrc.{js,cjs} is also handled (custom rules and non-@useinsider/eslint-config/* extends are dropped with a loud warning so you can review what changed). Legacy .eslintrc.{json,yml,yaml} are not supported in this release — convert them to .eslintrc.{js,cjs} first.

Migrate also rewrites your source: an // eslint-disable-next-line <rule> naming a rule oxlint has taken over becomes // oxlint-disable-next-line <rule>. This is not cosmetic — the emitted oxlint config refuses the eslint-disable spelling (see Unused disable directives), so an un-migrated comment suppresses nothing on either side. A comment listing rules from both sides is split into one comment per linter. A -line or -next-line directive targets one exact physical line, so both halves stay on a single line as block comments — a newline between them would point the first half at the second instead of at the code. Only a whole-file eslint-disable is split across two lines. The rewrite reads each file's syntax tree and touches only real comment nodes, so a directive spelled out inside a string or template literal is left alone.

Example insider.lint.ts for a Node + TypeScript project:

import { useInsider } from '@useinsider/eslint-config';

export const { useInsiderESLint, useInsiderOXC } = useInsider({
    typescriptNode: [
        {
            files: ['**/*.{ts,tsx}'],
            tsconfigPaths: ['tsconfig.json'],
        },
    ],
});

What the CLI installs for that answer set (versions reflect the publish-time pins):

  • @useinsider/eslint-config
  • eslint
  • oxlint
  • globals
  • typescript
  • typescript-eslint

Vue, Jest, or Vitest answers add the matching plugin set (e.g. eslint-plugin-vue, vue-eslint-parser, eslint-plugin-vue-scoped-css, eslint-plugin-jest, or @vitest/eslint-plugin).

Manual setup

Install the package first:

pnpm add -E -D @useinsider/eslint-config oxlint

Then create the three layout files and call useInsider(...) from insider.lint.ts with the scopes your project needs. Each scope holds an array of { files, ... } entries; each entry becomes its own block in both emitted configs.

Every example below shows the body of insider.lint.ts; the two linter configs never change.

Node + TypeScript

import { useInsider } from '@useinsider/eslint-config';

export const { useInsiderESLint, useInsiderOXC } = useInsider({
    typescriptNode: [
        {
            files: ['**/*.{ts,tsx}'],
            tsconfigPaths: ['tsconfig.json'],
        },
    ],
    vitest: [
        { files: ['**/*.{test,spec}.{ts,tsx}'] },
    ],
});

Browser + Vue 3 + TypeScript with split tsconfigs

A project using tsconfig.app.json for source and tsconfig.test.json for tests, with RouterLink and RouterView as globally-registered components.

import { useInsider } from '@useinsider/eslint-config';

export const { useInsiderESLint, useInsiderOXC } = useInsider({
    vue3Typescript: [
        {
            files: ['src/**/*.{ts,tsx,vue}'],
            tsconfigPaths: ['tsconfig.app.json'],
            globalComponents: ['RouterLink', 'RouterView'],
        },
    ],
    vitest: [
        {
            files: ['**/*.{test,spec}.{ts,tsx}'],
        },
    ],
    typescriptNode: [
        {
            files: ['*.config.{ts,mts,cts}'],
            tsconfigPaths: ['tsconfig.node.json'],
        },
    ],
});

Multiple tsconfigPaths entries are supported per block — pass an array of paths if a single scope needs more than one project reference.

Vue 2 + JavaScript with jQuery globals

import { useInsider } from '@useinsider/eslint-config';

export const { useInsiderESLint, useInsiderOXC } = useInsider({
    vue2: [
        { files: ['src/**/*.{js,vue}'] },
    ],
    javascriptNode: [
        { files: ['*.{js,cjs,mjs}'] },
    ],
    jquery: [
        { files: ['src/**/*.{js,vue}'] },
    ],
});

jquery is a top-level scope. Pass an array of { files } entries describing the globs that should see the jQuery globals ($, jQuery). The block is emitted after the scope blocks and before strict/spellChecker, so the matching language scope still applies its rule set and jQuery globals are layered on top of it.

Strict and spell-checker knobs

strict and spellChecker are flat cross-cutting options. They sit at the top level of the config, not inside a scope entry.

import { useInsider } from '@useinsider/eslint-config';

export const { useInsiderESLint, useInsiderOXC } = useInsider({
    typescriptNode: [
        {
            files: ['**/*.{ts,tsx}'],
            tsconfigPaths: ['tsconfig.json'],
        },
    ],
    strict: ['src/**/*.ts'],
    spellChecker: {
        ignoredPaths: ['vendor/**', 'dist/**'],
        customWordListFile: './cspell.custom.json',
    },
    silenceRules: ['no-console'],
});
  • strict: true applies strict rules to every file.
  • strict: string[] applies strict rules only to the matching paths.
  • spellChecker.customWordListFile re-points @cspell/spellchecker to a project-local word list.
  • spellChecker.ignoredPaths turns the spell-checker off for the matching globs (the off block is emitted after the custom-list block, so ignore wins on overlap).
  • silenceRules rewrites the severity of the listed rule IDs to 'warn' across every emitted block while preserving each rule's options.

API reference

useInsider(config) accepts the following top-level properties. Each scope holds an array of entries; each entry produces its own flat-config block.

Every scope entry shape also accepts two common fields:

  • ignores?: string[] — applies as the block's ignores key, scoping the rule set away from the listed globs.
  • globals?: Record<string, GlobalConf> — merged on top of the globals the preset already provides; pass 'readonly', 'writable', or 'off' per ESLint's Linter.Globals. Use this to declare project-specific globals like APP_URL, axios, atatus.
Property Entry shape Notes
ignores string[] (top-level) Emits a leading { ignores } block applied to every other block.
javascript { files: string[]; ignores?: string[]; globals?: Linter.Globals } Environment-agnostic JS (workers, isomorphic).
javascriptDom { files: string[]; ignores?: string[]; globals?: Linter.Globals } Browser JS.
javascriptNode { files: string[]; ignores?: string[]; globals?: Linter.Globals } Node JS.
typescript { files: string[]; tsconfigPaths: string[]; ignores?: string[]; globals?: Linter.Globals } Environment-agnostic TS.
typescriptDom { files: string[]; tsconfigPaths: string[]; ignores?: string[]; globals?: Linter.Globals } Browser TS.
typescriptNode { files: string[]; tsconfigPaths: string[]; ignores?: string[]; globals?: Linter.Globals } Node TS.
vue2 { files: string[]; globalComponents?: string[]; ignores?: string[]; globals?: Linter.Globals } Vue 2 single-file components in JS.
vue3 { files: string[]; globalComponents?: string[]; ignores?: string[]; globals?: Linter.Globals } Vue 3 single-file components in JS.
vue2Typescript { files: string[]; globalComponents?: string[]; tsconfigPaths: string[]; ignores?: string[]; globals?: Linter.Globals } Vue 2 + TS.
vue3Typescript { files: string[]; globalComponents?: string[]; tsconfigPaths: string[]; ignores?: string[]; globals?: Linter.Globals } Vue 3 + TS.
jest { files: string[]; ignores?: string[]; globals?: Linter.Globals } Jest test files.
vitest { files: string[]; ignores?: string[]; globals?: Linter.Globals } Vitest test files.
jsdocBasedTs { files: string[]; tsconfigPaths: string[]; ignores?: string[]; globals?: Linter.Globals } JS files type-checked via JSDoc.
jquery { files: string[]; ignores?: string[]; globals?: Linter.Globals } Layers jQuery globals ($, jQuery) on top of the matching globs. Per-entry globals merge with the jQuery ones rather than replacing them.
strict boolean | string[] Applies the strict preset globally (true) or scoped to globs.
spellChecker { ignoredPaths?: string[]; customWordListFile?: string } Cross-cutting cspell overrides.
silenceRules string[] Rule IDs to demote to 'warn' across every block.
configFiles false | string[] Emits a dedicated block for root-level configuration files (*.config.*, eslint.config.*) that disables import-x/no-extraneous-dependencies and import-x/prefer-default-export. Defaults are applied automatically — omit the key (or pass []) to use the built-in defaults only, pass a string[] to layer your own globs on top, or pass false to opt out entirely.

A scope entry with per-entry globals — declare project-specific bindings without spreading the useInsider return into your own array:

import { useInsider } from '@useinsider/eslint-config';

export const { useInsiderESLint, useInsiderOXC } = useInsider({
    ignores: ['dist', 'coverage'],
    javascriptDom: [
        {
            files: ['src/**/*.js'],
            ignores: ['src/vendor/**'],
            globals: {
                APP_URL: 'readonly',
                axios: 'readonly',
                atatus: 'readonly',
            },
        },
    ],
});

Block-emission order — the leading ignores block, then scope blocks (in the order their keys were inserted into the config object), then jquery blocks, then the configFiles block, then any strict blocks, then spellChecker blocks. Later blocks win on overlap, matching ESLint's flat-config precedence model.

This order is why configFiles — not a scope — is the right place to relax rules for configuration files: its block is emitted after every scope block, so its overrides survive. A scope listing the same globs is emitted before the language scopes and gets overridden by any of them that also match.

Removed in v2: the config scope. It only switched import-x/no-extraneous-dependencies and import-x/prefer-default-export off, and because it was emitted with the scope blocks it was silently overridden for every file a language scope also matched — which, for .js/.ts, was all of them. Passing config now throws with a pointer to configFiles. Move its globs to configFiles: [...]; root-level *.config.* and eslint.config.* are already covered by the defaults, so most projects can simply drop the key.

useInsider is synchronous and returns { useInsiderESLint, useInsiderOXC }. useInsiderESLint() returns Promise<Linter.Config[]> — export it without awaiting, ESLint 9 resolves it at load time. useInsiderOXC() also returns a Promise, and oxlint does not await a default export, so oxlint.config.ts must use top-level await. The UseInsiderResult type alias is exported for TypeScript callers that wrap the renderers in a helper of their own.

Two file-matching differences between the linters are handled for you, but worth knowing:

  • An unanchored oxlint glob matches at any depth, so files: ['*.mjs'] would also claim .husky/install.mjs. The emitted oxlint config anchors every glob that does not already start with **, restoring ESLint's semantics.
  • oxlint discovers a nested oxlint.config.* in any subdirectory and lints that directory with it, ignoring the parent config's ignorePatterns. The generated lint script passes --disable-nested-config, which turns that off and makes the root config's ignorePatterns authoritative. It is also required rather than optional: the emitted config carries root-only options, and oxlint aborts with a config-parse error the moment it discovers those in a nested file — which a monorepo whose packages each ran this CLI always has. Run oxlint once per package, from that package.

Unused disable directives

Both linters report a disable comment that no longer suppresses anything, and each one judges only its own spelling:

  • The emitted oxlint config sets options.reportUnusedDisableDirectives to warn, so a stale // oxlint-disable-* is reported.
  • The emitted ESLint config pins linterOptions.reportUnusedDisableDirectives to warn, so a stale // eslint-disable-* is reported.

It also sets options.respectEslintDisableDirectives: false, which is what makes the pair sound. oxlint judges an eslint-disable against the rules it ran, so with the default (true) a comment naming @cspell/spellchecker, sonarjs/*, @stylistic/* or jsdoc/check-tag-names — every rule the split deliberately keeps on the ESLint side — is reported as unused. Refusing the eslint-disable spelling removes that false positive entirely, at the cost of making the ownership split explicit in your source: a rule oxlint owns must be suppressed with oxlint-disable, a rule ESLint owns with eslint-disable. migrate performs that rewrite for you; an un-migrated comment is reported by ESLint as an unused directive, so nothing fails silently.

Neither severity fails the build on its own. Escalate with oxlint --deny-warnings and eslint . --max-warnings 0 if you want that.

silenceRules() (named export) is also available as a global registration hook. Call it at module top level to register rule IDs to demote before useInsider is invoked; the per-call silenceRules: [...] option layers on top.

import { useInsider, silenceRules } from '@useinsider/eslint-config';

silenceRules(['no-magic-numbers']);

export const { useInsiderESLint, useInsiderOXC } = useInsider({ /* ... */ });

Lint scripts

Both linters have to run, and oxlint has to run first — the ESLint side turns off every rule oxlint owns, so ESLint alone silently drops those checks. The canonical script is:

{ "scripts": { "lint": "oxlint --disable-nested-config && eslint ." } }

The CLI never replaces a script it did not author. It finds the one script that really invokes the eslint binary and prepends oxlint to it, leaving the original command verbatim inside the new value. A composed lint that fans out to a type-check and a stylelint pass is therefore untouched:

  "lint":          "run-p lint:type lint:es lint:style",
  "lint:es":       "npm run lint:es-ci -- --cache",
- "lint:es-ci":    "NODE_OPTIONS=--max-old-space-size=6144 eslint .",
+ "lint:es-ci":    "oxlint --disable-nested-config && NODE_OPTIONS=--max-old-space-size=6144 eslint .",
  "lint:style":    "npm run lint:style-ci -- --cache",
  "lint:type":     "run-p lint:type:app lint:type:test lint:type:node",

Prepending rather than appending is deliberate: a -- passthrough (npm run lint:es-ci -- --cache) lands on the last command, so --cache still reaches ESLint. A script whose name ends in a --fix invocation gets oxlint --disable-nested-config --fix. Every rewrite is printed with its previous value.

Exactly one script is rewritten. When several entry points run ESLint — lint, lint:fix, lint:ci — prepending to all of them would run oxlint once per entry point over the same tree, so the CLI picks the entry point lint designates (itself, or the leaf it delegates to) and reports the rest with the prefix to add if you want oxlint on them too.

If no script invokes eslint and a lint script already exists, nothing is written — the conflict is reported with the command to add by hand.

Migrating to v4

v4 requires Node.js 24.19+ (engines.node was >=20 through v3). Check your CI images and any repo pinning an older Node before upgrading: npm and pnpm warn with EBADENGINE, and fail outright under engine-strict.

v4 splits the lint run across ESLint and oxlint. Run the CLI to do all of this automatically:

npx @useinsider/eslint-config migrate

The guided migration offer

You do not have to know that command exists. Running lint against a v3 config under v4 detects the outdated structure and, in an interactive terminal, offers to run the migration there and then:

@useinsider/eslint-config: eslint.config.js still uses the v3 single-file layout.
  /repo/eslint.config.js calls `useInsider()` itself, and
  insider.lint.ts and oxlint.config.ts are missing.

Starting the guided migration. It shows every change before applying it.
Skip it with INSIDER_NO_PROMPT=true.

◆  eslint.config.js still uses the v3 single-file layout. Migrate it now?
│  ● Yes / ○ No

Accepting runs the normal migrate flow — same summary, same confirmations — and then exits non-zero asking you to re-run lint, because this process already loaded the configuration that was just rewritten.

The offer is suppressed, and the failure message leads with the migrate command instead, whenever prompting would be wrong: under oxlint (whose stdout is its report), in CI, without a TTY, with INSIDER_NO_PROMPT=true, or with INSIDER_SKIP_CONFIG_VALIDATION=true.

The directive rewrite is reported as its own step, with the file count shown before anything is written, so you can take it as a separate commit or decline it and run migrate again later.

What changes:

  • eslint-disable no longer suppresses an oxlint-owned rule. This is the largest behavior change in v4. The emitted oxlint config sets options.respectEslintDisableDirectives: false, so oxlint reads only the oxlint-disable spelling. A comment left in the v3 form suppresses nothing, and because ESLint no longer runs that rule it reports the comment as an unused directive rather than failing quietly. migrate rewrites the affected comments for you (AST-based — it touches only real comment nodes).

    - // eslint-disable-next-line no-console -- CLI banner
    + // oxlint-disable-next-line no-console -- CLI banner
      console.info(banner);
    

    Rules the split keeps on the ESLint side — @cspell/*, sonarjs/*, @stylistic/*, jsdoc/check-tag-names, no-inner-declarations, every type-aware @typescript-eslint rule — keep the eslint-disable spelling. See Unused disable directives for why refusing the eslint spelling is what makes stale-directive reporting sound in both directions.

  • useInsider() is now synchronous and returns two renderers. A v3 export default useInsider({...}) no longer produces a flat-config array.

    - // eslint.config.mjs
    - import { useInsider } from '@useinsider/eslint-config';
    -
    - export default useInsider({ typescriptNode: [{ files: ['src/**/*.ts'], tsconfigPaths: ['tsconfig.json'] }] });
    + // insider.lint.ts
    + import { useInsider } from '@useinsider/eslint-config';
    +
    + export const { useInsiderESLint, useInsiderOXC } = useInsider({
    +     typescriptNode: [{ files: ['src/**/*.ts'], tsconfigPaths: ['tsconfig.json'] }],
    + });
    
  • Two new sibling files — eslint.config.* and oxlint.config.*, each a thin re-export. See the three-file layout.

  • The lint command runs both linters: oxlint --disable-nested-config && eslint .. --disable-nested-config is required, not stylistic. Without it oxlint discovers any nested oxlint.config.* in the tree and lints that subtree with it, bypassing the root config's ignorePatterns. Worse, the emitted config carries options.*, which oxlint accepts only in a root config — a nested config carrying options aborts the whole run, even when its directory is listed in ignorePatterns.

  • Monorepo consumers run oxlint per package. oxlint has no workspace mode, so each package owns its own three files and its own lint script, and each script carries --disable-nested-config. Running one oxlint pass from the repo root over sibling packages is not equivalent.

  • Only oxlint.config.ts and oxlint.config.mts are discovered. Any other extension — .js, .mjs, .cjs, .cts — is ignored without a word, and oxlint falls through to the nearest ancestor config. The config appears live while none of its rules run. ensureOxlintConfig() fails the ESLint side when neither accepted filename sits beside the lint module, so the mistake surfaces rather than silently disarming half the rule set.

  • oxlint is version-tracked like eslint and globals. A consumer whose installed oxlint is older than this package's pin is upgraded automatically on the next lint run, because an older oxlint silently drops whatever rules that release did not yet implement. Opt out with silenceDependencyWarning(['oxlint']).

  • tsconfig.json gains allowImportingTsExtensions: true. eslint.config.ts and oxlint.config.ts must import the lint module by its real extension — nothing here applies TypeScript's .js → .ts rewriting — and tsc fails with TS5097 otherwise. The CLI prompts to add it and resolves the full extends chain first, so a shared base that already sets it is left alone; declining prints the snippet to paste. A project that emits is never edited — TypeScript rejects the flag there with TS5096.

  • @typescript-eslint type-aware rules, sonarjs, @stylistic, @cspell, jsdoc/check-tag-names and no-inner-declarations stay with ESLint. Two of those are permanent, for different reasons:

    • jsdoc/check-tag-names is driven by settings.jsdoc.tagNamePreference (the TypeScript scopes remap @desc/@callback onto @use/@watch), and oxlint has no equivalent of flat-config settings.
    • no-inner-declarations is implemented by both, but they disagree on the default for an omitted option: ESLint 9 defaults blockScopedFunctions to allow, oxlint to disallow. Handing oxlint the bare 'error' would reject every block-scoped function declaration ESLint accepts.
  • Stale disable directives warn on both sides, and neither fails the build on its own. Escalate with oxlint --deny-warnings and eslint . --max-warnings 0.

Migrating from v1.x

This section describes the v1 → v2 step. The scope-keyed config object it introduces is still the v4 shape; only the wiring around the call changed again, which Migrating to v4 covers. Do both steps if you are coming from v1.

  • preset: string[] → top-level scope properties. The order in the v1 preset array no longer matters — the new shape has one property per scope and overrides are explicit.

    - await useInsider({
    -     preset: ['typescript-node'],
    -     config: { files: ['src/**/*.ts'], languageOptions: { parserOptions: { project: ['./tsconfig.json'] } } },
    - })
    + useInsider({
    +     typescriptNode: [
    +         { files: ['src/**/*.ts'], tsconfigPaths: ['tsconfig.json'] },
    +     ],
    + })
    
  • preset: ['vue3-typescript'] + config → vue3Typescript entry with a dedicated globalComponents field. No more hand-rolled vue/no-undef-components overrides.

    - await useInsider({
    -     preset: ['vue3-typescript'],
    -     config: {
    -         files: ['src/**/*.{ts,vue}'],
    -         languageOptions: { parserOptions: { project: ['./tsconfig.app.json'] } },
    -         rules: { 'vue/no-undef-components': ['error', { ignorePatterns: ['^RouterLink$', '^RouterView$'] }] },
    -     },
    - })
    + useInsider({
    +     vue3Typescript: [
    +         {
    +             files: ['src/**/*.{ts,vue}'],
    +             tsconfigPaths: ['tsconfig.app.json'],
    +             globalComponents: ['RouterLink', 'RouterView'],
    +         },
    +     ],
    + })
    
  • preset: ['strict'] → top-level strict: true | string[].

    - await useInsider({ preset: ['typescript-node', 'strict'], config: { files: ['src/**/*.ts'] } })
    + useInsider({
    +     typescriptNode: [{ files: ['src/**/*.ts'], tsconfigPaths: ['tsconfig.json'] }],
    +     strict: true,
    + })
    

    Behavior change: in v1 the strict preset was always global when listed. In v2, strict: true keeps that global behavior; strict: string[] scopes the strict rule set to the matching globs only. If you have paths-targeted strict checks, set strict: [...] instead of strict: true.

  • Shape change: useInsider(...) stopped being an awaited, spreadable array. In v2 it became export default useInsider({...}); in v4 it became the renderer destructuring shown in Migrating to v4. Either way there is no supported override path — describe every glob you want linted inside the call, including jQuery files via the dedicated jquery scope.

  • Removed: the preset array, the config: Linter.Config escape hatch, and the config preset itself. Configuration-file linting is now an ordinary typescriptNode (or javascriptNode) entry pointed at the right tsconfig.

  • Kept: silenceRules accepts the same string[] shape both as a top-level option and via the named silenceRules() export. silenceDependencyWarning() works unchanged. Side-effect lines such as silenceDependencyWarning(true) continue to sit above the useInsider(...) call, now in insider.lint.*.

First lint after install

The first time you run lint after installing, ensureDependencies may detect that your project's eslint or globals version is older than the pin and auto-install them, exiting with code 1. Run lint again and it will complete normally.

Troubleshooting

Missing dependencies detected:

If you see this error, your project is missing a peer plugin this config needs. The error message lists the install command. To silence the warning permanently:

import { useInsider, silenceDependencyWarning } from '@useinsider/eslint-config';

silenceDependencyWarning(['@cspell/eslint-plugin', '@stylistic/eslint-plugin']);

export const { useInsiderESLint, useInsiderOXC } = useInsider({ /* ... */ });

Passing true silences every warning; that is not recommended because the warning is the only signal that your installed plugin versions drifted from what the config expects.

silenceDependencyWarning(true);

Contributing

See CONTRIBUTING.md at the repo root.

Related Packages

@kcuf/eslint-config

The Kcuf shareable eslint config.

3,067 1
@renton/eslint-config-vue

ESLint configuration for Vue.js and Vue I18n projects

0 0
@claylevering/eslint-config

A set of ESLint configurations supporting flat config format with Vue (and with...

30,479 1
@tresinternet/eslint-config-vue3

Opinionated ESLint config for consistent, clean, auto-sorted code in TRES Vue3 a...

2,631
slidev-md-lint

Slidev の .md に書いた Vue(HTML)部分を ESLint でチェック・自動整形する共有設定...

407 0

Version History

Version Released License
4.1.0 MIT
4.0.0 MIT
3.0.2 MIT
3.0.1 MIT
3.0.0 MIT
Pre-releases (76)
Version Released License
4.1.0-beta.2 MIT
4.1.0-beta.1 MIT
4.0.0-beta.6 MIT
4.0.0-beta.5 MIT
4.0.0-beta.4 MIT