Skip to content

fix(runtime): node:process default import & globalThis.process expose the full process object (#4987)#4993

Merged
proggeramlug merged 1 commit into
mainfrom
fix/process-import-4987
Jun 11, 2026
Merged

fix(runtime): node:process default import & globalThis.process expose the full process object (#4987)#4993
proggeramlug merged 1 commit into
mainfrom
fix/process-import-4987

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Fixes #4987.

Problem

import process from 'node:process' and globalThis.process returned an incomplete process object — env, stdout, stderr, stdin, platform, arch, argv, pid, ppid, version, versions were all undefined (only callables like cwd resolved). Only the bare process identifier was fully populated. This was the next ink wall (#348 "Blocker #2: process import"): terminal-size does const {env, stdout, stderr} = process off the node:process import and threw TypeError: Cannot read properties of undefined (reading 'COLUMNS') during ink's first render.

Root cause

The bare process identifier lowers those members to codegen intrinsics (Expr::ProcessEnv, Expr::ProcessStdout, …). The default import and globalThis.process are native-module namespace objects whose property reads go through the runtime dispatcher — both the static path (js_native_module_property_by_name) and the dynamic path (js_object_get_field_by_name's NATIVE_MODULE arm) funnel into get_native_module_constantprocess_metadata_property, which only knew metadata extras (argv0, execPath, config, …). The core properties fell through to undefined.

Fix

  • process_metadata_property (crates/perry-runtime/src/process.rs) now serves the core value-properties from the same runtime constructors the codegen intrinsics call (js_process_env, js_process_stdout, js_os_platform, …), so all three forms observe identical values — env and the stream objects are live cached singletons, matching Node's identity semantics.
  • PROCESS_NAMESPACE_KEYS / PROCESS_DEFAULT_KEYS (crates/perry-runtime/src/object/native_module.rs) gain stderr/stdin/stdout so Object.keys(process) enumerates them like Node (the values were previously unreachable, so the keys were missing too).

Validation

  • New gap test test-files/test_gap_process_import_4987.tsbyte-identical vs node --experimental-strip-types: issue repro, full member sweep on the default import, globalThis.process, value-identity with the bare identifier, and the terminal-size destructure shape.
  • import * as ns from 'node:process' sweep (incl. ns.default.platform === ns.platform) byte-identical vs Node.
  • Existing process gap tests all pass vs Node: test_gap_node_process, test_gap_process_allowed_flags, test_gap_process_features, test_gap_process_misc_3045plus, test_gap_process_emitter_3047_3046_3050.
  • cargo test --release -p perry-runtime (RUST_TEST_THREADS=1): 1022 passed, 1 failed — date::tests::test_full_year_setters_revive_invalid_date_only, pre-existing on this base (fails with the change stashed too). perry-api-manifest tests green. cargo fmt --check and the file-size gate pass.

Code-only PR — no version bump / changelog per maintainer-folds-metadata-at-merge convention.

After this, ink's next gate is yoga-layout's WASM runtime (out of scope, #348).

… the full process object (#4987)

The bare `process` identifier lowers env/stdout/stderr/stdin/platform/
arch/argv/pid/ppid/version/versions to codegen intrinsics, but
`import process from 'node:process'` and `globalThis.process` resolve
through the native-module runtime dispatcher, which only knew metadata
extras (argv0, config, ...) — the core properties read as undefined.

- process_metadata_property now serves the core value-properties from
  the same runtime constructors the intrinsics call, so all three forms
  observe the same live objects (env/stdout are cached singletons).
- PROCESS_NAMESPACE_KEYS / PROCESS_DEFAULT_KEYS gain stderr/stdin/stdout
  so Object.keys(process) enumerates them like Node.
- New gap test test_gap_process_import_4987.ts — byte-identical vs
  node --experimental-strip-types.

Unblocks terminal-size (ink #348 "Blocker #2: process import").
@proggeramlug proggeramlug merged commit a848c3a into main Jun 11, 2026
13 checks passed
@proggeramlug proggeramlug deleted the fix/process-import-4987 branch June 11, 2026 10:38
proggeramlug added a commit that referenced this pull request Jun 11, 2026
…ntEmitter (#4995) (#5002)

The bare core specifier `events` (default import, namespace import, or
`require('events')`) produced an EventEmitter whose instances had an empty
prototype — `.on`/`.emit`/`.setMaxListeners` were all undefined — while
`node:events` worked. This blocked signal-exit (cli-cursor → restore-cursor),
the next ink wall after #4993.

Two layers, both fixed:

- HIR (expr_new.rs, expr_member.rs): `new EE()` over an events default
  import / namespace import / CJS alias now lowers to the real
  `EventEmitter` constructor (Expr::New { class_name: "EventEmitter" })
  instead of the empty-object placeholder. Emitter method-value reads on a
  native `events` instance are gated on module name (not the canonical class
  name) so an aliased constructor binding's reads stay PropertyGet rather
  than calling `events.on()` with no receiver.

- Runtime + stdlib: a new `js_set_native_events_construct` dispatcher lets
  `js_new_function_construct` build a real emitter when `new` lands on a
  bound `events.EventEmitter` export value. The handle-dispatch arms in
  perry-stdlib now call the linker-resolved `extern "C"` events symbols
  (probe / on / constructors) instead of in-crate `crate::events::*`, so when
  the well-known flip links perry-ext-events, dynamic dispatch consults the
  same handle registry the constructors used — previously every dynamic
  `.on`/`.emit` on an emitter silently no-op'd. New `external-events-construct`
  feature wires the constructor registration when the flip strips
  `bundled-events`. Mirrors the sqlite duplicate-symbol contract (#643).

New gap test test_gap_events_import_4995.ts — byte-identical vs
node --experimental-strip-types. Zero gap-suite regressions vs baseline.

Co-authored-by: Ralph Küpper <ralph@skelpo.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant