From b16dd70deb8f9a36a9be6ac9642ea88508354fbc Mon Sep 17 00:00:00 2001 From: suzmii Date: Thu, 27 Aug 2026 13:17:48 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E9=BD=90=20Windows=20SpacetimeDB=20sm?= =?UTF-8?q?oke=20=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复管理员 procedure smoke 在 Windows 下只终止 wrapper 父进程的问题。 让编辑器幂等性 smoke 递归终止 spacetime 子进程树并正常退出。 保留非 Windows 的 SIGTERM/SIGKILL 清理路径。 补跑真实运行态 smoke、单测、ESLint、编码和 diff 检查。 --- scripts/check-admin-account-procedures.mjs | 35 +++++++++++++++++++ .../spacetime-editor-idempotency-smoke.mjs | 35 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/scripts/check-admin-account-procedures.mjs b/scripts/check-admin-account-procedures.mjs index 1fff5253b..4880f09f6 100644 --- a/scripts/check-admin-account-procedures.mjs +++ b/scripts/check-admin-account-procedures.mjs @@ -144,6 +144,10 @@ async function stopStandalone(child) { if (child.exitCode !== null) { return; } + if (process.platform === 'win32') { + await stopWindowsProcessTree(child); + return; + } child.kill('SIGTERM'); const exited = await Promise.race([ once(child, 'exit').then(() => true), @@ -155,6 +159,37 @@ async function stopStandalone(child) { } } +async function stopWindowsProcessTree(child) { + if (typeof child.pid === 'number') { + await runTaskKill(child.pid); + } + const exited = await Promise.race([ + once(child, 'exit').then(() => true), + delay(5_000).then(() => false), + ]); + if (!exited && child.exitCode === null) { + child.kill('SIGKILL'); + await once(child, 'exit'); + } +} + +function runTaskKill(pid) { + return new Promise((resolve, reject) => { + const taskKill = spawn('taskkill', ['/PID', String(pid), '/T', '/F'], { + stdio: 'ignore', + shell: false, + }); + taskKill.once('error', reject); + taskKill.once('exit', (code, signal) => { + if (code === 0 || code === 128 || code === 1) { + resolve(); + return; + } + reject(new Error(`taskkill exited with ${signal ?? code}`)); + }); + }); +} + async function callProcedure(serverUrl, token, procedureName, input) { const response = await fetch( `${serverUrl}/v1/database/${database}/call/${procedureName}`, diff --git a/scripts/spacetime-editor-idempotency-smoke.mjs b/scripts/spacetime-editor-idempotency-smoke.mjs index 4003b92ed..3b79c0836 100644 --- a/scripts/spacetime-editor-idempotency-smoke.mjs +++ b/scripts/spacetime-editor-idempotency-smoke.mjs @@ -228,6 +228,10 @@ export async function stopStandalone(child) { if (child.exitCode !== null) { return; } + if (process.platform === 'win32') { + await stopWindowsProcessTree(child); + return; + } child.kill('SIGTERM'); const exited = await Promise.race([ once(child, 'exit').then(() => true), @@ -239,6 +243,37 @@ export async function stopStandalone(child) { } } +async function stopWindowsProcessTree(child) { + if (typeof child.pid === 'number') { + await runTaskKill(child.pid); + } + const exited = await Promise.race([ + once(child, 'exit').then(() => true), + delay(5_000).then(() => false), + ]); + if (!exited && child.exitCode === null) { + child.kill('SIGKILL'); + await once(child, 'exit'); + } +} + +function runTaskKill(pid) { + return new Promise((resolve, reject) => { + const taskKill = spawn('taskkill', ['/PID', String(pid), '/T', '/F'], { + stdio: 'ignore', + shell: false, + }); + taskKill.once('error', reject); + taskKill.once('exit', (code, signal) => { + if (code === 0 || code === 128 || code === 1) { + resolve(); + return; + } + reject(new Error(`taskkill exited with ${signal ?? code}`)); + }); + }); +} + function cliPrefix(configPath) { return ['--config-path', configPath]; }