From c3536af2094a0fcbc7b01ec6d8d63a42ca28e083 Mon Sep 17 00:00:00 2001 From: scanner Date: Sun, 18 Oct 2026 04:24:55 -0400 Subject: [PATCH] fix: make Iterator polyfill cover jsdom vm contexts and spawned child processes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The polyfill from #7443 fixed the worker-global Iterator but left two failure modes that the prow presubmit (which never ran on #7443's scripts-only diff) exposed on the next PR (#7440): 1. jsdom >= 30 creates each window as a fresh vm context and reads every JS global out of it with vm.runInContext(name, window). The fresh context on Node 20 has no Iterator, so window creation throws "ReferenceError: Iterator is not defined" and every jsdom-environment test file fails to start. The polyfill now wraps vm.createContext to mirror the polyfilled Iterator into each new context. 2. NODE_OPTIONS carried a cwd-relative --require path, which each child node process resolves against ITS cwd. Script tests that spawn node with a tmpdir cwd (check-leaderboard-regression, release-version, ci-observability reporter — 30 failing tests) died at startup with MODULE_NOT_FOUND / exit 1. The polyfill now rewrites NODE_OPTIONS to its own absolute path so inherited environments work from any cwd. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: scanner --- .../fixed-iterator-polyfill-followups.md | 1 + scripts/node-iterator-polyfill.cjs | 45 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 changelog.d/fixed-iterator-polyfill-followups.md diff --git a/changelog.d/fixed-iterator-polyfill-followups.md b/changelog.d/fixed-iterator-polyfill-followups.md new file mode 100644 index 0000000000..7d6ca38c0c --- /dev/null +++ b/changelog.d/fixed-iterator-polyfill-followups.md @@ -0,0 +1 @@ +Fixed the Node 20 `Iterator` polyfill so vitest passes again on the prow image: the polyfill is now mirrored into the vm contexts jsdom 30 creates per window, and `NODE_OPTIONS` is rewritten to the polyfill's absolute path so child `node` processes spawned by script tests from tmpdir cwds no longer die with MODULE_NOT_FOUND. diff --git a/scripts/node-iterator-polyfill.cjs b/scripts/node-iterator-polyfill.cjs index 4c218ba25c..23f514b245 100644 --- a/scripts/node-iterator-polyfill.cjs +++ b/scripts/node-iterator-polyfill.cjs @@ -13,4 +13,49 @@ // prototype methods; on Node >= 22 the native implementation is kept. if (typeof globalThis.Iterator === 'undefined') { require('core-js/actual/iterator') + + // jsdom >= 30 also reads every JS global out of the fresh vm context it + // creates per window (lib/jsdom/browser/Window.js: vm.runInContext(name, + // window) for each host global it found, which now includes our polyfilled + // `Iterator`). A fresh context on Node 20 has no native Iterator, so window + // creation throws "ReferenceError: Iterator is not defined" from inside + // runInContext. Mirror the polyfilled Iterator into every new context. + const vm = require('vm') + const originalCreateContext = vm.createContext + vm.createContext = function (...args) { + const context = originalCreateContext.apply(this, args) + // createContext returns the contextified object (or the context's global + // object with DONT_CONTEXTIFY); property sets on it reflect as globals + // inside the context. + const globalObject = + context !== null && typeof context === 'object' ? context : args[0] + try { + if ( + globalObject && + vm.runInContext('typeof Iterator', globalObject) === 'undefined' + ) { + globalObject.Iterator = globalThis.Iterator + } + } catch { + // Non-contextified or frozen targets: leave untouched. + } + return context + } +} + +// The `test` script preloads this file with a cwd-relative path +// (`--require ./scripts/node-iterator-polyfill.cjs`). Node resolves that +// against the cwd of EACH process, so a child `node` process a test spawns +// with a tmpdir cwd dies at startup with MODULE_NOT_FOUND before running +// anything. Rewrite NODE_OPTIONS to this file's absolute path so inherited +// environments keep working from any cwd. +const RELATIVE_SPEC = './scripts/node-iterator-polyfill.cjs' +if ( + process.env.NODE_OPTIONS && + process.env.NODE_OPTIONS.includes(RELATIVE_SPEC) && + !__filename.includes(' ') +) { + process.env.NODE_OPTIONS = process.env.NODE_OPTIONS + .split(RELATIVE_SPEC) + .join(__filename) }