LPM-cli

Migrating from npm

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

lpm migrate converts an npm project to LPM in one command. It detects the source package manager, parses package-lock.json, writes lpm.lock + lpm.lockb, 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 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 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's lockfile shape.
  3. Write — emits lpm.lock + lpm.lockb. Backs up package-lock.json to package-lock.json.backup.
  4. .npmrc — adds LPM-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 lpm.lockb .npmrc package.json
git rm package-lock.json package-lock.json.backup .npmrc.backup     # if you don't want them committed
git commit -m "Migrate to LPM"

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 (npm-flat → LPM's isolated layout); your imports continue to work because LPM'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 honors .npmrc for routing — see Registries.

What changes

  • package-lock.jsonlpm.lock + lpm.lockb. Both are committed, both should travel with the repo.
  • node_modules/ layout stays hoisted (npm-style flat) for single packages — same shape your project is on today. Workspaces auto-flip to isolated (pnpm-style symlinks) for phantom-dep catching; force-flat 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's isolated layout doesn't hoist — undeclared imports become hard errors. Add the missing entries to dependencies.
  • Lifecycle scripts don't run on lpm install by default. If your project relies on postinstall (esbuild, sharp, etc.), run lpm rebuild after install, then approve the script-running packages with lpm approve-scripts so future installs don't need re-approval.

See also