import assert from "node:assert/strict"; import { spawn } from "node:child_process"; import { access, readFile } from "node:fs/promises"; import { createServer } from "node:net"; import { fileURLToPath } from "node:url"; import test, { after, before } from "node:test"; let serverProcess; let serverOrigin; async function reservePort() { const server = createServer(); await new Promise((resolve, reject) => { server.once("error", reject); server.listen(0, "127.0.0.1", resolve); }); const address = server.address(); assert(address && typeof address === "object"); await new Promise((resolve, reject) => { server.close((error) => (error ? reject(error) : resolve())); }); return address.port; } before(async () => { const port = await reservePort(); serverOrigin = `http://127.0.0.1:${port}`; serverProcess = spawn( process.execPath, [ fileURLToPath(new URL("../node_modules/srvx/bin/srvx.mjs", import.meta.url)), "serve", "--prod", "--entry", "dist/server/server.js", "--static", "../client", ], { env: { ...process.env, HOST: "127.0.0.1", PORT: String(port), }, stdio: ["ignore", "pipe", "pipe"], }, ); for (let attempt = 0; attempt < 50; attempt += 1) { if (serverProcess.exitCode !== null) { throw new Error(`Node server exited with code ${serverProcess.exitCode}`); } try { const response = await fetch(serverOrigin); if (response.ok) return; } catch { // The server is still starting. } await new Promise((resolve) => setTimeout(resolve, 100)); } throw new Error("Node server did not become ready"); }); after(() => { serverProcess?.kill("SIGTERM"); }); test("server-renders the Wasm management console", async () => { const response = await fetch(serverOrigin, { headers: { accept: "text/html" }, }); assert.equal(response.status, 200); assert.match(response.headers.get("content-type") ?? "", /^text\/html\b/i); const html = await response.text(); assert.match(html, /Wasmeld Console<\/title>/i); assert.match(html, /运行概览/); assert.match(html, /WIT 包/); assert.match(html, /连接中/); assert.match(html, /0 个对外服务/); assert.match(html, /Component Model/); assert.doesNotMatch(html, /本地演示|codex-preview|vinext|next\.js/i); }); test("uses TanStack Start routing and produces Node artifacts", async () => { const [rootRoute, indexRoute, apiClient, router, packageJson, viteConfig] = await Promise.all([ readFile(new URL("../src/routes/__root.tsx", import.meta.url), "utf8"), readFile(new URL("../src/routes/index.tsx", import.meta.url), "utf8"), readFile(new URL("../src/api.ts", import.meta.url), "utf8"), readFile(new URL("../src/router.tsx", import.meta.url), "utf8"), readFile(new URL("../package.json", import.meta.url), "utf8"), readFile(new URL("../vite.config.ts", import.meta.url), "utf8"), ]); assert.match(rootRoute, /createRootRoute/); assert.match(rootRoute, /<HeadContent \/>/); assert.match(rootRoute, /<Scripts \/>/); assert.match(indexRoute, /createFileRoute\("\/"\)/); assert.match(indexRoute, /切换对外版本/); assert.match(indexRoute, /Host 能力/); assert.match(indexRoute, /service\.capabilities/); assert.match(apiClient, /\/api\/v1\/deployments/); assert.match(apiClient, /capabilities: BackendCapability\[\]/); assert.match(router, /createRouter/); assert.match(packageJson, /"@tanstack\/react-start"/); assert.match(packageJson, /"srvx"/); assert.doesNotMatch(packageJson, /"next"|"vinext"|"drizzle-orm"/); assert.match(viteConfig, /tanstackStart\(\{/); await access(new URL("../dist/server/server.js", import.meta.url)); await access(new URL("../dist/client/og.png", import.meta.url)); });