UNPKG

locadot

Version:

Secure your local development environment with HTTPS and custom domains like dev.localhost.

183 lines (182 loc) 8.02 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const chokidar_1 = __importDefault(require("chokidar")); const logger_1 = __importDefault(require("./logger")); const constants_1 = __importDefault(require("../constants")); class FileModule { /** * Watches a file: creates folder and file if they do not exist, and listens to changes. * @param filePath - Path to the file (relative or absolute) * @param onChange - Callback to execute on file change * @returns Chokidar watcher instance */ static watchFileWithInit(filePath, onChange) { const resolvedPath = path_1.default.resolve(constants_1.default.paths[filePath]); const folderPath = path_1.default.dirname(resolvedPath); // Ensure folder exists if (!fs_1.default.existsSync(folderPath)) { fs_1.default.mkdirSync(folderPath, { recursive: true }); // logger.info(`Created folder: ${folderPath}`); } // Ensure file exists if (!fs_1.default.existsSync(resolvedPath)) { fs_1.default.writeFileSync(resolvedPath, "", "utf8"); // logger.info(`Created file: ${resolvedPath}`); } // Watch the file const watcher = chokidar_1.default.watch(resolvedPath, { persistent: true, ignoreInitial: true, awaitWriteFinish: true, }); watcher .on("change", (changedPath) => { try { if (!fs_1.default.existsSync(resolvedPath)) { console.log(`\n[Info] File ${resolvedPath} no longer exists. Stopping watch.`); if (watcher) watcher.close(); return; } onChange(); } catch (error) { logger_1.default.error(error); } }) .on("error", (error) => { console.error("Watcher error:", error); watcher.close(); }); return watcher; } static writeFileStream(filePath, stream) { const resolvedPath = path_1.default.resolve(constants_1.default.paths[filePath]); const folderPath = path_1.default.dirname(resolvedPath); // Ensure folder exists if (!fs_1.default.existsSync(folderPath)) { fs_1.default.mkdirSync(folderPath, { recursive: true }); // logger.info(`Created folder: ${folderPath}`); } // Ensure file exists if (!fs_1.default.existsSync(resolvedPath)) { fs_1.default.writeFileSync(resolvedPath, "", "utf8"); // logger.info(`Created file: ${resolvedPath}`); } return fs_1.default.createWriteStream(resolvedPath, stream ? { flags: "a" } : {}); } static async writeFileSync(filePath, data) { const resolvedPath = path_1.default.resolve(constants_1.default.paths[filePath]); const folderPath = path_1.default.dirname(resolvedPath); // Ensure folder exists if (!fs_1.default.existsSync(folderPath)) { fs_1.default.mkdirSync(folderPath, { recursive: true }); logger_1.default.info(`Created folder: ${folderPath}`); } return fs_1.default.writeFileSync(resolvedPath, data, "utf8"); } static async readFileSync(filePath, defaultValue = "") { const resolvedPath = path_1.default.resolve(constants_1.default.paths[filePath]); const folderPath = path_1.default.dirname(resolvedPath); // Ensure folder exists if (!fs_1.default.existsSync(folderPath)) { fs_1.default.mkdirSync(folderPath, { recursive: true }); // logger.info(`Created folder: ${folderPath}`); } if (!fs_1.default.existsSync(resolvedPath)) { fs_1.default.writeFileSync(resolvedPath, defaultValue || "", "utf8"); // logger.info(`Created file: ${resolvedPath}`); } return fs_1.default.readFileSync(resolvedPath, "utf8"); } static async removeFileSync(filePath) { const resolvedPath = path_1.default.resolve(constants_1.default.paths[filePath]); const folderPath = path_1.default.dirname(resolvedPath); if (!fs_1.default.existsSync(folderPath)) { return; } if (!fs_1.default.existsSync(resolvedPath)) { return; } fs_1.default.rmSync(resolvedPath, { force: true, recursive: true }); } static async tailFile(filePathKey, maxLines = 10) { const resolvedPath = path_1.default.resolve(constants_1.default.paths[filePathKey]); try { const initialContent = await this.readFileSync(filePathKey); const lines = initialContent.trim().split(/\r?\n/); const nonEmptyLines = lines.filter((line) => line.length > 0); if (nonEmptyLines.length > 0) { const lastNLines = nonEmptyLines.slice(-Math.abs(maxLines)).join("\n"); if (lastNLines) { process.stdout.write(lastNLines + "\n"); } } } catch (err) { console.error(`[Error] Reading initial content from ${resolvedPath}:`, err); } let lastKnownSize = 0; try { lastKnownSize = fs_1.default.statSync(resolvedPath).size; } catch (err) { console.error(`[Error] Getting initial size of ${resolvedPath}. Assuming 0. Error:`, err); } let watcher = null; try { watcher = this.watchFileWithInit(filePathKey, () => { if (!watcher) return; try { const stats = fs_1.default.statSync(resolvedPath); const currentSize = stats.size; if (currentSize > lastKnownSize) { const stream = fs_1.default.createReadStream(resolvedPath, { start: lastKnownSize, end: currentSize - 1, // end is inclusive for createReadStream encoding: "utf8", }); stream.on("data", (chunk) => { process.stdout.write(chunk); }); stream.on("error", (readErr) => { console.error(`[Error] Reading new content from ${resolvedPath}:`, readErr); }); lastKnownSize = currentSize; } else if (currentSize < lastKnownSize) { process.stdout.write(`\n[Info] --- File ${resolvedPath} truncated or replaced. Tailing from new end. ---\n`); lastKnownSize = currentSize; } } catch (err) { console.error(`[Error] Processing file change for ${resolvedPath}:`, err); } }); const cleanup = (err) => { console.error(`Tailing file exiting with error: ${err}`); watcher?.close(); process.exit(0); }; process.on("exit", () => cleanup("exit")); process.on("SIGINT", () => cleanup("stop")); process.on("SIGTERM", () => cleanup("stop")); process.on("uncaughtException", () => cleanup("uncaughtException")); return { stop: () => { cleanup("stop"); }, }; } catch (watchSetupError) { console.error(`[Error] Failed to setup watcher for ${resolvedPath}:`, watchSetupError); } } } exports.default = FileModule;