LPM-cli

npm compatibility

What works the same as npm, what's different, and what LPM adds on top.

LPM 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 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 pin source AND enforced — install aborts if the effective Node version doesn't satisfy the constraint
package.json > engines.lpmEnforced against the running CLI version (npm only enforces with engine-strict=true; LPM 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 namespreinstall, install, postinstall execute (see Differences). prepare, prepublishOnly, preuninstall, uninstall, postuninstall are recognized for detection — declaring them puts the package in lpm audit and the lpm approve-scripts review queue, but the install pipeline never runs them.

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 defaults differ where npm's defaults aren't safe, and a handful of legacy npm behaviors are simplified.

Lifecycle scripts: deny by default

npm runs preinstall / install / postinstall for every package by default. LPM doesn't.

lpm install        # downloads + links, no script execution
lpm rebuild        # runs scripts for trusted packages (second step)

To match npm's behavior:

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 starts with npm's flat node_modules/ layout for single-package projects. 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 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       # keep npm-style flat for workspaces

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 + lpm.lockb

npm produces package-lock.json. LPM produces lpm.lock (TOML, git-diffable) + lpm.lockb (binary, mmap). Both are committed.

Convert with:

lpm migrate

See Migrating from npm.

Save policy: never *

npm's npm install <pkg> saves ^resolvedVersion. LPM does the same — but LPM also enforces that * can never become a default. Wildcards must be requested per-package via pkg@*. See Save policy.

Cooldown on recent versions

LPM blocks installs of versions published less than 24h ago by default. npm doesn't. Override with lpm install --allow-new. 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 enforces by default — install / rebuild / add abort when the workspace root's engines.lpm constraint doesn't match the running CLI version, or engines.node doesn't match the effective Node runtime.

lpm install --no-engine-strict     # opt out 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 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 adds

Things LPM 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
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
Path-selector overrides (baz>foo)package.json lpm.overrides
Multi-registry routing via .npmrc (lpm.dev + 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 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 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'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