Skip to content

Instantly share code, notes, and snippets.

@mgratzer
Created October 18, 2025 08:20
Show Gist options
  • Select an option

  • Save mgratzer/832c150c1d6e437d0335d9da5eba8e34 to your computer and use it in GitHub Desktop.

Select an option

Save mgratzer/832c150c1d6e437d0335d9da5eba8e34 to your computer and use it in GitHub Desktop.

Revisions

  1. mgratzer created this gist Oct 18, 2025.
    656 changes: 656 additions & 0 deletions DEPENDENCY_AUDIT_GUIDE.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,656 @@
    # Node.js Dependency Audit Guide

    This is a universal guide for auditing and managing dependencies in any Node.js/TypeScript project with a pragmatic, minimalist philosophy. Use this to audit any project and create a comprehensive `DEPENDENCIES.md` file.

    **Supports:** npm, pnpm, yarn, bun
    **Works with:** Node.js, TypeScript, JavaScript, monorepos

    ## Quick Start

    ### For Claude Code

    To audit a project using this guide:

    ```
    Using the DEPENDENCY_AUDIT_GUIDE.md as your reference, audit this project's
    dependencies and create a DEPENDENCIES.md file following the template provided.
    ```

    Claude will:
    1. Run audit commands for your package manager
    2. Analyze each dependency
    3. Categorize them (🟢 Excellent / 🟡 Acceptable / 🔴 Questionable)
    4. Create a project-specific DEPENDENCIES.md
    5. Identify removable dependencies
    6. Suggest version pinning strategies

    ### Package Manager Detection

    This guide works with any package manager. Commands are provided for all major tools:
    - **npm** - Default Node.js package manager
    - **pnpm** - Fast, disk-space efficient
    - **yarn** - Classic or Berry (v2+)
    - **bun** - Fast all-in-one toolkit

    Most commands follow this pattern: `<package-manager> <command> <args>`

    ## Philosophy

    > "A little copying is better than a little dependency" - Go Proverb
    Apply this mindset to JavaScript: Every dependency is code you don't control, a potential security vulnerability, technical debt, and a maintenance burden. Choose wisely.

    ## The Audit Process

    ### Step 1: Gather Current State

    Run these commands to understand the project (adjust for your package manager):

    ```bash
    # List direct dependencies only
    npm ls --depth=0 # npm
    pnpm ls --depth=0 # pnpm
    yarn list --depth=0 # yarn
    bun pm ls # bun

    # Check node_modules size
    du -sh node_modules/

    # Check for security vulnerabilities
    npm audit # npm
    pnpm audit # pnpm
    yarn audit # yarn
    bun pm audit # bun (if available)

    # Find outdated packages
    npm outdated # npm
    pnpm outdated # pnpm
    yarn outdated # yarn
    bun outdated # bun (if available)

    # Check TypeScript configuration (if TypeScript project)
    cat tsconfig.json | grep -A 5 "compilerOptions"
    ```

    **Record:**
    - Total direct dependencies (production + dev)
    - node_modules size
    - Security vulnerabilities (severity and count)
    - TypeScript strict mode status

    ### Step 2: Analyze Each Dependency

    For each production dependency, gather:

    ```bash
    # Check version (any package manager)
    npm view <package> version
    pnpm info <package> version
    yarn info <package> version
    bun pm view <package> version

    # Check transitive dependencies
    npm view <package> dependencies
    pnpm info <package> dependencies
    yarn info <package> dependencies

    # Check why it's installed
    npm ls <package>
    pnpm why <package>
    yarn why <package>

    # Check package details (weekly downloads, publish date, etc.)
    npm view <package>
    pnpm info <package>
    yarn info <package>

    # Or use online tools
    # - https://bundlephobia.com (bundle size)
    # - https://npmtrends.com (popularity trends)
    # - https://www.npmjs.com/package/<package> (full details)
    ```

    ### Step 3: Categorize Dependencies

    Use this decision tree for each dependency:

    #### 🟢 EXCELLENT (Keep Without Question)
    - **Zero transitive dependencies**
    - **Small size** (<100KB)
    - **Single, clear purpose**
    - **Would take >50 lines to reimplement**
    - **Examples:**
    - Validation: zod, yup
    - HTTP: hono, fastify (lightweight frameworks)
    - Database: postgres.js, better-sqlite3
    - Utilities: date-fns (if you need it), ms, dotenv

    #### 🟡 ACCEPTABLE (Necessary for business logic)
    - **Few transitive deps** (<10)
    - **Official SDK** or well-maintained library
    - **Core to product functionality**
    - **Examples:**
    - ORMs: drizzle-orm, prisma (if needed)
    - Official SDKs: @google-cloud/*, @aws-sdk/*, stripe
    - Frontend: React, Vue, Svelte (framework dependencies)
    - Testing: vitest, jest (dev deps)
    - Build: vite, esbuild (dev deps)

    #### 🔴 QUESTIONABLE (High cost, evaluate alternatives)
    - **Many transitive deps** (>15)
    - **Large size** (>500KB)
    - **Could be replaced with simpler approach**
    - **Brings in duplicate dependencies**
    - **Examples:**
    - Heavy cloud SDKs (consider REST API instead)
    - All-in-one frameworks (evaluate what you actually use)
    - Deprecated packages (lodash → native methods)
    - Utility libraries for trivial tasks (left-pad, is-number)

    ### Step 4: Apply the Dependency Test

    Before keeping any dependency, ask:

    1. **Can I write this in <50 lines of code?**
    - If YES → Write it yourself
    - If NO → Continue to question 2

    2. **Does it have minimal dependencies itself?**
    - Run: `npm view <package> dependencies` (or pnpm/yarn equivalent)
    - RED FLAG: >10 transitive dependencies
    - GREEN FLAG: 0-3 transitive dependencies

    3. **Is it widely used and actively maintained?**
    - Check: npm downloads/week (prefer >100k)
    - Check: Last update (prefer <6 months ago)
    - Check: GitHub stars and open issues

    4. **What's the bundle size impact?**
    - Check: https://bundlephobia.com
    - RED FLAG: >10MB for a utility library
    - Consider: Is the size justified by value?

    5. **Could I use a REST API instead?**
    - Many SDKs are just wrappers around HTTP APIs
    - Sometimes `fetch` + 100 lines of code beats a 50-dependency SDK

    ### Step 5: Identify Removable Dependencies

    Look for:

    **Duplicate functionality:**
    ```bash
    # Check for multiple env loaders, loggers, http clients, etc.
    npm ls | grep -i "dotenv"
    npm ls | grep -i "logger"
    npm ls | grep -i "http"
    # (or use pnpm/yarn ls)
    ```

    **Unused dependencies:**
    ```bash
    # Search for imports across codebase
    grep -r "from ['\"]<package-name>['\"]" . --include="*.ts" --include="*.tsx" --include="*.js"

    # Or use specialized tools
    npx depcheck # Find unused dependencies
    npx npm-check # Interactive dependency checker
    ```

    **Redundant type packages:**
    ```bash
    # Check if types are already provided by peer dependencies
    npm ls @types/<package>
    # (or use pnpm why / yarn why)
    ```

    **Leftover dev dependencies:**
    - Check package.json for tools you're not using
    - Look for packages from old configs or experiments

    ### Step 6: Version Management Strategy

    Categorize dependencies into pinning strategies:

    #### Pin to Exact Versions (No Caret `^`)
    **When:**
    - Pre-1.0 packages (0.x.x versions) - minor bumps can break
    - Database layers (ORMs, query builders) - critical stability
    - Core business logic packages - breaking changes are catastrophic
    - Packages with history of breaking changes

    **Format:** `"package": "1.2.3"` (no `^`)

    #### Use Caret Ranges (Allow Minor Updates)
    **When:**
    - Mature, stable packages (v3+, good semver history)
    - Official SDKs from trusted vendors
    - Well-maintained frameworks with good semver discipline
    - Dev dependencies (always want latest tooling)

    **Format:** `"package": "^1.2.3"` (allows 1.x.x updates)

    **Upgrade process for pinned packages:**
    1. Check changelog for breaking changes
    2. Test in development environment
    3. Run full test suite
    4. Update manually: `pnpm add <package>@<version>`
    5. Document the upgrade in DEPENDENCIES.md

    ### Step 7: Security Assessment

    ```bash
    # Run security audit
    npm audit # npm
    pnpm audit # pnpm
    yarn audit # yarn

    # Check specific severity levels (npm/pnpm)
    npm audit --audit-level=moderate
    npm audit --audit-level=high
    npm audit --audit-level=critical

    # Fix automatically (use with caution)
    npm audit fix # npm
    pnpm audit --fix # pnpm (if available)
    yarn audit --fix # yarn (deprecated, use upgrade-interactive)
    ```

    **For each vulnerability:**
    - Identify if it's in production or dev dependencies
    - Check if it affects your usage (many vulns are in unused code paths)
    - Evaluate severity vs. effort to fix
    - Document accepted risks (with justification)

    **Acceptable vulnerabilities:**
    - Low/Moderate in dev-only dependencies
    - Vulnerabilities in unused code paths (document why it's unused)
    - Awaiting upstream fixes (set reminder to check monthly)

    **Unacceptable vulnerabilities:**
    - High/Critical in production dependencies
    - Known exploits in your attack surface
    - No upstream fix available (consider alternatives)

    ## Creating the DEPENDENCIES.md File

    Use this template structure:

    ````markdown
    # Dependency Management Guidelines

    ## Current State (Baseline)
    - **Direct dependencies:** X (Y production, Z dev)
    - **node_modules size:** XMB
    - **Last audit:** YYYY-MM-DD
    - **Security vulnerabilities:** X (severity breakdown)
    - **Last cleanup:** YYYY-MM-DD

    ## Philosophy
    > "A little copying is better than a little dependency" - Go Proverb

    [Your project-specific philosophy paragraph]

    ## Before Adding a Dependency

    Ask these questions in order:

    1. **Can I write this in <50 lines of code?**
    - If yes: Write it yourself
    - Examples: String utilities, simple validators, array helpers

    2. **Does it have minimal dependencies itself?**
    - Run: `npm view <package> dependencies` (or pnpm/yarn equivalent)
    - Red flag: >10 dependencies
    - Check: When was it last updated?

    3. **Is it widely used and trusted?**
    - Check npm downloads/week
    - Check GitHub stars and issues
    - Prefer: >100k weekly downloads OR official SDK

    4. **What's the bundle size impact?**
    - Check: https://bundlephobia.com
    - Red flag: >10MB for a utility library

    ## Version Management

    ### Pinned Dependencies (Exact Versions)
    **These are pinned to prevent breaking changes:**

    ```json
    // Pre-1.0 packages (0.x.x can break on minor updates)
    "package-name": "0.20.2",

    // Database layer (critical stability)
    "orm-package": "1.44.6",

    // Core business logic
    "core-package": "5.0.76"
    ```

    **To upgrade pinned dependencies:**
    1. Check changelog for breaking changes
    2. Test in development thoroughly
    3. Update manually:
    - `npm install <package>@<version>` (npm)
    - `pnpm add <package>@<version>` (pnpm)
    - `yarn add <package>@<version>` (yarn)
    - `bun add <package>@<version>` (bun)
    4. Update this document with new version

    ### Flexible Dependencies (Caret Ranges)
    **These can auto-update within their major version:**

    ```json
    // Mature, stable frameworks
    "framework": "^4.10.1",

    // Official SDKs (good semver discipline)
    "official-sdk": "^7.17.2"
    ```

    ### Dev Dependencies
    **All dev dependencies use caret ranges** - always get latest tooling improvements.

    ### When to upgrade
    - **Pinned packages:** Manual upgrade only, after testing
    - **Flexible packages (patch):** Auto-apply via update command
    - **Flexible packages (minor):** Review changelog, test locally
    - **Major versions:** Read migration guide, block time for testing

    **Update commands:**
    ```bash
    npm update # npm
    pnpm update # pnpm
    yarn upgrade # yarn
    bun update # bun
    ```

    ## Security Protocol

    ### Monthly (Set calendar reminder)
    ```bash
    npm audit && npm outdated # npm
    pnpm audit && pnpm outdated # pnpm
    yarn audit && yarn outdated # yarn
    ```

    ### Before deploying to production
    ```bash
    npm audit --audit-level=high # npm
    pnpm audit --audit-level=high # pnpm
    yarn audit # yarn
    # Fix any high/critical vulnerabilities before deploy
    ```

    ### Lockfile discipline
    - **NEVER delete** lockfiles to "fix" issues
    - `package-lock.json` (npm)
    - `pnpm-lock.yaml` (pnpm)
    - `yarn.lock` (yarn)
    - `bun.lockb` (bun)
    - **ALWAYS commit** lockfile changes
    - **PRODUCTION:** Use exact versions in lockfile (default behavior for all package managers)

    ## Dependency Assessment

    ### Production Dependencies

    | Package | Version | Transitive Deps | Size | Verdict | Rationale |
    |---------|---------|-----------------|------|---------|-----------|
    | package-name | 1.2.3 | 0 | 50KB | 🟢 Keep | Zero deps, minimal, essential |
    | heavy-sdk | 5.0.0 | 15 | 500KB | 🔴 Watch | Consider REST API alternative |

    ### Dev Dependencies

    | Package | Version | Purpose | Verdict |
    |---------|---------|---------|---------|
    | typescript | 5.9.3 | Type checking | ✅ Essential |

    ## Current Vulnerabilities

    ### [Package Name] v[version] ([severity])
    - **Status:** Accepted/Mitigating/Fixing
    - **Path:** package → dependency → vulnerable-package
    - **Risk:** [Description of actual risk]
    - **Mitigation:** [Why it's acceptable or what you're doing]
    - **Resolution:** [Waiting for upstream/Will fix in next release/etc]

    ## Approved Dependency Categories

    ### ✅ Always Acceptable
    - Official SDKs (Google Cloud, OpenAI, etc.)
    - Core framework dependencies
    - TypeScript types (`@types/*`)
    - Build/dev tools

    ### ⚠️ Requires Justification
    - Utility libraries (can you write it?)
    - Additional ORMs/frameworks
    - Alternative HTTP libraries

    ### ❌ Generally Avoid
    - Packages with <1000 weekly downloads
    - Packages not updated in >2 years
    - Micro-packages for trivial tasks (left-pad syndrome)
    - Multiple packages doing the same thing

    ## Red Flags to Watch For

    If you see these, investigate immediately:
    - Audit shows HIGH or CRITICAL vulnerabilities
    - node_modules grows beyond reasonable size (set your threshold: 500MB, 1GB, etc.)
    - Direct dependencies exceed reasonable count (set your threshold: 25, 30, 50, etc.)
    - Build time increases significantly
    - New deprecation warnings appear

    ## Monitoring Commands

    ### See direct dependencies only
    ```bash
    npm ls --depth=0
    pnpm ls --depth=0
    yarn list --depth=0
    ```

    ### Check node_modules size
    ```bash
    du -sh node_modules/ # Unix/Mac
    dir node_modules # Windows (shows size in properties)
    ```

    ### Why is this package installed?
    ```bash
    npm ls <package-name>
    pnpm why <package-name>
    yarn why <package-name>
    ```

    ### Find outdated packages
    ```bash
    npm outdated
    pnpm outdated
    yarn outdated
    ```

    ### Check package details
    ```bash
    npm view <package>
    npm view <package> dependencies

    pnpm info <package>
    pnpm info <package> dependencies

    yarn info <package>
    ```

    ## Adding a New Dependency - Checklist

    Before running install command:

    - [ ] Checked if I can write it myself in <50 lines
    - [ ] Reviewed package's own dependencies
    - [ ] Verified last update was <6 months ago
    - [ ] Checked weekly downloads (>10k preferred)
    - [ ] Measured bundle size impact (bundlephobia.com)
    - [ ] Read recent issues on GitHub
    - [ ] Documented reason in git commit message
    - [ ] Updated this DEPENDENCIES.md if it's a new category

    ## Update History

    ### YYYY-MM-DD: [Update Description]
    **Updated:**
    - `package`: X.X.X → Y.Y.Y
    - **Compatibility:** ✅/⚠️/❌
    - **Breaking changes:** [List or "None detected"]
    - **Notes:** [Any important details]

    ### YYYY-MM-DD: Initial Audit
    **Removed X unnecessary dependencies:**
    - `package-name` - Reason
    - `another-package` - Reason

    **Result:** XX → YY dependencies (-Z%)

    ## Resources

    - Security advisories: `pnpm audit`
    - Package info: `pnpm info <package>`
    - Dependency tree: `pnpm ls <package>`
    - Bundle analysis: https://bundlephobia.com
    - Package trends: https://npmtrends.com
    ````

    ## Special Considerations

    ### TypeScript Configuration

    Always check that strict mode is enabled:

    ```json
    {
    "compilerOptions": {
    "strict": true,
    "strictNullChecks": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true
    }
    }
    ```

    This provides your safety net - it's 80% of the safety of Go/Rust's type systems.

    ### Common Bloat Sources to Watch For

    **Heavy SDKs that could be replaced:**
    - Cloud storage SDKs (often can use REST API with just `fetch`)
    - Analytics SDKs (often can POST events directly)
    - Social media SDKs (REST APIs are usually simple)

    **Frameworks that pull in the world:**
    - All-in-one frameworks (check what you actually use)
    - State management libraries (could you use React Context?)
    - Utility libraries (lodash, ramda - write the 3 functions you need)

    **Duplicate functionality:**
    - Multiple HTTP clients (axios, got, node-fetch, native fetch)
    - Multiple date libraries (date-fns, moment, dayjs, native Date)
    - Multiple validation libraries (yup, joi, zod - pick one)
    - Multiple test frameworks (jest, vitest, mocha - pick one)

    ### When to Use This Guide

    **Initial project setup:**
    1. Run the audit process
    2. Create DEPENDENCIES.md
    3. Establish baselines and thresholds

    **Monthly maintenance:**
    1. Run audit and outdated commands for your package manager
    2. Review any new dependencies added
    3. Update DEPENDENCIES.md with changes

    **Before adding new dependencies:**
    1. Consult the "Before Adding a Dependency" checklist
    2. Document the decision in DEPENDENCIES.md
    3. Update the assessment table

    **When onboarding new developers:**
    1. Have them read DEPENDENCIES.md
    2. Explain the project's dependency philosophy
    3. Use it as a code review checkpoint

    ### Special Cases

    **Monorepos:**
    - Run audit at root level and in each workspace
    - Watch for duplicate dependencies across workspaces
    - Use workspace protocol to share dependencies: `"@my/package": "workspace:*"`
    - Consider tools like `manypkg check` or `syncpack` for consistency

    **Frontend + Backend:**
    - Maintain separate DEPENDENCIES.md for each if significantly different
    - Or use sections: "Frontend Dependencies" and "Backend Dependencies"
    - Watch for unnecessary shared dependencies

    ## Success Metrics

    A well-managed Node.js project should aim for these targets (adjust based on project type):

    ### Backend/API Projects
    -**<30 direct dependencies** (total)
    -**<500MB node_modules**
    -**0 high/critical security vulnerabilities**
    -**>50% dependencies with 0 transitive deps**

    ### Frontend Projects (React/Vue/Angular)
    -**<50 direct dependencies** (frameworks bring more deps)
    -**<1GB node_modules** (build tools are larger)
    -**0 high/critical security vulnerabilities**
    -**>30% dependencies with 0 transitive deps**

    ### Universal Metrics
    -**TypeScript strict mode enabled** (if using TypeScript)
    -**All dependencies documented and justified**
    -**Clear pinning strategy**
    -**Monthly audit cadence**
    -**No duplicate functionality**
    -**Lockfile committed to version control**

    ## The "Could You Do This in Go?" Test

    When evaluating a JavaScript dependency, ask:

    > "If I were writing this in Go, would I import a library for this?"
    **If NO → You probably don't need the JavaScript library either.**

    Examples:
    - String manipulation → Use native methods
    - Simple HTTP requests → Use fetch
    - Date formatting → Use Intl.DateTimeFormat
    - Array utilities → Use native map/filter/reduce
    - Retry logic → Write a 10-line retry function

    **If YES → The dependency might be justified.**

    Examples:
    - Database drivers (you'd use `database/sql` + driver)
    - HTTP routing (you'd use `net/http` or chi/gin)
    - Validation (you'd use a validator package)
    - ORM functionality (you might use sqlc or gorm)

    ## Conclusion

    This dependency philosophy is about **conscious choices**:

    - Every dependency should earn its place
    - Prefer small, focused packages over frameworks
    - Value zero-dependency packages highly
    - When in doubt, write it yourself
    - Security and maintainability over convenience

    Remember: **Dependencies are liabilities.** Treat them like technical debt - some is necessary and good, but too much will sink the project.