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.jsonin the project root. - A reasonably-clean working tree (commit before migrating, just to be safe).
1. Preview
lpm migrate --dry-runDetects 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 migrateWhat runs, in order:
- Pre-flight — confirms
package.jsonexists, refuses to clobber an existinglpm.lock. - Detect, parse, convert — reads
package-lock.json, converts entries to LPM's lockfile shape. - Write — emits
lpm.lock+lpm.lockb. Backs uppackage-lock.jsontopackage-lock.json.backup. .npmrc— adds LPM-aware config to.npmrc(or creates one). Backs up the original to.npmrc.backup.- Install — runs
lpm installagainst the new lockfile (lockfile fast-path, no re-resolution). - Verify — runs
build+testif those scripts exist, to confirm nothing broke. - CI hint — when a CI platform is detected (
.github/workflows,.gitlab-ci.yml, etc.), prints a hint suggesting you re-run withlpm migrate --cito generate a workflow template. - 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 --checkIf 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:
*.backupRollback
If anything goes wrong (or the migration was a mistake):
lpm migrate --rollbackWalks 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
| Flag | Effect |
|---|---|
--dry-run | Parse + convert only, write nothing |
--force | Overwrite an existing lpm.lock |
-y, --yes | Reserved. The flow is non-interactive today, so this flag is a no-op. It does NOT imply --force. |
--no-install | Convert lockfile only, skip the install step |
--skip-verify | Skip the build + test verification |
--no-npmrc | Don't touch .npmrc |
--ci | Also generate a CI workflow template for the detected platform |
--no-ci | Suppress the CI template hint |
--rollback | Restore from .backup files |
What stays the same
package.json—dependencies,devDependencies,scripts,workspacesare 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.npmrcalready declared that scope, the step is a no-op. LPM honors.npmrcfor routing — see Registries.
What changes
package-lock.json→lpm.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 withlpm install --linker=hoistedorpackage.json > lpm > linker = "hoisted".- New
.gitattributesline:lpm.lockb binary(so git doesn't try to text-merge the binary lockfile).
Common pitfalls
- The lockfile is now strict.
lpm install --offlinewill fail on any drift betweenpackage.jsonandlpm.lock. Whichever wins, runlpm 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 intonode_modules/and your code worked anyway. LPM's isolated layout doesn't hoist — undeclared imports become hard errors. Add the missing entries todependencies. - Lifecycle scripts don't run on
lpm installby default. If your project relies onpostinstall(esbuild, sharp, etc.), runlpm rebuildafter install, then approve the script-running packages withlpm approve-scriptsso future installs don't need re-approval.
See also
- Migrating from pnpm — same shape, different source lockfile
lpm migrate— full flag reference- Registries — how
.npmrcrouting works after migration - Lockfile — what gets committed and why
lpm install --linker— keeping the flat layout if you need to