feat: .npm-extension transformManifest for imperative manifest repairs#9586
Conversation
ae8e7ac to
f29b151
Compare
64e45c5 to
7a92e3a
Compare
|
CC: @owlstronaut |
…294) Force-excludes root `.npm-extension.mjs` and `.npm-extension.cjs` from the package tarball, even when a package's `files` array would otherwise include them. These files are root-owned npm install policy (the imperative `transformManifest` extension point from [npm/rfcs#903](npm/rfcs#903)), not package contents — the same category as `.npmrc`, VCS metadata, lockfiles, and native-patch files, which npm-packlist already strips unconditionally. Keeping them out of the tarball lets a public package use `.npm-extension` locally (for its own install, tests, and linked-install migration) without publishing that policy to consumers or bloating its packument. ## What - Add `/.npm-extension.mjs` and `/.npm-extension.cjs` to the strict (forced) exclusion list, alongside the existing lockfile/`.npmrc`/VCS entries. - Root-anchored: only the package's own root file is excluded; nested paths are unaffected (a dependency's `.npm-extension` is never npm's concern at pack time). ## Test - `test/cannot-include-npm-extension.js`: a package whose `files` array lists both `.npm-extension.mjs` and `.npm-extension.cjs` still excludes them from the packed file list. ## References Supports npm/rfcs#903 Consumed by the npm CLI `.npm-extension` implementation npm/cli#9586 (pacote will pick this up via a dependency bump once released).
… workspace warning
7a92e3a to
5a62f31
Compare
owlstronaut
left a comment
There was a problem hiding this comment.
Nice work. I ran this through install/ci/ls/explain, workspaces, all the allowlist fields, and a real end-to-end CLI run, and it behaves as documented. One thing to fix before merge: the extension-file source check works but its error is swallowed. set it from a disallowed source (user/global/env) and every npm command silently exits 1 with no output.
Minor, doc-only: loadActual executes the root file on the stale/absent hidden-lockfile path, and the feature forces lockfileVersion: 4
|
Thank you for the review
Let me look into it. |
That is now fixed. Thank you. Nice catch
Should we change anything about that> |
Nope, I think those look good |
Bot-generated transition of RFC **#56** to status `implemented`. Moved to `implemented/0056-npm-extension-manifest-repairs.md`. Front-matter `status` and the relevant date field were updated. `INDEX.md` was regenerated. Implementation: npm/cli#9586 Co-authored-by: npm CLI robot <npm-cli+bot@github.com>
Implements the accepted RFC npm/rfcs#903: a root-owned
.npm-extension.mjs/.npm-extension.cjsfile exportingtransformManifest(pkg, context)that imperatively repairs third-party dependency manifests before Arborist finalizes the ideal tree. It is the imperative counterpart topackageExtensions(#9496) and operates in the same pre-resolution phase, running beforepackageExtensions.Why
packageExtensionsis declarative JSON: it cannot carry comments or issue links, repeats itself across many packages, is add-only, and lives inpackage.json(so public packages cannot publish while it is present)..npm-extensioncovers the gap for advanced projects that need conditional repairs, deletion, range rewrites, repeated rules expressed as code, stale-repair guards, and a policy location outside the published manifest.What it does
.npm-extension.mjsor.npm-extension.cjs(both present is an error). Theextension-fileconfig overrides discovery with a project-local path that must resolve inside the project root and use.mjs/.cjs.transformManifest(pkg, context)— receives a deeply isolated copy of the normalized manifest;contextexposeslog,root, andextensionPoint. Must return a manifest synchronously;null/primitive/array/promise returns and throws fail the install with a.npm-extension-named error.dependencies,optionalDependencies,peerDependencies, andpeerDependenciesMetamay change (add, replace, or delete). Any other changed field (scripts,bin, …) is rejected. pacote's cached manifest is never mutated.npmExtensionHash(a format-tagged digest of the file bytes); affected entries record minimalnpmExtensionAppliedprovenance. Extension state reuses the existinglockfileVersion: 4threshold.npm install, reverting transforms that no longer apply.npm ci— never imports or executes the file; it validates the recorded hash and reifies the locked graph (which already carries the extension-influenced edges).ignore-extensiondisables import/execution;ignore-scriptsimplies it;extension-fileis honored only from project config or the command line, never from user/global/builtin sources..npm-extensionfile in a non-root workspace is ignored with a warning; only the workspace root's file is honored.npm explainannotates extension-changed edges andnpm ls(human +--json) surfaces the provenance.npm-packlistforce-excludes root.npm-extension.{mjs,cjs}from package tarballs.Companion change
Requires npm/npm-packlist#294 to exclude root
.npm-extension.{mjs,cjs}from tarballs.pacote/CLI will pick this up via a version bump once that publishes.Notes / out of scope for this PR
One item is deferred for a genuine structural reason the RFC itself flags:
file:/link:/directory sources.transformManifestapplies to fetched manifests (registry, git, remote tarball,file:tarballs) and is re-derived on the installed tree across all install strategies includinginstall-strategy=linked. It is not yet applied to local sources that createLinknodes directly and bypass the fetch phase — the RFC flags this as net-new wiring ("npm must add an analogous pre-edge-read transform path for theLinktarget"). Follow-up.References
Implements npm/rfcs#903
Builds on #9496
Companion: npm/npm-packlist#294