lpm patch / patch-commit / patch-remove
Create, finalize, and remove local dependency patches with integrity binding.
lpm patch <selector> # step 1 — extract for editing
lpm patch-commit <staging_dir> # step 2 — finalize as a .patch file
lpm patch-remove <selector> # remove a registered patchTwo-step workflow for fixing a bug in a dependency without forking it. The patch travels with your repo under patches/<name>@<version>.patch and re-applies automatically on every subsequent lpm install.
Same shape as patch-package — but bound to the original store integrity, so a silently-republished version of the dep can never substitute for the version the patch was authored against.
The workflow
# 1. Extract a clean copy of the store entry to a staging dir
lpm patch lodash@4.17.21
# → /tmp/lpm-patch-XXXXXX/node_modules/lodash/ (path printed)
# 2. Edit files in the staging dir however you want
vim /tmp/lpm-patch-XXXXXX/node_modules/lodash/index.js
# 3. Produce the patch file and register it in package.json
lpm patch-commit /tmp/lpm-patch-XXXXXX
# → patches/lodash@4.17.21.patch
# → package.json :: lpm.patchedDependencies updatedAfter patch-commit, commit patches/ and package.json to your repo. The next lpm install (yours, your teammates', CI's) auto-applies the patch after linking.
lpm patch <selector>
lpm patch lodash # bare name — resolves against the project lockfile
lpm patch lodash@4.17.21 # exact pin — works without a lockfile
lpm patch lodash@^4.0.0 # semver range — resolves against the project lockfile| Selector | Notes |
|---|---|
Bare name (lodash) | Resolves to whatever exact version is in lpm.lock. Requires a lockfile (run lpm install first). |
Exact pin (lodash@4.17.21) | Works without a lockfile. The version must already be in the global store. |
Semver range (lodash@^4.0.0) | Resolves to the lockfile's pinned version. Requires a lockfile. |
Dist-tags (latest, next) | Not accepted. Pin to an exact version or range. |
Steps:
- Resolves the selector to an exact
(name, version)pair (using the lockfile for non-exact inputs). - Looks up
<name>@<version>in the global store. Errors with alpm install <name>@<version>hint if not present. - Copies the pristine store bytes into a unique staging directory under the OS temp root (
/tmp/lpm-patch-XXXXXX/node_modules/<name>/). "Pristine" matters: re-runninglpm patchon a project where this dep is already patched still seeds the staging dir from the upstream bytes, so the resulting diff is delta-from-upstream, not delta-from-previously-patched. - Writes a
.lpm-patch.jsonbreadcrumb in the staging root recording the resolvedname,version, exact-pinkey, and the store source path. - Prints the staging path. The dir is intentionally NOT auto-deleted — it must outlive the process so you can edit it.
lpm patch-commit <staging_dir>
lpm patch-commit /tmp/lpm-patch-XXXXXXSteps:
- Reads the breadcrumb to recover
(name, version, key). - Re-locates the store baseline from the live store (not the breadcrumb's recorded path), so a store relocation between
patchandpatch-commitdoesn't break the diff. - Generates a unified diff between the staging directory and the pristine baseline. Aborts if the diff is empty ("no changes detected") or contains binary file edits (patches must be text-only —
originalIntegrityalready covers binary drift on install). - Writes
<project>/patches/<key>.patchatomically (.tmpthen rename). Scoped names have/translated to__for cross-platform portability —@scope/pkg@1.0.0lands atpatches/@scope__pkg@1.0.0.patch. Mirrors pnpm. - Updates
package.json > lpm > patchedDependencieswith an entry recording the patch path and the original SRI integrity hash. - Cleans up the staging directory (best-effort).
The on-disk package.json > lpm > patchedDependencies entry looks like:
{
"lpm": {
"patchedDependencies": {
"lodash@4.17.21": {
"path": "patches/lodash@4.17.21.patch",
"originalIntegrity": "sha512-..."
}
}
}
}lpm patch-remove <selector>
lpm patch-remove lodash@4.17.21
lpm patch-remove lodash --dry-run
lpm patch-remove lodash@4.17.21 --keep-fileRemoves entries from package.json > lpm > patchedDependencies. Exact pins (lodash@4.17.21) match one manifest entry. Bare names (lodash) work only when they uniquely match a single patched version; if multiple patched versions exist, use the exact key.
By default, LPM also deletes the patch file when it is safely inside the project and no remaining patch entry references it. Use --keep-file to leave the file on disk. Use --dry-run to preview the manifest and file changes without writing.
After removing a patch, run lpm install to refresh node_modules.
Drift safety
On every install, the patch engine verifies the store entry's integrity matches originalIntegrity. Drift is a hard error — your install will refuse to apply the patch if the underlying package's content changed (re-publish under the same version, tampering, etc.). Bump the patched dep, regenerate the patch.
This is the key safety property versus stock patch-package: a republished lodash@4.17.21 with different bytes can't silently slip past your patch.
When to use
- A dependency has a known bug; the upstream PR isn't merged yet.
- A peer-dep mismatch needs a one-line fix to make installation work.
- A vendored package needs a tiny tweak that doesn't justify forking.
For larger or longer-lived divergence, prefer forking + publishing your own scoped variant.
See also
lpm install— auto-applies patches after linkinglpm sbom— includes patch metadata in generated SBOMspackage.json"lpm.patchedDependencies" — the file format- Content-addressable store — what the patch is generated against