name: package-management description: | Use when choosing or configuring JavaScript/TypeScript package managers, managing dependencies, setting up workspaces, or publishing packages. Covers npm, yarn, pnpm, and bun. USE FOR: package manager selection, npm/yarn/pnpm/bun configuration, monorepo workspaces, dependency management, package.json anatomy, publishing to registries, lockfile strategies DO NOT USE FOR: tsconfig.json or build tool configuration (use project-system), CLI tool development (use cli), specific library usage (use packages) license: MIT metadata: displayName: "Package Management" author: "Tyler-R-Kendrick" compatibility: claude, copilot, cursor references:
- title: "npm Documentation" url: "https://docs.npmjs.com"
- title: "pnpm Documentation" url: "https://pnpm.io"
- title: "Yarn Documentation" url: "https://yarnpkg.com"
Package Management
Overview
JavaScript/TypeScript package managers handle dependency installation, version resolution, lockfile management, script execution, and workspace orchestration. Choosing the right one affects install speed, disk usage, monorepo workflow, and CI performance. This skill covers the four major package managers and their ecosystems.
Tool Comparison
| Feature | npm | Yarn Classic (1.x) | Yarn Berry (3+) | pnpm | Bun |
|---|---|---|---|---|---|
| Lockfile | package-lock.json |
yarn.lock |
yarn.lock |
pnpm-lock.yaml |
bun.lockb (binary) |
| Install Speed | Moderate | Moderate | Fast (PnP) | Fast | Fastest |
| Disk Usage | High (flat node_modules) |
High (hoisted) | Low (PnP, no node_modules) |
Low (content-addressable store) | Moderate |
| Monorepo Support | Workspaces (basic) | Workspaces | Workspaces + constraints | Workspaces + filtering | Workspaces |
| Plug'n'Play | No | No | Yes (default) | No | No |
| Strictness | Loose (phantom deps) | Loose | Strict (PnP) | Strict (no hoisting) | Loose |
| Patching | overrides (npm 8+) |
resolutions |
resolutions + yarn patch |
pnpm patch |
overrides |
| Built-in Runner | npx |
N/A | yarn dlx |
pnpm dlx / pnpx |
bunx |
| Offline Mode | --prefer-offline |
--offline |
Yes (zero-installs) | --offline |
No |
| Corepack | Yes | Yes | Yes | Yes | No |
npm Essentials
npm is the default package manager shipped with Node.js.
Core Commands
# Install all dependencies from package.json
npm install
# Install with clean lockfile (CI-optimized, fails if lockfile is outdated)
npm ci
# Add a dependency
npm install express
npm install -D typescript # devDependency
npm install -g tsx # global
# Remove a dependency
npm uninstall express
# Run a script from package.json
npm run build
npm test # shorthand for npm run test
npm start # shorthand for npm run start
# Execute a one-off package binary
npx tsx src/index.ts
npx create-next-app@latest
# View dependency tree
npm ls
npm ls --depth=0 # top-level only
# Audit for vulnerabilities
npm audit
npm audit fix
package-lock.json
- Always commit
package-lock.jsonto version control. - Use
npm ciin CI pipelines (faster, deterministic, fails on lockfile mismatch). - Never manually edit the lockfile.
Overrides (npm 8.3+)
Force a transitive dependency version:
// package.json
{
"overrides": {
"lodash": "4.17.21",
"foo": {
"bar": "1.0.0" // override bar only within foo
}
}
}
pnpm Deep Dive
pnpm uses a content-addressable store and strict symlink structure to save disk space and prevent phantom dependencies.
How It Works
- All packages are stored in a global content-addressable store (
~/.pnpm-store). - Each project's
node_modulescontains symlinks to the store instead of copies. - Each package can only access its declared dependencies (strict mode), eliminating phantom dependency bugs.
Core Commands
# Install
pnpm install
pnpm add express
pnpm add -D typescript
pnpm add -g tsx
# Remove
pnpm remove express
# Run scripts
pnpm run build
pnpm test
pnpm exec tsx src/index.ts
pnpm dlx create-next-app@latest
# Workspace filtering
pnpm --filter @myorg/api install
pnpm --filter @myorg/api run build
pnpm --filter "./packages/**" run test
pnpm -r run build # run in all packages recursively
# Store management
pnpm store status
pnpm store prune # remove unreferenced packages
pnpm-workspace.yaml
packages:
- "packages/*"
- "apps/*"
- "!**/test/**" # exclude test directories
Patching Dependencies
# Create a patch for a dependency
pnpm patch express@4.18.2
# Apply patches (creates patches/ directory)
pnpm patch-commit ./path-to-modified-package
Patches are stored in a patches/ directory and applied automatically on install.
Configuration (.npmrc for pnpm)
# .npmrc
auto-install-peers=true
strict-peer-dependencies=false
shamefully-hoist=false # keep strict mode (default)
node-linker=hoisted # opt into flat node_modules (escape hatch)
Yarn Berry (3+)
Yarn Berry is a complete rewrite of Yarn with Plug'n'Play, constraints, and plugin extensibility.
Plug'n'Play (PnP)
PnP eliminates node_modules entirely. Dependencies are stored as compressed archives in .yarn/cache, and a .pnp.cjs file maps import requests to the correct archive.
# Enable PnP (default in Yarn Berry)
yarn set version berry
yarn install
# Generated files
.pnp.cjs # module resolution map
.yarn/cache/ # dependency archives
.yarnrc.yml # configuration
.yarnrc.yml
nodeLinker: pnp # or "node-modules" for compatibility
enableGlobalCache: false
compressionLevel: mixed
plugins:
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
spec: "@yarnpkg/plugin-typescript"
npmScopes:
myorg:
npmRegistryServer: "https://npm.pkg.github.com"
npmAlwaysAuth: true
Constraints
Yarn Berry constraints enforce rules across all workspace packages:
// .yarn/constraints.pro (Prolog-based)
% All packages must have a license field
gen_enforced_field(WorkspaceCwd, 'license', 'MIT').
% All packages must use the same TypeScript version
gen_enforced_dependency(WorkspaceCwd, 'typescript', '~5.4.0', 'devDependencies').
yarn constraints # check
yarn constraints --fix # auto-fix
Zero-Installs
With PnP, the entire .yarn/cache directory can be committed to git, enabling zero-install: cloning the repo gives you a working project with no yarn install needed.
# .gitattributes for zero-installs
.yarn/cache/** binary
.yarn/releases/** binary
.yarn/plugins/** binary
Core Commands
yarn add express
yarn add -D typescript
yarn remove express
yarn dlx create-next-app@latest
yarn workspaces foreach run build
yarn workspaces foreach --topological run build # respect dependency order
yarn up express # interactive upgrade
yarn patch express # create a patch
Bun as Package Manager
Bun's package manager is built in Zig for maximum speed.
Core Commands
# Install all dependencies
bun install
# Add dependencies
bun add express
bun add -d typescript # devDependency
bun add -g tsx # global
# Remove
bun remove express
# Run scripts
bun run build
bun test
# Execute one-off packages
bunx create-next-app@latest
# Generate lockfile without installing
bun install --dry-run
bun.lockb
Bun uses a binary lockfile (bun.lockb) for speed. To inspect it:
bun install --yarn # generate a yarn.lock for readability
Workspaces
// package.json (root)
{
"workspaces": ["packages/*", "apps/*"]
}
# Run a script in a specific workspace
bun run --filter @myorg/api build
Key Differences
- Bun is the fastest installer by a significant margin (often 10-30x faster than npm).
- Binary lockfile is not human-readable but is smaller and faster to parse.
- Compatible with
package.json,node_modules, and most npm packages. - Does not support Plug'n'Play or content-addressable store.
Monorepo Workspace Patterns
Workspace Configuration
All major package managers support workspaces via package.json:
// package.json (root)
{
"private": true, // root must be private
"workspaces": [
"packages/*",
"apps/*"
]
}
For pnpm, use pnpm-workspace.yaml instead of package.json workspaces.
Internal Package References
// apps/web/package.json
{
"dependencies": {
"@myorg/shared": "workspace:*", // pnpm/yarn syntax
"@myorg/utils": "*" // npm resolves from workspace
}
}
Turborepo Integration
Turborepo orchestrates builds across workspaces with caching and parallelism:
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"], // build dependencies first
"outputs": ["dist/**"]
},
"test": {
"dependsOn": ["build"]
},
"lint": {},
"dev": {
"cache": false,
"persistent": true
}
}
}
# Run build across all packages in dependency order with caching
turbo run build
# Run in specific packages
turbo run build --filter=@myorg/api
# Run dev servers in parallel
turbo run dev
package.json Anatomy
{
// Identity
"name": "@myorg/my-package", // scoped package name
"version": "1.2.3", // semver version
"description": "A brief description",
"license": "MIT",
"author": "Your Name <you@example.com>",
"repository": {
"type": "git",
"url": "https://github.com/myorg/my-package"
},
// Module system
"type": "module", // treat .js as ESM (omit for CJS)
// Entry points
"main": "./dist/index.cjs", // CJS entry (Node.js require)
"module": "./dist/index.mjs", // ESM entry (bundlers)
"types": "./dist/index.d.ts", // TypeScript types
// Modern exports map (preferred over main/module/types)
"exports": {
".": {
"types": "./dist/index.d.ts", // must come first
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./utils": {
"types": "./dist/utils.d.ts",
"import": "./dist/utils.mjs",
"require": "./dist/utils.cjs"
}
},
// What gets published
"files": ["dist", "README.md", "LICENSE"],
// Runtime requirements
"engines": {
"node": ">=18.0.0"
},
// Dependencies
"dependencies": {
"express": "^4.18.0"
},
"devDependencies": {
"typescript": "~5.4.0",
"vitest": "^1.0.0"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"react": { "optional": true }
},
// Override transitive dependency versions
"overrides": { // npm
"lodash": "4.17.21"
},
"resolutions": { // yarn
"lodash": "4.17.21"
},
// Scripts
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"test": "vitest run",
"lint": "eslint .",
"typecheck": "tsc --noEmit",
"prepublishOnly": "npm run build && npm run test"
}
}
Key Fields Explained
| Field | Purpose |
|---|---|
type: "module" |
Treat .js files as ESM. Without it, Node.js treats them as CJS. |
exports |
Modern entry point map. Supports conditional exports for CJS/ESM/types. |
files |
Whitelist of files to include in the published package. Reduces package size. |
engines |
Declare minimum Node.js version. Enforced by npm with engine-strict=true. |
peerDependencies |
Dependencies the consumer must provide. Common for plugins and frameworks. |
overrides / resolutions |
Force specific versions of transitive dependencies for security patches or compatibility. |
Publishing Packages
Pre-publish Checklist
- Set
"files"inpackage.jsonto include only necessary files. - Set
"prepublishOnly"script to build and test before publish. - Verify the package contents:
npm pack --dry-run. - Ensure
"types"or"exports"types conditions point to valid.d.tsfiles. - Test the package locally:
npm linkornpm pack && npm install ./my-package-1.0.0.tgz.
Publishing Commands
# Log in to npm
npm login
# Publish a public scoped package
npm publish --access public
# Publish with provenance (links package to source commit)
npm publish --provenance --access public
# Publish a pre-release version
npm version prerelease --preid=beta
npm publish --tag beta
# Deprecate a version
npm deprecate @myorg/my-package@1.0.0 "Use v2 instead"
# View published package info
npm view @myorg/my-package
Provenance
npm provenance (npm 9.5+) cryptographically links a published package to its source commit and build environment via Sigstore. Enable it in CI:
# GitHub Actions
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Consumers can verify provenance on npmjs.com or with npm audit signatures.
Private Registries
.npmrc Configuration
# .npmrc (project-level)
# Scoped registry — all @myorg packages come from GitHub Packages
@myorg:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
# Scoped registry — Artifactory
@internal:registry=https://artifactory.example.com/api/npm/npm-local/
//artifactory.example.com/api/npm/npm-local/:_authToken=${ARTIFACTORY_TOKEN}
# Default registry for everything else
registry=https://registry.npmjs.org/
GitHub Packages
// package.json
{
"name": "@myorg/my-package",
"publishConfig": {
"registry": "https://npm.pkg.github.com"
}
}
npm login --registry=https://npm.pkg.github.com --scope=@myorg
npm publish
Artifactory / Verdaccio
# Set registry for a scope
npm config set @internal:registry https://artifactory.example.com/api/npm/npm-local/
# Authenticate
npm login --registry=https://artifactory.example.com/api/npm/npm-local/
Best Practices
Choose one package manager per project and commit its lockfile. Do not mix lockfiles.
Use
npm ci(orpnpm install --frozen-lockfile) in CI pipelines for deterministic installs.Pin exact TypeScript versions (
~5.4.0) in devDependencies. TypeScript minor versions can introduce stricter checks that break builds.Use
workspace:*protocol (pnpm/yarn) for internal package references so they always resolve to the local version.Enable
engine-strictin.npmrcto enforce theenginesfield and catch Node.js version mismatches early.Use
"files"inpackage.jsonto explicitly whitelist published files. This prevents accidentally shipping source, tests, or credentials.Set up
prepublishOnlyto build and test before every publish.Use
"exports"instead of"main"for new packages. It provides explicit entry points and prevents deep imports into private modules.Audit dependencies regularly with
npm auditorpnpm audit. Configureaudit-level=moderatein.npmrcfor CI gates.Use Corepack to pin the package manager version across your team:
corepack enable corepack use pnpm@9This adds a
"packageManager"field topackage.jsonthat Corepack enforces.