npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@petbee/eslint-config

v5.4.0

Published

Petbee's eslint config

Downloads

8,232

Readme

@petbee/eslint-config

This package provides Petbee's ESLint configuration as an extensible shared config. From 5.3.0 it supports ESLint 10 only (peerDependencies.eslint: ^10.0.0).

⚠ 5.3.0 is a breaking change released as a minor. It is published inside the ^5 range on purpose.

Move the repo to eslint 10 before regenerating or updating its lockfile.

  • Any repo on @petbee/eslint-config@^5 picks up 5.3.0 the next time its lockfile is regenerated or re-resolved, without anyone choosing to: an npm install with no lockfile, an npm update, a Dependabot or manual bump that targets this package, or deleting and rebuilding the lockfile. A plain npm install against a committed package-lock.json that already satisfies ^5 keeps the locked 5.2.0.
  • If that repo is still on eslint 9, npm install fails with ERESOLVE (peer eslint@"^10.0.0" from @petbee/eslint-config); yarn v1 does not fail, it only warns incorrect peer dependency and installs 5.3.0 anyway, so a yarn repo needs eslint 10 just the same. The fix is to move the repo to eslint 10 — not --legacy-peer-deps, --force or an overrides entry.
  • Node 22+ is required (engines.node >=22). npm only warns on older Node, but yarn v1 fails to install on Node < 22.
  • A repo that wants to stay on the old behaviour must pin ~5.2.0 (or 5.2.0) explicitly.

Migration steps and the React rule mapping are in CHANGELOG.md.

Features (v4.0.0)

  • ✅ ESLint 10 - the only supported major from 5.3.0; 5.0–5.2 declared ^9.39.1 || ^10.0.0 but no eslint version satisfied its own dependencies
  • ✅ Node 22+ - engines.node >=22 from 5.3.0, because @eslint-react/eslint-plugin declares it. That includes backend repos: see React rules below
  • ✅ TypeScript 4/5/6 support - Broad compatibility for incremental upgrades
  • ✅ typescript-eslint 8.59+ - Current type-aware linting stack
  • ✅ Auto NestJS detection - Automatically configures for NestJS projects
  • ✅ Jest 29 support - Updated test linting rules
  • ✅ Cypress 6 support - Modern E2E test linting
  • ✅ Next.js plugin bundled - @next/eslint-plugin-next ships with this package
  • ✅ Prettier integration - Conflicts automatically resolved

Installation

Give that you already have ESLint installed, run:

npm install -D @petbee/eslint-config typescript prettier

Usage

For ESLint v8.x and older (legacy config) — 5.2 and earlier only

5.3.0 requires ESLint 10, and ESLint 10 has no .eslintrc support, so this form only applies to a project still on 5.2 or earlier with ESLint 9 or older. The ./legacy entry still ships in 5.3.0, but no ESLint version that 5.3.0 installs with can load it.

// .eslintrc
{
  "extends": ["@petbee/eslint-config"],
}

For ESLint v10 (flat config)

ESLint 10 is required from 5.3.0. Do not import eslint-plugin-react in your own config: its newest release (7.37.5) peers eslint <=9.7 and crashes on ESLint 10 when settings.react.version is 'detect'.

React rules come from @eslint-react/eslint-plugin (plugin key @eslint-react) and apply only to **/*.{jsx,tsx}. They are loaded whenever react resolves from the package, which is not the same as "React projects": Prisma 7 installs react as a peer of @prisma/studio-core, so backend services with Prisma load both React plugins too. Their lint results do not change (they have no .jsx/.tsx), but the plugins must import, so the Node 22+ floor applies to them as well.

// eslint.config.js
import petbeeConfig from '@petbee/eslint-config'

export default [
  ...petbeeConfig,
  // Your custom configurations...
]

As any other eslint preset, it's possible to override some rules and configurations. We encourage trying to keep the closest possible to the preset rules, but every project is different and sometimes overriding is needed, use it carefully.

For typescript

The preset will automatically load Typescript rules when dealing with .ts or .tsx files. However, there are some rules that require type-checking. This means that a tsconfig.json, which includes all files supposed to be linted, must be present. If your existing configuration does not include all of the files you would like to lint, you can create a separate tsconfig.eslint.json, at the root of your project, as follows:

// tsconfig.eslint.json
{
  "extends": "./tsconfig.json",
  "include": ["**/*.ts", "**/*.tsx", "**/*.js"],
  "exclude": [],
}

And you should be good to go.

For NestJS

The preset will automatically load NestJS rules when your projects has @nestjs/core installed. However, if you are using NestJS with Typescript, you need to make sure that the tsconfig.json file is present at the root of your project. If you are using a custom tsconfig.json, you can create a separate tsconfig.eslint.json file as follows:

