统一 AGC 开发端口分配
Project CI / Repository checks (push) Failing after 1m2s
Project CI / Frontend tests (push) Successful in 3m19s
Project CI / Backend tests (push) Successful in 4m18s
Project CI / Native shell tests (push) Failing after 11m47s

将 AGC Vite 纳入 Linux 用户端口段第六槽位
同步 Tauri devUrl、Vite 监听和配套后端端口预留
兼容迁移旧五端口注册记录并阻止重复分配
补齐动态配置顺序、跨平台和进程生命周期回归测试
更新开发运维文档、端口 skill 与项目共享记忆
This commit is contained in:
2026-08-08 16:18:45 +08:00
parent 6b1dfc77d9
commit 83f07fc58d
19 changed files with 710 additions and 96 deletions
+70 -13
View File
@@ -16,6 +16,8 @@ const LINUX_DEV_PORT_RANGE_REGISTRY_ROOT = '/var/tmp/genarrative-dev-port-ranges
const LINUX_DEV_PORT_RANGE_POOL_START = 10000;
const LINUX_DEV_PORT_RANGE_POOL_END = 39999;
const LINUX_DEV_PORT_RANGE_BLOCK_SIZE = 100;
const LEGACY_DEV_PORT_RANGE_MIN_SIZE = 5;
const DEV_PORT_RANGE_MIN_SIZE = 6;
function toListenHosts(host) {
if (host === '0.0.0.0') {
@@ -38,7 +40,7 @@ export function normalizePort(value, fallback) {
return port;
}
export function parsePortRangeSpec(value) {
function parsePortRangeSpecWithMinimum(value, minimumSize) {
const spec = String(value ?? '').trim();
if (!spec) {
return null;
@@ -55,23 +57,33 @@ export function parsePortRangeSpec(value) {
throw new Error(`端口段无效: ${spec},端口必须在 1024-65535 且起始不大于结束`);
}
if (end - start + 1 < 5) {
throw new Error(`端口段至少需要 5 个端口: ${spec}`);
if (end - start + 1 < minimumSize) {
throw new Error(`端口段至少需要 ${minimumSize} 个端口: ${spec}`);
}
return {start, end, label: `${start}-${end}`};
}
function normalizePortRange(portRange) {
export function parsePortRangeSpec(value) {
return parsePortRangeSpecWithMinimum(value, DEV_PORT_RANGE_MIN_SIZE);
}
function normalizePortRange(
portRange,
{minimumSize = DEV_PORT_RANGE_MIN_SIZE} = {},
) {
if (!portRange) {
return null;
}
if (typeof portRange === 'string') {
return parsePortRangeSpec(portRange);
return parsePortRangeSpecWithMinimum(portRange, minimumSize);
}
return parsePortRangeSpec(`${portRange.start}-${portRange.end}`);
return parsePortRangeSpecWithMinimum(
`${portRange.start}-${portRange.end}`,
minimumSize,
);
}
export function getLinuxDevPortRangeRegistryPaths(env = process.env) {
@@ -108,7 +120,9 @@ export function getLinuxDevPortRangeSpec(env = process.env) {
}
export function mapDevPortsToPortRange(portRange) {
const normalizedRange = normalizePortRange(portRange);
const normalizedRange = normalizePortRange(portRange, {
minimumSize: LEGACY_DEV_PORT_RANGE_MIN_SIZE,
});
if (!normalizedRange) {
return null;
}
@@ -119,6 +133,10 @@ export function mapDevPortsToPortRange(portRange) {
spacetimePort: normalizedRange.start + 2,
adminWebPort: normalizedRange.start + 3,
bgfilterWorkerPort: normalizedRange.start + 4,
agcVitePort:
normalizedRange.start + 5 <= normalizedRange.end
? normalizedRange.start + 5
: null,
range: normalizedRange,
};
}
@@ -252,9 +270,12 @@ function readLinuxPortRangeRegistry(registryPath) {
return registry;
}
function safeNormalizePortRange(portRange) {
function safeNormalizePortRange(
portRange,
{minimumSize = LEGACY_DEV_PORT_RANGE_MIN_SIZE} = {},
) {
try {
return normalizePortRange(portRange);
return normalizePortRange(portRange, {minimumSize});
} catch {
return null;
}
@@ -264,6 +285,32 @@ function rangesOverlap(left, right) {
return left.start <= right.end && right.start <= left.end;
}
function tryExpandLegacyPortRange(registry, username, portRange) {
const normalizedRange = safeNormalizePortRange(portRange);
if (!normalizedRange) {
return null;
}
if (
normalizedRange.end - normalizedRange.start + 1 >=
DEV_PORT_RANGE_MIN_SIZE
) {
return normalizedRange;
}
const expandedEnd = normalizedRange.start + DEV_PORT_RANGE_MIN_SIZE - 1;
if (expandedEnd > 65535) {
return null;
}
const expandedRange = {
start: normalizedRange.start,
end: expandedEnd,
label: `${normalizedRange.start}-${expandedEnd}`,
};
return findRangeConflict(registry, expandedRange, username)
? null
: expandedRange;
}
function findRangeConflict(registry, portRange, excludingUsername = '') {
const rangeToCheck = safeNormalizePortRange(portRange);
if (!rangeToCheck) {
@@ -380,11 +427,16 @@ export async function reserveLinuxDevPortRange({
const current = registry.allocations[username];
if (current) {
const expandedRange = tryExpandLegacyPortRange(
registry,
username,
current.range,
);
registry.updatedAt = now;
registry.allocations[username] = {
...current,
username,
range: current.range,
range: expandedRange ?? current.range,
updatedAt: now,
};
atomicWriteJsonFile(registryPath, registry);
@@ -482,7 +534,9 @@ export async function findAvailablePort({
portRange = null,
strict = false,
}) {
const range = normalizePortRange(portRange);
const range = normalizePortRange(portRange, {
minimumSize: LEGACY_DEV_PORT_RANGE_MIN_SIZE,
});
const startPort = normalizePort(preferredPort, 0);
if (startPort === 0 && range) {
@@ -563,8 +617,11 @@ async function reserveEphemeralPort(host, reservedPorts) {
throw new Error(`无法为 ${host} 分配临时可用端口`);
}
export async function resolveDevStackPorts(config) {
const reservedPorts = new Set();
export async function resolveDevStackPorts(
config,
{reservedPorts: initialReservedPorts = new Set()} = {},
) {
const reservedPorts = new Set(initialReservedPorts);
const entries = [
['spacetime', config.spacetime],
['api', config.api],