feat: Add automatic free port finder for frontend and backend
Some checks failed
CI/CD / lint-and-build (push) Has been cancelled

This commit is contained in:
DosAi 2025-11-02 17:39:38 +03:00
parent 766d758be1
commit ea61c5f493
5 changed files with 200 additions and 8 deletions

77
backend/lib/portFinder.js Normal file
View File

@ -0,0 +1,77 @@
// Утилита для поиска свободного порта
const net = require('net');
/**
* Проверяет, свободен ли порт
* @param {number} port - Порт для проверки
* @returns {Promise<boolean>} - true если порт свободен, false если занят
*/
function isPortAvailable(port) {
return new Promise((resolve) => {
const server = net.createServer();
server.listen(port, () => {
server.once('close', () => {
resolve(true);
});
server.close();
});
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
resolve(false);
} else {
resolve(false);
}
});
});
}
/**
* Находит свободный порт начиная с заданного
* @param {number} startPort - Начальный порт для поиска
* @param {number} maxAttempts - Максимальное количество попыток (по умолчанию 10)
* @returns {Promise<number>} - Свободный порт
*/
async function findFreePort(startPort, maxAttempts = 10) {
for (let i = 0; i < maxAttempts; i++) {
const port = startPort + i;
const available = await isPortAvailable(port);
if (available) {
return port;
}
}
throw new Error(
`Не удалось найти свободный порт в диапазоне ${startPort}-${startPort + maxAttempts - 1}`
);
}
/**
* Получить порт для сервера (проверяет занятость и находит свободный)
* @param {number} defaultPort - Порт по умолчанию
* @param {boolean} autoFind - Автоматически искать свободный если занят
* @returns {Promise<number>} - Порт для использования
*/
async function getServerPort(defaultPort, autoFind = true) {
const available = await isPortAvailable(defaultPort);
if (available) {
return defaultPort;
}
if (!autoFind) {
throw new Error(`Порт ${defaultPort} занят`);
}
console.log(`⚠️ Порт ${defaultPort} занят, ищем свободный...`);
const freePort = await findFreePort(defaultPort + 1);
console.log(`✅ Найден свободный порт: ${freePort}`);
return freePort;
}
module.exports = {
isPortAvailable,
findFreePort,
getServerPort,
};

View File

@ -9,7 +9,11 @@ const errorHandler = require('./middleware/errorHandler');
const { generalLimiter } = require('./middleware/rateLimiter'); const { generalLimiter } = require('./middleware/rateLimiter');
const app = express(); const app = express();
const PORT = process.env.PORT || 3001; const { getServerPort } = require('./lib/portFinder');
// Получаем порт (с автоматическим поиском свободного если нужно)
const DEFAULT_PORT = process.env.PORT || 3001;
let PORT = DEFAULT_PORT;
// Middleware // Middleware
app.use(simpleLogger); // Простое логирование запросов app.use(simpleLogger); // Простое логирование запросов
@ -63,10 +67,23 @@ app.use((req, res) => {
// Обработка ошибок (должен быть последним middleware) // Обработка ошибок (должен быть последним middleware)
app.use(errorHandler); app.use(errorHandler);
// Запуск сервера // Запуск сервера с автоматическим поиском свободного порта
app.listen(PORT, () => { (async () => {
winstonLogger.info(`🚀 Server is running on http://localhost:${PORT}`); try {
winstonLogger.info(`📡 Health check: http://localhost:${PORT}/api/health`); PORT = await getServerPort(DEFAULT_PORT, true);
winstonLogger.info(`📝 Environment: ${process.env.NODE_ENV || 'development'}`); app.listen(PORT, () => {
}); winstonLogger.info(`🚀 Server is running on http://localhost:${PORT}`);
winstonLogger.info(`📡 Health check: http://localhost:${PORT}/api/health`);
winstonLogger.info(`📝 Environment: ${process.env.NODE_ENV || 'development'}`);
if (PORT !== DEFAULT_PORT) {
winstonLogger.info(
` Используется порт ${PORT} вместо ${DEFAULT_PORT} (занят)`
);
}
});
} catch (error) {
winstonLogger.error(`❌ Ошибка запуска сервера: ${error.message}`);
process.exit(1);
}
})();

View File

@ -3,7 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev --turbopack", "dev": "node scripts/devWithPort.js",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",

View File

@ -0,0 +1,42 @@
// Скрипт для запуска Next.js dev с автоматическим поиском свободного порта
const { spawn } = require('child_process');
const { findFreePort } = require('./findPort');
const DEFAULT_PORT = 3000;
(async () => {
try {
const port = await findFreePort(DEFAULT_PORT);
if (port !== DEFAULT_PORT) {
console.log(`⚠️ Порт ${DEFAULT_PORT} занят, используем порт ${port}`);
} else {
console.log(`✅ Порт ${port} свободен`);
}
// Запускаем Next.js dev с найденным портом
const nextDev = spawn('npx', ['next', 'dev', '--turbopack', '-p', String(port)], {
stdio: 'inherit',
shell: true,
});
nextDev.on('error', (error) => {
console.error('Ошибка запуска Next.js:', error);
process.exit(1);
});
nextDev.on('exit', (code) => {
process.exit(code || 0);
});
// Обработка Ctrl+C
process.on('SIGINT', () => {
nextDev.kill('SIGINT');
});
} catch (error) {
console.error('Ошибка поиска порта:', error.message);
process.exit(1);
}
})();

View File

@ -0,0 +1,56 @@
// Скрипт для поиска свободного порта для Next.js
const net = require('net');
/**
* Проверяет, свободен ли порт
*/
function isPortAvailable(port) {
return new Promise((resolve) => {
const server = net.createServer();
server.listen(port, () => {
server.once('close', () => {
resolve(true);
});
server.close();
});
server.on('error', () => {
resolve(false);
});
});
}
/**
* Находит свободный порт начиная с заданного
*/
async function findFreePort(startPort, maxAttempts = 10) {
for (let i = 0; i < maxAttempts; i++) {
const port = startPort + i;
const available = await isPortAvailable(port);
if (available) {
return port;
}
}
throw new Error(
`Не удалось найти свободный порт в диапазоне ${startPort}-${startPort + maxAttempts - 1}`
);
}
// Если запущен как скрипт
if (require.main === module) {
const startPort = parseInt(process.argv[2]) || 3000;
findFreePort(startPort)
.then((port) => {
console.log(port);
process.exit(0);
})
.catch((error) => {
console.error(error.message);
process.exit(1);
});
}
module.exports = { isPortAvailable, findFreePort };