#!/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);