Aalexbit-codemod

patch-package-to-pnpm

onverts patch-package patches to pnpm format by removing node_modules/ prefixes from patch file content and updating package.json/pnpm-workspace.yaml configuration.

pnpmmigration
Public
2 downloads
1 stars
How to Use
Run this codemod on your codebase using one of the following commands

The easiest way to run this codemod without installing anything globally:

Documentation

patch-package uses patches with filenames like @scope+pkg+1.2.3.patch and content containing node_modules/ prefixes.

pnpm uses patches with filenames like @scope__pkg@1.2.3.patch and content without node_modules/ prefixes.

Features

Core Flow

  • Scans for patch files (default ./patches directory) when processing config files
  • Normalizes the path, then performs conversion: content rewrite + filename rewrite
  • Aggregates results into patchedDependencies entries, writes them back to config
  • Runs pnpm install automatically via workflow step to apply the patches

Content Conversion

  • Detection: converts only if patch content includes node_modules diff headers (diff --git a/node_modules/..., -- a/node_modules/..., +++ b/node_modules/...)
  • Prefix calculation: derives a removal pattern from the patch filename by taking all +separated parts except the last (version), joining with /, and prefixing with node_modules
  • Rewrite: globally removes that prefix from the patch content to meet pnpm's expected format

Filename Conversion

  • Input naming (patch-package): <name parts joined by '+'>+<version>.patch
  • Output naming (pnpm): <name parts joined by '__'>@<version>.patch
  • Example: @scope+pkg+1.2.3.patch@scope__pkg@1.2.3.patch

Batch vs Single-File Processing

  • Directory mode: enumerates all .patch files, then converts each (content + filename)
  • Single-file mode: validates existence, .patch extension, presence of + in name, and ensures the target converted file doesn't already exist; then converts
  • If a patch doesn't need conversion (no node_modules headers), it's skipped

Configuration Updates

Patched Dependencies

For each converted file:

  • Key: filename without .patch, replace __ back to /, e.g. <name>/<sub>/...@<version>
  • Value: <patchesDir>/<convertedFileName> where patchesDir is the directory name containing the patches (e.g., patches)
  • Merges these into existing patchedDependencies, overwriting duplicate keys

Config Files

  • Prefers pnpm-workspace.yaml: reads top-level patchedDependencies, merges new entries, and writes back
  • Fallback to package.json: updates pnpm.patchedDependencies in place if workspace file is absent
  • Merge strategy: additive with overwrite, preserving unrelated fields

Examples

Before (patch-package)

File: patches/@react-spring+core+9.4.5.patch

diff

After (pnpm)

File: patches/@react-spring__core@9.4.5.patch

diff

Configuration in pnpm-workspace.yaml:

yaml

Error Handling

  • Directory mode: errors on non-existent path or non-directory
  • Single-file mode: blocks if the converted target file already exists (to avoid overwrite)
  • Requires filenames containing + and ending with .patch; otherwise errors
  • Normalizes Windows path separators to / where relevant (regex and key generation)

Ready to contribute?

Build your own codemod and share it with the community.