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

dead-dep

v1.0.0

Published

Detect unused dependencies and aging TODOs in a project

Readme

dead-dep

A CLI tool that detects unused dependencies and aging TODO comments in a TypeScript/JavaScript project.

Usage

node dist/index.js [path]            # analyse a project (defaults to current directory)
node dist/index.js . --no-deps       # skip dependency check
node dist/index.js . --no-todos      # skip TODO scan
node dist/index.js . --min-age 30    # only show TODOs older than 30 days
node dist/index.js . --json          # machine-readable output

The tool exits with code 1 if any findings are reported, 0 if clean — so it can block a CI pipeline automatically.

Building and testing

npm run build    # compile TypeScript → dist/
npm test         # run the test suite (or use the ddt alias if configured)

How it works

The tool is made up of eight focused modules that snap together in index.ts.

src/normalize.ts — Clean up import strings

Every import string found in source code passes through here before being compared against package.json. The function returns the bare package name, or null if the import should be ignored entirely.

'./utils'           → null   (relative — not a package)
'fs'                → null   (Node.js built-in)
'node:path'         → null   (Node.js built-in, prefixed form)
'lodash/fp'         → 'lodash'          (strip subpath)
'@org/pkg/deep/sub' → '@org/pkg'        (strip subpath from scoped package)

A Set of all Node built-in names is built once at startup from Node's own builtinModules list so the check is fast.


src/parsePackage.ts — Read package.json

Reads and parses package.json from the target directory and returns three Set<string> values: dependencies, devDependencies, and all (the union of both).

Two defensive measures:

  • JSON.parse is wrapped in a try/catch so a malformed file produces a friendly error instead of a raw stack trace.
  • A safeKeys() helper checks that dependencies/devDependencies are actually objects before calling Object.keys() on them — guarding against unusual (but valid) package.json shapes.

src/findFiles.ts — Discover source files

Uses the glob library to recursively find all .ts, .tsx, .js, and .jsx files under the target directory. Common directories that should never be scanned (node_modules, dist, build, coverage, .git) are excluded automatically.


src/extractImports.ts — Find what each file imports

Uses ts-morph to parse source files into an AST (Abstract Syntax Tree) — a structured representation of the code the same way a compiler sees it. This is more reliable than regex because it understands the language.

Three forms of import are detected:

| Form | Example | |---|---| | Static import | import lodash from 'lodash' | | CommonJS require | const x = require('lodash') | | Dynamic import | const x = await import('lodash') | | Re-export | export { foo } from 'lodash' |

Each specifier is passed through normalizeSpecifier before being added to the result set.


src/analyzeDeps.ts — Compare the two lists

Takes the set of declared packages (from package.json) and the set of used packages (from source files) and produces two lists:

  • Unused — declared but never imported
  • Missing — imported but not declared

Some packages are legitimately never imported directly — build tools, test runners, linters, type packages. These are filtered out before reporting to avoid false positives:

// Never imported directly, but genuinely used:
'typescript', 'ts-node', 'jest', 'eslint', 'prettier', 'webpack', '@types/*', ...

src/scanTodos.ts — Find TODO comments

Reads each source file line by line and tests each line against a regular expression that matches TODO-style comments in any comment syntax:

// TODO: fix this
/* FIXME: urgent */
/** TODO: add docs */
 * HACK: workaround   ← line inside a JSDoc block

Tags detected: TODO, FIXME, HACK, XXX. The tag and description text are captured separately so they can be displayed and coloured independently.


src/blameCache.ts — Git blame with caching

For each TODO found, this module asks git who wrote that line and when. It calls git blame --line-porcelain which outputs detailed per-line author and timestamp data, then parses that output to extract the author name and compute how many days ago the commit was made.

Caching: blame is run once per file and the results stored in a Map. If a file contains ten TODOs, git is only called once for that file and the per-line lookups hit the in-memory cache. The cache is cleared at the start of each run via clearCache().


src/report.ts — Format and print results

Renders the findings to the terminal using chalk for colour. TODOs are sorted oldest-first so the most stale items appear at the top. Age is colour-coded:

| Age | Colour | |---|---| | > 180 days | Red | | > 60 days | Yellow | | ≤ 60 days | Green |

When --json is passed, the entire output is serialised with JSON.stringify instead, suitable for piping into other tools.


src/index.ts — The entry point

Wires all the modules together using the commander library for CLI argument parsing. The key steps in order:

  1. Clear the blame cache (clearCache())
  2. Resolve the target path and discover all source files
  3. If --deps: parse package.json, extract imports, diff the two sets
  4. If --todos: scan for TODOs, pre-warm the blame cache for all unique files in parallel with Promise.all, then look up blame per TODO
  5. Print the report
  6. Exit with code 0 (clean) or 1 (findings found)

The parallel blame pre-warming in step 4 means all git subprocess calls happen concurrently rather than sequentially — important for projects with many TODO-containing files.

Project structure

src/
├── index.ts          CLI entry point — wires everything together
├── parsePackage.ts   Reads and validates package.json
├── findFiles.ts      Globs source files
├── extractImports.ts AST-based import extraction via ts-morph
├── normalize.ts      Cleans import specifiers
├── analyzeDeps.ts    Set diff: unused and missing deps
├── scanTodos.ts      Regex scan for TODO/FIXME/HACK/XXX
├── blameCache.ts     git blame with per-file caching
└── report.ts         Formats and prints results

tests/
├── normalize.test.ts
├── analyzeDeps.test.ts
├── parsePackage.test.ts
├── scanTodos.test.ts
├── extractImports.test.ts
├── blameCache.test.ts
└── report.test.ts