LPM CLI

Migrating from npm

Convert a package-lock.json project to LPM CLI with rollback safety.

lpm migrate converts an npm project to LPM CLI in one command. It detects the source package manager, parses package-lock.json, writes lpm.lock plus lpm.lockb when the graph fits the binary format, optionally configures .npmrc, runs lpm install against the new lockfile, and (optionally) verifies that build + test still pass. Every file it touches is backed up first — --rollback restores the original state.

This guide is the migration walkthrough. For the full flag reference, see lpm migrate.

Prerequisites

  • LPM CLI installed — see Installation.
  • A working package-lock.json in the project root.
  • A reasonably-clean working tree (commit before migrating, just to be safe).

1. Preview

lpm migrate --dry-run

Detects npm, parses package-lock.json, converts to the LPM CLI format, but writes nothing. Reports the package count, any packages that couldn't be converted (with reasons), and the workspace member count.

If anything looks wrong, fix it before doing the real run.

2. Run the migration

lpm migrate

What runs, in order:

  1. Pre-flight — confirms package.json exists, refuses to clobber an existing lpm.lock.
  2. Detect, parse, convert — reads package-lock.json, converts entries to LPM CLI's lockfile shape.
  3. Write — emits lpm.lock and, when supported by the binary format, lpm.lockb. Backs up package-lock.json to package-lock.json.backup.
  4. .npmrc — adds LPM CLI-aware config to .npmrc (or creates one). Backs up the original to .npmrc.backup.
  5. Install — runs lpm install against the new lockfile (lockfile fast-path, no re-resolution).
  6. Verify — runs build + test if those scripts exist, to confirm nothing broke.
  7. CI hint — when a CI platform is detected (.github/workflows, .gitlab-ci.yml, etc.), prints a hint suggesting you re-run with lpm migrate --ci to generate a workflow template.
  8. Summary — what was done, where backups live, and how to roll back.

The migrate flow is non-interactive — every step runs straight through. If lpm.lock already exists, pass --force to overwrite it.

3. Verify

After migration, run a fresh install + your usual checks:

lpm install --offline       # confirm reproducibility from the new lockfile
lpm test
lpm lint
lpm fmt --check

If anything's broken, see Rollback below.

4. Commit

git add lpm.lock .npmrc package.json
test ! -f lpm.lockb || git add lpm.lockb
git rm package-lock.json package-lock.json.backup .npmrc.backup     # if you don't want them committed
git commit -m "Migrate to LPM CLI"

You probably don't want the .backup files in the repo. Add them to .gitignore:

*.backup

Rollback

If anything goes wrong (or the migration was a mistake):

lpm migrate --rollback

Walks the backups created by the previous run and restores package-lock.json, .npmrc (if it was touched), and .gitattributes to their pre-migration state. Files the migration newly created — lpm.lock, lpm.lockb, any new .gitattributes, and any patch files copied to patches/ — are removed. package.json is only rolled back if the migration mutated it (i.e., when pnpm.* blocks were translated; usually not on the npm path). Safe to run repeatedly.

Optional flags

FlagEffect
--dry-runParse + convert only, write nothing
--forceOverwrite an existing lpm.lock
-y, --yesReserved. The flow is non-interactive today, so this flag is a no-op. It does NOT imply --force.
--no-installConvert lockfile only, skip the install step
--skip-verifySkip the build + test verification
--no-npmrcDon't touch .npmrc
--ciAlso generate a CI workflow template for the detected platform
--no-ciSuppress the CI template hint
--rollbackRestore from .backup files

What stays the same

  • package.jsondependencies, devDependencies, scripts, workspaces are read as-is. No renames, no deletions.
  • node_modules/ — gets rebuilt by the install step. The on-disk shape may change from npm's flat tree to LPM CLI's v2 hoisted layout, or to LPM CLI's isolated layout for workspaces and peer-conflict installs; declared imports continue to work because LPM CLI's resolver respects the same semver semantics npm does.
  • .npmrc — any existing @scope:registry= lines for private registries survive the migration. The migration additively appends @lpm.dev:registry=https://lpm.dev/api/registry/ (unless you pass --no-npmrc); if your .npmrc already declared that scope, the step is a no-op. LPM CLI honors .npmrc for routing — see Registries.

What changes

  • package-lock.jsonlpm.lock plus lpm.lockb when the graph fits the binary format. Commit lpm.lock always and lpm.lockb when present.
  • node_modules/ layout starts hoisted for single packages in LPM CLI's v2 virtual-store layout. Workspaces auto-flip to isolated (pnpm-style symlinks) for phantom-dep catching; force hoisted with lpm install --linker=hoisted or package.json > lpm > linker = "hoisted".
  • New .gitattributes line: lpm.lockb binary (so git doesn't try to text-merge the binary lockfile).

Common pitfalls

  • The lockfile is now strict. lpm install --offline will fail on any drift between package.json and lpm.lock. Whichever wins, run lpm install (without --offline) once after migration to settle them.
  • Phantom-dep code breaks under isolated layout. If you imported a package you didn't declare in dependencies, npm hoisted it into node_modules/ and your code worked anyway. LPM CLI's isolated layout doesn't hoist — undeclared imports become hard errors. Add the missing entries to dependencies.
  • Dependency lifecycle scripts don't run on lpm install by default. Bare installs run the root project's lifecycle, including prepare, but dependency postinstall scripts (esbuild, sharp, etc.) stay blocked until you run lpm rebuild and approve the script-running packages with lpm approve-scripts.

See also