From f0ba63750f514e19cb143280e2f957529ece0bbb Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Sun, 11 Oct 2026 06:06:19 +0400 Subject: [PATCH 1/4] build: accept every node: builtin as an import The import check allowed twelve hand-listed node: modules and reported any other builtin, node:http among them, as missing from package.json dependencies. A builtin is never a dependency; the list now comes from node:module. --- packages/dev/scripts/pezkuwi-dev-build-ts.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/dev/scripts/pezkuwi-dev-build-ts.mjs b/packages/dev/scripts/pezkuwi-dev-build-ts.mjs index c1a1ff6..428494a 100755 --- a/packages/dev/scripts/pezkuwi-dev-build-ts.mjs +++ b/packages/dev/scripts/pezkuwi-dev-build-ts.mjs @@ -4,6 +4,7 @@ import JSON5 from 'json5'; import fs from 'node:fs'; +import { builtinModules } from 'node:module'; import path from 'node:path'; import process from 'node:process'; import ts from 'typescript'; @@ -32,8 +33,9 @@ const TARGET_TSES = ts.ScriptTarget.ES2022; const TARGET_NODE = '>=18'; const IGNORE_IMPORTS = [ - // node (new-style) - ...['assert', 'child_process', 'crypto', 'fs', 'module', 'os', 'path', 'process', 'readline', 'test', 'url', 'util'].map((m) => `node:${m}`), + // node (new-style): every builtin, none of them is a package.json dependency. + // Some are listed with the prefix already (node:test, node:sqlite). + ...builtinModules.map((m) => m.startsWith('node:') ? m : `node:${m}`), // other '@testing-library/react', 'react', 'react-native', 'styled-components' From 13a6acf80d422a718fe8fd971b73785a294044cd Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Sun, 11 Oct 2026 06:06:19 +0400 Subject: [PATCH 2/4] dev-test: JSDOM's WebSocket in the browser environment The browser environment replaces Node's Event with JSDOM's. Node's WebSocket dispatches through Node's EventTarget, which rejects that Event, so no open, message or error reached a listener and no connection ever came up: an ApiPromise against a live node never became ready. JSDOM's WebSocket now replaces Node's, as Event and CustomEvent already do. A spec completes a handshake with a local server and receives a frame. --- packages/dev-test/src/env/browser.spec.ts | 48 +++++++++++++++++++++++ packages/dev-test/src/env/browser.ts | 15 ++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/packages/dev-test/src/env/browser.spec.ts b/packages/dev-test/src/env/browser.spec.ts index 47f0018..1a72884 100644 --- a/packages/dev-test/src/env/browser.spec.ts +++ b/packages/dev-test/src/env/browser.spec.ts @@ -1,6 +1,9 @@ // Copyright 2017-2026 @pezkuwi/dev-test authors & contributors // SPDX-License-Identifier: Apache-2.0 +import { createHash } from 'node:crypto'; +import { createServer } from 'node:http'; + import { browser } from './browser.js'; const all = browser(); @@ -61,4 +64,49 @@ describe('browser', () => { expect(() => window.dispatchEvent(new CustomEvent('custom', { detail: 1 }))).not.toThrow(); expect(() => window.dispatchEvent(new Event('plain'))).not.toThrow(); }); + + it('opens a WebSocket and exchanges a message', async () => { + // the smallest server that completes the RFC 6455 handshake and sends one text frame + const server = createServer(); + // an upgraded socket leaves the server's tracking, so it is closed here + const sockets: { destroy: () => void }[] = []; + + server.on('upgrade', (req, socket) => { + sockets.push(socket); + + const accept = createHash('sha1') + .update(`${String(req.headers['sec-websocket-key'])}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`) + .digest('base64'); + + socket.write(`HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ${accept}\r\n\r\n`); + socket.write(Buffer.from([0x81, 0x02, 0x68, 0x69])); + }); + + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); + + const { port } = server.address() as { port: number }; + const ws = new WebSocket(`ws://127.0.0.1:${port}`); + + try { + const received = await new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error('no message within 5s')), 5_000); + + ws.onmessage = (event) => { + clearTimeout(timer); + resolve(String(event.data)); + }; + + ws.onerror = () => { + clearTimeout(timer); + reject(new Error('WebSocket error')); + }; + }); + + expect(received).toBe('hi'); + } finally { + ws.close(); + sockets.forEach((socket) => socket.destroy()); + await new Promise((resolve) => server.close(resolve)); + } + }); }); diff --git a/packages/dev-test/src/env/browser.ts b/packages/dev-test/src/env/browser.ts index fda9a8b..5926bfc 100644 --- a/packages/dev-test/src/env/browser.ts +++ b/packages/dev-test/src/env/browser.ts @@ -9,8 +9,13 @@ import { JSDOM } from 'jsdom'; * Constructors that Node defines as well. A DOM node only accepts an event * created in its own realm, so in the browser environment the JSDOM ones * replace Node's, as they do under jest-environment-jsdom. + * + * WebSocket follows from that: Node's own dispatches its events through + * Node's EventTarget, which rejects the JSDOM Event that replaced Node's, so + * no open, message or error ever reaches a listener and a connection never + * comes up. JSDOM's WebSocket dispatches events from its own realm. */ -export const BROWSER_OVERRIDES: readonly string[] = ['CustomEvent', 'Event']; +export const BROWSER_OVERRIDES: readonly string[] = ['CustomEvent', 'Event', 'WebSocket']; /** * JSDOM does no layout, so no element ever changes size and the callback @@ -62,6 +67,13 @@ function events (window: DOMWindow) { }; } +/** Network, see BROWSER_OVERRIDES for why WebSocket is JSDOM's */ +function network (window: DOMWindow) { + return { + WebSocket: window.WebSocket + }; +} + /** Styles, geometry and the observers */ function layout (window: DOMWindow) { return { @@ -90,6 +102,7 @@ export function browser () { ...nodes(window), ...events(window), ...layout(window), + ...network(window), // All HTML Elements that are defined on the JSDOM window object. // (we copied as-is from the types definition). We cannot get this // via Object.keys(window).filter(...) so we have to specify explicitly From 4e6a40ad1451b77b8b76f5911fb6edbf1275637a Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Sun, 11 Oct 2026 07:22:37 +0400 Subject: [PATCH 3/4] dev-test: expose location in the browser environment The browser environment copied window, document, navigator and localStorage onto the global, but not location. Code that reads the bare global, such as the apps URL type decoder, threw "location is not defined" while an ApiPromise was being created, so the api never became ready. location is now exposed with the other window objects. --- packages/dev-test/src/env/browser.spec.ts | 1 + packages/dev-test/src/env/browser.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/dev-test/src/env/browser.spec.ts b/packages/dev-test/src/env/browser.spec.ts index 1a72884..690ff88 100644 --- a/packages/dev-test/src/env/browser.spec.ts +++ b/packages/dev-test/src/env/browser.spec.ts @@ -21,6 +21,7 @@ describe('browser', () => { it('contains the top-level objects', () => { expect(all.document).toBeDefined(); expect(all.navigator).toBeDefined(); + expect(location.href).toBe('http://localhost/'); }); it('contains HTML*Element', () => { diff --git a/packages/dev-test/src/env/browser.ts b/packages/dev-test/src/env/browser.ts index 5926bfc..07d079d 100644 --- a/packages/dev-test/src/env/browser.ts +++ b/packages/dev-test/src/env/browser.ts @@ -181,6 +181,7 @@ export function browser () { crypto: window.crypto, document: window.document, localStorage: window.localStorage, + location: window.location, navigator: window.navigator, // window... window From ee8740effccc9913fd43a5bbec177da39122ee1b Mon Sep 17 00:00:00 2001 From: Satoshi Qazi Muhammed Date: Sun, 11 Oct 2026 07:22:58 +0400 Subject: [PATCH 4/4] Release dev, dev-test and dev-ts together as 0.86.8 Carries the browser environment fixes: JSDOM's WebSocket and the location global, which an ApiPromise against a live node needs in a browser-env spec. Also the build import check that accepts every node: builtin. dev depends on ^0.86.8 of the other two. --- packages/dev-test/package.json | 2 +- packages/dev-test/src/packageInfo.ts | 2 +- packages/dev-ts/package.json | 2 +- packages/dev-ts/src/packageInfo.ts | 2 +- packages/dev/package.json | 6 +++--- packages/dev/src/packageInfo.ts | 2 +- yarn.lock | 8 ++++---- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/dev-test/package.json b/packages/dev-test/package.json index c836614..339306b 100644 --- a/packages/dev-test/package.json +++ b/packages/dev-test/package.json @@ -15,7 +15,7 @@ }, "sideEffects": false, "type": "module", - "version": "0.86.7", + "version": "0.86.8", "main": "./index.js", "exports": { "./globals.d.ts": "./src/globals.d.ts" diff --git a/packages/dev-test/src/packageInfo.ts b/packages/dev-test/src/packageInfo.ts index d67254e..5ba5961 100644 --- a/packages/dev-test/src/packageInfo.ts +++ b/packages/dev-test/src/packageInfo.ts @@ -3,4 +3,4 @@ // Do not edit, auto-generated by @pezkuwi/dev -export const packageInfo = { name: '@pezkuwi/dev-test', path: 'auto', type: 'auto', version: '0.86.7' }; +export const packageInfo = { name: '@pezkuwi/dev-test', path: 'auto', type: 'auto', version: '0.86.8' }; diff --git a/packages/dev-ts/package.json b/packages/dev-ts/package.json index e63fa69..5679996 100644 --- a/packages/dev-ts/package.json +++ b/packages/dev-ts/package.json @@ -15,7 +15,7 @@ }, "sideEffects": false, "type": "module", - "version": "0.86.7", + "version": "0.86.8", "main": "./index.js", "exports": { "./globals.d.ts": "./src/globals.d.ts" diff --git a/packages/dev-ts/src/packageInfo.ts b/packages/dev-ts/src/packageInfo.ts index b6cff06..1f9f3ca 100644 --- a/packages/dev-ts/src/packageInfo.ts +++ b/packages/dev-ts/src/packageInfo.ts @@ -3,4 +3,4 @@ // Do not edit, auto-generated by @pezkuwi/dev -export const packageInfo = { name: '@pezkuwi/dev-ts', path: 'auto', type: 'auto', version: '0.86.7' }; +export const packageInfo = { name: '@pezkuwi/dev-ts', path: 'auto', type: 'auto', version: '0.86.8' }; diff --git a/packages/dev/package.json b/packages/dev/package.json index 6b1d0de..26e898f 100644 --- a/packages/dev/package.json +++ b/packages/dev/package.json @@ -15,7 +15,7 @@ }, "sideEffects": false, "type": "module", - "version": "0.86.7", + "version": "0.86.8", "bin": { "pezkuwi-ci-ghact-build": "./scripts/pezkuwi-ci-ghact-build.mjs", "pezkuwi-ci-ghact-docs": "./scripts/pezkuwi-ci-ghact-docs.mjs", @@ -52,8 +52,8 @@ "dependencies": { "@eslint/compat": "^2.1.1", "@eslint/js": "^10.0.1", - "@pezkuwi/dev-test": "^0.86.7", - "@pezkuwi/dev-ts": "^0.86.7", + "@pezkuwi/dev-test": "^0.86.8", + "@pezkuwi/dev-ts": "^0.86.8", "@rollup/plugin-alias": "^5.1.1", "@rollup/plugin-commonjs": "^29.0.3", "@rollup/plugin-dynamic-import-vars": "^2.1.5", diff --git a/packages/dev/src/packageInfo.ts b/packages/dev/src/packageInfo.ts index e138bc1..51a0879 100644 --- a/packages/dev/src/packageInfo.ts +++ b/packages/dev/src/packageInfo.ts @@ -3,4 +3,4 @@ // Do not edit, auto-generated by @pezkuwi/dev -export const packageInfo = { name: '@pezkuwi/dev', path: 'auto', type: 'auto', version: '0.86.7' }; +export const packageInfo = { name: '@pezkuwi/dev', path: 'auto', type: 'auto', version: '0.86.8' }; diff --git a/yarn.lock b/yarn.lock index a7720ca..08fd13d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1189,7 +1189,7 @@ __metadata: languageName: node linkType: hard -"@pezkuwi/dev-test@npm:^0.86.7, @pezkuwi/dev-test@workspace:packages/dev-test": +"@pezkuwi/dev-test@npm:^0.86.8, @pezkuwi/dev-test@workspace:packages/dev-test": version: 0.0.0-use.local resolution: "@pezkuwi/dev-test@workspace:packages/dev-test" dependencies: @@ -1205,7 +1205,7 @@ __metadata: languageName: unknown linkType: soft -"@pezkuwi/dev-ts@npm:^0.86.7, @pezkuwi/dev-ts@workspace:packages/dev-ts": +"@pezkuwi/dev-ts@npm:^0.86.8, @pezkuwi/dev-ts@workspace:packages/dev-ts": version: 0.0.0-use.local resolution: "@pezkuwi/dev-ts@workspace:packages/dev-ts" dependencies: @@ -1221,8 +1221,8 @@ __metadata: dependencies: "@eslint/compat": "npm:^2.1.1" "@eslint/js": "npm:^10.0.1" - "@pezkuwi/dev-test": "npm:^0.86.7" - "@pezkuwi/dev-ts": "npm:^0.86.7" + "@pezkuwi/dev-test": "npm:^0.86.8" + "@pezkuwi/dev-ts": "npm:^0.86.8" "@rollup/plugin-alias": "npm:^5.1.1" "@rollup/plugin-commonjs": "npm:^29.0.3" "@rollup/plugin-dynamic-import-vars": "npm:^2.1.5"