# Installation

> [!NOTE]  
> When using Docker with `[p]npm` Make recipes add `make` before every command, and surround instruction with quotes.

Install the JavaScript config and required dependencies:

```bash
# Using pnpm (recommended)
pnpm add @bitfactory/eslint-config --save-dev --save-exact

# Using npm
npm install @bitfactory/eslint-config --save-dev --save-exact
```

`latest` always points at the newest major, and the major line it names is the supported one (ADR-0036).
Every patch on that line stays installable; a line is retired - deprecated as a whole, with no backports
from then on - when its successor major publishes. An end of life upstream, of the oldest ESLint major its
peer range admits or the oldest Node line its `engines.node` declares, is usually what brings that major
about. An individual
version carries a deprecation warning only when it is defective, naming the first version without the
defect: `14.0.0` and `14.0.1` are deprecated for declaration faults - a peer floor admitting versions that
crash on ESLint 10, and an open-ended `engines.node` end - which the `14.0.1` and `14.0.2` entries below
describe. `14.0.2` is the first 14.x without either.

Some earlier majors received a patch after `latest` had already moved on; the *newest* such patch on each
line carries a `release-<major>` tag - `npm view @bitfactory/eslint-config dist-tags` lists which lines
currently have one, and installs the same way as any version:

```bash
pnpm add @bitfactory/eslint-config@release-12 --save-dev --save-exact
```

It is still a deprecated, unsupported version: the tag exists so you can reach it deliberately, not as a
support claim (ADR-0005, ADR-0036). Pin an exact version when in doubt, and see the list below to move onto
the supported line.

