From fd0003a804ed1887e457ab8918313e72f73efa49 Mon Sep 17 00:00:00 2001 From: suyx Date: Wed, 5 Aug 2026 12:46:59 +0800 Subject: [PATCH] =?UTF-8?q?fix(POSTV1-02):=20=E8=A1=A5=E9=BD=90=E4=BE=BF?= =?UTF-8?q?=E6=90=BA=E5=8C=85Worker=E8=BF=90=E8=A1=8C=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/lib/portable-package.mjs | 2 +- tests/package/postv1-runtime.test.mjs | 67 +++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/scripts/lib/portable-package.mjs b/scripts/lib/portable-package.mjs index 20e8aaa..86a12f5 100644 --- a/scripts/lib/portable-package.mjs +++ b/scripts/lib/portable-package.mjs @@ -336,7 +336,7 @@ export async function buildAndValidatePortablePackage({ evidenceDirectory, outpu debug("copy API application"); const apiDependencies = copyApplication(join(repositoryRoot, "apps", "api"), join(serverRoot, "api"), ["@fastify/multipart", "@fastify/swagger", "@sinclair/typebox", "better-sqlite3", "fastify", "sharp"]); debug("copy Worker application"); - const workerDependencies = copyApplication(join(repositoryRoot, "apps", "worker"), join(serverRoot, "worker"), ["better-sqlite3"]); + const workerDependencies = copyApplication(join(repositoryRoot, "apps", "worker"), join(serverRoot, "worker"), ["better-sqlite3", "sharp"]); const sharedDestination = join(serverRoot, "api", "node_modules", "@dada", "shared-contracts"); mkdirSync(sharedDestination, { recursive: true }); copyTree(join(repositoryRoot, "packages", "shared-contracts", "dist"), join(sharedDestination, "dist")); diff --git a/tests/package/postv1-runtime.test.mjs b/tests/package/postv1-runtime.test.mjs index d5917a2..99892c2 100644 --- a/tests/package/postv1-runtime.test.mjs +++ b/tests/package/postv1-runtime.test.mjs @@ -5,6 +5,7 @@ import { existsSync } from "node:fs"; import { join, resolve } from "node:path"; import { tmpdir } from "node:os"; import { spawn } from "node:child_process"; +import { createServer } from "node:net"; const packageRoot = resolve(process.env.DADA_POSTV1_PACKAGE_ROOT ?? ".build/portable-release/Dada-P0A-0.0.0-win-x64"); const port = 43121; @@ -43,6 +44,71 @@ async function stop(child) { } } +async function verifyWorkerStartup(configPath) { + const pipeName = `Dada.P0A.PostV1.${process.pid}.${Date.now()}`; + const pipePath = `\\\\.\\pipe\\${pipeName}`; + const server = createServer(); + await new Promise((resolveListen, rejectListen) => { + server.once("error", rejectListen); + server.listen(pipePath, resolveListen); + }); + const child = spawn(join(packageRoot, "runtime", "node.exe"), [ + join(packageRoot, "server", "worker.mjs"), + "--dada-control-pipe", pipeName, + "--dada-credential-stdin", + ], { + cwd: packageRoot, + env: { + ...process.env, + DADA_INSTANCE_CONFIG_PATH: configPath, + DADA_SQLITE_NATIVE_BINDING: join(packageRoot, "server", "native", "better_sqlite3.node"), + }, + stdio: ["pipe", "ignore", "ignore"], + windowsHide: true, + }); + child.stdin.end(JSON.stringify({ "Dada/P0A/worker/ai-gateway": "synthetic-runtime-token" })); + try { + await new Promise((resolveReady, rejectReady) => { + let settled = false; + const finish = (error) => { + if (settled) return; + settled = true; + clearTimeout(deadline); + child.off("exit", onExit); + if (error) rejectReady(error); else resolveReady(); + }; + const deadline = setTimeout(() => finish(new Error("packaged worker ready timeout")), 15_000); + const onExit = (code) => finish(new Error(`packaged worker exited before ready: ${code}`)); + child.once("exit", onExit); + server.once("connection", (connection) => { + connection.setEncoding("utf8"); + let pending = ""; + connection.on("data", (chunk) => { + pending += chunk; + while (pending.includes("\n")) { + const newline = pending.indexOf("\n"); + const status = pending.slice(0, newline).trim(); + pending = pending.slice(newline + 1); + if (status === "storage_unavailable") finish(new Error("packaged worker reported storage_unavailable")); + if (status === "ready") { + setTimeout(() => { + if (settled) return; + connection.write("shutdown\n"); + finish(); + }, 500); + } + } + }); + }); + }); + const exitCode = await new Promise((resolveExit) => child.once("exit", resolveExit)); + assert.equal(exitCode, 0); + } finally { + if (child.exitCode === null) child.kill(); + await new Promise((resolveClose) => server.close(resolveClose)); + } +} + test("portable package serves the product and keeps SQLite data across API restart", async () => { assert.ok(existsSync(join(packageRoot, "Dada.exe"))); assert.ok(existsSync(join(packageRoot, "web", "index.html"))); @@ -72,6 +138,7 @@ test("portable package serves the product and keeps SQLite data across API resta assert.match(await page.text(), /
<\/div>/); } assert.ok(existsSync(join(dataRoot, "db", "dada.sqlite3"))); + await verifyWorkerStartup(configPath); } finally { await stop(api); }