# Lockfile (/docs/lockfile)



`snpm-lock.yaml` is snpm's native lockfile. It pins the exact resolved version, tarball, and integrity hash for every package in the dependency graph, making installs deterministic across machines and CI runners.

Schema [#schema]

The lockfile is YAML and currently uses schema version `1`.

```yaml title="snpm-lock.yaml"
version: 1

root:
  dependencies:
    express:
      requested: "^4.18.0"
      version: 4.18.2
    typescript:
      requested: ^5.4.0
      version: 5.4.5
      optional: false

packages:
  express@4.18.2:
    name: express
    version: 4.18.2
    tarball: https://registry.npmjs.org/express/-/express-4.18.2.tgz
    integrity: sha512-...
    dependencies:
      body-parser: body-parser@1.20.1
      cookie: cookie@0.5.0
    hasBin: false

  body-parser@1.20.1:
    name: body-parser
    version: 1.20.1
    tarball: https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz
    integrity: sha512-...
    dependencies: {}
```

Key fields:

* **`root.dependencies`** — direct dependencies declared in `package.json`. Each entry records the originally `requested` range plus the resolved `version` (and `optional` if the dep is optional).
* **`packages.<name>@<version>`** — the resolved package. Stores `name`, `version`, `tarball`, `integrity`, transitive `dependencies` (map name → `name@version` of the resolved entry), `bundledDependencies` (if any), and `hasBin` / `bin` for packages that ship binaries.

Workspaces share a single `snpm-lock.yaml` at the workspace root.

When the lockfile is written [#when-the-lockfile-is-written]

The lockfile updates whenever the install plan changes — `snpm add`, `snpm remove`, `snpm upgrade`, or `snpm install` with new packages.

snpm only writes a dev-inclusive lockfile when `include_dev = true`. With `--production` (or running an install path that skips dev), the existing lockfile is reused but not rewritten, so dev resolution does not get clobbered.

Frozen, prefer, fix [#frozen-prefer-fix]

| Mode      | CLI flag                                                               | Behavior                                                          |
| --------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------- |
| Frozen    | `--frozen-lockfile` (alias `--immutable`), or `SNPM_FROZEN_LOCKFILE=1` | Fail if the lockfile is missing or does not match the manifest.   |
| No frozen | `--no-frozen-lockfile`                                                 | Ignore lockfile data and re-resolve.                              |
| Prefer    | `--prefer-frozen-lockfile`                                             | Reuse lockfile entries that are still valid, re-resolve the rest. |
| Fix       | `--fix-lockfile` (install only)                                        | Re-resolve drifted entries while keeping unchanged ones pinned.   |

Pass these flags globally (`snpm --frozen-lockfile install`) and they propagate to every install-like subcommand, or pass them directly to the subcommand.

Integrity marker [#integrity-marker]

After a successful install, snpm writes `node_modules/.snpm-integrity` containing a lockfile-derived hash. The next install reads this marker first and short-circuits if the hash still matches — that is the "hot install" path that completes in tens of milliseconds.

In workspaces, the integrity marker is written per project so changes to one project's deps don't invalidate the others.

Importing other lockfiles [#importing-other-lockfiles]

When `snpm-lock.yaml` is missing, snpm can seed the first install from a compatible lockfile and then take over with its own format:

| Source | Files                                                                 |
| ------ | --------------------------------------------------------------------- |
| pnpm   | `pnpm-lock.yaml`, branch lockfiles like `pnpm-lock.feature!name.yaml` |
| Bun    | `bun.lock`                                                            |
| Yarn   | `yarn.lock`                                                           |
| npm    | `npm-shrinkwrap.json`, `package-lock.json`                            |

Imported data is used only as compatibility input. Once the install completes, `snpm-lock.yaml` is the source of truth. Subsequent installs read snpm's lockfile.

Workspaces [#workspaces]

A workspace has exactly one `snpm-lock.yaml` at the root — it covers every workspace project:

```
my-monorepo/
├── snpm-workspace.yaml
├── snpm-lock.yaml           ← single lockfile
├── packages/
│   ├── ui/package.json
│   └── utils/package.json
└── apps/
    └── web/package.json
```

Version control [#version-control]

**Always commit `snpm-lock.yaml`.**

It is small, human-readable, and the only guarantee that local builds match CI and production.

Merge conflicts [#merge-conflicts]

When you hit a conflict in `snpm-lock.yaml`:

1. Resolve conflicts in every affected `package.json` first.
2. Delete `snpm-lock.yaml`.
3. Run `snpm install`.

```bash
git checkout --theirs package.json apps/*/package.json
# resolve manually if needed
rm snpm-lock.yaml
snpm install
git add snpm-lock.yaml
```

If you want to keep the existing resolution where possible, run `snpm install --fix-lockfile` instead — it only re-resolves entries that drifted.

Best practices [#best-practices]

* **Always commit the lockfile.**
* **Use `--frozen-lockfile` in CI** — it catches missed commits and prevents quiet upgrades.
* **Review the diff in PRs** — the YAML format makes it readable; new tarball URLs or integrity changes show up cleanly.
* **Prefer `--fix-lockfile` over deleting it** when only a small subset of deps needs to be re-resolved.
* **Use `snpm why <pkg>`** to trace why an unexpected package landed in the lockfile.
