import { createHash } from 'node:crypto' import { createReadStream, existsSync } from 'node:fs' import { mkdir, readFile, stat, writeFile } from 'node:fs/promises' import { createServer } from 'node:http' import { extname, join, normalize, resolve } from 'node:path' import { spawn } from 'node:child_process' import { createChromeProfile, removeChromeProfile } from './chrome-profile.mjs' const root = resolve(new URL('..', import.meta.url).pathname) const candidateRoot = resolve(root, '.cache/candidates/freecad-naming-worker') const reportPath = resolve(root, '.cache/toolchains/freecad-naming-sdk/chrome-candidate-worker-report.json') const chrome = process.env.CHROME_BIN || '/home/mes123456/.local/bin/google-chrome' const artifactNames = ['bitbybit-occt-history.js', 'bitbybit-occt-history.wasm', 'bitbybit-occt-history.data'] for (const name of artifactNames) if (!existsSync(resolve(candidateRoot, name))) throw new Error(`Missing candidate Worker artifact: ${name}`) if (!existsSync(chrome)) throw new Error(`Chrome executable is unavailable: ${chrome}`) const artifacts = await Promise.all(artifactNames.map(async (name) => { const path = resolve(candidateRoot, name) const [bytes, content] = await Promise.all([stat(path).then(({ size }) => size), readFile(path)]) return { name, bytes, sha256: createHash('sha256').update(content).digest('hex') } })) let resolveReport let rejectReport const reportPromise = new Promise((resolveValue, rejectValue) => { resolveReport = resolveValue; rejectReport = rejectValue }) const html = `FreeCAD naming candidate Worker` const contentTypes = { '.js': 'text/javascript', '.wasm': 'application/wasm', '.data': 'application/octet-stream' } const server = createServer((request, response) => { response.setHeader('Cross-Origin-Opener-Policy', 'same-origin') response.setHeader('Cross-Origin-Embedder-Policy', 'require-corp') response.setHeader('Cross-Origin-Resource-Policy', 'same-origin') if (request.method === 'POST' && request.url === '/__report') { let body = '' request.setEncoding('utf8') request.on('data', (chunk) => { body += chunk }) request.on('end', () => { try { resolveReport(JSON.parse(body)); response.writeHead(204); response.end() } catch (error) { rejectReport(error); response.writeHead(400); response.end(String(error)) } }) return } if (request.url === '/' || request.url === '/index.html') { response.setHeader('content-type', 'text/html'); response.end(html); return } if (!request.url?.startsWith('/candidate/')) { response.writeHead(404); response.end('not found'); return } const file = normalize(join(candidateRoot, decodeURIComponent(request.url.slice('/candidate/'.length)))) if (!file.startsWith(candidateRoot) || !existsSync(file)) { response.writeHead(404); response.end('not found'); return } response.setHeader('content-type', contentTypes[extname(file)] || 'application/octet-stream') createReadStream(file).pipe(response) }) await new Promise((resolveServer) => server.listen(0, '127.0.0.1', resolveServer)) const port = server.address().port const profile = await createChromeProfile('freecad-naming-candidate') const child = spawn(chrome, ['--headless=new', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage', '--no-first-run', '--no-default-browser-check', `--user-data-dir=${profile}`, `http://127.0.0.1:${port}/`], { cwd: root, stdio: ['ignore', 'ignore', 'pipe'] }) let stderr = '' child.stderr.on('data', (chunk) => { stderr += String(chunk) }) const timeout = setTimeout(() => { rejectReport(new Error(`Chrome candidate Worker harness timed out. ${stderr.slice(-2000)}`)); child.kill('SIGTERM') }, 240_000) let report try { report = await reportPromise report = { ...report, artifacts, generatedAt: new Date().toISOString() } await mkdir(resolve(reportPath, '..'), { recursive: true }) await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`) console.log(JSON.stringify(report, null, 2)) if (report.status !== 'pass') process.exitCode = 1 } finally { clearTimeout(timeout) child.kill('SIGTERM') await new Promise((resolveServer) => server.close(resolveServer)) await removeChromeProfile(profile) }