From ed3288ed31291b3fe82cba3e16ccbb552ad64f7a Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 3 Mar 2023 12:43:06 -0800 Subject: [PATCH] add filter option Re: #140 Fix: #160 --- CHANGELOG.md | 5 + README.md | 28 +++- libtap-settings.js | 2 +- package-lock.json | 128 ++++++++++-------- package.json | 2 +- src/bin.ts | 2 +- src/index.ts | 32 +++-- src/rimraf-move-remove.ts | 61 ++++++--- src/rimraf-native.ts | 13 +- src/rimraf-posix.ts | 60 ++++++-- src/rimraf-windows.ts | 73 +++++++--- src/use-native.ts | 8 +- .../test/rimraf-move-remove.js.test.cjs | 70 ++++++++++ tap-snapshots/test/rimraf-posix.js.test.cjs | 76 +++++++++++ tap-snapshots/test/rimraf-windows.js.test.cjs | 86 ++++++++++++ test/index.js | 14 +- test/rimraf-move-remove.js | 82 +++++++++++ test/rimraf-posix.js | 120 +++++++++++++--- test/rimraf-windows.js | 108 +++++++++++++-- 19 files changed, 793 insertions(+), 177 deletions(-) create mode 100644 tap-snapshots/test/rimraf-posix.js.test.cjs diff --git a/CHANGELOG.md b/CHANGELOG.md index ae9f5f44..97971fab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 4.3 + +- Return boolean indicating whether the path was fully removed +- Add filter option + # v4.2 - Brought back `glob` support, using the new and improved glob v9 diff --git a/README.md b/README.md index 99aa3752..1e0b5c98 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Install with `npm install rimraf`. resolve the situation. - Simplified implementation on Posix, since the Windows affordances are not necessary there. +- As of 4.3, return/resolve value is boolean instead of undefined ## API @@ -32,6 +33,12 @@ import { rimraf, rimrafSync, native, nativeSync } from 'rimraf' const { rimraf, rimrafSync, native, nativeSync } = require('rimraf') ``` +All removal functions return a boolean indicating that all +entries were successfully removed. + +The only case in which this will not return `true` is if +something was omitted from the removal via a `filter` option. + ### `rimraf(f, [opts]) -> Promise` This first parameter is a path or array of paths. The second @@ -64,9 +71,24 @@ Options: linear backoff. Default `100`. - `signal` Pass in an AbortSignal to cancel the directory removal. This is useful when removing large folder structures, - if you'd like to limit the amount of time spent. Using a - `signal` option prevents the use of Node's built-in `fs.rm` - because that implementation does not support abort signals. + if you'd like to limit the amount of time spent. + + Using a `signal` option prevents the use of Node's built-in + `fs.rm` because that implementation does not support abort + signals. + +- `filter` Method that receives a path string as an argument, and + returns a boolean indicating whether that path should be + deleted. + + If a filter method is provided, it _must_ return a truthy + value, or nothing will be removed. Filtering out a directory + will still allow its children to be removed, unless they are + also filtered out, but any parents of a filtered entry will not + be removed. + + Using a filter method prevents the use of Node's built-in + `fs.rm` because that implementation does not support filtering. Any other options are provided to the native Node.js `fs.rm` implementation when that is used. diff --git a/libtap-settings.js b/libtap-settings.js index baf0d578..f9edd744 100644 --- a/libtap-settings.js +++ b/libtap-settings.js @@ -4,6 +4,6 @@ const rimraf = require('./') module.exports = { rmdirRecursiveSync: path => rimraf.sync(path), rmdirRecursive(path, cb) { - rimraf(path).then(cb, cb) + rimraf(path, {}).then(() => cb(), cb) }, } diff --git a/package-lock.json b/package-lock.json index 93517d75..2222a3cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "eslint-config-prettier": "^8.6.0", "mkdirp": "1", "prettier": "^2.8.2", - "tap": "^16.3.3", + "tap": "^16.3.4", "ts-node": "^10.9.1", "typedoc": "^0.23.21", "typescript": "^4.9.3" @@ -145,6 +145,15 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/@babel/helper-environment-visitor": { "version": "7.18.9", "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", @@ -752,9 +761,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.14.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.4.tgz", - "integrity": "sha512-VhCw7I7qO2X49+jaKcAUwi3rR+hbxT5VcYF493+Z5kMLI0DL568b7JI4IDJaxWFH0D/xwmGJNoXisyX+w7GH/g==", + "version": "18.14.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.5.tgz", + "integrity": "sha512-CRT4tMK/DHYhw1fcCEBwME9CSaZNclxfzVMe7GsO6ULSwsttbj70wSiX6rZdIjGblu93sTJxLdhNIT85KKI7Qw==", "dev": true }, "node_modules/@types/tap": { @@ -1050,9 +1059,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001458", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz", - "integrity": "sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w==", + "version": "1.0.30001460", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001460.tgz", + "integrity": "sha512-Bud7abqjvEjipUkpLs4D7gR0l8hBYBHoa+tGtKJHvT2AYzLp1z7EmVkUT4ERpVUfca8S2HGIVs883D8pUH1ZzQ==", "dev": true, "funding": [ { @@ -1277,9 +1286,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.317", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.317.tgz", - "integrity": "sha512-JhCRm9v30FMNzQSsjl4kXaygU+qHBD0Yh7mKxyjmF0V8VwYVB6qpBRX28GyAucrM9wDCpSUctT6FpMUQxbyKuA==", + "version": "1.4.319", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.319.tgz", + "integrity": "sha512-WeoI6NwZUgteKB+Wmn692S35QycwwNxwgTomNnoCJ79znBAjtBi6C/cIW62JkXmpJRX5rKNYSLDBdAM8l5fH0w==", "dev": true }, "node_modules/emoji-regex": { @@ -1730,9 +1739,9 @@ } }, "node_modules/glob": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.2.0.tgz", - "integrity": "sha512-VbqdQB87R58tWoOMO8dBmPEw0gpAn+FNIT7PAEoPMKx/BTiFM847t3uvGER+oIIKKmUyPEHiG3gGz7rwPVzzXQ==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-9.2.1.tgz", + "integrity": "sha512-Pxxgq3W0HyA3XUvSXcFhRSs+43Jsx0ddxcFrbjxNGkL2Ak5BAUBxLqI5G6ADDeCHLfzzXFhe0b1yYcctGmytMA==", "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^7.4.1", @@ -1781,14 +1790,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob/node_modules/minipass": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz", - "integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/globals": { "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", @@ -2234,6 +2235,24 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/libtap/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/libtap/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2263,20 +2282,13 @@ "peer": true }, "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.1.tgz", + "integrity": "sha512-8/HcIENyQnfUTCDizRu9rrDyG6XG/21M4X7/YEGZeD76ZJilFPAUVb/2zysFf7VVO1LEjCDFyHp8pMMvozIrvg==", + "engines": { + "node": ">=12" } }, - "node_modules/lru-cache/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, "node_modules/lunr": { "version": "2.3.9", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", @@ -2329,13 +2341,9 @@ } }, "node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz", + "integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==", "engines": { "node": ">=8" } @@ -2752,22 +2760,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.1.tgz", - "integrity": "sha512-8/HcIENyQnfUTCDizRu9rrDyG6XG/21M4X7/YEGZeD76ZJilFPAUVb/2zysFf7VVO1LEjCDFyHp8pMMvozIrvg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/path-scurry/node_modules/minipass": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.4.tgz", - "integrity": "sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==", - "engines": { - "node": ">=8" - } - }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -3364,6 +3356,24 @@ "node": ">= 8" } }, + "node_modules/tap-parser/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tap-parser/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/tap-yaml": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/tap-yaml/-/tap-yaml-1.0.2.tgz", @@ -5618,9 +5628,9 @@ } }, "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, "node_modules/yaml": { diff --git a/package.json b/package.json index cdc132fd..836bb0f2 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "eslint-config-prettier": "^8.6.0", "mkdirp": "1", "prettier": "^2.8.2", - "tap": "^16.3.3", + "tap": "^16.3.4", "ts-node": "^10.9.1", "typedoc": "^0.23.21", "typescript": "^4.9.3" diff --git a/src/bin.ts b/src/bin.ts index 0785d829..f71b6130 100755 --- a/src/bin.ts +++ b/src/bin.ts @@ -44,7 +44,7 @@ const main = async (...args: string[]) => { const opt: RimrafOptions = {} const paths: string[] = [] let dashdash = false - let impl: (path: string | string[], opt?: RimrafOptions) => Promise = + let impl: (path: string | string[], opt?: RimrafOptions) => Promise = rimraf for (const arg of args) { diff --git a/src/index.ts b/src/index.ts index e417d519..7d845f18 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ export interface RimrafOptions { maxBackoff?: number signal?: AbortSignal glob?: boolean | GlobOptions + filter?: (path: string) => boolean } const typeOrUndef = (val: any, t: string) => @@ -26,7 +27,8 @@ export const isRimrafOptions = (o: any): o is RimrafOptions => typeOrUndef(o.retryDelay, 'number') && typeOrUndef(o.backoff, 'number') && typeOrUndef(o.maxBackoff, 'number') && - (typeOrUndef(o.glob, 'boolean') || (o.glob && typeof o.glob === 'object')) + (typeOrUndef(o.glob, 'boolean') || (o.glob && typeof o.glob === 'object')) && + typeOrUndef(o.filter, 'function') export const assertRimrafOptions: (o: any) => void = ( o: any @@ -44,27 +46,35 @@ import { rimrafWindows, rimrafWindowsSync } from './rimraf-windows.js' import { useNative, useNativeSync } from './use-native.js' const wrap = - (fn: (p: string, o: RimrafOptions) => Promise) => - async (path: string | string[], opt?: RimrafOptions): Promise => { + (fn: (p: string, o: RimrafOptions) => Promise) => + async (path: string | string[], opt?: RimrafOptions): Promise => { const options = optArg(opt) if (options.glob) { path = await glob(path, options.glob) } - await (Array.isArray(path) - ? Promise.all(path.map(p => fn(pathArg(p, options), options))) - : fn(pathArg(path, options), options)) + if (Array.isArray(path)) { + return !!( + await Promise.all(path.map(p => fn(pathArg(p, options), options))) + ).reduce((a, b) => a && b, true) + } else { + return !!(await fn(pathArg(path, options), options)) + } } const wrapSync = - (fn: (p: string, o: RimrafOptions) => void) => - (path: string | string[], opt?: RimrafOptions): void => { + (fn: (p: string, o: RimrafOptions) => boolean) => + (path: string | string[], opt?: RimrafOptions): boolean => { const options = optArg(opt) if (options.glob) { path = globSync(path, options.glob) } - return Array.isArray(path) - ? path.forEach(p => fn(pathArg(p, options), options)) - : fn(pathArg(path, options), options) + if (Array.isArray(path)) { + return !!path + .map(p => fn(pathArg(p, options), options)) + .reduce((a, b) => a && b, true) + } else { + return !!fn(pathArg(path, options), options) + } } export const nativeSync = wrapSync(rimrafNativeSync) diff --git a/src/rimraf-move-remove.ts b/src/rimraf-move-remove.ts index a9e7e15b..a28262ed 100644 --- a/src/rimraf-move-remove.ts +++ b/src/rimraf-move-remove.ts @@ -72,14 +72,13 @@ const unlinkFixEPERMSync = (path: string) => { export const rimrafMoveRemove = async ( path: string, opt: RimrafOptions -): Promise => { +): Promise => { if (opt?.signal?.aborted) { throw opt.signal.reason } if (!opt.tmp) { return rimrafMoveRemove(path, { ...opt, tmp: await defaultTmp(path) }) } - if (path === opt.tmp && parse(path).root !== path) { throw new Error('cannot delete temp directory used for deletion') } @@ -87,28 +86,38 @@ export const rimrafMoveRemove = async ( const entries = await readdirOrError(path) if (!Array.isArray(entries)) { if (entries.code === 'ENOENT') { - return + return true } - if (entries.code !== 'ENOTDIR') { throw entries } - - return await ignoreENOENT(tmpUnlink(path, opt.tmp, unlinkFixEPERM)) + if (opt.filter && !opt.filter(path)) { + return false + } + await ignoreENOENT(tmpUnlink(path, opt.tmp, unlinkFixEPERM)) + return true } - await Promise.all( - entries.map(entry => rimrafMoveRemove(resolve(path, entry), opt)) - ) + const removedAll = ( + await Promise.all( + entries.map(entry => rimrafMoveRemove(resolve(path, entry), opt)) + ) + ).reduce((a, b) => a && b, true) + if (!removedAll) { + return false + } // we don't ever ACTUALLY try to unlink /, because that can never work // but when preserveRoot is false, we could be operating on it. // No need to check if preserveRoot is not false. if (opt.preserveRoot === false && path === parse(path).root) { - return + return false } - - return await ignoreENOENT(tmpUnlink(path, opt.tmp, rmdir)) + if (opt.filter && !opt.filter(path)) { + return false + } + await ignoreENOENT(tmpUnlink(path, opt.tmp, rmdir)) + return true } const tmpUnlink = async ( @@ -124,7 +133,7 @@ const tmpUnlink = async ( export const rimrafMoveRemoveSync = ( path: string, opt: RimrafOptions -): void => { +): boolean => { if (opt?.signal?.aborted) { throw opt.signal.reason } @@ -140,25 +149,33 @@ export const rimrafMoveRemoveSync = ( const entries = readdirOrErrorSync(path) if (!Array.isArray(entries)) { if (entries.code === 'ENOENT') { - return + return true } - if (entries.code !== 'ENOTDIR') { throw entries } - - return ignoreENOENTSync(() => tmpUnlinkSync(path, tmp, unlinkFixEPERMSync)) + if (opt.filter && !opt.filter(path)) { + return false + } + ignoreENOENTSync(() => tmpUnlinkSync(path, tmp, unlinkFixEPERMSync)) + return true } + let removedAll = true for (const entry of entries) { - rimrafMoveRemoveSync(resolve(path, entry), opt) + removedAll = rimrafMoveRemoveSync(resolve(path, entry), opt) && removedAll + } + if (!removedAll) { + return false } - if (opt.preserveRoot === false && path === parse(path).root) { - return + return false } - - return ignoreENOENTSync(() => tmpUnlinkSync(path, tmp, rmdirSync)) + if (opt.filter && !opt.filter(path)) { + return false + } + ignoreENOENTSync(() => tmpUnlinkSync(path, tmp, rmdirSync)) + return true } const tmpUnlinkSync = ( diff --git a/src/rimraf-native.ts b/src/rimraf-native.ts index 6c1c0be0..47e2bc34 100644 --- a/src/rimraf-native.ts +++ b/src/rimraf-native.ts @@ -2,16 +2,23 @@ import { RimrafOptions } from '.' import { promises, rmSync } from './fs.js' const { rm } = promises -export const rimrafNative = (path: string, opt: RimrafOptions) => - rm(path, { +export const rimrafNative = async ( + path: string, + opt: RimrafOptions +): Promise => { + await rm(path, { ...opt, force: true, recursive: true, }) + return true +} -export const rimrafNativeSync = (path: string, opt: RimrafOptions) => +export const rimrafNativeSync = (path: string, opt: RimrafOptions): boolean => { rmSync(path, { ...opt, force: true, recursive: true, }) + return true +} diff --git a/src/rimraf-posix.ts b/src/rimraf-posix.ts index f567bc9d..6fa79e2e 100644 --- a/src/rimraf-posix.ts +++ b/src/rimraf-posix.ts @@ -15,55 +15,87 @@ import { readdirOrError, readdirOrErrorSync } from './readdir-or-error.js' import { RimrafOptions } from '.' import { ignoreENOENT, ignoreENOENTSync } from './ignore-enoent.js' -export const rimrafPosix = async (path: string, opt: RimrafOptions) => { +export const rimrafPosix = async ( + path: string, + opt: RimrafOptions +): Promise => { if (opt?.signal?.aborted) { throw opt.signal.reason } const entries = await readdirOrError(path) if (!Array.isArray(entries)) { if (entries.code === 'ENOENT') { - return + return true } if (entries.code !== 'ENOTDIR') { throw entries } - return ignoreENOENT(unlink(path)) + if (opt.filter && !opt.filter(path)) { + return false + } + await ignoreENOENT(unlink(path)) + return true + } + + const removedAll = ( + await Promise.all( + entries.map(entry => rimrafPosix(resolve(path, entry), opt)) + ) + ).reduce((a, b) => a && b, true) + + if (!removedAll) { + return false } - await Promise.all( - entries.map(entry => rimrafPosix(resolve(path, entry), opt)) - ) // we don't ever ACTUALLY try to unlink /, because that can never work // but when preserveRoot is false, we could be operating on it. // No need to check if preserveRoot is not false. if (opt.preserveRoot === false && path === parse(path).root) { - return + return false + } + + if (opt.filter && !opt.filter(path)) { + return false } - return ignoreENOENT(rmdir(path)) + await ignoreENOENT(rmdir(path)) + return true } -export const rimrafPosixSync = (path: string, opt: RimrafOptions) => { +export const rimrafPosixSync = (path: string, opt: RimrafOptions): boolean => { if (opt?.signal?.aborted) { throw opt.signal.reason } const entries = readdirOrErrorSync(path) if (!Array.isArray(entries)) { if (entries.code === 'ENOENT') { - return + return true } if (entries.code !== 'ENOTDIR') { throw entries } - return ignoreENOENTSync(() => unlinkSync(path)) + if (opt.filter && !opt.filter(path)) { + return false + } + ignoreENOENTSync(() => unlinkSync(path)) + return true } + let removedAll: boolean = true for (const entry of entries) { - rimrafPosixSync(resolve(path, entry), opt) + removedAll = rimrafPosixSync(resolve(path, entry), opt) && removedAll + } + if (!removedAll) { + return false } if (opt.preserveRoot === false && path === parse(path).root) { - return + return false + } + + if (opt.filter && !opt.filter(path)) { + return false } - return ignoreENOENTSync(() => rmdirSync(path)) + ignoreENOENTSync(() => rmdirSync(path)) + return true } diff --git a/src/rimraf-windows.ts b/src/rimraf-windows.ts index 2e3245c7..68728f08 100644 --- a/src/rimraf-windows.ts +++ b/src/rimraf-windows.ts @@ -26,17 +26,19 @@ const rimrafWindowsDirSync = retryBusySync(fixEPERMSync(rmdirSync)) const rimrafWindowsDirMoveRemoveFallback = async ( path: string, opt: RimrafOptions -) => { +): Promise => { /* c8 ignore start */ if (opt?.signal?.aborted) { throw opt.signal.reason } /* c8 ignore stop */ + // already filtered, remove from options so we don't call unnecessarily + const { filter, ...options } = opt try { - await rimrafWindowsDir(path, opt) + return await rimrafWindowsDir(path, options) } catch (er) { if ((er as NodeJS.ErrnoException)?.code === 'ENOTEMPTY') { - return await rimrafMoveRemove(path, opt) + return await rimrafMoveRemove(path, options) } throw er } @@ -45,15 +47,18 @@ const rimrafWindowsDirMoveRemoveFallback = async ( const rimrafWindowsDirMoveRemoveFallbackSync = ( path: string, opt: RimrafOptions -) => { +): boolean => { if (opt?.signal?.aborted) { throw opt.signal.reason } + // already filtered, remove from options so we don't call unnecessarily + const { filter, ...options } = opt try { - rimrafWindowsDirSync(path, opt) + return rimrafWindowsDirSync(path, options) } catch (er) { - if ((er as NodeJS.ErrnoException)?.code === 'ENOTEMPTY') { - return rimrafMoveRemoveSync(path, opt) + const fer = er as NodeJS.ErrnoException + if (fer?.code === 'ENOTEMPTY') { + return rimrafMoveRemoveSync(path, options) } throw er } @@ -68,7 +73,7 @@ export const rimrafWindows = async ( path: string, opt: RimrafOptions, state = START -): Promise => { +): Promise => { if (opt?.signal?.aborted) { throw opt.signal.reason } @@ -79,36 +84,48 @@ export const rimrafWindows = async ( const entries = await readdirOrError(path) if (!Array.isArray(entries)) { if (entries.code === 'ENOENT') { - return + return true } if (entries.code !== 'ENOTDIR') { throw entries } + if (opt.filter && !opt.filter(path)) { + return false + } // is a file - return ignoreENOENT(rimrafWindowsFile(path, opt)) + await ignoreENOENT(rimrafWindowsFile(path, opt)) + return true } - await Promise.all( - entries.map(entry => - rimrafWindows(resolve(path, entry), opt, state === START ? CHILD : state) + const s = state === START ? CHILD : state + const removedAll = ( + await Promise.all( + entries.map(entry => rimrafWindows(resolve(path, entry), opt, s)) ) - ) + ).reduce((a, b) => a && b, true) if (state === START) { return rimrafWindows(path, opt, FINISH) } else if (state === FINISH) { if (opt.preserveRoot === false && path === parse(path).root) { - return + return false + } + if (opt.filter && !opt.filter(path)) { + return false + } + if (!removedAll) { + return false } - return ignoreENOENT(rimrafWindowsDirMoveRemoveFallback(path, opt)) + await ignoreENOENT(rimrafWindowsDirMoveRemoveFallback(path, opt)) } + return true } export const rimrafWindowsSync = ( path: string, opt: RimrafOptions, state = START -): void => { +): boolean => { if (!states.has(state)) { throw new TypeError('invalid third argument passed to rimraf') } @@ -116,28 +133,40 @@ export const rimrafWindowsSync = ( const entries = readdirOrErrorSync(path) if (!Array.isArray(entries)) { if (entries.code === 'ENOENT') { - return + return true } if (entries.code !== 'ENOTDIR') { throw entries } + if (opt.filter && !opt.filter(path)) { + return false + } // is a file - return ignoreENOENTSync(() => rimrafWindowsFileSync(path, opt)) + ignoreENOENTSync(() => rimrafWindowsFileSync(path, opt)) + return true } + let removedAll = true for (const entry of entries) { const s = state === START ? CHILD : state - rimrafWindowsSync(resolve(path, entry), opt, s) + removedAll = rimrafWindowsSync(resolve(path, entry), opt, s) && removedAll } if (state === START) { return rimrafWindowsSync(path, opt, FINISH) } else if (state === FINISH) { if (opt.preserveRoot === false && path === parse(path).root) { - return + return false + } + if (opt.filter && !opt.filter(path)) { + return false + } + if (!removedAll) { + return false } - return ignoreENOENTSync(() => { + ignoreENOENTSync(() => { rimrafWindowsDirMoveRemoveFallbackSync(path, opt) }) } + return true } diff --git a/src/use-native.ts b/src/use-native.ts index fba2583b..76f47e55 100644 --- a/src/use-native.ts +++ b/src/use-native.ts @@ -6,6 +6,10 @@ import { RimrafOptions } from './index.js' // rm implementation is less advanced. Change this code if that changes. import platform from './platform.js' export const useNative: (opt?: RimrafOptions) => boolean = - !hasNative || platform === 'win32' ? () => false : opt => !opt?.signal + !hasNative || platform === 'win32' + ? () => false + : opt => !opt?.signal && !opt?.filter export const useNativeSync: (opt?: RimrafOptions) => boolean = - !hasNative || platform === 'win32' ? () => false : opt => !opt?.signal + !hasNative || platform === 'win32' + ? () => false + : opt => !opt?.signal && !opt?.filter diff --git a/tap-snapshots/test/rimraf-move-remove.js.test.cjs b/tap-snapshots/test/rimraf-move-remove.js.test.cjs index 4d9b1d74..c6e8d882 100644 --- a/tap-snapshots/test/rimraf-move-remove.js.test.cjs +++ b/tap-snapshots/test/rimraf-move-remove.js.test.cjs @@ -5,6 +5,76 @@ * Make sure to inspect the output below. Do not ignore changes! */ 'use strict' +exports[`test/rimraf-move-remove.js TAP filter function filter=i async > paths seen 1`] = ` +Array [ + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-async/a", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-async/b", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-async/c/d", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-async/c/e", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-async/c/f/g", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-async/c/f/h", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-async/c/f/i", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-async/c/f/i/j", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-async/c/f/i/k", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-async/c/f/i/l", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-async/c/f/i/m", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-async/c/f/i/m/n", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-async/c/f/i/m/o", +] +` + +exports[`test/rimraf-move-remove.js TAP filter function filter=i sync > paths seen 1`] = ` +Array [ + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-sync/a", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-sync/b", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-sync/c/d", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-sync/c/e", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-sync/c/f/g", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-sync/c/f/h", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-sync/c/f/i", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-sync/c/f/i/j", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-sync/c/f/i/k", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-sync/c/f/i/l", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-sync/c/f/i/m", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-sync/c/f/i/m/n", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-i-sync/c/f/i/m/o", +] +` + +exports[`test/rimraf-move-remove.js TAP filter function filter=j async > paths seen 1`] = ` +Array [ + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-async/a", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-async/b", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-async/c/d", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-async/c/e", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-async/c/f/g", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-async/c/f/h", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-async/c/f/i/j", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-async/c/f/i/k", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-async/c/f/i/l", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-async/c/f/i/m", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-async/c/f/i/m/n", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-async/c/f/i/m/o", +] +` + +exports[`test/rimraf-move-remove.js TAP filter function filter=j sync > paths seen 1`] = ` +Array [ + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-sync/a", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-sync/b", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-sync/c/d", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-sync/c/e", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-sync/c/f/g", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-sync/c/f/h", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-sync/c/f/i/j", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-sync/c/f/i/k", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-sync/c/f/i/l", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-sync/c/f/i/m", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-sync/c/f/i/m/n", + "test/tap-testdir-rimraf-move-remove-filter-function-filter-j-sync/c/f/i/m/o", +] +` + exports[`test/rimraf-move-remove.js TAP handle EPERMs on unlink by trying to chmod 0o666 async > must match snapshot 1`] = ` Array [ Array [ diff --git a/tap-snapshots/test/rimraf-posix.js.test.cjs b/tap-snapshots/test/rimraf-posix.js.test.cjs new file mode 100644 index 00000000..93ae5152 --- /dev/null +++ b/tap-snapshots/test/rimraf-posix.js.test.cjs @@ -0,0 +1,76 @@ +/* IMPORTANT + * This snapshot file is auto-generated, but designed for humans. + * It should be checked into source control and tracked carefully. + * Re-generate by setting TAP_SNAPSHOT=1 and running tests. + * Make sure to inspect the output below. Do not ignore changes! + */ +'use strict' +exports[`test/rimraf-posix.js TAP filter function filter=i async > paths seen 1`] = ` +Array [ + "test/tap-testdir-rimraf-posix-filter-function-filter-i-async/a", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-async/b", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-async/c/d", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-async/c/e", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-async/c/f/g", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-async/c/f/h", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-async/c/f/i", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-async/c/f/i/j", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-async/c/f/i/k", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-async/c/f/i/l", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-async/c/f/i/m", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-async/c/f/i/m/n", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-async/c/f/i/m/o", +] +` + +exports[`test/rimraf-posix.js TAP filter function filter=i sync > paths seen 1`] = ` +Array [ + "test/tap-testdir-rimraf-posix-filter-function-filter-i-sync/a", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-sync/b", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-sync/c/d", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-sync/c/e", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-sync/c/f/g", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-sync/c/f/h", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-sync/c/f/i", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-sync/c/f/i/j", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-sync/c/f/i/k", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-sync/c/f/i/l", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-sync/c/f/i/m", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-sync/c/f/i/m/n", + "test/tap-testdir-rimraf-posix-filter-function-filter-i-sync/c/f/i/m/o", +] +` + +exports[`test/rimraf-posix.js TAP filter function filter=j async > paths seen 1`] = ` +Array [ + "test/tap-testdir-rimraf-posix-filter-function-filter-j-async/a", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-async/b", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-async/c/d", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-async/c/e", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-async/c/f/g", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-async/c/f/h", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-async/c/f/i/j", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-async/c/f/i/k", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-async/c/f/i/l", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-async/c/f/i/m", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-async/c/f/i/m/n", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-async/c/f/i/m/o", +] +` + +exports[`test/rimraf-posix.js TAP filter function filter=j sync > paths seen 1`] = ` +Array [ + "test/tap-testdir-rimraf-posix-filter-function-filter-j-sync/a", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-sync/b", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-sync/c/d", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-sync/c/e", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-sync/c/f/g", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-sync/c/f/h", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-sync/c/f/i/j", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-sync/c/f/i/k", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-sync/c/f/i/l", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-sync/c/f/i/m", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-sync/c/f/i/m/n", + "test/tap-testdir-rimraf-posix-filter-function-filter-j-sync/c/f/i/m/o", +] +` diff --git a/tap-snapshots/test/rimraf-windows.js.test.cjs b/tap-snapshots/test/rimraf-windows.js.test.cjs index ec86e1ae..3c85c6c0 100644 --- a/tap-snapshots/test/rimraf-windows.js.test.cjs +++ b/tap-snapshots/test/rimraf-windows.js.test.cjs @@ -5,6 +5,92 @@ * Make sure to inspect the output below. Do not ignore changes! */ 'use strict' +exports[`test/rimraf-windows.js TAP filter function filter=i async > paths seen 1`] = ` +Array [ + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/a", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/b", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/c", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/c/d", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/c/e", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/c/f", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/c/f/g", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/c/f/h", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/c/f/i", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/c/f/i/j", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/c/f/i/k", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/c/f/i/l", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/c/f/i/m", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/c/f/i/m/n", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-async/c/f/i/m/o", +] +` + +exports[`test/rimraf-windows.js TAP filter function filter=i sync > paths seen 1`] = ` +Array [ + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/a", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/b", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/c", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/c/d", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/c/e", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/c/f", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/c/f/g", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/c/f/h", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/c/f/i", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/c/f/i/j", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/c/f/i/k", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/c/f/i/l", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/c/f/i/m", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/c/f/i/m/n", + "test/tap-testdir-rimraf-windows-filter-function-filter-i-sync/c/f/i/m/o", +] +` + +exports[`test/rimraf-windows.js TAP filter function filter=j async > paths seen 1`] = ` +Array [ + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/a", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/b", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/c", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/c/d", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/c/e", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/c/f", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/c/f/g", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/c/f/h", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/c/f/i", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/c/f/i/j", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/c/f/i/j", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/c/f/i/k", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/c/f/i/l", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/c/f/i/m", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/c/f/i/m/n", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-async/c/f/i/m/o", +] +` + +exports[`test/rimraf-windows.js TAP filter function filter=j sync > paths seen 1`] = ` +Array [ + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/a", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/b", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/c", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/c/d", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/c/e", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/c/f", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/c/f/g", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/c/f/h", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/c/f/i", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/c/f/i/j", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/c/f/i/j", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/c/f/i/k", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/c/f/i/l", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/c/f/i/m", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/c/f/i/m/n", + "test/tap-testdir-rimraf-windows-filter-function-filter-j-sync/c/f/i/m/o", +] +` + exports[`test/rimraf-windows.js TAP handle EPERMs on unlink by trying to chmod 0o666 async > must match snapshot 1`] = ` Array [ Array [ diff --git a/test/index.js b/test/index.js index d7cb429d..ddac1778 100644 --- a/test/index.js +++ b/test/index.js @@ -139,8 +139,8 @@ t.test('actually delete some stuff', t => { }, }, } - const { rimraf, rimrafSync } = require('../') - const { statSync } = require('../dist/cjs/src/fs.js') + const { rimraf } = require('../') + const { statSync } = require('fs') t.test('sync', t => { const path = t.testdir(fixture) rimraf.sync(path) @@ -150,7 +150,7 @@ t.test('actually delete some stuff', t => { t.test('async', async t => { const path = t.testdir(fixture) await rimraf(path) - t.throws(() => statSync(path), { code: 'ENOENT' }, 'deleted') + t.throws(() => statSync(path), { code: 'ENOENT' }) }) t.end() }) @@ -169,8 +169,8 @@ t.test('accept array of paths as first arg', async t => { rimrafNativeSync: (path, opt) => SYNC_CALLS.push([path, opt]), }, }) - t.equal(await rimraf(['a', 'b', 'c']), undefined) - t.equal(await rimraf(['i', 'j', 'k'], { x: 'ya' }), undefined) + t.equal(await rimraf(['a', 'b', 'c']), true) + t.equal(await rimraf(['i', 'j', 'k'], { x: 'ya' }), true) t.same(ASYNC_CALLS, [ [resolve('a'), {}], [resolve('b'), {}], @@ -180,8 +180,8 @@ t.test('accept array of paths as first arg', async t => { [resolve('k'), { x: 'ya' }], ]) - t.equal(rimrafSync(['x', 'y', 'z']), undefined) - t.equal(rimrafSync(['m', 'n', 'o'], { cat: 'chai' }), undefined) + t.equal(rimrafSync(['x', 'y', 'z']), true) + t.equal(rimrafSync(['m', 'n', 'o'], { cat: 'chai' }), true) t.same(SYNC_CALLS, [ [resolve('x'), {}], [resolve('y'), {}], diff --git a/test/rimraf-move-remove.js b/test/rimraf-move-remove.js index c158e568..9ce9e982 100644 --- a/test/rimraf-move-remove.js +++ b/test/rimraf-move-remove.js @@ -10,6 +10,9 @@ t.formatSnapshot = calls => ) ) +const { relative, basename } = require('path') +const { statSync } = require('fs') + const fixture = { a: 'a', b: 'b', @@ -535,3 +538,82 @@ t.test( t.end() } ) + +t.test('filter function', t => { + t.formatSnapshot = undefined + const { + rimrafMoveRemove, + rimrafMoveRemoveSync, + } = require('../dist/cjs/src/rimraf-move-remove.js') + + for (const f of ['i', 'j']) { + t.test(`filter=${f}`, t => { + t.test('sync', t => { + const dir = t.testdir(fixture) + const saw = [] + const filter = p => { + saw.push(relative(process.cwd(), p).replace(/\\/g, '/')) + return basename(p) !== f + } + rimrafMoveRemoveSync(dir, { filter }) + t.matchSnapshot( + saw.sort((a, b) => a.localeCompare(b, 'en')), + 'paths seen' + ) + statSync(dir) + statSync(dir + '/c') + statSync(dir + '/c/f') + statSync(dir + '/c/f/i') + if (f === 'j') { + statSync(dir + '/c/f/i/j') + } else { + t.throws(() => statSync(dir + '/c/f/i/j')) + } + t.throws(() => statSync(dir + '/a')) + t.throws(() => statSync(dir + '/b')) + t.throws(() => statSync(dir + '/c/d')) + t.throws(() => statSync(dir + '/c/e')) + t.throws(() => statSync(dir + '/c/f/g')) + t.throws(() => statSync(dir + '/c/f/h')) + t.throws(() => statSync(dir + '/c/f/i/k')) + t.throws(() => statSync(dir + '/c/f/i/l')) + t.throws(() => statSync(dir + '/c/f/i/m')) + t.end() + }) + + t.test('async', async t => { + const dir = t.testdir(fixture) + const saw = [] + const filter = p => { + saw.push(relative(process.cwd(), p).replace(/\\/g, '/')) + return basename(p) !== f + } + await rimrafMoveRemove(dir, { filter }) + t.matchSnapshot( + saw.sort((a, b) => a.localeCompare(b, 'en')), + 'paths seen' + ) + statSync(dir) + statSync(dir + '/c') + statSync(dir + '/c/f') + statSync(dir + '/c/f/i') + if (f === 'j') { + statSync(dir + '/c/f/i/j') + } else { + t.throws(() => statSync(dir + '/c/f/i/j')) + } + t.throws(() => statSync(dir + '/a')) + t.throws(() => statSync(dir + '/b')) + t.throws(() => statSync(dir + '/c/d')) + t.throws(() => statSync(dir + '/c/e')) + t.throws(() => statSync(dir + '/c/f/g')) + t.throws(() => statSync(dir + '/c/f/h')) + t.throws(() => statSync(dir + '/c/f/i/k')) + t.throws(() => statSync(dir + '/c/f/i/l')) + t.throws(() => statSync(dir + '/c/f/i/m')) + }) + t.end() + }) + } + t.end() +}) diff --git a/test/rimraf-posix.js b/test/rimraf-posix.js index f80025db..e5ce11eb 100644 --- a/test/rimraf-posix.js +++ b/test/rimraf-posix.js @@ -12,6 +12,8 @@ const { rimrafPosix, rimrafPosixSync, } = require('../dist/cjs/src/rimraf-posix.js') +const { parse, relative, basename } = require('path') +const { statSync } = require('fs') const fs = require('../dist/cjs/src/fs.js') @@ -175,7 +177,6 @@ t.test('ignore ENOENTs from unlink/rmdir', async t => { t.test('rimraffing root, do not actually rmdir root', async t => { let ROOT = null - const { parse } = require('path') const { rimrafPosix, rimrafPosixSync } = t.mock( '../dist/cjs/src/rimraf-posix.js', { @@ -206,26 +207,109 @@ t.test('rimraffing root, do not actually rmdir root', async t => { t.end() }) -t.test('abort on signal', { skip: typeof AbortController === 'undefined' }, t => { +t.test( + 'abort on signal', + { skip: typeof AbortController === 'undefined' }, + t => { + const { + rimrafPosix, + rimrafPosixSync, + } = require('../dist/cjs/src/rimraf-posix.js') + t.test('sync', t => { + const d = t.testdir(fixture) + const ac = new AbortController() + const { signal } = ac + ac.abort(new Error('aborted rimraf')) + t.throws(() => rimrafPosixSync(d, { signal })) + t.end() + }) + t.test('async', async t => { + const d = t.testdir(fixture) + const ac = new AbortController() + const { signal } = ac + const p = t.rejects(() => rimrafPosix(d, { signal })) + ac.abort(new Error('aborted rimraf')) + await p + }) + t.end() + } +) + +t.test('filter function', t => { + t.formatSnapshot = undefined const { rimrafPosix, rimrafPosixSync, } = require('../dist/cjs/src/rimraf-posix.js') - t.test('sync', t => { - const d = t.testdir(fixture) - const ac = new AbortController() - const { signal } = ac - ac.abort(new Error('aborted rimraf')) - t.throws(() => rimrafPosixSync(d, { signal })) - t.end() - }) - t.test('async', async t => { - const d = t.testdir(fixture) - const ac = new AbortController() - const { signal } = ac - const p = t.rejects(() => rimrafPosix(d, { signal })) - ac.abort(new Error('aborted rimraf')) - await p - }) + + for (const f of ['i', 'j']) { + t.test(`filter=${f}`, t => { + t.test('sync', t => { + const dir = t.testdir(fixture) + const saw = [] + const filter = p => { + saw.push(relative(process.cwd(), p).replace(/\\/g, '/')) + return basename(p) !== f + } + rimrafPosixSync(dir, { filter }) + t.matchSnapshot( + saw.sort((a, b) => a.localeCompare(b, 'en')), + 'paths seen' + ) + statSync(dir) + statSync(dir + '/c') + statSync(dir + '/c/f') + statSync(dir + '/c/f/i') + if (f === 'j') { + statSync(dir + '/c/f/i/j') + } else { + t.throws(() => statSync(dir + '/c/f/i/j')) + } + t.throws(() => statSync(dir + '/a')) + t.throws(() => statSync(dir + '/b')) + t.throws(() => statSync(dir + '/c/d')) + t.throws(() => statSync(dir + '/c/e')) + t.throws(() => statSync(dir + '/c/f/g')) + t.throws(() => statSync(dir + '/c/f/h')) + t.throws(() => statSync(dir + '/c/f/i/k')) + t.throws(() => statSync(dir + '/c/f/i/l')) + t.throws(() => statSync(dir + '/c/f/i/m')) + t.end() + }) + + t.test('async', async t => { + const dir = t.testdir(fixture) + const saw = [] + const filter = p => { + saw.push(relative(process.cwd(), p).replace(/\\/g, '/')) + return basename(p) !== f + } + await rimrafPosix(dir, { filter }) + t.matchSnapshot( + saw.sort((a, b) => a.localeCompare(b, 'en')), + 'paths seen' + ) + statSync(dir) + statSync(dir + '/c') + statSync(dir + '/c/f') + statSync(dir + '/c/f/i') + if (f === 'j') { + statSync(dir + '/c/f/i/j') + } else { + t.throws(() => statSync(dir + '/c/f/i/j')) + } + t.throws(() => statSync(dir + '/a')) + t.throws(() => statSync(dir + '/b')) + t.throws(() => statSync(dir + '/c/d')) + t.throws(() => statSync(dir + '/c/e')) + t.throws(() => statSync(dir + '/c/f/g')) + t.throws(() => statSync(dir + '/c/f/h')) + t.throws(() => statSync(dir + '/c/f/i/k')) + t.throws(() => statSync(dir + '/c/f/i/l')) + t.throws(() => statSync(dir + '/c/f/i/m')) + }) + t.end() + }) + } t.end() }) diff --git a/test/rimraf-windows.js b/test/rimraf-windows.js index f492bdef..26b5605d 100644 --- a/test/rimraf-windows.js +++ b/test/rimraf-windows.js @@ -1,4 +1,6 @@ const t = require('tap') +const { parse, basename, relative } = require('path') +const { statSync } = require('fs') t.formatSnapshot = calls => calls.map(args => args.map(arg => @@ -32,7 +34,7 @@ const fixture = { }, } -t.only('actually delete some stuff', async t => { +t.test('actually delete some stuff', async t => { const fs = require('../dist/cjs/src/fs.js') const fsMock = { ...fs, promises: { ...fs.promises } } @@ -47,7 +49,7 @@ t.only('actually delete some stuff', async t => { const danglers = [] const unlinkLater = path => { const p = new Promise(res => { - setTimeout(() => unlink(path).then(res, res), 50) + setTimeout(() => unlink(path).then(res, res), 100) }) danglers.push(p) } @@ -55,7 +57,9 @@ t.only('actually delete some stuff', async t => { fsMock.promises.unlink = async path => unlinkLater(path) // but actually do wait to clean them up, though - t.teardown(() => Promise.all(danglers)) + t.teardown(async () => { + await Promise.all(danglers) + }) const { rimrafPosix, rimrafPosixSync } = t.mock( '../dist/cjs/src/rimraf-posix.js', @@ -75,9 +79,9 @@ t.only('actually delete some stuff', async t => { t.throws(() => rimrafPosixSync(path)) t.end() }) - t.test('async', t => { + t.test('async', async t => { const path = t.testdir(fixture) - t.rejects(() => rimrafPosix(path)) + await t.rejects(() => rimrafPosix(path)) t.end() }) t.end() @@ -98,12 +102,12 @@ t.only('actually delete some stuff', async t => { const path = t.testdir(fixture) await rimrafWindows(path, {}) t.throws(() => statSync(path), { code: 'ENOENT' }, 'deleted') - t.resolves(rimrafWindows(path, {}), 'deleting a second time is OK') + await t.resolves(rimrafWindows(path, {}), 'deleting a second time is OK') }) t.end() }) -t.only('throw unlink errors', async t => { +t.test('throw unlink errors', async t => { const fs = require('../dist/cjs/src/fs.js') // only throw once here, or else it messes with tap's fixture cleanup // that's probably a bug in t.mock? @@ -148,7 +152,7 @@ t.only('throw unlink errors', async t => { t.end() }) -t.only('ignore ENOENT unlink errors', async t => { +t.test('ignore ENOENT unlink errors', async t => { const fs = require('../dist/cjs/src/fs.js') const threwAsync = false let threwSync = false @@ -200,12 +204,12 @@ t.test('throw rmdir errors', async t => { { '../dist/cjs/src/fs.js': { ...fs, - rmdirSync: path => { + rmdirSync: () => { throw Object.assign(new Error('cannot rmdir'), { code: 'FOO' }) }, promises: { ...fs.promises, - rmdir: async path => { + rmdir: async () => { throw Object.assign(new Error('cannot rmdir'), { code: 'FOO' }) }, }, @@ -234,12 +238,12 @@ t.test('throw unexpected readdir errors', async t => { { '../dist/cjs/src/fs.js': { ...fs, - readdirSync: path => { + readdirSync: () => { throw Object.assign(new Error('cannot readdir'), { code: 'FOO' }) }, promises: { ...fs.promises, - readdir: async path => { + readdir: async () => { throw Object.assign(new Error('cannot readdir'), { code: 'FOO' }) }, }, @@ -450,7 +454,6 @@ t.test('handle EPERMs, chmod raises something other than ENOENT', async t => { t.test('rimraffing root, do not actually rmdir root', async t => { const fs = require('../dist/cjs/src/fs.js') let ROOT = null - const { parse } = require('path') const { rimrafWindows, rimrafWindowsSync } = t.mock( '../dist/cjs/src/rimraf-windows.js', { @@ -525,3 +528,82 @@ t.test( t.end() } ) + +t.test('filter function', t => { + t.formatSnapshot = undefined + const { + rimrafWindows, + rimrafWindowsSync, + } = require('../dist/cjs/src/rimraf-windows.js') + + for (const f of ['i', 'j']) { + t.test(`filter=${f}`, t => { + t.test('sync', t => { + const dir = t.testdir(fixture) + const saw = [] + const filter = p => { + saw.push(relative(process.cwd(), p).replace(/\\/g, '/')) + return basename(p) !== f + } + rimrafWindowsSync(dir, { filter }) + t.matchSnapshot( + saw.sort((a, b) => a.localeCompare(b, 'en')), + 'paths seen' + ) + statSync(dir) + statSync(dir + '/c') + statSync(dir + '/c/f') + statSync(dir + '/c/f/i') + if (f === 'j') { + statSync(dir + '/c/f/i/j') + } else { + t.throws(() => statSync(dir + '/c/f/i/j')) + } + t.throws(() => statSync(dir + '/a')) + t.throws(() => statSync(dir + '/b')) + t.throws(() => statSync(dir + '/c/d')) + t.throws(() => statSync(dir + '/c/e')) + t.throws(() => statSync(dir + '/c/f/g')) + t.throws(() => statSync(dir + '/c/f/h')) + t.throws(() => statSync(dir + '/c/f/i/k')) + t.throws(() => statSync(dir + '/c/f/i/l')) + t.throws(() => statSync(dir + '/c/f/i/m')) + t.end() + }) + + t.test('async', async t => { + const dir = t.testdir(fixture) + const saw = [] + const filter = p => { + saw.push(relative(process.cwd(), p).replace(/\\/g, '/')) + return basename(p) !== f + } + await rimrafWindows(dir, { filter }) + t.matchSnapshot( + saw.sort((a, b) => a.localeCompare(b, 'en')), + 'paths seen' + ) + statSync(dir) + statSync(dir + '/c') + statSync(dir + '/c/f') + statSync(dir + '/c/f/i') + if (f === 'j') { + statSync(dir + '/c/f/i/j') + } else { + t.throws(() => statSync(dir + '/c/f/i/j')) + } + t.throws(() => statSync(dir + '/a')) + t.throws(() => statSync(dir + '/b')) + t.throws(() => statSync(dir + '/c/d')) + t.throws(() => statSync(dir + '/c/e')) + t.throws(() => statSync(dir + '/c/f/g')) + t.throws(() => statSync(dir + '/c/f/h')) + t.throws(() => statSync(dir + '/c/f/i/k')) + t.throws(() => statSync(dir + '/c/f/i/l')) + t.throws(() => statSync(dir + '/c/f/i/m')) + }) + t.end() + }) + } + t.end() +})