deps-finder
v1.0.1
Published
A TypeScript dependency analyzer that detects unused and misplaced dependencies
Maintainers
Readme
deps-finder
Catch unused, misplaced, and orphan-peer dependencies in TypeScript projects.
deps-finder reads your package.json, walks the project's source files, and tells you which declared packages no source file actually imports — and which packages your code does import that live in the wrong section. It runs entirely on your machine, never phones home, and treats peerDependencies as a consumer contract by default (since real peers like typescript are intentionally never imported by the library itself). Opt in with --check-peer when you want orphan-peer detection.
한국어 · English
Table of contents
Features
- Detects unused dependencies — declared in
package.json, never imported. - Detects misplaced dependencies — used in source but living in
devDependencies. - Detects orphan peers — declared as
peerDependenciesbut never imported (opt-in via--check-peer). - Reports type-only imports separately so they don't pollute the unused list.
- Reads JS and TS sources, Vue, Svelte and Astro components, and CSS, SCSS and Less stylesheets.
- Honours
.gitignoreand auto-detects build output directories (dist,build, etc.) at the project root, and excludes them. - Checks one package per run. In a monorepo, run it inside each workspace package.
- Outputs colorized text or machine-readable JSON.
- Friendly errors and warnings — actionable messages when files are missing, JSON is malformed, or a flag is given without its required value.
Install
npm install -D deps-finderOr run once without installing:
npx deps-finderRequires Node.js ≥ 22.12.
Quickstart
# from the project root (where package.json lives)
deps-finder
# JSON output for CI / scripts
deps-finder --json
# also check peerDependencies and devDependencies
deps-finder --all
# monorepo: one run per workspace package
deps-finder apps/webExpected output (truncated):
⚠ Unused Dependencies:
• moment
⚠ Misplaced Dependencies:
• zod (used in 1 file)
└─ src/api/schema.ts:5Options
deps-finder [options] [<root>]<root> is the project directory that holds package.json (default: the current directory).
If
--helpand this table disagree,--helpwins — please open an issue. The source of truth issrc/cli/command.ts.
| Option | Alias | Description |
|--------|-------|-------------|
| --text | -t | Output as text (default) |
| --json | -j | Output as JSON |
| --all | -a | Also report unused devDependencies and peerDependencies (peers only under unusedPeer; misplaced checks stay on) |
| --check-peer | -p | Also check peerDependencies (off by default; on with --all) — see peerDependencies note |
| --ignore <pkgs> | -i | Ignore packages (comma-separated, repeatable, --ignore=a,b) |
| --exclude <patterns> | -e | Exclude files/dirs by .gitignore-style pattern; ./src/x and absolute paths under the project are anchored at its root (comma-separated, repeatable) |
| --no-auto-detect | — | Disable automatic build directory detection |
| --version | -v | Print the version |
| --help | -h | Show help message |
Unknown flags and flags missing their value are errors, not warnings.
Exit codes
| Code | Meaning |
|------|---------|
| 0 | No issues |
| 1 | Issues found |
| 2 | The run failed (bad flags, missing or malformed package.json, dependencies not installed, or installed with Yarn Plug'n'Play, when unused must be decided, a report that could not be written) |
How it works
package.json ──┐
├─→ declared deps ──┐
walk project ──┤ ├─→ diff ──→ unused / unusedPeer / misplaced / typeOnly
└─→ parsed imports ─┘- Read
package.jsonto get declareddependencies,optionalDependencies,peerDependencies, anddevDependencies.optionalDependenciesship to consumers asdependenciesdo, so they are treated alike: checked for unused by default and reported under Unused Dependencies, never misplaced (also whendevDependencieslists them), and reported type-only when only types use them. A platform-only package such asfseventsthat nothing imports is reported unused; pass it to--ignore. - Walk the project for
.ts,.tsx,.mts,.cts,.js,.jsx,.mjs,.cjs,.vue,.svelte,.astro,.css,.pcss,.postcss,.scss,.less, hand-written declaration files (.d.ts,.d.mts,.d.cts) andtsconfig*.json, hidden files and directories included. An ignored directory is never listed, so a large gitignored cache costs nothing; a directory that cannot be listed is a stderr warning, as an unreadable source file is. The walk skips.git/andnode_modules/, anything the project's.gitignorefiles ignore (the root one, nested ones, and those in parent directories up to the git repository's top, plus.git/info/exclude; git's rules, always matched case-sensitively, unlike git withcore.ignorecaseon, the macOS and Windows default), build outputs at each layout root (see below):dist/,build/,out/andcoverage/, and the auto-detected output dirs at the project root (outDiranddeclarationDirof every roottsconfig*.jsonthrough itsextendschain, a relative value resolved from the file that sets it and${configDir}from the extending one;--outDir,--out-dirand--outdirin scripts, with=or a space, and-dofbabelandswc;*-dist-style names). An output dir that is the project root itself, or outside it, excludes nothing; symlinks in either path are resolved first. A project directory that its repository ignores is still scanned, with only its own.gitignorefiles. Symlinked files are followed; symlinked directories are not. A project without a.gitignoreat its root (or above it in the repository) also skips common framework and cache dirs at each layout root (.next/,.turbo/,.cache/,storybook-static/and the like) and.venv/,.gradle/,.claude/,.idea/and.vscode/at any depth. A subdirectory with apackage.jsonis a separate package when it is a workspace member (matched by the rootpackage.jsonworkspaces, as an array or as{"packages": [...]}, or by thepackageslist of a rootpnpm-workspace.yaml,!negations included: inworkspacesa later pattern that an earlier!pattern itself matches (such aspackages/bafter!packages/b) cancels that!pattern, as npm does, and inpnpm-workspace.yamla!pattern always wins, as pnpm does) or when it has its own lockfile (package-lock.json,npm-shrinkwrap.json,yarn.lock,pnpm-lock.yaml,bun.lock,bun.lockb) ornode_modules/. Its whole tree is left out of this run's checks and stderr names it. Its imports of packages that its ownpackage.jsondoes not list independencies,optionalDependenciesordevDependencies(a peer-only declaration installs nothing there) still mark the root's dependency as used, because Node falls back to the root install for them; they count as development use, so they never make a dependency misplaced or type-only. A malformedworkspacesorpnpm-workspace.yamlis reported on stderr, and the scan goes on without it. Any otherpackage.jsonstays part of this run, even one with anameand dependencies, such as an Nx-style lib that resolves from the root install. The project root and every scanned directory with apackage.jsonthat has aname, or with an Nxproject.json(one with a stringname, or atargetsor$schemakey), are layout roots; a file is matched against every layout root above it, not only the nearest. Apackage.jsonwithout aname, such as{"sideEffects": false}, is not a layout root. Each file is tagged development or production. Development files are tests, specs, stories, and test setup files; anything undertest/,tests/,__tests__/,__mocks__/,e2e/,cypress/,playwright/or.storybook/at any depth; dotfiles such as.eslintrc.jsat any depth; and, only at a layout root,*.config.*and*.preset.*files (webpack.config.prod.jsincluded), thescripts/directory and hidden directories such as.husky/or.github/, except a file that a scannedpackage.jsonnames inbin, which ships to users. Everything else is production, includingsrc/app.config.ts,src/scripts/,src/.generated/and a feature folder namedstories/. - Parse each file with oxc and collect
import,export … from,require(),import x = require(), and dynamicimport()with a string literal; resolve to package roots (e.g.lodash/fp→lodash), dropping a?queryor#fragmentsuffix (x/icon.svg?rawandx?url→x).require.resolve(),module.require(),import.meta.resolve()and a function made bycreateRequire(…)count asrequire()does:const req = createRequire(import.meta.url); req("x"),req.resolve("x")andcreateRequire(…)("x"), wherecreateRequireis imported frommoduleornode:moduleor read off an object (m.createRequire,process.getBuiltinModule("module").createRequire);require.resolve.paths()is not a load. A template literal without${}counts as a string literal. A#namespecifier is not a package: it resolves through theimportsfield of the nearest scannedpackage.jsonwith anameabove the file (Node uses the nearest one of any kind, so a nameless{"sideEffects": false}in between is skipped here) (an exact key, else the*pattern with the longest prefix, as Node picks), and every package that any of its targets names, under any condition, is used with the import's type and the file's context;./targets are local. A bare specifier resolves through thecompilerOptions.pathsandbaseUrlof the tsconfig files that compile the file (of the nearest directory above it that has any, as in step 4, only those whosefilesorinclude, lessexclude, cover the file, with no fallback; throughextends), astscdoes, after its?queryor#fragmentsuffix is dropped: the paths key that matches best tries its targets in order; a declaration target (.d.ts,.d.mts,.d.cts) that exists redirects only the types, so the specifier stays use of the package it names; a target that resolves throughnode_modules/below the tsconfig's directory, or a bare one whose first segment names nothing in the base directory (such aslodash-es), is use of that package; any other is local when a file resolves there (with a.ts,.tsx,.d.ts,.mts,.cts,.js,.jsx,.mjs,.cjsor.jsonextension or anindexfile, or, for a path written with.js,.jsx,.mjsor.cjs, the.ts,.tsx,.mtsor.ctssource that emits it). When a key matches but none of its targets resolves, the specifier is the package it names, andbaseUrlis not tried. When no key matches, a specifier that resolves to a file underbaseUrlis local; one that resolves undernode_modules/below the tsconfig's directory is that package. So"utils/*": ["src/utils/*"]makesutils/formatlocal, not use of a declaredutils. A local file that lies inside the package the specifier names, as installed in the nearestnode_modulesabove the importing file (a workspace package linked there, with an alias to its source such as"@ws/ui": ["../../packages/ui/src/index.ts"]), is use of that package. Targets resolve frombaseUrl, else from the tsconfig that setspaths. When oxc stops at a syntax error in a file, stderr names the file and the line of the error. What comes before the error partly counts: itsimportandexportstatements,import()calls and the type imports in its comments (JSDocimport("x")and@import,/// <reference types="x" />). Nothing else in that file counts: notrequire(),import.meta.resolve(),import x = require()ordeclare module "x"before the error, and nothing after it. A TypeScript grammar error, such as an initializer in an ambient context, leaves the parse whole, so everything in the file counts and nothing is printed: deps-finder is no type checker. Type positions count as type-only usage:import("x").Tandtypeof import("x"), JSDocimport("x")inside a{type}and@import { T } from "x"in/** */comments,/// <reference types="x" />,declare module "x"in a file with its ownimportorexport(in a file without one it declares an ambient module and counts for nothing), and every import in a declaration file. A TypeScript source (a declaration file included) or atsconfig*.jsonanywhere in the walk counts as development use oftypescript..js,.mjsand.cjsfiles are parsed with JSX allowed, and a.jsor.cjsfile withoutimportorexportthat does not parse as a module is parsed as CommonJS, as Node runs it, so a top-levelreturnis no error. A file is read as UTF-8, or as UTF-16 when it starts with that byte order mark. In a.vueor.sveltecomponent, each<script>block (<script setup>and Svelte's module script too) is parsed in the language of itslangattribute (ts,tsx,jsx, else JavaScript); in an.astrocomponent, the---frontmatter and each<script>tag are parsed as TypeScript. In a.vueor.sveltefile only top-level blocks count, as their compilers read them; in an.astrofile a<script>or<style>anywhere in the markup counts, as Astro processes it there. In a.vuefile every other top-level block, such as<docs>or<i18n>, is opaque text, and so is a tag in a{{ }}interpolation or an attribute value of the<template>; a<script>or<style>inside an HTML comment, a Vue<template>, or a Svelte element (<svelte:head>included) or{ }expression is markup, and one in Astro frontmatter is code. A self-closing<script src="…" />has no body. Line numbers point into the component. A component counts as runtime use ofvue,svelteorastro, which its compiled output imports. Stylesheets and component<style>blocks (langcss,postcss,scss,lessor none) are parsed with PostCSS: the quoted orurl()target of@import,@useand@forwardis a runtime import in the file's own context, and that of@plugin,@configand@reference, which the build runs or reads without emitting it, is development use, with webpack's~prefix dropped (~bootstrap/scss/x→bootstrap). Relative and absolute paths, URLs,data:andsass:modules are not packages, andurl()inside a declaration is an asset, not an import. A stylesheet that does not parse is a stderr warning, and its imports are not counted; a component<style>block that does not parse is warned about alone, and the component's other blocks still count. A file holding JSX is runtime use of its JSX runtime package under the automatic runtime (tsconfigjsxset toreact-jsx,react-jsxdev,preserveorreact-native, or not set at all): a/** @jsxImportSource x */pragma namesx, else the governingjsxImportSource, elsereact. Under"jsx": "react", the classic runtime, the factory is imported explicitly and nothing is added. Underpreserveorreact-nativewithout ajsxImportSource,tscalso keeps the factory import (jsxFactory, elseReact), so a JSX element counts as a value use of it. A/** @jsxRuntime classic */or/** @jsxRuntime automatic */pragma switches the file to that runtime, and under the classic runtime a/** @jsx f */pragma names the factoryf. An import counts as type-only when TypeScript erases it:import type, an import made only of inlinetypespecifiers, and, in a TypeScript source, an import none of whose bindings is used as a value. A type reference, a type literal, a type parameter, atypeoftype query, an interface, animplementsclause, a type-only export, a property, class member or enum key, or a label is no value use; a call, a JSX tag, a decorator,typeofin an expression,export defaultorexport { X }is. Scopes are not tracked here, so a parameter or local variable that reuses an imported name keeps the import. Only the production imports of adevDependencythat would be misplaced are checked for value use. Adependenciesentry imported as a value is never moved to Type-Only Imports this way: such a package is often a runtime peer of another dependency (graphqlfor@apollo/client). When the governing tsconfig setsverbatimModuleSyntax,preserveValueImportsorimportsNotUsedAsValuestopreserveorerror, onlyimport typeandexport typeare erased; withemitDecoratorMetadatano import is checked for value use, since the emitted metadata can name a type. A development file that callsdescribe,it,test,expect,beforeEachorafterEach(or a member of one, such asit.each) as a global, one it neither imports nor declares as a variable, function, parameter or class in a scope around the call, counts as development type use of@types/jest,@types/mochaand@types/jasmine; the names in comments and strings do not count. - Read every
tsconfig*.jsonthe walk finds at a layout root, the projects theirreferences[].pathname (a file, or a directory holdingtsconfig.json), and theextendschains of all of them (a root that another root extends, such astsconfig.base.json, is read only through that one), through relative paths and through packages installed innode_modulesat the extending file's directory or above. An extended or referenced file that cannot be found is a warning on stderr. In each chain the nearestcompilerOptions.typescounts each entry as development type use of that package (vitest/globals→vitest, so"types": ["jest"]intsconfig.spec.jsoncounts); anextendspackage such as@tsconfig/node20(or a relative path intonode_modules/@tsconfig/node20/) is development use; the nearestcompilerOptions.importHelpers: trueis production runtime use oftslib. A source file follows the tsconfig files of the nearest directory above it that has any forjsx,jsxImportSource,jsxFactory,verbatimModuleSyntax,preserveValueImports,importsNotUsedAsValuesandemitDecoratorMetadata: those whosefilesorinclude, lessexclude, cover it (paths are relative to the tsconfig that sets them, and a tsconfig with neither covers everything under its own directory), elsetsconfig.json, else all of them. Each tsconfig reads these options from its ownextendschain, and a file that several of them cover gets every JSX runtime they set and the elision that keeps the most imports. Anulloption clears the inherited one, as intsc, and an option with a value of the wrong type is ignored while the rest of the file is still used. - A declared
@types/xcounts as used whenxor any subpath of it is used in any of the ways above:@types/scope__namefor@scope/name,@types/nodefor any Node builtin (fsornode:fs, fromnode:module'sbuiltinModules) and for"types": ["node"],@types/bunforbunandbun:*. This alone never makes it misplaced or type-only. An@types/xwhosexis never used is still reported, even though TypeScript loads every@typespackage whentypesis not set. - Count the packages a project uses without importing them. None of these makes a package misplaced or type-only.
- Binaries run by scripts. The
scriptsof the rootpackage.jsonand of every scanned layout root, the git hooks in.husky/(its files without an extension) and lint-staged commands are split into commands on&&,||,;,|,&, parentheses, backticks and newlines outside quotes, soecho "a; eslint"runs onlyecho. LeadingNAME=valueassignments (NODE_OPTIONS='--a --b'too), shell keywords (if,then,else,do,{and the like) and runners (npx,npm exec,pnpm exec,pnpm dlx,yarn exec,yarn dlx,bunx,bun x,cross-env,env,dotenv … --) are skipped, and the next word is the command. Afternpx,npm exec,pnpm dlx,yarn dlx,bunxandbun xthat word also names a package, less any@version(npx @biomejs/biome,npx prettier@3).sh -c '…'andbash -c '…'run their quoted script. Afterpnpm,yarnorbun(with or withoutrun), a word that names a script of the samepackage.jsonis that script, not a binary;npm runandpnpm runrun only scripts. A command uses the declared package whose installedpackage.jsonlists it inbin(a stringbinis named after the package without its scope). Packages are looked up innode_modulesat the project root and above, as Node resolves them. A declared package that is not installed is matched by its own name and by its name without the scope. - Peers. A declared package that a used, installed dependency lists in
peerDependenciesorpeerDependenciesMeta, optional or not, is used, and so are the declared peers of that one in turn. A peer of a package that production code loads is never reported type-only, since production needs it installed. - Unused is decided only against an install; lockfiles are not read. When packages are declared but none is installed in a
node_modulesat or above the project, binaries and peers are unknown, so a run that would decide unused (one that checks at least one declared package, after--ignore) prints one error naming the project directory, prints no report, and exits2. Install the dependencies first. A run that checks nothing for unused, such as the default run of a project with onlydevDependencies, goes on as usual. - Tool configs. At each layout root, these files and
package.jsonkeys are read (JSON and YAML as data, JS and TS from their object literals without running them), with each tool's short-name convention. An rc file without an extension, such as.eslintrc, is read as JSON with comments, or else as YAML. In JS and TS, a call with one argument stands for that argument (getAbsolutePath('@storybook/addon-a11y')), and a condition for each value it can take (prod ? 'cssnano' : null,prod && 'cssnano'). Every object in the file counts, sooverridesand nested blocks do too. Layout roots here include Nx projects that hold only aproject.json.- ESLint (
.eslintrc,.eslintrc.json/.yaml/.yml/.js/.cjs,eslintConfig):parser,plugins(react→eslint-plugin-react,@scope→@scope/eslint-plugin,@scope/x→@scope/eslint-plugin-x),extends(airbnb/hooks→eslint-config-airbnb,@scope→@scope/eslint-config,plugin:x/y→ the pluginx), the plugin of a rule such asimport/no-cycle, andsettings["import/resolver"](typescript→eslint-import-resolver-typescript). A flateslint.config.*is read through its imports. - Babel (
.babelrc*,babel.config.*,babel):presetsandplugins(@babel/env→@babel/preset-env,macros→babel-plugin-macros,module:x→x,next/babel→next). - PostCSS (
postcss.config.*,.postcssrc*,postcss): the keys and entries ofplugins. - Jest (
jest.config.*,jest):preset,testEnvironment(jsdom→jest-environment-jsdom),transformvalues,setupFiles,setupFilesAfterEnv,snapshotSerializersandreporters. - Prettier (
.prettierrc*,prettier.config.*,prettier):plugins, or the whole config when it is a string naming a shared config. - Stylelint (
.stylelintrc*,stylelint.config.*,stylelint) and commitlint (.commitlintrc*,commitlint.config.*,commitlint):extendsandplugins. - Storybook
.storybook/main.*:addons,frameworkandcore.builder.serverless.yml/.ts:plugins. Nxproject.jsonandnx.json: eachexecutor(@nx/jest:jest→@nx/jest). lint-staged (.lintstagedrc*,lint-staged.config.*,lint-staged): its commands, read as scripts above. tsconfig:compilerOptions.plugins[].name. - Any other
*.config.*or*.preset.*JS or TS file, and any other.json,.jsonc,.yamlor.ymlfile, at a layout root: a string that is exactly a package name, such asminify: 'terser'invite.config.tsor a plugin in.releaserc.json.package.json, lockfiles,pnpm-workspace.yamland tsconfigs are not read this way.
- ESLint (
- Binaries run by scripts. The
- Diff the two sets to produce four buckets: unused, unusedPeer (when
--check-peer), misplaced, typeOnly. An import from any file counts as usage. misplaced and typeOnly look only at production files, so adevDependencyused only in tests or tooling is never misplaced.
Output
Text format (default):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Dependency Analysis Report
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠ Unused Dependencies:
(declared but not imported in source code)
• moment
⚠ Misplaced Dependencies:
(in devDependencies but used in source code)
• zod (used in 1 file)
└─ src/api/schema.ts:5
import { z } from 'zod';
ℹ️ Type-Only Imports:
(used only for type definitions)
○ type-fest
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total Issues: 3
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━A dependencies entry that is only ever imported as a type is listed under Type-Only Imports and counts as an issue: it could live in devDependencies. A package that publishes declarations (a types or typings entry, or a types condition anywhere in exports) skips this check, because its consumers need those types installed. Colours are used only when stdout is a terminal and NO_COLOR is unset.
With --check-peer (or --all), an additional section appears for declared peers that no source file imports:
⚠ Unused peerDependencies:
(declared as a consumer contract but not imported in source code)
• react-domJSON format (--json --check-peer; file paths are absolute):
{
"unused": ["moment"],
"unusedPeer": ["react-dom"],
"misplaced": [
{
"packageName": "zod",
"locations": [
{ "file": "/path/to/project/src/api/schema.ts", "line": 5, "importStatement": "import { z } from 'zod';" }
]
}
],
"typeOnly": ["type-fest"],
"ignored": [],
"totalIssues": 4
}unusedPeer is [] when --check-peer is off (default). ignored lists the packages passed to --ignore.
CI integration
Fail the build on any finding:
- run: npx deps-finderOr keep a report without blocking on findings, while still failing when the run itself breaks (exit 2):
- run: npx deps-finder --json > deps-report.json || [ $? -eq 1 ]peerDependencies note
peerDependencies are a contract with consumers, not a usage indicator — many real peers (e.g. typescript, ESLint plugin peers) are intentionally never imported by the library itself. By default deps-finder skips them. Opt in with --check-peer and they appear in a separate Unused peerDependencies section.
Honest-use notice
deps-finder uses static AST scanning, so dynamic patterns are invisible to it: require(variable), import(expr), a template literal with ${}, eval, virtual modules from bundler plugins, packages a config names only as a string outside the tools and keys listed above. Test files that a runner config points at from an unusual place (a Playwright testDir, a codegen.ts run only from a script, src/mocks/ imported only by tests) are treated as production. The tool prefers under-reporting over over-reporting, but false positives still happen. When one does, --ignore <pkg> is the escape valve — and an issue report is welcome.
Yarn Plug'n'Play installs are not read. When nothing declared is in a node_modules and a .pnp.cjs or .pnp.js sits at or above the project, a run that decides unused prints one error and exits 2, as it does without an install. Set nodeLinker: node-modules in .yarnrc.yml and run yarn install to make the install readable.
A package used without an import is still reported as unused when nothing above finds it: a subcommand another CLI provides (nuxt storybook), a binary run from CI files such as bitbucket-pipelines.yml, a config file without an extension that no tool above owns (.swcrc), and, in a partial install, a binary named unlike its package (tsc from typescript) when that package is not installed, or a peer of a package that is not installed. Pass it to --ignore.
A bare builtin name such as buffer or events is matched against a declared package of that name (the npm polyfill a bundler would use). Write node:buffer when you mean the Node builtin.
createRequire is followed by name, not by scope: a function assigned later (let r; r = createRequire(…)) is not counted, and a parameter that shadows an imported createRequire is.
Indented Sass (.sass, <style lang="sass">) and Stylus (.styl, <style lang="stylus">) are not read. A Sass or Less @import "name" that loads a local partial counts as use of a declared package called name. The src attribute of a component's <script> or <style> (<style src="pkg/theme.css">) is not read. An outDir that holds the analysed sources, such as "outDir": "src", is excluded as build output, so the imports in it are not counted.
deps-finder runs on a worker thread with a 256 MB stack, because oxc's native parser recurses once per nesting level. A source nested deeper than that stack holds still crashes the process (exit 132 or 139, outside the codes above), and no report is printed: on macOS arm64 that is past about 140,000 nested object literals, 160,000 nested arrays or parentheses, or 180,000 nested type arguments. Such files are usually generated; --exclude them.
Development
git clone https://github.com/jazz1x/deps-finder.git
cd deps-finder
bun install
bun run validate # typecheck + lint + format + testsThe project uses Bun for tests and tsc from TypeScript 7 for typecheck and the published build. See package.json for the full script list.
