Files
pikasTech-HWLAB/web/hwlab-cloud-web/scripts/check.mjs
T
2026-05-22 11:13:47 +00:00

209 lines
8.4 KiB
JavaScript

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 { runDevCloudWorkbenchStaticSmoke } from "../../../scripts/src/dev-cloud-workbench-smoke-lib.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}`;
const workbenchSmoke = runDevCloudWorkbenchStaticSmoke();
const helpSmokeCheck = workbenchSmoke.checks.find((check) => check.id === "help-md-contract");
assert.equal(workbenchSmoke.status, "pass", JSON.stringify(workbenchSmoke.blockers, null, 2));
assert.equal(workbenchSmoke.evidenceLevel, "SOURCE");
assert.equal(workbenchSmoke.devLive, false);
assert.equal(helpSmokeCheck?.status, "pass", "PR #114 help Markdown route must be present and ready");
assert.equal(workbenchSmoke.help.status, "pass", "smoke report must not leave #114 help contract pending");
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, /<section class="view help-view"[\s\S]*?hidden/);
assert.match(html, /id="help-content"/);
assert.match(html, /data-help-route-policy="non-default-internal-help"/);
assert.match(app, /from "\.\/third_party\/marked\/marked\.esm\.js"/);
assert.match(app, /marked\.parse\(/);
assert.doesNotMatch(app, /function\s+parseMarkdown|markedRegex|markdownRegex/);
assert.match(app, /fetch\("\.\/help\.md"/);
assert.match(app, /帮助内容加载失败/);
assert.match(app, /return "workspace";/);
assert.match(styles, /\.help-view\s*{[^}]*overflow:\s*hidden;/s);
assert.match(styles, /\.help-panel\s*{[^}]*overflow:\s*hidden;/s);
assert.match(styles, /\.help-content\s*{[^}]*overflow:\s*auto;/s);
assert.match(helpMarkdown, /^# 云工作台内部使用说明/m);
for (const helpTerm of [
"左侧资源与功能导航",
"Agent 对话与 trace",
"BOX-SIMU",
"Gateway-SIMU",
"hwlab-patch-panel",
"控制",
"接线",
"可信记录",
"诊断",
"Gate",
"SOURCE",
"LOCAL",
"DRY-RUN",
"DEV-LIVE",
"BLOCKED",
"res_boxsimu_1:DO1 -> 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, "\\$&");
}