Ship Novya PWA with backend for deployable production use.
Fix production TypeScript builds and add DEPLOY.md so the app can be hosted from this repo. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Prepare local dev: free backend port and ensure MySQL is reachable.
|
||||
* Runs automatically via npm "predev" before `npm run dev`.
|
||||
*/
|
||||
import { execSync } from 'node:child_process';
|
||||
import net from 'node:net';
|
||||
import { platform } from 'node:os';
|
||||
import { dirname, join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const MYSQL_PORT = 3306;
|
||||
const BACKEND_LISTEN_PORT = 3001;
|
||||
const FRONTEND_PORT = 5173;
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const rootDir = join(__dirname, '..');
|
||||
const backendDir = join(rootDir, 'backend');
|
||||
|
||||
function log(msg) {
|
||||
console.log(`[prepare-dev] ${msg}`);
|
||||
}
|
||||
|
||||
function isPortOpen(port, host = '127.0.0.1') {
|
||||
return new Promise((resolve) => {
|
||||
const socket = net.createConnection({ port, host }, () => {
|
||||
socket.destroy();
|
||||
resolve(true);
|
||||
});
|
||||
socket.on('error', () => resolve(false));
|
||||
socket.setTimeout(1500, () => {
|
||||
socket.destroy();
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function freePort(port) {
|
||||
if (platform() === 'win32') {
|
||||
try {
|
||||
const out = execSync(`netstat -ano | findstr :${port}`, { encoding: 'utf8' });
|
||||
const pids = new Set();
|
||||
for (const line of out.split('\n')) {
|
||||
if (!line.includes('LISTENING')) continue;
|
||||
const parts = line.trim().split(/\s+/);
|
||||
const pid = parts[parts.length - 1];
|
||||
if (pid && pid !== '0') pids.add(pid);
|
||||
}
|
||||
for (const pid of pids) {
|
||||
log(`Stopping process ${pid} on port ${port}`);
|
||||
execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' });
|
||||
}
|
||||
} catch {
|
||||
// nothing listening
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
execSync(`lsof -ti:${port} | xargs kill -9 2>/dev/null`, { stdio: 'ignore', shell: true });
|
||||
} catch {
|
||||
// nothing listening
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureMysql() {
|
||||
if (await isPortOpen(MYSQL_PORT)) {
|
||||
log(`MySQL is reachable on port ${MYSQL_PORT}`);
|
||||
return;
|
||||
}
|
||||
|
||||
log(`MySQL not reachable on port ${MYSQL_PORT}; starting Docker Compose...`);
|
||||
try {
|
||||
execSync('docker compose -f docker-compose.dev.yml up -d', {
|
||||
cwd: backendDir,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch {
|
||||
console.error(
|
||||
'[prepare-dev] ERROR: MySQL is not running. Start it with:\n' +
|
||||
' cd backend && docker compose -f docker-compose.dev.yml up -d',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
for (let i = 0; i < 30; i += 1) {
|
||||
if (await isPortOpen(MYSQL_PORT)) {
|
||||
log('MySQL is ready');
|
||||
return;
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 1000));
|
||||
}
|
||||
|
||||
console.error('[prepare-dev] ERROR: MySQL did not become ready in time.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
freePort(BACKEND_LISTEN_PORT);
|
||||
freePort(FRONTEND_PORT);
|
||||
await ensureMysql();
|
||||
log(`Ready — backend will listen on port ${BACKEND_LISTEN_PORT}`);
|
||||
Reference in New Issue
Block a user