refactor: migrate v0.2 runtime services to TypeScript
This commit is contained in:
@@ -312,7 +312,7 @@ test("compact summary blocks publish on stale Cloud Web source dist", () => {
|
||||
assert.equal(summary.canApply, false);
|
||||
assert.equal(summary.cloudWebBuildFreshness.status, "blocked");
|
||||
assert.ok(summary.blockedReasons.some((item) => item.id === "cloud-web-build-freshness"));
|
||||
assert.ok(summary.nextCommands.includes("node web/hwlab-cloud-web/scripts/build.mjs"));
|
||||
assert.ok(summary.nextCommands.includes("bun run --cwd web/hwlab-cloud-web build"));
|
||||
});
|
||||
|
||||
test("compact summary separates Code Agent timeout and durable runtime blockers from apply readiness", () => {
|
||||
|
||||
@@ -18,6 +18,15 @@ test("check plan exposes grouped tasks", () => {
|
||||
assert.ok(checkProfiles.validate.length > 5);
|
||||
assert.ok(checkProfiles.check.some((task) => task.group === "cloud-api"));
|
||||
assert.ok(checkProfiles.check.some((task) => task.group === "cloud-web"));
|
||||
assert.ok(checkProfiles.check.some((task) => task.group === "runtime-services"));
|
||||
});
|
||||
|
||||
test("check plan runs migrated runtime service TypeScript tests", () => {
|
||||
const commands = checkProfiles.check.map((task) => task.command.join(" "));
|
||||
assert.ok(commands.some((command) => command.includes("cmd/hwlab-edge-proxy/main.test.ts")));
|
||||
assert.ok(commands.some((command) => command.includes("cmd/hwlab-deepseek-responses-bridge/main.test.ts")));
|
||||
assert.equal(commands.some((command) => command.includes("cmd/hwlab-edge-proxy/main.mjs")), false);
|
||||
assert.equal(commands.some((command) => command.includes("cmd/hwlab-deepseek-responses-bridge/main.test.mjs")), false);
|
||||
});
|
||||
|
||||
test("runner filters profile tasks by group", () => {
|
||||
|
||||
@@ -155,6 +155,7 @@ async function runLocalContractSmoke() {
|
||||
HWLAB_CODE_AGENT_CODEX_WORKSPACE: stdioWorkspace,
|
||||
HWLAB_CODE_AGENT_CODEX_SANDBOX: "workspace-write",
|
||||
HWLAB_CODE_AGENT_SKILLS_DIRS: stdioSkillsDir,
|
||||
HWLAB_CODE_AGENT_SKILLS_STRICT: "1",
|
||||
HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses"
|
||||
};
|
||||
const stdioPwd = await handleCodeAgentChat(
|
||||
@@ -213,7 +214,7 @@ async function runLocalContractSmoke() {
|
||||
assert.ok(runnerTraceLabels(stdioPwd).includes("tool:codex-app-server.thread/start+turn/start:started"));
|
||||
assert.ok(runnerTraceLabels(stdioPwd).includes("tool:codex-app-server.thread/start+turn/start:output_chunk"));
|
||||
assert.ok(runnerTraceLabels(stdioPwd).includes("tool:codex-app-server.thread/start+turn/start:completed"));
|
||||
assert.ok(runnerTraceLabels(stdioPwd).includes("assistant:chunk"));
|
||||
assert.ok(stdioPwd.runnerTrace.assistantStreams.some((stream) => stream.label === "assistant:stream" && stream.chunkCount >= 1));
|
||||
assert.ok(runnerTraceLabels(stdioPwd).includes("assistant:completed"));
|
||||
assert.equal(classifyCodexRunnerCapability(stdioPwd, { httpStatus: 200 }).capabilityPass, true);
|
||||
|
||||
|
||||
@@ -281,7 +281,7 @@ async function makeFixture({
|
||||
{
|
||||
name: "hwlab-codex-api-forwarder",
|
||||
image: workloadImage,
|
||||
command: ["node", "/app/cmd/hwlab-codex-api-responses-forwarder/main.mjs"],
|
||||
command: ["/usr/local/bin/bun", "run", "/app/cmd/hwlab-codex-api-responses-forwarder/main.ts"],
|
||||
ports: [{ name: "codex-api", containerPort: 49280 }],
|
||||
env: [
|
||||
{ name: "HWLAB_CODE_AGENT_CODEX_API_FORWARDER_HOST", value: "127.0.0.1" },
|
||||
|
||||
@@ -102,7 +102,7 @@ if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
||||
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 构建产物。",
|
||||
"--build 会先运行 cd web/hwlab-cloud-web && bun run build,再检查本地 dist 构建产物。",
|
||||
"--live --url http://74.48.78.17:16666/ 只做 DEV live UI 布局/命中/溢出检查,不发送 Code Agent 消息,不调用 Device Pod 或硬件写操作,不等同于硬件验收。",
|
||||
"结构化失败会包含 status、viewport、selector、failureType、artifact screenshot/report path。"
|
||||
]
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
compactLayoutSmokeCliOutput,
|
||||
parseLayoutSmokeArgs
|
||||
} from "./dev-cloud-workbench-layout-smoke.mjs";
|
||||
import { checkProfiles } from "./src/check-plan.mjs";
|
||||
|
||||
const sourceIdentity = Object.freeze({
|
||||
status: "observed",
|
||||
@@ -46,7 +47,7 @@ const expectedRuntimeIdentity = Object.freeze({
|
||||
});
|
||||
const rootPackage = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
||||
const cloudWebPackage = JSON.parse(readFileSync(new URL("../web/hwlab-cloud-web/package.json", import.meta.url), "utf8"));
|
||||
const cloudWebCheckSource = readFileSync(new URL("../web/hwlab-cloud-web/scripts/check.mjs", import.meta.url), "utf8");
|
||||
const cloudWebCheckSource = readFileSync(new URL("../web/hwlab-cloud-web/scripts/check.ts", import.meta.url), "utf8");
|
||||
|
||||
test("workbench smoke defaults to SOURCE mode and requires live confirmation before DEV-LIVE provider calls", () => {
|
||||
const defaultArgs = parseSmokeArgs([]);
|
||||
@@ -314,17 +315,17 @@ test("live web asset identity blocks stale deployed primary assets", () => {
|
||||
const pass = classifyLiveWebAssetIdentity([
|
||||
{ path: "index.html", status: "match" },
|
||||
{ path: "styles.css", status: "match" },
|
||||
{ path: "app.mjs", status: "match" }
|
||||
{ path: "app.js", status: "match" }
|
||||
]);
|
||||
assert.equal(pass.status, "pass");
|
||||
|
||||
const stale = classifyLiveWebAssetIdentity([
|
||||
{ path: "index.html", status: "match" },
|
||||
{ path: "styles.css", status: "mismatch" },
|
||||
{ path: "app.mjs", status: "mismatch" }
|
||||
{ path: "app.js", status: "mismatch" }
|
||||
]);
|
||||
assert.equal(stale.status, "blocked");
|
||||
assert.deepEqual(stale.mismatches, ["styles.css", "app.mjs"]);
|
||||
assert.deepEqual(stale.mismatches, ["styles.css", "app.js"]);
|
||||
assert.match(stale.summary, /Deployment drift/u);
|
||||
});
|
||||
|
||||
@@ -731,8 +732,9 @@ test("layout smoke verifies desktop and mobile default workbench geometry withou
|
||||
});
|
||||
|
||||
test("repo-owned web checks expose source build and DEV live layout smoke gates", () => {
|
||||
assert.match(rootPackage.scripts["web:check"], /^node web\/hwlab-cloud-web\/scripts\/check\.mjs/u);
|
||||
assert.match(rootPackage.scripts["web:check"], /node --test web\/hwlab-cloud-web\/message-markdown\.test\.mjs web\/hwlab-cloud-web\/scripts\/trace-scroll\.test\.mjs/u);
|
||||
const checkCommands = checkProfiles.check.map((task) => task.command.join(" "));
|
||||
assert.match(rootPackage.scripts["web:check"], /cd web\/hwlab-cloud-web && bun run check/u);
|
||||
assert.match(rootPackage.scripts["web:check"], /bun test web\/hwlab-cloud-web\/message-markdown\.test\.ts/u);
|
||||
assert.equal(
|
||||
rootPackage.scripts["web:layout"],
|
||||
"node scripts/dev-cloud-workbench-layout-smoke.mjs --static --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout.json"
|
||||
@@ -745,9 +747,9 @@ test("repo-owned web checks expose source build and DEV live layout smoke gates"
|
||||
rootPackage.scripts["web:layout:live"],
|
||||
"node scripts/dev-cloud-workbench-layout-smoke.mjs --live --url http://74.48.78.17:16666/ --report /tmp/hwlab-dev-gate/dev-cloud-workbench-layout-live.json"
|
||||
);
|
||||
assert.match(rootPackage.scripts.check, /node --check scripts\/dev-cloud-workbench-layout-smoke\.mjs/u);
|
||||
assert.match(rootPackage.scripts.check, /node web\/hwlab-cloud-web\/scripts\/check\.mjs/u);
|
||||
assert.doesNotMatch(rootPackage.scripts.check, /gate-summary\.mjs|export-web-gate-summary/u);
|
||||
assert.ok(checkCommands.some((command) => command === "node --check scripts/dev-cloud-workbench-layout-smoke.mjs"));
|
||||
assert.ok(checkProfiles.check.some((task) => task.cwd === "web/hwlab-cloud-web" && task.command.join(" ") === "bun run check"));
|
||||
assert.doesNotMatch(checkCommands.join("\n"), /gate-summary\.mjs|export-web-gate-summary/u);
|
||||
assert.equal(cloudWebPackage.scripts.layout.includes("--static"), true);
|
||||
assert.equal(cloudWebPackage.scripts["layout:build"].includes("--build"), true);
|
||||
assert.equal(cloudWebPackage.scripts["layout:live"].includes("--live --url http://74.48.78.17:16666/"), true);
|
||||
|
||||
@@ -17,7 +17,7 @@ const execFileAsync = promisify(execFile);
|
||||
|
||||
test("G14 CI planner maps bridge changes to cloud-api only", async () => {
|
||||
const repo = await createFixtureRepo();
|
||||
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.mjs"), "console.log('bridge v2');\n");
|
||||
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.ts"), "console.log('bridge v2');\n");
|
||||
await git(repo, ["add", "."]);
|
||||
await git(repo, ["commit", "-m", "change bridge"]);
|
||||
|
||||
@@ -72,7 +72,7 @@ test("component model uses built-in service paths", () => {
|
||||
|
||||
test("planner remains compatible with deploy.k3s.serviceMappings shape", async () => {
|
||||
const repo = await createFixtureRepo({ deployServices: false, k3sServiceMappings: true });
|
||||
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.mjs"), "console.log('bridge v2');\n");
|
||||
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.ts"), "console.log('bridge v2');\n");
|
||||
await git(repo, ["add", "."]);
|
||||
await git(repo, ["commit", "-m", "change bridge"]);
|
||||
|
||||
@@ -85,7 +85,7 @@ test("planner remains compatible with deploy.k3s.serviceMappings shape", async (
|
||||
|
||||
test("global change classifier distinguishes gitops-only and test-only", () => {
|
||||
assert.equal(classifyGlobalChange(["deploy/gitops/g14/runtime-dev/workloads.yaml"]).gitopsOnly, true);
|
||||
assert.equal(classifyGlobalChange(["cmd/hwlab-cloud-api/main.test.mjs"]).testOnly, true);
|
||||
assert.equal(classifyGlobalChange(["cmd/hwlab-cloud-api/runtime-options.test.ts"]).testOnly, true);
|
||||
});
|
||||
|
||||
async function createFixtureRepo(options = {}) {
|
||||
@@ -104,8 +104,8 @@ async function createFixtureRepo(options = {}) {
|
||||
await writeFile(path.join(repo, "package.json"), JSON.stringify({ type: "module" }, null, 2));
|
||||
await writeFile(path.join(repo, "package-lock.json"), JSON.stringify({ lockfileVersion: 3 }, null, 2));
|
||||
await writeFile(path.join(repo, "cmd/hwlab-cloud-api/main.ts"), "console.log('api');\n");
|
||||
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.mjs"), "console.log('bridge');\n");
|
||||
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.test.mjs"), "console.log('test');\n");
|
||||
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.ts"), "console.log('bridge');\n");
|
||||
await writeFile(path.join(repo, "cmd/hwlab-deepseek-responses-bridge/main.test.ts"), "console.log('test');\n");
|
||||
await writeFile(path.join(repo, "web/hwlab-cloud-web/index.html"), "<html></html>\n");
|
||||
await git(repo, ["init"]);
|
||||
await git(repo, ["add", "."]);
|
||||
|
||||
@@ -132,9 +132,7 @@ NODE`,
|
||||
{
|
||||
name: "codex-api-forwarder-check",
|
||||
commands: [
|
||||
"node --check cmd/hwlab-codex-api-responses-forwarder/main.mjs",
|
||||
"node --check cmd/hwlab-codex-api-responses-forwarder/main.test.mjs",
|
||||
"node --test cmd/hwlab-codex-api-responses-forwarder/main.test.mjs"
|
||||
"/root/.bun/bin/bun test cmd/hwlab-codex-api-responses-forwarder/main.test.ts"
|
||||
]
|
||||
}
|
||||
]);
|
||||
@@ -2602,7 +2600,7 @@ function deepSeekProxyManifest({ profile = "dev", source, registryPrefix = defau
|
||||
name: "responses-bridge",
|
||||
image: bridgeImage,
|
||||
imagePullPolicy: "IfNotPresent",
|
||||
command: ["node", "/app/cmd/hwlab-deepseek-responses-bridge/main.mjs"],
|
||||
command: ["/usr/local/bin/bun", "run", "/app/cmd/hwlab-deepseek-responses-bridge/main.ts"],
|
||||
env: [
|
||||
{ name: "PORT", value: "4000" },
|
||||
{ name: "HWLAB_COMMIT_ID", value: bridgeCommit },
|
||||
|
||||
@@ -39,7 +39,7 @@ try {
|
||||
await waitForJson(`http://127.0.0.1:${cloudPort}/health/live`);
|
||||
|
||||
if (useEdgeProxy) {
|
||||
const edge = startNode("cmd/hwlab-edge-proxy/main.mjs", {
|
||||
const edge = startNode("cmd/hwlab-edge-proxy/main.ts", {
|
||||
...commonEnv,
|
||||
HWLAB_EDGE_HOST: "127.0.0.1",
|
||||
HWLAB_EDGE_PORT: String(edgePort),
|
||||
|
||||
@@ -48,8 +48,8 @@ function freePort() {
|
||||
}
|
||||
|
||||
function startNode(relativeScript, env = {}) {
|
||||
const command = relativeScript.endsWith(".ts") ? bunCommand : process.execPath;
|
||||
const child = spawn(command, [relativeScript], {
|
||||
const command = commandForScript(relativeScript);
|
||||
const child = spawn(command.bin, command.args, {
|
||||
cwd: repoRoot,
|
||||
env: {
|
||||
...process.env,
|
||||
@@ -87,7 +87,8 @@ function resolveBunCommand() {
|
||||
|
||||
function runNodeJson(relativeScript, args = [], env = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(process.execPath, [relativeScript, ...args], {
|
||||
const command = commandForScript(relativeScript, args);
|
||||
const child = spawn(command.bin, command.args, {
|
||||
cwd: repoRoot,
|
||||
env: {
|
||||
...process.env,
|
||||
@@ -121,6 +122,19 @@ function runNodeJson(relativeScript, args = [], env = {}) {
|
||||
});
|
||||
}
|
||||
|
||||
function commandForScript(relativeScript, args = []) {
|
||||
if (relativeScript.endsWith(".ts")) {
|
||||
return {
|
||||
bin: bunCommand,
|
||||
args: ["run", relativeScript, ...args]
|
||||
};
|
||||
}
|
||||
return {
|
||||
bin: process.execPath,
|
||||
args: [relativeScript, ...args]
|
||||
};
|
||||
}
|
||||
|
||||
async function stopNode(record) {
|
||||
const { child } = record;
|
||||
if (child.exitCode !== null || child.signalCode !== null) {
|
||||
@@ -547,7 +561,7 @@ async function smokeAgentMgrLocalContract() {
|
||||
let agentMgr;
|
||||
|
||||
try {
|
||||
agentMgr = startNode("cmd/hwlab-agent-mgr/main.mjs", {
|
||||
agentMgr = startNode("cmd/hwlab-agent-mgr/main.ts", {
|
||||
PORT: String(agentMgrPort),
|
||||
HWLAB_AGENT_RUNTIME_STATE_DIR: stateDir,
|
||||
HWLAB_ENVIRONMENT: ENVIRONMENT_DEV
|
||||
@@ -567,7 +581,7 @@ async function smokeAgentMgrLocalContract() {
|
||||
assert.ok(healthLive.body.health.skills.missing.includes("manager.skillVersion"));
|
||||
assert.equal(healthLive.body.skillsManifest, null);
|
||||
|
||||
const health = await runNodeJson("cmd/hwlab-agent-mgr/main.mjs", [
|
||||
const health = await runNodeJson("cmd/hwlab-agent-mgr/main.ts", [
|
||||
"health",
|
||||
"--state-dir",
|
||||
stateDir,
|
||||
@@ -587,7 +601,7 @@ async function smokeAgentMgrLocalContract() {
|
||||
assert.equal(health.skillsManifest.services[0].serviceId, "hwlab-agent-skills");
|
||||
assert.equal(health.skillsManifest.environment, ENVIRONMENT_DEV);
|
||||
|
||||
const created = await runNodeJson("cmd/hwlab-agent-mgr/main.mjs", [
|
||||
const created = await runNodeJson("cmd/hwlab-agent-mgr/main.ts", [
|
||||
"create",
|
||||
"--state-dir",
|
||||
stateDir,
|
||||
@@ -616,7 +630,7 @@ async function smokeAgentMgrLocalContract() {
|
||||
assert.equal(created.traceCount, 1);
|
||||
assert.equal(created.evidenceCount, 0);
|
||||
|
||||
const status = await runNodeJson("cmd/hwlab-agent-mgr/main.mjs", [
|
||||
const status = await runNodeJson("cmd/hwlab-agent-mgr/main.ts", [
|
||||
"status",
|
||||
"--state-dir",
|
||||
stateDir,
|
||||
@@ -629,7 +643,7 @@ async function smokeAgentMgrLocalContract() {
|
||||
assert.equal(status.status, "ok");
|
||||
assert.equal(status.task.taskId, `tsk_${agentSessionId}`);
|
||||
|
||||
const cleanup = await runNodeJson("cmd/hwlab-agent-mgr/main.mjs", [
|
||||
const cleanup = await runNodeJson("cmd/hwlab-agent-mgr/main.ts", [
|
||||
"cleanup",
|
||||
"--state-dir",
|
||||
stateDir,
|
||||
|
||||
@@ -11,7 +11,7 @@ export const checkProfiles = Object.freeze({
|
||||
{ id: "validate-004-deploy-run-bun", group: "deploy", command: ["node","scripts/run-bun.mjs","scripts/deploy-desired-state-plan.mjs","--check"] },
|
||||
{ id: "validate-005-dev-runtime-run-bun", group: "dev-runtime", command: ["node","scripts/run-bun.mjs","scripts/dev-runtime-provisioning.mjs","--check"] },
|
||||
{ id: "validate-006-dev-runtime-run-bun", group: "dev-runtime", command: ["node","scripts/run-bun.mjs","scripts/dev-runtime-migration.mjs","--check"] },
|
||||
{ id: "validate-007-dev-runtime-dev-runtime-postflight", group: "dev-runtime", command: ["node","scripts/dev-runtime-postflight.mjs","--check"] },
|
||||
{ id: "validate-007-dev-runtime-run-bun", group: "dev-runtime", command: ["node","scripts/run-bun.mjs","scripts/dev-runtime-postflight.mjs","--check"] },
|
||||
{ id: "validate-008-dev-runtime-dev-runtime-hotfix-audit", group: "dev-runtime", command: ["node","scripts/dev-runtime-hotfix-audit.mjs"] },
|
||||
{ id: "validate-009-smoke-run-bun", group: "smoke", command: ["node","scripts/run-bun.mjs","scripts/rpt004-mvp-e2e-harness.mjs","--check","--no-write"] },
|
||||
{ id: "validate-010-artifact-artifact-runtime-readiness-guard", group: "artifact", command: ["node","--check","scripts/artifact-runtime-readiness-guard.mjs"] },
|
||||
@@ -68,13 +68,7 @@ export const checkProfiles = Object.freeze({
|
||||
{ id: "check-045-cloud-api-run-bun", group: "cloud-api", command: ["node","scripts/run-bun.mjs","build","cmd/hwlab-cloud-api/main.ts","--target=bun","--packages=external","--outdir=/tmp/hwlab-ts-check"] },
|
||||
{ id: "check-046-cloud-api-run-bun", group: "cloud-api", command: ["node","scripts/run-bun.mjs","build","cmd/hwlab-cloud-api/provision.ts","--target=bun","--packages=external","--outdir=/tmp/hwlab-ts-check"] },
|
||||
{ id: "check-047-cloud-api-run-bun", group: "cloud-api", command: ["node","scripts/run-bun.mjs","build","cmd/hwlab-cloud-api/migrate.ts","--target=bun","--packages=external","--outdir=/tmp/hwlab-ts-check"] },
|
||||
{ id: "check-048-repo-main", group: "repo", command: ["node","--check","cmd/hwlab-deepseek-responses-bridge/main.mjs"] },
|
||||
{ id: "check-049-repo-main-test", group: "repo", command: ["node","--check","cmd/hwlab-deepseek-responses-bridge/main.test.mjs"] },
|
||||
{ id: "check-050-repo-main-test", group: "repo", command: ["node","--test","cmd/hwlab-deepseek-responses-bridge/main.test.mjs"] },
|
||||
{ id: "check-051-repo-main", group: "repo", command: ["node","--check","cmd/hwlab-edge-proxy/main.mjs"] },
|
||||
{ id: "check-052-repo-main", group: "repo", command: ["node","--check","cmd/hwlab-agent-mgr/main.mjs"] },
|
||||
{ id: "check-053-repo-main", group: "repo", command: ["node","--check","cmd/hwlab-agent-worker/main.mjs"] },
|
||||
{ id: "check-054-repo-main", group: "repo", command: ["node","--check","cmd/hwlab-device-pod/main.mjs"] },
|
||||
{ id: "check-048-runtime-services-run-bun", group: "runtime-services", command: ["node","scripts/run-bun.mjs","test","cmd/hwlab-edge-proxy/main.test.ts","cmd/hwlab-device-pod/main.test.ts","cmd/hwlab-agent-mgr/main.test.ts","cmd/hwlab-agent-worker/main.test.ts","cmd/hwlab-codex-api-responses-forwarder/main.test.ts","cmd/hwlab-deepseek-responses-bridge/main.test.ts"] },
|
||||
{ id: "check-055-repo-main", group: "repo", command: ["node","--check","cmd/hwlab-box-simu/main.mjs"] },
|
||||
{ id: "check-056-repo-main", group: "repo", command: ["node","--check","cmd/hwlab-gateway/main.mjs"] },
|
||||
{ id: "check-057-repo-main", group: "repo", command: ["node","--check","cmd/hwlab-gateway-simu/main.mjs"] },
|
||||
@@ -157,8 +151,8 @@ export const checkProfiles = Object.freeze({
|
||||
{ id: "check-134-dev-runtime-run-bun", group: "dev-runtime", command: ["node","scripts/run-bun.mjs","scripts/dev-runtime-migration.mjs","--check"] },
|
||||
{ id: "check-135-cloud-api-run-bun", group: "cloud-api", command: ["node","scripts/run-bun.mjs","cmd/hwlab-cloud-api/provision.ts","--check"] },
|
||||
{ id: "check-136-cloud-api-run-bun", group: "cloud-api", command: ["node","scripts/run-bun.mjs","cmd/hwlab-cloud-api/migrate.ts","--check"] },
|
||||
{ id: "check-137-dev-runtime-dev-runtime-postflight", group: "dev-runtime", command: ["node","scripts/dev-runtime-postflight.mjs","--check"] },
|
||||
{ id: "check-138-smoke-rpt004-mvp-e2e-harness", group: "smoke", command: ["node","scripts/rpt004-mvp-e2e-harness.mjs","--check","--no-write"] },
|
||||
{ id: "check-137-dev-runtime-run-bun", group: "dev-runtime", command: ["node","scripts/run-bun.mjs","scripts/dev-runtime-postflight.mjs","--check"] },
|
||||
{ id: "check-138-smoke-run-bun", group: "smoke", command: ["node","scripts/run-bun.mjs","scripts/rpt004-mvp-e2e-harness.mjs","--check","--no-write"] },
|
||||
{ id: "check-139-smoke-dev-evidence-blocker-aggregator", group: "smoke", command: ["node","scripts/dev-evidence-blocker-aggregator.mjs","--check"] },
|
||||
{ id: "check-140-smoke-l2-runtime-contract-smoke", group: "smoke", command: ["node","scripts/l2-runtime-contract-smoke.mjs"] },
|
||||
{ id: "check-141-smoke-l6-cli-web-smoke", group: "smoke", command: ["node","scripts/l6-cli-web-smoke.mjs"] },
|
||||
|
||||
@@ -63,17 +63,15 @@ function serviceSourceState(catalog, serviceId) {
|
||||
|
||||
export async function resolveDevArtifactService(repoRoot, serviceId, catalog = null) {
|
||||
const sourceState = serviceSourceState(catalog, serviceId);
|
||||
if (serviceId === "hwlab-cloud-api") {
|
||||
const tsCmdPath = `cmd/${serviceId}/main.ts`;
|
||||
if (await pathExists(repoRoot, tsCmdPath)) {
|
||||
return withServicePublishPolicy({
|
||||
serviceId,
|
||||
runtimeKind: "bun-command",
|
||||
entrypoint: tsCmdPath,
|
||||
implementationState: "repo-entrypoint",
|
||||
sourceState
|
||||
});
|
||||
}
|
||||
const tsCmdPath = `cmd/${serviceId}/main.ts`;
|
||||
if (await pathExists(repoRoot, tsCmdPath)) {
|
||||
return withServicePublishPolicy({
|
||||
serviceId,
|
||||
runtimeKind: "bun-command",
|
||||
entrypoint: tsCmdPath,
|
||||
implementationState: "repo-entrypoint",
|
||||
sourceState
|
||||
});
|
||||
}
|
||||
|
||||
const cmdPath = `cmd/${serviceId}/main.mjs`;
|
||||
|
||||
@@ -6,7 +6,6 @@ import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
|
||||
import { buildDevicePodRestPayload } from "../../internal/device-pod/fake-data.mjs";
|
||||
import { runtime } from "../../web/hwlab-cloud-web/runtime.mjs";
|
||||
import {
|
||||
classifyCodeAgentBrowserJourney,
|
||||
classifyCodeAgentBrowserFailure,
|
||||
@@ -20,6 +19,7 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../
|
||||
const domOnlyReportPath = tempReportPath("dev-cloud-workbench-dom-only.json");
|
||||
const liveReportPath = tempReportPath("dev-cloud-workbench-live.json");
|
||||
const webRoot = path.join(repoRoot, "web/hwlab-cloud-web");
|
||||
const runtime = readCloudWebRuntime();
|
||||
const defaultLiveUrl = "http://74.48.78.17:16666/";
|
||||
const helpOwner = "codex_1779444232735_1";
|
||||
const legacyFailureWindowMs = 4500;
|
||||
@@ -76,23 +76,6 @@ const readOnlyRpcMethods = Object.freeze([
|
||||
"cloud.adapter.describe"
|
||||
]);
|
||||
|
||||
const requiredWebAssets = Object.freeze([
|
||||
"index.html",
|
||||
"styles.css",
|
||||
"auth.mjs",
|
||||
"app.mjs",
|
||||
"code-agent-facts.mjs",
|
||||
"code-agent-status.mjs",
|
||||
"message-markdown.mjs",
|
||||
"live-status.mjs",
|
||||
"runtime.mjs",
|
||||
"help.md",
|
||||
"third_party/marked/marked.esm.js",
|
||||
"third_party/marked/LICENSE"
|
||||
]);
|
||||
|
||||
const liveIdentityWebAssets = Object.freeze(["index.html", "styles.css", "auth.mjs", "app.mjs", "code-agent-facts.mjs", "code-agent-status.mjs", "message-markdown.mjs", "live-status.mjs"]);
|
||||
|
||||
const chineseWorkbenchLabels = Object.freeze([
|
||||
"云工作台登录",
|
||||
"HWLAB 云工作台",
|
||||
@@ -296,6 +279,37 @@ const markdownRendererPackages = Object.freeze([
|
||||
"commonmark"
|
||||
]);
|
||||
|
||||
const cloudWebAppSourceFiles = Object.freeze([
|
||||
"app.ts",
|
||||
"app-device-pod.ts",
|
||||
"app-conversation.ts",
|
||||
"app-trace.ts",
|
||||
"app-helpers.ts"
|
||||
]);
|
||||
|
||||
const cloudWebModuleSourceFiles = Object.freeze([
|
||||
"auth.ts",
|
||||
"code-agent-facts.ts",
|
||||
"code-agent-status.ts",
|
||||
"message-markdown.ts",
|
||||
"live-status.ts",
|
||||
"runtime.ts"
|
||||
]);
|
||||
|
||||
const requiredWebAssets = Object.freeze([
|
||||
"index.html",
|
||||
"styles.css",
|
||||
...cloudWebAppSourceFiles,
|
||||
...cloudWebModuleSourceFiles,
|
||||
"favicon.svg",
|
||||
"favicon.ico",
|
||||
"help.md",
|
||||
"third_party/marked/marked.esm.js",
|
||||
"third_party/marked/LICENSE"
|
||||
]);
|
||||
|
||||
const liveIdentityWebAssets = Object.freeze(["index.html", "styles.css", "app.js"]);
|
||||
|
||||
const playwrightDependencyCommand = "npm install";
|
||||
const playwrightDependencySummary =
|
||||
"Install repository dependencies before running browser smokes: npm install provides the declared playwright package; the Code Queue image may also provide the same package globally.";
|
||||
@@ -418,8 +432,8 @@ function runStaticSmoke() {
|
||||
const files = readStaticFiles();
|
||||
const source = Object.values(files).join("\n");
|
||||
const artifactPublisher = readText("scripts/artifact-publish.mjs");
|
||||
const buildScript = readText("web/hwlab-cloud-web/scripts/build.mjs");
|
||||
const distContractScript = readText("web/hwlab-cloud-web/scripts/dist-contract.mjs");
|
||||
const buildScript = readText("web/hwlab-cloud-web/scripts/build.ts");
|
||||
const distContractScript = readText("web/hwlab-cloud-web/scripts/dist-contract.ts");
|
||||
|
||||
addCheck(checks, blockers, "static-web-assets", assetsExist(), "Cloud Web source assets are present.", {
|
||||
evidence: requiredWebAssets.map((file) => `web/hwlab-cloud-web/${file}`)
|
||||
@@ -427,7 +441,7 @@ function runStaticSmoke() {
|
||||
|
||||
addCheck(checks, blockers, "default-login-entry", hasDefaultLoginEntry(files, artifactPublisher), "Default 16666 route shows the Chinese login page before the Cloud Workbench is exposed.", {
|
||||
blocker: "runtime_blocker",
|
||||
evidence: ["云工作台登录", "账号或密码不正确,请重新输入。", "data-app-shell hidden", "auth.mjs", "HWLAB_CLOUD_WEB_AUTH_USERNAME", "HWLAB_CLOUD_WEB_AUTH_PASSWORD"]
|
||||
evidence: ["云工作台登录", "账号或密码不正确,请重新输入。", "data-app-shell hidden", "auth.ts", "HWLAB_CLOUD_WEB_AUTH_USERNAME", "HWLAB_CLOUD_WEB_AUTH_PASSWORD"]
|
||||
});
|
||||
|
||||
addCheck(checks, blockers, "default-workbench-shell", hasDefaultWorkbench(files), "Authenticated route is the VS Code-style Cloud Workbench.", {
|
||||
@@ -955,7 +969,7 @@ export async function runDevCloudWorkbenchLayoutSmoke(args = {}) {
|
||||
}
|
||||
|
||||
if (!useLiveUrl && args.build === true) {
|
||||
execFileSync("node", ["web/hwlab-cloud-web/scripts/build.mjs"], {
|
||||
execFileSync(resolveBunCommand(), ["run", "web/hwlab-cloud-web/scripts/build.ts"], {
|
||||
cwd: repoRoot,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
@@ -1631,18 +1645,18 @@ function readStaticFiles() {
|
||||
return {
|
||||
html: readText("web/hwlab-cloud-web/index.html"),
|
||||
styles: readText("web/hwlab-cloud-web/styles.css"),
|
||||
auth: readText("web/hwlab-cloud-web/auth.mjs"),
|
||||
app: readText("web/hwlab-cloud-web/app.mjs"),
|
||||
codeAgentFacts: readText("web/hwlab-cloud-web/code-agent-facts.mjs"),
|
||||
codeAgentStatus: readText("web/hwlab-cloud-web/code-agent-status.mjs"),
|
||||
messageMarkdown: readText("web/hwlab-cloud-web/message-markdown.mjs"),
|
||||
liveStatus: readText("web/hwlab-cloud-web/live-status.mjs"),
|
||||
runtime: readText("web/hwlab-cloud-web/runtime.mjs"),
|
||||
auth: readText("web/hwlab-cloud-web/auth.ts"),
|
||||
app: readCloudWebAppSource(),
|
||||
codeAgentFacts: readText("web/hwlab-cloud-web/code-agent-facts.ts"),
|
||||
codeAgentStatus: readText("web/hwlab-cloud-web/code-agent-status.ts"),
|
||||
messageMarkdown: readText("web/hwlab-cloud-web/message-markdown.ts"),
|
||||
liveStatus: readText("web/hwlab-cloud-web/live-status.ts"),
|
||||
runtime: readText("web/hwlab-cloud-web/runtime.ts"),
|
||||
help: readText("web/hwlab-cloud-web/help.md"),
|
||||
artifactPublisher: readText("scripts/artifact-publish.mjs"),
|
||||
cloudApiServer: readText("internal/cloud/server.ts"),
|
||||
devicePodData: readText("internal/device-pod/fake-data.mjs"),
|
||||
devicePodService: readText("cmd/hwlab-device-pod/main.mjs"),
|
||||
devicePodService: readText("cmd/hwlab-device-pod/main.ts"),
|
||||
protocol: readText("internal/protocol/index.mjs"),
|
||||
deployJson: readText("deploy/deploy.json"),
|
||||
artifactCatalog: readText("deploy/artifact-catalog.dev.json")
|
||||
@@ -1653,6 +1667,24 @@ function readText(relativePath) {
|
||||
return fs.readFileSync(path.join(repoRoot, relativePath), "utf8");
|
||||
}
|
||||
|
||||
function readCloudWebAppSource() {
|
||||
return cloudWebAppSourceFiles.map((file) => readText(`web/hwlab-cloud-web/${file}`)).join("\n");
|
||||
}
|
||||
|
||||
function readCloudWebRuntime() {
|
||||
const source = fs.readFileSync(path.join(webRoot, "runtime.ts"), "utf8");
|
||||
const match = source.match(/export const runtime\s*=\s*([\s\S]*?);\s*$/u);
|
||||
if (!match) throw new Error("Unable to parse web/hwlab-cloud-web/runtime.ts");
|
||||
return Function(`return (${match[1]});`)();
|
||||
}
|
||||
|
||||
function resolveBunCommand() {
|
||||
for (const candidate of [process.env.HWLAB_BUN_BINARY, process.env.BUN_BINARY, "/root/.bun/bin/bun", "bun"]) {
|
||||
if (typeof candidate === "string" && candidate.trim()) return candidate;
|
||||
}
|
||||
return "bun";
|
||||
}
|
||||
|
||||
function observeSourceIdentity() {
|
||||
const head = runGit(["rev-parse", "--verify", "HEAD"]);
|
||||
const shortHead = head ? runGit(["rev-parse", "--short=12", "HEAD"]) : null;
|
||||
@@ -1835,7 +1867,7 @@ function identityVariantMatches(accepted, observed) {
|
||||
async function inspectLiveWebAssetIdentity(baseUrl, fetchedIndexHtml) {
|
||||
const assets = [];
|
||||
for (const assetPath of liveIdentityWebAssets) {
|
||||
const sourceText = readText(`web/hwlab-cloud-web/${assetPath}`);
|
||||
const sourceText = assetPath === "app.js" ? await buildCloudWebAppBundleText() : readText(`web/hwlab-cloud-web/${assetPath}`);
|
||||
const live = assetPath === "index.html"
|
||||
? { ok: true, status: 200, contentType: "text/html; charset=utf-8", body: fetchedIndexHtml }
|
||||
: await fetchText(new URL(assetPath, baseUrl).toString());
|
||||
@@ -1874,6 +1906,24 @@ function compareLiveAsset(assetPath, live, sourceText) {
|
||||
};
|
||||
}
|
||||
|
||||
async function buildCloudWebAppBundleText() {
|
||||
const tmpDir = fs.mkdtempSync(path.join("/tmp", "hwlab-cloud-web-live-asset-"));
|
||||
const entryPath = path.join(webRoot, ".app-entry.generated.ts");
|
||||
try {
|
||||
fs.writeFileSync(entryPath, `${readCloudWebAppSource()}\n`);
|
||||
execFileSync(resolveBunCommand(), ["build", entryPath, "--target", "browser", "--format", "esm", "--outfile", path.join(tmpDir, "app.js")], {
|
||||
cwd: repoRoot,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
timeout: 30000
|
||||
});
|
||||
return fs.readFileSync(path.join(tmpDir, "app.js"), "utf8");
|
||||
} finally {
|
||||
fs.rmSync(entryPath, { force: true });
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
function hashText(value) {
|
||||
return createHash("sha256").update(value).digest("hex").slice(0, 16);
|
||||
}
|
||||
@@ -2197,10 +2247,10 @@ function hasScrollLockContract(styles) {
|
||||
return (
|
||||
/html,\s*\nbody\s*\{[^\}]*height:\s*100%;[^\}]*overflow:\s*hidden;/su.test(styles) &&
|
||||
/body\s*>\s*\[data-app-shell\]\s*\{[^\}]*min-height:\s*0;/su.test(styles) &&
|
||||
/\.workbench-shell\s*\{[^\}]*height:\s*100(?:d)?vh;[^\}]*overflow:\s*hidden;/su.test(styles) &&
|
||||
/\.workbench-shell\s*\{[^\}]*height:\s*100vh;[^\}]*height:\s*100dvh;[^\}]*overflow:\s*hidden;/su.test(styles) &&
|
||||
/\.view\s*\{[^\}]*min-height:\s*0;[^\}]*overflow:\s*auto;/su.test(styles) &&
|
||||
/\.conversation-list[^\{]*\{[^\}]*min-height:\s*0;[^\}]*overflow:\s*auto;/su.test(styles) &&
|
||||
/\.device-event-scroll\s*\{[^\}]*min-height:\s*0;[^\}]*overflow:\s*auto;/su.test(styles)
|
||||
/[^\{]*\.conversation-list[^\{]*\{[^\}]*min-height:\s*0;[^\}]*overflow:\s*auto;/su.test(styles) &&
|
||||
/\.device-event-scroll\s*\{[^\}]*min-height:\s*[^;]+;[^\}]*overflow:\s*auto;/su.test(styles)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2714,7 +2764,7 @@ function findMarkdownRenderers() {
|
||||
const json = JSON.parse(readText(file));
|
||||
return Object.keys({ ...(json.dependencies ?? {}), ...(json.devDependencies ?? {}) });
|
||||
});
|
||||
const source = `${readText("web/hwlab-cloud-web/app.mjs")}\n${readText("web/hwlab-cloud-web/index.html")}`;
|
||||
const source = `${readCloudWebAppSource()}\n${readText("web/hwlab-cloud-web/index.html")}`;
|
||||
const imported = markdownRendererPackages.filter((name) => new RegExp(`from\\s+["']${escapeRegExp(name)}["']|import\\(["']${escapeRegExp(name)}["']\\)`, "u").test(source));
|
||||
const vendoredMarked = /from\s+["']\.\/third_party\/marked\/marked\.esm\.js["']/u.test(source) &&
|
||||
fs.existsSync(path.join(repoRoot, "web/hwlab-cloud-web/third_party/marked/marked.esm.js")) &&
|
||||
|
||||
@@ -64,6 +64,16 @@ const serviceSpecificPaths = Object.freeze({
|
||||
"hwlab-agent-skills": ["skills/"]
|
||||
});
|
||||
|
||||
const bunCommandServices = new Set([
|
||||
"hwlab-agent-mgr",
|
||||
"hwlab-agent-worker",
|
||||
"hwlab-cloud-api",
|
||||
"hwlab-codex-api-responses-forwarder",
|
||||
"hwlab-deepseek-responses-bridge",
|
||||
"hwlab-device-pod",
|
||||
"hwlab-edge-proxy"
|
||||
]);
|
||||
|
||||
export async function createG14CiPlan(options = {}) {
|
||||
const repoRoot = options.repoRoot ?? process.cwd();
|
||||
const deployJsonPath = options.deployJsonPath ?? "deploy/deploy.json";
|
||||
@@ -243,7 +253,7 @@ export function pathMatches(pattern, filePath) {
|
||||
|
||||
export function isTestOnlyPath(filePath) {
|
||||
const normalized = normalizeRepoPath(filePath);
|
||||
return normalized.endsWith(".test.mjs") || normalized.includes("/__tests__/") || normalized.includes("/fixtures/");
|
||||
return /\.test\.(mjs|ts)$/u.test(normalized) || normalized.includes("/__tests__/") || normalized.includes("/fixtures/");
|
||||
}
|
||||
|
||||
export function isDocsOnlyPath(filePath) {
|
||||
@@ -353,7 +363,7 @@ function reuseCandidate(catalogRecord, affected) {
|
||||
}
|
||||
|
||||
function runtimeKindForService(serviceId) {
|
||||
if (serviceId === "hwlab-cloud-api") return "bun-command";
|
||||
if (bunCommandServices.has(serviceId)) return "bun-command";
|
||||
if (serviceId === "hwlab-cloud-web") return "cloud-web";
|
||||
if (serviceId === "hwlab-cli") return "cli";
|
||||
if (serviceId === "hwlab-agent-skills") return "skills-bundle";
|
||||
@@ -361,7 +371,7 @@ function runtimeKindForService(serviceId) {
|
||||
}
|
||||
|
||||
function entrypointForService(serviceId) {
|
||||
if (serviceId === "hwlab-cloud-api") return "cmd/hwlab-cloud-api/main.ts";
|
||||
if (bunCommandServices.has(serviceId)) return `cmd/${serviceId}/main.ts`;
|
||||
if (serviceId === "hwlab-cloud-web") return "web/hwlab-cloud-web/index.html";
|
||||
if (serviceId === "hwlab-cli") return "tools/hwlab-cli/bin/hwlab-cli.mjs";
|
||||
if (serviceId === "hwlab-agent-skills") return "skills/hwlab-agent-runtime/SKILL.md";
|
||||
|
||||
Reference in New Issue
Block a user