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.

391 lines (387 loc) 11.9 kB
/* * The MIT License * * Copyright (c) 2026 Catbee Technologies. https://catbee.in/license * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ import os from 'os'; import fs from 'fs'; import fsp from 'fs/promises'; import path from 'path'; import { randomBytes } from 'crypto'; var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); async function ensureDir(dirPath) { await fsp.mkdir(dirPath, { recursive: true }); } __name(ensureDir, "ensureDir"); async function listFiles(dirPath, recursive = false) { const entries = await fsp.readdir(dirPath, { withFileTypes: true }); const files = []; for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); if (entry.isFile()) { files.push(fullPath); } else if (recursive && entry.isDirectory()) { const nestedFiles = await listFiles(fullPath, true); files.push(...nestedFiles); } } return files; } __name(listFiles, "listFiles"); async function deleteDirRecursive(dirPath) { await fsp.rm(dirPath, { recursive: true, force: true }); } __name(deleteDirRecursive, "deleteDirRecursive"); async function isDirectory(pathStr) { try { const stat = await fsp.stat(pathStr); return stat.isDirectory(); } catch { return false; } } __name(isDirectory, "isDirectory"); async function copyDir(src, dest) { await ensureDir(dest); const entries = await fsp.readdir(src, { withFileTypes: true }); for (const entry of entries) { const srcPath = path.join(src, entry.name); const destPath = path.join(dest, entry.name); if (entry.isDirectory()) { await copyDir(srcPath, destPath); } else { await fsp.copyFile(srcPath, destPath); } } } __name(copyDir, "copyDir"); async function moveDir(src, dest) { await copyDir(src, dest); await deleteDirRecursive(src); } __name(moveDir, "moveDir"); async function emptyDir(dirPath) { const entries = await fsp.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const entryPath = path.join(dirPath, entry.name); if (entry.isDirectory()) { await fsp.rm(entryPath, { recursive: true, force: true }); } else { await fsp.unlink(entryPath); } } } __name(emptyDir, "emptyDir"); async function getDirSize(dirPath) { let total = 0; const entries = await fsp.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const entryPath = path.join(dirPath, entry.name); if (entry.isDirectory()) { total += await getDirSize(entryPath); } else { const stat = await fsp.stat(entryPath); total += stat.size; } } return total; } __name(getDirSize, "getDirSize"); function watchDir(dirPath, callback) { const watcher = fs.watch(dirPath, (eventType, filename) => { callback(eventType, filename); }); return () => watcher.close(); } __name(watchDir, "watchDir"); async function findFilesByPattern(pattern, options = {}) { const matchPattern = /* @__PURE__ */ __name((name, pattern2) => { let escaped = pattern2.replaceAll(/[.+^${}()|[\]\\]/g, String.raw`\$&`); escaped = escaped.replaceAll("*", ".*").replaceAll("?", "."); const regex = new RegExp(`^${escaped}$`); return regex.test(name); }, "matchPattern"); const { cwd = process.cwd(), dot = false } = options; const segments = pattern.split("/"); async function walk(dir, segIndex) { if (segIndex >= segments.length) return []; const segment = segments[segIndex]; const entries = await fsp.readdir(dir, { withFileTypes: true }); let matchedFiles = []; for (const entry of entries) { const entryName = entry.name; if (!dot && entryName.startsWith(".")) continue; const fullPath = path.join(dir, entryName); const isLastSegment = segIndex === segments.length - 1; if (isLastSegment) { if (matchPattern(entryName, segment) && entry.isFile()) { matchedFiles.push(fullPath); } } else if (entry.isDirectory()) { if (matchPattern(entryName, segment)) { matchedFiles.push(...await walk(fullPath, segIndex + 1)); } } } return matchedFiles; } __name(walk, "walk"); return walk(cwd, 0); } __name(findFilesByPattern, "findFilesByPattern"); async function getSubdirectories(dirPath, recursive = false) { const entries = await fsp.readdir(dirPath, { withFileTypes: true }); const dirs = entries.filter((entry) => entry.isDirectory()).map((entry) => path.join(dirPath, entry.name)); if (recursive && dirs.length > 0) { const nestedDirs = await Promise.all(dirs.map((dir) => getSubdirectories(dir, true))); return [ ...dirs, ...nestedDirs.flat() ]; } return dirs; } __name(getSubdirectories, "getSubdirectories"); async function ensureEmptyDir(dirPath) { if (await isDirectory(dirPath)) { await emptyDir(dirPath); } else { try { await fsp.unlink(dirPath); } catch (error) { if (error.code !== "ENOENT") { throw error; } } await ensureDir(dirPath); } } __name(ensureEmptyDir, "ensureEmptyDir"); async function createTempDir(options = {}) { const prefix = options.prefix || "tmp-"; const parentDir = options.parentDir || os.tmpdir(); const dirName = `${prefix}${randomBytes(6).toString("hex")}`; const tempDirPath = path.join(parentDir, dirName); await ensureDir(tempDirPath); const cleanup = /* @__PURE__ */ __name(async () => { try { await deleteDirRecursive(tempDirPath); } catch { } }, "cleanup"); if (options.cleanup) { process.once("exit", () => { try { fs.rmSync(tempDirPath, { recursive: true, force: true }); } catch { } }); const signals = [ "SIGINT", "SIGTERM", "SIGUSR1", "SIGUSR2" ]; for (const signal of signals) { process.once(signal, () => { cleanup().then(() => process.exit()); }); } } return { path: tempDirPath, cleanup }; } __name(createTempDir, "createTempDir"); async function findNewestFile(dirPath, recursive = false) { const files = await listFiles(dirPath, recursive); if (files.length === 0) return null; const stats = await Promise.all(files.map(async (file) => ({ path: file, mtime: (await fsp.stat(file)).mtime }))); const sorted = stats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime()); return sorted[0].path; } __name(findNewestFile, "findNewestFile"); async function findOldestFile(dirPath, recursive = false) { const files = await listFiles(dirPath, recursive); if (files.length === 0) return null; const stats = await Promise.all(files.map(async (file) => ({ path: file, mtime: (await fsp.stat(file)).mtime }))); return stats.sort((a, b) => a.mtime.getTime() - b.mtime.getTime())[0].path; } __name(findOldestFile, "findOldestFile"); async function findInDir(dirPath, predicate, recursive = false) { const entries = await fsp.readdir(dirPath, { withFileTypes: true }); const results = []; for (const entry of entries) { const entryPath = path.join(dirPath, entry.name); const stat = await fsp.stat(entryPath); if (await predicate(entryPath, stat)) { results.push(entryPath); } if (recursive && entry.isDirectory()) { const nestedResults = await findInDir(entryPath, predicate, true); results.push(...nestedResults); } } return results; } __name(findInDir, "findInDir"); async function watchDirRecursive(dirPath, callback, includeSubdirs = true) { const normalizedBasePath = path.normalize(dirPath); const watchers = []; const addWatcher = /* @__PURE__ */ __name((dir) => { try { const watcher = fs.watch(dir, (eventType, filename) => { if (!filename) return; const fullPath = path.join(dir, filename); const relativePath = path.relative(normalizedBasePath, fullPath); callback(eventType, relativePath); if (eventType === "rename" && includeSubdirs) { setTimeout(async () => { try { if (await isDirectory(fullPath)) { addWatcher(fullPath); } } catch { } }, 100); } }); watchers.push(watcher); } catch { } }, "addWatcher"); addWatcher(normalizedBasePath); if (includeSubdirs) { const subdirs = await getSubdirectories(normalizedBasePath, true); for (const subdir of subdirs) { addWatcher(subdir); } } return () => { for (const watcher of watchers) { watcher.close(); } }; } __name(watchDirRecursive, "watchDirRecursive"); async function getDirStats(dirPath) { let fileCount = 0; let dirCount = 0; let totalSize = 0; async function processDir(currentPath) { const entries = await fsp.readdir(currentPath, { withFileTypes: true }); for (const entry of entries) { const entryPath = path.join(currentPath, entry.name); if (entry.isDirectory()) { dirCount++; await processDir(entryPath); } else if (entry.isFile()) { fileCount++; const stat = await fsp.stat(entryPath); totalSize += stat.size; } } } __name(processDir, "processDir"); await processDir(dirPath); return { fileCount, dirCount, totalSize }; } __name(getDirStats, "getDirStats"); async function walkDir(dirPath, options) { const traversalOrder = options.traversalOrder || "pre"; const entries = await fsp.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const entryPath = path.join(dirPath, entry.name); const stats = await fsp.stat(entryPath); const entryInfo = { path: entryPath, name: entry.name, isDirectory: entry.isDirectory(), stats }; if (entry.isDirectory()) { if (traversalOrder === "pre") { const shouldContinue = await options.visitorFn(entryInfo); if (shouldContinue !== false) { await walkDir(entryPath, options); } } else { await walkDir(entryPath, options); await options.visitorFn(entryInfo); } } else { await options.visitorFn(entryInfo); } } if (traversalOrder === "post") { const stats = await fsp.stat(dirPath); const entryInfo = { path: dirPath, name: path.basename(dirPath), isDirectory: true, stats }; await options.visitorFn(entryInfo); } } __name(walkDir, "walkDir"); export { copyDir, createTempDir, deleteDirRecursive, emptyDir, ensureDir, ensureEmptyDir, findFilesByPattern, findInDir, findNewestFile, findOldestFile, getDirSize, getDirStats, getSubdirectories, isDirectory, listFiles, moveDir, walkDir, watchDir, watchDirRecursive };