verify.ts
1 /** 2 * Verification: runs validation and optional smoke test. 3 * 4 * The smoke test is intentionally kept as a stub — full browser-based 5 * smoke testing requires a running browser session and is better suited 6 * to the `opencli test` command or CI pipelines. 7 */ 8 9 import { validateClisWithTarget, renderValidationReport, type ValidationReport } from './validate.js'; 10 import { spawn } from 'node:child_process'; 11 import * as fs from 'node:fs'; 12 import * as path from 'node:path'; 13 14 export interface VerifyOptions { 15 builtinClis: string; 16 userClis: string; 17 target?: string; 18 smoke?: boolean; 19 } 20 21 export interface VerifyReport { 22 ok: boolean; 23 validation: ValidationReport; 24 smoke: null | { 25 requested: boolean; 26 executed: boolean; 27 ok: boolean; 28 summary: string; 29 }; 30 } 31 32 export async function verifyClis(opts: VerifyOptions): Promise<VerifyReport> { 33 const report = validateClisWithTarget([opts.builtinClis, opts.userClis], opts.target); 34 let smoke: VerifyReport['smoke'] = null; 35 if (opts.smoke) { 36 smoke = await runSmokeTests(opts.builtinClis); 37 } 38 return { ok: report.ok && (smoke?.ok ?? true), validation: report, smoke }; 39 } 40 41 export function renderVerifyReport(report: VerifyReport): string { 42 const base = renderValidationReport(report.validation); 43 if (!report.smoke) return base; 44 const status = report.smoke.ok ? 'PASS' : 'FAIL'; 45 const mode = report.smoke.executed ? 'executed' : 'skipped'; 46 return `${base}\nSmoke: ${status} (${mode}) — ${report.smoke.summary}`; 47 } 48 49 async function runSmokeTests(builtinClis: string): Promise<NonNullable<VerifyReport['smoke']>> { 50 const projectRoot = path.resolve(builtinClis, '..', '..'); 51 const smokeDir = path.join(projectRoot, 'tests', 'smoke'); 52 53 if (!fs.existsSync(smokeDir)) { 54 return { 55 requested: true, 56 executed: false, 57 ok: false, 58 summary: 'Smoke tests are unavailable in this package/environment.', 59 }; 60 } 61 62 const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx'; 63 return new Promise((resolve) => { 64 const child = spawn(npx, ['vitest', 'run', 'tests/smoke/', '--reporter=dot'], { 65 cwd: projectRoot, 66 env: { ...process.env }, 67 stdio: ['ignore', 'pipe', 'pipe'], 68 }); 69 70 let stderr = ''; 71 child.stderr.on('data', (chunk) => { 72 stderr += chunk.toString(); 73 }); 74 75 child.on('error', (error) => { 76 resolve({ 77 requested: true, 78 executed: false, 79 ok: false, 80 summary: `Failed to start smoke tests: ${error.message}`, 81 }); 82 }); 83 84 child.on('close', (code) => { 85 resolve({ 86 requested: true, 87 executed: true, 88 ok: code === 0, 89 summary: code === 0 ? 'tests/smoke passed' : (stderr.trim() || `vitest exited with code ${code}`), 90 }); 91 }); 92 }); 93 }