UNPKG

@nodedaemon/core

Version:

Production-ready Node.js process manager with zero external dependencies

263 lines 9.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FileWatcher = void 0; const fs_1 = require("fs"); const path_1 = require("path"); const crypto_1 = require("crypto"); const events_1 = require("events"); const helpers_1 = require("../utils/helpers"); const constants_1 = require("../utils/constants"); class FileWatcher extends events_1.EventEmitter { watchers = new Map(); watchedFiles = new Map(); debouncedHandlers = new Map(); ignorePatterns = []; isWatching = false; constructor() { super(); this.setupIgnorePatterns(); } setupIgnorePatterns() { this.ignorePatterns = constants_1.FILE_WATCH_IGNORE.map(pattern => { const regexPattern = pattern .replace(/\./g, '\\.') .replace(/\*/g, '.*') .replace(/\?/g, '.'); return new RegExp(regexPattern); }); } watch(paths, options = {}) { // Don't unwatch existing paths, just add new ones // This was the bug - it was unwatching all previous paths! const pathsArray = Array.isArray(paths) ? paths : [paths]; const { ignoreInitial = true, recursive = true, ignored = [] } = options; if (ignored.length > 0) { const additionalPatterns = ignored.map(pattern => { const regexPattern = pattern .replace(/\./g, '\\.') .replace(/\*/g, '.*') .replace(/\?/g, '.'); return new RegExp(regexPattern); }); this.ignorePatterns.push(...additionalPatterns); } pathsArray.forEach(watchPath => { this.watchPath((0, path_1.resolve)(watchPath), recursive); }); if (!ignoreInitial) { this.scanInitialFiles(pathsArray); } this.isWatching = true; } watchPath(path, recursive) { try { if (!(0, helpers_1.isDirectory)(path) && !(0, helpers_1.isFile)(path)) { return; } console.log(`[FileWatcher] Watching path: ${path} (recursive: ${recursive})`); const watcher = (0, fs_1.watch)(path, { recursive }, (eventType, filename) => { if (!filename) return; const fullPath = (0, path_1.resolve)(path, filename); console.log(`[FileWatcher] Event: ${eventType} on ${fullPath}`); this.handleFileEvent(eventType, fullPath); }); watcher.on('error', (error) => { this.emit('error', error); }); this.watchers.set(path, watcher); } catch (error) { this.emit('error', new Error(`Failed to watch path ${path}: ${error}`)); } } handleFileEvent(eventType, filePath) { if (this.shouldIgnore(filePath)) { return; } const debouncedHandler = this.getDebouncedHandler(filePath); debouncedHandler(eventType, filePath); } getDebouncedHandler(filePath) { if (!this.debouncedHandlers.has(filePath)) { const handler = (0, helpers_1.debounce)((eventType, path) => { this.processFileChange(eventType, path); }, constants_1.FILE_WATCH_DEBOUNCE); this.debouncedHandlers.set(filePath, handler); } return this.debouncedHandlers.get(filePath); } processFileChange(eventType, filePath) { try { const existingFile = this.watchedFiles.get(filePath); if (!(0, helpers_1.isFile)(filePath)) { if (existingFile) { this.watchedFiles.delete(filePath); this.emitFileEvent('unlink', filePath); } return; } const stats = (0, fs_1.statSync)(filePath); const currentHash = this.calculateFileHash(filePath); const currentSize = stats.size; const currentMtime = stats.mtime.getTime(); if (!existingFile) { this.watchedFiles.set(filePath, { path: filePath, hash: currentHash, size: currentSize, mtime: currentMtime }); this.emitFileEvent('add', filePath, stats); } else { const hasChanged = existingFile.hash !== currentHash || existingFile.size !== currentSize || existingFile.mtime !== currentMtime; if (hasChanged) { this.watchedFiles.set(filePath, { path: filePath, hash: currentHash, size: currentSize, mtime: currentMtime }); this.emitFileEvent('change', filePath, stats); } } } catch (error) { this.emit('error', new Error(`Failed to process file change for ${filePath}: ${error}`)); } } calculateFileHash(filePath) { try { const content = (0, fs_1.readFileSync)(filePath); return (0, crypto_1.createHash)('md5').update(content).digest('hex'); } catch (error) { return ''; } } shouldIgnore(filePath) { const relativePath = (0, path_1.relative)(process.cwd(), filePath); return this.ignorePatterns.some(pattern => pattern.test(relativePath)); } scanInitialFiles(paths) { paths.forEach(path => { this.scanDirectory((0, path_1.resolve)(path)); }); } scanDirectory(dirPath) { try { if (!(0, helpers_1.isDirectory)(dirPath)) { if ((0, helpers_1.isFile)(dirPath) && !this.shouldIgnore(dirPath)) { this.processFileChange('add', dirPath); } return; } const fs = require('fs'); const entries = fs.readdirSync(dirPath, { withFileTypes: true }); entries.forEach((entry) => { const entryPath = (0, path_1.join)(dirPath, entry.name); if (this.shouldIgnore(entryPath)) { return; } if (entry.isDirectory()) { this.scanDirectory(entryPath); } else if (entry.isFile()) { this.processFileChange('add', entryPath); } }); } catch (error) { this.emit('error', new Error(`Failed to scan directory ${dirPath}: ${error}`)); } } emitFileEvent(type, filePath, stats) { const event = { type, filename: require('path').basename(filePath), path: filePath, stats }; this.emit('fileChange', event); this.emit(type, filePath, stats); } unwatch() { this.watchers.forEach(watcher => { try { watcher.close(); } catch (error) { this.emit('error', error); } }); this.watchers.clear(); this.watchedFiles.clear(); this.debouncedHandlers.clear(); this.isWatching = false; } getWatchedFiles() { return Array.from(this.watchedFiles.keys()); } isFileWatched(filePath) { return this.watchedFiles.has((0, path_1.resolve)(filePath)); } getFileInfo(filePath) { return this.watchedFiles.get((0, path_1.resolve)(filePath)); } addIgnorePattern(pattern) { const regexPattern = pattern .replace(/\./g, '\\.') .replace(/\*/g, '.*') .replace(/\?/g, '.'); this.ignorePatterns.push(new RegExp(regexPattern)); } removeIgnorePattern(pattern) { const regexPattern = pattern .replace(/\./g, '\\.') .replace(/\*/g, '.*') .replace(/\?/g, '.'); const regex = new RegExp(regexPattern); this.ignorePatterns = this.ignorePatterns.filter(existingPattern => existingPattern.source !== regex.source); } clearIgnorePatterns() { this.ignorePatterns = []; this.setupIgnorePatterns(); } getIgnorePatterns() { return this.ignorePatterns.map(pattern => pattern.source); } pause() { this.watchers.forEach(watcher => { try { watcher.close(); } catch (error) { this.emit('error', error); } }); this.isWatching = false; } resume() { if (!this.isWatching) { const watchedPaths = Array.from(this.watchers.keys()); this.watchers.clear(); watchedPaths.forEach(path => { this.watchPath(path, true); }); this.isWatching = true; } } getStats() { return { watchedPaths: this.watchers.size, watchedFiles: this.watchedFiles.size, isWatching: this.isWatching, ignorePatterns: this.ignorePatterns.length }; } } exports.FileWatcher = FileWatcher; //# sourceMappingURL=FileWatcher.js.map