fix(resolver): don't treat `deno eval` modules as npm package files (#36304)
`deno eval` and `deno run -` fail whenever the cwd is inside
`node_modules`:
```
$ cd node_modules/some-pkg && deno eval "console.log(1)"
error: Unable to load .../node_modules/some-pkg/$deno$eval.mts
Caused by:
No such file or directory (os error 2)
```
Both resolve their in-memory source to a synthetic `$deno$eval.mts` /
`$deno$stdin.mts` specifier in the cwd, so a cwd under `node_modules`
(or the
npm cache) classifies it as an npm package file.
`CliModuleLoader::prepare_load`
then skips graph preparation for it, and the load falls through to the
npm
module loader, which reads from disk and gets `ENOENT` — the source only
ever
existed in memory.
The practical fallout is that **no `node-gyp` based native package can
be
installed**, because gyp evaluates `binding.gyp` with `node -p` while
cwd'd into
the package being built:
```
gyp: Call to 'node -p "process.env.USE_GLOBAL || (process.platform === 'linux' ...)"'
returned exit status 1 while in binding.gyp
gyp ERR! configure error
error: script 'postinstall' in 'node-liblzma@2.2.0' failed with exit code 1
```
## Fix
- Exclude the synthetic `$deno$` modules from the in-npm-package check,
so they
get normal graph preparation and load from the in-memory source.
- Short-circuit main module resolution for them, so node resolution
doesn't
existence-check a file that will never be on disk. Without this the
*referrer*
(still the cwd) drags resolution back down the node path and it fails
with
`ERR_MODULE_NOT_FOUND` instead.
The synthetic specifier deliberately stays in the cwd. Moving it to the
closest
ancestor outside `node_modules` also fixes the load, but breaks the
idiom that
`binding.gyp` files depend on — bare requires have to resolve from the
package
being built:
```
$ deno eval -p "require('node-addon-api').include_dir"
error: Cannot find module 'node-addon-api'
```
There is a spec test covering that specifically, so this doesn't regress
later.
## Testing
New spec test `tests/specs/eval/cwd_in_node_modules` (ESM, `-p`, CJS,
nested
`node_modules/.deno/...` layout, and the `require`-resolves-from-cwd
guard).
Verified each case fails without this change.
End to end, `deno install --allow-scripts` on a `node-liblzma` +
`node-gyp`
project now builds the native addon to completion **with no `node` on
`PATH`**
— Deno drives the whole node-gyp build — and the resulting
`node_lzma.node`
loads.
Checked for regressions by diffing failing-test sets with and without
the change
across the `npm`, `run`, `node`, `install`, `task`, `workspaces` and
`cache` spec
suites: identical in every case.
## Note on #36291
This is filed as lifecycle scripts lacking `--allow-sys`, but that
premise
doesn't hold on `main`. Lifecycle scripts do run with `-A`
(`cli/task_runner.rs`), and the `process.execPath` subprocess that
`node-gyp-build` spawns is re-translated through the node shim in
`ext/node/polyfills/internal/child_process.ts`, which re-adds `-A` — the
child
does get `sys` access. The reported `NotCapable` error does not
reproduce.
What does reproduce, from that exact repro case, is the `deno eval`
failure
above. So this is `Ref` rather than `Closes` — worth confirming with the
reporter whether they still see a permission error once this lands.