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

@dnbhq/lintstaged-config

v0.2.1

Published

Shared, configurable lint-staged task factory for DNBHQ projects.

Readme

@dnbhq/lintstaged-config

Shared, configurable lint-staged task factory for DNBHQ projects

Version PR Workflow

Why this package

lint-staged configuration is a flat object: a glob pattern mapped to one or more commands. There is no extends field like Biome or markdownlint-cli2 have, so sharing a baseline across repositories means either copy-pasting the object everywhere, or wrapping it in a small factory function that can be imported and customised. This package is that factory.

It does not install the linters it wires up. Most of the underlying tools (secretlint, @biomejs/biome, markdownlint-cli2, stylelint) are npm packages you add as dev dependencies yourself, so their versions stay under your project's control (the same reasoning @dnbhq/tsconfig uses for TypeScript, and @dnbhq/biome-config for Biome). A few others (yamllint, jsonnetfmt) are not npm packages at all — they're Python/Go binaries — so this package could not install them even if it wanted to. See Checking that the required tools are installed for the doctor command that checks for them instead.

Installation

npm install --save-dev @dnbhq/lintstaged-config lint-staged

Then add whichever of the default tools you plan to use (see Default tasks) as dev dependencies, for example:

npm install --save-dev secretlint @biomejs/biome markdownlint-cli2

Quick start (config file)

Create lint-staged.config.ts in the repository root:

import { createLintStagedConfig } from '@dnbhq/lintstaged-config';
import type { Configuration } from 'lint-staged';

const config: Configuration = createLintStagedConfig();

export default config;

Wire it into a git hook with Husky:

npx husky init
echo "npx lint-staged" > .husky/pre-commit

Running .ts config files directly requires Node.js 22.6+ with --experimental-strip-types, or Node.js 23.6+ where it is unflagged — see TypeScript config file support if your Node version is older.

Quick start (package.json)

lint-staged's package.json field only accepts a plain JSON object — it cannot require() or import a function, so createLintStagedConfig() cannot be referenced from it directly. Two options if a standalone config file is not what you want:

Pipe the resolved default config into lint-staged via stdin. lint-staged reads JSON config from stdin when given --config -, and this package's CLI prints its default configuration as single-line JSON on stdout:

{
  "scripts": {
    "lint-staged": "lintstaged-config print | lint-staged --config -"
  }
}

This only covers the built-in defaults with no customisation — see the caveat in Per-task configuration options. Anything beyond flipping which tasks are enabled needs the config-file approach above.

Or copy the printed JSON once into the lint-staged key of package.json and edit it by hand from there:

npx lintstaged-config print

This gives you a static starting point rather than a live extension of the shared config — you will not pick up future changes to the defaults until you re-run and re-paste. Prefer the config-file approach in Quick start (config file) whenever you expect the defaults to evolve.

Default tasks

createLintStagedConfig() builds its result from eight named tasks. Three are enabled out of the box because they only depend on npm-installable tools; the rest are opt-in.

| Task | Enabled by default | Glob | Command(s) | Requires | | --- | --- | --- | --- | --- | | secrets | Yes | * | secretlint --no-glob | secretlint | | javascript | Yes | *.{js,cjs,mjs,jsx,ts,tsx,cts,mts,json,jsonc} | biome check --write --no-errors-on-unmatched | @biomejs/biome | | markdown | Yes | !(CHANGELOG)**/*.{md,markdown,mdx} | markdownlint-cli2 --fix | markdownlint-cli2 | | styles | No | *.{css,scss} | stylelint --fix | stylelint | | yaml | No | *.{yaml,yml} | yamllint | yamllint (not npm) | | images | No | *.{png,jpeg,jpg,gif,svg} | sharp-lint-staged | @dnbhq/sharp-lint-staged | | astro | No | *.astro | npx astro check --minimumFailingSeverity=error --minimumSeverity=error | astro | | jsonnet | No | *.jsonnet | jsonnetfmt --in-place | jsonnetfmt (not npm) |

styles, yaml, images, and astro are opt-in because they only apply to some projects. yaml and jsonnet are opt-in for an additional reason: yamllint and jsonnetfmt are not npm packages, so pulling them in as a default would fail silently for anyone without them on PATH.

Enabling an opt-in task

Set enabled: true on the task:

import { createLintStagedConfig } from '@dnbhq/lintstaged-config';
import type { Configuration } from 'lint-staged';

const config: Configuration = createLintStagedConfig({
  styles: { enabled: true },
  images: { enabled: true },
});

export default config;

Disabling a default task

Set enabled: false on the task. This removes its glob entry entirely, rather than replacing it with an empty command list:

import { createLintStagedConfig } from '@dnbhq/lintstaged-config';
import type { Configuration } from 'lint-staged';

const config: Configuration = createLintStagedConfig({
  markdown: { enabled: false },
});

export default config;

Overriding a task's glob or commands

Each task accepts glob and commands independently, so you can keep the default glob and swap the command, keep the command and swap the glob, or replace both:

import { createLintStagedConfig } from '@dnbhq/lintstaged-config';
import type { Configuration } from 'lint-staged';

const config: Configuration = createLintStagedConfig({
  javascript: {
    // Keep the default glob, run an extra Biome pass.
    commands: ['biome check --write --no-errors-on-unmatched', 'biome lint --write --no-errors-on-unmatched'],
  },
  markdown: {
    // Keep the default command, narrow the glob to docs/ only.
    glob: 'docs/**/*.md',
  },
});

