Files
pnpm/pnpm11/exec/esm-node-path-loader/package.json
Zoltan Kochan ae462e10ea feat(gvs): restore NODE_PATH resolution for scripts, ESM included (#13931)
With a global virtual store, package directories live outside the
project, so Node's upward node_modules walk from their real paths never
reaches the project's hoisted node_modules — undeclared (phantom)
dependencies stop resolving, and Node ignores NODE_PATH entirely for
ESM imports (Related to pnpm/pnpm#9618).

When enableGlobalVirtualStore is on (and extendNodePath is not
disabled), every child process pnpm spawns — run, exec, lifecycle
scripts, dlx — now receives NODE_PATH pointing at the private hoist dir
and the root node_modules, plus a NODE_OPTIONS --import flag that
registers a resolve hook restoring NODE_PATH lookups for ESM, replacing
the plugin-esm-node-path config dependency.

The flag is a self-contained constant: the registration module and the
hook are nested data: URLs, so no loader file has to exist on disk and
the flag stays valid regardless of which project or pnpm version
spawned the child. It prefers module.registerHooks() (in-thread, no
DEP0205 deprecation warning) and falls back to module.register() on
Node >=18.19 <22.15. Everything outside the RFC 3986 unreserved set is
percent-encoded: encodeURIComponent alone leaves single quotes bare,
and the NODE_OPTIONS tokenizer treats those as quote delimiters.

Both stacks embed identical hook sources and each asserts its derived
flag against the shared golden file
pnpm11/exec/esm-node-path-loader/test/import-flag.txt, so they cannot
drift apart silently.

Where the nodeOptions setting overwrites NODE_OPTIONS (run, exec,
recursive run, install lifecycle), the flag is re-applied. pacquet's
dlx and global installs force GVS off for their self-contained trees
and now strip the injected env from the caller's cloned config, so a
tool never resolves phantoms from the invoking project's tree; the
TypeScript dlx (GVS on by default) adds the flag and lets the bin shim
supply NODE_PATH.

This deliberately does not address the TypeScript-compiler side of
pnpm/pnpm#13210: tsc, tsserver, and bundlers implement their own
resolution and honor neither NODE_PATH nor Node loader hooks. That
class remains packageExtensions / compat-DB territory.
2026-08-16 11:20:12 +02:00

45 lines
1.3 KiB
JSON

{
"name": "@pnpm/exec.esm-node-path-loader",
"version": "1100.0.0",
"description": "Builds the NODE_OPTIONS flag that restores NODE_PATH resolution for ESM imports",
"keywords": [
"pnpm",
"pnpm11"
],
"license": "MIT",
"funding": "https://opencollective.com/pnpm",
"repository": "https://github.com/pnpm/pnpm/tree/main/pnpm11/exec/esm-node-path-loader",
"homepage": "https://github.com/pnpm/pnpm/tree/main/pnpm11/exec/esm-node-path-loader#readme",
"bugs": {
"url": "https://github.com/pnpm/pnpm/issues"
},
"type": "module",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"exports": {
".": "./lib/index.js"
},
"files": [
"lib",
"!*.map"
],
"scripts": {
"start": "tsgo --watch",
"test": "pn compile && pn .test",
"lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\"",
"prepublishOnly": "tsgo --build",
"compile": "tsgo --build && pn lint --fix",
".test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest"
},
"devDependencies": {
"@jest/globals": "catalog:",
"@pnpm/exec.esm-node-path-loader": "workspace:*"
},
"engines": {
"node": ">=22.13"
},
"jest": {
"preset": "@pnpm/jest-config"
}
}