neex
Version:
Neex - Modern Fullstack Framework Built on Express and Next.js. Fast to Start, Easy to Build, Ready to Deploy
246 lines (245 loc) • 9.29 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileWatcher = void 0;
// src/watcher.ts - File watcher for development (nodemon functionality)
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const events_1 = require("events");
const chalk_1 = __importDefault(require("chalk"));
const logger_process_1 = require("./logger-process");
class FileWatcher extends events_1.EventEmitter {
constructor(options) {
super();
this.watchers = [];
this.watchedFiles = new Set();
this.debounceTimer = null;
this.isWatching = false;
this.ignorePatterns = [];
// Initialize with a copy of user-provided options.
const processedOptions = { ...options };
// Apply defaults if properties were not set in the provided 'options'.
if (processedOptions.watch === undefined) {
processedOptions.watch = ['./'];
}
if (processedOptions.ignore === undefined) {
processedOptions.ignore = [
'node_modules/**',
'.git/**',
'*.log',
'dist/**',
'build/**',
'coverage/**',
'.nyc_output/**',
'*.tmp',
'*.temp'
];
}
if (processedOptions.ext === undefined) {
processedOptions.ext = ['js', 'mjs', 'json', 'ts', 'tsx', 'jsx'];
}
if (processedOptions.delay === undefined) {
processedOptions.delay = 1000;
}
if (processedOptions.verbose === undefined) {
processedOptions.verbose = false;
}
if (processedOptions.legacyWatch === undefined) {
processedOptions.legacyWatch = false;
}
if (processedOptions.pollingInterval === undefined) {
processedOptions.pollingInterval = 1000;
}
// 'cwd' and 'env' are optional and don't have explicit defaults in this setup;
// they will be taken from 'options' or remain undefined if not provided.
this.options = processedOptions;
this.setupIgnorePatterns();
}
setupIgnorePatterns() {
this.ignorePatterns = (this.options.ignore || []).map(pattern => {
// Convert glob patterns to regex
const regexPattern = pattern
.replace(/\*\*/g, '.*')
.replace(/\*/g, '[^/]*')
.replace(/\?/g, '[^/]')
.replace(/\./g, '\\.');
return new RegExp(regexPattern, 'i');
});
}
shouldIgnoreFile(filePath) {
const relativePath = path.relative(process.cwd(), filePath);
// Check ignore patterns
for (const pattern of this.ignorePatterns) {
if (pattern.test(relativePath) || pattern.test(filePath)) {
return true;
}
}
// Check file extension
if (this.options.ext && this.options.ext.length > 0) {
const ext = path.extname(filePath).slice(1);
if (!this.options.ext.includes(ext)) {
return true;
}
}
return false;
}
async isValidFile(filePath) {
try {
const stats = await fs.promises.stat(filePath);
return stats.isFile();
}
catch (_a) {
return false;
}
}
async watchDirectory(dirPath) {
try {
const absolutePath = path.resolve(dirPath);
if (this.options.verbose) {
logger_process_1.logger.printLine(`Watching directory: ${chalk_1.default.cyan(absolutePath)}`, 'info');
}
const watcher = fs.watch(absolutePath, { recursive: true }, async (eventType, filename) => {
if (!filename)
return;
const fullPath = path.join(absolutePath, filename);
if (this.shouldIgnoreFile(fullPath)) {
return;
}
if (!(await this.isValidFile(fullPath))) {
return;
}
this.handleFileChange(fullPath, eventType);
});
this.watchers.push(watcher);
}
catch (error) {
if (this.options.verbose) {
logger_process_1.logger.printLine(`Failed to watch directory ${dirPath}: ${error.message}`, 'warn');
}
}
}
async watchFile(filePath) {
try {
const absolutePath = path.resolve(filePath);
if (this.shouldIgnoreFile(absolutePath)) {
return;
}
if (!(await this.isValidFile(absolutePath))) {
return;
}
if (this.options.verbose) {
logger_process_1.logger.printLine(`Watching file: ${chalk_1.default.cyan(absolutePath)}`, 'info');
}
const watcher = fs.watch(absolutePath, (eventType) => {
this.handleFileChange(absolutePath, eventType);
});
this.watchers.push(watcher);
this.watchedFiles.add(absolutePath);
}
catch (error) {
if (this.options.verbose) {
logger_process_1.logger.printLine(`Failed to watch file ${filePath}: ${error.message}`, 'warn');
}
}
}
handleFileChange(filePath, eventType) {
if (this.options.verbose) {
logger_process_1.logger.printLine(`File ${eventType}: ${chalk_1.default.yellow(path.relative(process.cwd(), filePath))}`, 'info');
}
// Debounce file changes
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
}
this.debounceTimer = setTimeout(() => {
this.emit('change', {
path: filePath,
event: eventType,
relativePath: path.relative(process.cwd(), filePath)
});
}, this.options.delay);
}
async start() {
if (this.isWatching) {
return;
}
this.isWatching = true;
logger_process_1.logger.printLine('Starting file watcher...', 'info');
for (const watchPath of this.options.watch) {
const absolutePath = path.resolve(watchPath);
try {
const stats = await fs.promises.stat(absolutePath);
if (stats.isDirectory()) {
await this.watchDirectory(absolutePath);
}
else if (stats.isFile()) {
await this.watchFile(absolutePath);
}
}
catch (error) {
logger_process_1.logger.printLine(`Cannot watch ${watchPath}: ${error.message}`, 'warn');
}
}
const watchedCount = this.watchers.length;
logger_process_1.logger.printLine(`File watcher started. Monitoring ${chalk_1.default.green(watchedCount)} locations`, 'info');
if (this.options.ext && this.options.ext.length > 0) {
logger_process_1.logger.printLine(`Watching extensions: ${chalk_1.default.cyan(this.options.ext.join(', '))}`, 'info');
}
}
stop() {
if (!this.isWatching) {
return;
}
logger_process_1.logger.printLine('Stopping file watcher...', 'info');
this.watchers.forEach(watcher => {
try {
watcher.close();
}
catch (error) {
// Ignore errors when closing watchers
}
});
this.watchers = [];
this.watchedFiles.clear();
this.isWatching = false;
if (this.debounceTimer) {
clearTimeout(this.debounceTimer);
this.debounceTimer = null;
}
logger_process_1.logger.printLine('File watcher stopped', 'info');
}
isActive() {
return this.isWatching;
}
getWatchedFiles() {
return Array.from(this.watchedFiles);
}
getOptions() {
return { ...this.options };
}
}
exports.FileWatcher = FileWatcher;