修复桌面壳开发端口对齐
桌面壳 dev 命令显式锁定 Vite 3000 端口 dev 端口解析支持严格端口模式并避免显式端口被 Linux 端口段改写 补充端口错配回归测试和宿主壳文档记忆
This commit is contained in:
@@ -479,6 +479,7 @@ export async function findAvailablePort({
|
||||
reservedPorts = new Set(),
|
||||
maxAttempts = null,
|
||||
portRange = null,
|
||||
strict = false,
|
||||
}) {
|
||||
const range = normalizePortRange(portRange);
|
||||
const startPort = normalizePort(preferredPort, 0);
|
||||
@@ -501,6 +502,18 @@ export async function findAvailablePort({
|
||||
throw new Error(`端口 ${startPort} 不在允许端口段 ${range.label} 内`);
|
||||
}
|
||||
|
||||
if (strict && startPort !== 0) {
|
||||
if (reservedPorts.has(startPort)) {
|
||||
throw new Error(`端口 ${host}:${startPort} 已被当前 dev 启动流程占用,无法严格使用该端口`);
|
||||
}
|
||||
|
||||
if (await isPortAvailable({host, port: startPort})) {
|
||||
return startPort;
|
||||
}
|
||||
|
||||
throw new Error(`端口 ${host}:${startPort} 不可用,无法严格使用该端口`);
|
||||
}
|
||||
|
||||
const boundedAttempts = range
|
||||
? Number.isFinite(maxAttempts)
|
||||
? Math.min(Math.max(0, maxAttempts), range.end - startPort)
|
||||
@@ -565,6 +578,7 @@ export async function resolveDevStackPorts(config) {
|
||||
preferredPort: portConfig.preferredPort,
|
||||
reservedPorts,
|
||||
portRange: portConfig.portRange,
|
||||
strict: Boolean(portConfig.strict),
|
||||
});
|
||||
reservedPorts.add(resolvedPort);
|
||||
result[name] = resolvedPort;
|
||||
|
||||
@@ -58,6 +58,23 @@ describe('dev stack port utils', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('严格端口模式在端口被占用时直接失败而不是漂移', async () => {
|
||||
const server = await reservePort(0);
|
||||
const port = server.address().port;
|
||||
|
||||
try {
|
||||
await expect(
|
||||
findAvailablePort({
|
||||
host: '127.0.0.1',
|
||||
preferredPort: port,
|
||||
strict: true,
|
||||
}),
|
||||
).rejects.toThrow('无法严格使用该端口');
|
||||
} finally {
|
||||
await new Promise((resolve) => server.close(resolve));
|
||||
}
|
||||
});
|
||||
|
||||
it('端口查找不会越过 Linux 用户端口段', async () => {
|
||||
await expect(
|
||||
findAvailablePort({
|
||||
|
||||
+11
-4
@@ -132,6 +132,7 @@ function parseArgs(argv, baseEnv) {
|
||||
migrationBootstrapSecretMode: 'auto',
|
||||
watch: false,
|
||||
interactive: true,
|
||||
strictWebPort: false,
|
||||
};
|
||||
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
@@ -167,6 +168,9 @@ function parseArgs(argv, baseEnv) {
|
||||
options.webPort = normalizePort(readValue(), options.webPort);
|
||||
explicitOptions.add('webPort');
|
||||
break;
|
||||
case '--strict-web-port':
|
||||
options.strictWebPort = true;
|
||||
break;
|
||||
case '--admin-web-host':
|
||||
options.adminWebHost = readValue();
|
||||
explicitOptions.add('adminWebHost');
|
||||
@@ -956,13 +960,15 @@ class DevRunner {
|
||||
async resolvePorts(command) {
|
||||
const {options} = this;
|
||||
const portConfig = {};
|
||||
const portRangeFor = (optionName) =>
|
||||
this.explicitOptions.has(optionName) ? null : this.state.portRange;
|
||||
|
||||
if (command === 'all' || command === 'spacetime') {
|
||||
if (!options.skipSpacetime && !this.state.spacetimeReused) {
|
||||
portConfig.spacetime = {
|
||||
host: options.spacetimeHost,
|
||||
preferredPort: options.spacetimePort,
|
||||
portRange: this.state.portRange,
|
||||
portRange: portRangeFor('spacetimePort'),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -971,7 +977,7 @@ class DevRunner {
|
||||
portConfig.api = {
|
||||
host: options.apiHost,
|
||||
preferredPort: options.apiPort,
|
||||
portRange: this.state.portRange,
|
||||
portRange: portRangeFor('apiPort'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -979,7 +985,8 @@ class DevRunner {
|
||||
portConfig.web = {
|
||||
host: options.webHost,
|
||||
preferredPort: options.webPort,
|
||||
portRange: this.state.portRange,
|
||||
portRange: portRangeFor('webPort'),
|
||||
strict: options.strictWebPort,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -987,7 +994,7 @@ class DevRunner {
|
||||
portConfig.adminWeb = {
|
||||
host: options.adminWebHost,
|
||||
preferredPort: options.adminWebPort,
|
||||
portRange: this.state.portRange,
|
||||
portRange: portRangeFor('adminWebPort'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -139,6 +139,36 @@ describe('dev scheduler argument routing', () => {
|
||||
}
|
||||
});
|
||||
|
||||
linuxTest('Linux 桌面壳显式指定 web-port 时不被系统级端口段改写', async () => {
|
||||
const tempDir = mkdtempSync(join(tmpdir(), 'genarrative-dev-port-range-'));
|
||||
try {
|
||||
const {command, explicitOptions, options} = parseArgs(
|
||||
['web', '--web-port', '3000', '--strict-web-port'],
|
||||
{
|
||||
USER: 'alice',
|
||||
LOGNAME: 'alice',
|
||||
GENARRATIVE_DEV_PORT_RANGE: '22000-22099',
|
||||
GENARRATIVE_DEV_PORT_RANGE_REGISTRY_DIR: tempDir,
|
||||
},
|
||||
);
|
||||
const runner = new DevRunner(options, {
|
||||
USER: 'alice',
|
||||
LOGNAME: 'alice',
|
||||
GENARRATIVE_DEV_PORT_RANGE: '22000-22099',
|
||||
GENARRATIVE_DEV_PORT_RANGE_REGISTRY_DIR: tempDir,
|
||||
}, explicitOptions);
|
||||
|
||||
await runner.prepareLinuxPortRange(command);
|
||||
expect(runner.state.portRange.label).toBe('22000-22099');
|
||||
expect(runner.options.webPort).toBe(3000);
|
||||
expect(runner.options.apiPort).toBe(22001);
|
||||
expect(runner.options.spacetimePort).toBe(22002);
|
||||
expect(runner.options.adminWebPort).toBe(22003);
|
||||
} finally {
|
||||
rmSync(tempDir, {recursive: true, force: true});
|
||||
}
|
||||
});
|
||||
|
||||
test('Windows 仍沿用原有端口解析,不启用 Linux 端口段登记', async () => {
|
||||
const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform');
|
||||
Object.defineProperty(process, 'platform', {
|
||||
|
||||
Reference in New Issue
Block a user