diff --git a/README.md b/README.md index 6a312d5..e754b5f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Install with `npm install rimraf`, or just drop rimraf.js somewhere. * The function returns a `Promise` instead of taking a callback. * Built-in glob support removed. +* Functions take arrays of paths, as well as a single path. * Native implementation used by default when available. * New implementation on Windows, making the exponential backoff for `EBUSY` and `ENOTEMPTY` errors no longer necessary. diff --git a/lib/index.js b/lib/index.js index 6f40b65..8087ecb 100644 --- a/lib/index.js +++ b/lib/index.js @@ -7,51 +7,53 @@ const {rimrafWindows, rimrafWindowsSync} = require('./rimraf-windows.js') const {rimrafPosix, rimrafPosixSync} = require('./rimraf-posix.js') const {useNative, useNativeSync} = require('./use-native.js') -const rimraf = (path, opts) => { - path = pathArg(path, opts) +const wrap = fn => async (path, opts) => { opts = optsArg(opts) - return useNative(opts) ? rimrafNative(path, opts) - : rimrafManual(path, opts) + await (Array.isArray(path) + ? Promise.all(path.map(p => fn(pathArg(p, opts), opts))) + : fn(pathArg(path, opts), opts)) } -const rimrafSync = async (path, opts) => { - path = pathArg(path, opts) +const wrapSync = fn => (path, opts) => { opts = optsArg(opts) - return useNativeSync(opts) ? rimrafNativeSync(path, opts) - : rimrafManualSync(path, opts) + return Array.isArray(path) + ? path.forEach(p => fn(pathArg(p, opts), opts)) + : fn(pathArg(path, opts), opts) } +const rimraf = wrap((path, opts) => + useNative(opts) + ? rimrafNative(path, opts) + : rimrafManual(path, opts)) + +const rimrafSync = wrapSync((path, opts) => + useNativeSync(opts) + ? rimrafNativeSync(path, opts) + : rimrafManualSync(path, opts)) + rimraf.rimraf = rimraf rimraf.sync = rimraf.rimrafSync = rimrafSync -const native = async (path, opts) => - rimrafNative(pathArg(path, opts), optsArg(opts)) -const nativeSync = (path, opts) => - rimrafNativeSync(pathArg(path, opts), optsArg(opts)) +const native = wrap(rimrafNative) +const nativeSync = wrapSync(rimrafNativeSync) native.sync = nativeSync rimraf.native = native rimraf.nativeSync = nativeSync -const manual = async (path, opts) => - rimrafManual(pathArg(path, opts), optsArg(opts)) -const manualSync = (path, opts) => - rimrafManualSync(pathArg(path, opts), optsArg(opts)) +const manual = wrap(rimrafManual) +const manualSync = wrapSync(rimrafManualSync) manual.sync = manualSync rimraf.manual = manual rimraf.manualSync = manualSync -const windows = async (path, opts) => - rimrafWindows(pathArg(path, opts), optsArg(opts)) -const windowsSync = (path, opts) => - rimrafWindowsSync(pathArg(path, opts), optsArg(opts)) +const windows = wrap(rimrafWindows) +const windowsSync = wrapSync(rimrafWindowsSync) windows.sync = windowsSync rimraf.windows = windows rimraf.windowsSync = windowsSync -const posix = async (path, opts) => - rimrafPosix(pathArg(path, opts), optsArg(opts)) -const posixSync = (path, opts) => - rimrafPosixSync(pathArg(path, opts), optsArg(opts)) +const posix = wrap(rimrafPosix) +const posixSync = wrapSync(rimrafPosixSync) posix.sync = posixSync rimraf.posix = posix rimraf.posixSync = posixSync diff --git a/lib/path-arg.js b/lib/path-arg.js index 905b36f..d9ef5bf 100644 --- a/lib/path-arg.js +++ b/lib/path-arg.js @@ -1,6 +1,21 @@ const platform = require('./platform.js') const { resolve, parse } = require('path') +const { inspect } = require('util') const pathArg = (path, opts = {}) => { + const type = typeof path + if (type !== 'string') { + const ctor = path && type === 'object' && path.constructor + const received = ctor && ctor.name ? `an instance of ${ctor.name}` + : type === 'object' ? inspect(path) + : `type ${type} ${path}` + const msg = 'The "path" argument must be of type string. ' + + `Received ${received}` + throw Object.assign(new TypeError(msg), { + path, + code: 'ERR_INVALID_ARG_TYPE', + }) + } + if (/\0/.test(path)) { // simulate same failure that node raises const msg = 'path must be a string without null bytes' diff --git a/tap-snapshots/test/index.js.test.cjs b/tap-snapshots/test/index.js.test.cjs index ad55cde..8695ca2 100644 --- a/tap-snapshots/test/index.js.test.cjs +++ b/tap-snapshots/test/index.js.test.cjs @@ -7,16 +7,16 @@ 'use strict' exports[`test/index.js TAP mocky unit tests to select the correct function main function, useNative=false > must match snapshot 1`] = ` Array [ - Array [ - "pathArg", - "path", - ], Array [ "optsArg", Object { "a": 1, }, ], + Array [ + "pathArg", + "path", + ], Array [ "useNative", Object { @@ -30,16 +30,16 @@ Array [ "a": 1, }, ], - Array [ - "pathArg", - "path", - ], Array [ "optsArg", Object { "a": 2, }, ], + Array [ + "pathArg", + "path", + ], Array [ "useNativeSync", Object { @@ -58,16 +58,16 @@ Array [ exports[`test/index.js TAP mocky unit tests to select the correct function main function, useNative=true > must match snapshot 1`] = ` Array [ - Array [ - "pathArg", - "path", - ], Array [ "optsArg", Object { "a": 1, }, ], + Array [ + "pathArg", + "path", + ], Array [ "useNative", Object { @@ -81,16 +81,16 @@ Array [ "a": 1, }, ], - Array [ - "pathArg", - "path", - ], Array [ "optsArg", Object { "a": 2, }, ], + Array [ + "pathArg", + "path", + ], Array [ "useNativeSync", Object { @@ -109,16 +109,16 @@ Array [ exports[`test/index.js TAP mocky unit tests to select the correct function manual > must match snapshot 1`] = ` Array [ - Array [ - "pathArg", - "path", - ], Array [ "optsArg", Object { "a": 3, }, ], + Array [ + "pathArg", + "path", + ], Array [ "rimrafPosix", "path", @@ -126,16 +126,16 @@ Array [ "a": 3, }, ], - Array [ - "pathArg", - "path", - ], Array [ "optsArg", Object { "a": 4, }, ], + Array [ + "pathArg", + "path", + ], Array [ "rimrafPosixSync", "path", @@ -148,16 +148,16 @@ Array [ exports[`test/index.js TAP mocky unit tests to select the correct function native > must match snapshot 1`] = ` Array [ - Array [ - "pathArg", - "path", - ], Array [ "optsArg", Object { "a": 5, }, ], + Array [ + "pathArg", + "path", + ], Array [ "rimrafNative", "path", @@ -165,16 +165,16 @@ Array [ "a": 5, }, ], - Array [ - "pathArg", - "path", - ], Array [ "optsArg", Object { "a": 6, }, ], + Array [ + "pathArg", + "path", + ], Array [ "rimrafNativeSync", "path", @@ -187,16 +187,16 @@ Array [ exports[`test/index.js TAP mocky unit tests to select the correct function posix > must match snapshot 1`] = ` Array [ - Array [ - "pathArg", - "path", - ], Array [ "optsArg", Object { "a": 7, }, ], + Array [ + "pathArg", + "path", + ], Array [ "rimrafPosix", "path", @@ -204,16 +204,16 @@ Array [ "a": 7, }, ], - Array [ - "pathArg", - "path", - ], Array [ "optsArg", Object { "a": 8, }, ], + Array [ + "pathArg", + "path", + ], Array [ "rimrafPosixSync", "path", @@ -226,16 +226,16 @@ Array [ exports[`test/index.js TAP mocky unit tests to select the correct function windows > must match snapshot 1`] = ` Array [ - Array [ - "pathArg", - "path", - ], Array [ "optsArg", Object { "a": 9, }, ], + Array [ + "pathArg", + "path", + ], Array [ "rimrafWindows", "path", @@ -243,16 +243,16 @@ Array [ "a": 9, }, ], - Array [ - "pathArg", - "path", - ], Array [ "optsArg", Object { "a": 10, }, ], + Array [ + "pathArg", + "path", + ], Array [ "rimrafWindowsSync", "path", diff --git a/test/index.js b/test/index.js index f105f83..7a12db7 100644 --- a/test/index.js +++ b/test/index.js @@ -139,3 +139,40 @@ t.test('actually delete some stuff', t => { }) t.end() }) + +t.test('accept array of paths as first arg', async t => { + const { resolve } = require('path') + const ASYNC_CALLS = [] + const SYNC_CALLS = [] + const {rimraf, rimrafSync} = t.mock('../', { + '../lib/use-native.js': { + useNative: () => true, + useNativeSync: () => true, + }, + '../lib/rimraf-native.js': { + rimrafNative: async (path, opts) => ASYNC_CALLS.push([path, opts]), + rimrafNativeSync: (path, opts) => SYNC_CALLS.push([path, opts]), + }, + }) + t.equal(await rimraf(['a', 'b', 'c']), undefined) + t.equal(await rimraf(['i', 'j', 'k'], { x: 'ya' }), undefined) + t.same(ASYNC_CALLS, [ + [resolve('a'), {}], + [resolve('b'), {}], + [resolve('c'), {}], + [resolve('i'), {x: 'ya'}], + [resolve('j'), {x: 'ya'}], + [resolve('k'), {x: 'ya'}], + ]) + + t.equal(rimrafSync(['x', 'y', 'z']), undefined) + t.equal(rimrafSync(['m', 'n', 'o'], { cat: 'chai' }), undefined) + t.same(SYNC_CALLS, [ + [resolve('x'), {}], + [resolve('y'), {}], + [resolve('z'), {}], + [resolve('m'), {cat: 'chai'}], + [resolve('n'), {cat: 'chai'}], + [resolve('o'), {cat: 'chai'}], + ]) +}) diff --git a/test/path-arg.js b/test/path-arg.js index 5cef8cd..7f7647a 100644 --- a/test/path-arg.js +++ b/test/path-arg.js @@ -42,3 +42,33 @@ t.throws(() => pathArg('/'), { code: 'ERR_PRESERVE_ROOT' }) t.throws(() => pathArg('/', { preserveRoot: null }), { code: 'ERR_PRESERVE_ROOT' }) t.equal(pathArg('/', { preserveRoot: false }), resolve('/')) + +t.throws(() => pathArg({}), { + code: 'ERR_INVALID_ARG_TYPE', + path: {}, + message: 'The "path" argument must be of type string. ' + + 'Received an instance of Object', + name: 'TypeError', +}) +t.throws(() => pathArg([]), { + code: 'ERR_INVALID_ARG_TYPE', + path: [], + message: 'The "path" argument must be of type string. ' + + 'Received an instance of Array', + name: 'TypeError', +}) +const { inspect } = require('util') +t.throws(() => pathArg(Object.create(null)), { + code: 'ERR_INVALID_ARG_TYPE', + path: Object.create(null), + message: 'The "path" argument must be of type string. ' + + `Received ${inspect(Object.create(null))}`, + name: 'TypeError', +}) +t.throws(() => pathArg(true), { + code: 'ERR_INVALID_ARG_TYPE', + path: true, + message: 'The "path" argument must be of type string. ' + + `Received type boolean true`, + name: 'TypeError', +})