Repository: denoland/deno
Pull request: fix(ext/node): handle -p flag in spawn, fix "from dependency" check in windows

This PR updates `op_node_call_is_from_dependency` op, which now detects node_modules path on windows correctly.

This PR also updates the handling of `child_process.spawn(process.execPath, ["-p", souceCode])`. It runs the given string as source code, and prints the last evaluated value. This PR adds wrapper for simulating that behavior.

Changed files:
filename: ext/node/ops/util.rs
patch: @@ -119,7 +119,10 @@ pub fn op_node_call_is_from_dependency<
     let name = script.to_rust_string_lossy(scope);
     if name.starts_with("node:") || name.starts_with("ext:") {
       continue;
-    } else if name.starts_with("https:") || name.contains("/node_modules/") {
+    } else if name.starts_with("https:")
+      || name.contains("/node_modules/")
+      || name.contains(r"\node_modules\")
+    {
       return true;
     } else {
       let Ok(specifier) = url::Url::parse(&name) else {

filename: ext/node/polyfills/internal/child_process.ts
patch: @@ -1176,6 +1176,22 @@ const kDenoSubcommands = new Set([
   "vendor",
 ]);
 
+/** Wraps the script for (Node.js) --eval / --print argument
+ * Note: Builtin modules are available as global variables */
+function wrapScriptForEval(sourceCode: string): string {
+  // Note: We need vm.runInThisContext call here to get the last evaluated
+  // value of the source with multiple statements. `deno eval -p` surrounds
+  // the source code like `console.log(${source})`, and it only allows a
+  // single expression.
+  return `
+    process.getBuiltinModule("module").builtinModules
+      .filter((m) => !/\\/|crypto|process/.test(m))
+      .forEach((m) => { globalThis[m] = process.getBuiltinModule(m); }),
+    vm.runInThisContext(${JSON.stringify(sourceCode)})
+  `;
+}
+
+/** Returns deno args and NODE_OPTIONS for simulating Node.js cli */
 function toDenoArgs(args: string[]): [string[], string[]] {
   if (args.length === 0) {
     return [args, args];
@@ -1266,7 +1282,10 @@ function toDenoArgs(args: string[]): [string[], string[]] {
 
     // Remap Node's eval flags to Deno.
     if (flag === "-e" || flag === "--eval") {
-      denoArgs.push("eval", flagValue);
+      denoArgs.push("eval", wrapScriptForEval(flagValue));
+      useRunArgs = false;
+    } else if (flag === "-p" || flag === "--print") {
+      denoArgs.push("eval", "-p", wrapScriptForEval(flagValue));
       useRunArgs = false;
     } else if (isLongWithValue) {
       denoArgs.push(arg);

filename: tests/node_compat/config.jsonc
patch: @@ -279,6 +279,7 @@
       "test-buffer-concat.js",
       "test-buffer-constants.js",
       "test-buffer-constructor-deprecation-error.js",
+      "test-buffer-constructor-node-modules-paths.js",
       "test-buffer-constructor-node-modules.js",
       "test-buffer-constructor-outside-node-modules.js",
       "test-buffer-copy.js",

filename: tests/node_compat/runner/TODO.md
patch: @@ -1,7 +1,7 @@
 <!-- deno-fmt-ignore-file -->
 # Remaining Node Tests
 
-1194 tests out of 3993 have been ported from Node 23.9.0 (29.90% ported, 70.70% remaining).
+1195 tests out of 3993 have been ported from Node 23.9.0 (29.93% ported, 70.67% remaining).
 
 NOTE: This file should not be manually edited. Please edit `tests/node_compat/config.json` and run `deno task setup` in `tests/node_compat/runner` dir instead.
 
@@ -282,7 +282,6 @@ NOTE: This file should not be manually edited. Please edit `tests/node_compat/co
 - [parallel/test-blocklist-clone.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-blocklist-clone.js)
 - [parallel/test-bootstrap-modules.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-bootstrap-modules.js)
 - [parallel/test-broadcastchannel-custom-inspect.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-broadcastchannel-custom-inspect.js)
-- [parallel/test-buffer-constructor-node-modules-paths.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-buffer-constructor-node-modules-paths.js)
 - [parallel/test-buffer-prototype-inspect.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-buffer-prototype-inspect.js)
 - [parallel/test-c-ares.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-c-ares.js)
 - [parallel/test-child-process-advanced-serialization-largebuffer.js](https://github.com/nodejs/node/tree/v23.9.0/test/parallel/test-child-process-advanced-serialization-largebuffer.js)

filename: tests/node_compat/test/parallel/test-buffer-constructor-node-modules-paths.js
patch: @@ -0,0 +1,44 @@
+// deno-fmt-ignore-file
+// deno-lint-ignore-file
+
+// Copyright Joyent and Node contributors. All rights reserved. MIT license.
+// Taken from Node 23.9.0
+// This file is automatically generated by `tests/node_compat/runner/setup.ts`. Do not modify this file manually.
+
+'use strict';
+
+const common = require('../common');
+const child_process = require('child_process');
+const assert = require('assert');
+
+if (process.env.NODE_PENDING_DEPRECATION)
+  common.skip('test does not work when NODE_PENDING_DEPRECATION is set');
+
+function test(main, callSite, expected) {
+  const { stderr } = child_process.spawnSync(process.execPath, ['-p', `
+  process.mainModule = { filename: ${JSON.stringify(main)} };
+
+  vm.runInNewContext('new Buffer(10)', { Buffer }, {
+    filename: ${JSON.stringify(callSite)}
+  });`], { encoding: 'utf8' });
+  if (expected)
+    assert(stderr.includes('[DEP0005] DeprecationWarning'), stderr);
+  else
+    assert.strictEqual(stderr.trim(), '');
+}
+
+test('/a/node_modules/b.js', '/a/node_modules/x.js', false);
+test('/a/node_modules/b.js', '/a/node_modules/foo/node_modules/x.js', false);
+test('/a/node_modules/foo/node_modules/b.js', '/a/node_modules/x.js', false);
+test('/node_modules/foo/b.js', '/node_modules/foo/node_modules/x.js', false);
+test('/a.js', '/b.js', true);
+test('/a.js', '/node_modules/b.js', false);
+test('/node_modules/a.js.js', '/b.js', true);
+test('c:\\a\\node_modules\\b.js', 'c:\\a\\node_modules\\x.js', false);
+test('c:\\a\\node_modules\\b.js',
+     'c:\\a\\node_modules\\foo\\node_modules\\x.js', false);
+test('c:\\node_modules\\foo\\b.js',
+     'c:\\node_modules\\foo\\node_modules\\x.js', false);
+test('c:\\a.js', 'c:\\b.js', true);
+test('c:\\a.js', 'c:\\node_modules\\b.js', false);
+test('c:\\node_modules\\a.js', 'c:\\b.js', true);