LPM-cli

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 patch

Two-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 updated

After 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
SelectorNotes
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:

  1. Resolves the selector to an exact (name, version) pair (using the lockfile for non-exact inputs).
  2. Looks up <name>@<version> in the global store. Errors with a lpm install <name>@<version> hint if not present.
  3. 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-running lpm patch on 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.
  4. Writes a .lpm-patch.json breadcrumb in the staging root recording the resolved name, version, exact-pin key, and the store source path.
  5. 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-XXXXXX

Steps:

  1. Reads the breadcrumb to recover (name, version, key).
  2. Re-locates the store baseline from the live store (not the breadcrumb's recorded path), so a store relocation between patch and patch-commit doesn't break the diff.
  3. 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 — originalIntegrity already covers binary drift on install).
  4. Writes <project>/patches/<key>.patch atomically (.tmp then rename). Scoped names have / translated to __ for cross-platform portability — @scope/pkg@1.0.0 lands at patches/@scope__pkg@1.0.0.patch. Mirrors pnpm.
  5. Updates package.json > lpm > patchedDependencies with an entry recording the patch path and the original SRI integrity hash.
  6. 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-file

Removes 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