mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-24 00:51:53 +00:00
feat(desktop): Dynamic port selection via --port 0 + stdout signal
Spawn the GitNexus backend server with --port 0 so the OS assigns a free port instead of hardcoding 4747. The server announces the chosen port via a GITNEXUS_PORT=N stdout line, which the desktop captures and uses for health checks and identity verification. Also fixes server/api.ts to log the actual bound port (server.address().port) instead of the port parameter, which was wrong when using --port 0. Removes GITNEXUS_PORT, GITNEXUS_SERVER_URL, GITNEXUS_SERVER_HEALTH_URLS constants. Adds identity verification via /api/info version field. Passes server URL to web UI via ?server= query param (already supported by App.tsx auto-connect). Refs GH-985
This commit is contained in:
parent
71a2e4ba9d
commit
28768d9eaa
2 changed files with 57 additions and 29 deletions
|
|
@ -25,14 +25,8 @@ const DESKTOP_APP_ICON_PATH = app.isPackaged
|
|||
const DESKTOP_GET_SHELL_STATE_CHANNEL = 'gitnexus-desktop:get-shell-state';
|
||||
const DESKTOP_WINDOW_ACTION_CHANNEL = 'gitnexus-desktop:window-action';
|
||||
const DESKTOP_WINDOW_STATE_CHANGED_CHANNEL = 'gitnexus-desktop:window-state-changed';
|
||||
const GITNEXUS_PORT = 4747;
|
||||
const GITNEXUS_HOST = 'localhost';
|
||||
const GITNEXUS_SERVER_URL = `http://${GITNEXUS_HOST}:${GITNEXUS_PORT}`;
|
||||
const GITNEXUS_SERVER_HEALTH_URLS = [
|
||||
`${GITNEXUS_SERVER_URL}/api/info`,
|
||||
`http://127.0.0.1:${GITNEXUS_PORT}/api/info`,
|
||||
`http://[::1]:${GITNEXUS_PORT}/api/info`,
|
||||
];
|
||||
let gitNexusPort = 0;
|
||||
const GITNEXUS_DEV_RUNTIME_DIR = path.resolve(__dirname, '../../../gitnexus');
|
||||
const GITNEXUS_WEB_DEV_HOST = 'localhost';
|
||||
const GITNEXUS_WEB_DEV_PORT = 5173;
|
||||
|
|
@ -72,7 +66,7 @@ const MIME_TYPES: Record<string, string> = {
|
|||
'.woff2': 'font/woff2',
|
||||
};
|
||||
|
||||
let serverReadyPromise: Promise<void> | null = null;
|
||||
let serverReadyPromise: Promise<number> | null = null;
|
||||
let gitNexusServerProcess: ChildProcess | null = null;
|
||||
let gitNexusServerOutput = '';
|
||||
let webDevServerProcess: ChildProcess | null = null;
|
||||
|
|
@ -284,7 +278,7 @@ const getNodeProcessEnvironment = (overrides: NodeJS.ProcessEnv = {}): NodeJS.Pr
|
|||
const spawnGitNexusServer = (): ChildProcess => {
|
||||
const childProcess = spawn(
|
||||
getNodeCommand(),
|
||||
[getGitNexusCliEntry(), 'serve', '--host', GITNEXUS_HOST],
|
||||
[getGitNexusCliEntry(), 'serve', '--host', GITNEXUS_HOST, '--port', '0'],
|
||||
{
|
||||
cwd: getGitNexusRuntimeDir(),
|
||||
env: getNodeProcessEnvironment(app.isPackaged ? { GITNEXUS_DISABLE_MCP_HTTP: '1' } : {}),
|
||||
|
|
@ -295,7 +289,12 @@ const spawnGitNexusServer = (): ChildProcess => {
|
|||
|
||||
childProcess.stdout?.on('data', (chunk) => {
|
||||
appendGitNexusServerOutput(chunk);
|
||||
process.stdout.write(`[gitnexus-server] ${chunk.toString()}`);
|
||||
const str = chunk.toString();
|
||||
const match = str.match(/GITNEXUS_PORT=(\d+)/);
|
||||
if (match) {
|
||||
gitNexusPort = parseInt(match[1], 10);
|
||||
}
|
||||
process.stdout.write(`[gitnexus-server] ${str}`);
|
||||
});
|
||||
|
||||
childProcess.stderr?.on('data', (chunk) => {
|
||||
|
|
@ -314,12 +313,25 @@ const spawnGitNexusServer = (): ChildProcess => {
|
|||
return childProcess;
|
||||
};
|
||||
|
||||
const waitForGitNexusServerReady = async (): Promise<void> => {
|
||||
const waitForGitNexusServerReady = async (): Promise<number> => {
|
||||
const deadline = Date.now() + GITNEXUS_SERVER_READY_TIMEOUT_MS;
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
if (await isAnyHttpUrlReady(GITNEXUS_SERVER_HEALTH_URLS)) {
|
||||
return;
|
||||
if (gitNexusPort > 0) {
|
||||
const healthUrl = `http://${GITNEXUS_HOST}:${gitNexusPort}/api/info`;
|
||||
const responseText = await fetchUrlText(healthUrl, 2_000);
|
||||
|
||||
if (responseText) {
|
||||
try {
|
||||
const data = JSON.parse(responseText);
|
||||
// Identity check: /api/info must return { version: string }
|
||||
if (typeof data.version === 'string') {
|
||||
return gitNexusPort;
|
||||
}
|
||||
} catch {
|
||||
// Retry — response may be incomplete or not yet the API server
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gitNexusServerProcess?.exitCode !== null && gitNexusServerProcess?.exitCode !== undefined) {
|
||||
|
|
@ -337,8 +349,8 @@ const waitForGitNexusServerReady = async (): Promise<void> => {
|
|||
const output = gitNexusServerOutput.trim();
|
||||
throw new Error(
|
||||
output
|
||||
? `Timed out waiting for GitNexus backend at ${GITNEXUS_SERVER_HEALTH_URLS.join(', ')}.\n\n${output}`
|
||||
: `Timed out waiting for GitNexus backend at ${GITNEXUS_SERVER_HEALTH_URLS.join(', ')}.`,
|
||||
? `Timed out waiting for GitNexus backend.\n\n${output}`
|
||||
: 'Timed out waiting for GitNexus backend.',
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -687,7 +699,7 @@ const showStartupError = (error: unknown): void => {
|
|||
if (isAddressInUseError(error)) {
|
||||
dialog.showErrorBox(
|
||||
'GitNexus Desktop Startup Failed',
|
||||
`Port ${GITNEXUS_PORT} is already in use.\n\nStop the other process using port ${GITNEXUS_PORT} and try again.`,
|
||||
'Another process is already using the port GitNexus needs.\n\nClose the other application and try again.',
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -698,29 +710,40 @@ const showStartupError = (error: unknown): void => {
|
|||
);
|
||||
};
|
||||
|
||||
const ensureGitNexusServerStarted = async (): Promise<void> => {
|
||||
const ensureGitNexusServerStarted = async (): Promise<number> => {
|
||||
if (!serverReadyPromise) {
|
||||
serverReadyPromise = (async () => {
|
||||
if (await isAnyHttpUrlReady(GITNEXUS_SERVER_HEALTH_URLS)) {
|
||||
return;
|
||||
if (gitNexusPort > 0) {
|
||||
const healthUrl = `http://${GITNEXUS_HOST}:${gitNexusPort}/api/info`;
|
||||
const responseText = await fetchUrlText(healthUrl, 2_000);
|
||||
if (responseText) {
|
||||
try {
|
||||
const data = JSON.parse(responseText);
|
||||
if (typeof data.version === 'string') {
|
||||
return gitNexusPort;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
if (!gitNexusServerProcess || gitNexusServerProcess.exitCode !== null) {
|
||||
gitNexusPort = 0;
|
||||
gitNexusServerOutput = '';
|
||||
gitNexusServerProcess = spawnGitNexusServer();
|
||||
}
|
||||
|
||||
await waitForGitNexusServerReady();
|
||||
return await waitForGitNexusServerReady();
|
||||
})().catch((error) => {
|
||||
serverReadyPromise = null;
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
await serverReadyPromise;
|
||||
return serverReadyPromise;
|
||||
};
|
||||
|
||||
async function createWindow(): Promise<void> {
|
||||
const serverPort = await ensureGitNexusServerStarted();
|
||||
let embeddedAppUrl: string;
|
||||
|
||||
if (app.isPackaged) {
|
||||
|
|
@ -730,6 +753,11 @@ async function createWindow(): Promise<void> {
|
|||
embeddedAppUrl = GITNEXUS_WEB_DEV_URL;
|
||||
}
|
||||
|
||||
// Pass server URL to web UI via query param so it auto-connects (App.tsx reads ?server=)
|
||||
const serverUrl = `http://${GITNEXUS_HOST}:${serverPort}`;
|
||||
const separator = embeddedAppUrl.includes('?') ? '&' : '?';
|
||||
embeddedAppUrl += `${separator}server=${encodeURIComponent(serverUrl)}`;
|
||||
|
||||
const window = new BrowserWindow({
|
||||
width: 1280,
|
||||
height: 800,
|
||||
|
|
@ -803,7 +831,6 @@ async function createWindow(): Promise<void> {
|
|||
app.whenReady().then(async () => {
|
||||
try {
|
||||
registerWindowIpcHandlers();
|
||||
await ensureGitNexusServerStarted();
|
||||
await createWindow();
|
||||
} catch (error) {
|
||||
showStartupError(error);
|
||||
|
|
@ -812,12 +839,10 @@ app.whenReady().then(async () => {
|
|||
|
||||
app.on('activate', () => {
|
||||
if (openWindows.size === 0) {
|
||||
void ensureGitNexusServerStarted()
|
||||
.then(() => createWindow())
|
||||
.catch((error) => {
|
||||
showStartupError(error);
|
||||
exitStartupFailure();
|
||||
});
|
||||
void createWindow().catch((error) => {
|
||||
showStartupError(error);
|
||||
exitStartupFailure();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1865,8 +1865,11 @@ export const createServer = async (port: number, host: string = '127.0.0.1') =>
|
|||
// to the caller instead of crashing with an unhandled 'error' event.
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const server = app.listen(port, host, () => {
|
||||
const addr = server.address();
|
||||
const actualPort: number = addr && typeof addr !== 'string' ? addr.port : port;
|
||||
const displayHost = host === '::' || host === '0.0.0.0' ? 'localhost' : host;
|
||||
console.log(`GitNexus server running on http://${displayHost}:${port}`);
|
||||
console.log(`GITNEXUS_PORT=${actualPort}`);
|
||||
console.log(`GitNexus server running on http://${displayHost}:${actualPort}`);
|
||||
resolve();
|
||||
});
|
||||
server.on('error', (err) => reject(err));
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue