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

@zyplux/eslint-config

v0.7.0

Published

Shared ESLint flat config and custom rules

Readme

@zyplux/eslint-config

Shared ESLint flat config and custom rules. Ships TypeScript source — consumed directly under Bun.

Install

bun add -D @zyplux/eslint-config eslint typescript

Use

eslint.config.ts:

import { zyplux } from '@zyplux/eslint-config';

export default zyplux({ tsconfigRootDir: import.meta.dirname });

One call is the whole config: ESLint recommended, type-checked typescript-eslint, unicorn, perfectionist (natural sorting), the custom @zyplux rules, vitest (recommended, on *.{test,spec}.{ts,tsx}), and prettier last. React is off until you ask for it.

React renderers

react takes a renderer → globs map. Only listed globs receive React rules, so non-React packages match nothing.

export default zyplux({
  react: {
    dom: ['apps/web/**/*.{ts,tsx}'], // full eslint-plugin-react (DOM)
    opentui: ['apps/tui/**/*.{ts,tsx}'], // non-DOM renderer
  },
  reactVersion: '19.0',
  tsconfigRootDir: import.meta.dirname,
});
  • dom — DOM rules on (eslint-plugin-react recommended + jsx-runtime + React Hooks).
  • opentui / ink / r3f / react-pdf — same, but react/no-unknown-property off, since tsc already validates each renderer's host props through JSX.IntrinsicElements.
  • Shorthand: react: true{ dom: ['**/src/**/*.{ts,tsx}'] }.

Monorepos

One zyplux() call with a renderer map covers a whole repo: set tsconfigRootDir once and projectService resolves each package's nearest tsconfig.json. When packages need genuinely different baselines, scope whole presets with defineConfig and share options through withDefaults:

import { defineConfig } from 'eslint/config';
import { zyplux } from '@zyplux/eslint-config';

const tv = zyplux.withDefaults({ tsconfigRootDir: import.meta.dirname });

export default defineConfig(
  { files: ['packages/api/**'], extends: [tv()] },
  { files: ['packages/web/**'], extends: [tv({ react: true })] },
);

Options

| Option | Default | Description | | ----------------- | --------------- | ----------------------------------------------------------------------- | | react | false | true, or a renderer → globs map (see above) | | tanstack | false | Enforce kebab-case filenames under routes/ | | tsconfigRootDir | process.cwd() | Root for typed linting (projectService); pin to import.meta.dirname | | reactVersion | 'detect' | React version; pin (e.g. '19.0') where workspace detection fails | | ignores | [] | Extra ignore globs appended to the defaults |

Deprecated, mapped onto react for back-compat: reactFilesreact: { dom }, nonDomReactFilesreact: { opentui }.

What's always on

  • Custom @zyplux rules: contracts-only-schemas (on **/src/contracts.ts only), no-anonymous-param-type, no-identity-cast, no-return-array-push, no-schemas-outside-contracts (everywhere except the contracts module), no-stray-pascal-const, no-type-annotations, no-type-predicate, no-unvalidated-json, no-zod-custom, prefer-arrow-functions, prefer-destructured-params, test-seam-only-imports (on **/stories/*.test.{ts,tsx} — a story test imports only describe, expect, and test from #fixtures; everything else reaches it as a fixture on the test context), type-over-interface.
  • Type-checked TypeScript (the full typescript-eslint all preset), arrow-only functions, type over interface (except declaration-merging interfaces inside declare module/declare global blocks), no type assertions.
  • No parent-relative (../) imports — route through a tsconfig paths alias (@/foo).
  • unicorn + perfectionist (natural sorting); prettier last, so formatting rules are off.
  • Your .gitignore is honored — patterns from <tsconfigRootDir>/.gitignore become ESLint ignores (flat config doesn't read .gitignore on its own).

zod at the boundary

consistent-type-assertions: 'never' plus no-zod-custom, no-type-predicate, and no-unvalidated-json steer every deserialization boundary (JSON.parse, await response.json(), event.data, JSONL) toward a zod schema that returns the typed value rather than parse(x) as T. no-unvalidated-json is the direct enforcer: a JSON.parse(…) (matched syntactically) or a .json() returning any/Promise<any> (matched by type, so a typed domain .json() is exempt) must flow into a schema .parse()/.safeParse(). Annotate hand-written schemas as z.ZodType<T> to keep their declared type. Blind spot: that annotation only rejects schemas producing values outside T — a schema missing a union member still type-checks (a narrower output is assignable to the wider T), so a discriminated union can silently drop a case. Keep wire schemas exhaustive by hand.

Tweaking rules

Flat config is last-wins — append an override after the preset:

export default [...zyplux({ tsconfigRootDir: import.meta.dirname }), { rules: { 'unicorn/no-null': 'off' } }];