114 lines
6.3 KiB
JavaScript
114 lines
6.3 KiB
JavaScript
import { execFile } from 'node:child_process'
|
|
import { createHash } from 'node:crypto'
|
|
import { existsSync } from 'node:fs'
|
|
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises'
|
|
import { resolve } from 'node:path'
|
|
import { promisify } from 'node:util'
|
|
import { createServer as createViteServer, preview as createVitePreview } from 'vite'
|
|
import { chromium } from '../cnc_wams_gpt6/linuxcnc-master/web/node_modules/playwright/index.mjs'
|
|
|
|
const root = resolve(new URL('..', import.meta.url).pathname)
|
|
const reportPath = resolve(root, 'config/chrome-cam-linuxcnc-machine-verification.json')
|
|
const artifactManifestPath = resolve(root, 'config/linuxcnc-wasm-machine-artifact.json')
|
|
const linuxcncRoot = resolve(root, 'cnc_wams_gpt6/linuxcnc-master')
|
|
const linuxcncWebRoot = resolve(linuxcncRoot, 'web')
|
|
const executable = process.env.CHROME_BIN || '/home/mes123456/.local/bin/google-chrome'
|
|
|
|
let linuxcnc
|
|
let vite
|
|
let browser
|
|
let report
|
|
try {
|
|
if (!existsSync(executable)) throw new Error(`Chrome executable is unavailable: ${executable}`)
|
|
const manifest = JSON.parse(await readFile(artifactManifestPath, 'utf8'))
|
|
if (manifest.schemaVersion !== 1 || !/^[0-9a-f]{40}$/.test(manifest.worktreeRevision) || !Array.isArray(manifest.artifacts) || manifest.artifacts.length !== 7 || manifest.machineConfigPath !== manifest.artifacts.find(({ role }) => role === 'machine-config')?.path) {
|
|
throw new Error('LinuxCNC WASM machine artifact manifest is invalid.')
|
|
}
|
|
const actualRevision = (await promisify(execFile)('git', ['-C', linuxcncRoot, 'rev-parse', 'HEAD'])).stdout.trim()
|
|
if (actualRevision !== manifest.worktreeRevision) throw new Error(`LinuxCNC worktree revision mismatch: expected ${manifest.worktreeRevision}, got ${actualRevision}.`)
|
|
const artifactIdentity = []
|
|
for (const artifact of manifest.artifacts) {
|
|
const path = resolve(root, manifest.distRoot, artifact.path)
|
|
const [bytes, metadata] = await Promise.all([readFile(path), stat(path)])
|
|
const sha256 = createHash('sha256').update(bytes).digest('hex')
|
|
if (metadata.size !== artifact.bytes || sha256 !== artifact.sha256) throw new Error(`LinuxCNC WASM artifact mismatch: ${artifact.path}.`)
|
|
artifactIdentity.push({ role: artifact.role, path: artifact.path, runtimeLoaded: artifact.runtimeLoaded, bytes: metadata.size, sha256 })
|
|
}
|
|
|
|
// Own the LinuxCNC server lifecycle so a missing external preview cannot be
|
|
// misreported as a parser/controller timeout.
|
|
linuxcnc = await createVitePreview({
|
|
root: linuxcncWebRoot,
|
|
configFile: resolve(linuxcncWebRoot, 'vite.config.js'),
|
|
preview: { host: '127.0.0.1', port: 0, strictPort: false },
|
|
})
|
|
const linuxcncAddress = linuxcnc.httpServer.address()
|
|
if (!linuxcncAddress || typeof linuxcncAddress === 'string') throw new Error('LinuxCNC WASM preview did not expose a port.')
|
|
const linuxcncUrl = `https://127.0.0.1:${linuxcncAddress.port}`
|
|
|
|
vite = await createViteServer({
|
|
configFile: false,
|
|
root,
|
|
server: {
|
|
host: '127.0.0.1',
|
|
port: 5201,
|
|
strictPort: false,
|
|
watch: null,
|
|
proxy: {
|
|
'/linuxcnc-machine': {
|
|
target: linuxcncUrl,
|
|
changeOrigin: true,
|
|
secure: false,
|
|
rewrite: (path) => path.replace(/^\/linuxcnc-machine/, ''),
|
|
},
|
|
},
|
|
},
|
|
})
|
|
await vite.listen()
|
|
const address = vite.httpServer?.address()
|
|
if (!address || typeof address === 'string') throw new Error('CAM machine harness server did not expose a port.')
|
|
browser = await chromium.launch({ headless: true, executablePath: executable, args: ['--no-sandbox', '--disable-dev-shm-usage', '--enable-unsafe-swiftshader'] })
|
|
const browserVersion = await browser.version()
|
|
if (browserVersion !== manifest.chromeVersion) throw new Error(`Chrome version mismatch: expected ${manifest.chromeVersion}, got ${browserVersion}.`)
|
|
const context = await browser.newContext({ ignoreHTTPSErrors: true })
|
|
const page = await context.newPage()
|
|
const networkStatuses = new Map()
|
|
page.on('response', (response) => {
|
|
const pathname = new URL(response.url()).pathname
|
|
if (!pathname.startsWith('/linuxcnc-machine/')) return
|
|
const relativePath = pathname.slice('/linuxcnc-machine/'.length) || 'index.html'
|
|
networkStatuses.set(relativePath, response.status())
|
|
})
|
|
await page.goto(`http://127.0.0.1:${address.port}/chrome-cam-linuxcnc-machine-harness.html`, { waitUntil: 'domcontentloaded' })
|
|
// The harness performs independent dry-run and run submissions, each with a
|
|
// 180 second controller budget, plus initial machine startup.
|
|
await page.waitForFunction(() => Boolean(window.__bitbybitCamLinuxcncMachineReport), null, { timeout: 420_000 })
|
|
report = await page.evaluate(() => window.__bitbybitCamLinuxcncMachineReport)
|
|
if (report.machine?.machineCase !== manifest.machineCase || report.machine?.dryRunMachineCase !== manifest.machineCase) throw new Error(`LinuxCNC browser loaded unexpected machine cases: run=${report.machine?.machineCase || '<missing>'}, dry-run=${report.machine?.dryRunMachineCase || '<missing>'}.`)
|
|
const loadedArtifacts = manifest.artifacts.filter(({ runtimeLoaded }) => runtimeLoaded).map(({ role, path }) => ({ role, path, status: networkStatuses.get(path) ?? 0 }))
|
|
const missingArtifacts = loadedArtifacts.filter(({ status }) => status !== 200)
|
|
if (missingArtifacts.length > 0) throw new Error(`Chrome did not load pinned LinuxCNC runtime artifacts: ${missingArtifacts.map(({ role, path, status }) => `${role}:${path}:${status}`).join(', ')}.`)
|
|
report = {
|
|
...report,
|
|
runtime: {
|
|
server: 'managed-vite-preview',
|
|
worktreeRevision: actualRevision,
|
|
machineCase: report.machine.machineCase,
|
|
artifacts: artifactIdentity,
|
|
loadedArtifacts,
|
|
},
|
|
browserId: 'chrome',
|
|
browser: { product: browserVersion, userAgent: await page.evaluate(() => navigator.userAgent) },
|
|
}
|
|
await context.close()
|
|
if (report.status !== 'pass') process.exitCode = 1
|
|
} catch (error) {
|
|
report = { schemaVersion: 1, status: 'failed', browserId: 'chrome', error: error instanceof Error ? error.stack || error.message : String(error) }
|
|
process.exitCode = 1
|
|
} finally {
|
|
await Promise.allSettled([browser?.close(), vite?.close(), linuxcnc?.close()])
|
|
await mkdir(resolve(root, 'config'), { recursive: true })
|
|
await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`)
|
|
console.log(JSON.stringify(report, null, 2))
|
|
}
|