142 lines
4.2 KiB
JavaScript
Executable File
142 lines
4.2 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
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)
|
|
})
|