115 lines
4.1 KiB
JavaScript
115 lines
4.1 KiB
JavaScript
/**
|
|
* Dev environment startup script.
|
|
*
|
|
* Starts the local Postgres container, waits for it to be ready, runs
|
|
* migrations + seed, then spawns `next dev` bound to 0.0.0.0 so the app
|
|
* is reachable from other devices on the LAN.
|
|
*
|
|
* Usage: pnpm dev:local
|
|
*/
|
|
|
|
import { execSync, spawn } from "child_process";
|
|
import net from "net";
|
|
import os from "os";
|
|
import { fileURLToPath } from "url";
|
|
|
|
/** Return the first LAN IPv4 address (prefers 192.168.x / 10.x ranges). */
|
|
function getLanIp() {
|
|
const all = Object.values(os.networkInterfaces())
|
|
.flat()
|
|
.filter((n) => n.family === "IPv4" && !n.internal)
|
|
.map((n) => n.address);
|
|
return all.find((a) => a.startsWith("192.168.") || a.startsWith("10.")) ?? all[0] ?? "unknown";
|
|
}
|
|
|
|
/** Poll TCP host:port until it accepts a connection or the timeout expires. */
|
|
function waitForTcp(host, port, timeoutMs = 30_000) {
|
|
return new Promise((resolve, reject) => {
|
|
const deadline = Date.now() + timeoutMs;
|
|
|
|
function attempt() {
|
|
const sock = net.createConnection({ host, port });
|
|
sock.on("connect", () => {
|
|
sock.destroy();
|
|
resolve();
|
|
});
|
|
sock.on("error", () => {
|
|
sock.destroy();
|
|
if (Date.now() >= deadline) {
|
|
reject(
|
|
new Error(
|
|
`Postgres not ready on ${host}:${port} after ${timeoutMs / 1000}s. ` +
|
|
"Is Docker running? Check: docker compose -f docker-compose.dev.yaml logs",
|
|
),
|
|
);
|
|
} else {
|
|
setTimeout(attempt, 500);
|
|
}
|
|
});
|
|
}
|
|
|
|
attempt();
|
|
});
|
|
}
|
|
|
|
/** Run a shell command synchronously, streaming output to the terminal. */
|
|
function run(cmd) {
|
|
execSync(cmd, { stdio: "inherit", shell: true });
|
|
}
|
|
|
|
export async function main() {
|
|
// 1. Start DB container (idempotent — safe to call when already running)
|
|
console.log("\n▸ Starting database container...");
|
|
run("docker compose -f docker-compose.dev.yaml up -d");
|
|
|
|
// 2. Wait for Postgres to accept connections
|
|
process.stdout.write("▸ Waiting for Postgres");
|
|
const tick = setInterval(() => process.stdout.write("."), 500);
|
|
try {
|
|
await waitForTcp("127.0.0.1", 5432);
|
|
} finally {
|
|
clearInterval(tick);
|
|
process.stdout.write(" ready\n");
|
|
}
|
|
|
|
// 3. Apply any pending migrations (no-op if already current)
|
|
console.log("▸ Running migrations...");
|
|
run("pnpm db:migrate");
|
|
|
|
// 4. Seed default data (idempotent)
|
|
console.log("▸ Seeding...");
|
|
run("pnpm db:seed");
|
|
|
|
// 5. Print access URLs before the Next.js banner appears
|
|
const lanIp = getLanIp();
|
|
console.log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
|
|
console.log(` Local → http://localhost:3000`);
|
|
console.log(` LAN → http://${lanIp}:3000 (phone on same WiFi)`);
|
|
console.log(` HTTPS → https://dev.ginnoir.com (push/PWA — needs Caddy snippet)`);
|
|
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
|
|
|
|
// 6. Spawn Next.js dev server bound to all interfaces.
|
|
// HMR handles most code changes without a restart.
|
|
// Restart needed for: .env changes, next.config.ts changes.
|
|
// Pass command as a single string with no args array — shell handles .cmd
|
|
// resolution on Windows and the empty args avoids DEP0190.
|
|
const next = spawn("pnpm run dev:network", [], { stdio: "inherit", shell: true });
|
|
|
|
// Forward Ctrl+C / SIGTERM to the child so it shuts down cleanly.
|
|
const forward = (sig) => () => next.kill(sig);
|
|
process.on("SIGINT", forward("SIGINT"));
|
|
process.on("SIGTERM", forward("SIGTERM"));
|
|
|
|
await new Promise((resolve) => next.on("exit", (code) => resolve(code)));
|
|
process.exit(0);
|
|
}
|
|
|
|
// Run when invoked directly (not imported by dev-reset.mjs)
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
if (process.argv[1] === __filename) {
|
|
main().catch((err) => {
|
|
console.error("\ndev startup failed:", err.message);
|
|
process.exit(1);
|
|
});
|
|
}
|