Files
ycode/scripts/test-mode-chip-native.mjs
T

77 lines
4.5 KiB
JavaScript

/** Own-YCode isolated component fixture. Run on a permitted Electron desktop:
* node scripts/test-mode-chip-native.mjs
* No original app, model, SDK, user data, or production session access.
*/
import fs from 'node:fs'
import path from 'node:path'
import { createRequire } from 'node:module'
import { spawn } from 'node:child_process'
const require = createRequire(import.meta.url)
if (process.versions.electron) throw new Error('Launch this fixture builder with Node, not Electron')
const base = path.resolve('.tmp')
fs.mkdirSync(base, { recursive: true })
const file = 'src/renderer/src/workbuddy/composer/ModeChip.tsx'
async function prepareFixture(output) {
const src = relative => JSON.stringify(path.resolve(relative).replaceAll('\\', '/'))
fs.writeFileSync(path.join(output, 'fixture.tsx'), `
import React,{useState} from 'react'; import {createRoot} from 'react-dom/client';
import {ModeChip} from ${src(file)};
import ${src('src/renderer/src/workbuddy/typography/tokens.css')};
import ${src('src/renderer/src/styles/tokens.css')};
import ${src('src/renderer/src/ui/tokens.css')};
import ${src('src/renderer/src/styles/app.css')};
import ${src('src/renderer/src/workbuddy/conversation/source-styles.css')};
const q=(window as any).qa={clears:0};
function Fixture(){const [mode,setMode]=useState<any>('plan'),[disabled,setDisabled]=useState(false);Object.assign(q,{setMode,setDisabled});return <div className="chat-pane cr-theme" style={{padding:40}}><div style={{display:"flex",alignItems:"center",gap:8}}><button id="before">Before</button><ModeChip mode={mode} disabled={disabled} onClear={()=>{q.clears++;setMode('craft')}}/><button id="after">After</button></div></div>};createRoot(document.getElementById('root')!).render(<Fixture/>);`)
await require('esbuild').build({ entryPoints: [path.join(output, 'fixture.tsx')], bundle: true, platform: 'browser', jsx: 'automatic', outfile: path.join(output, 'fixture.js'), logLevel: 'warning' })
fs.writeFileSync(path.join(output, 'index.html'), '<html data-theme="light"><head><meta charset="utf-8"><link rel="stylesheet" href="fixture.css"></head><body><div id="root"></div><script src="fixture.js"></script></body></html>')
}
const output = fs.mkdtempSync(path.join(base, 'mode-chip-native-'))
function phase(name) {
const detail = { phase: name, time: new Date().toISOString(), output }
fs.writeFileSync(path.join(output, 'runner-phase.json'), JSON.stringify(detail, null, 2))
console.log(JSON.stringify(detail))
}
function fail(error) {
const detail = String(error.stack || error)
fs.writeFileSync(path.join(output, 'runner-failure.txt'), detail)
console.error(detail); process.exitCode = 1
}
phase('prepare-fixture')
const buildTimeout = setTimeout(() => {
fail(new Error('Fixture preparation timed out after 30000ms'))
process.exit(1)
}, 30000)
try {
// All esbuild work finishes under Node, before starting Electron's CJS entry.
await prepareFixture(output)
clearTimeout(buildTimeout)
phase('fixture-built')
if (process.argv.includes('--build-only')) {
console.log(JSON.stringify({ output, fixtureBuilt: true, nativeExecuted: false }))
} else {
const env = { ...process.env }
delete env.ELECTRON_RUN_AS_NODE; delete env.ELECTRON_RENDERER_URL
phase('launch-electron')
const child = spawn(require('electron'), [path.resolve('scripts/test-mode-chip-native-electron.cjs'), output], { env, windowsHide: true, stdio: 'inherit' })
let timedOut = false, spawnFailed = false
const timeout = setTimeout(() => {
timedOut = true
let lastPhase = 'no Electron phase recorded'
try { lastPhase = fs.readFileSync(path.join(output, 'phase.json'), 'utf8') } catch {}
fail(new Error(`Electron timed out after 60000ms; last phase: ${lastPhase}`))
// Only terminate the child created by this runner, never an existing app/dev.
child.kill('SIGKILL')
}, 60000)
child.once('error', error => { spawnFailed = true; clearTimeout(timeout); fail(error) })
child.once('close', (code, signal) => {
clearTimeout(timeout)
const passed = !timedOut && !spawnFailed && code === 0 && fs.existsSync(path.join(output, 'report.json'))
process.exitCode = passed ? 0 : 1
if (!passed && !timedOut && !spawnFailed) fail(new Error(`Electron did not complete: exitCode=${code}, signal=${signal}; see phase.json / failure.txt`))
phase(passed ? 'complete' : 'failed')
console.log(JSON.stringify({ output, exitCode: code, signal, timedOut, nativeExecuted: passed }))
})
}
} catch (error) { clearTimeout(buildTimeout); fail(error) }