fix(desktop): 动态分配 sidecar 端口替代硬编码,避免端口冲突
使用 net.createServer().listen(0) 探测可用端口,通过 PORT 环境变量 传递给 sidecar binary(VS Code language server 同款模式)
This commit is contained in:
@@ -1,13 +1,24 @@
|
|||||||
import { spawn } from 'node:child_process'
|
import { spawn } from 'node:child_process'
|
||||||
|
import type { AddressInfo } from 'node:net'
|
||||||
|
import { createServer } from 'node:net'
|
||||||
import { join } from 'node:path'
|
import { join } from 'node:path'
|
||||||
import { app, BrowserWindow, shell } from 'electron'
|
import { app, BrowserWindow, shell } from 'electron'
|
||||||
|
|
||||||
const DEV_SERVER_URL = 'http://localhost:3000'
|
const DEV_SERVER_URL = 'http://localhost:3000'
|
||||||
const PROD_SERVER_PORT = 23_410
|
|
||||||
|
|
||||||
let mainWindow: BrowserWindow | null = null
|
let mainWindow: BrowserWindow | null = null
|
||||||
let serverProcess: ReturnType<typeof spawn> | null = null
|
let serverProcess: ReturnType<typeof spawn> | null = null
|
||||||
|
|
||||||
|
const getAvailablePort = (): Promise<number> =>
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
const server = createServer()
|
||||||
|
server.listen(0, () => {
|
||||||
|
const { port } = server.address() as AddressInfo
|
||||||
|
server.close(() => resolve(port))
|
||||||
|
})
|
||||||
|
server.on('error', reject)
|
||||||
|
})
|
||||||
|
|
||||||
const isServerReady = async (url: string): Promise<boolean> => {
|
const isServerReady = async (url: string): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, { method: 'HEAD' })
|
const response = await fetch(url, { method: 'HEAD' })
|
||||||
@@ -29,14 +40,14 @@ const waitForServer = async (
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const spawnServer = (): string => {
|
const spawnServer = (port: number): string => {
|
||||||
const binaryName = process.platform === 'win32' ? 'server.exe' : 'server'
|
const binaryName = process.platform === 'win32' ? 'server.exe' : 'server'
|
||||||
const binaryPath = join(process.resourcesPath, binaryName)
|
const binaryPath = join(process.resourcesPath, binaryName)
|
||||||
|
|
||||||
serverProcess = spawn(binaryPath, [], {
|
serverProcess = spawn(binaryPath, [], {
|
||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
PORT: String(PROD_SERVER_PORT),
|
PORT: String(port),
|
||||||
HOST: '127.0.0.1',
|
HOST: '127.0.0.1',
|
||||||
},
|
},
|
||||||
stdio: 'pipe',
|
stdio: 'pipe',
|
||||||
@@ -54,14 +65,15 @@ const spawnServer = (): string => {
|
|||||||
console.error('Failed to start server:', err)
|
console.error('Failed to start server:', err)
|
||||||
})
|
})
|
||||||
|
|
||||||
return `http://127.0.0.1:${PROD_SERVER_PORT}`
|
return `http://127.0.0.1:${port}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const getServerUrl = (): string => {
|
const getServerUrl = async (): Promise<string> => {
|
||||||
if (!app.isPackaged) {
|
if (!app.isPackaged) {
|
||||||
return DEV_SERVER_URL
|
return DEV_SERVER_URL
|
||||||
}
|
}
|
||||||
return spawnServer()
|
const port = await getAvailablePort()
|
||||||
|
return spawnServer(port)
|
||||||
}
|
}
|
||||||
|
|
||||||
const createWindow = async () => {
|
const createWindow = async () => {
|
||||||
@@ -87,7 +99,7 @@ const createWindow = async () => {
|
|||||||
}
|
}
|
||||||
mainWindow.show()
|
mainWindow.show()
|
||||||
|
|
||||||
const serverUrl = getServerUrl()
|
const serverUrl = await getServerUrl()
|
||||||
|
|
||||||
console.log(`Waiting for server at ${serverUrl}...`)
|
console.log(`Waiting for server at ${serverUrl}...`)
|
||||||
const ready = await waitForServer(serverUrl)
|
const ready = await waitForServer(serverUrl)
|
||||||
|
|||||||
Reference in New Issue
Block a user