Created
October 18, 2025 08:20
-
-
Save mgratzer/832c150c1d6e437d0335d9da5eba8e34 to your computer and use it in GitHub Desktop.
Revisions
-
mgratzer created this gist
Oct 18, 2025 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal 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.