import {spawn} from 'node:child_process'; import {copyFileSync, existsSync} from 'node:fs'; import path from 'node:path'; const [, , rawCommand = 'help', ...args] = process.argv; const command = rawCommand.trim(); const printComposeConfig = args.includes('--print'); const passthroughArgs = args.filter((arg) => arg !== '--print'); const projectRoot = process.cwd(); const composeFile = path.join('deploy', 'container', 'docker-compose.loadtest.yml'); const envExamplePath = path.join('deploy', 'container', 'api-server.env.example'); const envPath = path.join('deploy', 'container', 'api-server.env'); const supportedCommands = new Set(['init', 'build', 'up', 'down', 'logs', 'ps', 'config', 'k6']); if (command === 'help' || !supportedCommands.has(command)) { printHelp(command !== 'help'); process.exit(command === 'help' ? 0 : 1); } if (command === 'init') { ensureEnvFile(); process.exit(0); } if (!existsSync(envPath)) { ensureEnvFile(); console.error('[container] 请先检查 deploy/container/api-server.env 中的 SpacetimeDB 地址、库名和 token。'); process.exit(1); } const composeArgs = buildComposeArgs(command, passthroughArgs); const child = spawn('docker', composeArgs, { cwd: projectRoot, env: process.env, stdio: 'inherit', shell: false, }); child.on('error', (error) => { console.error(`[container] docker compose 启动失败: ${error.message}`); console.error('[container] 请确认 Docker Desktop 或 Docker Engine 已安装,并且 docker 在 PATH 中。'); process.exit(1); }); child.on('exit', (code, signal) => { if (signal) { console.error(`[container] docker compose 被信号终止: ${signal}`); process.exit(1); } process.exit(code ?? 0); }); function buildComposeArgs(selectedCommand, extraArgs) { const baseArgs = ['compose', '-f', composeFile]; switch (selectedCommand) { case 'build': return [...baseArgs, 'build', ...extraArgs]; case 'up': return [...baseArgs, 'up', '-d', ...extraArgs]; case 'down': return [...baseArgs, 'down', ...extraArgs]; case 'logs': return [...baseArgs, 'logs', ...extraArgs]; case 'ps': return [...baseArgs, 'ps', ...extraArgs]; case 'config': return [...baseArgs, 'config', ...(printComposeConfig ? [] : ['--quiet']), ...extraArgs]; case 'k6': return [...baseArgs, '--profile', 'loadtest', 'run', '--rm', 'k6', ...extraArgs]; default: throw new Error(`unsupported command: ${selectedCommand}`); } } function ensureEnvFile() { if (existsSync(envPath)) { console.log(`[container] 已存在 ${envPath}`); return; } copyFileSync(envExamplePath, envPath); console.log(`[container] 已从 ${envExamplePath} 生成 ${envPath}`); } function printHelp(isError) { const output = isError ? console.error : console.log; output(`Usage: npm run container: -- [docker compose args] Commands: container:init 生成 deploy/container/api-server.env container:build 构建 api-server 容器镜像 container:up 后台启动 api-server + nginx + otelcol container:down 停止并清理容器 container:logs 查看容器日志 container:ps 查看容器状态 container:config 校验 compose 配置,传 -- --print 可展开完整配置 container:k6 在 compose 网络内运行 k6 `); }