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.
494 lines (490 loc) • 19.7 kB
JavaScript
'use strict';
var FileWatcher = require('../../service/Reload/FileWatcher.js');
var HotReloader = require('../../service/Reload/HotReloader.js');
var Logger = require('../../utils/Logger.js');
var TypeScriptChecker = require('./typescript/TypeScriptChecker.js');
/***************************************************************************
* FortifyJS - Secure Array Types
*
* This file contains type definitions for the SecureArray architecture
*
* @author Nehonix
* @license MIT
*
* Copyright (c) 2025 Nehonix. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
***************************************************************************** */
/**
* FileWatcherManager - Handles all file watching and hot reload operations for FastApi.ts
* Manages file monitoring, hot reload functionality, and server restart coordination
*/
class FileWatcherManager {
constructor(options, dependencies) {
this.isMainProcess = true;
this.options = options;
this.dependencies = dependencies;
if (this.options.fileWatcher?.enabled) {
this.initializeFileWatcher();
}
// Initialize TypeScript checker if enabled
if (this.options.fileWatcher?.typeCheck?.enabled) {
this.initializeTypeScriptChecker();
}
}
/**
* Initialize file watcher and hot reloader
*/
initializeFileWatcher() {
if (!this.options.fileWatcher?.enabled)
return;
Logger.logger.debug("fileWatcher", "Initializing file watcher...");
// Check if we're in the main process or a child process
this.isMainProcess = !process.env.FORTIFY_CHILD_PROCESS;
if (this.isMainProcess) {
console.log("reloading...");
// Main process: Initialize hot reloader for true process restart
this.hotReloader = new HotReloader.HotReloader({
enabled: true,
script: process.argv[1] || "index.js",
args: process.argv.slice(2),
env: process.env, // Don't set FORTIFY_CHILD_PROCESS here - it will be set when spawning child processes
cwd: process.cwd(),
restartDelay: this.options.fileWatcher.restartDelay || 500,
maxRestarts: this.options.fileWatcher.maxRestarts || 10,
gracefulShutdownTimeout: 5000,
verbose: this.options.fileWatcher.verbose || false,
typescript: this.options.fileWatcher.typescript || {
enabled: true,
runner: "auto",
runnerArgs: [],
fallbackToNode: true,
autoDetectRunner: true,
},
}, this.options);
// Initialize file watcher for the main process
this.fileWatcher = new FileWatcher.UltraFastFileWatcher(this.options.fileWatcher);
}
else {
// Child process: Don't initialize file watcher to avoid conflicts
Logger.logger.debug("fileWatcher", "Running in child process mode (hot reload enabled)");
}
Logger.logger.debug("fileWatcher", "FW initialized");
}
/**
* Initialize TypeScript checker
*/
initializeTypeScriptChecker() {
if (!this.options.fileWatcher?.typeCheck?.enabled)
return;
Logger.logger.debug("typescript", "Initializing TypeScript checker...");
const typeCheckConfig = {
enabled: true,
configFile: this.options.fileWatcher.typeCheck.configFile,
watchMode: false,
checkOnSave: this.options.fileWatcher.typeCheck.checkOnSave ?? true,
showWarnings: this.options.fileWatcher.typeCheck.showWarnings ?? true,
showInfos: this.options.fileWatcher.typeCheck.showInfos ?? false,
maxErrors: this.options.fileWatcher.typeCheck.maxErrors ?? 50,
excludePatterns: [
"node_modules",
"dist",
"build",
".git",
...(this.options.fileWatcher.typeCheck.excludePatterns || []),
],
includePatterns: this.options.fileWatcher.typeCheck
.includePatterns || ["**/*.ts", "**/*.tsx"],
verbose: this.options.fileWatcher.typeCheck.verbose ?? false,
};
this.typeScriptChecker = new TypeScriptChecker.TypeScriptChecker(typeCheckConfig);
Logger.logger.debug("typescript", "TypeScript checker initialized");
}
/**
* Get file watcher instance
*/
getFileWatcher() {
return this.fileWatcher;
}
/**
* Get hot reloader instance
*/
getHotReloader() {
return this.hotReloader;
}
/**
* Check if file watcher is enabled
*/
isFileWatcherEnabled() {
return this.options.fileWatcher?.enabled === true;
}
/**
* Check if running in main process
*/
isInMainProcess() {
return this.isMainProcess;
}
/**
* Set HTTP server reference for restart operations
*/
setHttpServer(server) {
this.httpServer = server;
}
/**
* Start file watcher for auto-reload (main process only)
*/
async startFileWatcher() {
if (!this.fileWatcher)
return;
try {
Logger.logger.debug("fileWatcher", " Starting file watcher for auto-reload...");
// Setup file watcher event handlers
this.setupFileWatcherEventHandlers();
// Start watching with restart callback
await this.fileWatcher.startWatching(async () => {
await this.restartServer();
});
Logger.logger.debug("fileWatcher", "File watcher started successfully");
}
catch (error) {
Logger.logger.error("fileWatcher", "Failed to start file watcher:", error.message);
}
}
/**
* Start file watcher with hot reload (main process)
*/
async startFileWatcherWithHotReload() {
if (!this.fileWatcher || !this.hotReloader)
return;
try {
Logger.logger.debug("fileWatcher", "Starting file watcher with hot reload...");
// Setup file watcher event handlers for hot reload
this.setupHotReloadEventHandlers();
// Start watching with hot reload callback
await this.fileWatcher.startWatching(async () => {
await this.triggerHotReload();
});
Logger.logger.debug("fileWatcher", "File watcher with hot reload started successfully");
}
catch (error) {
Logger.logger.error("fileWatcher", "Failed to start file watcher with hot reload:", error.message);
}
}
/**
* Setup hot reload event handlers
*/
setupHotReloadEventHandlers() {
if (!this.fileWatcher || !this.hotReloader)
return;
this.fileWatcher.on("file:changed", async (event) => {
if (this.options.fileWatcher?.verbose) {
Logger.logger.debug("fileWatcher", `File changed: ${event.filename}`);
}
// Automatically check TypeScript if enabled and file is a TypeScript file
await this.handleTypeScriptCheck(event);
});
this.fileWatcher.on("restart:starting", (event) => {
Logger.logger.debug("fileWatcher", `Hot reloading due to: ${event.filename}`);
});
this.hotReloader.on("restart:completed", (data) => {
Logger.logger.debug("fileWatcher", `Hot reload completed in ${data.duration}ms`);
});
this.hotReloader.on("restart:failed", (data) => {
Logger.logger.error("fileWatcher", `Hot reload failed: ${data.error}`);
});
}
/**
* Setup file watcher event handlers
*/
setupFileWatcherEventHandlers() {
if (!this.fileWatcher)
return;
this.fileWatcher.on("file:changed", async (event) => {
if (this.options.fileWatcher?.verbose) {
Logger.logger.debug("fileWatcher", `File changed: ${event.filename}`);
}
// Automatically check TypeScript if enabled and file is a TypeScript file
await this.handleTypeScriptCheck(event);
});
this.fileWatcher.on("restart:starting", (event) => {
Logger.logger.debug("fileWatcher", `Restarting due to: ${event.filename}`);
});
this.fileWatcher.on("restart:completed", (data) => {
Logger.logger.debug("fileWatcher", `Restart completed in ${data.duration}ms`);
});
this.fileWatcher.on("restart:failed", (data) => {
Logger.logger.error("fileWatcher", `Restart failed: ${data.error}`);
});
}
/**
* Trigger hot reload (true process restart)
*/
async triggerHotReload() {
if (!this.hotReloader) {
Logger.logger.warn("fileWatcher", "Hot reloader not available, falling back to regular restart");
await this.restartServer();
return;
}
try {
// Check TypeScript types before restarting if enabled
if (this.typeScriptChecker &&
this.options.fileWatcher?.typeCheck?.checkBeforeRestart) {
Logger.logger.debug("typescript", "Checking TypeScript types before restart...");
const typeCheckResult = await this.typeScriptChecker.checkFiles();
if (!typeCheckResult.success &&
this.options.fileWatcher?.typeCheck?.failOnError) {
Logger.logger.error("typescript", "❌ TypeScript errors found, skipping restart");
Logger.logger.error("typescript", `Found ${typeCheckResult.errors.length} errors`);
return;
}
if (typeCheckResult.errors.length > 0) {
Logger.logger.warn("typescript", `TypeScript errors found but continuing restart (${typeCheckResult.errors.length} errors)`);
}
}
Logger.logger.debug("fileWatcher", "Triggering hot reload (process restart)...");
await this.hotReloader.restart();
}
catch (error) {
Logger.logger.error("fileWatcher", "Hot reload failed:", error.message);
// Fallback to regular restart
Logger.logger.debug("fileWatcher", "Falling back to regular restart...");
await this.restartServer();
}
}
/**
* Restart server (for file watcher) with hot reload
* Uses HotReloader for true process restart with TypeScript support
*/
async restartServer() {
try {
Logger.logger.info("fileWatcher", "🔄 Hot reloading server...");
// Use hot reloader for true process restart (supports TypeScript)
if (this.hotReloader) {
await this.hotReloader.restart();
return;
}
// This should not happen if FileWatcherManager is properly initialized
Logger.logger.error("fileWatcher", "❌ HotReloader not available - this indicates a configuration issue");
throw new Error("HotReloader not initialized. Cannot perform hot reload.");
}
catch (error) {
Logger.logger.error("fileWatcher", "Server hot reload failed:", error.message);
throw error;
}
}
/**
* Stop file watcher
*/
async stopFileWatcher() {
if (this.fileWatcher) {
await this.fileWatcher.stopWatching();
Logger.logger.debug("fileWatcher", "File watcher stopped");
}
}
/**
* Get file watcher status
*/
getFileWatcherStatus() {
return this.fileWatcher?.getStatus() || null;
}
/**
* Get file watcher restart stats
*/
getFileWatcherStats() {
return this.fileWatcher?.getRestartStats() || null;
}
/**
* Check TypeScript files for errors
*/
async checkTypeScript(files) {
if (!this.typeScriptChecker) {
return {
success: false,
errors: [
{
file: "system",
line: 0,
column: 0,
message: "TypeScript checker not initialized",
code: 0,
severity: "error",
category: "system",
source: "FileWatcherManager",
},
],
warnings: [],
totalFiles: 0,
checkedFiles: [],
duration: 0,
timestamp: new Date(),
};
}
return await this.typeScriptChecker.checkFiles(files);
}
/**
* Get TypeScript checker status
*/
getTypeScriptStatus() {
return this.typeScriptChecker?.getStatus() || null;
}
/**
* Enable TypeScript checking
*/
enableTypeScriptChecking() {
if (this.typeScriptChecker) {
this.typeScriptChecker.setEnabled(true);
}
else {
Logger.logger.warn("typescript", "TypeScript checker not initialized");
}
}
/**
* Disable TypeScript checking
*/
disableTypeScriptChecking() {
if (this.typeScriptChecker) {
this.typeScriptChecker.setEnabled(false);
}
else {
Logger.logger.warn("typescript", "TypeScript checker not initialized");
}
}
/**
* Handle automatic TypeScript checking when files change
*/
async handleTypeScriptCheck(event) {
if (!this.typeScriptChecker ||
!this.options.fileWatcher?.typeCheck?.checkOnSave) {
return;
}
// Only check TypeScript files
const filename = event.filename || event.path || "";
if (!filename.endsWith(".ts") && !filename.endsWith(".tsx")) {
return;
}
try {
Logger.logger.debug("typescript", `Checking TypeScript for: ${filename}`);
const result = await this.typeScriptChecker.checkFiles([filename]);
if (result.errors.length > 0) {
Logger.logger.error("typescript", `❌ TypeScript errors in ${filename}:`);
result.errors.slice(0, 3).forEach((error) => {
Logger.logger.error("typescript", ` Line ${error.line}: ${error.message} (TS${error.code})`);
});
if (result.errors.length > 3) {
Logger.logger.error("typescript", ` ... and ${result.errors.length - 3} more errors`);
}
}
else {
if (this.options.fileWatcher?.typeCheck?.verbose) {
Logger.logger.info("typescript", `✔ No TypeScript errors in ${filename}`);
}
}
if (result.warnings.length > 0 &&
this.options.fileWatcher?.typeCheck?.showWarnings) {
Logger.logger.warn("typescript", `TypeScript warnings in ${filename}:`);
result.warnings.slice(0, 2).forEach((warning) => {
Logger.logger.warn("typescript", ` Line ${warning.line}: ${warning.message} (TS${warning.code})`);
});
if (result.warnings.length > 2) {
Logger.logger.warn("typescript", ` ... and ${result.warnings.length - 2} more warnings`);
}
}
}
catch (error) {
Logger.logger.warn("typescript", `Failed to check TypeScript for ${filename}: ${error.message}`);
}
}
/**
* Add file watcher monitoring endpoints
*/
addFileWatcherMonitoringEndpoints(basePoint) {
if (!this.fileWatcher || !this.options.fileWatcher?.enabled)
return;
// File watcher status endpoint
this.dependencies.app.get(basePoint + "/health/filewatcher", async (req, res) => {
try {
const status = this.getFileWatcherStatus();
const stats = this.getFileWatcherStats();
res.json({
timestamp: new Date().toISOString(),
fileWatcher: {
status,
stats,
},
});
}
catch (error) {
res.status(500).json({
error: "Failed to get file watcher status",
message: error.message,
});
}
});
// File watcher control endpoint
this.dependencies.app.post(basePoint + "/filewatcher/control", async (req, res) => {
try {
const { action } = req.body;
if (action === "stop") {
await this.stopFileWatcher();
res.json({
success: true,
message: "File watcher stopped",
});
}
else if (action === "start") {
await this.startFileWatcher();
res.json({
success: true,
message: "File watcher started",
});
}
else if (action === "restart") {
await this.stopFileWatcher();
await this.startFileWatcher();
res.json({
success: true,
message: "File watcher restarted",
});
}
else if (action === "reset-stats") {
if (this.fileWatcher) {
this.fileWatcher.resetStats();
}
res.json({
success: true,
message: "File watcher stats reset",
});
}
else {
res.status(400).json({
error: "Invalid action. Use 'start', 'stop', 'restart', or 'reset-stats'",
});
}
}
catch (error) {
res.status(500).json({
error: "Failed to control file watcher",
message: error.message,
});
}
});
}
}
exports.FileWatcherManager = FileWatcherManager;
//# sourceMappingURL=FileWatcherManager.js.map