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

@fohte/eslint-config

v0.4.1

Published

ESLint config for fohte

Readme

@fohte/eslint-config

Personal ESLint configuration package with TypeScript support.

Installation

npm install --save-dev @fohte/eslint-config

# Install peer dependencies
npm install --save-dev @eslint-community/eslint-plugin-eslint-comments @typescript-eslint/eslint-plugin @typescript-eslint/parser @vitest/eslint-plugin eslint eslint-config-prettier eslint-plugin-import-x eslint-plugin-simple-import-sort

# Optional: If using TypeScript
npm install --save-dev typescript

# Optional: If using the errorHandling option
npm install --save-dev @ninoseki/eslint-plugin-neverthrow
npm install neverthrow

# Optional: If using the tailwind option
npm install --save-dev eslint-plugin-tailwindcss
npm install tailwindcss

Usage

eslint.config.js:

import { config } from '@fohte/eslint-config'

// Basic (JS + TypeScript strict rules)
export default config()

// Alternatively, enable type-checked rules
// (strict-type-checked + strict-boolean-expressions):
// export default config({ typescript: { typeChecked: true } })

// Optionally, ban throw/try-catch and enforce neverthrow Result handling
// (requires typescript.typeChecked: true):
// export default config({
//   typescript: { typeChecked: true },
//   errorHandling: {},
// })

// Optionally, ban raw tracer.startSpan()/startActiveSpan() calls that skip context.with():
// export default config({ opentelemetry: { enabled: true } })

// Optionally, ban Tailwind CSS arbitrary values (e.g. `w-[600px]`),
// steering towards design tokens defined in `@theme` instead:
// export default config({
//   tailwind: { cssConfigPath: 'src/index.css' },
// })

Import policy

config() bans relative imports (./foo, ../foo) and the @/* alias via no-restricted-imports, and steers both towards a Node subpath import (#foo, declared under the imports field in package.json). The @/* alias only resolves at the TypeScript/bundler level, so a plain Node/tsx runtime that doesn't share that resolution step fails at runtime; # imports are resolved natively by Node's own module resolver, so this failure mode can't happen.

To opt out (e.g. for a package that doesn't use a src/ layout), override the rule in a trailing userConfigs argument passed to config():

export default config(
  {},
  {
    rules: {
      'no-restricted-imports': 'off',
    },
  },
)

errorHandling option

Requires typescript.typeChecked: true, because neverthrow/must-use-result needs type information to detect unused Result values.

When enabled, it applies two rules to all .ts{,x} files except test files:

  • no-restricted-syntax: bans throw and try/catch. Return a Result via err()/errAsync() instead, or use ResultAsync.fromPromise() to interop with a throwing API without a local throw. If an external SDK's throw-based contract genuinely can't be wrapped that way, add an eslint-disable-next-line comment explaining why:

    // eslint-disable-next-line no-restricted-syntax -- interops with an external SDK's throw-based contract
    try {
      return externalSdkCall()
    } catch (error) {
      return err(error)
    }
  • neverthrow/must-use-result: bans discarding a neverthrow Result/ResultAsync without handling it.

opentelemetry option

When enabled, no-restricted-syntax bans direct calls to tracer.startSpan()/tracer.startActiveSpan() on all .ts{,x} files, since both can silently produce a span that fails to parent child spans created during its execution:

  • startSpan() never enters the active context on its own — a child span (e.g. an HTTP call fired during this span) won't be nested under it unless the caller explicitly wraps the surrounding code in context.with(trace.setSpan(context.active(), span), ...).
  • startActiveSpan()'s callback runs inside the active context automatically, but only for the callback's own duration — storing the span to end() it later (e.g. start and end split across separate callbacks) drops it from the active context before that later code runs.

This surfaces only as a mis-parented span in a trace backend, not as a runtime error. If neither pattern fits, add an eslint-disable-next-line comment explaining why.

When combined with errorHandling, both options configure no-restricted-syntax — ESLint's flat config fully replaces a rule's settings (rather than merging them) when two config objects set the same rule for the same file, so the startSpan/startActiveSpan selectors are merged into errorHandling's no-restricted-syntax entry instead of their own config. This means the opentelemetry ban then shares errorHandling's exemption for test files too.

tailwind option

cssConfigPath (the path to the CSS file where Tailwind's @theme tokens are defined) is required; files defaults to all .ts{,x} files, but can be narrowed (e.g. a single package in a monorepo):

export default config({
  tailwind: {
    files: ['web/**/*.ts', 'web/**/*.tsx'], // optional, defaults to all .ts{,x} files
    cssConfigPath: 'web/src/index.css',
  },
})

