├── .eslintrc.cjs ├── .github └── workflows │ └── push.yml ├── .gitignore ├── .nvmrc ├── .vscode ├── extenstions.json └── settings.json ├── README.md ├── dist ├── find-remove.js ├── find-remove.js.map ├── find-remove.mjs ├── find-remove.mjs.map ├── find-remove.modern.mjs ├── find-remove.modern.mjs.map ├── find-remove.umd.js ├── find-remove.umd.js.map ├── src │ └── index.d.ts └── tests │ └── basics.d.ts ├── package-lock.json ├── package.json ├── prettier.config.cjs ├── src └── index.ts ├── tests └── basics.ts └── tsconfig.json /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const PROJECTS = ["./tsconfig.json"]; 4 | 5 | module.exports = { 6 | parser: "@typescript-eslint/parser", 7 | parserOptions: { 8 | ecmaVersion: 2022, 9 | project: PROJECTS, 10 | }, 11 | extends: [ 12 | "eslint:recommended", 13 | "plugin:@typescript-eslint/recommended", 14 | "plugin:import/recommended", 15 | "plugin:import/typescript", 16 | ], 17 | plugins: ["@typescript-eslint", "import"], 18 | env: { 19 | es2022: true, 20 | node: true, 21 | }, 22 | settings: { 23 | "import/resolver": { 24 | typescript: true, 25 | node: true, 26 | }, 27 | }, 28 | rules: { 29 | // Turns on errors for missing imports which is great 30 | "import/no-unresolved": "error", 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | name: Test Runner for find-remove 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | jobs: 13 | build-test-lint-check: 14 | name: Build, Test, Prettier, Lint and Check 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | 21 | - name: Install Node.js via nvm 22 | shell: bash --login {0} 23 | run: | 24 | nvm install --no-progress 25 | echo "$(dirname $(nvm which node))" >> $GITHUB_PATH 26 | 27 | - name: Install npm dependencies 28 | run: npm ci 29 | 30 | - name: Build assets 31 | run: npm run build 32 | 33 | - name: Run tests 34 | uses: coactions/setup-xvfb@v1 35 | with: 36 | run: npm run test 37 | 38 | - name: Run prettier 39 | run: npm run prettier 40 | 41 | - name: Run linter 42 | run: npm run lint 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | package-lock.json 4 | *.code-workspace -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v22.8.0 -------------------------------------------------------------------------------- /.vscode/extenstions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "files.trimTrailingWhitespace": true, 4 | "editor.defaultFormatter": "esbenp.prettier-vscode", 5 | "editor.formatOnSave": true, 6 | "prettier.configPath": "./prettier.config.cjs", 7 | "cSpell.words": [ 8 | "blahblah", 9 | "fmerge", 10 | "hehehe", 11 | "microbundle", 12 | "nodeunit", 13 | "randomstring", 14 | "someth" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # find-remove 2 | 3 | finally in typescript (since v5) 4 | 5 | recursively finds files by filter options from a start directory onwards and deletes only those which meet conditions you can define. useful if you want to clean up a directory in your node.js app. 6 | 7 | you can filter by extensions, names, level in directory structure, file creation date and ignore by name, yeah! 8 | 9 | ## installation 10 | 11 | to install find-remove, use [npm](http://github.com/isaacs/npm): 12 | 13 | $ npm install -S find-remove 14 | 15 | then in your node.js app, get reference to the function like that: 16 | 17 | ```ts 18 | import findRemoveSync from "find-remove"; 19 | ``` 20 | 21 | ## quick examples 22 | 23 | ### 1. delete all _.bak or _.log files within the /temp/ directory 24 | 25 | ```ts 26 | const result = findRemoveSync("/temp", { extensions: [".bak", ".log"] }); 27 | ``` 28 | 29 | the return value `result` is a json object with successfully deleted files. if you output `result` to the console, you will get something like this: 30 | 31 | ``` 32 | { 33 | '/tmp/haumiblau.bak': true, 34 | '/tmp/dump.log': true 35 | } 36 | ``` 37 | 38 | ### 2. delete all files called 'dump.log' within the /temp/ directory and within its subfolders 39 | 40 | ```ts 41 | const result = findRemoveSync("/temp", { files: "dump.log" }); 42 | ``` 43 | 44 | ### 3. same as above, but also deletes any subfolders 45 | 46 | ```ts 47 | const result = findRemoveSync("/temp", { files: "dump.log", dir: "*" }); 48 | ``` 49 | 50 | ### 4. delete all \*.bak files but not file 'haumiblau.bak' 51 | 52 | ```ts 53 | const result = findRemoveSync("/temp", { extensions: [".bak"], ignore: "haumiblau.bak" }); 54 | ``` 55 | 56 | ### 5. delete recursively any subdirectory called 'CVS' within /dist/ 57 | 58 | ```ts 59 | const result = findRemoveSync("/dist", { dir: "CVS" }); 60 | ``` 61 | 62 | ### 6. delete all jpg files older than one hour with limit of 100 files deletion per operation 63 | 64 | ```ts 65 | const result = findRemoveSync("/tmp", { 66 | age: { seconds: 3600 }, 67 | extensions: ".jpg", 68 | limit: 100, 69 | }); 70 | ``` 71 | 72 | ### 7. delete all files with prefix 'filenamestartswith' 73 | 74 | ```javascript 75 | const result = findRemoveSync("/tmp", { prefix: "filenamestartswith" }); 76 | ``` 77 | 78 | ### 8. apply filter options only for two levels inside the /temp directory for all tmp files 79 | 80 | ```ts 81 | const result = findRemoveSync("/tmp", { maxLevel: 2, extensions: ".tmp" }); 82 | ``` 83 | 84 | this deletes any `.tmp` files up to two levels, for example: `/tmp/level1/level2/a.tmp` 85 | 86 | but not `/tmp/level1/level2/level3/b.tmp` 87 | 88 | why the heck do we have this `maxLevel` option? because of performance. if you care about deep subfolders, apply that option to get a speed boost. 89 | 90 | ### 9. delete everything recursively (hey, who needs that when you can use nodejs' fs.unlink?) 91 | 92 | ```ts 93 | const result = findRemoveSync(rootDirectory, { dir: "*", files: "*.*" }); 94 | ``` 95 | 96 | ### 10. delete all files that match a regular expression 97 | 98 | ```ts 99 | const result = findRemoveSync(rootDirectory, { files: "example[1-3]", regex: true }); 100 | ``` 101 | 102 | this deletes files `example1.txt`, `example2.txt`, and `example3.txt`, but not `example8.txt`. 103 | 104 | ### 11. delete all directories that match a regular expression 105 | 106 | ```ts 107 | const result = findRemoveSync(rootDirectory, { dir: "^assets_", regex: true }); 108 | ``` 109 | 110 | this deletes all directories that start with `assets_`. 111 | 112 | ## api 113 | 114 | ### findRemoveSync(dir, options) 115 | 116 | findRemoveSync takes any start directory and searches files from there for removal. the selection of files for removal depends on the given options. and at last, it deletes the selected files/directories. 117 | 118 | **arguments** 119 | 120 | - `dir` - any directory to search for files and/or directories for deletion (does not delete that directory itself) 121 | - options - currently those properties are supported: 122 | - `files` - can be a string or an array of files you want to delete within `dir`. 123 | - `dir` - can be a string or an array of directories you want to delete within `dir`. 124 | - `extensions` - this too, can be a string or an array of file extensions you want to delete within `dir`. 125 | - `ignore` - useful to exclude some files. again, can be a string or an array of file names you do NOT want to delete within `dir` 126 | - `age.seconds` - can be any float number. findRemoveSync then compares it with the file stats and deletes those with modification times older than `age.seconds` 127 | - `limit` - can be any integer number. Will limit the number of files to be deleted at single operation to be `limit` 128 | - `prefix` - can be any string. Will delete any files that start with `prefix`. 129 | - `maxLevel` - advanced: limits filtering to a certain level. useful for performance. recommended for crawling huge directory trees. 130 | - `test` - advanced: set to true for a test run, meaning it does not delete anything but returns a JSON of files/directories it would have deleted. useful for testing. 131 | - `regex` - set to true to treat `files` or `dir` option strings as regular expression patterns. 132 | 133 | as a precaution, nothing happens when there are no options. 134 | 135 | the unit tests are good examples on how to use the above arguments. 136 | 137 | **returns** 138 | 139 | JSON of files/directories that were deleted. For limit option - will only return number of files deleted. 140 | 141 | ## todo 142 | 143 | - add more filtering options (e.g. combinations) 144 | - have an asynchronous solution 145 | - use streams instead 146 | 147 | ## license 148 | 149 | MIT 150 | -------------------------------------------------------------------------------- /dist/find-remove.js: -------------------------------------------------------------------------------- 1 | var e=require("fs"),t=require("path"),r=require("rimraf");function i(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n,a,o=/*#__PURE__*/i(e),l=/*#__PURE__*/i(t);function u(){return u=Object.assign?Object.assign.bind():function(e){for(var t=1;tr+1e3*t}function d(e){return void 0===e&&(e={}),void 0!==e.totalRemoved?e.totalRemoved:-2}function v(e){return void 0===e&&(e={}),d(e)>=function(e){return void 0===e&&(e={}),void 0!==e.limit?e.limit:-1}(e)}function c(e){return void 0===e&&(e={}),void 0!==e.maxLevel?e.maxLevel:-1}function s(e){var t;return void 0===e&&(e={}),e.age&&null!=(t=e.age.seconds)?t:null}var m=function(e,t,i){void 0===t&&(t={});var y={};if(v(t))return y;var x=!1,g=o.default.existsSync(e),O=function(e){try{return o.default.lstatSync(e),!0}catch(e){return!1}}(e);if(g&&!O)x=!0;else if(g){var R=c(t);void 0!==t.limit&&(t.totalRemoved=void 0!==t.totalRemoved?d(t):0),void 0===i?i=0:i++,i<1?(n=(new Date).getTime(),a=t.test):x=function(e,t,r){void 0===r&&(r={});var i=!1,n=r.dir;if(n){var a=s(r),o=l.default.basename(e);Array.isArray(n)?i=-1!==n.indexOf("*")||-1!==n.indexOf(o):(r.regex&&o.match(new RegExp(n))||o===n||"*"===n)&&(i=!0),i&&void 0!==r.limit&&(i=!v(r)),i&&void 0!==r.maxLevel&&t>0&&(i=t<=c(r)),a&&i&&(i=f(e,a))}return i}(e,i,t),(-1===R||i 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 = {};\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","stat","currentFile","join","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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"} -------------------------------------------------------------------------------- /dist/find-remove.mjs: -------------------------------------------------------------------------------- 1 | import e from"fs";import r from"path";import{rimrafSync as i}from"rimraf";function t(){return t=Object.assign?Object.assign.bind():function(e){for(var r=1;rt+1e3*i}function v(e){return void 0===e&&(e={}),void 0!==e.totalRemoved?e.totalRemoved:-2}function l(e){return void 0===e&&(e={}),v(e)>=function(e){return void 0===e&&(e={}),void 0!==e.limit?e.limit:-1}(e)}function f(e){return void 0===e&&(e={}),void 0!==e.maxLevel?e.maxLevel:-1}function d(e){var r;return void 0===e&&(e={}),e.age&&null!=(r=e.age.seconds)?r:null}var u=function(c,m,s){void 0===m&&(m={});var x={};if(l(m))return x;var y=!1,g=e.existsSync(c),p=function(r){try{return e.lstatSync(r),!0}catch(e){return!1}}(c);if(g&&!p)y=!0;else if(g){var O=f(m);void 0!==m.limit&&(m.totalRemoved=void 0!==m.totalRemoved?v(m):0),void 0===s?s=0:s++,s<1?(n=(new Date).getTime(),o=m.test):y=function(e,i,t){void 0===t&&(t={});var n=!1,o=t.dir;if(o){var v=d(t),u=r.basename(e);Array.isArray(o)?n=-1!==o.indexOf("*")||-1!==o.indexOf(u):(t.regex&&u.match(new RegExp(o))||u===o||"*"===o)&&(n=!0),n&&void 0!==t.limit&&(n=!l(t)),n&&void 0!==t.maxLevel&&i>0&&(n=i<=f(t)),v&&n&&(n=a(e,v))}return n}(c,s,m),(-1===O||s 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 = {};\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","stat","currentFile","join","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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"} -------------------------------------------------------------------------------- /dist/find-remove.modern.mjs: -------------------------------------------------------------------------------- 1 | import e from"fs";import t from"path";import{rimrafSync as n}from"rimraf";function i(){return i=Object.assign?Object.assign.bind():function(e){for(var t=1;ti+1e3*n}function a(e={}){return void 0!==e.totalRemoved?e.totalRemoved:-2}function c(e={}){return a(e)>=function(e={}){return void 0!==e.limit?e.limit:-1}(e)}function f(e={}){return void 0!==e.maxLevel?e.maxLevel:-1}function s(e={}){var t;return e.age&&null!=(t=e.age.seconds)?t:null}const u=function u(d,m={},v){let x={};if(c(m))return x;let y=!1;const g=e.existsSync(d),p=function(t){try{return e.lstatSync(t),!0}catch(e){return!1}}(d);if(g&&!p)y=!0;else if(g){const n=f(m);void 0!==m.limit&&(m.totalRemoved=void 0!==m.totalRemoved?a(m):0),void 0===v?v=0:v++,v<1?(r=(new Date).getTime(),o=m.test):y=function(e,n,i={}){let r=!1;const o=i.dir;if(o){const a=s(i),u=t.basename(e);Array.isArray(o)?r=-1!==o.indexOf("*")||-1!==o.indexOf(u):(i.regex&&u.match(new RegExp(o))||u===o||"*"===o)&&(r=!0),r&&void 0!==i.limit&&(r=!c(i)),r&&void 0!==i.maxLevel&&n>0&&(r=n<=f(i)),a&&r&&(r=l(e,a))}return r}(d,v,m),(-1===n||v 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 = {};\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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"} -------------------------------------------------------------------------------- /dist/find-remove.umd.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t(require("fs"),require("path"),require("rimraf")):"function"==typeof define&&define.amd?define(["fs","path","rimraf"],t):(e||self).findRemove=t(e.fs,e.path,e.rimraf)}(this,function(e,t,i){function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var r,a,o=/*#__PURE__*/n(e),f=/*#__PURE__*/n(t);function l(){return l=Object.assign?Object.assign.bind():function(e){for(var t=1;ti+1e3*t}function u(e){return void 0===e&&(e={}),void 0!==e.totalRemoved?e.totalRemoved:-2}function v(e){return void 0===e&&(e={}),u(e)>=function(e){return void 0===e&&(e={}),void 0!==e.limit?e.limit:-1}(e)}function c(e){return void 0===e&&(e={}),void 0!==e.maxLevel?e.maxLevel:-1}function s(e){var t;return void 0===e&&(e={}),e.age&&null!=(t=e.age.seconds)?t:null}var m=function(e,t,n){void 0===t&&(t={});var y={};if(v(t))return y;var x=!1,p=o.default.existsSync(e),g=function(e){try{return o.default.lstatSync(e),!0}catch(e){return!1}}(e);if(p&&!g)x=!0;else if(p){var h=c(t);void 0!==t.limit&&(t.totalRemoved=void 0!==t.totalRemoved?u(t):0),void 0===n?n=0:n++,n<1?(r=(new Date).getTime(),a=t.test):x=function(e,t,i){void 0===i&&(i={});var n=!1,r=i.dir;if(r){var a=s(i),o=f.default.basename(e);Array.isArray(r)?n=-1!==r.indexOf("*")||-1!==r.indexOf(o):(i.regex&&o.match(new RegExp(r))||o===r||"*"===r)&&(n=!0),n&&void 0!==i.limit&&(n=!v(i)),n&&void 0!==i.maxLevel&&t>0&&(n=t<=c(i)),a&&n&&(n=d(e,a))}return n}(e,n,t),(-1===h||n 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 = {};\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","stat","currentFile","join","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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"} -------------------------------------------------------------------------------- /dist/src/index.d.ts: -------------------------------------------------------------------------------- 1 | interface Options { 2 | test?: boolean; 3 | limit?: number; 4 | totalRemoved?: number; 5 | maxLevel?: number; 6 | dir?: string | string[]; 7 | regex?: boolean; 8 | prefix?: string; 9 | ignore?: string | string[]; 10 | extensions?: string | string[]; 11 | files?: string | string[]; 12 | age?: { 13 | seconds?: number; 14 | }; 15 | } 16 | /** 17 | * FindRemoveSync(currentDir, options) takes any start directory and searches files from there for removal. 18 | * the selection of files for removal depends on the given options. when no options are given, or only the maxLevel 19 | * parameter is given, then everything is removed as if there were no filters. 20 | * 21 | * Beware: everything happens synchronously. 22 | * 23 | * 24 | * @param {string} currentDir any directory to operate within. it will seek files and/or directories recursively from there. 25 | * beware that it deletes the given currentDir when no options or only the maxLevel parameter are given. 26 | * @param options json object with optional properties like extensions, files, ignore, maxLevel and age.seconds. 27 | * @return {Object} json object of files and/or directories that were found and successfully removed. 28 | * @api public 29 | */ 30 | declare const findRemoveSync: (currentDir: string, options?: Options, currentLevel?: number) => Record; 31 | export default findRemoveSync; 32 | -------------------------------------------------------------------------------- /dist/tests/basics.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "find-remove", 3 | "version": "5.1.1", 4 | "description": "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.", 5 | "main": "dist/find-remove.js", 6 | "author": "Michael Heuberger ", 7 | "keywords": [ 8 | "file", 9 | "remove", 10 | "rmdir", 11 | "unlink", 12 | "find", 13 | "filter", 14 | "recursive", 15 | "fs", 16 | "delete", 17 | "extension", 18 | "file stats" 19 | ], 20 | "engines": { 21 | "node": ">=20.12.2", 22 | "npm": ">=10.5.0" 23 | }, 24 | "dependencies": { 25 | "rimraf": "6.0.1" 26 | }, 27 | "prettier": "./prettier.config.cjs", 28 | "devDependencies": { 29 | "@tsconfig/node22": "22.0.0", 30 | "@tsconfig/strictest": "2.0.5", 31 | "@types/node": "22.13.9", 32 | "@types/randomstring": "1.3.0", 33 | "@types/tape": "5.8.1", 34 | "@typescript-eslint/eslint-plugin": "7.18.0", 35 | "@typescript-eslint/parser": "7.18.0", 36 | "cross-env": "7.0.3", 37 | "eslint": "8.56.0", 38 | "eslint-config-prettier": "9.1.0", 39 | "eslint-import-resolver-typescript": "3.8.3", 40 | "eslint-plugin-import": "2.31.0", 41 | "eslint-plugin-node": "11.1.0", 42 | "eslint-plugin-promise": "6.0.1", 43 | "microbundle": "0.15.1", 44 | "mkdirp": "3.0.1", 45 | "prettier": "3.5.3", 46 | "randomstring": "1.3.1", 47 | "tape": "5.9.0", 48 | "tsx": "4.19.3", 49 | "typescript": "5.8.2" 50 | }, 51 | "source": "src/index.ts", 52 | "module": "dist/find-remove.mjs", 53 | "unpkg": "dist/find-remove.umd.js", 54 | "scripts": { 55 | "test": "cross-env NODE_OPTIONS='--import=tsx' tape tests/basics.ts", 56 | "lint": "eslint --color ./src ./tests", 57 | "lint:fix": "npm --silent lint --fix; exit 0", 58 | "prettier": "prettier --check ./src ./tests", 59 | "prettier:fix": "prettier --write ./src ./tests", 60 | "build": "microbundle", 61 | "types:check": "tsc --noEmit" 62 | }, 63 | "repository": { 64 | "type": "git", 65 | "url": "https://github.com/binarykitchen/find-remove.git" 66 | }, 67 | "license": "MIT", 68 | "readmeFilename": "README.md", 69 | "directories": { 70 | "test": "tests" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /prettier.config.cjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /** @type {import('prettier').Config} */ 4 | const config = { 5 | printWidth: 90, 6 | }; 7 | 8 | module.exports = config; 9 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import { rimrafSync } from "rimraf"; 4 | 5 | let now: number | undefined; 6 | let testRun: boolean | undefined; 7 | 8 | interface Options { 9 | test?: boolean; 10 | limit?: number; 11 | totalRemoved?: number; 12 | maxLevel?: number; 13 | dir?: string | string[]; 14 | regex?: boolean; 15 | prefix?: string; 16 | ignore?: string | string[]; 17 | extensions?: string | string[]; 18 | files?: string | string[]; 19 | age?: { seconds?: number }; 20 | } 21 | 22 | function isOlder(path: string, ageSeconds: number) { 23 | if (!now) return false; 24 | const stats = fs.statSync(path); 25 | const mtime = stats.mtime.getTime(); 26 | const expirationTime = mtime + ageSeconds * 1000; 27 | 28 | return now > expirationTime; 29 | } 30 | 31 | function getLimit(options: Options = {}) { 32 | if (options.limit !== undefined) { 33 | return options.limit; 34 | } 35 | 36 | return -1; 37 | } 38 | 39 | function getTotalRemoved(options: Options = {}) { 40 | if (options.totalRemoved !== undefined) { 41 | return options.totalRemoved; 42 | } 43 | 44 | return -2; 45 | } 46 | 47 | function isOverTheLimit(options: Options = {}) { 48 | return getTotalRemoved(options) >= getLimit(options); 49 | } 50 | 51 | function getMaxLevel(options: Options = {}) { 52 | if (options.maxLevel !== undefined) { 53 | return options.maxLevel; 54 | } 55 | 56 | return -1; 57 | } 58 | 59 | function getAgeSeconds(options: Options = {}) { 60 | if (!options.age) { 61 | return null; 62 | } 63 | 64 | return options.age.seconds ?? null; 65 | } 66 | 67 | function doDeleteDirectory( 68 | currentDir: string, 69 | currentLevel: number, 70 | options: Options = {}, 71 | ) { 72 | let doDelete = false; 73 | 74 | const dir = options.dir; 75 | 76 | if (dir) { 77 | const ageSeconds = getAgeSeconds(options); 78 | const basename = path.basename(currentDir); 79 | 80 | if (Array.isArray(dir)) { 81 | doDelete = dir.indexOf("*") !== -1 || dir.indexOf(basename) !== -1; 82 | } else if ( 83 | (options.regex && basename.match(new RegExp(dir))) || 84 | basename === dir || 85 | dir === "*" 86 | ) { 87 | doDelete = true; 88 | } 89 | 90 | if (doDelete && options.limit !== undefined) { 91 | doDelete = !isOverTheLimit(options); 92 | } 93 | 94 | if (doDelete && options.maxLevel !== undefined && currentLevel > 0) { 95 | doDelete = currentLevel <= getMaxLevel(options); 96 | } 97 | 98 | if (ageSeconds && doDelete) { 99 | doDelete = isOlder(currentDir, ageSeconds); 100 | } 101 | } 102 | 103 | return doDelete; 104 | } 105 | 106 | function doDeleteFile(currentFile: string, options: Options = {}) { 107 | // by default it deletes nothing 108 | let doDelete = false; 109 | 110 | const extensions = options.extensions ? options.extensions : null; 111 | const files = options.files ? options.files : null; 112 | const prefix = options.prefix ? options.prefix : null; 113 | const ignore = options.ignore ?? null; 114 | 115 | // return the last portion of a path, the filename aka basename 116 | const basename = path.basename(currentFile); 117 | 118 | if (files) { 119 | if (Array.isArray(files)) { 120 | doDelete = files.indexOf("*.*") !== -1 || files.indexOf(basename) !== -1; 121 | } else { 122 | if ((options.regex && basename.match(new RegExp(files))) || files === "*.*") { 123 | doDelete = true; 124 | } else { 125 | doDelete = basename === files; 126 | } 127 | } 128 | } 129 | 130 | if (!doDelete && extensions) { 131 | const currentExt = path.extname(currentFile); 132 | 133 | if (Array.isArray(extensions)) { 134 | doDelete = extensions.indexOf(currentExt) !== -1; 135 | } else { 136 | doDelete = currentExt === extensions; 137 | } 138 | } 139 | 140 | if (!doDelete && prefix) { 141 | doDelete = basename.indexOf(prefix) === 0; 142 | } 143 | 144 | if (doDelete && options.limit !== undefined) { 145 | doDelete = !isOverTheLimit(options); 146 | } 147 | 148 | if (doDelete && ignore) { 149 | if (Array.isArray(ignore)) { 150 | doDelete = !(ignore.indexOf(basename) !== -1); 151 | } else { 152 | doDelete = !(basename === ignore); 153 | } 154 | } 155 | 156 | if (doDelete) { 157 | const ageSeconds = getAgeSeconds(options); 158 | 159 | if (ageSeconds) { 160 | doDelete = isOlder(currentFile, ageSeconds); 161 | } 162 | } 163 | 164 | return doDelete; 165 | } 166 | 167 | function hasStats(dir: string) { 168 | try { 169 | fs.lstatSync(dir); 170 | return true; 171 | } catch (err) { 172 | return false; 173 | } 174 | } 175 | 176 | /** 177 | * FindRemoveSync(currentDir, options) takes any start directory and searches files from there for removal. 178 | * the selection of files for removal depends on the given options. when no options are given, or only the maxLevel 179 | * parameter is given, then everything is removed as if there were no filters. 180 | * 181 | * Beware: everything happens synchronously. 182 | * 183 | * 184 | * @param {string} currentDir any directory to operate within. it will seek files and/or directories recursively from there. 185 | * beware that it deletes the given currentDir when no options or only the maxLevel parameter are given. 186 | * @param options json object with optional properties like extensions, files, ignore, maxLevel and age.seconds. 187 | * @return {Object} json object of files and/or directories that were found and successfully removed. 188 | * @api public 189 | */ 190 | const findRemoveSync = function ( 191 | currentDir: string, 192 | options: Options = {}, 193 | currentLevel?: number, 194 | ) { 195 | let removed: Record = {}; 196 | 197 | if (isOverTheLimit(options)) { 198 | // Return early in that case 199 | return removed; 200 | } 201 | 202 | let deleteDirectory = false; 203 | 204 | const dirExists = fs.existsSync(currentDir); 205 | const dirHasStats = hasStats(currentDir); 206 | 207 | if (dirExists && !dirHasStats) { 208 | // Must be a broken symlink. Flag it for deletion. See: 209 | // https://github.com/binarykitchen/find-remove/issues/42 210 | deleteDirectory = true; 211 | } else if (dirExists) { 212 | const maxLevel = getMaxLevel(options); 213 | 214 | if (options.limit !== undefined) { 215 | options.totalRemoved = 216 | options.totalRemoved !== undefined ? getTotalRemoved(options) : 0; 217 | } 218 | 219 | if (currentLevel === undefined) { 220 | currentLevel = 0; 221 | } else { 222 | currentLevel++; 223 | } 224 | 225 | if (currentLevel < 1) { 226 | now = new Date().getTime(); 227 | testRun = options.test; 228 | } else { 229 | // check directories before deleting files inside. 230 | // this to maintain the original creation time, 231 | // because linux modifies creation date of folders when files within have been deleted. 232 | deleteDirectory = doDeleteDirectory(currentDir, currentLevel, options); 233 | } 234 | 235 | if (maxLevel === -1 || currentLevel < maxLevel) { 236 | const filesInDir = fs.readdirSync(currentDir); 237 | 238 | filesInDir.forEach(function (file) { 239 | const currentFile = path.join(currentDir, file); 240 | let skip = false; 241 | let stat; 242 | 243 | try { 244 | // Add extra checks for invalid symlinks using lstatSync 245 | stat = fs.lstatSync(currentFile); 246 | } catch (exc) { 247 | // ignore 248 | skip = true; 249 | } 250 | 251 | if (skip) { 252 | // ignore, do nothing 253 | } else if (stat?.isDirectory()) { 254 | // the recursive call 255 | const result = findRemoveSync(currentFile, options, currentLevel); 256 | 257 | // merge results 258 | removed = { ...removed, ...result }; 259 | 260 | if (options.totalRemoved !== undefined) { 261 | options.totalRemoved += Object.keys(result).length; 262 | } 263 | } else if (doDeleteFile(currentFile, options)) { 264 | let unlinked; 265 | 266 | if (!testRun) { 267 | try { 268 | fs.unlinkSync(currentFile); 269 | unlinked = true; 270 | } catch (exc) { 271 | // ignore 272 | } 273 | } else { 274 | unlinked = true; 275 | } 276 | 277 | if (unlinked) { 278 | removed[currentFile] = true; 279 | 280 | if (options.totalRemoved !== undefined) { 281 | options.totalRemoved++; 282 | } 283 | } 284 | } 285 | }); 286 | } 287 | } 288 | 289 | if (deleteDirectory) { 290 | if (!testRun) { 291 | rimrafSync(currentDir); 292 | } 293 | 294 | if (options.totalRemoved === undefined) { 295 | // for limit of files - we do not want to count the directories 296 | removed[currentDir] = true; 297 | } 298 | } 299 | 300 | return removed; 301 | }; 302 | 303 | export default findRemoveSync; 304 | -------------------------------------------------------------------------------- /tests/basics.ts: -------------------------------------------------------------------------------- 1 | import test from "tape"; 2 | import randomstring from "randomstring"; 3 | import { mkdirp } from "mkdirp"; 4 | import path from "path"; 5 | import { existsSync } from "fs"; 6 | import { rimrafSync } from "rimraf"; 7 | import os from "os"; 8 | 9 | import findRemoveSync from "../src/index"; 10 | import { writeFile } from "fs/promises"; 11 | 12 | const rootDirectory = path.join(os.tmpdir(), "find-remove"); 13 | 14 | function generateRandomFilename(ext?: string) { 15 | let filename = randomstring.generate(24); 16 | 17 | if (ext) { 18 | filename += "." + ext; 19 | } 20 | 21 | return filename; 22 | } 23 | 24 | function sleep(ms: number) { 25 | return new Promise((resolve) => { 26 | setTimeout(resolve, ms); 27 | }); 28 | } 29 | 30 | /* 31 | pre defined directories: 32 | + rootDirectory 33 | 34 | * randomFile1 (*.bak) 35 | * randomFile2 (*.log) 36 | * randomFile3 (*.log) 37 | * randomFile4 (*.csv) 38 | 39 | + CVS (directory3) 40 | + directory1 41 | + CVS (directory1_3) 42 | + directory1_1 43 | + directory1_2 44 | + directory1_2_1 45 | * randomFile1_2_1_1 (*.log) 46 | * randomFile1_2_1_2 (*.bak) 47 | * randomFile1_2_1_3 (*.bak) 48 | * fixFile1_2_1_4 (something.jpg) 49 | * fixFile1_2_1_5 (something.png) 50 | + directory1_2_2 51 | + directory2 52 | * randomFile2_1 (*.bak) 53 | * randomFile2_2 (*.csv) 54 | + patternDirectory_token (directory4) 55 | + token_patternDirectory (directory5) 56 | */ 57 | 58 | const directory1 = path.join(rootDirectory, "directory1"); 59 | const directory2 = path.join(rootDirectory, "directory2"); 60 | const directory3 = path.join(rootDirectory, "CVS"); 61 | const directory4 = path.join(rootDirectory, "patternDirectory_token"); 62 | const directory5 = path.join(rootDirectory, "token_patternDirectory"); 63 | 64 | const directory1_1 = path.join(directory1, "directory1_1"); 65 | const directory1_2 = path.join(directory1, "directory1_2"); 66 | const directory1_3 = path.join(directory1, "CVS"); 67 | 68 | const directory1_2_1 = path.join(directory1_2, "directory1_2_1"); 69 | const directory1_2_2 = path.join(directory1_2, "directory1_2_2"); 70 | 71 | // mix of pre defined and random file names 72 | const randomFilename1 = generateRandomFilename("bak"); 73 | const randomFile1 = path.join(rootDirectory, randomFilename1); 74 | const randomFilename2 = generateRandomFilename("log"); 75 | const randomFile2 = path.join(rootDirectory, randomFilename2); 76 | const randomFile3 = path.join(rootDirectory, generateRandomFilename("log")); 77 | const randomFile4 = path.join(rootDirectory, generateRandomFilename("csv")); 78 | 79 | const randomFile2_1 = path.join(directory2, generateRandomFilename("bak")); 80 | const randomFile2_2 = path.join(directory2, generateRandomFilename("csv")); 81 | 82 | const randomFilename1_2_1_1 = generateRandomFilename("log"); 83 | const randomFile1_2_1_1 = path.join(directory1_2_1, randomFilename1_2_1_1); 84 | const randomFile1_2_1_2 = path.join(directory1_2_1, generateRandomFilename("bak")); 85 | const randomFilename1_2_1_3 = generateRandomFilename("bak"); 86 | const randomFile1_2_1_3 = path.join(directory1_2_1, randomFilename1_2_1_3); 87 | 88 | const fixFilename1_2_1_4 = "something.jpg"; 89 | const fixFile1_2_1_4 = path.join(directory1_2_1, fixFilename1_2_1_4); 90 | const fixFilename1_2_1_5 = "something.png"; 91 | const fixFile1_2_1_5 = path.join(directory1_2_1, fixFilename1_2_1_5); 92 | 93 | async function createFakeDirectoryTree() { 94 | try { 95 | await mkdirp(directory1); 96 | await mkdirp(directory2); 97 | await mkdirp(directory3); 98 | await mkdirp(directory1_1); 99 | await mkdirp(directory1_2); 100 | await mkdirp(directory1_3); 101 | await mkdirp(directory1_2_1); 102 | await mkdirp(directory1_2_2); 103 | 104 | await writeFile(randomFile1, ""); 105 | await writeFile(randomFile2, ""); 106 | await writeFile(randomFile3, ""); 107 | await writeFile(randomFile4, ""); 108 | await writeFile(randomFile2_1, ""); 109 | await writeFile(randomFile2_2, ""); 110 | await writeFile(randomFile1_2_1_1, ""); 111 | await writeFile(randomFile1_2_1_2, ""); 112 | await writeFile(randomFile1_2_1_3, ""); 113 | await writeFile(fixFile1_2_1_4, ""); 114 | await writeFile(fixFile1_2_1_5, ""); 115 | } catch (exc) { 116 | console.error(exc); 117 | } 118 | } 119 | 120 | async function createFakeDirectoryTreeRegex() { 121 | try { 122 | await createFakeDirectoryTree(); 123 | await mkdirp(directory4); 124 | await mkdirp(directory5); 125 | } catch (exc) { 126 | console.error(exc); 127 | } 128 | } 129 | 130 | function destroyFakeDirectoryTree() { 131 | rimrafSync(rootDirectory); 132 | } 133 | 134 | test("find-remove", function (t) { 135 | t.test("TC 1: tests without real files", function (tt) { 136 | tt.test("removing non-existing directory", function (ttt) { 137 | const dir = generateRandomFilename(); 138 | const result = findRemoveSync(dir); 139 | 140 | ttt.strictEqual(Object.keys(result).length, 0, "returned empty"); 141 | 142 | ttt.end(); 143 | }); 144 | }); 145 | 146 | t.test("TC 2: tests with real files", async function (tt) { 147 | tt.teardown(() => { 148 | destroyFakeDirectoryTree(); 149 | }); 150 | 151 | tt.test("findRemoveSync(nonexisting)", async function (ttt) { 152 | await createFakeDirectoryTree(); 153 | 154 | const result = findRemoveSync("/tmp/blahblah/hehehe/yo/what/"); 155 | 156 | ttt.strictEqual(Object.keys(result).length, 0, "did nothing."); 157 | 158 | ttt.end(); 159 | }); 160 | 161 | tt.test("findRemoveSync(no params)", async function (ttt) { 162 | await createFakeDirectoryTree(); 163 | 164 | const result = findRemoveSync(rootDirectory); 165 | 166 | ttt.strictEqual(Object.keys(result).length, 0, "did nothing."); 167 | 168 | const exists = existsSync(rootDirectory); 169 | ttt.equal(exists, true, "did not remove root directory"); 170 | 171 | const exists1_1 = existsSync(directory1_1); 172 | ttt.equal(exists1_1, true, "findRemoveSync(no params) did not remove directory1_1"); 173 | 174 | ttt.end(); 175 | }); 176 | 177 | tt.test("findRemoveSync(all files)", async function (ttt) { 178 | await createFakeDirectoryTree(); 179 | 180 | findRemoveSync(rootDirectory, { files: "*.*" }); 181 | 182 | const exists1_1 = existsSync(directory1_1); 183 | ttt.equal(exists1_1, true, "did not remove directory1_1"); 184 | 185 | const exists1_2_1_2 = existsSync(randomFile1_2_1_2); 186 | ttt.equal(exists1_2_1_2, false, "removed randomFile1_2_1_2 fine"); 187 | 188 | const exists1_2_1_3 = existsSync(randomFile1_2_1_3); 189 | ttt.equal(exists1_2_1_3, false, "removed randomFile1_2_1_3 fine"); 190 | 191 | ttt.end(); 192 | }); 193 | 194 | tt.test("findRemoveSync(all directories)", async function (ttt) { 195 | await createFakeDirectoryTree(); 196 | 197 | const result = findRemoveSync(rootDirectory, { dir: "*" }); 198 | ttt.strictEqual(Object.keys(result).length, 8, "all 8 directories deleted"); 199 | 200 | const exists1_1 = existsSync(directory1_1); 201 | ttt.equal(exists1_1, false, "removed directory1_1"); 202 | 203 | const exists1_2_1_2 = existsSync(randomFile1_2_1_2); 204 | ttt.equal(exists1_2_1_2, false, "removed randomFile1_2_1_2"); 205 | 206 | const exists1_2_1_3 = existsSync(randomFile1_2_1_3); 207 | ttt.equal(exists1_2_1_3, false, "removed randomFile1_2_1_3"); 208 | 209 | ttt.end(); 210 | }); 211 | 212 | tt.test("findRemoveSync(everything)", async function (ttt) { 213 | await createFakeDirectoryTree(); 214 | 215 | const result = findRemoveSync(rootDirectory, { dir: "*", files: "*.*" }); 216 | 217 | ttt.strictEqual( 218 | Object.keys(result).length, 219 | 19, 220 | "all 19 directories + files deleted", 221 | ); 222 | 223 | const exists1_1 = existsSync(directory1_1); 224 | ttt.equal(exists1_1, false, "removed directory1_1"); 225 | 226 | const exists1_2_1_2 = existsSync(randomFile1_2_1_2); 227 | ttt.equal(exists1_2_1_2, false, "did not remove randomFile1_2_1_2 fine"); 228 | 229 | const exists1_2_1_3 = existsSync(randomFile1_2_1_3); 230 | ttt.equal(exists1_2_1_3, false, "dit not remove randomFile1_2_1_3 fine"); 231 | 232 | ttt.end(); 233 | }); 234 | 235 | tt.test("findRemoveSync(files no hit)", async function (ttt) { 236 | await createFakeDirectoryTree(); 237 | 238 | findRemoveSync(rootDirectory, { files: "no.hit.me" }); 239 | 240 | const exists1_1 = existsSync(directory1_1); 241 | ttt.equal(exists1_1, true, "did not remove directory1_1"); 242 | 243 | const exists1_2_1_3 = existsSync(randomFile1_2_1_3); 244 | ttt.equal(exists1_2_1_3, true, "did not remove randomFile1_2_1_3"); 245 | 246 | ttt.end(); 247 | }); 248 | 249 | tt.test("findRemoveSync(directory1_2_1)", async function (ttt) { 250 | await createFakeDirectoryTree(); 251 | 252 | findRemoveSync(rootDirectory, { dir: "directory1_2_1" }); 253 | 254 | const exists1_2_1 = existsSync(directory1_2_1); 255 | ttt.equal(exists1_2_1, false, "did remove directory1_2_1"); 256 | 257 | const exists1_1 = existsSync(directory1_1); 258 | ttt.equal(exists1_1, true, "did not remove directory1_1"); 259 | 260 | ttt.end(); 261 | }); 262 | 263 | tt.test("findRemoveSync(one directory and all files)", async function (ttt) { 264 | await createFakeDirectoryTree(); 265 | 266 | const result = findRemoveSync(rootDirectory, { 267 | dir: "directory1_2_1", 268 | files: "*.*", 269 | }); 270 | 271 | const exists1_2_1 = existsSync(directory1_2_1); 272 | ttt.equal(exists1_2_1, false, "did remove directory1_2_1"); 273 | 274 | const exists1_1 = existsSync(directory1_1); 275 | ttt.equal(exists1_1, true, "did not remove directory1_1"); 276 | 277 | ttt.ok(result[randomFile1_2_1_1], "randomFile1_2_1_1 is in result"); 278 | ttt.ok(result[randomFile1_2_1_2], "randomFile1_2_1_2 is in result"); 279 | ttt.ok(result[randomFile1_2_1_3], "randomFile1_2_1_3 is in result"); 280 | ttt.ok(result[directory1_2_1], "directory1_2_1 is in result"); 281 | 282 | ttt.end(); 283 | }); 284 | 285 | tt.test("findRemoveSync(another directory and all files)", async function (ttt) { 286 | await createFakeDirectoryTree(); 287 | 288 | const result = findRemoveSync(rootDirectory, { dir: "directory2", files: "*.*" }); 289 | 290 | const exists2 = existsSync(directory2); 291 | ttt.equal(exists2, false, "directory2 not removed"); 292 | 293 | const exists1_2 = existsSync(directory1_2); 294 | ttt.equal(exists1_2, true, "directory1_2 not removed"); 295 | 296 | ttt.ok(result[randomFile2_1], "randomFile2_1 is in result"); 297 | 298 | ttt.end(); 299 | }); 300 | 301 | tt.test("findRemoveSync(all bak files from root)", async function (ttt) { 302 | await createFakeDirectoryTree(); 303 | 304 | findRemoveSync(rootDirectory, { extensions: ".bak" }); 305 | 306 | const exists1 = existsSync(randomFile1); 307 | const exists2_1 = existsSync(randomFile2_1); 308 | const exists1_2_1_2 = existsSync(randomFile1_2_1_2); 309 | const exists1_2_1_3 = existsSync(randomFile1_2_1_3); 310 | 311 | ttt.equal( 312 | exists1, 313 | false, 314 | "findRemoveSync(all bak files from root) removed randomFile1 fine", 315 | ); 316 | 317 | ttt.equal( 318 | exists2_1, 319 | false, 320 | "findRemoveSync(all bak files from root) removed exists2_1 fine", 321 | ); 322 | 323 | ttt.equal( 324 | exists1_2_1_2, 325 | false, 326 | "findRemoveSync(all bak files from root) removed exists1_2_1_2 fine", 327 | ); 328 | 329 | ttt.equal( 330 | exists1_2_1_3, 331 | false, 332 | "findRemoveSync(all bak files from root) removed exists1_2_1_3 fine", 333 | ); 334 | 335 | const exists3 = existsSync(randomFile3); 336 | const exists1_2_1_1 = existsSync(randomFile1_2_1_1); 337 | const exists0 = existsSync(rootDirectory); 338 | const exists1_2_1 = existsSync(directory1_2_1); 339 | 340 | ttt.equal( 341 | exists3, 342 | true, 343 | "findRemoveSync(all bak files from root) did not remove log file exists3", 344 | ); 345 | 346 | ttt.equal( 347 | exists1_2_1_1, 348 | true, 349 | "findRemoveSync(all bak files from root) did not remove log file exists1_2_1_1", 350 | ); 351 | 352 | ttt.equal( 353 | exists0, 354 | true, 355 | "findRemoveSync(all bak files from root) did not remove root directory", 356 | ); 357 | 358 | ttt.equal( 359 | exists1_2_1, 360 | true, 361 | "findRemoveSync(all bak files from root) did not remove directory directory1_2_1", 362 | ); 363 | 364 | ttt.end(); 365 | }); 366 | 367 | tt.test("findRemoveSync(all log files from directory1_2_1)", async function (ttt) { 368 | await createFakeDirectoryTree(); 369 | 370 | findRemoveSync(directory1_2_1, { extensions: ".log" }); 371 | 372 | const exists1_2_1_1 = existsSync(randomFile1_2_1_1); 373 | 374 | ttt.equal( 375 | exists1_2_1_1, 376 | false, 377 | "findRemoveSync(all log files from directory1_2_1) removed randomFile1_2_1_1 fine", 378 | ); 379 | 380 | const exists1_2_1_2 = existsSync(randomFile1_2_1_2); 381 | ttt.equal( 382 | exists1_2_1_2, 383 | true, 384 | "findRemoveSync(all log files from directory1_2_1) did not remove file randomFile1_2_1_2", 385 | ); 386 | 387 | const exists1_2_1 = existsSync(directory1_2_1); 388 | ttt.equal( 389 | exists1_2_1, 390 | true, 391 | "findRemoveSync(all log files from directory1_2_1) did not remove directory directory1_2_1", 392 | ); 393 | 394 | ttt.end(); 395 | }); 396 | 397 | tt.test("findRemoveSync(all bak or log files from root)", async function (ttt) { 398 | await createFakeDirectoryTree(); 399 | 400 | findRemoveSync(rootDirectory, { extensions: [".bak", ".log"] }); 401 | 402 | const exists1 = existsSync(randomFile1); 403 | const exists2_1 = existsSync(randomFile2_1); 404 | const exists1_2_1_2 = existsSync(randomFile1_2_1_2); 405 | const exists1_2_1_3 = existsSync(randomFile1_2_1_3); 406 | 407 | const exists2 = existsSync(randomFile2); 408 | const exists3 = existsSync(randomFile3); 409 | const exists1_2_1_1 = existsSync(randomFile1_2_1_1); 410 | 411 | ttt.equal( 412 | exists1, 413 | false, 414 | "findRemoveSync(all bak and log files from root) removed randomFile1 fine", 415 | ); 416 | 417 | ttt.equal( 418 | exists2_1, 419 | false, 420 | "findRemoveSync(all bak and log files from root) removed exists2_1 fine", 421 | ); 422 | 423 | ttt.equal( 424 | exists1_2_1_2, 425 | false, 426 | "findRemoveSync(all bak and log files from root) removed exists1_2_1_2 fine", 427 | ); 428 | 429 | ttt.equal( 430 | exists1_2_1_3, 431 | false, 432 | "findRemoveSync(all bak and log files from root) removed exists1_2_1_3 fine", 433 | ); 434 | 435 | ttt.equal( 436 | exists2, 437 | false, 438 | "findRemoveSync(all bak and log files from root) removed exists2 fine", 439 | ); 440 | 441 | ttt.equal( 442 | exists3, 443 | false, 444 | "findRemoveSync(all bak and log files from root) removed exists3 fine", 445 | ); 446 | 447 | ttt.equal( 448 | exists1_2_1_1, 449 | false, 450 | "findRemoveSync(all bak and log files from root) removed exists1_2_1_1 fine", 451 | ); 452 | 453 | const exists1_1 = existsSync(directory1_1); 454 | ttt.equal( 455 | exists1_1, 456 | true, 457 | "findRemoveSync(all bak and log files from root) did not remove directory1_1", 458 | ); 459 | 460 | ttt.end(); 461 | }); 462 | 463 | tt.test( 464 | "findRemoveSync(filename randomFilename1_2_1_1 from directory1_2", 465 | async function (ttt) { 466 | await createFakeDirectoryTree(); 467 | 468 | findRemoveSync(directory1_2, { files: randomFilename1_2_1_1 }); 469 | 470 | const exists1_2_1_1 = existsSync(randomFile1_2_1_1); 471 | ttt.equal( 472 | exists1_2_1_1, 473 | false, 474 | "findRemoveSync(filename randomFilename1_2_1_1 from directory1_2) removed randomFile1_2_1_1 fine", 475 | ); 476 | 477 | const exists1_2_1_2 = existsSync(randomFile1_2_1_2); 478 | ttt.equal( 479 | exists1_2_1_2, 480 | true, 481 | "findRemoveSync(filename randomFilename1_2_1_1 from directory1_2) did not remove randomFile1_2_1_2", 482 | ); 483 | 484 | const exists1_2 = existsSync(directory1_2); 485 | ttt.equal( 486 | exists1_2, 487 | true, 488 | "findRemoveSync(filename randomFilename1_2_1_1 from directory1_2) did not remove directory1_2", 489 | ); 490 | 491 | ttt.end(); 492 | }, 493 | ); 494 | 495 | tt.test("two files from root", async function (ttt) { 496 | await createFakeDirectoryTree(); 497 | 498 | findRemoveSync(rootDirectory, { files: [randomFilename2, randomFilename1_2_1_3] }); 499 | 500 | const exists2 = existsSync(randomFile2); 501 | ttt.equal( 502 | exists2, 503 | false, 504 | "findRemoveSync(two files from root) removed randomFile2 fine", 505 | ); 506 | 507 | const exists1_2_1_3 = existsSync(randomFile1_2_1_3); 508 | ttt.equal( 509 | exists1_2_1_3, 510 | false, 511 | "findRemoveSync(two files from root) removed randomFile1_2_1_3 fine", 512 | ); 513 | 514 | const exists1 = existsSync(randomFile1); 515 | ttt.equal( 516 | exists1, 517 | true, 518 | "findRemoveSync(two files from root) did not remove randomFile1", 519 | ); 520 | 521 | const exists0 = existsSync(rootDirectory); 522 | ttt.equal( 523 | exists0, 524 | true, 525 | "findRemoveSync(two files from root) did not remove root directory", 526 | ); 527 | 528 | ttt.end(); 529 | }); 530 | 531 | tt.test("files set to *.*", async function (ttt) { 532 | await createFakeDirectoryTree(); 533 | 534 | findRemoveSync(directory1_2_1, { files: "*.*" }); 535 | 536 | const exists1_2_1_1 = existsSync(randomFile1_2_1_1); 537 | ttt.equal( 538 | exists1_2_1_1, 539 | false, 540 | "findRemoveSync(files set to *.*) removed randomFile1_2_1_1 fine", 541 | ); 542 | 543 | const exists1_2_1_2 = existsSync(randomFile1_2_1_2); 544 | ttt.equal( 545 | exists1_2_1_2, 546 | false, 547 | "findRemoveSync(files set to *.*) removed randomFile1_2_1_2 fine", 548 | ); 549 | 550 | const exists1_2_1_3 = existsSync(randomFile1_2_1_3); 551 | ttt.equal( 552 | exists1_2_1_3, 553 | false, 554 | "findRemoveSync(files set to *.*) removed randomFile1_2_1_3 fine", 555 | ); 556 | 557 | const exists1_2_1 = existsSync(directory1_2_1); 558 | ttt.equal( 559 | exists1_2_1, 560 | true, 561 | "findRemoveSync(files set to *.* did not remove directory1_2_1", 562 | ); 563 | 564 | ttt.end(); 565 | }); 566 | 567 | tt.test("with mixed ext and file params", async function (ttt) { 568 | await createFakeDirectoryTree(); 569 | 570 | const result = findRemoveSync(rootDirectory, { 571 | files: randomFilename1, 572 | extensions: [".log"], 573 | }); 574 | 575 | const exists1 = existsSync(randomFile1); 576 | const exists2 = existsSync(randomFile2); 577 | const exists1_2_1_1 = existsSync(randomFile1_2_1_1); 578 | ttt.equal( 579 | exists1, 580 | false, 581 | "findRemoveSync(with mixed ext and file params) removed randomFile1 fine", 582 | ); 583 | 584 | ttt.equal( 585 | exists2, 586 | false, 587 | "findRemoveSync(with mixed ext and file params) removed randomFile2 fine", 588 | ); 589 | 590 | ttt.equal( 591 | exists1_2_1_1, 592 | false, 593 | "findRemoveSync(with mixed ext and file params) removed randomFile1_2_1_1 fine", 594 | ); 595 | 596 | const exists1_2_1 = existsSync(directory1_2_1); 597 | ttt.equal(exists1_2_1, true, "did not remove directory1_2_1"); 598 | 599 | ttt.strictEqual( 600 | typeof result[randomFile1], 601 | "boolean", 602 | "randomFile1 in result is boolean", 603 | ); 604 | 605 | ttt.strictEqual( 606 | typeof result[randomFile1_2_1_2], 607 | "undefined", 608 | "randomFile1_2_1_2 is NOT in result", 609 | ); 610 | 611 | ttt.end(); 612 | }); 613 | 614 | tt.test("with ignore param)", async function (ttt) { 615 | await createFakeDirectoryTree(); 616 | 617 | const result = findRemoveSync(rootDirectory, { 618 | files: "*.*", 619 | ignore: fixFilename1_2_1_4, 620 | }); 621 | 622 | const exists1_2_1_1 = existsSync(randomFile1_2_1_1); 623 | ttt.equal( 624 | exists1_2_1_1, 625 | false, 626 | "findRemoveSync(with ignore) did remove file randomFile1_2_1_1", 627 | ); 628 | 629 | const exists1_2_1_4 = existsSync(fixFile1_2_1_4); 630 | ttt.equal(exists1_2_1_4, true, "file fixFile1_2_1_4 not removed"); 631 | 632 | ttt.strictEqual( 633 | typeof result[randomFile1_2_1_1], 634 | "boolean", 635 | "randomFile1_2_1_1 in result is boolean", 636 | ); 637 | ttt.strictEqual( 638 | typeof result[fixFile1_2_1_4], 639 | "undefined", 640 | "fixFile1_2_1_4 is NOT in result", 641 | ); 642 | 643 | ttt.end(); 644 | }); 645 | 646 | tt.test("with ignore and jpg extension params", async function (ttt) { 647 | await createFakeDirectoryTree(); 648 | 649 | const result = findRemoveSync(rootDirectory, { 650 | ignore: fixFilename1_2_1_4, 651 | extensions: ".jpg", 652 | }); 653 | 654 | const exists1_2_1_1 = existsSync(randomFile1_2_1_1); 655 | const exists1_2_1_4 = existsSync(fixFile1_2_1_4); 656 | 657 | ttt.equal( 658 | exists1_2_1_1, 659 | true, 660 | "findRemoveSync(with ignore + jpg extension) did not remove file randomFile1_2_1_1", 661 | ); 662 | 663 | ttt.equal( 664 | exists1_2_1_4, 665 | true, 666 | "findRemoveSync(with ignore + jpg extension) did not remove file fixFile1_2_1_4", 667 | ); 668 | 669 | ttt.strictEqual( 670 | typeof result[randomFile1_2_1_1], 671 | "undefined", 672 | "randomFile1_2_1_1 is NOT in result", 673 | ); 674 | 675 | ttt.strictEqual( 676 | typeof result[fixFile1_2_1_4], 677 | "undefined", 678 | "fixFile1_2_1_4 is NOT in result", 679 | ); 680 | 681 | ttt.end(); 682 | }); 683 | 684 | tt.test("with multiple ignore", async function (ttt) { 685 | await createFakeDirectoryTree(); 686 | 687 | const result = findRemoveSync(rootDirectory, { 688 | files: "*.*", 689 | ignore: [fixFilename1_2_1_4, fixFilename1_2_1_5], 690 | }); 691 | 692 | const exists1_2_1_1 = existsSync(randomFile1_2_1_1); 693 | ttt.equal( 694 | exists1_2_1_1, 695 | false, 696 | "findRemoveSync(with multiple ignore) did remove file randomFile1_2_1_1", 697 | ); 698 | 699 | const exists1_2_1_4 = existsSync(fixFile1_2_1_4); 700 | ttt.equal( 701 | exists1_2_1_4, 702 | true, 703 | "findRemoveSync(with multiple ignore) did not remove file fixFile1_2_1_4", 704 | ); 705 | 706 | const exists1_2_1_5 = existsSync(fixFile1_2_1_5); 707 | ttt.equal( 708 | exists1_2_1_5, 709 | true, 710 | "findRemoveSync(with multiple ignore) did not remove file fixFile1_2_1_5", 711 | ); 712 | 713 | ttt.strictEqual( 714 | typeof result[randomFile1_2_1_1], 715 | "boolean", 716 | "randomFile1_2_1_1 is in result", 717 | ); 718 | 719 | ttt.strictEqual( 720 | typeof result[fixFile1_2_1_4], 721 | "undefined", 722 | "fixFile1_2_1_4 is NOT in result", 723 | ); 724 | 725 | ttt.strictEqual( 726 | typeof result[fixFile1_2_1_5], 727 | "undefined", 728 | "fixFile1_2_1_5 is NOT in result", 729 | ); 730 | 731 | ttt.end(); 732 | }); 733 | 734 | tt.test("with ignore and bak extension params", async function (ttt) { 735 | await createFakeDirectoryTree(); 736 | 737 | const result = findRemoveSync(rootDirectory, { 738 | ignore: fixFilename1_2_1_4, 739 | extensions: ".bak", 740 | }); 741 | 742 | const exists1_2_1_1 = existsSync(randomFile1_2_1_1); 743 | ttt.equal( 744 | exists1_2_1_1, 745 | true, 746 | "findRemoveSync(with ignore + bak extension) did not remove file randomFile1_2_1_1", 747 | ); 748 | 749 | const exists1_2_1_2 = existsSync(randomFile1_2_1_2); 750 | ttt.equal( 751 | exists1_2_1_2, 752 | false, 753 | "findRemoveSync(with ignore + bak extension) did remove file randomFile1_2_1_2", 754 | ); 755 | 756 | const exists1_2_1_4 = existsSync(fixFile1_2_1_4); 757 | ttt.equal( 758 | exists1_2_1_4, 759 | true, 760 | "findRemoveSync(with ignore + bak extension) did not remove file fixFile1_2_1_4", 761 | ); 762 | 763 | ttt.strictEqual( 764 | typeof result[randomFile1_2_1_1], 765 | "undefined", 766 | "randomFile1_2_1_1 is NOT in result", 767 | ); 768 | 769 | ttt.strictEqual( 770 | typeof result[randomFile1_2_1_2], 771 | "boolean", 772 | "randomFile1_2_1_2 is in result", 773 | ); 774 | 775 | ttt.strictEqual( 776 | typeof result[fixFile1_2_1_4], 777 | "undefined", 778 | "fixFile1_2_1_4 is NOT in result", 779 | ); 780 | 781 | ttt.end(); 782 | }); 783 | 784 | tt.test("two files and check others", async function (ttt) { 785 | await createFakeDirectoryTree(); 786 | 787 | const result = findRemoveSync(rootDirectory, { 788 | files: [randomFilename1_2_1_1, randomFilename1_2_1_3], 789 | }); 790 | 791 | const exists1_2_1_1 = existsSync(randomFile1_2_1_1); 792 | ttt.equal( 793 | exists1_2_1_1, 794 | false, 795 | "findRemoveSync(two files and check others) removed randomFile1_2_1_1 fine", 796 | ); 797 | 798 | const exists1_2_1_3 = existsSync(randomFile1_2_1_3); 799 | ttt.equal( 800 | exists1_2_1_3, 801 | false, 802 | "findRemoveSync(two files and check others) removed randomFile1_2_1_3 fine", 803 | ); 804 | 805 | const exists1_2_1_4 = existsSync(fixFile1_2_1_4); 806 | ttt.equal( 807 | exists1_2_1_4, 808 | true, 809 | "findRemoveSync(two files and check others) did not remove fixFile1_2_1_4", 810 | ); 811 | 812 | const exists1_2_1_5 = existsSync(fixFile1_2_1_5); 813 | ttt.equal( 814 | exists1_2_1_5, 815 | true, 816 | "findRemoveSync(two files and check others) did not remove fixFile1_2_1_5", 817 | ); 818 | 819 | ttt.strictEqual( 820 | typeof result[randomFile1_2_1_1], 821 | "boolean", 822 | "randomFile1_2_1_1 is in result", 823 | ); 824 | 825 | ttt.strictEqual( 826 | typeof result[randomFile1_2_1_3], 827 | "boolean", 828 | "randomFile1_2_1_3 is in result", 829 | ); 830 | 831 | ttt.strictEqual( 832 | typeof result[fixFile1_2_1_4], 833 | "undefined", 834 | "fixFile1_2_1_4 is NOT in result", 835 | ); 836 | 837 | ttt.strictEqual( 838 | typeof result[fixFile1_2_1_5], 839 | "undefined", 840 | "fixFile1_2_1_5 is NOT in result", 841 | ); 842 | 843 | ttt.end(); 844 | }); 845 | 846 | tt.test("limit to maxLevel = 0", async function (ttt) { 847 | await createFakeDirectoryTree(); 848 | 849 | const result = findRemoveSync(rootDirectory, { 850 | files: "*.*", 851 | dir: "*", 852 | maxLevel: 0, 853 | }); 854 | 855 | ttt.strictEqual( 856 | Object.keys(result).length, 857 | 0, 858 | "findRemoveSync(limit to maxLevel = 0) returned empty an array.", 859 | ); 860 | 861 | ttt.end(); 862 | }); 863 | 864 | tt.test("limit to maxLevel = 1", async function (ttt) { 865 | await createFakeDirectoryTree(); 866 | 867 | const result = findRemoveSync(rootDirectory, { 868 | files: "*.*", 869 | dir: "*", 870 | maxLevel: 1, 871 | }); 872 | 873 | ttt.strictEqual( 874 | Object.keys(result).length, 875 | 7, 876 | "findRemoveSync(limit to maxLevel = 1) returned 7 entries.", 877 | ); 878 | 879 | ttt.end(); 880 | }); 881 | 882 | tt.test("limit to maxLevel = 2", async function (ttt) { 883 | await createFakeDirectoryTree(); 884 | 885 | const result = findRemoveSync(rootDirectory, { 886 | files: "*.*", 887 | dir: "*", 888 | maxLevel: 2, 889 | }); 890 | 891 | ttt.strictEqual( 892 | Object.keys(result).length, 893 | 12, 894 | "findRemoveSync(limit to maxLevel = 2) returned 12 entries.", 895 | ); 896 | 897 | ttt.end(); 898 | }); 899 | 900 | tt.test("limit to maxLevel = 3", async function (ttt) { 901 | await createFakeDirectoryTree(); 902 | 903 | const result = findRemoveSync(rootDirectory, { files: "*.*", maxLevel: 3 }); 904 | 905 | ttt.strictEqual( 906 | Object.keys(result).length, 907 | 6, 908 | "findRemoveSync(limit to maxLevel = 3) returned 6 entries.", 909 | ); 910 | 911 | ttt.end(); 912 | }); 913 | 914 | tt.test("limit to maxLevel = 3 + bak only", async function (ttt) { 915 | await createFakeDirectoryTree(); 916 | 917 | const result = findRemoveSync(rootDirectory, { maxLevel: 3, extensions: ".bak" }); 918 | 919 | ttt.strictEqual( 920 | Object.keys(result).length, 921 | 2, 922 | "findRemoveSync(limit to maxLevel = 3 + bak only) returned 2 entries.", 923 | ); 924 | 925 | ttt.end(); 926 | }); 927 | 928 | tt.test("single dir", async function (ttt) { 929 | await createFakeDirectoryTree(); 930 | 931 | findRemoveSync(rootDirectory, { dir: "directory1_2" }); 932 | 933 | const exists1_1 = existsSync(directory1_1); 934 | ttt.equal( 935 | exists1_1, 936 | true, 937 | "findRemoveSync(single dir) did not remove directory1_1", 938 | ); 939 | 940 | const exists1_2 = existsSync(directory1_2); 941 | ttt.equal(exists1_2, false, "findRemoveSync(single dir) removed directory1_2"); 942 | 943 | ttt.end(); 944 | }); 945 | 946 | tt.test("two directories", async function (ttt) { 947 | await createFakeDirectoryTree(); 948 | 949 | findRemoveSync(rootDirectory, { dir: ["directory1_1", "directory1_2"] }); 950 | 951 | const exists1_1 = existsSync(directory1_1); 952 | ttt.equal(exists1_1, false, "findRemoveSync(two dirs) removed directory1_1"); 953 | 954 | const exists1_2 = existsSync(directory1_2); 955 | ttt.equal(exists1_2, false, "findRemoveSync(two dirs) removed directory1_2"); 956 | 957 | const exists1_3 = existsSync(directory1_3); 958 | ttt.equal(exists1_3, true, "findRemoveSync(two dirs) did not remove directory1_3"); 959 | 960 | ttt.end(); 961 | }); 962 | 963 | tt.test("directories with the same basename", async function (ttt) { 964 | await createFakeDirectoryTree(); 965 | 966 | findRemoveSync(rootDirectory, { dir: "CVS" }); 967 | 968 | const exists1_3 = existsSync(directory1_3); 969 | ttt.equal( 970 | exists1_3, 971 | false, 972 | "findRemoveSync(directories with the same basename) removed root/directory1/CVS", 973 | ); 974 | 975 | const exists3 = existsSync(directory3); 976 | ttt.equal( 977 | exists3, 978 | false, 979 | "findRemoveSync(directories with the same basename) removed root/CVS", 980 | ); 981 | 982 | const exists1_1 = existsSync(directory1_1); 983 | ttt.equal( 984 | exists1_1, 985 | true, 986 | "findRemoveSync(remove single dir) did not remove directory1_1", 987 | ); 988 | 989 | const exists1_2 = existsSync(directory1_2); 990 | ttt.equal( 991 | exists1_2, 992 | true, 993 | "findRemoveSync(remove single dir) did not remove directory1_2", 994 | ); 995 | 996 | ttt.end(); 997 | }); 998 | 999 | tt.test("test run", async function (ttt) { 1000 | await createFakeDirectoryTree(); 1001 | 1002 | const result = findRemoveSync(rootDirectory, { 1003 | files: "*.*", 1004 | dir: "*", 1005 | test: true, 1006 | }); 1007 | 1008 | ttt.strictEqual( 1009 | Object.keys(result).length, 1010 | 19, 1011 | "findRemoveSync(test run) returned 19 entries.", 1012 | ); 1013 | 1014 | const exists1_2_1_1 = existsSync(randomFile1_2_1_1); 1015 | ttt.equal( 1016 | exists1_2_1_1, 1017 | true, 1018 | "findRemoveSync(test run) did not remove randomFile1_2_1_1", 1019 | ); 1020 | 1021 | const exists1_2_1_3 = existsSync(randomFile1_2_1_3); 1022 | ttt.equal( 1023 | exists1_2_1_3, 1024 | true, 1025 | "findRemoveSync(test run) did not remove randomFile1_2_1_3", 1026 | ); 1027 | 1028 | const exists1_1 = existsSync(directory1_1); 1029 | ttt.equal(exists1_1, true, "findRemoveSync(test run) did not remove directory1_1"); 1030 | 1031 | ttt.end(); 1032 | }); 1033 | }); 1034 | 1035 | t.test("TC 3: age checks", async function (tt) { 1036 | tt.teardown(() => { 1037 | destroyFakeDirectoryTree(); 1038 | }); 1039 | 1040 | tt.test( 1041 | "findRemoveSync(files and dirs older than 10000000000000000 sec)", 1042 | async function (ttt) { 1043 | await createFakeDirectoryTree(); 1044 | 1045 | const result = findRemoveSync(rootDirectory, { 1046 | files: "*.*", 1047 | dir: "*", 1048 | age: { seconds: 10000000000000000 }, 1049 | }); 1050 | 1051 | ttt.strictEqual( 1052 | Object.keys(result).length, 1053 | 0, 1054 | "findRemoveSync(files older than 10000000000000000 sec) returned zero entries.", 1055 | ); 1056 | 1057 | ttt.end(); 1058 | }, 1059 | ); 1060 | 1061 | tt.test("findRemoveSync(files and dirs older than 10 sec)", async function (ttt) { 1062 | await createFakeDirectoryTree(); 1063 | 1064 | const result = findRemoveSync(rootDirectory, { 1065 | files: "*.*", 1066 | dir: "*", 1067 | age: { seconds: 10 }, 1068 | }); 1069 | 1070 | ttt.strictEqual( 1071 | Object.keys(result).length, 1072 | 0, 1073 | "findRemoveSync(files older than 10 sec) returned zero entries.", 1074 | ); 1075 | 1076 | ttt.end(); 1077 | }); 1078 | 1079 | tt.test("findRemoveSync(files older than 2 sec with wait)", async function (ttt) { 1080 | await createFakeDirectoryTree(); 1081 | 1082 | await sleep(2000); 1083 | 1084 | const result = findRemoveSync(rootDirectory, { 1085 | files: "*.*", 1086 | age: { seconds: 2 }, 1087 | }); 1088 | 1089 | ttt.strictEqual( 1090 | Object.keys(result).length, 1091 | 11, 1092 | "findRemoveSync(files older than 2 sec with wait) returned 11 entries.", 1093 | ); 1094 | 1095 | ttt.end(); 1096 | }); 1097 | 1098 | tt.test( 1099 | "findRemoveSync(files older than 2 sec with wait + maxLevel = 1)", 1100 | async function (ttt) { 1101 | await createFakeDirectoryTree(); 1102 | 1103 | await sleep(2000); 1104 | 1105 | const result = findRemoveSync(rootDirectory, { 1106 | files: "*.*", 1107 | maxLevel: 1, 1108 | age: { seconds: 2 }, 1109 | }); 1110 | 1111 | ttt.strictEqual( 1112 | Object.keys(result).length, 1113 | 4, 1114 | "findRemoveSync(files older than 2 sec with wait + maxLevel = 1) returned 4 entries.", 1115 | ); 1116 | 1117 | ttt.end(); 1118 | }, 1119 | ); 1120 | }); 1121 | 1122 | t.test("TC 4: github issues", async function (tt) { 1123 | tt.teardown(() => { 1124 | destroyFakeDirectoryTree(); 1125 | }); 1126 | 1127 | // from https://github.com/binarykitchen/find-remove/issues/7 1128 | tt.test("findRemoveSync(issues/7a)", async function (ttt) { 1129 | await createFakeDirectoryTree(); 1130 | 1131 | await sleep(3000); 1132 | 1133 | const result = findRemoveSync(rootDirectory, { 1134 | age: { seconds: 2 }, 1135 | extensions: ".csv", 1136 | }); 1137 | 1138 | ttt.strictEqual( 1139 | Object.keys(result).length, 1140 | 2, 1141 | "findRemoveSync(issues/7) deleted 2 files.", 1142 | ); 1143 | 1144 | ttt.end(); 1145 | }); 1146 | 1147 | // from https://github.com/binarykitchen/find-remove/issues/7 1148 | tt.test("findRemoveSync(issues/7b)", async function (ttt) { 1149 | await createFakeDirectoryTree(); 1150 | 1151 | await sleep(3000); 1152 | 1153 | const result = findRemoveSync(rootDirectory, { extensions: ".dontexist" }); 1154 | 1155 | ttt.deepEqual(result, {}, "is an empty json"); 1156 | 1157 | ttt.end(); 1158 | }); 1159 | }); 1160 | 1161 | t.test("TC 5: limit checks", async function (tt) { 1162 | tt.teardown(() => { 1163 | destroyFakeDirectoryTree(); 1164 | }); 1165 | 1166 | tt.test("files older with limit of 2", async function (ttt) { 1167 | await createFakeDirectoryTree(); 1168 | 1169 | const result = findRemoveSync(rootDirectory, { files: "*.*", limit: 2 }); 1170 | 1171 | ttt.strictEqual( 1172 | Object.keys(result).length, 1173 | 2, 1174 | "findRemoveSync(files with limit of 2) returned 2 entries (out of 11).", 1175 | ); 1176 | 1177 | ttt.end(); 1178 | }); 1179 | 1180 | tt.test("files and dirs with limit of 5", async function (ttt) { 1181 | await createFakeDirectoryTree(); 1182 | 1183 | const result = findRemoveSync(rootDirectory, { files: "*.*", dir: "*", limit: 5 }); 1184 | 1185 | ttt.strictEqual( 1186 | Object.keys(result).length, 1187 | 5, 1188 | "findRemoveSync(files and dirs with limit of 5) returned 5 entries (out of 19).", 1189 | ); 1190 | 1191 | ttt.end(); 1192 | }); 1193 | }); 1194 | 1195 | t.test("TC 6: prefix checks", async function (tt) { 1196 | tt.teardown(() => { 1197 | destroyFakeDirectoryTree(); 1198 | }); 1199 | 1200 | tt.test("files with exiting prefix 'someth'", async function (ttt) { 1201 | await createFakeDirectoryTree(); 1202 | 1203 | const result = findRemoveSync(rootDirectory, { prefix: "someth" }); 1204 | 1205 | ttt.strictEqual( 1206 | Object.keys(result).length, 1207 | 2, 1208 | 'findRemoveSync(files with prefix "someth") returned 2 entries (out of 11).', 1209 | ); 1210 | 1211 | ttt.end(); 1212 | }); 1213 | 1214 | tt.test( 1215 | "files with non-existing prefix 'ssssssssssssssssssssssssss' - too many chars", 1216 | async function (ttt) { 1217 | await createFakeDirectoryTree(); 1218 | 1219 | const result = findRemoveSync(rootDirectory, { 1220 | prefix: "ssssssssssssssssssssssssss", 1221 | }); 1222 | 1223 | ttt.strictEqual( 1224 | Object.keys(result).length, 1225 | 0, 1226 | 'findRemoveSync(files with non-existing prefix "ssssssssssssssssssssssssss"- too many chars) returned 0 entries (out of 11).', 1227 | ); 1228 | 1229 | ttt.end(); 1230 | }, 1231 | ); 1232 | }); 1233 | 1234 | t.test("TC 7: tests with regex patterns", async function (tt) { 1235 | tt.teardown(() => { 1236 | destroyFakeDirectoryTree(); 1237 | }); 1238 | 1239 | tt.test("regex pattern files", async function (ttt) { 1240 | await createFakeDirectoryTreeRegex(); 1241 | 1242 | findRemoveSync(rootDirectory, { files: "thing", regex: true }); 1243 | 1244 | const exists1_2_1_2 = existsSync(randomFile1_2_1_2); 1245 | ttt.equal(exists1_2_1_2, true, "did not remove randomFile1_2_1_2"); 1246 | 1247 | const exists1_2_1_4 = existsSync(fixFile1_2_1_4); // something.png 1248 | ttt.equal(exists1_2_1_4, false, "removed fixFile1_2_1_4 fine"); 1249 | 1250 | const exists1_2_1_5 = existsSync(fixFile1_2_1_5); // something.jpg 1251 | ttt.equal(exists1_2_1_5, false, "removed fixFile1_2_1_5 fine"); 1252 | 1253 | ttt.end(); 1254 | }); 1255 | 1256 | tt.test("regex pattern directories", async function (ttt) { 1257 | await createFakeDirectoryTreeRegex(); 1258 | 1259 | findRemoveSync(rootDirectory, { dir: "^token", regex: true }); 1260 | 1261 | const exists4 = existsSync(directory4); 1262 | ttt.equal(exists4, true, "did not remove directory4"); 1263 | 1264 | const exists5 = existsSync(directory5); 1265 | ttt.equal(exists5, false, "removed directory5 fine"); 1266 | 1267 | ttt.end(); 1268 | }); 1269 | }); 1270 | }); 1271 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "tests", ".eslintrc.cjs", "prettier.config.cjs"], 3 | "extends": ["@tsconfig/strictest/tsconfig.json", "@tsconfig/node22/tsconfig.json"], 4 | "compilerOptions": { 5 | "module": "es2022", 6 | "moduleResolution": "bundler", 7 | "lib": ["es2022"], 8 | "noEmit": true 9 | } 10 | } 11 | --------------------------------------------------------------------------------