Files
EpicNext-Cms/e2e/ui/node-stubs.mjs
T
openhands 1dd584cd82 fix(ui): make fixture bundle browser-safe, fast and cacheable
The UI fixture bundle crashed at runtime, so every test waited out its
45s timeout:
- server-only modules leaked into the bundle and referenced node
  built-ins, the process global, and validated env at import time
- the previous platform: 'node' fix kept esbuild happy but emitted
  require() calls that cannot run in the browser

Fix it at the bundle boundary:
- alias node:* imports to e2e/ui/node-stubs.mjs (shape-compatible stubs,
  no real I/O)
- inject e2e/ui/process-shim.mjs for the process global
- define process.env.SKIP_ENV_VALIDATION=1 so src/env.ts skips parsing
  in the fixture (same escape hatch vitest uses)

Also make the fixture server fast to start:
- build JS and Tailwind CSS in parallel
- cache built assets in node_modules/.cache/nexst-ui-fixture keyed on a
  content hash of src/, fixtures, stubs and the server file
- startup drops from ~1.7s to ~220ms on cache hits
2026-10-17 17:13:59 +02:00

188 lines
4.1 KiB
JavaScript

// Stubs for Node.js built-ins used by server-only modules that leak into the
// UI fixture bundle. These keep esbuild from failing on `node:*` imports
// while providing shape-compatible exports so the bundle stays loadable.
// Nothing here performs real I/O; it only exists so module evaluation in the
// browser does not crash on import of server-only helpers.
export class AsyncLocalStorage {
enterWith() {}
exit() {}
getStore() {
return undefined;
}
run(_store, work) {
return work();
}
}
const cryptoApi = {
createHash: () => ({
update: () => ({
digest: () => "",
}),
}),
randomUUID: () => "00000000-0000-0000-0000-000000000000",
randomBytes: (size) => new Uint8Array(size),
createHmac: () => ({
update: () => ({
digest: () => "",
}),
}),
timingSafeEqual: () => true,
createCipheriv: () => ({
update: () => new Uint8Array(0),
final: () => new Uint8Array(0),
}),
createDecipheriv: () => ({
update: () => new Uint8Array(0),
final: () => new Uint8Array(0),
}),
webcrypto: globalThis.crypto,
};
export const {
createHash,
randomUUID,
randomBytes,
createHmac,
timingSafeEqual,
createCipheriv,
createDecipheriv,
webcrypto,
} = cryptoApi;
export const appendFile = async () => {};
export const mkdir = async () => {};
export const readdir = async () => [];
export const readFile = async () => new Uint8Array(0);
export const rename = async () => {};
export const stat = async () => ({
isDirectory: () => false,
isFile: () => true,
size: 0,
mtimeMs: 0,
});
export const unlink = async () => {};
export const writeFile = async () => {};
export const rm = async () => {};
export const mkdtemp = async () => "/tmp/fixture-stub";
export const promises = {
appendFile,
mkdir,
readdir,
readFile,
rename,
stat,
unlink,
writeFile,
rm,
mkdtemp,
};
export const existsSync = () => false;
export const readFileSync = () => new Uint8Array(0);
export const writeFileSync = () => {};
export const readdirSync = () => [];
export const mkdirSync = () => {};
export const rmSync = () => {};
export const mkdtempSync = () => "/tmp/fixture-stub";
export const appendFileSync = () => {};
export const statSync = () => ({
isDirectory: () => false,
isFile: () => true,
size: 0,
});
const pathApi = {
join: (...parts) => parts.join("/"),
resolve: (...parts) => parts.join("/"),
dirname: (value) => value.split("/").slice(0, -1).join("/"),
basename: (value) => value.split("/").pop() ?? "",
extname: (value) => {
const base = value.split("/").pop() ?? "";
const index = base.lastIndexOf(".");
return index > 0 ? base.slice(index) : "";
},
relative: (_from, to) => to,
normalize: (value) => value,
isAbsolute: (value) => value.startsWith("/"),
sep: "/",
delimiter: ":",
};
export const {
join,
resolve,
dirname,
basename,
extname,
relative,
normalize,
isAbsolute,
sep,
delimiter,
} = pathApi;
export const path = pathApi;
const osApi = {
hostname: () => "localhost",
tmpdir: () => "/tmp",
platform: () => "linux",
homedir: () => "/tmp",
};
export const { hostname, tmpdir, platform, homedir } = osApi;
export const promisify = (fn) => fn;
export const isIP = () => 0;
export const lookup = async () => ({ address: "127.0.0.1", family: 4 });
export const execFile = () => {};
export const execFileSync = () => "";
export const spawnSync = () => ({ status: 0, stdout: "", stderr: "" });
export const fileURLToPath = (value) => String(value);
export const createServer = () => ({
listen: () => {},
close: () => {},
on: () => {},
address: () => ({ port: 0 }),
});
// Single default export: all node:* aliases point at this one file, and the
// merged object keeps `import crypto from "node:crypto"`, `import path from
// "node:path"` and `import os from "node:os"` usable after aliasing.
export default {
...cryptoApi,
...pathApi,
...osApi,
appendFile,
mkdir,
readdir,
readFile,
rename,
stat,
unlink,
writeFile,
rm,
mkdtemp,
promises,
existsSync,
readFileSync,
writeFileSync,
readdirSync,
mkdirSync,
rmSync,
mkdtempSync,
appendFileSync,
statSync,
promisify,
isIP,
lookup,
execFile,
execFileSync,
spawnSync,
fileURLToPath,
createServer,
};