datacops-cms
Version:
A modern, extensible CMS built with Next.js and Prisma.
110 lines (97 loc) • 3.41 kB
JavaScript
const { spawn, execSync } = require("child_process");
const chokidar = require("chokidar");
const detect = require("detect-port").default;
const path = require("path");
const os = require("os");
// The dev server port (change if you use a different port)
const DEV_PORT = 3000;
let devServer = null;
function startDev() {
devServer = spawn("pnpm", ["dev-turbo"], { stdio: "inherit", shell: true });
devServer.on("exit", code => {
if (code !== 0 && code !== null) {
console.error(`Dev server exited with code ${code}`);
}
});
}
async function stopDev() {
if (!devServer) return;
await new Promise(resolve => {
devServer.on("exit", resolve);
devServer.kill();
devServer = null;
});
await waitForPortToBeFree(DEV_PORT, 20000); // Wait up to 20s
}
// On Windows, force-kill any process listening on the port
async function killProcessOnPort(port) {
if (os.platform() !== "win32") return; // Only do this on Windows
try {
const output = execSync(`netstat -ano | findstr :${port}`).toString();
// Look for a LISTENING entry
const match = output.match(/LISTENING\s+(\d+)/);
if (match) {
const pid = match[1];
console.log(`⚠️ Forcibly killing process ${pid} on port ${port}...`);
execSync(`taskkill /PID ${pid} /F`);
}
} catch (err) {
// No process found, that's fine
}
}
async function waitForPortToBeFree(port, timeout = 20000) {
const start = Date.now();
let killed = false;
while (Date.now() - start < timeout) {
const freePort = await detect(port);
if (freePort === port) return;
// If not free after 10s, force kill (Windows only)
if (!killed && Date.now() - start > 10000) {
await killProcessOnPort(port);
killed = true;
}
await new Promise(res => setTimeout(res, 250));
}
throw new Error(`Port ${port} not free after ${timeout}ms!`);
}
function runCmd(cmd, args) {
return new Promise((resolve, reject) => {
const p = spawn(cmd, args, { stdio: "inherit", shell: true });
p.on("close", code => (code === 0 ? resolve() : reject(code)));
});
}
async function updatePrisma() {
console.log("🛠️ Updating Prisma schema...");
await runCmd("npx", ["tsx", "scripts/generate-prisma-schema.ts"]);
await runCmd("pnpm", ["prisma", "generate"]);
await runCmd("pnpm", ["prisma", "db", "push", "--accept-data-loss"]);
console.log("✅ Prisma updated!");
}
// Debounce/lock for rebuilds
let rebuilding = false;
function safeRebuild(eventType, file) {
if (rebuilding) return;
rebuilding = true;
(async () => {
console.log(`🔄 Detected content-type ${eventType} in:`, file);
await stopDev();
try {
await updatePrisma();
} catch (e) {
console.error("❌ Error updating Prisma!", e);
}
startDev();
// Wait 5s before next allowed rebuild
setTimeout(() => { rebuilding = false }, 5000);
})();
}
// Watch only for real file events
const watcher = chokidar.watch(path.join(process.cwd(), "content-types"), {
ignoreInitial: true,
awaitWriteFinish: { stabilityThreshold: 500 },
});
["add", "change", "unlink"].forEach(eventType => {
watcher.on(eventType, file => safeRebuild(eventType, file));
});
console.log("👀 Watching /content-types for add/change/unlink events...");
startDev();