# Managed runtimes (/docs/dev/node-version-pinning)



LPM CLI manages project runtimes itself. It detects the versions your project asks for, downloads missing installs on demand, stores them under `~/.lpm/runtimes/`, and prepends the right `bin/` directories to `PATH` for every script you run.

Node and Bun are supported managed runtimes. Deno is still unsupported.

## Detection Order [#detection-order]

When [`lpm run`](/docs/dev/run), [`lpm dev`](/docs/dev/dev), [`lpm <file>` / `lpm exec`](/docs/dev/exec), [`lpm test`](/docs/dev/test), or [`lpm bench`](/docs/dev/bench) starts, LPM CLI resolves managed runtime pins in deterministic order: Node first, then Bun.

### Node [#node]

| Priority   | Source                        | Format                                                 |
| ---------- | ----------------------------- | ------------------------------------------------------ |
| 1          | `lpm.json > runtime.node`     | Semver: `">=22.0.0"`, `"22.5.0"`, `"22"`               |
| 2          | `package.json > engines.node` | Semver                                                 |
| 3          | `.nvmrc`                      | Plain version: `22.5.0`, `lts/iron`, `v22`             |
| 4          | `.node-version`               | Plain version                                          |
| (fallback) | None of the above             | Use the system `node` from `PATH` — no managed runtime |

`.nvmrc` lives in this list intentionally — projects migrating from `nvm` get Node pinning for free without writing `lpm.json`. New projects should prefer `lpm.json > runtime.node` because it sits next to the rest of the LPM CLI config.

### Bun [#bun]

| Priority   | Source                    | Format                                                      |
| ---------- | ------------------------- | ----------------------------------------------------------- |
| 1          | `lpm.json > runtime.bun`  | Exact, `latest`, `v1.3.14`, `bun-v1.3.14`, prefix, or range |
| (fallback) | No `runtime.bun` declared | Do not prepend managed Bun                                  |

`package.json > engines.bun` is recognized as a compatibility warning, but LPM CLI does not enforce it and does not use it as a managed-runtime pin. Use `lpm.json > runtime.bun` when scripts need Bun available.

## Auto-Install [#auto-install]

If a detected version is not already installed, LPM CLI downloads it before running the script unless auto-install is disabled:

```bash
LPM_NO_AUTO_INSTALL=true lpm dev
```

`LPM_NO_AUTO_INSTALL=true` applies to both Node and Bun. When auto-install is off and a managed runtime is missing, LPM CLI warns and leaves the system `PATH` in place for that runtime.

Node releases come from `https://nodejs.org/dist/index.json`, cached for 1 hour at `~/.lpm/runtimes/index-cache.json`. Downloads are SHA-256 verified against the matching `SHASUMS256.txt` entry.

Bun releases come from the GitHub releases API for `oven-sh/bun`, cached for 1 hour at `~/.lpm/runtimes/bun-index-cache.json`. Downloads use Bun's platform asset names such as `darwin-aarch64`, `linux-x64-musl`, `linux-x64-baseline`, and `windows-x64`. Downloads are SHA-256 verified against GitHub asset digests and/or `SHASUMS256.txt`; when both are present, both must verify.

## Storage Layout [#storage-layout]

```text
~/.lpm/runtimes/
├── node/
│   └── 22.12.0/
│       └── bin/
│           ├── node
│           ├── npm
│           └── npx
├── bun/
│   └── 1.3.14/
│       └── bin/
│           └── bun
├── index-cache.json
└── bun-index-cache.json
```

Each version is installed once and reused by every project on the machine. `LPM_HOME=<path>` moves the entire LPM CLI root, including `runtimes/`, which is useful for hermetic CI.

## Script PATH Order [#script-path-order]

When a script runs, LPM CLI builds `PATH` from:

1. Project-local `node_modules/.bin/`
2. Managed Node `bin/`, if a Node pin resolves to an installed version
3. Managed Bun `bin/`, if `runtime.bun` resolves to an installed version
4. The inherited `PATH`

This means locally-installed CLIs win over managed runtime tools, and managed Node wins over managed Bun when both provide a command with the same name.

`runtime.bun` only exposes `bun` to scripts. It does **not** switch LPM CLI script execution to `bun run`.

## Node Engine Enforcement [#node-engine-enforcement]

The workspace root's `engines.node` plays two roles. As a **detection source** it tells LPM CLI which managed Node runtime to install for `lpm dev` / `lpm run` when `runtime.node` is absent. As an **enforcement constraint** it gates `lpm install` / `lpm rebuild` / `lpm add`. Selected dependencies' `engines.node` ranges are install-time enforcement only: required mismatches abort, optional-only incompatible packages are skipped, and the ranges are persisted for lockfile replay.

```text
$ lpm install
Error: lpm::engine_mismatch
  × node version 18.20.4 does not satisfy required >=22.0.0 (from package.json
  │ > engines.node (compared against system PATH))
```

Resolution: install the matching managed runtime first (`lpm use node@22`), or relax the constraint.

`engines.bun` is not enforced in this first Bun runtime pass. Put the desired Bun version in `lpm.json > runtime.bun` to manage Bun availability.

## `lpm use` [#lpm-use]

```bash
lpm use node@22                 # install the latest 22.x, pin in lpm.json
lpm use node@lts                # install latest Node LTS
lpm use bun@1.3.14              # install + pin Bun
lpm use bun@latest              # install + pin latest Bun
lpm use --list bun              # list installed Bun versions
lpm use remove bun@1.3          # remove all installed 1.3.x Bun runtimes
```

`lpm use <runtime>@<spec>` writes the resolved version into `lpm.json > runtime.<runtime>`.
During a fresh install, the human output shows the resolved version, downloaded size, SHA-256 verification, extraction/linking, final runtime, and PATH hint.

`lpm use <runtime>@<spec> --pin` skips the download step. If the spec already matches an installed runtime, LPM CLI writes that exact installed version; otherwise it records the requested spec unchanged so a later `lpm dev` / `lpm run` can resolve or auto-install it.

`lpm use remove <runtime>@<spec>` deletes installed managed runtimes matching the spec without changing `lpm.json`. If the project still pins a matching runtime, LPM CLI warns because later commands can auto-install it again.

## Compatibility With `nvm` / `fnm` [#compatibility-with-nvm--fnm]

LPM CLI's Node management is independent. If you have `nvm` or `fnm` installed:

* They modify `PATH` in your shell, affecting `node` lookup outside LPM CLI.
* LPM CLI's Node detection respects `.nvmrc`, so a project pinned via `nvm` works under LPM CLI without converting.
* When LPM CLI runs a script with a managed Node pin, LPM CLI's runtime takes precedence over whatever `nvm` set in the shell.

You do not have to remove `nvm` to adopt LPM CLI. If a project has both `.nvmrc` and `lpm.json > runtime.node`, the `lpm.json` value wins.

## See Also [#see-also]

* [`lpm use`](/docs/dev/use) — install + pin command
* [`lpm.json` runtime](/docs/reference/lpm-json#runtime) — config field reference
* [Environment variables](/docs/reference/env-vars#install-pipeline) — `LPM_NO_AUTO_INSTALL`
* [`lpm dev`](/docs/dev/dev) — picks up runtime pins automatically
* [`lpm doctor`](/docs/infra/doctor) — checks managed runtime state
