diff --git a/scripts/dev-all.mjs b/scripts/dev-all.mjs index 34918d6d6..448b33cc9 100644 --- a/scripts/dev-all.mjs +++ b/scripts/dev-all.mjs @@ -91,6 +91,20 @@ function buildRootDevArgs(argv = []) { ]; } +function buildDevAllBaseEnvironment(argv = [], baseEnv = process.env) { + let portRange; + for (let index = 0; index < argv.length; index += 1) { + if (argv[index] === '--port-range') { + portRange = argv[index + 1]; + index += 1; + } + } + + return portRange + ? { ...baseEnv, GENARRATIVE_DEV_PORT_RANGE: portRange } + : { ...baseEnv }; +} + function buildDevAllEnvironment(baseEnv = process.env, agcVitePort) { if (!Number.isInteger(agcVitePort) || agcVitePort < 1024) { throw new Error(`AGC Vite 端口无效: ${agcVitePort}`); @@ -120,7 +134,7 @@ async function isHttpReady(url, timeoutMs = 1_000) { const timeout = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetch(url, { signal: controller.signal }); - return response.status >= 200 && response.status < 500; + return response.status >= 200 && response.status < 300; } catch { return false; } finally { @@ -511,11 +525,12 @@ async function runDevAll(argv = process.argv.slice(2), dependencies = {}) { } try { + const baseEnv = buildDevAllBaseEnvironment(argv); const endpoint = await resolveEndpoint({ - env: process.env, + env: baseEnv, strictConfigured: true, }); - const env = buildDevAllEnvironment(process.env, endpoint.port); + const env = buildDevAllEnvironment(baseEnv, endpoint.port); const rootArgs = buildRootDevArgs(argv); console.log( @@ -589,10 +604,12 @@ async function runDevAll(argv = process.argv.slice(2), dependencies = {}) { export { agcDatabase, agcDataDir, + buildDevAllBaseEnvironment, buildDevAllEnvironment, buildManagedSpawnOptions, buildRootDevArgs, buildStartupSummary, + isHttpReady, isSharedRootStackReady, readDevStackState, renderStartupTable, diff --git a/scripts/dev-all.test.ts b/scripts/dev-all.test.ts index 9a3352c03..be5f2c37e 100644 --- a/scripts/dev-all.test.ts +++ b/scripts/dev-all.test.ts @@ -1,4 +1,6 @@ import { EventEmitter } from 'node:events'; +import { createServer } from 'node:http'; +import type { AddressInfo } from 'node:net'; import { resolve } from 'node:path'; import { describe, expect, test } from 'vitest'; @@ -6,10 +8,12 @@ import { describe, expect, test } from 'vitest'; import { agcDatabase, agcDataDir, + buildDevAllBaseEnvironment, buildDevAllEnvironment, buildManagedSpawnOptions, buildRootDevArgs, buildStartupSummary, + isHttpReady, isSharedRootStackReady, renderStartupTable, runDevAll, @@ -86,6 +90,48 @@ describe('dev:all 参数和共享开发栈合同', () => { expect(baseEnv.AGC_DEV_ADMIN_WEB).toBe('1'); }); + test('CLI port-range 覆盖旧环境并同步给 AGC', () => { + const env = buildDevAllBaseEnvironment(['--port-range', '10100-10199'], { + GENARRATIVE_DEV_PORT_RANGE: '10000-10099', + }); + + expect(env.GENARRATIVE_DEV_PORT_RANGE).toBe('10100-10199'); + }); + + test.each([ + [200, true], + [204, true], + [299, true], + [400, false], + [401, false], + [403, false], + [404, false], + [500, false], + [503, false], + ])('HTTP %i 的前端就绪结果为 %s', async (status, expected) => { + const server = createServer((_request, response) => { + response.writeHead(status).end(); + }); + await new Promise((resolveListen) => { + server.listen(0, '127.0.0.1', resolveListen); + }); + + try { + const { port } = server.address() as AddressInfo; + await expect(isHttpReady(`http://127.0.0.1:${port}`)).resolves.toBe( + expected, + ); + } finally { + server.closeAllConnections(); + await new Promise((resolveClose, rejectClose) => { + server.close((error) => { + if (error) rejectClose(error); + else resolveClose(); + }); + }); + } + }); + test('Windows 下通过 shell 启动 npm.cmd', () => { const options = buildManagedSpawnOptions(process.env);