Files
memind/scripts/run-release-gate-page-scenarios.mjs
john d67ebb0c77
Memind CI / Test, build, and release guards (pull_request) Failing after 17s
fix(release-gate): isolate PAGE agent runs from split-service MCP routing.
Gate stacks inherited developer MCP env and routed sandbox-fs writes to the
8082 workspace root, so PAGE/DATA scenarios failed with ENOENT despite a
healthy goosed. Sanitize isolated portal env, grant gate skills, and align
CHAT search assertions with the tkmind_search tool name.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 10:43:28 +08:00

102 lines
2.9 KiB
JavaScript

#!/usr/bin/env node
import { spawn } from 'node:child_process';
import path from 'node:path';
import {
createLocalGateStack,
grantGateUserSkillsByUsername,
selectBackendLlmProvider,
seedSelectedProviderKeys,
} from '../release-gate/local-stack.mjs';
const root = path.resolve(new URL('..', import.meta.url).pathname);
const scenarioIds = [
'john2-suzhou-page',
'long-content-rich-page',
];
async function runScenario(scenarioId, port) {
const childTimeoutMs = Math.max(
60_000,
Number(process.env.RELEASE_GATE_SCENARIO_PROCESS_TIMEOUT_MS ?? 900_000) || 900_000,
);
const stepTimeoutMs = Math.max(
60_000,
Number(process.env.RELEASE_GATE_SCENARIO_TIMEOUT_MS ?? 900_000) || 900_000,
);
const code = await new Promise((resolve, reject) => {
const child = spawn(
process.execPath,
['scripts/run-scenario-test.mjs', '--scenario', scenarioId, '--port', String(port)],
{
cwd: root,
env: {
...process.env,
JOHN_PASSWORD: '888888',
RELEASE_GATE_SCENARIO_TIMEOUT_MS: String(stepTimeoutMs),
},
stdio: 'inherit',
},
);
const timeout = setTimeout(() => {
child.kill('SIGTERM');
setTimeout(() => child.kill('SIGKILL'), 3_000).unref();
}, childTimeoutMs);
timeout.unref();
child.once('error', reject);
child.once('close', (status) => {
clearTimeout(timeout);
resolve(status ?? 1);
});
});
return code;
}
const runId = `page-${new Date().toISOString().replace(/[-:.TZ]/g, '').slice(0, 14)}`;
const stack = await createLocalGateStack({
root,
runId,
port: Number(process.env.MEMIND_RELEASE_GATE_PAGE_PORT ?? 19087),
portalRoot: path.join(root, '.runtime', 'portal'),
nodeEnv: 'test',
runtimeProfile: 'local',
});
await seedSelectedProviderKeys({ sourceUrl: stack.sourceDatabaseUrl, targetUrl: stack.mysqlUrl });
await selectBackendLlmProvider({ targetUrl: stack.mysqlUrl });
async function register(username) {
const response = await fetch(`${stack.baseUrl}/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username,
password: '888888',
displayName: `Release Gate ${username}`,
email: `${username}@example.invalid`,
}),
});
if (!response.ok) throw new Error(`register ${username} failed: ${response.status}`);
}
try {
await register('john2');
await grantGateUserSkillsByUsername({
targetUrl: stack.mysqlUrl,
username: 'john2',
skillNames: ['static-page-publish'],
});
const results = [];
for (const scenarioId of scenarioIds) {
results.push({
scenarioId,
code: await runScenario(scenarioId, new URL(stack.baseUrl).port),
});
}
for (const result of results) {
console.log(`${result.code === 0 ? 'PASS' : 'FAIL'} ${result.scenarioId}`);
}
if (results.some((result) => result.code !== 0)) process.exitCode = 1;
} finally {
await stack.cleanup();
}