npm compatibility
What works the same as npm, what's different, and what LPM CLI adds on top.
LPM CLI is drop-in compatible with the npm ecosystem. Reads package.json. Resolves npm-flavored semver. Materializes node_modules/. Installs from registry.npmjs.org by default. Honors .npmrc for routing and auth. Forwards subprocess exit codes. Almost any npm-shaped project works without conversion — lpm install produces a runnable tree.
This page is the explicit scope: what carries over verbatim, what's different, and what LPM CLI adds.
Carries over verbatim
| Surface | Status |
|---|---|
package.json > dependencies / devDependencies / peerDependencies / optionalDependencies | Read identically |
package.json > scripts | Run via lpm run <name> (or bare lpm <name> as shorthand) |
package.json > bin (string or map form) | Both shapes accepted |
package.json > files | Honored at publish time |
package.json > engines.node | Honored as a root pin source and enforced for both the root and selected dependency versions; required mismatches abort and optional-only incompatible packages are skipped |
package.json > engines.lpm | Enforced against the running CLI version (npm only enforces with engine-strict=true; LPM CLI enforces by default) |
package.json > workspaces (array or object form) | Both shapes accepted |
package.json > overrides | npm-style overrides honored (lower precedence than lpm.overrides) |
package.json > resolutions | yarn-style resolutions honored |
package.json > catalogs and pnpm-workspace.yaml > catalog / catalogs | Centralized version catalogs |
.npmrc | Read for registry=, @scope:registry=, and //host/:_authToken= lines |
| Semver dialect | ^, ~, ` |
| Lifecycle script names | Bare root installs run pnpm:devPreinstall, then preinstall, install, postinstall, preprepare, prepare, and postprepare. Dependency packages execute only preinstall, install, and postinstall through the trust-gated rebuild path (see Differences); dependency prepare, prepublishOnly, preuninstall, uninstall, and postuninstall are recognized for detection but not executed. |
If a project's only contract with npm is "I have a package.json with deps and scripts," that project works unchanged.
Differences
The differences are deliberate — LPM CLI defaults differ where npm's defaults aren't safe, and a handful of legacy npm behaviors are simplified.
Dependency lifecycle scripts: deny by default
npm runs preinstall / install / postinstall for every dependency package by default. LPM CLI doesn't. Bare lpm install still runs the root project's install lifecycle, including prepare; dependency package scripts stay behind the approval gate.
lpm install # root lifecycle + downloads + links; dependency scripts blocked
lpm rebuild # manually runs scripts for trusted packagesTo run dependency scripts during install:
lpm install --policy=allow # or --yoloTo approve packages for the default deny path: lpm approve-scripts walks the blocked set interactively.
This is the single biggest behavioral difference. See Security & audit for why.
node_modules layout: hoisted by default, isolated for workspaces and peer conflicts
LPM CLI starts single-package projects in its v2 hoisted virtual-store layout: declared root deps are exposed at project node_modules/<dep>, and package-local dependency links live inside shared store link entries. This is compatible with normal Node resolution, but it is not npm-v3-style "flatten every transitive to the project root." Workspaces auto-flip to isolated (pnpm-style symlinks into the global virtual store) — phantom-dep bugs are most expensive in monorepos, so the safer layout becomes the default there. If a default-hoisted resolve finds incompatible peer requirements, LPM CLI also auto-switches that project to isolated and records the decision in lpm.lock. Force a specific mode:
lpm install --linker=isolated # opt into pnpm-style for non-workspaces
lpm install --linker=hoisted # use LPM CLI's hoisted virtual-store layoutOr persist it in package.json > lpm > linker / ~/.lpm/config.toml > linker. Explicit linker settings opt out of peer-conflict auto-switching.
Under isolated layout, code that imports a dep it didn't declare in dependencies (phantom-dep access) breaks. npm's hoisting silently allowed it; isolated doesn't. Add the missing entries to dependencies — the fix is one-line per package.
Lockfile: lpm.lock + optional lpm.lockb
npm produces package-lock.json. LPM CLI always produces lpm.lock (TOML, git-diffable, authoritative). It also produces lpm.lockb when the current binary wire format can represent the graph. Commit lpm.lock always; commit lpm.lockb when it exists.
Convert with:
lpm migrateSee Migrating from npm.
Save policy: never *
npm's npm install <pkg> saves ^resolvedVersion. LPM CLI does the same — but LPM CLI also enforces that * can never become a default. Wildcards must be requested per-package via pkg@*. See Save policy.
Cooldown on recent versions
LPM CLI blocks direct/root dependency versions published less than 24h ago by default. npm doesn't. Override with lpm install --allow-new, or opt into strict transitive cooldown with minimumReleaseAgePolicy: "strict". See Recently published packages.
engines enforced by default
npm reads engines.node / engines.npm and only enforces them when engine-strict=true is set in .npmrc. LPM CLI enforces by default: workspace-root engines.lpm and engines.node mismatches abort, as do incompatible required dependencies. An incompatible package reachable only through optional edges is skipped. Dependency Node ranges are stored in lpm.lock and revalidated on warm, frozen, and offline installs.
lpm install --no-engine-strict # warning-only for this invocationOr persistent in package.json > lpm > engineStrict = false (per-project) or ~/.lpm/config.toml > engine-strict = false (per-user). See lpm install § engines enforcement.
npm publish --otp → lpm publish with otpRequired
npm prompts for OTP at publish time when 2FA is enabled. LPM CLI mirrors this; configure via lpm.json > publish.npm.otpRequired = true to prompt up-front (saves a round-trip). See lpm.json publish.npm.
What LPM CLI adds
Things LPM CLI offers on top of the npm-compatible baseline:
| Feature | Where |
|---|---|
Strict trust binding for lifecycle scripts ({name, version, integrity, scriptHash}) | lpm approve-scripts |
| Behavioral analysis tags | lpm audit, lpm query |
| Registry signature audit and optional install-time verification | lpm audit signatures, lpm config signatures |
| npm publish-trust and verified-provenance no-downgrade policy | lpm config trust-policy |
| Provenance drift detection | Security & audit § Layer 4 |
| Triage gate | Security & audit § Layer 5 |
| Local patches with integrity binding | lpm patch |
| Dual-format lockfile | Lockfile |
Workspace --affected + topology-aware caching | Workspaces, Task runner |
workspace:* protocol | Workspaces |
| Catalog protocol | Workspaces |
Native jsr: dependency specs | lpm install, Registries |
Path-selector overrides (baz>foo) | package.json lpm.overrides |
Multi-registry routing via .npmrc (LPM.dev Registry + npm + private in one project) | Registries |
Source delivery (lpm add) | lpm add |
| Built-in lazy-downloaded tools (oxlint, biome) | lpm lint, lpm fmt |
| Zero-config dev server with HTTPS, tunnel, services | lpm dev |
| SE-0292 Swift Package Registry support | Swift Package Registry |
What LPM CLI does not do
Honest scope:
- Lockfile-only PR review — no first-class diff tool yet beyond
git diff lpm.lock. Use the deterministic format and your PR review tool of choice. - Reading the
pnpm.*namespace at install time — plainlpm installdoes not consumepnpm.overrides,pnpm.patchedDependencies, orpnpm.peerDependencyRules.lpm migratetranslates all three into theirlpm.*equivalents in one pass; install-time warnings (and stablelpm doctor --jsoncodespnpm_overrides_drift,pnpm_patches_drift,pnpm_peer_rules_drift) flag any post-migration drift. See Migrating from pnpm. - Yarn berry's
.pnp.cjs— no Plug'n'Play support. LPM CLI materializesnode_modules/(isolated or hoisted). - GUI — there's no
lpm-vault-style desktop app shipped today. The CLI is the surface.
Compatibility with the broader ecosystem
- Tools that read
package.json(TypeScript, ESLint, build tools): work unchanged. - Tools that walk
node_modules/(most bundlers, test runners): work under both isolated and hoisted layouts. - Tools that hard-code
package-lock.json: don't see LPM CLI's lockfile. Either runlpm migrateto addlpm.lockalongside, or uselpm install --linker=hoistedfor a closer-to-npm shape.
See also
lpm migrate— convert an npm/pnpm/yarn/bun project- Migrating from npm — walkthrough
- Registries —
.npmrcrouting - Save policy — npm-compatible defaults with stricter rails
- Security & audit — what's different about LPM CLI's trust model