mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-03 08:53:36 +00:00
feat: enhance port management and server initialization process
- Added a new function to check if a port is in use without terminating processes, improving user experience during server startup. - Updated the health check function to accept a dynamic port parameter, allowing for flexible server configurations. - Implemented user prompts for handling port conflicts, enabling users to kill processes, choose different ports, or cancel the operation. - Enhanced CORS configuration to support localhost and IPv6 addresses, ensuring compatibility across different development environments. - Refactored the main function to utilize dynamic port assignments for both the web and server applications, improving overall flexibility.
This commit is contained in:
@@ -133,7 +133,11 @@ app.use(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For local development, allow localhost origins
|
// For local development, allow localhost origins
|
||||||
if (origin.startsWith('http://localhost:') || origin.startsWith('http://127.0.0.1:')) {
|
if (
|
||||||
|
origin.startsWith('http://localhost:') ||
|
||||||
|
origin.startsWith('http://127.0.0.1:') ||
|
||||||
|
origin.startsWith('http://[::1]:')
|
||||||
|
) {
|
||||||
callback(null, origin);
|
callback(null, origin);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,9 +53,12 @@ let mainWindow: BrowserWindow | null = null;
|
|||||||
let serverProcess: ChildProcess | null = null;
|
let serverProcess: ChildProcess | null = null;
|
||||||
let staticServer: Server | null = null;
|
let staticServer: Server | null = null;
|
||||||
|
|
||||||
// Default ports - will be dynamically assigned if these are in use
|
// Default ports (can be overridden via env) - will be dynamically assigned if these are in use
|
||||||
const DEFAULT_SERVER_PORT = 3008;
|
// When launched via root init.mjs we pass:
|
||||||
const DEFAULT_STATIC_PORT = 3007;
|
// - PORT (backend)
|
||||||
|
// - TEST_PORT (vite dev server / static)
|
||||||
|
const DEFAULT_SERVER_PORT = parseInt(process.env.PORT || '3008', 10);
|
||||||
|
const DEFAULT_STATIC_PORT = parseInt(process.env.TEST_PORT || '3007', 10);
|
||||||
|
|
||||||
// Actual ports in use (set during startup)
|
// Actual ports in use (set during startup)
|
||||||
let serverPort = DEFAULT_SERVER_PORT;
|
let serverPort = DEFAULT_SERVER_PORT;
|
||||||
@@ -75,7 +78,9 @@ function isPortAvailable(port: number): Promise<boolean> {
|
|||||||
resolve(true);
|
resolve(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
server.listen(port, '127.0.0.1');
|
// Use Node's default binding semantics (matches most dev servers)
|
||||||
|
// This avoids false-positives when a port is taken on IPv6/dual-stack.
|
||||||
|
server.listen(port);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
166
init.mjs
166
init.mjs
@@ -170,6 +170,14 @@ function killProcess(pid) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a port is in use (without killing)
|
||||||
|
*/
|
||||||
|
function isPortInUse(port) {
|
||||||
|
const pids = getProcessesOnPort(port);
|
||||||
|
return pids.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kill processes on a port and wait for it to be freed
|
* Kill processes on a port and wait for it to be freed
|
||||||
*/
|
*/
|
||||||
@@ -211,9 +219,9 @@ function sleep(ms) {
|
|||||||
/**
|
/**
|
||||||
* Check if the server health endpoint is responding
|
* Check if the server health endpoint is responding
|
||||||
*/
|
*/
|
||||||
function checkHealth() {
|
function checkHealth(port = 3008) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const req = http.get('http://localhost:3008/api/health', (res) => {
|
const req = http.get(`http://localhost:${port}/api/health`, (res) => {
|
||||||
resolve(res.statusCode === 200);
|
resolve(res.statusCode === 200);
|
||||||
});
|
});
|
||||||
req.on('error', () => resolve(false));
|
req.on('error', () => resolve(false));
|
||||||
@@ -245,10 +253,16 @@ function prompt(question) {
|
|||||||
* Run npm command using cross-spawn for Windows compatibility
|
* Run npm command using cross-spawn for Windows compatibility
|
||||||
*/
|
*/
|
||||||
function runNpm(args, options = {}) {
|
function runNpm(args, options = {}) {
|
||||||
|
const { env, ...restOptions } = options;
|
||||||
const spawnOptions = {
|
const spawnOptions = {
|
||||||
stdio: 'inherit',
|
stdio: 'inherit',
|
||||||
cwd: __dirname,
|
cwd: __dirname,
|
||||||
...options,
|
...restOptions,
|
||||||
|
// Ensure environment variables are properly merged with process.env
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
...(env || {}),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
// cross-spawn handles Windows .cmd files automatically
|
// cross-spawn handles Windows .cmd files automatically
|
||||||
return crossSpawn('npm', args, spawnOptions);
|
return crossSpawn('npm', args, spawnOptions);
|
||||||
@@ -352,10 +366,123 @@ async function main() {
|
|||||||
log('Playwright installation skipped', 'yellow');
|
log('Playwright installation skipped', 'yellow');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kill any existing processes on required ports
|
// Check for processes on required ports and prompt user
|
||||||
log('Checking for processes on ports 3007 and 3008...', 'yellow');
|
log('Checking for processes on ports 3007 and 3008...', 'yellow');
|
||||||
|
|
||||||
|
const webPortInUse = isPortInUse(3007);
|
||||||
|
const serverPortInUse = isPortInUse(3008);
|
||||||
|
|
||||||
|
let webPort = 3007;
|
||||||
|
let serverPort = 3008;
|
||||||
|
let corsOriginEnv = process.env.CORS_ORIGIN || '';
|
||||||
|
|
||||||
|
if (webPortInUse || serverPortInUse) {
|
||||||
|
console.log('');
|
||||||
|
if (webPortInUse) {
|
||||||
|
const pids = getProcessesOnPort(3007);
|
||||||
|
log(`⚠ Port 3007 is in use by process(es): ${pids.join(', ')}`, 'yellow');
|
||||||
|
}
|
||||||
|
if (serverPortInUse) {
|
||||||
|
const pids = getProcessesOnPort(3008);
|
||||||
|
log(`⚠ Port 3008 is in use by process(es): ${pids.join(', ')}`, 'yellow');
|
||||||
|
}
|
||||||
|
console.log('');
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const choice = await prompt('What would you like to do? (k)ill processes, (u)se different ports, or (c)ancel: ');
|
||||||
|
const lowerChoice = choice.toLowerCase();
|
||||||
|
|
||||||
|
if (lowerChoice === 'k' || lowerChoice === 'kill') {
|
||||||
|
if (webPortInUse) {
|
||||||
await killPort(3007);
|
await killPort(3007);
|
||||||
|
} else {
|
||||||
|
log(`✓ Port 3007 is available`, 'green');
|
||||||
|
}
|
||||||
|
if (serverPortInUse) {
|
||||||
await killPort(3008);
|
await killPort(3008);
|
||||||
|
} else {
|
||||||
|
log(`✓ Port 3008 is available`, 'green');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if (lowerChoice === 'u' || lowerChoice === 'use') {
|
||||||
|
// Prompt for new ports
|
||||||
|
while (true) {
|
||||||
|
const newWebPort = await prompt('Enter web port (default 3007): ');
|
||||||
|
const parsedWebPort = newWebPort.trim() ? parseInt(newWebPort.trim(), 10) : 3007;
|
||||||
|
|
||||||
|
if (isNaN(parsedWebPort) || parsedWebPort < 1024 || parsedWebPort > 65535) {
|
||||||
|
log('Invalid port. Please enter a number between 1024 and 65535.', 'red');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPortInUse(parsedWebPort)) {
|
||||||
|
const pids = getProcessesOnPort(parsedWebPort);
|
||||||
|
log(`Port ${parsedWebPort} is already in use by process(es): ${pids.join(', ')}`, 'red');
|
||||||
|
const useAnyway = await prompt('Use this port anyway? (y/n): ');
|
||||||
|
if (useAnyway.toLowerCase() !== 'y' && useAnyway.toLowerCase() !== 'yes') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
webPort = parsedWebPort;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const newServerPort = await prompt('Enter server port (default 3008): ');
|
||||||
|
const parsedServerPort = newServerPort.trim() ? parseInt(newServerPort.trim(), 10) : 3008;
|
||||||
|
|
||||||
|
if (isNaN(parsedServerPort) || parsedServerPort < 1024 || parsedServerPort > 65535) {
|
||||||
|
log('Invalid port. Please enter a number between 1024 and 65535.', 'red');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedServerPort === webPort) {
|
||||||
|
log('Server port cannot be the same as web port.', 'red');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPortInUse(parsedServerPort)) {
|
||||||
|
const pids = getProcessesOnPort(parsedServerPort);
|
||||||
|
log(`Port ${parsedServerPort} is already in use by process(es): ${pids.join(', ')}`, 'red');
|
||||||
|
const useAnyway = await prompt('Use this port anyway? (y/n): ');
|
||||||
|
if (useAnyway.toLowerCase() !== 'y' && useAnyway.toLowerCase() !== 'yes') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serverPort = parsedServerPort;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`Using ports: Web=${webPort}, Server=${serverPort}`, 'blue');
|
||||||
|
break;
|
||||||
|
} else if (lowerChoice === 'c' || lowerChoice === 'cancel') {
|
||||||
|
log('Cancelled.', 'yellow');
|
||||||
|
process.exit(0);
|
||||||
|
} else {
|
||||||
|
log('Invalid choice. Please enter k (kill), u (use different ports), or c (cancel).', 'red');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log(`✓ Port 3007 is available`, 'green');
|
||||||
|
log(`✓ Port 3008 is available`, 'green');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure backend CORS allows whichever UI port we ended up using.
|
||||||
|
// If CORS_ORIGIN is set, server enforces it strictly (see apps/server/src/index.ts),
|
||||||
|
// so we must include the selected web origin(s) in that list.
|
||||||
|
{
|
||||||
|
const existing = (process.env.CORS_ORIGIN || '')
|
||||||
|
.split(',')
|
||||||
|
.map((o) => o.trim())
|
||||||
|
.filter(Boolean)
|
||||||
|
.filter((o) => o !== '*');
|
||||||
|
const origins = new Set(existing);
|
||||||
|
origins.add(`http://localhost:${webPort}`);
|
||||||
|
origins.add(`http://127.0.0.1:${webPort}`);
|
||||||
|
corsOriginEnv = Array.from(origins).join(',');
|
||||||
|
}
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
// Show menu
|
// Show menu
|
||||||
@@ -388,7 +515,7 @@ async function main() {
|
|||||||
log('Launching Web Application...', 'blue');
|
log('Launching Web Application...', 'blue');
|
||||||
|
|
||||||
// Start the backend server
|
// Start the backend server
|
||||||
log('Starting backend server on port 3008...', 'blue');
|
log(`Starting backend server on port ${serverPort}...`, 'blue');
|
||||||
|
|
||||||
// Create logs directory
|
// Create logs directory
|
||||||
if (!fs.existsSync(path.join(__dirname, 'logs'))) {
|
if (!fs.existsSync(path.join(__dirname, 'logs'))) {
|
||||||
@@ -399,6 +526,10 @@ async function main() {
|
|||||||
const logStream = fs.createWriteStream(path.join(__dirname, 'logs', 'server.log'));
|
const logStream = fs.createWriteStream(path.join(__dirname, 'logs', 'server.log'));
|
||||||
serverProcess = runNpm(['run', 'dev:server'], {
|
serverProcess = runNpm(['run', 'dev:server'], {
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
stdio: ['ignore', 'pipe', 'pipe'],
|
||||||
|
env: {
|
||||||
|
PORT: String(serverPort),
|
||||||
|
CORS_ORIGIN: corsOriginEnv,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pipe to both log file and console so user can see API key
|
// Pipe to both log file and console so user can see API key
|
||||||
@@ -418,7 +549,7 @@ async function main() {
|
|||||||
let serverReady = false;
|
let serverReady = false;
|
||||||
|
|
||||||
for (let i = 0; i < maxRetries; i++) {
|
for (let i = 0; i < maxRetries; i++) {
|
||||||
if (await checkHealth()) {
|
if (await checkHealth(serverPort)) {
|
||||||
serverReady = true;
|
serverReady = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -436,11 +567,17 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log('✓ Server is ready!', 'green');
|
log('✓ Server is ready!', 'green');
|
||||||
log(`The application will be available at: http://localhost:3007`, 'green');
|
log(`The application will be available at: http://localhost:${webPort}`, 'green');
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
// Start web app
|
// Start web app
|
||||||
webProcess = runNpm(['run', 'dev:web'], { stdio: 'inherit' });
|
webProcess = runNpm(['run', 'dev:web'], {
|
||||||
|
stdio: 'inherit',
|
||||||
|
env: {
|
||||||
|
TEST_PORT: String(webPort),
|
||||||
|
VITE_SERVER_URL: `http://localhost:${serverPort}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
webProcess.on('close', resolve);
|
webProcess.on('close', resolve);
|
||||||
});
|
});
|
||||||
@@ -452,7 +589,18 @@ async function main() {
|
|||||||
log('(Electron will start its own backend server)', 'yellow');
|
log('(Electron will start its own backend server)', 'yellow');
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
||||||
electronProcess = runNpm(['run', 'dev:electron'], { stdio: 'inherit' });
|
// Pass selected ports through to Vite + Electron backend
|
||||||
|
// - TEST_PORT controls Vite dev server port (see apps/ui/vite.config.mts)
|
||||||
|
// - PORT controls backend server port (see apps/server/src/index.ts)
|
||||||
|
electronProcess = runNpm(['run', 'dev:electron'], {
|
||||||
|
stdio: 'inherit',
|
||||||
|
env: {
|
||||||
|
TEST_PORT: String(webPort),
|
||||||
|
PORT: String(serverPort),
|
||||||
|
VITE_SERVER_URL: `http://localhost:${serverPort}`,
|
||||||
|
CORS_ORIGIN: corsOriginEnv,
|
||||||
|
},
|
||||||
|
});
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
electronProcess.on('close', resolve);
|
electronProcess.on('close', resolve);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user