mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-23 06:10:02 +08:00
- Make DNS promises imports load with the expected exports and explicit query errors. - Resolve CommonJS directory requires used during Redis initialization. - Record the missing TypeScript order baselines required by the full gate. Fixes #434 Co-authored-by: Artem Gurtovoi <1233869+temich@users.noreply.github.com>
125 lines
6.3 KiB
JavaScript
125 lines
6.3 KiB
JavaScript
// Differential probe for the island plumbing the vercel CLI's module
|
|
// graph loads (or drives) on its read-only workflows. Every line prints
|
|
// under Node AND the island and must byte-match; surfaces whose island
|
|
// answers legitimately diverge (dns lookups fence, MessageChannel does
|
|
// not structured-clone) are only probed for their Node-agreeing halves.
|
|
import { createRequire } from 'node:module';
|
|
import { lookup as importedDnsPromiseLookup } from 'node:dns/promises';
|
|
const require = createRequire(import.meta.url);
|
|
// Packages such as node-redis load this subpath during module evaluation.
|
|
const dnsPromisesAtLoad = require('node:dns/promises');
|
|
const dnsPromisesBare = require('dns/promises');
|
|
|
|
export async function probe() {
|
|
const lines = [];
|
|
|
|
// URL: the live search/searchParams coupling the CLI's API client
|
|
// drives (searchParams.set then fetch(url)).
|
|
const u = new URL('https://api.example.com/v2/user?limit=20');
|
|
u.searchParams.set('teamId', 'team_123');
|
|
u.searchParams.append('x', 'a b&c');
|
|
lines.push('url:' + u.href);
|
|
lines.push('search:' + u.search + '|' + u.searchParams.get('x'));
|
|
u.search = 'fresh=1';
|
|
lines.push('reset:' + u.href + '|' + u.searchParams.get('fresh') + '|' + u.searchParams.get('teamId'));
|
|
const empty = new URL('https://h.example/p');
|
|
empty.searchParams.set('only', '1');
|
|
lines.push('fromEmpty:' + empty.href);
|
|
|
|
// Blob + File: bytes concatenate (strings through utf-8, views copied),
|
|
// type normalizes (printable-ASCII lowercased), WHATWG slice clamping,
|
|
// and node:buffer re-exports the SAME classes.
|
|
const b = new Blob(['hello w', new Uint8Array([111, 114]), 'ld'], { type: 'Text/Plain' });
|
|
lines.push('blob:' + b.size + ':' + b.type + ':' + (await b.text()));
|
|
const sl = b.slice(-5, 100, 'X/Y');
|
|
lines.push('slice:' + (await sl.text()) + ':' + sl.type);
|
|
lines.push('sliceBad:' + new Blob(['abc'], { type: 'aéb' }).type + '|' + b.slice(2, 1).size);
|
|
const f = new File(['abc'], 'a.txt', { lastModified: 42 });
|
|
lines.push('file:' + f.name + ':' + f.size + ':' + f.lastModified);
|
|
const buf = require('buffer');
|
|
lines.push('bufferBlob:' + (buf.Blob === Blob) + ':' + (buf.File === File));
|
|
|
|
// Event / EventTarget / CustomEvent: once, handleEvent objects,
|
|
// preventDefault vs dispatchEvent's answer.
|
|
const t = new EventTarget();
|
|
let got = '';
|
|
t.addEventListener('ping', (e) => { got += e.type + '=' + e.detail + ';'; }, { once: true });
|
|
t.dispatchEvent(new CustomEvent('ping', { detail: 7 }));
|
|
t.dispatchEvent(new CustomEvent('ping', { detail: 8 }));
|
|
t.addEventListener('h', { handleEvent(e) { got += 'h' + e.detail + ';'; } });
|
|
t.dispatchEvent(new CustomEvent('h', { detail: 9 }));
|
|
const cancelable = new Event('c', { cancelable: true });
|
|
t.addEventListener('c', (e) => e.preventDefault());
|
|
const answer = t.dispatchEvent(cancelable);
|
|
lines.push('events:' + got + answer + ':' + cancelable.defaultPrevented);
|
|
|
|
// worker_threads: the main-thread surface undici loads unguarded, and a
|
|
// working MessageChannel pair (string messages — clone-invisible).
|
|
const wt = require('worker_threads');
|
|
lines.push('wt:' + wt.isMainThread + ':' + (wt.parentPort === null) + ':' + wt.threadId);
|
|
const ch = new wt.MessageChannel();
|
|
ch.port1.postMessage('m1');
|
|
ch.port1.postMessage('m2');
|
|
const r1 = wt.receiveMessageOnPort(ch.port2);
|
|
const r2 = wt.receiveMessageOnPort(ch.port2);
|
|
const r3 = wt.receiveMessageOnPort(ch.port2);
|
|
lines.push('mc:' + r1.message + ':' + r2.message + ':' + (r3 === undefined));
|
|
|
|
// perf_hooks: a real clock behind performance.now.
|
|
const { performance } = require('perf_hooks');
|
|
lines.push('perf:' + (typeof performance.now() === 'number') + ':' + (performance.now() >= 0));
|
|
|
|
// domain: active tracking through run, sync throws PROPAGATE (Node's
|
|
// contract — the domain traps only fatal/async errors), intercept's
|
|
// error arm emits 'error' decorated with the domain.
|
|
const domain = require('domain');
|
|
const d = domain.create();
|
|
let active = false;
|
|
let caught = '';
|
|
try {
|
|
d.run(() => { active = domain.active === d; throw new Error('boomd'); });
|
|
} catch (e) {
|
|
caught = e.message;
|
|
}
|
|
let intercepted = '';
|
|
d.on('error', (e) => { intercepted = e.message + ':' + (e.domain === d); });
|
|
d.intercept(() => { intercepted = 'wrong'; })(new Error('picked'));
|
|
lines.push('domain:' + active + ':' + caught + ':' + !domain.active + ':' + intercepted);
|
|
|
|
// dns: the loadable surface (calls fence island-side, so only presence
|
|
// is differential).
|
|
const dns = require('dns');
|
|
lines.push('dns:' + (typeof dns.lookup === 'function') + ':' + (typeof dns.promises.lookup === 'function') + ':' + (typeof dns.Resolver === 'function'));
|
|
lines.push('dnsPromises:' + (dnsPromisesAtLoad === dns.promises) + ':' + (dnsPromisesBare === dnsPromisesAtLoad) + ':' + (typeof dnsPromisesAtLoad.lookup === 'function') + ':' + (typeof dnsPromisesAtLoad.Resolver === 'function') + ':' + (typeof dnsPromisesAtLoad.getDefaultResultOrder === 'function'));
|
|
lines.push('dnsPromisesImport:' + (importedDnsPromiseLookup === dnsPromisesAtLoad.lookup));
|
|
lines.push('dnsPromisesKeys:' + Object.keys(dnsPromisesAtLoad).sort().join(','));
|
|
const promiseResolver = new dnsPromisesAtLoad.Resolver();
|
|
lines.push('dnsResolver:' + (typeof promiseResolver.resolve4 === 'function') + ':' + (typeof promiseResolver.cancel === 'function'));
|
|
lines.push('dnsConstants:' + dnsPromisesAtLoad.NOTFOUND + ':' + dnsPromisesAtLoad.EOF + ':' + dnsPromisesAtLoad.CANCELLED);
|
|
dnsPromisesAtLoad.setDefaultResultOrder('ipv4first');
|
|
lines.push('dnsOrder:' + dns.getDefaultResultOrder() + ':' + dnsPromisesBare.getDefaultResultOrder());
|
|
dns.setDefaultResultOrder('verbatim');
|
|
|
|
// process.exitCode: Node's implicit exit status — set it, return
|
|
// normally, the process exits with it (the harness diffs exit codes).
|
|
process.exitCode = 3;
|
|
return lines.join('\n');
|
|
}
|
|
|
|
// Querying a network name is intentionally outside this loadable shim.
|
|
export async function probeDnsRefusal() {
|
|
const failures = [];
|
|
for (const [method, call] of [
|
|
['lookup', () => dnsPromisesAtLoad.lookup('localhost')],
|
|
['resolve4', () => new dnsPromisesAtLoad.Resolver().resolve4('localhost')],
|
|
]) {
|
|
try {
|
|
await call();
|
|
failures.push(method + ':unexpected success');
|
|
} catch (error) {
|
|
failures.push(method + ':' + error.code + ':' + error.syscall + ':' + error.message);
|
|
}
|
|
}
|
|
return failures.join('\n');
|
|
}
|