find-remove
Version:
recursively finds files and/or directories by filter options from a start directory onwards and deletes these according to plenty of options you can configure. useful if you want to clean up stuff within a directory in your node.js app.
1 lines • 11.9 kB
Source Map (JSON)
{"version":3,"file":"find-remove.modern.mjs","sources":["../src/index.ts"],"sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\nimport { rimrafSync } from \"rimraf\";\n\nlet now: number | undefined;\nlet testRun: boolean | undefined;\n\ninterface Options {\n test?: boolean;\n limit?: number;\n totalRemoved?: number;\n maxLevel?: number;\n dir?: string | string[];\n regex?: boolean;\n prefix?: string;\n ignore?: string | string[];\n extensions?: string | string[];\n files?: string | string[];\n age?: { seconds?: number };\n}\n\nfunction isOlder(path: string, ageSeconds: number) {\n if (!now) return false;\n const stats = fs.statSync(path);\n const mtime = stats.mtime.getTime();\n const expirationTime = mtime + ageSeconds * 1000;\n\n return now > expirationTime;\n}\n\nfunction getLimit(options: Options = {}) {\n if (options.limit !== undefined) {\n return options.limit;\n }\n\n return -1;\n}\n\nfunction getTotalRemoved(options: Options = {}) {\n if (options.totalRemoved !== undefined) {\n return options.totalRemoved;\n }\n\n return -2;\n}\n\nfunction isOverTheLimit(options: Options = {}) {\n return getTotalRemoved(options) >= getLimit(options);\n}\n\nfunction getMaxLevel(options: Options = {}) {\n if (options.maxLevel !== undefined) {\n return options.maxLevel;\n }\n\n return -1;\n}\n\nfunction getAgeSeconds(options: Options = {}) {\n if (!options.age) {\n return null;\n }\n\n return options.age.seconds ?? null;\n}\n\nfunction doDeleteDirectory(\n currentDir: string,\n currentLevel: number,\n options: Options = {},\n) {\n let doDelete = false;\n\n const dir = options.dir;\n\n if (dir) {\n const ageSeconds = getAgeSeconds(options);\n const basename = path.basename(currentDir);\n\n if (Array.isArray(dir)) {\n doDelete = dir.indexOf(\"*\") !== -1 || dir.indexOf(basename) !== -1;\n } else if (\n (options.regex && basename.match(new RegExp(dir))) ||\n basename === dir ||\n dir === \"*\"\n ) {\n doDelete = true;\n }\n\n if (doDelete && options.limit !== undefined) {\n doDelete = !isOverTheLimit(options);\n }\n\n if (doDelete && options.maxLevel !== undefined && currentLevel > 0) {\n doDelete = currentLevel <= getMaxLevel(options);\n }\n\n if (ageSeconds && doDelete) {\n doDelete = isOlder(currentDir, ageSeconds);\n }\n }\n\n return doDelete;\n}\n\nfunction doDeleteFile(currentFile: string, options: Options = {}) {\n // by default it deletes nothing\n let doDelete = false;\n\n const extensions = options.extensions ? options.extensions : null;\n const files = options.files ? options.files : null;\n const prefix = options.prefix ? options.prefix : null;\n const ignore = options.ignore ?? null;\n\n // return the last portion of a path, the filename aka basename\n const basename = path.basename(currentFile);\n\n if (files) {\n if (Array.isArray(files)) {\n doDelete = files.indexOf(\"*.*\") !== -1 || files.indexOf(basename) !== -1;\n } else {\n if ((options.regex && basename.match(new RegExp(files))) || files === \"*.*\") {\n doDelete = true;\n } else {\n doDelete = basename === files;\n }\n }\n }\n\n if (!doDelete && extensions) {\n const currentExt = path.extname(currentFile);\n\n if (Array.isArray(extensions)) {\n doDelete = extensions.indexOf(currentExt) !== -1;\n } else {\n doDelete = currentExt === extensions;\n }\n }\n\n if (!doDelete && prefix) {\n doDelete = basename.indexOf(prefix) === 0;\n }\n\n if (doDelete && options.limit !== undefined) {\n doDelete = !isOverTheLimit(options);\n }\n\n if (doDelete && ignore) {\n if (Array.isArray(ignore)) {\n doDelete = !(ignore.indexOf(basename) !== -1);\n } else {\n doDelete = !(basename === ignore);\n }\n }\n\n if (doDelete) {\n const ageSeconds = getAgeSeconds(options);\n\n if (ageSeconds) {\n doDelete = isOlder(currentFile, ageSeconds);\n }\n }\n\n return doDelete;\n}\n\nfunction hasStats(dir: string) {\n try {\n fs.lstatSync(dir);\n return true;\n } catch (err) {\n return false;\n }\n}\n\n/**\n * FindRemoveSync(currentDir, options) takes any start directory and searches files from there for removal.\n * the selection of files for removal depends on the given options. when no options are given, or only the maxLevel\n * parameter is given, then everything is removed as if there were no filters.\n *\n * Beware: everything happens synchronously.\n *\n *\n * @param {string} currentDir any directory to operate within. it will seek files and/or directories recursively from there.\n * beware that it deletes the given currentDir when no options or only the maxLevel parameter are given.\n * @param options json object with optional properties like extensions, files, ignore, maxLevel and age.seconds.\n * @return {Object} json object of files and/or directories that were found and successfully removed.\n * @api public\n */\nconst findRemoveSync = function (\n currentDir: string,\n options: Options = {},\n currentLevel?: number,\n) {\n let removed: Record<string, boolean> = {};\n\n if (isOverTheLimit(options)) {\n // Return early in that case\n return removed;\n }\n\n let deleteDirectory = false;\n\n const dirExists = fs.existsSync(currentDir);\n const dirHasStats = hasStats(currentDir);\n\n if (dirExists && !dirHasStats) {\n // Must be a broken symlink. Flag it for deletion. See:\n // https://github.com/binarykitchen/find-remove/issues/42\n deleteDirectory = true;\n } else if (dirExists) {\n const maxLevel = getMaxLevel(options);\n\n if (options.limit !== undefined) {\n options.totalRemoved =\n options.totalRemoved !== undefined ? getTotalRemoved(options) : 0;\n }\n\n if (currentLevel === undefined) {\n currentLevel = 0;\n } else {\n currentLevel++;\n }\n\n if (currentLevel < 1) {\n now = new Date().getTime();\n testRun = options.test;\n } else {\n // check directories before deleting files inside.\n // this to maintain the original creation time,\n // because linux modifies creation date of folders when files within have been deleted.\n deleteDirectory = doDeleteDirectory(currentDir, currentLevel, options);\n }\n\n if (maxLevel === -1 || currentLevel < maxLevel) {\n const filesInDir = fs.readdirSync(currentDir);\n\n filesInDir.forEach(function (file) {\n const currentFile = path.join(currentDir, file);\n let skip = false;\n let stat;\n\n try {\n stat = fs.statSync(currentFile);\n } catch (exc) {\n // ignore\n skip = true;\n }\n\n if (skip) {\n // ignore, do nothing\n } else if (stat?.isDirectory()) {\n // the recursive call\n const result = findRemoveSync(currentFile, options, currentLevel);\n\n // merge results\n removed = { ...removed, ...result };\n\n if (options.totalRemoved !== undefined) {\n options.totalRemoved += Object.keys(result).length;\n }\n } else if (doDeleteFile(currentFile, options)) {\n let unlinked;\n\n if (!testRun) {\n try {\n fs.unlinkSync(currentFile);\n unlinked = true;\n } catch (exc) {\n // ignore\n }\n } else {\n unlinked = true;\n }\n\n if (unlinked) {\n removed[currentFile] = true;\n\n if (options.totalRemoved !== undefined) {\n options.totalRemoved++;\n }\n }\n }\n });\n }\n }\n\n if (deleteDirectory) {\n if (!testRun) {\n rimrafSync(currentDir);\n }\n\n if (options.totalRemoved === undefined) {\n // for limit of files - we do not want to count the directories\n removed[currentDir] = true;\n }\n }\n\n return removed;\n};\n\nexport default findRemoveSync;\n"],"names":["now","testRun","isOlder","path","ageSeconds","mtime","fs","statSync","getTime","getTotalRemoved","options","undefined","totalRemoved","isOverTheLimit","limit","getLimit","getMaxLevel","maxLevel","getAgeSeconds","_options$age$seconds","age","seconds","findRemoveSync","currentDir","currentLevel","removed","deleteDirectory","dirExists","existsSync","dirHasStats","dir","lstatSync","err","hasStats","Date","test","doDelete","basename","Array","isArray","indexOf","regex","match","RegExp","doDeleteDirectory","readdirSync","forEach","file","_stat","currentFile","join","stat","skip","exc","isDirectory","result","_extends","Object","keys","length","_options$ignore","extensions","files","prefix","ignore","currentExt","extname","doDeleteFile","unlinked","unlinkSync","rimrafSync"],"mappings":"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"}