GitHub Packages · MIT · Node ≥ 18

dead-deps-gauge

Find dependencies you declare but never use, and imports that are missing from package.json.

Find dependencies declared in package.json but never imported, and imports missing from package.json (or only in devDependencies). Understands ESM/CJS/TS, dynamic import, scripts, bins, config files and @types. Text/JSON/SARIF/Markdown, allowlist with expiry, GitHub Action. Zero deps.

  1. 1 Point the @mrlouiswright scope at GitHub Packages in .npmrc

    @mrlouiswright:registry=https://npm.pkg.github.com
  2. 2 Install

    npm i -D @mrlouiswright/dead-deps-gauge
Installing from GitHub Packages needs a token

Published to GitHub Packages, not npmjs.com. GitHub Packages needs a GitHub token with read:packages to install, even for public packages: add //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN} to .npmrc or run npm login --registry=https://npm.pkg.github.com.

~/project — zsh
$ npx dead-deps-gaugedead-deps-gauge — fixture-app@1.0.06 file(s) scanned; 6 dependencies, 9 devDependencies HIGH     src/index.js:5 "debug" is used by production code (src/index.js:5) but is only a devDependency [dev-dependency-in-production]         ↳ fix: Move it to "dependencies" (npm install debug --save-prod), or mark the file as dev-only with "devPaths".HIGH     src/index.js:7 "dotenv" is used by src/index.js:7 but is not in package.json [missing-dependency]         ↳ fix: npm install dotenv  (it only works today through hoisting or a transitive install)MEDIUM   package.json "left-pad" is in dependencies but nothing imports or runs it [unused-dependency]         ↳ fix: npm uninstall left-pad  (or allowlist it with a reason if it is loaded indirectly)LOW      package.json "fs" has the same name as the Node.js built-in "fs"; imports resolve to the built-in [builtin-shadowed]         ↳ fix: Remove it and use "node:fs".
Sample output from the dead-deps-gauge README.

Why it exists

dead-deps-gauge reads every JavaScript and TypeScript file in a package, works out which packages each one imports, and compares that with package.json. Code that imports something not in package.json (and only works through hoisting), production code that needs a package listed only under devDependencies, and dependencies nothing uses all fail the build, each with the file and line and the exact command to fix it.

CLI, library and GitHub Action. Node.js 18+, zero runtime dependencies. By Louis Wright.

Features

Import extraction

without a parser: import/export … from, import type, side-effect imports, require, require.resolve, dynamic import(), TypeScript import x = require(), jest.mock/vi.mock, and /// <reference types> headers. A lexer skips comments, and template literals and strings that hold code samples.

More than imports

binaries run from package.json scripts (resolved through each dependency's bin, or a table of well-known tools), ESLint/Babel/Prettier/Jest/commitlint/stylelint/TypeScript config files and their short plugin names, tsconfig types/extends/importHelpers, @types/* paired with the package they type, and tools implied by config files (tsconfig.json → typescript, .husky/ → husky…).

Production vs development

tests, fixtures, scripts, examples, stories and *.config.* files are dev-only. If package.json has "files", anything that isn't published is dev-only too. install/postinstall scripts of published packages count as production.

Monorepos

nested packages are skipped. A workspace root's devDependencies satisfy dev imports inside packages, but production imports must still be declared locally, which catches "works only because it's hoisted" bugs.

Output

text, JSON, SARIF 2.1.0 and Markdown; --fail-on; exit codes 0/1/2; allowlist entries with expiry dates.

Usage

# scan the current package

 npx dead-deps-gauge

# Example

 npx dead-deps-gauge packages/api --fail-on medium

# Example

 npx dead-deps-gauge --ignore "generated/**" --ignore-deps "@company/runtime-*"

# Example

 npx dead-deps-gauge -f json > deps-report.json

CI / GitHub Action

permissions:
  contents: read
  security-events: write   # only needed for the SARIF upload
jobs:
  dead-deps-gauge:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
        with: { persist-credentials: false }
      - uses: MrLouisWright/dead-deps-gauge@v1
        with:
          fail-on: high
          sarif: dead-deps-gauge.sarif
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: dead-deps-gauge.sarif }

Options & flags

FlagDescription
Scan
--ignore <globs>Paths to skip (comma-separated or repeated), e.g. "generated/**"
--ignore-deps <globs>Dependencies never to report, e.g. "@company/runtime-*"
--dev-paths <globs>Extra globs treated as dev-only (tests, tooling); defaults include test/, scripts/, *.test.*, *.config.*
Common
-c, --config <file>JSON config (default: .dead-deps-gauge.json or "deadDepsGauge" in package.json)
-f, --format <fmt>text (default) | json | sarif | markdown
--json <file>Also write a JSON report
--sarif <file>Also write SARIF 2.1.0
--markdown <file>Also write Markdown (e.g. for a job summary)
--fail-on <sev>critical | high | medium | low | info | none (default: high)
--list-rulesPrint rules and default severities
--no-fixesHide fix suggestions in text output
--no-colorDisable colours

Full reference, config file format and API in the README.

Support this project

Support open-source security tools

I build free, open-source tools that help developers ship safer websites. Every coffee helps keep them maintained, documented and free for everyone.

Buy me a coffee (opens Stripe in a new tab)

Secure checkout by Stripe

Portrait of Louis Wright

About the author

Louis Wright

Cyber security researcher and software engineer based in the UK.

More tools by Louis Wright

58 tools

Sentinels security scanners & CI gates · 20