stabilizaiton work - projects rework for maintenanceability and ui livening
This commit is contained in:
146
src/Web/StellaOps.Web/scripts/serve.js
Normal file
146
src/Web/StellaOps.Web/scripts/serve.js
Normal file
@@ -0,0 +1,146 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// Wrapper around `ng serve` that resolves the best binding:
|
||||
// 1. https://stella-ops.local (port 443) — if hostname resolves and port is free
|
||||
// 2. https://localhost:10000 — always available fallback
|
||||
//
|
||||
// Additionally binds http://stella-ops.local (port 80) as a redirect to HTTPS
|
||||
// when the hostname resolves and port 80 is available.
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const http = require('http');
|
||||
const dns = require('dns');
|
||||
const net = require('net');
|
||||
const path = require('path');
|
||||
|
||||
const HOSTNAME = 'stella-ops.local';
|
||||
const HTTPS_PORT = 443;
|
||||
const HTTP_PORT = 80;
|
||||
const DEV_PORT = 10000;
|
||||
const SETUP_DOC = 'docs/technical/architecture/port-registry.md';
|
||||
|
||||
function isWindows() {
|
||||
return process.platform === 'win32';
|
||||
}
|
||||
|
||||
function hostsFilePath() {
|
||||
return isWindows()
|
||||
? 'C:\\Windows\\System32\\drivers\\etc\\hosts'
|
||||
: '/etc/hosts';
|
||||
}
|
||||
|
||||
function resolveHostnameIp(hostname) {
|
||||
return new Promise((resolve) => {
|
||||
dns.lookup(hostname, { family: 4 }, (err, address) => {
|
||||
if (err) return resolve(null);
|
||||
resolve(address);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function isPortAvailable(port, ip) {
|
||||
return new Promise((resolve) => {
|
||||
const server = net.createServer();
|
||||
server.once('error', () => resolve(false));
|
||||
server.once('listening', () => {
|
||||
server.close(() => resolve(true));
|
||||
});
|
||||
server.listen(port, ip);
|
||||
});
|
||||
}
|
||||
|
||||
function startHttpRedirect(httpsHost, httpsPort, bindIp) {
|
||||
return new Promise((resolve) => {
|
||||
const server = http.createServer((req, res) => {
|
||||
const location = `https://${httpsHost}${httpsPort === 443 ? '' : ':' + httpsPort}${req.url}`;
|
||||
res.writeHead(301, { Location: location });
|
||||
res.end();
|
||||
});
|
||||
|
||||
server.on('error', (err) => {
|
||||
console.warn(` HTTP redirect on port ${HTTP_PORT} failed: ${err.message}`);
|
||||
resolve(false);
|
||||
});
|
||||
|
||||
server.listen(HTTP_PORT, bindIp, () => {
|
||||
console.log(` HTTP redirect active: http://${HOSTNAME} -> https://${httpsHost}${httpsPort === 443 ? '' : ':' + httpsPort}`);
|
||||
console.log(` Bound to ${bindIp}:${HTTP_PORT}`);
|
||||
resolve(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const extraArgs = process.argv.slice(2);
|
||||
const resolvedIp = await resolveHostnameIp(HOSTNAME);
|
||||
const hostnameOk = !!resolvedIp;
|
||||
const port443Free = hostnameOk ? await isPortAvailable(HTTPS_PORT, resolvedIp) : false;
|
||||
const port80Free = hostnameOk ? await isPortAvailable(HTTP_PORT, resolvedIp) : false;
|
||||
|
||||
let host, port;
|
||||
|
||||
if (hostnameOk && port443Free) {
|
||||
host = HOSTNAME;
|
||||
port = HTTPS_PORT;
|
||||
console.log('');
|
||||
console.log(` ${HOSTNAME} resolves to ${resolvedIp}; port ${HTTPS_PORT} is available.`);
|
||||
console.log(` Dev server binding to https://${HOSTNAME}`);
|
||||
console.log(` Also accessible at https://localhost:${DEV_PORT}`);
|
||||
|
||||
if (port80Free) {
|
||||
const redirectOk = await startHttpRedirect(HOSTNAME, HTTPS_PORT, resolvedIp);
|
||||
if (redirectOk) {
|
||||
console.log(` Also accessible at http://${HOSTNAME} (redirects to HTTPS)`);
|
||||
} else {
|
||||
console.warn(` Failed to start HTTP redirect on ${resolvedIp}:${HTTP_PORT}`);
|
||||
}
|
||||
} else {
|
||||
console.warn(` Port ${HTTP_PORT} on ${resolvedIp} is unavailable; skipping http://${HOSTNAME} redirect.`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
} else if (hostnameOk) {
|
||||
host = HOSTNAME;
|
||||
port = DEV_PORT;
|
||||
console.warn('');
|
||||
console.warn(` ${HOSTNAME} resolves to ${resolvedIp} but port ${HTTPS_PORT} is unavailable`);
|
||||
console.warn(` (requires elevated privileges or is already in use).`);
|
||||
console.warn(` Dev server binding to https://${HOSTNAME}:${DEV_PORT}`);
|
||||
console.warn('');
|
||||
} else {
|
||||
host = 'localhost';
|
||||
port = DEV_PORT;
|
||||
console.warn('');
|
||||
console.warn(` WARNING: ${HOSTNAME} does not resolve.`);
|
||||
console.warn(` Dev server binding to https://localhost:${DEV_PORT}`);
|
||||
console.warn('');
|
||||
console.warn(` To use https://${HOSTNAME}, add to ${hostsFilePath()}:`);
|
||||
console.warn('');
|
||||
console.warn(` 127.1.0.1 ${HOSTNAME}`);
|
||||
console.warn('');
|
||||
console.warn(` See ${SETUP_DOC} for the full list of hostnames.`);
|
||||
console.warn('');
|
||||
}
|
||||
|
||||
runNgServe(host, port, extraArgs);
|
||||
}
|
||||
|
||||
function runNgServe(host, port, extraArgs) {
|
||||
const cwd = path.resolve(__dirname, '..');
|
||||
const ngCli = path.resolve(cwd, 'node_modules', '@angular', 'cli', 'bin', 'ng.js');
|
||||
// Pass the hostname (not IP) so Vite resolves it for both HTTPS and HMR websocket.
|
||||
// The hostname resolves to a unique loopback IP via the hosts file, so ports
|
||||
// won't collide with other services.
|
||||
const args = ['serve', '--host', host, '--port', String(port), '--ssl', ...extraArgs];
|
||||
|
||||
console.log(` ng serve binding to ${host}:${port}`);
|
||||
|
||||
const child = spawn(process.execPath, [ngCli, ...args], {
|
||||
stdio: 'inherit',
|
||||
cwd,
|
||||
});
|
||||
|
||||
child.on('exit', (code) => process.exit(code ?? 1));
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user