> [!IMPORTANT]
> Each entry below applies if your upgrade crosses it. They are listed oldest first, so find the version you
> are coming from and work downwards. Most are breaking changes; the `14.0.1` and `14.0.2` entries are
> patches worth checking even though they ship no major. Several of these releases also raised peer floors,
> so bump any peer you pin below the floor of the version you land on.
>
> **Coming from 5.x or earlier**: the config is flat-config only from `6.0.0`. See the
> [Flat Config Migration Guide](migration-flat-config.md).
>
> **Coming from 6.x**: `7.0.0` removed the `.flat.js` subpaths, so import `./vue` and `./typescript` instead
> of `./vue.flat.js` and `./typescript.flat.js`, and it moved `@typescript-eslint` to v8. See the
> [TypeScript-ESLint v8 Migration Guide](migration-typescript-eslint-v8.md).
>
> **Coming from 7.x or earlier**: `8.0.0` raised the ESLint floor to `^9.38.0`.
>
> **Coming from 8.x or earlier**: `9.0.0` stopped shipping a Babel parser, so bring your own if your code
> needs syntax beyond ESLint's built-in one. See the
> [espree Parser Migration Guide](migration-espree-parser.md).
>
> **Coming from 9.x or earlier**: `10.0.0` swapped the `eslint-plugin-import` peer for
> `eslint-plugin-import-x`. See the [ESLint 10 Migration Guide](migration-eslint-10.md).
>
> **Coming from 10.x or earlier**: `11.0.0` dropped Node.js 20, so the engine check runs before any config
> loads: npm warns by default and fails under strict enforcement, pnpm is silent by default and fails only
> if your own project sets `engine-strict`. See the
> [Node.js 20 Drop Migration Guide](migration-node-20-drop.md).
>
> **Coming from 11.x or earlier**: `12.0.0` moved the import plugin to the `import-x` namespace, so every
> `import/*` rule that resolves through this config becomes `import-x/*`. Active-severity references and
> stale `eslint-disable import/*` comments fail until renamed. See the
> [`import-x` Namespace Migration Guide](migration-import-x-namespace.md).
>
> **Coming from 12.x or earlier**: `13.0.0` raises several peer floors and caps `typescript` below `6.1.0`.
> ESLint support itself is unchanged. Raise any peer you pin below its new floor, and lower a `typescript`
> pin at or above `6.1.0`. See the [Peer Range Bounds Migration Guide](migration-peer-range-bounds.md). It
> also stops setting a type-aware throw rule for `.ts`; see
> [Configuration: Enable Type-Aware Linting](02-configuration.md#enable-type-aware-linting-typescript).
>
> **Coming from 13.0.x**: `13.1.0` widens the `eslint-plugin-unicorn` peer to `>=62.0.0 <66.0.0`, admitting
> 64 and 65. The install needs no action; linting may. This config spreads that plugin's `recommended`
> preset, which grew over those versions. If you resolve the peer by range rather than pinning it, the rules
> the preset added can report new violations, so a job that lints can start failing even though the install
> succeeded. Either fix those findings, or pin `eslint-plugin-unicorn` to `63.x` to keep the current rule set.
>
> **Coming from 13.x or earlier**: `14.0.0` raises `engines.node` to `^22.22.2 || >=24.15.0`; npm warns by
> default and fails under strict enforcement, pnpm is silent by default and fails only if your own project
> sets `engine-strict`. See the [Node Engines Floor Migration Guide](migration-node-floor.md).
>
> It also rebuilds `./json` on `eslint-plugin-jsonc` and `eslint-plugin-package-json`, replacing the
> `@eslint/json` peer. Rule IDs move from `json/*` to `jsonc/*` and `package-json/*`, and the preset now orders
> `package.json` and sorts JSON keys, so a repository that has never done either sees new findings. They are
> auto-fixable, and a separate `sort-package-json` step can be removed. See the
> [`./json` Preset Migration Guide](migration-json-preset.md).
>
> Every step below, organised by project shape rather than by break, is in the
> [14.0.0 Migration Guide](migration-v14.md): the entrypoints each stack takes, the ignores that go with it,
> the plugins this release makes redundant, and how to adopt the rule changes in stages.
>
> **Add `gitignore()` to your config if it is not there already.** This package now ignores only what that
> helper cannot reach - a committed file, which never appears in a `.gitignore` - and leaves every generated
> directory to it (ADR-0027). The base config used to name `.nuxt/`, `dist/` and `static/sw.*` itself, but wrote
> them with a leading slash - the gitignore anchor, which flat config does not read that way - so since `6.0.0`
> they matched nothing. They are removed rather than repaired, so **nothing changes here against `13.x`**: if
> your build output was being linted, it still is until `gitignore()` covers it.
>
> `./json` does not lint a generated dependency lockfile. `package-lock.json` and `npm-shrinkwrap.json` are
> ignored at any depth, so the key-ordering rules this release introduces never reach one. The package manager
> owns that file's order and rewrites it wholesale. `package.json` itself is linted, and ordered.
>
> These three ignores are global, so nothing else in your run lints those files either. That is deliberate: a
> lockfile is committed, so no `gitignore()` call can reach it, and it is the one case this package still
> ignores on a consumer's behalf (ADR-0027).
>
> Three of the old peer's validity checks have no replacement, so those findings stop appearing rather than
> being reported differently. The migration guide names them; a repository that relied on them needs another
> check for that JSON.
>
> `./markdown` is removed along with its `@eslint/markdown` peer; use
> [markdownlint](https://github.com/DavidAnson/markdownlint) directly. `./package-json` is added for packages
> that publish. `no-duplicate-imports` is dropped in favour of `import-x/no-duplicates`, which reports the same
> thing once. No peer range narrowed: `eslint-plugin-jsdoc` widened to `<65.0.0`, and the rest
> were added or removed rather than tightened.
>
> `import-x/no-duplicates` now resolves for `.ts`, `.tsx`, `.mts` and `.cts` under `./typescript`, and for
> `.vue` under `./vue` and `./vue3`; before, no duplicate-import rule reached either file type. `./nuxt`
> already carried one through its own import configuration and is unchanged. A file importing the same module
> twice reports where it did not before, so an upgrade can raise findings in code you have not touched.
> `eslint --fix` merges the duplicate imports; a separate `import type` from the same module is left alone.
>
> Under `./nuxt`, a `.jsx` file at the repository root is linted where it was skipped, so it can report on
> upgrade.
>
> Sorting arrives in three scopes. Imports, exports, named imports and exports, destructured keys and variable
> declarations are sorted everywhere; TypeScript interface, object-type and union members are sorted in `.ts`, and in a `<script lang="ts">` block;
> and object literals are sorted **only** under `config/`, `lang/`, `locales/` and `i18n/`, because a key's
> position in ordinary code can be relied on and in a data map it cannot. Every one of these is auto-fixable.
> The core `sort-imports` and `sort-vars` rules are removed, superseded by the plugin versions.
>
> Two new required peers come with it: `eslint-plugin-perfectionist` and
> `eslint-plugin-sort-destructure-keys`. Those are required, so an auto-installing package manager fetches them.
> Watch the *optional* peers instead: `/vue`, `/vue3` and `/nuxt` now import `@typescript-eslint/eslint-plugin`
> and `@typescript-eslint/parser` unconditionally, `/tailwind` imports the parser too, and `/tailwind-vue`
> imports both it and `vue-eslint-parser`. Nothing auto-installs an optional peer, so a project on any of those
> entrypoints that has not installed them fails to load the configuration at all.
>
> **The big one.** The entrypoints that lint the JavaScript family now apply the same house rule set to every
> file type they lint, which is what this package did before flat config and stopped doing in `6.0.0`. Until now a `.ts` or `.vue` file
> resolved only its framework's recommended set, so a `.ts` file was required to omit semicolons and indent by
> two while a `.js` file in the same repository was required to do the opposite, and an SFC resolved neither -
> `var` in a `.vue` file passed. See ADR-0022.
>
> What that means in practice: the resolved rule count for `.ts`, `.vue` and a Nuxt project's `.js` rises
> several-fold. Run `npx eslint --print-config <file>` against a file of each kind before and after the upgrade
> to see your own figures. Indentation on `.ts` and `.vue` changes from two spaces to four,
> semicolons become required, and the core rules the base config has always applied now apply there too, which
> is what `.editorconfig` already states. Expect one large reformatting commit.
>
> Most of the formatting is `--fix`-able and some of the rest is not: `func-style` and `init-declarations` have
> no fixer, and neither do the framework checks a widened rule set newly reaches. Run `eslint --fix` and then
> `eslint` to see what is left in your own codebase rather than budgeting from a figure here. The staged
> approach in the [14.0.0 Migration Guide](migration-v14.md) exists so none of it has to be done at once.
>
> Composition also stops mattering. `/vue3` lints `.ts` itself, so it no longer needs `/typescript` beside it;
> `/nuxt` is `/vue3` plus Nuxt's own layer. Writing `/typescript` alongside `/vue3` still works and changes
> nothing; alongside `/nuxt` it has to come first, because `/nuxt` switches `no-undef` off last and anything
> after it turns that back on.
>
> Two things a `/nuxt` project must do. The export is a plain array rather than a `FlatConfigComposer`, so
> spread it and add your own blocks after it instead of calling `.append()`. And `gitignore()` is not optional
> here: `features.standalone: false` (ADR-0023) drops the community layer's own ignores, which is the one
> entrypoint where this release genuinely removes coverage you had. Without that line the first run reports on
> `.nuxt/` and `.output/`.
>
> Two removals of coverage, which the rest of this list does not imply. `vue/*` rules no longer reach a
> component defined in a `.js` or `.ts` file: they are scoped to `.vue`, where a template is, rather than
> applying to the JavaScript family as the unscoped upstream blocks used to. And `/nuxt` selecting Nuxt's layer
> alone (ADR-0023) drops the `import` and `@typescript-eslint` rules that layer used to bring with it; this
> package supplies its own for both, under `import-x/*` and the recommended TypeScript tier.
>
> `/vue` (Vue 2) is deprecated and will be removed when the `eslint` 9 peer is dropped. It receives the house
> set and a TypeScript sub-parser in this release, so a Vue 2 project with `<script lang="ts">` is linted
> rather than rejected, and nothing after that (ADR-0026).
>
> Around thirty `jsdoc/*` rules apply again, to every file type the house set reaches. They have been absent
> since `6.0.0`, when the flat-config rewrite dropped the upstream set. What returns is the half that reads a
> docblock you have already written and reports where it is wrong: a `@param` naming an argument that does not
> exist, an undefined type, a `@returns` on a function that returns nothing, a malformed block. Nothing asks
> you to write a docblock, or to complete one you have not written - the rules that demand documentation stay
> off, because this package's own standard is not to write it (ADR-0029). Everything these rules report is a
> warning, so it fails a run only where you set `--max-warnings 0`.
>
> `/tailwind-vue` is added. `eslint-plugin-tailwindcss` supplies no parser, so an entrypoint reaching `.vue`
> has to bring `vue-eslint-parser`; the new export does, and carries the same rules. `/tailwind` changes too:
> it now states its own globs - `.js`, `.jsx`, `.ts` and `.tsx` - and supplies a
> TypeScript parser for `.ts` and `.tsx` rather than claiming them unparsed. Taking only the rule set is what
> stops the entrypoint contributing unscoped rules to your JSON under the plugin's v3 shape (ADR-0025). Both
> need your project's own Tailwind entry point configured. Each parses the files it globs, so neither needs
> another entrypoint beside it.
>
> `./nuxt` resolved none of that set before and now resolves all of it, so a Nuxt project sees new stylistic
> findings on upgrade. Most are auto-fixable. This config's own conventions are used rather than Nuxt's
> `stylistic` option, so `./nuxt` now agrees with the other entrypoints on indentation, quotes and
> semicolons.
>
> **Coming from 14.0.0**: `14.0.1` raises the `@stylistic/eslint-plugin` floor to `>=5.9.0`. `5.0.0`-`5.8.0`
> all declared `eslint: >=9.0.0`, an open range that admits ESLint 10 without actually stating support for
> it; `5.0.0`-`5.4.0` crash there (`context.getSourceCode`, removed in ESLint 10). `5.9.0` is the first
> version whose own declared range narrows to `^9.0.0 || ^10.0.0`, so the floor moved to what the peer itself
> states, not to wherever the crash happens to stop. A pin below `5.9.0` now fails outright under npm's
> default resolution, or under pnpm with `strict-peer-dependencies` (pnpm's own default only warns) - even a
> version between `5.5.0` and `5.8.0` that runs clean on ESLint 10 despite the undeclared range; bump it
> (ADR-0011, issue #310 tracked in the private repository).
>
> **Coming from 14.0.1 or earlier**: `14.0.2` caps the `24` end of `engines.node` at `^24.15.0`; it was
> `>=24.15.0`, open-ended. Node 25 and 26 satisfied the old check and do not satisfy the new one (ADR-0035,
> issue #321 tracked in the private repository).

## Peer Dependencies

If your project uses `.npmrc` with auto-install configuration, peer dependencies will be installed automatically. Ensure your `.npmrc` includes `auto-install-peers=true`, for example:

```ini
auto-install-peers=true
package-import-method=copy
save-exact=true
update-notifier=false
```

On ESLint 9, pin `@eslint/js` to `9.38.0` or a newer `9.x`. Left to resolve by range it selects an ESLint 10
build and the install fails; see
[Known issue: `@eslint/js` resolves to an ESLint 10 build on ESLint 9](known-issue-eslint-js-peer.md).

If you're not using auto-install, install peer dependencies manually:

```bash
pnpm dlx install-peerdeps --dev --extra-args="-E" @bitfactory/eslint-config
# or: npx install-peerdeps --dev --extra-args="-E" @bitfactory/eslint-config
```

## Vue & TypeScript installation

For Vue.js projects, also install. The `@typescript-eslint` pair is on this list rather than the TypeScript one
because `/vue`, `/vue3` and `/nuxt` import both unconditionally, to parse a `<script lang="ts">` block and to
apply the type-member rules; a JavaScript-only Vue project still needs them present or the configuration fails
to load:

```bash
pnpm add eslint-plugin-vue eslint-plugin-vuejs-accessibility vue-eslint-parser @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev --save-exact
# or: npm install eslint-plugin-vue eslint-plugin-vuejs-accessibility vue-eslint-parser @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev --save-exact
```

For TypeScript projects without Vue, install just the pair:

```bash
pnpm add @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev --save-exact
# or: npm install @typescript-eslint/eslint-plugin @typescript-eslint/parser --save-dev --save-exact
```
