LPM CLI

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

SurfaceStatus
package.json > dependencies / devDependencies / peerDependencies / optionalDependenciesRead identically
package.json > scriptsRun via lpm run <name> (or bare lpm <name> as shorthand)
package.json > bin (string or map form)Both shapes accepted
package.json > filesHonored at publish time
package.json > engines.nodeHonored 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.lpmEnforced 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 > overridesnpm-style overrides honored (lower precedence than lpm.overrides)
package.json > resolutionsyarn-style resolutions honored
package.json > catalogs and pnpm-workspace.yaml > catalog / catalogsCentralized version catalogs
.npmrcRead for registry=, @scope:registry=, and //host/:_authToken= lines
Semver dialect^, ~, `
Lifecycle script namesBare 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 packages

To run dependency scripts during install:

lpm install --policy=allow      # or --yolo

To 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 layout

Or 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 migrate

See 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 invocation

Or 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 --otplpm 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:

FeatureWhere
Strict trust binding for lifecycle scripts ({name, version, integrity, scriptHash})lpm approve-scripts
Behavioral analysis tagslpm audit, lpm query
Registry signature audit and optional install-time verificationlpm audit signatures, lpm config signatures
npm publish-trust and verified-provenance no-downgrade policylpm config trust-policy
Provenance drift detectionSecurity & audit § Layer 4
Triage gateSecurity & audit § Layer 5
Local patches with integrity bindinglpm patch
Dual-format lockfileLockfile
Workspace --affected + topology-aware cachingWorkspaces, Task runner
workspace:* protocolWorkspaces
Catalog protocolWorkspaces
Native jsr: dependency specslpm 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, serviceslpm dev
SE-0292 Swift Package Registry supportSwift 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 — plain lpm install does not consume pnpm.overrides, pnpm.patchedDependencies, or pnpm.peerDependencyRules. lpm migrate translates all three into their lpm.* equivalents in one pass; install-time warnings (and stable lpm doctor --json codes pnpm_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 materializes node_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 run lpm migrate to add lpm.lock alongside, or use lpm install --linker=hoisted for a closer-to-npm shape.

See also