#!/usr/bin/env node import { mkdir, writeFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { parseSmokeArgs, printSmokeHelp, runDevCloudWorkbenchLayoutSmoke } from "./src/dev-cloud-workbench-smoke-lib.mjs"; import { ensureNotRepoReportsPath, tempReportPath } from "./src/report-paths.mjs"; const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); export function parseLayoutSmokeArgs(argv) { if (argv.includes("--help")) return { help: true }; const normalized = ["--layout"]; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; if (arg === "--static" || arg === "--source") { continue; } if (arg === "--live") { const next = argv[index + 1]; if (next && !next.startsWith("--")) { normalized.push("--url"); normalized.push(next); index += 1; } else { normalized.push("--url"); normalized.push("http://74.48.78.17:16666/"); } continue; } normalized.push(arg); if (["--url", "--report"].includes(arg)) { index += 1; if (!argv[index]) throw new Error(`${arg} requires a value`); normalized.push(argv[index]); } } return parseSmokeArgs(normalized); } export function layoutSmokeExitCode(report) { return report.status === "pass" || report.status === "usage" ? 0 : report.status === "skip" ? 2 : 2; } export function compactLayoutSmokeCliOutput(report) { if (report.status === "usage") return report; return { status: report.status, task: report.task, issue: report.issue, mode: report.mode, sourceMode: report.sourceMode, url: report.url, generatedAt: report.generatedAt, evidenceLevel: report.evidenceLevel, devLive: report.devLive, summary: report.summary, failures: (report.failures ?? []).map(compactLayoutFailure), blockers: (report.blockers ?? []).map(compactLayoutFailure), skipped: (report.skipped ?? []).map((item) => ({ checkId: item.checkId, failureType: item.failureType ?? "skip", summary: item.summary })), artifacts: { reportPath: report.artifacts?.reportPath ?? null, screenshotDir: report.artifacts?.screenshotDir ?? null, screenshotCount: Array.isArray(report.artifacts?.screenshots) ? report.artifacts.screenshots.length : 0 }, validationCommands: report.validationCommands, safety: { layoutOnly: report.safety?.layoutOnly === true, codeAgentPostSent: report.safety?.codeAgentPostSent === true, hardwareWriteApis: report.safety?.hardwareWriteApis === true, hitTestMethod: report.safety?.hitTestMethod ?? null, statement: report.safety?.statement ?? null } }; } function compactLayoutFailure(failure) { return { checkId: failure.checkId ?? failure.scope ?? null, viewport: failure.viewport ?? null, selector: failure.selector ?? null, failureType: failure.failureType ?? "blocked", summary: failure.summary ?? null, artifact: failure.artifact ?? null }; } if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) { try { const args = parseLayoutSmokeArgs(process.argv.slice(2)); const report = args.help ? { ...printSmokeHelp(), command: `node scripts/dev-cloud-workbench-layout-smoke.mjs [--static|--build|--live --url http://74.48.78.17:16666/] [--report ${tempReportPath("dev-cloud-workbench-layout.json")}]`, layoutNotes: [ "默认 --static 使用 source/static 本地静态服务。", "--build 会先运行 web/hwlab-cloud-web/scripts/build.mjs,再检查本地 dist 构建产物。", "--live --url http://74.48.78.17:16666/ 只做 DEV live UI 布局/命中/溢出检查,不发送 Code Agent 消息,不调用 M3 IO,不等于 M3 DEV-LIVE 功能通过。", "结构化失败会包含 status、viewport、selector、failureType、artifact screenshot/report path。" ] } : await runDevCloudWorkbenchLayoutSmoke(args); if (args.reportPath && report.status !== "usage") { const reportPath = ensureNotRepoReportsPath(repoRoot, path.resolve(process.cwd(), args.reportPath), "--report"); report.artifacts = { ...(report.artifacts ?? {}), reportPath }; await mkdir(path.dirname(reportPath), { recursive: true }); await writeFile(reportPath, `${JSON.stringify(report, null, 2)}\n`, "utf8"); } process.stdout.write(`${JSON.stringify(compactLayoutSmokeCliOutput(report), null, 2)}\n`); process.exitCode = layoutSmokeExitCode(report); } catch (error) { const failure = { status: "blocked", mode: "layout-browser", generatedAt: new Date().toISOString(), failureType: "blocked", selector: null, viewport: null, artifact: null, reportPath: null, summary: error instanceof Error ? error.message : String(error) }; process.stderr.write(`[dev-cloud-workbench-layout-smoke] ${error instanceof Error ? error.stack : String(error)}\n`); process.stdout.write(`${JSON.stringify(failure, null, 2)}\n`); process.exitCode = 2; } } export { repoRoot };