81 lines
5.4 KiB
JavaScript
81 lines
5.4 KiB
JavaScript
import { createHash } from 'node:crypto'
|
|
import { createReadStream, existsSync } from 'node:fs'
|
|
import { readFile, stat, writeFile } from 'node:fs/promises'
|
|
import { createServer } from 'node:http'
|
|
import { extname, 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 productionRoot = resolve(root, 'public/native/occt-history')
|
|
const matrixPath = resolve(root, 'scripts/freecad-naming-production-matrix.mjs')
|
|
const reportPath = resolve(root, 'config/chrome-freecad-naming-production-verification.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(productionRoot, name))) throw new Error(`Missing production 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(productionRoot, name)
|
|
const [bytes, content] = await Promise.all([stat(path).then(({ size }) => size), readFile(path)])
|
|
return { name, bytes, sha256: createHash('sha256').update(content).digest('hex') }
|
|
}))
|
|
const matrixContent = await readFile(matrixPath)
|
|
const harness = { path: 'scripts/freecad-naming-production-matrix.mjs', bytes: matrixContent.length, sha256: createHash('sha256').update(matrixContent).digest('hex') }
|
|
|
|
let resolveReport
|
|
let rejectReport
|
|
const reportPromise = new Promise((resolveValue, rejectValue) => { resolveReport = resolveValue; rejectReport = rejectValue })
|
|
const html = `<!doctype html><meta charset="utf-8"><title>Production FreeCAD naming Worker</title><script type="module">
|
|
const send = async (report) => fetch('/__report', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(report) })
|
|
try {
|
|
if (!crossOriginIsolated || typeof SharedArrayBuffer !== 'function') throw new Error('Production naming harness requires cross-origin isolation and SharedArrayBuffer.')
|
|
const [{ default: createModule }, { runFreeCadNamingProductionMatrix }] = await Promise.all([import('/production/bitbybit-occt-history.js'), import('/matrix.mjs')])
|
|
const module = await createModule({ locateFile: (path) => new URL('/production/' + path, location.href).href })
|
|
const result = await runFreeCadNamingProductionMatrix(module)
|
|
await send({ ...result, runtime: 'chrome', browserId: 'chrome', userAgent: navigator.userAgent, crossOriginIsolated })
|
|
} catch (error) {
|
|
await send({ schemaVersion: 1, status: 'failed', runtime: 'chrome', browserId: 'chrome', error: error instanceof Error ? error.stack || error.message : String(error), productionWorkerLinked: false, systemExact: false })
|
|
}
|
|
</script>`
|
|
const contentTypes = { '.js': 'text/javascript', '.mjs': '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 }
|
|
const file = request.url === '/matrix.mjs'
|
|
? matrixPath
|
|
: request.url?.startsWith('/production/') ? normalize(resolve(productionRoot, decodeURIComponent(request.url.slice('/production/'.length)))) : ''
|
|
if (!file || (file !== matrixPath && !file.startsWith(productionRoot)) || !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-production')
|
|
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 production naming harness timed out. ${stderr.slice(-2000)}`)); child.kill('SIGTERM') }, 300_000)
|
|
try {
|
|
const report = { ...(await reportPromise), artifacts, harness, generatedAt: new Date().toISOString() }
|
|
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)
|
|
}
|