Skip to main content
Technology & EngineeringPackage Management282 lines

Lockfile Management

Lock file strategies for deterministic installs across npm, pnpm, and Yarn

Quick Summary34 lines
You are an expert in lock file management for JavaScript/TypeScript projects, including strategies for deterministic installs, resolving conflicts, and maintaining lock file hygiene across npm, pnpm, and Yarn.

## Key Points

- Every resolved package version
- The resolved tarball URL or integrity hash
- The dependency tree structure (which packages depend on which)
- Integrity checksums (SHA-512 hashes) for tarball verification
1. A committed lock file
2. Using `--frozen-lockfile` (pnpm), `--immutable` (Yarn), or `npm ci` in CI
- **v1**: npm 5-6. Flat dependency list.
- **v2**: npm 7-8. Includes both v1 and v2 data for backward compatibility.
- **v3**: npm 9+. Drops v1 data, smaller file size.
- **npm workspaces**: Single `package-lock.json` at root.
- **pnpm workspaces**: Single `pnpm-lock.yaml` at root (configurable with `shared-workspace-lockfile`).
- **Yarn Berry workspaces**: Single `yarn.lock` at root.

## Quick Example

```yaml
# Yarn Berry (.yarnrc.yml)
resolutions:
  lodash: 4.17.21
```

```json
{
  "resolutions": {
    "lodash": "4.17.21"
  }
}
```
skilldb get package-management-skills/Lockfile ManagementFull skill: 282 lines
Paste into your CLAUDE.md or agent config

Lock File Management — Package Management

You are an expert in lock file management for JavaScript/TypeScript projects, including strategies for deterministic installs, resolving conflicts, and maintaining lock file hygiene across npm, pnpm, and Yarn.

Core Philosophy

Overview

Lock files record the exact resolved version of every dependency in a project's dependency tree. They ensure that every developer, CI server, and production deployment installs the same dependency versions, regardless of when install is run. Without a lock file, version ranges in package.json could resolve to different versions at different times.

Core Concepts

Lock File by Package Manager

ManagerLock fileFormat
npmpackage-lock.jsonJSON
pnpmpnpm-lock.yamlYAML
Yarn Classicyarn.lockCustom
Yarn Berryyarn.lockYAML (v2+ format)

What a Lock File Contains

  • Every resolved package version
  • The resolved tarball URL or integrity hash
  • The dependency tree structure (which packages depend on which)
  • Integrity checksums (SHA-512 hashes) for tarball verification

Deterministic Installs

A deterministic install means: given the same package.json + lock file, every install produces the exact same node_modules tree (or PnP map). This requires:

  1. A committed lock file
  2. Using --frozen-lockfile (pnpm), --immutable (Yarn), or npm ci in CI

lockfileVersion

npm's package-lock.json has evolved:

  • v1: npm 5-6. Flat dependency list.
  • v2: npm 7-8. Includes both v1 and v2 data for backward compatibility.
  • v3: npm 9+. Drops v1 data, smaller file size.

Implementation Patterns

Installing with Lock File Integrity

# npm: clean install from lock file (deletes node_modules first)
npm ci

# pnpm: fail if lockfile is out of date
pnpm install --frozen-lockfile

# Yarn Berry: fail if lockfile would change
yarn install --immutable

Use these commands in CI to guarantee deterministic builds. Never use npm install in CI — it may modify the lock file.

Updating Dependencies

# npm: update within range and rewrite lock file
npm update

# npm: update a specific package
npm update lodash

# pnpm: update interactively
pnpm update --interactive

# pnpm: update all to latest (ignoring ranges)
pnpm update --latest

# Yarn Berry: update within ranges
yarn up

# Yarn Berry: update to latest
yarn up '*'

Resolving Lock File Merge Conflicts

Lock file conflicts are common in active repositories. The correct resolution:

# npm: accept either side, then regenerate
git checkout --theirs package-lock.json
npm install

# pnpm: accept either side, then regenerate
git checkout --theirs pnpm-lock.yaml
pnpm install

# Yarn Berry: has built-in merge resolution
# First, accept both sides, then:
yarn install
# Yarn auto-resolves the lock file conflicts

Never manually edit lock files to resolve conflicts. Always regenerate through the package manager.

Deduplication

Over time, lock files accumulate duplicate versions of the same package:

# npm: deduplicate resolved versions
npm dedupe

# pnpm: deduplicate
pnpm dedupe

