Spaces:
Sleeping
Sleeping
| import { spawn } from 'node:child_process' | |
| import fs from 'node:fs' | |
| import net from 'node:net' | |
| import path from 'node:path' | |
| import process from 'node:process' | |
| async function findAvailablePort(host = '127.0.0.1') { | |
| return await new Promise((resolve, reject) => { | |
| const server = net.createServer() | |
| server.unref() | |
| server.on('error', reject) | |
| server.listen(0, host, () => { | |
| const address = server.address() | |
| if (!address || typeof address === 'string') { | |
| server.close(() => reject(new Error('failed to resolve dynamic port'))) | |
| return | |
| } | |
| const { port } = address | |
| server.close((err) => { | |
| if (err) reject(err) | |
| else resolve(port) | |
| }) | |
| }) | |
| }) | |
| } | |
| const modeArg = process.argv.find((arg) => arg.startsWith('--mode=')) | |
| const mode = modeArg ? modeArg.split('=')[1] : 'local' | |
| if (mode !== 'local' && mode !== 'gateway') { | |
| process.stderr.write(`Invalid mode: ${mode}\n`) | |
| process.exit(1) | |
| } | |
| const repoRoot = process.cwd() | |
| const fixtureSource = path.join(repoRoot, 'tests', 'fixtures', 'openclaw') | |
| const runtimeRoot = path.join(repoRoot, '.tmp', 'e2e-openclaw', mode) | |
| const dataDir = path.join(runtimeRoot, 'data') | |
| const mockBinDir = path.join(repoRoot, 'scripts', 'e2e-openclaw', 'bin') | |
| const skillsRoot = path.join(runtimeRoot, 'skills') | |
| fs.rmSync(runtimeRoot, { recursive: true, force: true }) | |
| fs.mkdirSync(runtimeRoot, { recursive: true }) | |
| fs.mkdirSync(dataDir, { recursive: true }) | |
| fs.cpSync(fixtureSource, runtimeRoot, { recursive: true }) | |
| const gatewayHost = '127.0.0.1' | |
| const gatewayPort = String(await findAvailablePort(gatewayHost)) | |
| const baseEnv = { | |
| ...process.env, | |
| API_KEY: process.env.API_KEY || 'test-api-key-e2e-12345', | |
| AUTH_USER: process.env.AUTH_USER || 'admin', | |
| AUTH_PASS: process.env.AUTH_PASS || 'admin', | |
| MISSION_CONTROL_TEST_MODE: process.env.MISSION_CONTROL_TEST_MODE || '1', | |
| MC_DISABLE_RATE_LIMIT: '1', | |
| MISSION_CONTROL_DATA_DIR: dataDir, | |
| MISSION_CONTROL_DB_PATH: path.join(dataDir, 'mission-control.db'), | |
| OPENCLAW_STATE_DIR: runtimeRoot, | |
| OPENCLAW_CONFIG_PATH: path.join(runtimeRoot, 'openclaw.json'), | |
| OPENCLAW_GATEWAY_HOST: gatewayHost, | |
| OPENCLAW_GATEWAY_PORT: gatewayPort, | |
| OPENCLAW_BIN: path.join(mockBinDir, 'openclaw'), | |
| CLAWDBOT_BIN: path.join(mockBinDir, 'clawdbot'), | |
| MC_SKILLS_USER_AGENTS_DIR: path.join(skillsRoot, 'user-agents'), | |
| MC_SKILLS_USER_CODEX_DIR: path.join(skillsRoot, 'user-codex'), | |
| MC_SKILLS_PROJECT_AGENTS_DIR: path.join(skillsRoot, 'project-agents'), | |
| MC_SKILLS_PROJECT_CODEX_DIR: path.join(skillsRoot, 'project-codex'), | |
| MC_SKILLS_OPENCLAW_DIR: path.join(skillsRoot, 'openclaw'), | |
| PATH: `${mockBinDir}:${process.env.PATH || ''}`, | |
| E2E_GATEWAY_EXPECTED: mode === 'gateway' ? '1' : '0', | |
| } | |
| const children = [] | |
| let app = null | |
| if (mode === 'gateway') { | |
| const gw = spawn('node', ['scripts/e2e-openclaw/mock-gateway.mjs'], { | |
| cwd: repoRoot, | |
| env: baseEnv, | |
| stdio: 'inherit', | |
| }) | |
| gw.on('error', (err) => { | |
| process.stderr.write(`[openclaw-e2e] mock gateway failed to start: ${String(err)}\n`) | |
| shutdown('SIGTERM') | |
| process.exit(1) | |
| }) | |
| gw.on('exit', (code, signal) => { | |
| const exitCode = code ?? (signal ? 1 : 0) | |
| if (exitCode !== 0) { | |
| process.stderr.write(`[openclaw-e2e] mock gateway exited unexpectedly (code=${exitCode}, signal=${signal ?? 'none'})\n`) | |
| shutdown('SIGTERM') | |
| process.exit(exitCode) | |
| } | |
| }) | |
| children.push(gw) | |
| } | |
| const standaloneServerPath = path.join(repoRoot, '.next', 'standalone', 'server.js') | |
| app = fs.existsSync(standaloneServerPath) | |
| ? spawn('node', [standaloneServerPath], { | |
| cwd: repoRoot, | |
| env: { | |
| ...baseEnv, | |
| HOSTNAME: '127.0.0.1', | |
| PORT: '3005', | |
| }, | |
| stdio: 'inherit', | |
| }) | |
| : spawn('pnpm', ['start'], { | |
| cwd: repoRoot, | |
| env: baseEnv, | |
| stdio: 'inherit', | |
| }) | |
| children.push(app) | |
| function shutdown(signal = 'SIGTERM') { | |
| for (const child of children) { | |
| if (!child.killed) { | |
| try { | |
| child.kill(signal) | |
| } catch { | |
| // noop | |
| } | |
| } | |
| } | |
| } | |
| process.on('SIGINT', () => { | |
| shutdown('SIGINT') | |
| process.exit(130) | |
| }) | |
| process.on('SIGTERM', () => { | |
| shutdown('SIGTERM') | |
| process.exit(143) | |
| }) | |
| app.on('exit', (code) => { | |
| shutdown('SIGTERM') | |
| process.exit(code ?? 0) | |
| }) | |