import { spawn } from 'node:child_process' import { existsSync } from 'node:fs' import { mkdir, writeFile } from 'node:fs/promises' import { resolve } from 'node:path' import { createServer as createViteServer } from 'vite' import { createChromeProfile, removeChromeProfile } from './chrome-profile.mjs' const root = resolve(new URL('..', import.meta.url).pathname) const reportPath = resolve(root, 'config/chrome-part-primitives-verification.json') const chromeExecutable = process.env.CHROME_BIN || '/home/mes123456/.local/bin/google-chrome' if (!existsSync(chromeExecutable)) throw new Error(`Chrome executable is unavailable: ${chromeExecutable}`) const vite = await createViteServer({ root, server: { host: '127.0.0.1', port: 0, strictPort: false, headers: { 'Cross-Origin-Opener-Policy': 'same-origin', 'Cross-Origin-Embedder-Policy': 'require-corp' } }, optimizeDeps: { exclude: ['@bitbybit-dev/occt', '@bitbybit-dev/occt-worker'] } }) await vite.listen() const address = vite.httpServer?.address() if (!address || typeof address === 'string') throw new Error('Vite Part primitive harness server did not expose a port.') const url = `http://127.0.0.1:${address.port}/chrome-part-primitives-harness.html` const chromeProfile = await createChromeProfile('part-primitives') const chrome = spawn(chromeExecutable, ['--headless=new', '--no-sandbox', '--disable-dev-shm-usage', '--no-first-run', '--no-default-browser-check', '--enable-webgl', '--enable-unsafe-swiftshader', '--use-angle=swiftshader', '--remote-debugging-port=0', `--user-data-dir=${chromeProfile}`, 'about:blank'], { cwd: root, stdio: ['ignore', 'ignore', 'pipe'] }) let stderr = '' const debuggerUrl = await new Promise((resolveDebugger, rejectDebugger) => { const timer = setTimeout(() => rejectDebugger(new Error(`Chrome DevTools endpoint timed out. ${stderr.slice(-2000)}`)), 30_000) chrome.stderr.on('data', (chunk) => { stderr += String(chunk); const match = stderr.match(/DevTools listening on (ws:\/\/[^\s]+)/); if (match) { clearTimeout(timer); resolveDebugger(match[1]) } }) chrome.once('exit', (code, signal) => { clearTimeout(timer); rejectDebugger(new Error(`Chrome exited before DevTools was ready: code=${code}, signal=${signal}.`)) }) }) class CdpClient { constructor(url) { this.socket = new WebSocket(url); this.sequence = 0; this.pending = new Map(); this.listeners = new Set() } async open() { await new Promise((resolveOpen, rejectOpen) => { this.socket.addEventListener('open', resolveOpen, { once: true }); this.socket.addEventListener('error', () => rejectOpen(new Error('Chrome DevTools WebSocket failed to open.')), { once: true }) }); this.socket.addEventListener('message', (event) => { const message = JSON.parse(String(event.data)); if (message.id) { const pending = this.pending.get(message.id); if (!pending) return; this.pending.delete(message.id); if (message.error) pending.reject(new Error(`${pending.method}: ${message.error.message}`)); else pending.resolve(message.result); return } for (const listener of this.listeners) listener(message) }) } send(method, params = {}, sessionId) { const id = ++this.sequence; return new Promise((resolveMessage, rejectMessage) => { this.pending.set(id, { resolve: resolveMessage, reject: rejectMessage, method }); this.socket.send(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) })) }) } close() { this.socket.close() } } const client = new CdpClient(debuggerUrl) let report try { await client.open() const version = await client.send('Browser.getVersion') if (!/Chrome/i.test(version.product || '')) throw new Error(`Unexpected browser product: ${version.product || 'unknown'}`) const { targetInfos } = await client.send('Target.getTargets') const target = targetInfos.find((candidate) => candidate.type === 'page') if (!target) throw new Error('Chrome did not expose a page target.') const { sessionId } = await client.send('Target.attachToTarget', { targetId: target.targetId, flatten: true }) await Promise.all([client.send('Page.enable', {}, sessionId), client.send('Runtime.enable', {}, sessionId)]) const evaluate = async (expression) => { const result = await client.send('Runtime.evaluate', { expression, returnByValue: true, awaitPromise: true }, sessionId); if (result.exceptionDetails) throw new Error(result.exceptionDetails.exception?.description || result.exceptionDetails.text || 'Chrome evaluation failed.'); return result.result?.value } await client.send('Page.navigate', { url }, sessionId) const deadline = Date.now() + 120_000 while (Date.now() < deadline) { report = await evaluate('window.__bitbybitPartPrimitiveReport || null'); if (report) break; await new Promise((resolveWait) => setTimeout(resolveWait, 250)) } if (!report) throw new Error('Chrome Part primitive harness timed out.') if (report.status !== 'pass') throw new Error(report.error || 'Chrome Part primitive harness failed.') report = { ...report, browser: { product: version.product, userAgent: version.userAgent } } await mkdir(resolve(root, 'config'), { recursive: true }) await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`) console.log(JSON.stringify(report, null, 2)) } finally { client.close(); chrome.kill('SIGTERM'); await vite.close(); await removeChromeProfile(chromeProfile) }