fix(dev): stop stale frontend when portal exits
This commit is contained in:
@@ -22,6 +22,19 @@ pnpm dev
|
||||
pnpm open:local-test # 浏览器打开 H5
|
||||
```
|
||||
|
||||
### H5 多个接口同时返回 500
|
||||
|
||||
如果 `5173` 页面里的 `/auth/status`、`/auth/wechat/config`、`/api/analytics/context`
|
||||
同时返回 `text/plain` 500,先检查 Portal:
|
||||
|
||||
```bash
|
||||
curl -i http://127.0.0.1:8081/auth/status
|
||||
```
|
||||
|
||||
`8081` 无法连接表示 Vite 还在运行、但它代理的 Portal 已退出,并非这些路由同时故障。
|
||||
前台 `pnpm dev` 会在任一受管子进程意外退出时结束整个开发栈;重新运行 `pnpm dev`
|
||||
即可干净启动。LaunchAgent 模式则查看 `~/Library/Logs/memind-dev.log` 并按下节命令重启。
|
||||
|
||||
### LaunchAgent 常驻(推荐本机联调)
|
||||
|
||||
终端里跑 `pnpm dev` 会在 Cursor / 终端关闭时被 SIGTERM 停掉。若希望登录后自动拉起、掉线自恢复,可安装 LaunchAgent(与 imgproxy / goosed 同类):
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@
|
||||
"smoke:orchestrator-shadow": "node scripts/smoke-orchestrator-shadow.mjs",
|
||||
"mock:memory-v2-services": "node scripts/mock-memory-v2-services.mjs",
|
||||
"test:memind": "node scripts/run-memind-tests.mjs",
|
||||
"pretest": "node scripts/verify-portal-access-policy.mjs && node --test episodic-memory.test.mjs tkmind-proxy.test.mjs server/portal-access-policy.test.mjs server/portal-account-feedback-routes.test.mjs server/portal-agent-job-routes.test.mjs server/portal-agent-runtime-routes.test.mjs server/portal-agent-services-bootstrap.test.mjs server/portal-auth-services-bootstrap.test.mjs server/portal-auth-session-helpers.test.mjs server/portal-billing-routes.test.mjs server/portal-core-auth-routes.test.mjs server/portal-domain-services-bootstrap.test.mjs server/portal-gateway-services-bootstrap.test.mjs server/portal-integration-services-bootstrap.test.mjs server/portal-memory-session-services-bootstrap.test.mjs server/portal-notification-routes.test.mjs server/portal-mindspace-agent-operation-routes.test.mjs server/portal-mindspace-asset-delivery-routes.test.mjs server/portal-mindspace-asset-routes.test.mjs server/portal-mindspace-chat-save-routes.test.mjs server/portal-mindspace-chat-share-routes.test.mjs server/portal-mindspace-page-core-routes.test.mjs server/portal-mindspace-page-publish-routes.test.mjs server/portal-mindspace-space-routes.test.mjs server/portal-publication-routes.test.mjs server/portal-publication-shell.test.mjs server/portal-published-page-delivery.test.mjs server/portal-session-coordinator.test.mjs server/portal-session-routes.test.mjs server/portal-plaza-routes.test.mjs server/portal-static-delivery-routes.test.mjs server/portal-user-memory-routes.test.mjs server/portal-wechat-auth-routes.test.mjs server/portal-webhook-routes.test.mjs server/portal-wiki-routes.test.mjs server/portal-workspace-publication-delivery.test.mjs",
|
||||
"pretest": "node scripts/verify-portal-access-policy.mjs && node --test scripts/dev-process-lifecycle.test.mjs episodic-memory.test.mjs tkmind-proxy.test.mjs server/portal-access-policy.test.mjs server/portal-account-feedback-routes.test.mjs server/portal-agent-job-routes.test.mjs server/portal-agent-runtime-routes.test.mjs server/portal-agent-services-bootstrap.test.mjs server/portal-auth-services-bootstrap.test.mjs server/portal-auth-session-helpers.test.mjs server/portal-billing-routes.test.mjs server/portal-core-auth-routes.test.mjs server/portal-domain-services-bootstrap.test.mjs server/portal-gateway-services-bootstrap.test.mjs server/portal-integration-services-bootstrap.test.mjs server/portal-memory-session-services-bootstrap.test.mjs server/portal-notification-routes.test.mjs server/portal-mindspace-agent-operation-routes.test.mjs server/portal-mindspace-asset-delivery-routes.test.mjs server/portal-mindspace-asset-routes.test.mjs server/portal-mindspace-chat-save-routes.test.mjs server/portal-mindspace-chat-share-routes.test.mjs server/portal-mindspace-page-core-routes.test.mjs server/portal-mindspace-page-publish-routes.test.mjs server/portal-mindspace-space-routes.test.mjs server/portal-publication-routes.test.mjs server/portal-publication-shell.test.mjs server/portal-published-page-delivery.test.mjs server/portal-session-coordinator.test.mjs server/portal-session-routes.test.mjs server/portal-plaza-routes.test.mjs server/portal-static-delivery-routes.test.mjs server/portal-user-memory-routes.test.mjs server/portal-wechat-auth-routes.test.mjs server/portal-webhook-routes.test.mjs server/portal-wiki-routes.test.mjs server/portal-workspace-publication-delivery.test.mjs",
|
||||
"test:scenario": "node scripts/run-scenario-test.mjs",
|
||||
"verify:children-hobby-diet-survey": "node scripts/verify-children-hobby-diet-survey.mjs",
|
||||
"dev:page-data-aider-loop": "node scripts/page-data-aider-dev-loop.mjs",
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
deepseekDisableThinkingEnabled,
|
||||
resolveDeepseekNoThinkListenPort,
|
||||
} from '../deepseek-no-think-proxy.mjs';
|
||||
import { handleUnexpectedChildExit } from './dev-process-lifecycle.mjs';
|
||||
|
||||
const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const opsDir = path.join(root, 'ops');
|
||||
@@ -52,11 +53,13 @@ function spawnChild(command, args, label, cwd = root, extraEnv = {}) {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
child.on('exit', (code, signal) => {
|
||||
if (signal) return;
|
||||
if (code && code !== 0) {
|
||||
console.error(`[${label}] exited with code ${code}`);
|
||||
shutdown(code ?? 1);
|
||||
}
|
||||
handleUnexpectedChildExit({
|
||||
label,
|
||||
code,
|
||||
signal,
|
||||
stopping,
|
||||
shutdown,
|
||||
});
|
||||
});
|
||||
return child;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
export function handleUnexpectedChildExit({
|
||||
label,
|
||||
code,
|
||||
signal,
|
||||
stopping,
|
||||
shutdown,
|
||||
log = console.error,
|
||||
}) {
|
||||
if (stopping) return false;
|
||||
|
||||
const detail = signal ? `signal ${signal}` : `code ${code ?? 'unknown'}`;
|
||||
const shutdownCode = Number.isInteger(code) && code > 0 ? code : 1;
|
||||
log(`[${label}] exited unexpectedly (${detail}); shutting down dev stack`);
|
||||
shutdown(shutdownCode);
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { handleUnexpectedChildExit } from './dev-process-lifecycle.mjs';
|
||||
|
||||
function invokeExit({ code, signal, stopping = false }) {
|
||||
const shutdownCodes = [];
|
||||
const messages = [];
|
||||
const handled = handleUnexpectedChildExit({
|
||||
label: 'portal',
|
||||
code,
|
||||
signal,
|
||||
stopping,
|
||||
shutdown: (shutdownCode) => shutdownCodes.push(shutdownCode),
|
||||
log: (message) => messages.push(message),
|
||||
});
|
||||
return { handled, shutdownCodes, messages };
|
||||
}
|
||||
|
||||
test('clean child exit shuts down the remaining dev stack as a failure', () => {
|
||||
const result = invokeExit({ code: 0, signal: null });
|
||||
|
||||
assert.equal(result.handled, true);
|
||||
assert.deepEqual(result.shutdownCodes, [1]);
|
||||
assert.match(result.messages[0], /code 0/);
|
||||
});
|
||||
|
||||
test('signal exit shuts down the remaining dev stack as a failure', () => {
|
||||
const result = invokeExit({ code: null, signal: 'SIGKILL' });
|
||||
|
||||
assert.equal(result.handled, true);
|
||||
assert.deepEqual(result.shutdownCodes, [1]);
|
||||
assert.match(result.messages[0], /signal SIGKILL/);
|
||||
});
|
||||
|
||||
test('non-zero child exit preserves its failure code', () => {
|
||||
const result = invokeExit({ code: 7, signal: null });
|
||||
|
||||
assert.deepEqual(result.shutdownCodes, [7]);
|
||||
});
|
||||
|
||||
test('child exits during an intentional shutdown are ignored', () => {
|
||||
const result = invokeExit({ code: 0, signal: null, stopping: true });
|
||||
|
||||
assert.equal(result.handled, false);
|
||||
assert.deepEqual(result.shutdownCodes, []);
|
||||
assert.deepEqual(result.messages, []);
|
||||
});
|
||||
Reference in New Issue
Block a user