When enabled, it applies two rules to the given files (except test files):

  • tailwindcss/no-arbitrary-value: bans arbitrary values (e.g. w-[600px]) in className/class attributes and classname functions (clsx, cn, etc). Add a token to @theme in your CSS config instead:

    @theme {
      --width-panel: 600px;
    }
    - <div className="w-[600px]" />
    + <div className="w-panel" />
  • no-restricted-syntax: no-arbitrary-value only checks class attributes/functions, so a bracket value stashed in a bare string constant (then interpolated into className) would otherwise slip through undetected. This rule catches that case too.

Like opentelemetry, this shares its no-restricted-syntax entry with errorHandling (and opentelemetry) rather than silently overriding it, so all three bans keep applying together within tailwind.files. Since tailwind.files can be narrower than errorHandling/opentelemetry's default (all .ts{,x} files), the merge happens at tailwind.files's scope: files outside it keep only the errorHandling/opentelemetry bans, and files inside it get the full union.

Built-in rules

In addition to the upstream presets, this config ships a local plugin (fohte) for test files. Rules are enabled as error by default; override them in eslint.config.js if needed (e.g. 'fohte/no-inline-object-in-expect': 'off').

  • fohte/no-inline-object-in-expect: flags expect(<object/array literal>).toEqual(...) (and toStrictEqual / toMatchObject, including await … .resolves / .rejects / .not chains, and as const / satisfies / ! wrapped literals). Also flags the same literal aliased through a variable declared right before the expect() call. Pass the value under test directly, or split the assertion into multiple expect() calls.

    // bad
    expect({ result, calls: spy.mock.calls.length }).toEqual({
      result: 'ok',
      calls: 0,
    })
    
    // bad: aliasing the literal through a variable doesn't escape the rule
    const actual = { result, calls: spy.mock.calls.length }
    expect(actual).toEqual({ result: 'ok', calls: 0 })
    
    // good
    expect(result).toBe('ok')
    expect(spy).not.toHaveBeenCalled()

Development

Setup

# Install dependencies
npm install

# Build TypeScript
npm run build

# Watch mode
npm run watch

Scripts

  • npm run build - Compile TypeScript files
  • npm run watch - Watch mode for development
  • npm run lint - Run ESLint on source files
  • npm run test - Run build and lint

Project Structure

src/
├── index.ts           # Main export
├── main.ts            # Base ESLint configuration
├── typescript.ts      # TypeScript-specific configuration
├── error-handling.ts  # errorHandling option (throw/try-catch ban, neverthrow enforcement)
├── opentelemetry.ts   # opentelemetry option (startSpan/startActiveSpan ban)
├── tailwind.ts        # tailwind option (Tailwind arbitrary-value ban)
└── types/             # Type definitions for untyped packages

Release Process

This project uses release-please for automated releases.

1. Create a feature branch and make changes

  • Create a new branch from master
  • Make your changes (commit messages don't need to follow any specific format)

2. Create a PR and merge to master

  • Push your branch and create a PR
  • PR title must follow Conventional Commits:
    • fix: for bug fixes (patch release)
    • feat: for new features (minor release)
    • feat!: or fix!: for breaking changes (major release)
  • After review, merge the PR (squash merge only)

3. Automated release process

When changes are merged to master, release-please automatically:

  • Creates/updates a Release PR
  • Updates version in package.json
  • Updates CHANGELOG.md
  • When the Release PR is merged:
    • Creates GitHub release and git tag
    • Publishes to npm

Pre-commit Hooks

This project uses pre-commit hooks to ensure code quality. The hooks will automatically run when you commit changes.