locadot
Version:
Secure your local development environment with HTTPS and custom domains like dev.localhost.
183 lines (182 loc) • 8.02 kB
JavaScript
;
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;