Skip to content

Commit

Permalink
let the filter option be async for async methods
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacs committed Mar 4, 2023
1 parent 3b57687 commit ca28abb
Show file tree
Hide file tree
Showing 18 changed files with 325 additions and 97 deletions.
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,17 @@ Options:

- `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.
deleted. With async rimraf methods, this may return a Promise
that resolves to a boolean. (Since Promises are truthy,
returning a Promise from a sync filter is the same as just not
filtering anything.)

If a filter method is provided, it will _only_ remove entries
if the filter returns (or resolves to) a truthy value. Omitting
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, since the directory would not be empty in
that case.

Using a filter method prevents the use of Node's built-in
`fs.rm` because that implementation does not support filtering.
Expand Down
23 changes: 16 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import optArg from './opt-arg.js'
import { optArg, optArgSync } from './opt-arg.js'
import pathArg from './path-arg.js'

import { glob, GlobOptions, globSync } from 'glob'

export interface RimrafOptions {
export interface RimrafAsyncOptions {
preserveRoot?: boolean
tmp?: string
maxRetries?: number
Expand All @@ -12,9 +12,15 @@ export interface RimrafOptions {
maxBackoff?: number
signal?: AbortSignal
glob?: boolean | GlobOptions
filter?: ((path: string) => boolean) | ((path: string) => Promise<boolean>)
}

export interface RimrafSyncOptions extends RimrafAsyncOptions {
filter?: (path: string) => boolean
}

export type RimrafOptions = RimrafSyncOptions | RimrafAsyncOptions

const typeOrUndef = (val: any, t: string) =>
typeof val === 'undefined' || typeof val === t

Expand Down Expand Up @@ -46,8 +52,11 @@ import { rimrafWindows, rimrafWindowsSync } from './rimraf-windows.js'
import { useNative, useNativeSync } from './use-native.js'

const wrap =
(fn: (p: string, o: RimrafOptions) => Promise<boolean>) =>
async (path: string | string[], opt?: RimrafOptions): Promise<boolean> => {
(fn: (p: string, o: RimrafAsyncOptions) => Promise<boolean>) =>
async (
path: string | string[],
opt?: RimrafAsyncOptions
): Promise<boolean> => {
const options = optArg(opt)
if (options.glob) {
path = await glob(path, options.glob)
Expand All @@ -62,9 +71,9 @@ const wrap =
}

const wrapSync =
(fn: (p: string, o: RimrafOptions) => boolean) =>
(path: string | string[], opt?: RimrafOptions): boolean => {
const options = optArg(opt)
(fn: (p: string, o: RimrafSyncOptions) => boolean) =>
(path: string | string[], opt?: RimrafSyncOptions): boolean => {
const options = optArgSync(opt)
if (options.glob) {
path = globSync(path, options.glob)
}
Expand Down
29 changes: 21 additions & 8 deletions src/opt-arg.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import { GlobOptions } from 'glob'
import { assertRimrafOptions, RimrafOptions } from './index.js'
export default (
opt: RimrafOptions = {}
): RimrafOptions & {
glob?: GlobOptions & { withFileTypes: false }
} => {
import {
assertRimrafOptions,
RimrafAsyncOptions,
RimrafOptions,
RimrafSyncOptions,
} from './index.js'

const optArgT = <T extends RimrafOptions>(
opt: T
):
| (T & {
glob: GlobOptions & { withFileTypes: false }
})
| (T & { glob: undefined }) => {
assertRimrafOptions(opt)
const { glob, ...options } = opt
if (!glob) return options
if (!glob) {
return options as T & { glob: undefined }
}
const globOpt =
glob === true
? opt.signal
Expand All @@ -28,5 +38,8 @@ export default (
absolute: true,
withFileTypes: false,
},
}
} as T & { glob: GlobOptions & { withFileTypes: false } }
}

export const optArg = (opt: RimrafAsyncOptions = {}) => optArgT(opt)
export const optArgSync = (opt: RimrafSyncOptions = {}) => optArgT(opt)
8 changes: 4 additions & 4 deletions src/path-arg.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import platform from './platform.js'
import { resolve, parse } from 'path'
import { parse, resolve } from 'path'
import { inspect } from 'util'
import { RimrafOptions } from './index.js'
import { RimrafAsyncOptions } from './index.js'
import platform from './platform.js'

const pathArg = (path: string, opt: RimrafOptions = {}) => {
const pathArg = (path: string, opt: RimrafAsyncOptions = {}) => {
const type = typeof path
if (type !== 'string') {
const ctor = path && type === 'object' && path.constructor
Expand Down
4 changes: 2 additions & 2 deletions src/retry-busy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// note: max backoff is the maximum that any *single* backoff will do

import { RimrafOptions } from '.'
import { RimrafAsyncOptions, RimrafOptions } from '.'

export const MAXBACKOFF = 200
export const RATE = 1.2
Expand All @@ -10,7 +10,7 @@ export const codes = new Set(['EMFILE', 'ENFILE', 'EBUSY'])
export const retryBusy = (fn: (path: string) => Promise<any>) => {
const method = async (
path: string,
opt: RimrafOptions,
opt: RimrafAsyncOptions,
backoff = 1,
total = 0
) => {
Expand Down
10 changes: 5 additions & 5 deletions src/rimraf-move-remove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import {
} from './fs.js'
const { rename, unlink, rmdir, chmod } = fsPromises

import { RimrafOptions } from '.'
import { RimrafAsyncOptions, RimrafSyncOptions } from '.'
import { readdirOrError, readdirOrErrorSync } from './readdir-or-error.js'

// crypto.randomBytes is much slower, and Math.random() is enough here
Expand Down Expand Up @@ -71,7 +71,7 @@ const unlinkFixEPERMSync = (path: string) => {

export const rimrafMoveRemove = async (
path: string,
opt: RimrafOptions
opt: RimrafAsyncOptions
): Promise<boolean> => {
if (opt?.signal?.aborted) {
throw opt.signal.reason
Expand All @@ -91,7 +91,7 @@ export const rimrafMoveRemove = async (
if (entries.code !== 'ENOTDIR') {
throw entries
}
if (opt.filter && !opt.filter(path)) {
if (opt.filter && !(await opt.filter(path))) {
return false
}
await ignoreENOENT(tmpUnlink(path, opt.tmp, unlinkFixEPERM))
Expand All @@ -113,7 +113,7 @@ export const rimrafMoveRemove = async (
if (opt.preserveRoot === false && path === parse(path).root) {
return false
}
if (opt.filter && !opt.filter(path)) {
if (opt.filter && !(await opt.filter(path))) {
return false
}
await ignoreENOENT(tmpUnlink(path, opt.tmp, rmdir))
Expand All @@ -132,7 +132,7 @@ const tmpUnlink = async (

export const rimrafMoveRemoveSync = (
path: string,
opt: RimrafOptions
opt: RimrafSyncOptions
): boolean => {
if (opt?.signal?.aborted) {
throw opt.signal.reason
Expand Down
9 changes: 6 additions & 3 deletions src/rimraf-native.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { RimrafOptions } from '.'
import { RimrafAsyncOptions, RimrafSyncOptions } from '.'
import { promises, rmSync } from './fs.js'
const { rm } = promises

export const rimrafNative = async (
path: string,
opt: RimrafOptions
opt: RimrafAsyncOptions
): Promise<boolean> => {
await rm(path, {
...opt,
Expand All @@ -14,7 +14,10 @@ export const rimrafNative = async (
return true
}

export const rimrafNativeSync = (path: string, opt: RimrafOptions): boolean => {
export const rimrafNativeSync = (
path: string,
opt: RimrafSyncOptions
): boolean => {
rmSync(path, {
...opt,
force: true,
Expand Down
17 changes: 10 additions & 7 deletions src/rimraf-posix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import { parse, resolve } from 'path'

import { readdirOrError, readdirOrErrorSync } from './readdir-or-error.js'

import { RimrafOptions } from '.'
import { RimrafAsyncOptions, RimrafSyncOptions } from '.'
import { ignoreENOENT, ignoreENOENTSync } from './ignore-enoent.js'

export const rimrafPosix = async (
path: string,
opt: RimrafOptions
opt: RimrafAsyncOptions
): Promise<boolean> => {
if (opt?.signal?.aborted) {
throw opt.signal.reason
Expand All @@ -30,7 +30,7 @@ export const rimrafPosix = async (
if (entries.code !== 'ENOTDIR') {
throw entries
}
if (opt.filter && !opt.filter(path)) {
if (opt.filter && !(await opt.filter(path))) {
return false
}
await ignoreENOENT(unlink(path))
Expand All @@ -54,15 +54,18 @@ export const rimrafPosix = async (
return false
}

if (opt.filter && !opt.filter(path)) {
if (opt.filter && !(await opt.filter(path))) {
return false
}

await ignoreENOENT(rmdir(path))
return true
}

export const rimrafPosixSync = (path: string, opt: RimrafOptions): boolean => {
export const rimrafPosixSync = (
path: string,
opt: RimrafSyncOptions
): boolean => {
if (opt?.signal?.aborted) {
throw opt.signal.reason
}
Expand All @@ -84,11 +87,11 @@ export const rimrafPosixSync = (path: string, opt: RimrafOptions): boolean => {
for (const entry of entries) {
removedAll = rimrafPosixSync(resolve(path, entry), opt) && removedAll
}
if (!removedAll) {
if (opt.preserveRoot === false && path === parse(path).root) {
return false
}

if (opt.preserveRoot === false && path === parse(path).root) {
if (!removedAll) {
return false
}

Expand Down
20 changes: 10 additions & 10 deletions src/rimraf-windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// Note: "move then remove" is 2-10 times slower, and just as unreliable.

import { parse, resolve } from 'path'
import { RimrafOptions } from '.'
import { RimrafAsyncOptions, RimrafSyncOptions } from '.'
import { fixEPERM, fixEPERMSync } from './fix-eperm.js'
import { promises, rmdirSync, unlinkSync } from './fs.js'
import { ignoreENOENT, ignoreENOENTSync } from './ignore-enoent.js'
Expand All @@ -25,7 +25,7 @@ const rimrafWindowsDirSync = retryBusySync(fixEPERMSync(rmdirSync))

const rimrafWindowsDirMoveRemoveFallback = async (
path: string,
opt: RimrafOptions
opt: RimrafAsyncOptions
): Promise<boolean> => {
/* c8 ignore start */
if (opt?.signal?.aborted) {
Expand All @@ -46,7 +46,7 @@ const rimrafWindowsDirMoveRemoveFallback = async (

const rimrafWindowsDirMoveRemoveFallbackSync = (
path: string,
opt: RimrafOptions
opt: RimrafSyncOptions
): boolean => {
if (opt?.signal?.aborted) {
throw opt.signal.reason
Expand All @@ -71,7 +71,7 @@ const states = new Set([START, CHILD, FINISH])

export const rimrafWindows = async (
path: string,
opt: RimrafOptions,
opt: RimrafAsyncOptions,
state = START
): Promise<boolean> => {
if (opt?.signal?.aborted) {
Expand All @@ -89,7 +89,7 @@ export const rimrafWindows = async (
if (entries.code !== 'ENOTDIR') {
throw entries
}
if (opt.filter && !opt.filter(path)) {
if (opt.filter && !(await opt.filter(path))) {
return false
}
// is a file
Expand All @@ -110,10 +110,10 @@ export const rimrafWindows = async (
if (opt.preserveRoot === false && path === parse(path).root) {
return false
}
if (opt.filter && !opt.filter(path)) {
if (!removedAll) {
return false
}
if (!removedAll) {
if (opt.filter && !(await opt.filter(path))) {
return false
}
await ignoreENOENT(rimrafWindowsDirMoveRemoveFallback(path, opt))
Expand All @@ -123,7 +123,7 @@ export const rimrafWindows = async (

export const rimrafWindowsSync = (
path: string,
opt: RimrafOptions,
opt: RimrafSyncOptions,
state = START
): boolean => {
if (!states.has(state)) {
Expand Down Expand Up @@ -158,10 +158,10 @@ export const rimrafWindowsSync = (
if (opt.preserveRoot === false && path === parse(path).root) {
return false
}
if (opt.filter && !opt.filter(path)) {
if (!removedAll) {
return false
}
if (!removedAll) {
if (opt.filter && !opt.filter(path)) {
return false
}
ignoreENOENTSync(() => {
Expand Down
4 changes: 2 additions & 2 deletions src/use-native.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
const version = process.env.__TESTING_RIMRAF_NODE_VERSION__ || process.version
const versArr = version.replace(/^v/, '').split('.')
const hasNative = +versArr[0] > 14 || (+versArr[0] === 14 && +versArr[1] >= 14)
import { RimrafOptions } from './index.js'
import { RimrafAsyncOptions, RimrafOptions } from './index.js'
// we do NOT use native by default on Windows, because Node's native
// rm implementation is less advanced. Change this code if that changes.
import platform from './platform.js'
export const useNative: (opt?: RimrafOptions) => boolean =
export const useNative: (opt?: RimrafAsyncOptions) => boolean =
!hasNative || platform === 'win32'
? () => false
: opt => !opt?.signal && !opt?.filter
Expand Down
Loading

0 comments on commit ca28abb

Please sign in to comment.