Files
memind/scripts/run-release-gate-runtime-container.mjs
john b1577a16e9
Memind CI / Test, build, and release guards (push) Failing after 12m3s
fix: harden release gate and page delivery
2026-07-26 14:32:01 +08:00

117 lines
3.5 KiB
JavaScript

#!/usr/bin/env node
import { spawn } from 'node:child_process';
import path from 'node:path';
import { assertPortalRuntimePath, inspectPortalRuntime } from '../release-gate/artifact.mjs';
const root = path.resolve(new URL('..', import.meta.url).pathname);
const runtime = assertPortalRuntimePath(path.join(root, '.runtime', 'portal'), { repoRoot: root });
const image = process.env.RELEASE_GATE_NODE_IMAGE || 'node:24-bookworm';
async function runDocker(commandArgs, { input = '' } = {}) {
return new Promise((resolve, reject) => {
const child = spawn('docker', commandArgs, {
cwd: root,
env: process.env,
stdio: ['pipe', 'pipe', 'pipe'],
});
let stdout = '';
let stderr = '';
child.stdout.setEncoding('utf8');
child.stderr.setEncoding('utf8');
child.stdout.on('data', (chunk) => { stdout += chunk; });
child.stderr.on('data', (chunk) => { stderr += chunk; });
child.once('error', reject);
child.once('close', (code) => resolve({ code: code ?? 1, stdout, stderr }));
child.stdin.end(input);
});
}
function baseDockerArgs() {
return [
'run',
'--rm',
'-i',
'--network',
'none',
'--mount',
`type=bind,src=${runtime},dst=/opt/portal,readonly`,
'--tmpfs',
'/workspace:rw,noexec,nosuid,size=64m',
image,
];
}
async function requireSuccess(label, args, options) {
const result = await runDocker([...baseDockerArgs(), ...args], options);
if (result.code !== 0) {
throw new Error(`${label} failed (${result.code}): ${result.stderr.slice(-2_000)}`);
}
console.log(`PASS REL-11 ${label}`);
return result.stdout;
}
const inspection = await inspectPortalRuntime(runtime);
if (inspection.missing.length || inspection.forbidden.length) {
throw new Error(
`runtime inspection failed: missing=${inspection.missing.join(',')} forbidden=${inspection.forbidden.join(',')}`,
);
}
await requireSuccess('server bundle parses in Linux ARM64 Node', [
'node',
'--check',
'/opt/portal/server.mjs',
]);
await requireSuccess('WeChat bundle parses in Linux ARM64 Node', [
'node',
'--check',
'/opt/portal/wechat-mp.bundle.mjs',
]);
await requireSuccess('Agent Run worker loads its runtime dependencies', [
'node',
'/opt/portal/scripts/agent-run-worker.mjs',
'--help',
]);
const initialize = `${JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: {},
})}\n`;
const sandboxOutput = await requireSuccess(
'sandbox MCP starts and answers initialize',
['node', '/opt/portal/mindspace-sandbox-mcp.mjs', '/workspace'],
{ input: initialize },
);
if (!sandboxOutput.includes('"name":"mindspace-sandbox"')) {
throw new Error('sandbox MCP initialize response is missing server identity');
}
const searchOutput = await requireSuccess(
'MindSearch MCP starts and answers initialize',
['node', '/opt/portal/tkmind-search-mcp.mjs'],
{ input: initialize },
);
if (!searchOutput.includes('"name":"tkmind-search"')) {
throw new Error('MindSearch MCP initialize response is missing server identity');
}
const excelResult = await runDocker(
[
...baseDockerArgs(),
'env',
'EXCEL_ANALYST_ENABLED=1',
'MINDSPACE_WORKSPACE_ROOT=/workspace',
'node',
'/opt/portal/tkmind-excel-mcp.mjs',
'/workspace',
],
{ input: initialize },
);
if (excelResult.code !== 0 || !excelResult.stdout.includes('"name":"tkmind-excel"')) {
throw new Error(`Excel MCP failed to start: ${excelResult.stderr.slice(-2_000)}`);
}
console.log('PASS REL-11 Excel MCP starts and answers initialize');