import { createServer } from 'node:http'; import { createReadStream } from 'node:fs'; import { readFile, stat } from 'node:fs/promises'; import { pipeline } from 'node:stream/promises'; import { pathToFileURL } from 'node:url'; import path from 'node:path'; import { output } from './suite.mjs'; // A local-only source server; only the two indexes and package files are exposed. export async function serve(port = 0) { let base; const server = createServer(async (request, response) => { try { if (request.method !== 'GET' && request.method !== 'HEAD') { response.writeHead(405).end(); return; } const pathname = new URL(request.url, 'http://localhost').pathname; if (['/index.json', '/faults-index.json'].includes(pathname)) { const index = JSON.parse(await readFile(path.join(output, pathname.slice(1)), 'utf8')); for (const curio of index.curios) for (const version of curio.versions) for (const artifact of version.artifacts) { artifact.url = `${base}/packages/${encodeURIComponent(path.basename(new URL(artifact.url).pathname))}`; } const body = JSON.stringify(index); response.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': Buffer.byteLength(body), 'Cache-Control': 'no-store' }); response.end(request.method === 'HEAD' ? undefined : body); } else if (/^\/packages\/[a-z0-9][a-z0-9.+-]*\.curio$/.test(pathname)) { const file = path.join(output, pathname.slice(1)); const metadata = await stat(file); response.writeHead(200, { 'Content-Type': 'application/octet-stream', 'Content-Length': metadata.size }); if (request.method === 'HEAD') response.end(); else await pipeline(createReadStream(file), response); } else { response.writeHead(404).end(); } } catch (error) { if (!response.headersSent) response.writeHead(error.code === 'ENOENT' ? 404 : 500); response.end(); } }); await new Promise((resolve, reject) => { server.once('error', reject); server.listen(port, '127.0.0.1', resolve); }); base = `http://127.0.0.1:${server.address().port}`; return { base, close: () => new Promise(resolve => server.close(resolve)) }; } if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) { const port = Number(process.env.QA_HTTP_PORT || 8765); if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error('QA_HTTP_PORT must be 1..65535'); const server = await serve(port); console.log(`常规 HTTP 源:${server.base}/index.json\n故障 HTTP 源:${server.base}/faults-index.json\nCtrl-C 停止服务。`); process.once('SIGINT', () => { void server.close(); }); process.once('SIGTERM', () => { void server.close(); }); }