# Yarn Berry: deduplicate
yarn dedupe

# Check for duplicates without fixing
yarn dedupe --check

Auditing Lock File Contents

# List all resolved versions
npm ls

# Find why a specific package is installed
npm explain lodash
pnpm why lodash
yarn why lodash

# Check for duplicate versions
npm ls --all | grep "lodash@"

Overriding Resolved Versions

Sometimes you need to force a specific resolution:

// npm (package.json)
{
  "overrides": {
    "lodash": "4.17.21",
    "foo>bar": "2.0.0"
  }
}
// pnpm (package.json)
{
  "pnpm": {
    "overrides": {
      "lodash": "4.17.21"
    }
  }
}
# Yarn Berry (.yarnrc.yml)
resolutions:
  lodash: 4.17.21

Or in package.json for Yarn:

{
  "resolutions": {
    "lodash": "4.17.21"
  }
}

Enforcing a Single Package Manager

Use corepack and the packageManager field to ensure everyone uses the same tool:

{
  "packageManager": "pnpm@9.15.0",
  "engines": {
    "node": ">=20"
  }
}
corepack enable
# Now running `npm install` in a pnpm project will fail

Add an .npmrc or preinstall script as a second guard:

{
  "scripts": {
    "preinstall": "npx only-allow pnpm"
  }
}

Lock File in Monorepos

  • npm workspaces: Single package-lock.json at root.
  • pnpm workspaces: Single pnpm-lock.yaml at root (configurable with shared-workspace-lockfile).
  • Yarn Berry workspaces: Single yarn.lock at root.

All three managers use a single lock file for the entire monorepo, which ensures cross-workspace dependency consistency.

Reviewing Lock File Changes in PRs

Large lock file diffs are hard to review. Strategies:

# See a human-readable summary of what changed
npm diff

# For pnpm, compare resolved versions
pnpm ls --depth=0 > before.txt
# (after update)
pnpm ls --depth=0 > after.txt
diff before.txt after.txt

On GitHub, lock file diffs are collapsed by default. Use lockfile-lint or socket.dev for automated review.

Lock File Linting

npx lockfile-lint \
  --path package-lock.json \
  --type npm \
  --allowed-hosts npm \
  --validate-https

This checks that all resolved URLs use HTTPS and point to the official registry, catching supply chain tampering.

Best Practices

  • Always commit the lock file to source control. Never add it to .gitignore.
  • Use npm ci / --frozen-lockfile / --immutable in CI. Never run bare install.
  • Never manually edit lock files. Always regenerate through the package manager.
  • Run npm dedupe / pnpm dedupe / yarn dedupe periodically to reduce duplication.
  • Use packageManager in package.json with corepack to enforce the same package manager and version.
  • Review lock file changes in PRs. Unexpected new packages or registry URL changes may indicate supply chain attacks.
  • Use lockfile-lint to validate lock file integrity in CI.
  • When resolving merge conflicts, accept one side and regenerate — never hand-merge.
  • For applications, consider using exact versions in package.json plus the lock file for maximum reproducibility.
  • For libraries, use ^ ranges in package.json and do not publish the lock file (it is ignored by consumers anyway).

Common Pitfalls

  • Not committing the lock file: Results in non-deterministic installs. Every team member may get different versions.
  • Using npm install in CI instead of npm ci: npm install may update the lock file silently, meaning CI installs differ from what was tested locally.
  • Lock file from wrong package manager: Having both package-lock.json and pnpm-lock.yaml in the same project confuses tools and developers. Use only one.
  • Stale lock file: Running npm install without the lock file present (or after deleting it) resolves the latest versions within ranges, which may differ from what the team has tested.
  • Force-resolving conflicts incorrectly: Accepting "ours" or "theirs" on a lock file conflict without regenerating can leave the lock file inconsistent with package.json.
  • Publishing lock files in libraries: npm ignores lock files in dependencies. Publishing package-lock.json in a library adds size but provides no value.
  • Ignoring lockfileVersion upgrades: Upgrading npm may change lockfileVersion, causing churn. Coordinate npm version upgrades across the team.
  • Phantom dependencies after lock file regen: Regenerating a lock file may resolve newer transitive versions that introduce bugs or incompatibilities. Always run tests after regeneration.

Anti-Patterns

Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.

Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.

Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.

Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.

Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.

Install this skill directly: skilldb add package-management-skills

Get CLI access →