#!/usr/bin/env node /** * Browser translation matrix (on-device worker). * * Serves the production dist, instantiates the built translate.worker chunk in * headless Chromium, and round-trips one short sentence through every app * language (en→xx then xx→en). Verifies the worker returns text and records * latency. This is a hardware-reality check, not a CI test: it downloads the * ~617 MB multilingual model on first use. * * Usage: node scripts/translation-matrix.mjs * Output: /tmp/opencode/translation-matrix.json (+ a short summary on stdout) */ import { createServer } from 'node:http'; import { spawn } from 'node:child_process'; import { readFile, readdir } from 'node:fs/promises'; import { existsSync, rmSync, writeFileSync } from 'node:fs'; import { dirname, extname, join, normalize } from 'node:path'; import { fileURLToPath } from 'node:url'; import { tmpdir } from 'node:os'; import net from 'node:net'; import WebSocket from 'ws'; const root = join(dirname(fileURLToPath(import.meta.url)), '..'); const dist = join(root, 'dist'); const chromePath = process.env.CHROME_PATH || '/snap/bin/chromium'; const timeoutMs = Number(process.env.MATRIX_TIMEOUT_MS || 30 * 60 * 1000); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const MIME = { '.html': 'text/html', '.js': 'text/javascript', '.mjs': 'text/javascript', '.css': 'text/css', '.json': 'application/json', '.wasm': 'application/wasm', '.png': 'image/png', '.svg': 'image/svg+xml', '.woff2': 'font/woff2', }; const workerFile = (await readdir(join(dist, 'assets'))).find((file) => /^translate\.worker-.*\.js$/.test(file)); if (!workerFile) throw new Error('translate.worker chunk not found — build first'); if (!existsSync(chromePath)) throw new Error(`Chromium not found at ${chromePath}`); const freePort = () => new Promise((resolve) => { const s = net.createServer(); s.listen(0, '127.0.0.1', () => { const p = s.address().port; s.close(() => resolve(p)); }); }); const port = await freePort(); const workerUrl = `http://127.0.0.1:${port}/assets/${workerFile}`; const server = createServer(async (req, res) => { const url = decodeURIComponent((req.url || '/').split('?')[0]); if (url === '/matrix') { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end('translation matrixready'); return; } let file = join(dist, normalize(url).replace(/^(\.\.[/\\])+/, '')); if (url === '/' || !extname(file)) file = join(dist, 'index.html'); try { const body = await readFile(file); res.writeHead(200, { 'Content-Type': MIME[extname(file)] || 'application/octet-stream' }); res.end(body); } catch { res.writeHead(404); res.end('not found'); } }); await new Promise((resolve) => server.listen(port, '127.0.0.1', resolve)); const cdpPort = await freePort(); const profile = join(tmpdir(), `skatespots-matrix-${process.pid}-${cdpPort}`); const chrome = spawn(chromePath, [ '--headless=new', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage', '--disable-extensions', '--no-first-run', '--no-default-browser-check', '--js-flags=--max-old-space-size=4096', `--user-data-dir=${profile}`, `--remote-debugging-port=${cdpPort}`, `http://127.0.0.1:${port}/matrix`, ], { stdio: ['ignore', 'ignore', 'pipe'] }); chrome.stderr.on('data', () => {}); const cleanup = () => { try { chrome.kill('SIGKILL'); } catch {} try { server.close(); } catch {} try { rmSync(profile, { recursive: true, force: true }); } catch {} }; process.on('exit', cleanup); async function pageTarget() { for (let i = 0; i < 80; i++) { try { const list = await (await fetch(`http://127.0.0.1:${cdpPort}/json`)).json(); const page = list.find((t) => t.type === 'page' && t.url.includes('/matrix')); if (page) return page; } catch {} await sleep(300); } throw new Error('no Chromium page target'); } const target = await pageTarget(); const ws = new WebSocket(target.webSocketDebuggerUrl, { maxPayload: 64 * 1024 * 1024 }); let id = 0; const pending = new Map(); const exceptions = []; ws.on('message', (raw) => { const message = JSON.parse(raw); if (message.id && pending.has(message.id)) { pending.get(message.id)(message); pending.delete(message.id); return; } if (message.method === 'Runtime.exceptionThrown') { exceptions.push(message.params.exceptionDetails?.exception?.description || message.params.exceptionDetails?.text || 'exception'); } }); const send = (method, params = {}) => new Promise((resolve) => { const requestId = ++id; pending.set(requestId, resolve); ws.send(JSON.stringify({ id: requestId, method, params })); }); await new Promise((resolve) => ws.on('open', resolve)); await send('Runtime.enable'); await send('Page.enable'); const evalJS = async (expression, awaitPromise = false) => (await send('Runtime.evaluate', { expression, returnByValue: true, awaitPromise, }))?.result?.result?.value; const init = await evalJS(`(() => { try { const worker = new Worker(${JSON.stringify(workerUrl)}, { type: 'module' }); window.__matrix = { worker, seq: 0, pending: new Map() }; worker.onerror = (event) => { window.__matrixError = String(event.message || event); }; worker.onmessage = (event) => { const data = event.data; if (!data || typeof data.id !== 'number') return; if (data.type === 'progress') return; const p = window.__matrix.pending.get(data.id); if (!p) return; window.__matrix.pending.delete(data.id); if (data.type === 'result') p.resolve(data.text); else p.reject(new Error(data.code || 'FAILED')); }; window.__matrix.translate = (text, source, target) => new Promise((resolve, reject) => { const requestId = ++window.__matrix.seq; window.__matrix.pending.set(requestId, { resolve, reject }); worker.postMessage({ type: 'translate', id: requestId, text, source, target }); }); return 'ok'; } catch (error) { window.__matrixError = String(error); return 'error'; } })()`); if (init !== 'ok') throw new Error(`worker init failed: ${await evalJS('String(window.__matrixError || "unknown")')}`); const ALL_LANGUAGES = ['en', 'es', 'ca', 'eu', 'gl', 'hi', 'th', 'ja', 'pt', 'fr', 'de', 'it', 'nl', 'pl', 'ru', 'ko', 'zh-CN', 'zh-TW', 'uk', 'tr', 'vi', 'id', 'sv', 'cs', 'ro', 'el']; const selected = (process.env.MATRIX_LANGUAGES ?? '').split(',').map((v) => v.trim()).filter(Boolean); const languages = selected.length ? ['en', ...selected.filter((code) => code !== 'en')] : ALL_LANGUAGES; const sourceText = 'The skater dropped into the bowl and landed a clean trick.'; const results = []; const started = Date.now(); for (const language of languages.filter((code) => code !== 'en')) { const t0 = Date.now(); let ok = false; let forward = ''; let back = ''; let error = ''; try { forward = await evalJS(`window.__matrix.translate(${JSON.stringify(sourceText)}, ${JSON.stringify('en')}, ${JSON.stringify(language)})`, true) ?? ''; back = await evalJS(`window.__matrix.translate(${JSON.stringify(forward)}, ${JSON.stringify(language)}, ${JSON.stringify('en')})`, true) ?? ''; ok = typeof forward === 'string' && forward.trim().length > 0 && typeof back === 'string' && back.trim().length > 0; } catch (err) { error = err instanceof Error ? err.message : String(err); } const ms = Date.now() - t0; results.push({ language, ok, ms, forwardLength: forward.length, backLength: back.length, error }); writeFileSync('/tmp/opencode/translation-matrix.partial.json', JSON.stringify({ results }, null, 2)); console.log(`${language}: ${ok ? 'ok' : 'FAIL'} ${ms}ms forward=${forward.length} back=${back.length}${error ? ` (${error})` : ''}`); } const elapsed = Date.now() - started; const heap = await evalJS('performance.memory ? performance.memory.usedJSHeapSize : null'); const report = { workerFile, languages: languages.length, results, elapsedMs: elapsed, jsHeapBytes: heap, exceptions }; writeFileSync('/tmp/opencode/translation-matrix.json', JSON.stringify(report, null, 2)); console.log(JSON.stringify({ ok: results.every((r) => r.ok), languages: results.length, elapsedMs: elapsed, jsHeapBytes: heap, exceptions: exceptions.length })); ws.close(); cleanup(); process.exit(report.ok && exceptions.length === 0 ? 0 : 1);