Manage Node.js package managers, dependencies, lockfiles, workspaces, scripts, and module ecosystems across npm, pnpm, yarn, and bun. Use when selecting or migrating package managers, diagnosing dependency conflicts or phantom dependencies, configuring monorepos and Turborepo, hardening package.json, resolving lockfile drift, auditing vulnerabilities, configuring ESM/CJS compatibility, or publishing packages.
67
84%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Package management in the Node.js ecosystem governs dependency resolution, execution speed, reproducibility across continuous integration (CI) environments, and runtime module compatibility. Selecting the right package manager and applying strict configuration patterns prevents phantom dependencies, supply-chain vulnerabilities, lockfile synchronization failures, and dual-package hazards.
npm, pnpm, yarn, or bun).corepack enable) or native package managers installed globally.Evaluate project requirements, team workflow, and infrastructure before picking a package manager:
| Feature / Metric | npm (v10+) | pnpm (v9+) | Yarn Berry (v4+) | Bun (v1+) |
|---|---|---|---|---|
| Default Architecture | Flat / Hoisted node_modules | Content-addressable store + symlinks | Zero-installs / PnP or node-modules linker | Flat node_modules + binary cache |
| Phantom Dep Protection | ❌ No (hoisted globally) | ✅ Strict (only declared deps accessible) | ✅ Strict (with PnP linker) | ❌ No (hoisted) |
| Disk Space Efficiency | ❌ Duplicated across repos | ✅ High (global hard-link store) | ⚠️ Moderate (global cache or zip cache) | ⚠️ Moderate (global binary cache) |
| CI Install Speed | Moderate (npm ci) | Fast (pnpm install --frozen-lockfile) | Very fast (Zero-install or cache) | Fastest native installer |
| Monorepo Workspaces | Native basic workspaces | First-class workspace protocol (workspace:*) | First-class workspace protocol | Basic workspace support |
| Runtime Integration | Node.js standard default | Node.js ecosystem tool | Node.js ecosystem tool | All-in-one JS runtime, bundler, test runner |
pnpm for most production applications, libraries, and monorepos. Its hard-linked, content-addressable store saves disk space and strictly prevents phantom dependencies.npm when minimizing external tooling is paramount, when working in constrained corporate environments that forbid alternate package managers, or for baseline tutorials.Yarn Berry (v4+) when utilizing Zero-Installs (committing .yarn/cache for offline builds) or when existing monorepo tooling is deeply tied to Yarn Plug'n'Play (PnP).Bun when execution speed of installation, scripts, and runtime is top priority and the project relies entirely on Node.js-compatible APIs without deep dependence on native node-gyp bindings unsupported by Bun.Prevent "works on my machine" discrepancies by pinning both the Node.js runtime and the exact package manager version.
Create an .nvmrc and .node-version file at the repository root:
20.18.0nvm: Uses .nvmrc (nvm use).fnm (Fast Node Manager): Evaluates .node-version and .nvmrc instantly via shell hooks; written in Rust for high performance.volta: Pins toolchains directly in package.json:
"volta": {
"node": "20.18.0",
"pnpm": "9.12.0"
}Add the "packageManager" field to package.json and enable Corepack:
corepack enable
corepack use pnpm@9.12.0This writes the exact version and SHA hash to package.json:
{
"name": "app-template",
"packageManager": "pnpm@9.12.0+sha512.a85f..."
}Any developer running pnpm with Corepack enabled will automatically download and execute the exact specified version.
package.json Anatomy and MetadataStructure package.json into semantic categories to ensure correct build outputs and runtime boundaries:
{
"name": "@scope/example-package",
"version": "1.2.0",
"description": "Production Node.js service and utility",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"files": [
"dist",
"README.md",
"LICENSE"
],
"engines": {
"node": ">=20.0.0",
"npm": ">=10.0.0"
},
"scripts": {
"prepare": "husky || true",
"prebuild": "rimraf dist",
"build": "tsup",
"lint": "eslint .",
"test": "vitest run",
"prepublishOnly": "npm run test && npm run build"
},
"dependencies": {
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20.16.10",
"rimraf": "^6.0.1",
"tsup": "^8.3.0",
"typescript": "^5.6.2",
"vitest": "^2.1.2"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
}
}
}dependencies: Packages required at runtime in production (e.g., ORM, validation library, HTTP framework).devDependencies: Packages needed only during local development and build phases (e.g., compilers, linters, test harnesses).peerDependencies: Packages the consuming application must provide. Essential for plugins, component libraries, and shared singletons.peerDependenciesMeta: Marks peer dependencies as "optional": true when functionality degrades gracefully without them.optionalDependencies: Packages that may fail installation without halting the overall install process (e.g., platform-specific native binaries).bundledDependencies: Array of package names bundled directly into the tarball when publishing.engines: Enforces runtime compatibility. Combine with .npmrc setting engine-strict=true.files: An explicit allowlist of files to include in published artifacts. Preferred over .npmignore.Node.js package managers adhere to Semantic Versioning (MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]):
| Specifier | Syntax Example | Matches | Explanation |
|---|---|---|---|
| Exact | 1.2.3 | 1.2.3 only | Strict pinning. Zero drift. |
| Patch / Tilde | ~1.2.3 | >=1.2.3 <1.3.0 | Updates patch releases only. |
| Minor / Caret | ^1.2.3 | >=1.2.3 <2.0.0 | Updates minor and patch releases. |
| Caret on Zero | ^0.2.3^0.0.3 | >=0.2.3 <0.3.00.0.3 only | In 0.x.x, minor bump is breaking. In 0.0.x, patch bump is breaking. |
| Comparative | >=18.0.0 <21.0.0 | 18.0.0 up to 20.x | Bounded version range. |
| Union (OR) | ^18.0.0 || ^19.0.0 | Any matching either range | Multi-major support for peer dependencies. |
| Wildcard | * or x | Any version | Anti-pattern: Highly unstable. Never use in production. |
dependencies (save-exact=true in .npmrc).^) ranges for dependencies to permit non-breaking upgrades in consumers.^18.0.0 || ^19.0.0) for peerDependencies to avoid peer conflict errors in consumer projects.Never run standard install commands (npm install, pnpm install, yarn) in CI/CD pipelines. Standard install commands update lockfiles and permit nondeterministic transitive updates.
Always use the frozen/immutable flag for the detected package manager:
# npm: Deletes existing node_modules and strictly installs from package-lock.json
npm ci
# pnpm: Verifies pnpm-lock.yaml matches package.json and aborts if out of sync
pnpm install --frozen-lockfile
# Yarn (v2+): Fails if yarn.lock requires modifications
yarn install --immutable
# Bun: Enforces lockfile consistency
bun install --frozen-lockfileWhen git merge conflicts occur in lockfiles, never attempt manual text editing of multi-thousand-line diffs.
package.json:# For npm
git checkout --theirs package-lock.json
npm install --package-lock-only
# For pnpm
git checkout --theirs pnpm-lock.yaml
pnpm install --lockfile-only
# For Yarn
git checkout --theirs yarn.lock
yarn install --mode update-lockfileA phantom dependency occurs when code imports a package that exists inside node_modules (because another dependency pulled it in and it was hoisted to the top level) but is not declared in package.json. If the intermediate dependency updates and drops that package, the application breaks at runtime.
npm and yarn classic hoist dependencies to a flat tree by default.pnpm builds a symlinked nested tree inside node_modules/.pnpm, preventing code from importing unlisted packages unless explicitly declared in package.json.Remove duplicate versions of packages resolved across the dependency graph:
# npm
npm dedupe
# pnpm
pnpm dedupe
# Yarn
yarn dedupeWhen a nested transitive dependency contains a bug or security vulnerability, force a specific version across the entire dependency graph:
package.json for npm:{
"overrides": {
"semver": "7.6.3",
"lodash": {
".": "^4.17.21"
}
}
}package.json for pnpm:{
"pnpm": {
"overrides": {
"semver": "7.6.3"
}
}
}package.json for Yarn:{
"resolutions": {
"semver": "7.6.3"
}
}Organize multi-package repositories into workspaces to share dependencies and manage internal package references without local file paths.
In the root package.json:
{
"name": "root-monorepo",
"private": true,
"workspaces": [
"packages/*",
"apps/*"
]
}Run workspace-specific commands:
# Run script in a specific workspace
npm run test --workspace=@scope/shared-utils
# Install a dependency into a specific workspace
npm install axios --workspace=@scope/web-appCreate pnpm-workspace.yaml in the monorepo root:
packages:
- 'packages/*'
- 'apps/*'Consume internal workspace packages using the workspace:* protocol inside apps/web-app/package.json:
{
"dependencies": {
"@scope/shared-utils": "workspace:*"
}
}Run scoped commands with --filter:
# Run build only for @scope/web-app and its dependencies
pnpm --filter @scope/web-app... build
# Run test in all workspace packages recursively
pnpm -r run testIntegrate Turborepo (turbo) for intelligent task pipelines, parallel execution, and artifact caching. Create turbo.json:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!dist/.cache"]
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
},
"lint": {
"cache": true
},
"dev": {
"cache": false,
"persistent": true
}
}
}Execute with:
npx turbo run buildUse -- to forward extra arguments to the underlying script or binary:
npm run test -- --watch --coverage
pnpm run test -- --bailAvoid Unix-only shell builtins (rm -rf, export VAR=val) that fail on Windows. Use cross-platform libraries:
{
"scripts": {
"clean": "rimraf dist coverage",
"start:prod": "cross-env NODE_ENV=production node dist/index.js",
"build:all": "npm run clean && npm run build"
}
}prepare: Executes before package is packed and published, and on local npm install without arguments. Ideal for initializing Git hooks (e.g., Husky).prepublishOnly: Executes strictly before npm publish. Perfect for clean builds and running test suites before registry deployment.pre* and post* scripts (e.g., pretest, posttest) because some package managers (Yarn Berry, pnpm) do not execute custom prefix hooks automatically. Declare pipelines explicitly in your primary script or orchestrator.--env-file (Node.js 20.6+)Node.js 20.6 and later supports loading .env files natively without third-party dependencies:
# In package.json scripts
node --env-file=.env dist/index.jsdotenv Library (for Older Node.js or Multi-File Chains)pnpm add dotenvPreload in script execution:
{
"scripts": {
"dev": "node -r dotenv/config src/index.js"
}
}.env Files.env.example with blank or dummy values into git..env, .env.local, and .env.*.local to .gitignore.import { z } from 'zod';
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
});
export const env = envSchema.parse(process.env);Execute CLI utilities without polluting project dependencies or downloading unverified binaries.
When a package is installed in devDependencies, execute it without global installation:
# Executes binary from ./node_modules/.bin/
npx eslint .
pnpm exec eslint .
yarn run eslint .
bunx eslint .To run a command from a remote package without installing it:
# npm (prompts for confirmation unless -y is specified)
npx --yes create-vite@latest my-app
# pnpm (use dlx; pnpx is a deprecated alias)
pnpm dlx create-vite@latest my-app
# Yarn Berry
yarn dlx create-vite@latest my-app
# Bun
bunx create-vite@latest my-appUse --no-install to ensure npx only runs binaries already installed locally:
npx --no-install prettier --check .Modern Node.js projects should prioritize ECMAScript Modules (ESM) while supporting CommonJS (CJS) where backward compatibility is required.
"type": "module" in package.json to treat .js files as ESM..mjs: Always parsed as ESM, regardless of package.json..cjs: Always parsed as CommonJS, regardless of package.json..js: Inherits behavior from the nearest parent package.json "type" field (defaults to CJS if omitted).ESM does not provide __dirname or __filename. Replace them using import.meta.url:
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const configPath = join(__dirname, 'config.json');Note: ESM natively supports top-level await without requiring wrapping async IIFEs.
The dual-package hazard occurs when a stateful package is loaded by both CJS and ESM consumers, creating two distinct instances in memory. Avoid this by:
tsconfig.jsonAlign TypeScript compiler options with the Node.js module resolution algorithm:
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"skipLibCheck": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}Rather than running tsc twice, use modern bundlers such as tsup configured for dual outputs:
// tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
clean: true,
sourcemap: true,
});.npmrc and .npmignore Packaging Hygiene.npmrc ConfigurationCreate an .npmrc file at the project root for strict build controls and scoped registries:
# Enforce strict Node version checking
engine-strict=true
# Save exact versions by default on install
save-exact=true
# Auto-install peer dependencies (pnpm specific)
auto-install-peers=true
# Scoped package registry assignment
@myorg:registry=https://npm.pkg.github.com/
# Auth token interpolation (read from environment variable in CI)
//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}.npmignore vs "files" in package.json"files" field in package.json as an explicit allowlist (["dist", "README.md", "LICENSE"])..npmignore is a blocklist. Forgetting to update .npmignore when adding test suites, scratchpads, or temporary tokens can accidentally publish proprietary code or secrets to the public registry..npmignore is omitted, npm defaults to .gitignore. Never rely on .gitignore alone for library publishing.Regularly inspect installed packages against the Advisory Database:
# npm audit
npm audit
# Fix non-breaking changes only
npm audit fix
# pnpm audit
pnpm audit[!WARNING] Avoid running
npm audit fix --force. It forces major version upgrades on dependencies and often introduces breaking changes, unresolvable peer dependency conflicts, or broken builds. Use selectiveoverridesor update direct dependencies manually.
Disable execution of arbitrary post-install scripts from untrusted packages when installing in security-sensitive environments:
pnpm install --ignore-scriptsIncorporate audit-ci into CI workflows to fail only on high or critical vulnerabilities:
npx audit-ci --high --criticalBefore publishing, inspect the exact file archive that will be uploaded:
# Perform dry run
npm publish --dry-run
# Or generate and inspect the tarball
npm pack
tar -tf myorg-example-package-1.0.0.tgzPublish scoped packages publicly with cryptographic attestations linking the package directly to its source build commit and CI workflow:
npm publish --access public --provenancesave-exact=true in .npmrc for standalone applications and services to prevent accidental minor/patch breakages.^) ranges and broad peerDependencies (>=18.0.0 <20.0.0) in published libraries so consuming applications do not encounter dependency version duplication."files" in package.json: Always use an explicit allowlist in "files" rather than a denylist in .npmignore to prevent leaking source maps, test fixtures, or sensitive internal credentials.package-lock.json, pnpm-lock.yaml, or yarn.lock to .gitignore. They are essential for reproducible builds across machines and CI..node-version (for fnm and general tooling) and .nvmrc (for nvm users) containing identical LTS version strings.npm install in CI: Causes lockfile drift, ignores frozen constraints, and leads to irreproducible builds. Always use npm ci or pnpm install --frozen-lockfile.node_modules without noticing that a consumed module is not in package.json. Use pnpm or enable strict linting (eslint-plugin-import / knip).npm audit fix --force: Blanket-upgrades transitive dependencies across major versions, causing silent runtime failures and breaking API changes.package-lock.json AND pnpm-lock.yaml) creates desynchronized dependencies and unpredictable builds. Remove obsolete lockfiles immediately when migrating.-- in npm scripts: Running npm test --watch instead of npm test -- --watch results in npm consuming the --watch flag itself rather than forwarding it to the test runner.Run these verification steps to confirm package management integrity:
Verify Lockfile Integrity and Reproducibility:
# For npm
npm ci --dry-run
# For pnpm
pnpm install --frozen-lockfileExpected output: Installation completes with exit code 0 and reports zero modified files.
Verify Engine Compliance:
npm run buildEnsure no EBADENGINE warnings or errors occur when running under the specified Node.js version.
Check for Unused and Phantom Dependencies:
npx knipExpected output: Reports no undeclared dependencies or unused production packages.
Verify Package Export Maps:
npx @arethetypeswrong/cli --pack .Expected output: All export formats (ESM, CJS, Types) resolve cleanly across Node10, Node16, and Bundler resolutions without type definition mismatches.
Verify Security Status:
npm audit --audit-level=high
# or
pnpm audit --audit-level=highExpected output: 0 vulnerabilities found at or above the designated severity threshold.
87555db
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.