From 0db1ebf2b1b041c75811dd4bbf59557ef4451b72 Mon Sep 17 00:00:00 2001 From: scanner <280983584+hivecommons-hive[bot]@users.noreply.github.com> Date: Sun, 18 Oct 2026 08:35:48 -0400 Subject: [PATCH] fix: polyfill Promise.withResolvers for jsdom 30 on Node 20 prow image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit jsdom 30's Document-impl.js close() calls the ES2024 Promise.withResolvers (native only in Node >= 22). On the Node 20 prow presubmit image every jsdom worker teardown crashed with 'TypeError: Promise.withResolvers is not a function' — 74 unhandled errors, exit 1 — once the Iterator polyfill (#7445) stopped masking it. Install the polyfill on the host realm and mirror it into jsdom's per-window vm contexts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: scanner <280983584+hivecommons-hive[bot]@users.noreply.github.com> --- .../fixed-promise-withresolvers-polyfill.md | 1 + scripts/node-iterator-polyfill.cjs | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 changelog.d/fixed-promise-withresolvers-polyfill.md diff --git a/changelog.d/fixed-promise-withresolvers-polyfill.md b/changelog.d/fixed-promise-withresolvers-polyfill.md new file mode 100644 index 000000000..876835e9c --- /dev/null +++ b/changelog.d/fixed-promise-withresolvers-polyfill.md @@ -0,0 +1 @@ +Fixed vitest on the Node 20 prow image again: jsdom 30's Document teardown calls the ES2024 `Promise.withResolvers` (Node 22+), so every worker exited 1 with 74 unhandled errors once the Iterator gap stopped masking it. The preload polyfill now also installs `Promise.withResolvers` on the host realm and mirrors it into jsdom's vm contexts. diff --git a/scripts/node-iterator-polyfill.cjs b/scripts/node-iterator-polyfill.cjs index 23f514b24..1586df040 100644 --- a/scripts/node-iterator-polyfill.cjs +++ b/scripts/node-iterator-polyfill.cjs @@ -43,6 +43,56 @@ if (typeof globalThis.Iterator === 'undefined') { } } +// jsdom >= 30 also calls the ES2024 `Promise.withResolvers` (added in +// Node 22) from lib/jsdom/living/nodes/Document-impl.js close(), so on the +// Node 20 prow image every jsdom window teardown crashes with +// "TypeError: Promise.withResolvers is not a function" — same 74 +// unhandled-errors-exit-1 pattern as the Iterator gap, which masked this one +// until it was fixed. Polyfill it on the host realm and mirror it into every +// vm context (jsdom windows get the context realm's `Promise`). +function withResolversPolyfill () { + let resolve, reject + // eslint-disable-next-line promise/param-names + const promise = new this((res, rej) => { + resolve = res + reject = rej + }) + return { promise, resolve, reject } +} + +if (typeof Promise.withResolvers !== 'function') { + Object.defineProperty(Promise, 'withResolvers', { + value: withResolversPolyfill, + writable: true, + configurable: true + }) + + const vm = require('vm') + const originalCreateContext = vm.createContext + vm.createContext = function (...args) { + const context = originalCreateContext.apply(this, args) + const globalObject = + context !== null && typeof context === 'object' ? context : args[0] + try { + if ( + globalObject && + vm.runInContext('typeof Promise.withResolvers', globalObject) === + 'undefined' + ) { + const contextPromise = vm.runInContext('Promise', globalObject) + Object.defineProperty(contextPromise, 'withResolvers', { + value: withResolversPolyfill, + writable: true, + configurable: true + }) + } + } 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