2bdee990a2
- dev-port.mjs 新增 resolveAgcAdminWebEndpoint:后台 Web 端口按 Linux 用户端口段 start + 3、非 Linux 优先 3102 解析,ADMIN_WEB_PORT 可显式指定,并把已解析的 AGC Vite 端口列为保留端口 - start-dev-stack.mjs 随 AGC 一起拉起 apps/admin-web(AGC_DEV_ADMIN_WEB=0 关闭):与 AGC Vite 一样由启动器直接持有并纳入信号与退出收束,不走会整体重写 .app/dev-stack.json 的 dev:admin-web - start-dev-stack.mjs 新增启动汇总行,一次给出前端、后端、后台、数据库与 bgfilter-worker 的实际地址,端口漂移后以该行为准 - start-dev-stack.mjs 端口归属探测改用 netstat -ano 取端口到 PID、.NET Process 读可执行文件路径,仅在核对 SpacetimeDB --data-dir 归属时按 PID 取命令行并做 5 分钟 TTL 缓存:本机实测单轮探测由约 43 秒降到 0.37 秒,agc:serve 的 starting backend stack 到 backend ready 由约 80 秒降到 16.7 秒 - 后台 Web 端口解析或启动失败只告警,不阻断也不连带停止 AGC 客户端与配套后端 - tests/dev-port.test.ts、tests/start-dev-stack.test.ts 补充用例覆盖后台 Web 端口解析与严格占用、失败软化、启动汇总、netstat 探测脚本与命令行缓存失效 - docs/【开发运维】与 docs/technical 同步 AGC 开发态启动口径,pitfalls.md 记录 WMI 慢速探测的实测数据、netstat 改法与 PID 取最后一列的易错点
203 lines
5.7 KiB
JavaScript
203 lines
5.7 KiB
JavaScript
import {
|
|
findAvailablePort,
|
|
formatPortDecision,
|
|
mapDevPortsToPortRange,
|
|
normalizePort,
|
|
reserveLinuxDevPortRange,
|
|
} from '../../../scripts/dev-stack-port-utils.mjs';
|
|
|
|
const agcDevHost = '127.0.0.1';
|
|
const legacyAgcDevPort = 3080;
|
|
const agcVitePortEnvKey = 'GENARRATIVE_AGC_VITE_PORT';
|
|
const agcAdminWebHost = '127.0.0.1';
|
|
const legacyAgcAdminWebPort = 3102;
|
|
// 与 scripts/dev.mjs 的后台 Web 端口配置保持同一环境变量名。
|
|
const agcAdminWebPortEnvKey = 'ADMIN_WEB_PORT';
|
|
|
|
function readConfiguredAgcDevPort(env = process.env) {
|
|
const rawPort = String(env[agcVitePortEnvKey] ?? '').trim();
|
|
if (!rawPort) {
|
|
return null;
|
|
}
|
|
|
|
const port = normalizePort(rawPort, -1);
|
|
if (port < 1024) {
|
|
throw new Error(`${agcVitePortEnvKey} 必须是 1024-65535 的有效端口`);
|
|
}
|
|
return port;
|
|
}
|
|
|
|
function createAgcDevEndpoint(port, portRange = null) {
|
|
const url = `http://${agcDevHost}:${port}/`;
|
|
return {
|
|
host: agcDevHost,
|
|
port,
|
|
url,
|
|
markerUrl: `${url}__agc_dev_server.json`,
|
|
portRange,
|
|
};
|
|
}
|
|
|
|
function readAgcDevEndpoint(env = process.env) {
|
|
return createAgcDevEndpoint(
|
|
readConfiguredAgcDevPort(env) ?? legacyAgcDevPort,
|
|
);
|
|
}
|
|
|
|
async function resolveAgcDevEndpoint({
|
|
env = process.env,
|
|
platform = process.platform,
|
|
strictConfigured = false,
|
|
reservePortRange = reserveLinuxDevPortRange,
|
|
findPort = findAvailablePort,
|
|
} = {}) {
|
|
const configuredPort = readConfiguredAgcDevPort(env);
|
|
let portRange = null;
|
|
let preferredPort = configuredPort ?? legacyAgcDevPort;
|
|
|
|
if (platform === 'linux') {
|
|
const allocation = await reservePortRange({ env });
|
|
if (!allocation?.range) {
|
|
throw new Error('无法取得当前 Linux 用户的 dev 端口段');
|
|
}
|
|
portRange = allocation.range;
|
|
const mappedAgcVitePort = mapDevPortsToPortRange(portRange)?.agcVitePort;
|
|
if (!Number.isInteger(mappedAgcVitePort)) {
|
|
throw new Error(
|
|
`当前 Linux dev 端口段 ${portRange.label} 缺少 AGC Vite 槽位;请先迁移为至少 6 个端口且不与其它用户重叠的端口段`,
|
|
);
|
|
}
|
|
if (configuredPort != null && configuredPort < mappedAgcVitePort) {
|
|
throw new Error(
|
|
`${agcVitePortEnvKey} ${configuredPort} 占用了 Linux dev 端口段 ${portRange.label} 的前五个服务槽位`,
|
|
);
|
|
}
|
|
preferredPort = configuredPort ?? mappedAgcVitePort;
|
|
}
|
|
|
|
const port = await findPort({
|
|
host: agcDevHost,
|
|
preferredPort,
|
|
portRange,
|
|
strict: strictConfigured && configuredPort != null,
|
|
});
|
|
console.log(
|
|
formatPortDecision({
|
|
name: 'ai-game-creator-shell',
|
|
host: agcDevHost,
|
|
preferredPort,
|
|
resolvedPort: port,
|
|
}),
|
|
);
|
|
if (portRange) {
|
|
console.log(`[ai-game-creator-shell] dev port-range: ${portRange.label}`);
|
|
}
|
|
|
|
return createAgcDevEndpoint(port, portRange);
|
|
}
|
|
|
|
function withAgcDevEndpointEnv(endpoint, env = process.env) {
|
|
return {
|
|
...env,
|
|
[agcVitePortEnvKey]: String(endpoint.port),
|
|
};
|
|
}
|
|
|
|
function readConfiguredAgcAdminWebPort(env = process.env) {
|
|
const rawPort = String(env[agcAdminWebPortEnvKey] ?? '').trim();
|
|
if (!rawPort) {
|
|
return null;
|
|
}
|
|
|
|
const port = normalizePort(rawPort, -1);
|
|
if (port < 1024) {
|
|
throw new Error(`${agcAdminWebPortEnvKey} 必须是 1024-65535 的有效端口`);
|
|
}
|
|
return port;
|
|
}
|
|
|
|
function createAgcAdminWebEndpoint(port, portRange = null) {
|
|
const origin = `http://${agcAdminWebHost}:${port}`;
|
|
return {
|
|
host: agcAdminWebHost,
|
|
port,
|
|
origin,
|
|
basePath: '/admin/',
|
|
url: `${origin}/admin/`,
|
|
portRange,
|
|
};
|
|
}
|
|
|
|
// AGC 开发态的后台 Web 与 `npm run dev` 的后台 Vite 共用同一套优先端口约定:
|
|
// Linux 取当前用户端口段的 `start + 3` 槽位,非 Linux 保留 `3102` 兼容首选并允许统一漂移。
|
|
async function resolveAgcAdminWebEndpoint({
|
|
env = process.env,
|
|
platform = process.platform,
|
|
strictConfigured = false,
|
|
reservedPorts = [],
|
|
reservePortRange = reserveLinuxDevPortRange,
|
|
findPort = findAvailablePort,
|
|
} = {}) {
|
|
const configuredPort = readConfiguredAgcAdminWebPort(env);
|
|
let portRange = null;
|
|
let preferredPort = configuredPort ?? legacyAgcAdminWebPort;
|
|
|
|
if (platform === 'linux') {
|
|
const allocation = await reservePortRange({ env });
|
|
if (!allocation?.range) {
|
|
throw new Error('无法取得当前 Linux 用户的 dev 端口段');
|
|
}
|
|
portRange = allocation.range;
|
|
const mappedAdminWebPort = mapDevPortsToPortRange(portRange)?.adminWebPort;
|
|
if (!Number.isInteger(mappedAdminWebPort)) {
|
|
throw new Error(
|
|
`当前 Linux dev 端口段 ${portRange.label} 缺少后台 Web 槽位;请先迁移为至少 6 个端口且不与其它用户重叠的端口段`,
|
|
);
|
|
}
|
|
preferredPort = configuredPort ?? mappedAdminWebPort;
|
|
}
|
|
|
|
const reservedPortSet = new Set(
|
|
reservedPorts.filter((value) => Number.isInteger(value) && value > 0),
|
|
);
|
|
const port = await findPort({
|
|
host: agcAdminWebHost,
|
|
preferredPort,
|
|
portRange,
|
|
reservedPorts: reservedPortSet,
|
|
strict: strictConfigured && configuredPort != null,
|
|
});
|
|
console.log(
|
|
formatPortDecision({
|
|
name: 'ai-game-creator-shell-admin-web',
|
|
host: agcAdminWebHost,
|
|
preferredPort,
|
|
resolvedPort: port,
|
|
}),
|
|
);
|
|
if (portRange) {
|
|
console.log(
|
|
`[ai-game-creator-shell] admin-web port-range: ${portRange.label}`,
|
|
);
|
|
}
|
|
|
|
return createAgcAdminWebEndpoint(port, portRange);
|
|
}
|
|
|
|
export {
|
|
agcAdminWebHost,
|
|
agcAdminWebPortEnvKey,
|
|
agcDevHost,
|
|
agcVitePortEnvKey,
|
|
createAgcAdminWebEndpoint,
|
|
createAgcDevEndpoint,
|
|
legacyAgcAdminWebPort,
|
|
legacyAgcDevPort,
|
|
readAgcDevEndpoint,
|
|
readConfiguredAgcAdminWebPort,
|
|
readConfiguredAgcDevPort,
|
|
resolveAgcAdminWebEndpoint,
|
|
resolveAgcDevEndpoint,
|
|
withAgcDevEndpointEnv,
|
|
};
|