UNPKG

fortify2-js

Version:

MOST POWERFUL JavaScript Security Library! Military-grade cryptography + 19 enhanced object methods + quantum-resistant algorithms + perfect TypeScript support. More powerful than Lodash with built-in security.

391 lines (387 loc) 14.7 kB
'use strict'; var childProcess = require('child_process'); var events = require('events'); var fs = require('fs'); var path = require('path'); var FileWatcher_config = require('../../const/FileWatcher.config.js'); var TypeScriptExecutor = require('./exec/TypeScriptExecutor.js'); /** * Hot Reloader for True Process Restart * Enables real hot reload by restarting the entire process */ class HotReloader extends events.EventEmitter { constructor(config = {}) { super(); this.isRunning = false; this.restartCount = 0; this.lastRestart = 0; this.config = { enabled: true, script: process.argv[1] || "index.js", args: process.argv.slice(2), env: { ...process.env }, cwd: process.cwd(), restartDelay: 500, maxRestarts: FileWatcher_config.DEFAULT_FW_CONFIG.maxRestarts, gracefulShutdownTimeout: 5000, verbose: false, typescript: { enabled: true, runner: "auto", runnerArgs: [], fallbackToNode: true, autoDetectRunner: true, enableRuntimeCompilation: true, }, ...config, }; // Initialize TypeScript executor this.tsExecutor = new TypeScriptExecutor.TypeScriptExecutor({ verbose: this.config.verbose, fallbackToNode: this.config.typescript?.fallbackToNode ?? true, compilerOptions: this.config.typescript?.compilerOptions, }); // Check for standalone executable this.checkStandaloneExecutable(); } /** * Check for standalone TypeScript executable */ checkStandaloneExecutable() { const executablePaths = [ path.join(process.cwd(), "dist", "tsr", process.platform === "win32" ? "tsr.cjs" : "tsr"), path.join(process.cwd(), "dist", "tsr", "tsr.cmd"), // Windows batch file path.join(__dirname, "executable", "bin", process.platform === "win32" ? "tsr.exe" : "tsr"), ]; for (const path of executablePaths) { if (fs.existsSync(path)) { this.standaloneExecutablePath = path; if (this.config.verbose) { console.log(`Found standalone TSR executable: ${path}`); } break; } } if (!this.standaloneExecutablePath && this.config.verbose) { console.log("No standalone TSR executable found, using TypeScript executor"); } } /** * Start the hot reloader */ async start() { if (!this.config.enabled) { console.log("Hot reloader disabled"); return; } if (this.isRunning) { console.log("Hot reloader already running"); return; } try { console.log("Starting hot reloader..."); await this.startChildProcess(); this.isRunning = true; console.log("Hot reloader started"); } catch (error) { // console.error("Failed to start hot reloader:", error.message); throw error; } } /** * Stop the hot reloader */ async stop() { if (!this.isRunning) return; try { // console.log("Stopping hot reloader..."); if (this.childProcess) { await this.stopChildProcess(); } this.isRunning = false; // console.log("Hot reloader stopped"); } catch (error) { console.error("Error stopping hot reloader:", error.message); } } /** * Restart the child process (hot reload) */ async restart() { if (!this.isRunning) { console.log("Hot reloader not running, starting..."); await this.start(); return; } // Check restart limits const now = Date.now(); if (now - this.lastRestart < this.config.restartDelay) { console.log("Restart too soon, waiting..."); return; } if (this.restartCount >= this.config.maxRestarts) { console.warn(`Maximum restarts (${this.config.maxRestarts}) reached`); return; } try { console.log("Hot reloading process..."); const startTime = Date.now(); // Stop current process if (this.childProcess) { await this.stopChildProcess(); } // Wait for restart delay await new Promise((resolve) => setTimeout(resolve, this.config.restartDelay)); // Start new process await this.startChildProcess(); const duration = Date.now() - startTime; this.restartCount++; this.lastRestart = now; this.emit("restart:completed", { duration, restartCount: this.restartCount, }); console.log(`Process hot reloaded (${duration}ms)`); } catch (error) { this.emit("restart:failed", { error: error.message }); console.error("Hot reload failed:", error.message); } } /** * Check if the script is a TypeScript file */ isTypeScriptFile(script) { return script.endsWith(".ts") || script.endsWith(".tsx"); } /** * Get the appropriate runtime and arguments for the script using TypeScript executor */ async getRuntimeConfig() { const isTS = this.isTypeScriptFile(this.config.script); const tsConfig = this.config.typescript; // If TypeScript is disabled or not a TS file, use default behavior if (!tsConfig?.enabled || !isTS) { const runtime = process.execPath.includes("bun") ? "bun" : "node"; return { runtime, args: [this.config.script, ...this.config.args], }; } // Try standalone executable first (most reliable) if (this.standaloneExecutablePath) { if (this.config.verbose) { console.log(`Using standalone TSR executable: ${this.standaloneExecutablePath}`); } // For Windows .cmd files, use node to execute the .cjs file if (this.standaloneExecutablePath.endsWith(".cmd")) { const cjsPath = this.standaloneExecutablePath.replace(".cmd", ".cjs"); return { runtime: "node", args: [cjsPath, this.config.script, ...this.config.args], }; } else if (this.standaloneExecutablePath.endsWith(".cjs")) { return { runtime: "node", args: [ this.standaloneExecutablePath, this.config.script, ...this.config.args, ], }; } else { // Unix executable return { runtime: this.standaloneExecutablePath, args: [this.config.script, ...this.config.args], }; } } try { // Use the TypeScript executor to determine the best execution method const result = await this.tsExecutor.executeTypeScript(this.config.script, this.config.args); if (result.success) { if (this.config.verbose) { console.log(`Using ${result.method}: ${result.runtime} ${result.args.join(" ")}`); } return { runtime: result.runtime, args: result.args, }; } else { throw new Error(result.error || "TypeScript execution failed"); } } catch (error) { if (this.config.verbose) { console.warn(`TypeScript executor failed: ${error.message}`); } // Fallback to node if TypeScript executor fails if (tsConfig.fallbackToNode) { if (this.config.verbose) { console.warn("Falling back to node (may fail for TypeScript files)"); } return { runtime: "node", args: [this.config.script, ...this.config.args], }; } else { throw error; } } } /** * Start child process */ async startChildProcess() { try { const { runtime, args } = await this.getRuntimeConfig(); if (this.config.verbose) { console.log(`Starting process with: ${runtime} ${args.join(" ")}`); } return new Promise((resolve, reject) => { this.childProcess = childProcess.spawn(runtime, args, { cwd: this.config.cwd, env: this.config.env, stdio: "inherit", detached: false, }); this.childProcess.on("spawn", () => { if (this.config.verbose) { console.log(`Child process started (PID: ${this.childProcess?.pid})`); } resolve(); }); this.childProcess.on("error", async (error) => { // Handle TypeScript runner not found error if (error.message.includes("ENOENT") && this.isTypeScriptFile(this.config.script)) { const tsConfig = this.config.typescript; if (tsConfig?.fallbackToNode) { console.warn(`TypeScript runner failed, falling back to node (this will likely fail for .ts files)`); console.warn(`Install a TypeScript runner: npm install -g tsx`); // Retry with node this.childProcess = childProcess.spawn("node", [this.config.script, ...this.config.args], { cwd: this.config.cwd, env: this.config.env, stdio: "inherit", detached: false, }); return; } } console.error("Child process error:", error.message); // Provide helpful error messages for common issues if (error.message.includes("ENOENT")) { try { const { runtime: errorRuntime } = await this.getRuntimeConfig(); console.error(`Runtime '${errorRuntime}' not found. Please install it:`); switch (errorRuntime) { case "tsx": console.error(` npm install -g tsx`); break; case "ts-node": console.error(` npm install -g ts-node`); break; case "bun": console.error(` Visit: https://bun.sh/docs/installation`); break; default: console.error(` Make sure '${errorRuntime}' is installed and available in PATH`); } } catch { console.error(` Make sure the runtime is installed and available in PATH`); } } reject(error); }); this.childProcess.on("exit", (code, signal) => { if (this.config.verbose) { console.log(`Child process exited (code: ${code}, signal: ${signal})`); } this.emit("process:exit", { code, signal }); // Auto-restart on unexpected exit if (this.isRunning && code !== 0 && !signal) { console.log("Unexpected exit, restarting..."); setTimeout(() => this.restart(), this.config.restartDelay); } }); }); } catch (error) { throw error; } } /** * Stop child process gracefully */ async stopChildProcess() { if (!this.childProcess) return; return new Promise((resolve) => { const timeout = setTimeout(() => { if (this.childProcess) { // console.log('Force killing child process...'); this.childProcess.kill("SIGKILL"); } resolve(); }, this.config.gracefulShutdownTimeout); this.childProcess.on("exit", () => { clearTimeout(timeout); resolve(); }); // Try graceful shutdown first if (this.config.verbose) { console.log("Sending SIGTERM to child process..."); } this.childProcess.kill("SIGTERM"); }); } /** * Get hot reloader status */ getStatus() { return { isRunning: this.isRunning, restartCount: this.restartCount, lastRestart: this.lastRestart, childPid: this.childProcess?.pid, config: this.config, }; } /** * Reset restart counter */ resetRestartCount() { this.restartCount = 0; this.lastRestart = 0; // console.log('Restart counter reset'); } /** * Update configuration */ updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; this.emit("config:updated", this.config); } /** * Check if process is healthy */ isHealthy() { return (this.isRunning && this.childProcess !== undefined && !this.childProcess.killed && this.restartCount < this.config.maxRestarts); } } exports.HotReloader = HotReloader; exports.default = HotReloader; //# sourceMappingURL=HotReloader.js.map