import assert from "node:assert/strict"; import fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { loadMvpGateSummary } from "../../../internal/mvp-gate/summary.mjs"; import { gateSummary } from "../gate-summary.mjs"; import { runCloudWebM3ReadonlyContract } from "./m3-readonly-contract.mjs"; import { runWorkbenchHardwarePanelContract } from "./workbench-hardware-panel-contract.mjs"; const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const repoRoot = path.resolve(rootDir, "../.."); const requiredFiles = [ "index.html", "styles.css", "app.mjs", "gate-summary.mjs", "runtime.mjs", "workbench-hardware-panel.mjs", "help.md", "third_party/marked/marked.esm.js", "third_party/marked/LICENSE" ]; for (const file of requiredFiles) { const filePath = path.resolve(rootDir, file); if (!fs.existsSync(filePath)) { throw new Error(`missing web asset: ${file}`); } } const html = fs.readFileSync(path.resolve(rootDir, "index.html"), "utf8"); const styles = fs.readFileSync(path.resolve(rootDir, "styles.css"), "utf8"); const app = fs.readFileSync(path.resolve(rootDir, "app.mjs"), "utf8"); const helpMarkdown = fs.readFileSync(path.resolve(rootDir, "help.md"), "utf8"); const buildScript = fs.readFileSync(path.resolve(rootDir, "scripts/build.mjs"), "utf8"); const markedLicense = fs.readFileSync(path.resolve(rootDir, "third_party/marked/LICENSE"), "utf8"); const artifactPublisher = fs.readFileSync(path.resolve(repoRoot, "scripts/dev-artifact-publish.mjs"), "utf8"); const frontendSource = `${html}\n${app}\n${artifactPublisher}`; assert.match(html, /HWLAB 云工作台/); for (const chineseWorkbenchText of [ "硬件资源", "Agent 对话", "执行轨迹", "可信记录", "诊断", "使用说明", "允许的只读 RPC" ]) { assert.match(`${html}\n${app}`, new RegExp(chineseWorkbenchText)); } for (const legacyEnglishTitle of [ /Agent Conversation \/ Trace Workspace/, /Resource Tree/, /Current Blockers/, /Control Drafts/, /Trusted Records/, /Same-Origin Read Probe/, /Allowed Read RPC/, /Open Blockers/, /Light Validation/, /DEV Health Contract/, /Secondary Entry/, /Runtime Surface/ ]) { assert.doesNotMatch(`${html}\n${app}`, legacyEnglishTitle); } assert.match(html, /硬件状态:BOX-SIMU \/ Gateway-SIMU \/ hwlab-patch-panel/); assert.match(html, /Gate \/ 诊断/); for (const workbenchElement of [ "activity-rail", "explorer", "resource-tree", "conversation-list", "trace-list", "right-sidebar", "command-form", "hardware-list", "control-list", "wiring-list", "records-list", "diagnostics-list" ]) { assert.match(html, new RegExp(workbenchElement)); } for (const viewId of ["workspace", "gate"]) { assert.match(html, new RegExp(`data-view="${viewId}"`)); } assert.match(html, /data-route="help"/); assert.match(html, /data-view="help"/); assert.match(html, /
hwlab-patch-panel -> res_boxsimu_2:DI1", "same-origin `/v1`", "只读 `/json-rpc`", "`/health/live`", "`:16666`", "`:16667`" ]) { assert.match(helpMarkdown, new RegExp(escapeRegExp(helpTerm)), `missing help term: ${helpTerm}`); } assert.match(buildScript, /"help\.md"/); assert.match(buildScript, /"third_party\/marked\/marked\.esm\.js"/); assert.match(buildScript, /"third_party\/marked\/LICENSE"/); assert.match(markedLicense, /MarkedJS/); assert.match(markedLicense, /Permission is hereby granted, free of charge/); assert.match(html, /live-status/); assert.match(html, /method-list/); assert.match(html, /source-strip/); assert.match(html, /来源 SOURCE 加载中/); assert.match(html, /演练 DRY-RUN 加载中/); assert.match(html, /开发实况 DEV-LIVE 探测中/); assert.match(html, /阻塞 BLOCKED 等待中/); assert.doesNotMatch(html, /M3 Diagnostics Console/); assert.match(styles, /html,\s*\nbody\s*{[^}]*height:\s*100%;[^}]*overflow:\s*hidden;/s); assert.match(styles, /body\s*>\s*\[data-app-shell\]\s*{[^}]*min-height:\s*0;/s); assert.match(styles, /\.workbench-shell\s*{[^}]*height:\s*100(?:d)?vh;[^}]*overflow:\s*hidden;/s); assert.match(styles, /\.view\s*{[^}]*min-height:\s*0;[^}]*overflow:\s*auto;/s); assert.match(styles, /\.(?:resource-tree|compact-list|conversation-list|trace-list|hardware-list)[^{]*{[^}]*min-height:\s*0;[^}]*overflow:\s*auto;/s); assert.match(app, /fetchJson\("\/v1"\)/); assert.match(app, /fetchJson\("\/health\/live"\)/); assert.match(app, /callRpc\("system\.health"\)/); assert.match(app, /callRpc\("cloud\.adapter\.describe"\)/); assert.match(app, /callRpc\("audit\.event\.query"/); assert.match(app, /callRpc\("evidence\.record\.query"/); assert.match(artifactPublisher, /HWLAB_API_BASE_URL/); assert.match(artifactPublisher, /readOnlyRpcMethods/); assert.match(artifactPublisher, /requestUpstream/); assert.match(artifactPublisher, /from "node:http"/); assert.doesNotMatch(artifactPublisher, /await fetch\(target/); assert.match(artifactPublisher, /"system\.health"/); assert.match(artifactPublisher, /"cloud\.adapter\.describe"/); assert.match(artifactPublisher, /"audit\.event\.query"/); assert.match(artifactPublisher, /"evidence\.record\.query"/); assert.match(artifactPublisher, /readonly_rpc_required/); assert.match(artifactPublisher, /cloud_api_proxy_failed/); assert.match(app, /degraded/); assert.match(app, /runtime\.serviceRoute\.join\(" -> "\)/); assert.doesNotMatch(app, /callRpc\("hardware\./); assert.doesNotMatch(app, /hardware\.operation\.request/); assert.doesNotMatch(app, /hardware\.invoke\.shell/); assert.doesNotMatch(app, /audit\.event\.write/); assert.doesNotMatch(app, /evidence\.record\.write/); assert.doesNotMatch(frontendSource, /--live --confirm-dev --confirmed-non-production/); assert.doesNotMatch(frontendSource, /74\.48\.78\.17:666[67]\b|:666[67]\b/); const sourceSummary = loadMvpGateSummary(repoRoot); assert.equal(gateSummary.endpoint, sourceSummary.endpoint); assert.equal(gateSummary.gateStatus, sourceSummary.gateStatus); assert.equal(gateSummary.milestones.length, 6); assert.deepEqual( gateSummary.milestones.map((item) => item.id), ["M0", "M1", "M2", "M3", "M4", "M5"] ); assert.equal(gateSummary.artifactCount, 13); assert.equal(gateSummary.healthCount, 14); assert.equal(gateSummary.gatewaySessionCount, 2); assert.equal(gateSummary.boxResourceCount, 2); assert.equal(gateSummary.topology.patchPanel.serviceId, "hwlab-patch-panel"); assert.equal(gateSummary.agent.agentServiceId, "hwlab-agent-mgr"); assert.equal(gateSummary.agent.workerServiceId, "hwlab-agent-worker"); assert.equal(gateSummary.evidenceRecords.length, 2); assert.equal(gateSummary.safety.allowNetwork, false); runCloudWebM3ReadonlyContract(); runWorkbenchHardwarePanelContract(); console.log("hwlab-cloud-web check ok: workbench shell, hardware evidence panel, and read-only diagnostics contract are present"); function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }