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
JavaScript
'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