export default config;

Adding entries with no built-in task

Pass overrides for anything this package does not model as a task — a completely custom glob, or a function-based entry like lint-staged's advanced JS config supports. overrides is applied last with a shallow { ...generated, ...overrides } spread:

import { createLintStagedConfig } from '@dnbhq/lintstaged-config';
import type { Configuration } from 'lint-staged';

const config: Configuration = createLintStagedConfig({
  overrides: {
    '.vscode/settings.json': () => 'node scripts/vscode/merge-vscode-config.ts --audit',
    '*.jsonnet': ['jsonnetfmt --in-place', 'git add'],
  },
});

export default config;

Because the spread happens last, an overrides key that reuses a default task's exact glob string fully replaces that task's commands — this is an alternative to the javascript: { commands: [...] } form above when you would rather not touch the task options at all. The two approaches produce the same result; overrides is there for entries a task option cannot express (functions, or globs no task owns).

Per-task configuration options

secrets, markdown, styles, and yaml accept a configPath (and secrets also an ignorePath) that gets appended as the tool's own config flag, instead of relying on the tool's automatic config discovery:

import { createLintStagedConfig } from '@dnbhq/lintstaged-config';
import type { Configuration } from 'lint-staged';

const config: Configuration = createLintStagedConfig({
  secrets: {
    configPath: 'config/.secretlintrc.json',
    ignorePath: 'config/.secretlintignore',
  },
  markdown: {
    configPath: 'config/.markdownlint-cli2.jsonc',
  },
  yaml: {
    enabled: true,
    configPath: 'config/yamllint.yaml',
  },
});

export default config;

Setting configPath and setting commands together is redundant: configPath only has an effect on the command the task builds internally, so if you also set commands your explicit command list wins and configPath is ignored.

Checking that the required tools are installed

Since this package does not install any of the underlying linters, it ships a doctor command that checks whether they are reachable — on PATH, or in the project's local node_modules/.bin — without running them:

npx lintstaged-config doctor
✔ secretlint (secrets)
✖ markdownlint-cli2 (markdown)
  npm install --save-dev markdownlint-cli2 (or @dnbhq/markdownlint-config)
✔ biome (javascript)
...

Restrict the check to specific tasks by passing their names:

npx lintstaged-config doctor javascript markdown

doctor exits with code 1 when any checked tool is missing, so it is safe to wire into CI as a setup-verification step:

{
  "scripts": {
    "postinstall": "lintstaged-config doctor secrets javascript markdown || true"
  }
}

(the || true keeps a missing optional tool from failing npm install; drop it if you want a hard failure instead).

Testing your lint-staged setup

"Testing" a lint-staged config means two different things, and this package supports both:

  1. Testing this package itself — if you are contributing to @dnbhq/lintstaged-config, npm test builds the package and runs its vitest suite (tests/*.spec.ts), which covers task enabling/disabling, glob/command overrides, the overrides merge, the doctor tool-detection logic, and the print CLI output.

  2. Testing a consuming project's lint-staged.config.ts — do this with lint-staged's own dry-run tooling rather than a unit test:

    # Stage a file that should be picked up, then run lint-staged without
    # letting it touch your working tree:
    git add some-file.ts
    npx lint-staged --debug --no-stash

    --debug prints which glob matched which staged files and which commands ran, and --no-stash skips lint-staged's usual git stash so you can inspect the result directly. Revert or re-stage afterwards as needed.

    To sanity-check the resolved configuration itself without running any tools, print it and read the JSON:

    npx lintstaged-config print
    # or, for a customised config file:
    node --experimental-strip-types -e "import('./lint-staged.config.ts').then(m => console.log(JSON.stringify(m.default, null, 2)))"

TypeScript config file support

lint-staged loads .ts config files by handing them to Node's own type-stripping, not by bundling ts-node/jiti/tsx. That means:

  • Node.js 22.6+ requires the file to be run with NODE_OPTIONS=--experimental-strip-types (or node --experimental-strip-types when invoking lint-staged directly).
  • Node.js 23.6+ has this unflagged, so lint-staged.config.ts works with no extra setup.
  • The config file itself must use only erasable TypeScript syntax — type annotations, interfaces, and import type are fine (that's all the examples in this README use), but enum, namespaces with runtime values, and parameter properties are not, because Node only strips types, it does not transpile.

If your project's Node version does not support this yet, use a .mjs config file instead and keep type-checking via a JSDoc annotation, per lint-staged's own TypeScript documentation:

/**
 * @type {import('lint-staged').Configuration}
 */
import { createLintStagedConfig } from '@dnbhq/lintstaged-config';

export default createLintStagedConfig();

Release

Dry run:

npm run release:dry

Release:

npm run release

Releases are handled by release-it, configured through @dnbhq/release-config, with changelog generation via @release-it/conventional-changelog. Commit messages should follow Conventional Commits. Publishing is handled by the Publish GitHub Actions workflow when a v*.*.* tag is pushed.

Notes

  • This package has no runtime dependencies. lint-staged is a peer dependency; the individual linters (secretlint, @biomejs/biome, markdownlint-cli2, stylelint, ...) are dependencies of your project, not of this package, so their versions stay under your control.
  • TASK_NAMES, TOOL_REQUIREMENTS, checkTools, and isToolAvailable are also exported from the package root for anyone scripting around doctor instead of shelling out to the CLI.
  • See examples/ for complete config files covering the default setup, enabling optional tasks, and combining disables with overrides.