diff --git a/crates/perry-runtime/src/object/native_module.rs b/crates/perry-runtime/src/object/native_module.rs index 6ad427f21d..e234e4da3b 100644 --- a/crates/perry-runtime/src/object/native_module.rs +++ b/crates/perry-runtime/src/object/native_module.rs @@ -1789,6 +1789,9 @@ const PROCESS_NAMESPACE_KEYS: &[&[u8]] = &[ b"setSourceMapsEnabled", b"send", b"sourceMapsEnabled", + b"stderr", + b"stdin", + b"stdout", b"title", b"unref", b"uptime", @@ -1870,6 +1873,9 @@ const PROCESS_DEFAULT_KEYS: &[&[u8]] = &[ b"setSourceMapsEnabled", b"send", b"sourceMapsEnabled", + b"stderr", + b"stdin", + b"stdout", b"title", b"uptime", b"version", diff --git a/crates/perry-runtime/src/process.rs b/crates/perry-runtime/src/process.rs index a430d1d80e..c741b2cea2 100644 --- a/crates/perry-runtime/src/process.rs +++ b/crates/perry-runtime/src/process.rs @@ -1713,6 +1713,23 @@ fn process_allowed_flags_value() -> f64 { pub fn process_metadata_property(property: &str) -> Option { Some(match property { + // #4987: core value-properties. The bare `process` identifier lowers + // these to codegen intrinsics, but `import process from + // 'node:process'` and `globalThis.process` resolve through the + // native-module runtime dispatcher, which lands here. Serve them from + // the same runtime constructors the intrinsics call so all three + // forms observe the same values (env/stdout are live singletons). + "env" => js_process_env(), + "argv" => f64::from_bits(JSValue::array_ptr(crate::os::js_process_argv()).bits()), + "platform" => f64::from_bits(JSValue::string_ptr(crate::os::js_os_platform()).bits()), + "arch" => f64::from_bits(JSValue::string_ptr(crate::os::js_os_arch()).bits()), + "pid" => crate::os::js_process_pid(), + "ppid" => crate::os::js_process_ppid(), + "version" => f64::from_bits(JSValue::string_ptr(crate::os::js_process_version()).bits()), + "versions" => crate::os::js_process_versions(), + "stdin" => crate::os::js_process_stdin(), + "stdout" => crate::os::js_process_stdout(), + "stderr" => crate::os::js_process_stderr(), "allowedNodeEnvironmentFlags" => process_allowed_flags_value(), "argv0" | "execPath" => module_string_value(&process_argv0_string()), "config" => process_config_value(), diff --git a/test-files/test_gap_process_import_4987.ts b/test-files/test_gap_process_import_4987.ts new file mode 100644 index 0000000000..2aa47f19ab --- /dev/null +++ b/test-files/test_gap_process_import_4987.ts @@ -0,0 +1,44 @@ +// Issue #4987: `import process from 'node:process'` and `globalThis.process` +// must expose the same fully-populated process object as the bare `process` +// identifier (env/stdout/stderr/stdin/platform/arch/argv/pid/ppid/version/ +// versions were undefined on the import + globalThis forms). +// Expected: byte-identical to node --experimental-strip-types +import proc from 'node:process'; + +// --- minimal repro from the issue --- +console.log(typeof proc.env, typeof proc.stdout, typeof proc.platform); +console.log(typeof process.env); + +// --- full member sweep on the default import --- +console.log(typeof proc.env === 'object'); // true +console.log(typeof proc.stdout === 'object'); // true +console.log(typeof proc.stderr === 'object'); // true +console.log(typeof proc.stdin === 'object'); // true +console.log(typeof proc.platform === 'string'); // true +console.log(typeof proc.arch === 'string'); // true +console.log(typeof proc.pid === 'number'); // true +console.log(typeof proc.ppid === 'number'); // true +console.log(typeof proc.version === 'string'); // true +console.log(typeof proc.versions === 'object'); // true +console.log(Array.isArray(proc.argv)); // true +console.log(typeof proc.cwd === 'function'); // true + +// --- globalThis.process --- +console.log(typeof globalThis.process.env === 'object'); // true +console.log(typeof globalThis.process.stdout === 'object'); // true +console.log(typeof globalThis.process.platform === 'string'); // true + +// --- values agree with the bare identifier --- +console.log(proc.platform === process.platform); // true +console.log(proc.arch === process.arch); // true +console.log(proc.pid === process.pid); // true +console.log(proc.version === process.version); // true +console.log(typeof proc.env.PATH === 'string'); // true +console.log(proc.env.PATH === process.env.PATH); // true + +// --- terminal-size shape (the ink #348 wall): destructure off the import --- +const { env, stdout, stderr } = proc; +console.log(typeof env, typeof stdout, typeof stderr); // object object object +// env member reads must not throw (this was the TypeError in terminal-size) +console.log(env.COLUMNS === undefined || typeof env.COLUMNS === 'string'); // true +console.log(typeof stdout.write === 'function'); // true