// tsconfig.eslint.json
{
  "extends": "./tsconfig.json",
  "include": ["**/*.ts", "**/*.tsx", "**/*.js"],
  "exclude": [],
}

This will ensure that the ESLint parser can find all the files that need to be linted.

For Javascript

Sometimes you want to use modern, not yet officially supported, syntax in your Javascript files, such as dynamic import(). This can be achieved by using the babel-eslint parser. For size reasons, we don't include it in this preset but it's extremely simple to configure it:

npm install -D babel-eslint
// .eslintrc
{
  "extends": "@petbee/eslint-config",
  "parser": "babel-eslint",
  "parserOptions": {
    "sourceType": "module",
  },
}

If a project uses both Typescript and Javascript, you can configure the parser inside an override block:

// .eslintrc
{
  "extends": "@petbee/eslint-config",
  "overrides": [
    {
      "files": ["*.js", "*.jsx"],
      "parser": "babel-eslint",
      "parserOptions": {
        "sourceType": "module",
      },
    },
  ],
}

Please check the babel-eslint documentation for further options.

How the flat config is assembled

index.mjs composes the rule sets in rules/ through getFlat(), which reads each module's module.exports.flat. A rule file without that export contributes nothing, silently — no error, no warning, the rules are simply not there. That is how five files sat inert from v4.0.0 until v5. npm run test:flat-config now fails if any rule file stops reaching the config.

Order matters, because in flat config the last matching config object wins each rule. The TypeScript set is applied last so its deliberate tuning (rules/shared-config.js) wins over the language-agnostic sets, and so the base rules superseded by type-aware ones stay off on .ts files. A new rule set goes before it.

Import order

This package owns import order, through the import/order rule. @petbee/prettier-config does not touch it — up to v4 it loaded prettier-plugin-organize-imports, whose alphabetical ordering contradicted this rule and made the resulting error unfixable; the plugin was removed in v5.

Order is enforced as blank-line separated groups, and eslint --fix produces it:

import { createHash } from 'node:crypto'

import { Injectable } from '@nestjs/common'
import { PutCommand } from '@aws-sdk/lib-dynamodb'

import { buildHelmetConfig } from '@config/security'

import { DynamoDbClientProvider } from './dynamodb.client'

import type { SuppressionRecord } from './suppression.types'

