Skip to content

feat: strip patchedDependencies from the packed package.json#497

Merged
owlstronaut merged 1 commit into
npm:mainfrom
manzoorwanijk:feat/strip-patched-dependencies
Jun 5, 2026
Merged

feat: strip patchedDependencies from the packed package.json#497
owlstronaut merged 1 commit into
npm:mainfrom
manzoorwanijk:feat/strip-patched-dependencies

Conversation

@manzoorwanijk
Copy link
Copy Markdown
Contributor

@manzoorwanijk manzoorwanijk commented Jun 5, 2026

Part of native dependency patching (npm/rfcs#862). When packing a directory spec (the npm publish / npm pack path), this strips a top-level patchedDependencies field from the package.json written into the tarball.

Why

patchedDependencies declares project-local patches against installed dependencies. It is honored only in a root manifest, so it is meaningless to consumers of a published package and should never travel through the registry. The published packument manifest is already stripped in libnpmpublish; this closes the other half — the package.json inside the tarball itself — so npm pack --dry-run and the published tarball no longer carry the field. It pairs with the npm-packlist change that excludes the patch files themselves; together they guarantee a patched project publishes clean.

How

DirFetcher packs the raw on-disk files via tar.c, so the tarball's package.json is the literal file on disk — there is no manifest seam to edit. The new #tarOptions():

  1. Reads the on-disk package.json (after prepare) via @npmcli/package-json. If it has no patchedDependencies, returns the existing options unchanged — non-patched packs are byte-for-byte identical to before.
  2. Otherwise deletes the field and re-serializes preserving the original indent, newline, and key order (the indent/newline symbols @npmcli/package-json attaches; JSON.stringify ignores them), writes the stripped copy to a temp dir, and removes the temp dir if the write fails.
  3. Sets node-tar's onWriteEntry to redirect only the top-level package.json entry's absolute at the stripped copy and fix its stat.size/nlink. onWriteEntry runs before the header and the file's hardlink check, so the override is honored; every other file is untouched.
  4. The temp dir is removed once the tar source stream emits end/error, so it outlives content consumption.

No behavior change for any package without patchedDependencies.

References

Part of

Related to

@manzoorwanijk manzoorwanijk marked this pull request as ready for review June 5, 2026 12:31
@manzoorwanijk manzoorwanijk requested a review from a team as a code owner June 5, 2026 12:31
owlstronaut pushed a commit to npm/npm-packlist that referenced this pull request Jun 5, 2026
#291)

Part of native dependency patching
([npm/rfcs#862](npm/rfcs#862)). This
force-excludes the patch files declared in the root package's
`patchedDependencies` from the packed file list, even when they are
listed in `files`.

## Why

`patchedDependencies` maps a dependency selector to a project-local
patch file (e.g. `"abbrev@2.0.0": "patches/abbrev@2.0.0.patch"`). Those
patches are a property of the project, not something a consumer of the
published package applies. Without this, publishing a patched project
would ship the patch files — and, once pacote strips the
`patchedDependencies` field from the tarball's `package.json`, they
would be dangling, unreferenced files. Excluding them keeps the
published tarball clean.

## How

In `PackWalker.processPackage`, when the walker is the project root and
the manifest declares `patchedDependencies`, each declared patch file
path is pushed onto the strict (un-overridable) rule set, so it is
excluded even if `files` lists it. Design choices:

- **Exact files, not directories.** Only the declared patch files are
excluded — never their directory. A dedicated `patches/` dir becomes
empty and drops out naturally, but a patch that lives in a shared
directory (e.g. `src/foo.patch`) does not take the rest of `src/` down
with it.
- **`--patches-dir` honored for free.** The location is read straight
off the `patchedDependencies` values, which already encode wherever the
patches were written.
- **Root-only.** `patchedDependencies` is root-only state, so the block
is gated to the project root and never prunes a bundled dependency's
files.
- **Path safety.** Absolute paths and paths that escape the package root
are skipped (they are never packed anyway).
- **Warns** when a `files` entry pulled a patch file in (directly or via
its directory), so the override is not silent.

## References

Part of
- npm/rfcs#862

Related to
- npm/cli#9439
- npm/pacote#497
@owlstronaut owlstronaut merged commit 2ab74b0 into npm:main Jun 5, 2026
14 checks passed
@manzoorwanijk manzoorwanijk deleted the feat/strip-patched-dependencies branch June 5, 2026 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants