UNPKG

@catbee/utils

Version:

A modular, production-grade utility toolkit for Node.js and TypeScript, designed for robust, scalable applications (including Express-based services). All utilities are tree-shakable and can be imported independently.

977 lines 44 kB
var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __values = (this && this.__values) || function(o) { var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; if (m) return m.call(o); if (o && typeof o.length === "number") return { next: function () { if (o && i >= o.length) o = void 0; return { value: o && o[i++], done: !o }; } }; throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); }; var __read = (this && this.__read) || function (o, n) { var m = typeof Symbol === "function" && o[Symbol.iterator]; if (!m) return o; var i = m.call(o), r, ar = [], e; try { while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); } catch (error) { e = { error: error }; } finally { try { if (r && !r.done && (m = i["return"])) m.call(i); } finally { if (e) throw e.error; } } return ar; }; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; import os from "os"; import fs from "fs"; import fsp from "fs/promises"; import path from "path"; import { glob } from "glob"; import { randomBytes } from "crypto"; /** * Ensures that a directory exists, creating parent directories if needed (like `mkdir -p`). * * @param {string} dirPath - The directory path to ensure. * @returns {Promise<void>} Resolves when the directory exists. * @throws {Error} If directory cannot be created. */ export function ensureDir(dirPath) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, fsp.mkdir(dirPath, { recursive: true })]; case 1: _a.sent(); return [2 /*return*/]; } }); }); } /** * Recursively lists all files in a directory. * * @param {string} dirPath - The base directory. * @param {boolean} [recursive=false] - Whether to recurse into subdirectories. * @returns {Promise<string[]>} Array of absolute file paths. * @throws {Error} If the directory cannot be read. */ export function listFiles(dirPath_1) { return __awaiter(this, arguments, void 0, function (dirPath, recursive) { var entries, files, entries_1, entries_1_1, entry, fullPath, nestedFiles, e_1_1; var e_1, _a; if (recursive === void 0) { recursive = false; } return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, fsp.readdir(dirPath, { withFileTypes: true })]; case 1: entries = _b.sent(); files = []; _b.label = 2; case 2: _b.trys.push([2, 8, 9, 10]); entries_1 = __values(entries), entries_1_1 = entries_1.next(); _b.label = 3; case 3: if (!!entries_1_1.done) return [3 /*break*/, 7]; entry = entries_1_1.value; fullPath = path.join(dirPath, entry.name); if (!entry.isFile()) return [3 /*break*/, 4]; files.push(fullPath); return [3 /*break*/, 6]; case 4: if (!(recursive && entry.isDirectory())) return [3 /*break*/, 6]; return [4 /*yield*/, listFiles(fullPath, true)]; case 5: nestedFiles = _b.sent(); files.push.apply(files, __spreadArray([], __read(nestedFiles), false)); _b.label = 6; case 6: entries_1_1 = entries_1.next(); return [3 /*break*/, 3]; case 7: return [3 /*break*/, 10]; case 8: e_1_1 = _b.sent(); e_1 = { error: e_1_1 }; return [3 /*break*/, 10]; case 9: try { if (entries_1_1 && !entries_1_1.done && (_a = entries_1.return)) _a.call(entries_1); } finally { if (e_1) throw e_1.error; } return [7 /*endfinally*/]; case 10: return [2 /*return*/, files]; } }); }); } /** * Deletes a directory and all its contents recursively (like `rm -rf`). * * @param {string} dirPath - Directory to delete. * @returns {Promise<void>} Resolves when deletion is complete. * @throws {Error} If deletion fails. */ export function deleteDirRecursive(dirPath) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, fsp.rm(dirPath, { recursive: true, force: true })]; case 1: _a.sent(); return [2 /*return*/]; } }); }); } /** * Checks whether a given path is a directory. * * @param {string} pathStr - Path to check. * @returns {Promise<boolean>} True if the path is a directory, else false. */ export function isDirectory(pathStr) { return __awaiter(this, void 0, void 0, function () { var stat, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: _b.trys.push([0, 2, , 3]); return [4 /*yield*/, fsp.stat(pathStr)]; case 1: stat = _b.sent(); return [2 /*return*/, stat.isDirectory()]; case 2: _a = _b.sent(); return [2 /*return*/, false]; case 3: return [2 /*return*/]; } }); }); } /** * Recursively copies a directory and all its contents to a destination. * * @param {string} src - Source directory path. * @param {string} dest - Destination directory path. * @returns {Promise<void>} Resolves when copy is complete. * @throws {Error} If source does not exist or copy fails. */ export function copyDir(src, dest) { return __awaiter(this, void 0, void 0, function () { var entries, entries_2, entries_2_1, entry, srcPath, destPath, e_2_1; var e_2, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, ensureDir(dest)]; case 1: _b.sent(); return [4 /*yield*/, fsp.readdir(src, { withFileTypes: true })]; case 2: entries = _b.sent(); _b.label = 3; case 3: _b.trys.push([3, 10, 11, 12]); entries_2 = __values(entries), entries_2_1 = entries_2.next(); _b.label = 4; case 4: if (!!entries_2_1.done) return [3 /*break*/, 9]; entry = entries_2_1.value; srcPath = path.join(src, entry.name); destPath = path.join(dest, entry.name); if (!entry.isDirectory()) return [3 /*break*/, 6]; return [4 /*yield*/, copyDir(srcPath, destPath)]; case 5: _b.sent(); return [3 /*break*/, 8]; case 6: return [4 /*yield*/, fsp.copyFile(srcPath, destPath)]; case 7: _b.sent(); _b.label = 8; case 8: entries_2_1 = entries_2.next(); return [3 /*break*/, 4]; case 9: return [3 /*break*/, 12]; case 10: e_2_1 = _b.sent(); e_2 = { error: e_2_1 }; return [3 /*break*/, 12]; case 11: try { if (entries_2_1 && !entries_2_1.done && (_a = entries_2.return)) _a.call(entries_2); } finally { if (e_2) throw e_2.error; } return [7 /*endfinally*/]; case 12: return [2 /*return*/]; } }); }); } /** * Moves a directory to a new location by copying and deleting the original. * * @param {string} src - Source directory path. * @param {string} dest - Destination directory path. * @returns {Promise<void>} Resolves when move is complete. * @throws {Error} If copy or deletion fails. */ export function moveDir(src, dest) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, copyDir(src, dest)]; case 1: _a.sent(); return [4 /*yield*/, deleteDirRecursive(src)]; case 2: _a.sent(); return [2 /*return*/]; } }); }); } /** * Empties a directory by deleting all files and subdirectories inside it. * * @param {string} dirPath - Path to the directory to empty. * @returns {Promise<void>} Resolves when the directory has been emptied. * @throws {Error} If files or subdirectories cannot be removed. */ export function emptyDir(dirPath) { return __awaiter(this, void 0, void 0, function () { var entries, entries_3, entries_3_1, entry, entryPath, e_3_1; var e_3, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, fsp.readdir(dirPath, { withFileTypes: true })]; case 1: entries = _b.sent(); _b.label = 2; case 2: _b.trys.push([2, 9, 10, 11]); entries_3 = __values(entries), entries_3_1 = entries_3.next(); _b.label = 3; case 3: if (!!entries_3_1.done) return [3 /*break*/, 8]; entry = entries_3_1.value; entryPath = path.join(dirPath, entry.name); if (!entry.isDirectory()) return [3 /*break*/, 5]; return [4 /*yield*/, fsp.rm(entryPath, { recursive: true, force: true })]; case 4: _b.sent(); return [3 /*break*/, 7]; case 5: return [4 /*yield*/, fsp.unlink(entryPath)]; case 6: _b.sent(); _b.label = 7; case 7: entries_3_1 = entries_3.next(); return [3 /*break*/, 3]; case 8: return [3 /*break*/, 11]; case 9: e_3_1 = _b.sent(); e_3 = { error: e_3_1 }; return [3 /*break*/, 11]; case 10: try { if (entries_3_1 && !entries_3_1.done && (_a = entries_3.return)) _a.call(entries_3); } finally { if (e_3) throw e_3.error; } return [7 /*endfinally*/]; case 11: return [2 /*return*/]; } }); }); } /** * Calculates the total size (in bytes) of all files in a directory (recursive). * * @param {string} dirPath - Path to the directory. * @returns {Promise<number>} Total size in bytes. * @throws {Error} If any file stats cannot be read. */ export function getDirSize(dirPath) { return __awaiter(this, void 0, void 0, function () { var total, entries, entries_4, entries_4_1, entry, entryPath, _a, stat, e_4_1; var e_4, _b; return __generator(this, function (_c) { switch (_c.label) { case 0: total = 0; return [4 /*yield*/, fsp.readdir(dirPath, { withFileTypes: true })]; case 1: entries = _c.sent(); _c.label = 2; case 2: _c.trys.push([2, 9, 10, 11]); entries_4 = __values(entries), entries_4_1 = entries_4.next(); _c.label = 3; case 3: if (!!entries_4_1.done) return [3 /*break*/, 8]; entry = entries_4_1.value; entryPath = path.join(dirPath, entry.name); if (!entry.isDirectory()) return [3 /*break*/, 5]; _a = total; return [4 /*yield*/, getDirSize(entryPath)]; case 4: total = _a + _c.sent(); return [3 /*break*/, 7]; case 5: return [4 /*yield*/, fsp.stat(entryPath)]; case 6: stat = _c.sent(); total += stat.size; _c.label = 7; case 7: entries_4_1 = entries_4.next(); return [3 /*break*/, 3]; case 8: return [3 /*break*/, 11]; case 9: e_4_1 = _c.sent(); e_4 = { error: e_4_1 }; return [3 /*break*/, 11]; case 10: try { if (entries_4_1 && !entries_4_1.done && (_b = entries_4.return)) _b.call(entries_4); } finally { if (e_4) throw e_4.error; } return [7 /*endfinally*/]; case 11: return [2 /*return*/, total]; } }); }); } /** * Watches a directory for file changes and calls a callback on each event. * * @param {string} dirPath - Directory path to watch. * @param {(eventType: "rename" | "change", filename: string | null) => void} callback - Callback for each change event. * @returns {() => void} A function to stop watching the directory. */ export function watchDir(dirPath, callback) { var watcher = fs.watch(dirPath, function (eventType, filename) { callback(eventType, filename); }); return function () { return watcher.close(); }; } /** * Finds files matching a glob pattern. * * @param {string} pattern - Glob pattern to match files. * @param {object} [options] - Options for glob pattern matching. * @param {string} [options.cwd] - Current working directory for relative patterns. * @param {boolean} [options.dot=false] - Include dotfiles in matches. * @param {boolean} [options.nodir=true] - Only match files, not directories. * @returns {Promise<string[]>} Array of matched file paths. * @throws {Error} If pattern matching fails. */ export function findFilesByPattern(pattern_1) { return __awaiter(this, arguments, void 0, function (pattern, options) { var defaultOptions; if (options === void 0) { options = {}; } return __generator(this, function (_a) { defaultOptions = __assign({ nodir: true }, options); return [2 /*return*/, glob(pattern, defaultOptions)]; }); }); } /** * Gets all subdirectories in a directory. * * @param {string} dirPath - The directory to search in. * @param {boolean} [recursive=false] - Whether to include subdirectories recursively. * @returns {Promise<string[]>} Array of absolute subdirectory paths. * @throws {Error} If directory cannot be read. */ export function getSubdirectories(dirPath_1) { return __awaiter(this, arguments, void 0, function (dirPath, recursive) { var entries, dirs, nestedDirs; if (recursive === void 0) { recursive = false; } return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, fsp.readdir(dirPath, { withFileTypes: true })]; case 1: entries = _a.sent(); dirs = entries .filter(function (entry) { return entry.isDirectory(); }) .map(function (entry) { return path.join(dirPath, entry.name); }); if (!(recursive && dirs.length > 0)) return [3 /*break*/, 3]; return [4 /*yield*/, Promise.all(dirs.map(function (dir) { return getSubdirectories(dir, true); }))]; case 2: nestedDirs = _a.sent(); return [2 /*return*/, __spreadArray(__spreadArray([], __read(dirs), false), __read(nestedDirs.flat()), false)]; case 3: return [2 /*return*/, dirs]; } }); }); } /** * Ensures a directory exists and is empty. * * @param {string} dirPath - Path to the directory. * @returns {Promise<void>} Resolves when the directory exists and is empty. * @throws {Error} If directory cannot be created or emptied. */ export function ensureEmptyDir(dirPath) { return __awaiter(this, void 0, void 0, function () { var error_1; return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, isDirectory(dirPath)]; case 1: if (!_a.sent()) return [3 /*break*/, 3]; return [4 /*yield*/, emptyDir(dirPath)]; case 2: _a.sent(); return [3 /*break*/, 8]; case 3: _a.trys.push([3, 5, , 6]); return [4 /*yield*/, fsp.unlink(dirPath)]; case 4: _a.sent(); return [3 /*break*/, 6]; case 5: error_1 = _a.sent(); // Ignore if file doesn't exist if (error_1.code !== "ENOENT") { throw error_1; } return [3 /*break*/, 6]; case 6: return [4 /*yield*/, ensureDir(dirPath)]; case 7: _a.sent(); _a.label = 8; case 8: return [2 /*return*/]; } }); }); } /** * Creates a temporary directory with optional auto-cleanup. * * @param {object} [options] - Options for the temporary directory. * @param {string} [options.prefix='tmp-'] - Prefix for the directory name. * @param {string} [options.parentDir=os.tmpdir()] - Parent directory. * @param {boolean} [options.cleanup=false] - Whether to register cleanup on process exit. * @returns {Promise<{ path: string, cleanup: () => Promise<void> }>} Object with directory path and cleanup function. * @throws {Error} If directory cannot be created. */ export function createTempDir() { return __awaiter(this, arguments, void 0, function (options) { var prefix, parentDir, dirName, tempDirPath, cleanup; var _this = this; if (options === void 0) { options = {}; } return __generator(this, function (_a) { switch (_a.label) { case 0: prefix = options.prefix || "tmp-"; parentDir = options.parentDir || os.tmpdir(); dirName = "".concat(prefix).concat(randomBytes(6).toString("hex")); tempDirPath = path.join(parentDir, dirName); return [4 /*yield*/, ensureDir(tempDirPath)]; case 1: _a.sent(); cleanup = function () { return __awaiter(_this, void 0, void 0, function () { var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: _b.trys.push([0, 2, , 3]); return [4 /*yield*/, deleteDirRecursive(tempDirPath)]; case 1: _b.sent(); return [3 /*break*/, 3]; case 2: _a = _b.sent(); return [3 /*break*/, 3]; case 3: return [2 /*return*/]; } }); }); }; if (options.cleanup) { process.once("exit", function () { try { fs.rmSync(tempDirPath, { recursive: true, force: true }); } catch (_a) { // Ignore cleanup errors on exit } }); // Handle signals for better cleanup ["SIGINT", "SIGTERM", "SIGUSR1", "SIGUSR2"].forEach(function (signal) { process.once(signal, function () { cleanup().then(function () { return process.exit(); }); }); }); } return [2 /*return*/, { path: tempDirPath, cleanup: cleanup }]; } }); }); } /** * Finds the newest file in a directory. * * @param {string} dirPath - Directory to search. * @param {boolean} [recursive=false] - Whether to search subdirectories. * @returns {Promise<string | null>} Path to the newest file or null if no files. * @throws {Error} If directory cannot be read. */ export function findNewestFile(dirPath_1) { return __awaiter(this, arguments, void 0, function (dirPath, recursive) { var files, stats; var _this = this; if (recursive === void 0) { recursive = false; } return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, listFiles(dirPath, recursive)]; case 1: files = _a.sent(); if (files.length === 0) return [2 /*return*/, null]; return [4 /*yield*/, Promise.all(files.map(function (file) { return __awaiter(_this, void 0, void 0, function () { var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: _a = { path: file }; return [4 /*yield*/, fsp.stat(file)]; case 1: return [2 /*return*/, (_a.mtime = (_b.sent()).mtime, _a)]; } }); }); }))]; case 2: stats = _a.sent(); return [2 /*return*/, stats.sort(function (a, b) { return b.mtime.getTime() - a.mtime.getTime(); })[0].path]; } }); }); } /** * Finds the oldest file in a directory. * * @param {string} dirPath - Directory to search. * @param {boolean} [recursive=false] - Whether to search subdirectories. * @returns {Promise<string | null>} Path to the oldest file or null if no files. * @throws {Error} If directory cannot be read. */ export function findOldestFile(dirPath_1) { return __awaiter(this, arguments, void 0, function (dirPath, recursive) { var files, stats; var _this = this; if (recursive === void 0) { recursive = false; } return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, listFiles(dirPath, recursive)]; case 1: files = _a.sent(); if (files.length === 0) return [2 /*return*/, null]; return [4 /*yield*/, Promise.all(files.map(function (file) { return __awaiter(_this, void 0, void 0, function () { var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: _a = { path: file }; return [4 /*yield*/, fsp.stat(file)]; case 1: return [2 /*return*/, (_a.mtime = (_b.sent()).mtime, _a)]; } }); }); }))]; case 2: stats = _a.sent(); return [2 /*return*/, stats.sort(function (a, b) { return a.mtime.getTime() - b.mtime.getTime(); })[0].path]; } }); }); } /** * Finds files or directories in a directory matching a predicate function. * * @param {string} dirPath - Directory to search. * @param {(path: string, stat: fs.Stats) => boolean | Promise<boolean>} predicate - Function to test each path. * @param {boolean} [recursive=false] - Whether to search subdirectories. * @returns {Promise<string[]>} Array of matching paths. * @throws {Error} If directory cannot be read. */ export function findInDir(dirPath_1, predicate_1) { return __awaiter(this, arguments, void 0, function (dirPath, predicate, recursive) { var entries, results, entries_5, entries_5_1, entry, entryPath, stat, nestedResults, e_5_1; var e_5, _a; if (recursive === void 0) { recursive = false; } return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, fsp.readdir(dirPath, { withFileTypes: true })]; case 1: entries = _b.sent(); results = []; _b.label = 2; case 2: _b.trys.push([2, 9, 10, 11]); entries_5 = __values(entries), entries_5_1 = entries_5.next(); _b.label = 3; case 3: if (!!entries_5_1.done) return [3 /*break*/, 8]; entry = entries_5_1.value; entryPath = path.join(dirPath, entry.name); return [4 /*yield*/, fsp.stat(entryPath)]; case 4: stat = _b.sent(); return [4 /*yield*/, predicate(entryPath, stat)]; case 5: if (_b.sent()) { results.push(entryPath); } if (!(recursive && entry.isDirectory())) return [3 /*break*/, 7]; return [4 /*yield*/, findInDir(entryPath, predicate, true)]; case 6: nestedResults = _b.sent(); results.push.apply(results, __spreadArray([], __read(nestedResults), false)); _b.label = 7; case 7: entries_5_1 = entries_5.next(); return [3 /*break*/, 3]; case 8: return [3 /*break*/, 11]; case 9: e_5_1 = _b.sent(); e_5 = { error: e_5_1 }; return [3 /*break*/, 11]; case 10: try { if (entries_5_1 && !entries_5_1.done && (_a = entries_5.return)) _a.call(entries_5); } finally { if (e_5) throw e_5.error; } return [7 /*endfinally*/]; case 11: return [2 /*return*/, results]; } }); }); } /** * Watches a directory recursively for file changes. * * @param {string} dirPath - Base directory path to watch. * @param {(eventType: "rename" | "change", filename: string) => void} callback - Callback for each change event. * @param {boolean} [includeSubdirs=true] - Whether to watch subdirectories. * @returns {Promise<() => void>} A function to stop watching the directory. * @throws {Error} If directory cannot be watched. */ export function watchDirRecursive(dirPath_1, callback_1) { return __awaiter(this, arguments, void 0, function (dirPath, callback, includeSubdirs) { var normalizedBasePath, watchers, addWatcher, subdirs, subdirs_1, subdirs_1_1, subdir; var e_6, _a; var _this = this; if (includeSubdirs === void 0) { includeSubdirs = true; } return __generator(this, function (_b) { switch (_b.label) { case 0: normalizedBasePath = path.normalize(dirPath); watchers = []; addWatcher = function (dir) { try { var watcher = fs.watch(dir, function (eventType, filename) { if (!filename) return; var fullPath = path.join(dir, filename); // Make the path relative to the base directory var relativePath = path.relative(normalizedBasePath, fullPath); callback(eventType, relativePath); // If a new directory is created, we should watch it if (eventType === "rename" && includeSubdirs) { setTimeout(function () { return __awaiter(_this, void 0, void 0, function () { var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: _b.trys.push([0, 2, , 3]); return [4 /*yield*/, isDirectory(fullPath)]; case 1: if (_b.sent()) { addWatcher(fullPath); } return [3 /*break*/, 3]; case 2: _a = _b.sent(); return [3 /*break*/, 3]; case 3: return [2 /*return*/]; } }); }); }, 100); } }); watchers.push(watcher); } catch (_a) { // Silently ignore directories we can't watch } }; // Add watcher for the base directory addWatcher(normalizedBasePath); if (!includeSubdirs) return [3 /*break*/, 2]; return [4 /*yield*/, getSubdirectories(normalizedBasePath, true)]; case 1: subdirs = _b.sent(); try { for (subdirs_1 = __values(subdirs), subdirs_1_1 = subdirs_1.next(); !subdirs_1_1.done; subdirs_1_1 = subdirs_1.next()) { subdir = subdirs_1_1.value; addWatcher(subdir); } } catch (e_6_1) { e_6 = { error: e_6_1 }; } finally { try { if (subdirs_1_1 && !subdirs_1_1.done && (_a = subdirs_1.return)) _a.call(subdirs_1); } finally { if (e_6) throw e_6.error; } } _b.label = 2; case 2: // Return a function to close all watchers return [2 /*return*/, function () { watchers.forEach(function (watcher) { return watcher.close(); }); }]; } }); }); } /** * Gets detailed directory statistics including file count, directory count, and size. * * @param {string} dirPath - Path to the directory. * @returns {Promise<{ fileCount: number, dirCount: number, totalSize: number }>} Directory statistics. * @throws {Error} If directory cannot be read. */ export function getDirStats(dirPath) { return __awaiter(this, void 0, void 0, function () { function processDir(currentPath) { return __awaiter(this, void 0, void 0, function () { var entries, entries_6, entries_6_1, entry, entryPath, stat, e_7_1; var e_7, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: return [4 /*yield*/, fsp.readdir(currentPath, { withFileTypes: true })]; case 1: entries = _b.sent(); _b.label = 2; case 2: _b.trys.push([2, 9, 10, 11]); entries_6 = __values(entries), entries_6_1 = entries_6.next(); _b.label = 3; case 3: if (!!entries_6_1.done) return [3 /*break*/, 8]; entry = entries_6_1.value; entryPath = path.join(currentPath, entry.name); if (!entry.isDirectory()) return [3 /*break*/, 5]; dirCount++; return [4 /*yield*/, processDir(entryPath)]; case 4: _b.sent(); return [3 /*break*/, 7]; case 5: if (!entry.isFile()) return [3 /*break*/, 7]; fileCount++; return [4 /*yield*/, fsp.stat(entryPath)]; case 6: stat = _b.sent(); totalSize += stat.size; _b.label = 7; case 7: entries_6_1 = entries_6.next(); return [3 /*break*/, 3]; case 8: return [3 /*break*/, 11]; case 9: e_7_1 = _b.sent(); e_7 = { error: e_7_1 }; return [3 /*break*/, 11]; case 10: try { if (entries_6_1 && !entries_6_1.done && (_a = entries_6.return)) _a.call(entries_6); } finally { if (e_7) throw e_7.error; } return [7 /*endfinally*/]; case 11: return [2 /*return*/]; } }); }); } var fileCount, dirCount, totalSize; return __generator(this, function (_a) { switch (_a.label) { case 0: fileCount = 0; dirCount = 0; totalSize = 0; return [4 /*yield*/, processDir(dirPath)]; case 1: _a.sent(); return [2 /*return*/, { fileCount: fileCount, dirCount: dirCount, totalSize: totalSize }]; } }); }); } /** * Walks through a directory hierarchy, calling a visitor function for each entry. * * @param {string} dirPath - Starting directory path. * @param {object} options - Options for walking the directory. * @param {(entry: { path: string, name: string, isDirectory: boolean, stats: fs.Stats }) => boolean | void | Promise<boolean | void>} options.visitorFn - * Function called for each file/directory. Return false to skip a directory. * @param {'pre' | 'post'} [options.traversalOrder='pre'] - Whether to visit directories before or after their contents. * @throws {Error} If directory cannot be read. */ export function walkDir(dirPath, options) { return __awaiter(this, void 0, void 0, function () { var traversalOrder, entries, entries_7, entries_7_1, entry, entryPath, stats, entryInfo, shouldContinue, e_8_1, stats, entryInfo; var e_8, _a; return __generator(this, function (_b) { switch (_b.label) { case 0: traversalOrder = options.traversalOrder || "pre"; return [4 /*yield*/, fsp.readdir(dirPath, { withFileTypes: true })]; case 1: entries = _b.sent(); _b.label = 2; case 2: _b.trys.push([2, 16, 17, 18]); entries_7 = __values(entries), entries_7_1 = entries_7.next(); _b.label = 3; case 3: if (!!entries_7_1.done) return [3 /*break*/, 15]; entry = entries_7_1.value; entryPath = path.join(dirPath, entry.name); return [4 /*yield*/, fsp.stat(entryPath)]; case 4: stats = _b.sent(); entryInfo = { path: entryPath, name: entry.name, isDirectory: entry.isDirectory(), stats: stats, }; if (!entry.isDirectory()) return [3 /*break*/, 12]; if (!(traversalOrder === "pre")) return [3 /*break*/, 8]; return [4 /*yield*/, options.visitorFn(entryInfo)]; case 5: shouldContinue = _b.sent(); if (!(shouldContinue !== false)) return [3 /*break*/, 7]; return [4 /*yield*/, walkDir(entryPath, options)]; case 6: _b.sent(); _b.label = 7; case 7: return [3 /*break*/, 11]; case 8: // Post-order: visit directory after its contents return [4 /*yield*/, walkDir(entryPath, options)]; case 9: // Post-order: visit directory after its contents _b.sent(); return [4 /*yield*/, options.visitorFn(entryInfo)]; case 10: _b.sent(); _b.label = 11; case 11: return [3 /*break*/, 14]; case 12: // Files are always visited when encountered return [4 /*yield*/, options.visitorFn(entryInfo)]; case 13: // Files are always visited when encountered _b.sent(); _b.label = 14; case 14: entries_7_1 = entries_7.next(); return [3 /*break*/, 3]; case 15: return [3 /*break*/, 18]; case 16: e_8_1 = _b.sent(); e_8 = { error: e_8_1 }; return [3 /*break*/, 18]; case 17: try { if (entries_7_1 && !entries_7_1.done && (_a = entries_7.return)) _a.call(entries_7); } finally { if (e_8) throw e_8.error; } return [7 /*endfinally*/]; case 18: if (!(traversalOrder === "post")) return [3 /*break*/, 21]; return [4 /*yield*/, fsp.stat(dirPath)]; case 19: stats = _b.sent(); entryInfo = { path: dirPath, name: path.basename(dirPath), isDirectory: true, stats: stats, }; return [4 /*yield*/, options.visitorFn(entryInfo)]; case 20: _b.sent(); _b.label = 21; case 21: return [2 /*return*/]; } }); }); } //# sourceMappingURL=dir.utils.js.map