Reaching outside the module boundary is enforced by no-restricted-imports at error (pattern ../../*). Fix those with a path alias, not by relaxing the rule.

Unused imports are reported by @petbee/no-unused-vars (on the flat path; see below) at error. Prefix an intentionally unused binding with _.

@petbee/max-params

This package ships two rules of its own, under the @petbee plugin name: this one and @petbee/no-unused-vars. Each is an upstream rule plus one exemption that configuration cannot express.

@petbee/max-params is max-params plus a single exemption: a constructor whose parameters are all TypeScript parameter properties is not counted.

// Not reported — 5 parameter properties is constructor injection.
constructor(
  private readonly a: A,
  private readonly b: B,
  private readonly c: C,
  private readonly d: D,
  private readonly e: E
) {}

// Still reported — plain parameters.
constructor(a: A, b: B, c: C, d: D, e: E) {}

// Still reported — mixing injection with plain parameters is not injection.
constructor(private readonly a: A, b: B, c: C, d: D, e: E) {}

// Still reported — every non-constructor function is checked as before.
function build(a: A, b: B, c: C, d: D): void {}

NestJS constructor injection is the mandated pattern for every service in the fleet and routinely takes more than three dependencies, so a limit of 3 was wrong fleet-wide rather than wrong in one repo. The limit itself is unchanged at 3 — raising it would have silenced the rule everywhere, which is the opposite of what was wanted. A parameter property is precisely a constructor parameter carrying an accessibility modifier, readonly, or override, which the parser reports as a TSParameterProperty node, so that node type is the whole test.

The exemption cannot be expressed in configuration: neither max-params nor @typescript-eslint/max-params has an option for it (both take only max, maximum and countVoidThis), and ESLint config selects by file, never by node. lib/plugin.js therefore wraps the base rule's listeners and drops the exempt node — the same technique @typescript-eslint/max-params uses to wrap the core rule — so the severity, the limit, the options schema, the set of visited node types and the report messages are all unchanged.

On the flat path core max-params is off and @petbee/max-params carries the rule. The legacy eslintrc export (@petbee/eslint-config/legacy, ESLint < 9) keeps plain max-params, because that path cannot load a plugin this package does not publish separately.

Upgrading: if you have // eslint-disable-next-line max-params anywhere, rename it to // eslint-disable-next-line @petbee/max-params. Left alone it reports twice — once as an unused disable directive, once as the rule it no longer suppresses. Both are warnings, so nothing fails. Constructor-injection sites can drop the comment entirely.

npm run test:max-params asserts all of the above against a real fixture, so the exemption cannot go inert the way the five rule files did in v4.

@petbee/no-unused-vars

@typescript-eslint/no-unused-vars plus a single exemption: a value that is read only through typeof is not reported when it merges with an exported type alias or interface of the same name. That is the Prisma include/select idiom in the fleet's *.interfaces.ts files:

// Not reported — the value exists so `typeof` can read a checked args object, and the exported
// type of the same name is how the rest of the codebase reaches it.
const PetsFull = { include: { petFamilies: true } } satisfies Prisma.PetsDefaultArgs
export type PetsFull = Prisma.PetsGetPayload<typeof PetsFull>

// Still reported (usedOnlyAsType) — no type of the same name is exported.
const PetsWithOwner = { include: { owner: true } } satisfies Prisma.PetsDefaultArgs
export type PetsWithOwnerPayload = Prisma.PetsGetPayload<typeof PetsWithOwner>

// Still reported (unusedVar) — the same-name type is exported, but nothing reads the value.
const Orphan = { include: { owner: true } } satisfies Prisma.PetsDefaultArgs
export type Orphan = string

// Still reported (usedOnlyAsType) — `TypeHalf` in the second alias names the exported string type,
// not the value. Upstream counts it as a type-position use of the merged binding, but no `typeof`
// ever reads the value, so it is dead.
const TypeHalf = { include: { owner: true } } satisfies Prisma.PetsDefaultArgs
export type TypeHalf = string
export type UsesTypeHalf = TypeHalf

Up to typescript-eslint 8.71.0 the exported type counted as an export of the merged value, so the first case was never reported. 8.71.1 (typescript-eslint#12892, fixing #10658) stopped crediting the type's export to the value, and every such value became 'X' is assigned a value but only used as a type at error — 46 across five services (20 of them in petbee-api-v2) the moment their lockfiles float to 8.71.1. Upstream treats that as a bug fix, not a regression, so no upstream release will undo it, and the rule has no option for it: its options select by name pattern or by kind of binding, never by whether the binding merges with an exported type.

The exemption is deliberately narrow. A report is dropped only when all four hold:

  • it is usedOnlyAsType (the binding is referenced, in type positions);
  • the variable has a const / let / var definition;
  • the same variable has a type or interface definition that is itself exported;
  • at least one reference is a typeof X type query, so something actually reads the value.

Upstream's usedOnlyAsType also counts a plain type reference such as let y: X. On a merged binding that reference resolves to the type half, which is why the last condition exists. These are all still reported, exactly as upstream reports them:

  • a value that is never referenced;
  • a value used only as a type with no exported type of the same name;
  • a value merged with a type that is not exported;
  • a merged value that no typeof reads.

The rule wraps the upstream rule's context.report rather than reimplementing it, so the options schema, the default options, the messages, the fixes and the suggestions are identical. Against a typescript-eslint older than 8.71.1 the exemption never fires, because the upstream rule never makes that report.

On the flat path @typescript-eslint/no-unused-vars is off and @petbee/no-unused-vars carries the rule at the same severity with the same options. The legacy eslintrc export keeps @typescript-eslint/no-unused-vars, for the same reason it keeps plain max-params.

Upgrading:

  • If you have // eslint-disable-next-line @typescript-eslint/no-unused-vars anywhere, rename it to // eslint-disable-next-line @petbee/no-unused-vars. Unlike max-params, this one can fail lint: left alone the directive no longer suppresses anything, so it is reported as an unused directive (a warning) and the binding it covered is reported by @petbee/no-unused-vars at error.
  • If your eslint.config.* sets '@typescript-eslint/no-unused-vars', set '@petbee/no-unused-vars' instead. Left alone, the old entry does not do what it says:
    • 'off' or 'warn' (to relax the rule) is silently ignored. It configures a rule this package no longer runs, and @petbee/no-unused-vars keeps reporting at error with this package's options. With 'warn' the upstream rule also comes back on, so each binding is reported twice: once as a warning and once as an error.
    • 'error' with custom options turns the upstream rule back on next to this one. Every unused binding is reported twice, and the merged-declaration exemption is undone.

npm run test:no-unused-vars asserts the wiring, the exact set of reports on an eight-case fixture, and — so the exemption cannot pass vacuously — that the upstream rule with the same options does report the two exempt cases on the typescript-eslint this repository installs.

References