import { execFileSync } from 'node:child_process'; const image = process.env.EASYFLOW_FRONTEND_IMAGE || 'easyflow-frontend:0.1'; const containerName = `easyflow-frontend-smoke-${process.pid}`; function docker(args) { return execFileSync('docker', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], }).trim(); } async function waitForServer(origin) { let lastError; for (let attempt = 0; attempt < 40; attempt += 1) { try { const response = await fetch(`${origin}/flow/`); if (response.ok) { return; } lastError = new Error(`HTTP ${response.status}`); } catch (error) { lastError = error; } await new Promise((resolve) => setTimeout(resolve, 250)); } throw new Error(`Nginx 未在预期时间内启动: ${String(lastError)}`); } function assert(condition, message) { if (!condition) { throw new Error(message); } } try { docker([ 'run', '--detach', '--rm', '--publish', '127.0.0.1::80', '--name', containerName, image, ]); const portBinding = docker(['port', containerName, '80/tcp']) .split('\n') .find((line) => line.startsWith('127.0.0.1:')); const port = portBinding?.match(/:(\d+)$/)?.[1]; assert(port, `无法解析容器端口: ${portBinding || 'empty'}`); const origin = `http://127.0.0.1:${port}`; await waitForServer(origin); const rootResponse = await fetch(`${origin}/`, { redirect: 'manual' }); assert(rootResponse.status === 302, `根路径状态异常: ${rootResponse.status}`); const rootLocation = rootResponse.headers.get('location'); assert( rootLocation && new URL(rootLocation, origin).pathname === '/flow/', `根路径重定向异常: ${rootLocation}`, ); const flowResponse = await fetch(`${origin}/flow/`); const indexHtml = await flowResponse.text(); assert(flowResponse.ok, `/flow/ 状态异常: ${flowResponse.status}`); assert(indexHtml.includes('
'), '/flow/ 未返回应用入口'); const assetPath = indexHtml.match(/(?:src|href)="(\/flow\/[^"]+)"/)?.[1]; assert(assetPath, '构建产物未使用 /flow/ 静态资源前缀'); const assetResponse = await fetch(`${origin}${assetPath}`); assert(assetResponse.ok, `静态资源状态异常: ${assetResponse.status}`); const invalidRootShareResponse = await fetch( `${origin}/share/knowledge?shareKey=smoke-test`, ); assert( invalidRootShareResponse.status === 404, `根路径分享地址应返回 404,实际为 ${invalidRootShareResponse.status}`, ); console.log( '部署冒烟通过:根路径重定向、/flow/ 入口、静态资源前缀和错误分享路径均符合预期。', ); } finally { try { docker(['rm', '--force', containerName]); } catch { // 容器可能因 --rm 已自动删除。 } }