UNPKG

create-prisma-php-app

Version:

Prisma-PHP: A Revolutionary Library Bridging PHP with Prisma ORM

240 lines (210 loc) 6.29 kB
import { fileURLToPath } from "url"; import { dirname } from "path"; import chokidar, { FSWatcher } from "chokidar"; import { spawn, ChildProcess, execFile } from "child_process"; import { relative } from "path"; export const PUBLIC_DIR = "public"; export const SRC_DIR = "src"; export const APP_DIR = "src/app"; export function getFileMeta() { const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); return { __filename, __dirname }; } export type WatchEvent = "add" | "addDir" | "change" | "unlink" | "unlinkDir"; export const DEFAULT_IGNORES: (string | RegExp)[] = [ /(^|[\/\\])\../, "**/node_modules/**", "**/vendor/**", "**/dist/**", "**/build/**", "**/.cache/**", "**/*.log", "**/*.tmp", "**/*.swp", ]; export const DEFAULT_AWF = { stabilityThreshold: 300, pollInterval: 100 }; export function createSrcWatcher( root: string, opts: { exts?: string[]; onEvent: (event: WatchEvent, absPath: string, relPath: string) => void; ignored?: (string | RegExp)[]; awaitWriteFinish?: { stabilityThreshold: number; pollInterval: number }; logPrefix?: string; usePolling?: boolean; interval?: number; } ): FSWatcher { const { exts, onEvent, ignored = DEFAULT_IGNORES, awaitWriteFinish = DEFAULT_AWF, logPrefix = "watch", usePolling = true, } = opts; const watcher = chokidar.watch(root, { ignoreInitial: true, persistent: true, ignored, awaitWriteFinish, usePolling, interval: opts.interval ?? 1000, }); watcher .on("ready", () => { console.log(`[${logPrefix}] Watching ${root.replace(/\\/g, "/")}/**/*`); }) .on("all", (event: WatchEvent, filePath: string) => { if (exts && exts.length > 0) { const ok = exts.some((ext) => filePath.endsWith(ext)); if (!ok) return; } const rel = relative(root, filePath).replace(/\\/g, "/"); if (event === "add" || event === "change" || event === "unlink") { onEvent(event, filePath, rel); } }) .on("error", (err) => console.error(`[${logPrefix}] Error:`, err)); return watcher; } export class DebouncedWorker { private timer: NodeJS.Timeout | null = null; private running = false; private queued = false; constructor( private work: () => Promise<void> | void, private debounceMs = 350, private name = "worker" ) {} schedule(reason?: string) { if (reason) console.log(`[${this.name}] ${reason} → scheduled`); if (this.timer) clearTimeout(this.timer); this.timer = setTimeout(() => { this.timer = null; this.runNow().catch(() => {}); }, this.debounceMs); } private async runNow() { if (this.running) { this.queued = true; return; } this.running = true; try { await this.work(); } catch (err) { console.error(`[${this.name}] error:`, err); } finally { this.running = false; if (this.queued) { this.queued = false; this.runNow().catch(() => {}); } } } } export function createRestartableProcess(spec: { name: string; cmd: string; args?: string[]; stdio?: "inherit" | [any, any, any]; gracefulSignal?: NodeJS.Signals; forceKillAfterMs?: number; windowsKillTree?: boolean; onStdout?: (buf: Buffer) => void; onStderr?: (buf: Buffer) => void; }) { const { name, cmd, args = [], stdio = ["ignore", "pipe", "pipe"], gracefulSignal = "SIGINT", forceKillAfterMs = 2000, windowsKillTree = true, onStdout, onStderr, } = spec; let child: ChildProcess | null = null; function start() { console.log(`[${name}] Starting: ${cmd} ${args.join(" ")}`.trim()); child = spawn(cmd, args, { stdio, windowsHide: true }); child.stdout?.on("data", (buf: Buffer) => { if (onStdout) onStdout(buf); else process.stdout.write(`[${name}] ${buf.toString()}`); }); child.stderr?.on("data", (buf: Buffer) => { if (onStderr) onStderr(buf); else process.stderr.write(`[${name}:err] ${buf.toString()}`); }); child.on("close", (code) => { console.log(`[${name}] Exited with code ${code}`); }); child.on("error", (err) => { console.error(`[${name}] Failed to start:`, err); }); return child; } function killOnWindows(pid: number): Promise<void> { return new Promise((resolve) => { const cp = execFile("taskkill", ["/F", "/T", "/PID", String(pid)], () => resolve() ); cp.on("error", () => resolve()); }); } async function stop(): Promise<void> { if (!child || child.killed) return; const pid = child.pid!; console.log(`[${name}] Stopping…`); if (process.platform === "win32" && windowsKillTree) { await killOnWindows(pid); child = null; return; } await new Promise<void>((resolve) => { const done = () => resolve(); child!.once("close", done).once("exit", done).once("disconnect", done); try { child!.kill(gracefulSignal); } catch { resolve(); } setTimeout(() => { if (child && !child.killed) { try { process.kill(pid, "SIGKILL"); } catch {} } }, forceKillAfterMs); }); child = null; } async function restart(reason?: string) { if (reason) console.log(`[${name}] Restart requested: ${reason}`); await stop(); return start(); } function getChild() { return child; } return { start, stop, restart, getChild }; } export function onExit(fn: () => Promise<void> | void) { const wrap = (sig: string) => async () => { console.log(`[proc] Received ${sig}, shutting down…`); try { await fn(); } finally { process.exit(0); } }; process.on("SIGINT", wrap("SIGINT")); process.on("SIGTERM", wrap("SIGTERM")); process.on("uncaughtException", async (err) => { console.error("[proc] Uncaught exception:", err); await wrap("uncaughtException")(); }); }