Skip to content

Commit ca28abb

Browse files
committed
let the filter option be async for async methods
1 parent 3b57687 commit ca28abb

18 files changed

+325
-97
lines changed

README.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,17 @@ Options:
7979

8080
- `filter` Method that receives a path string as an argument, and
8181
returns a boolean indicating whether that path should be
82-
deleted.
83-
84-
If a filter method is provided, it _must_ return a truthy
85-
value, or nothing will be removed. Filtering out a directory
86-
will still allow its children to be removed, unless they are
87-
also filtered out, but any parents of a filtered entry will not
88-
be removed.
82+
deleted. With async rimraf methods, this may return a Promise
83+
that resolves to a boolean. (Since Promises are truthy,
84+
returning a Promise from a sync filter is the same as just not
85+
filtering anything.)
86+
87+
If a filter method is provided, it will _only_ remove entries
88+
if the filter returns (or resolves to) a truthy value. Omitting
89+
a directory will still allow its children to be removed, unless
90+
they are also filtered out, but any parents of a filtered entry
91+
will not be removed, since the directory would not be empty in
92+
that case.
8993

9094
Using a filter method prevents the use of Node's built-in
9195
`fs.rm` because that implementation does not support filtering.

src/index.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import optArg from './opt-arg.js'
1+
import { optArg, optArgSync } from './opt-arg.js'
22
import pathArg from './path-arg.js'
33

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

6-
export interface RimrafOptions {
6+
export interface RimrafAsyncOptions {
77
preserveRoot?: boolean
88
tmp?: string
99
maxRetries?: number
@@ -12,9 +12,15 @@ export interface RimrafOptions {
1212
maxBackoff?: number
1313
signal?: AbortSignal
1414
glob?: boolean | GlobOptions
15+
filter?: ((path: string) => boolean) | ((path: string) => Promise<boolean>)
16+
}
17+
18+
export interface RimrafSyncOptions extends RimrafAsyncOptions {
1519
filter?: (path: string) => boolean
1620
}
1721

22+
export type RimrafOptions = RimrafSyncOptions | RimrafAsyncOptions
23+
1824
const typeOrUndef = (val: any, t: string) =>
1925
typeof val === 'undefined' || typeof val === t
2026

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

4854
const wrap =
49-
(fn: (p: string, o: RimrafOptions) => Promise<boolean>) =>
50-
async (path: string | string[], opt?: RimrafOptions): Promise<boolean> => {
55+
(fn: (p: string, o: RimrafAsyncOptions) => Promise<boolean>) =>
56+
async (
57+
path: string | string[],
58+
opt?: RimrafAsyncOptions
59+
): Promise<boolean> => {
5160
const options = optArg(opt)
5261
if (options.glob) {
5362
path = await glob(path, options.glob)
@@ -62,9 +71,9 @@ const wrap =
6271
}
6372

6473
const wrapSync =
65-
(fn: (p: string, o: RimrafOptions) => boolean) =>
66-
(path: string | string[], opt?: RimrafOptions): boolean => {
67-
const options = optArg(opt)
74+
(fn: (p: string, o: RimrafSyncOptions) => boolean) =>
75+
(path: string | string[], opt?: RimrafSyncOptions): boolean => {
76+
const options = optArgSync(opt)
6877
if (options.glob) {
6978
path = globSync(path, options.glob)
7079
}

src/opt-arg.ts

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
import { GlobOptions } from 'glob'
2-
import { assertRimrafOptions, RimrafOptions } from './index.js'
3-
export default (
4-
opt: RimrafOptions = {}
5-
): RimrafOptions & {
6-
glob?: GlobOptions & { withFileTypes: false }
7-
} => {
2+
import {
3+
assertRimrafOptions,
4+
RimrafAsyncOptions,
5+
RimrafOptions,
6+
RimrafSyncOptions,
7+
} from './index.js'
8+
9+
const optArgT = <T extends RimrafOptions>(
10+
opt: T
11+
):
12+
| (T & {
13+
glob: GlobOptions & { withFileTypes: false }
14+
})
15+
| (T & { glob: undefined }) => {
816
assertRimrafOptions(opt)
917
const { glob, ...options } = opt
10-
if (!glob) return options
18+
if (!glob) {
19+
return options as T & { glob: undefined }
20+
}
1121
const globOpt =
1222
glob === true
1323
? opt.signal
@@ -28,5 +38,8 @@ export default (
2838
absolute: true,
2939
withFileTypes: false,
3040
},
31-
}
41+
} as T & { glob: GlobOptions & { withFileTypes: false } }
3242
}
43+
44+
export const optArg = (opt: RimrafAsyncOptions = {}) => optArgT(opt)
45+
export const optArgSync = (opt: RimrafSyncOptions = {}) => optArgT(opt)

src/path-arg.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import platform from './platform.js'
2-
import { resolve, parse } from 'path'
1+
import { parse, resolve } from 'path'
32
import { inspect } from 'util'
4-
import { RimrafOptions } from './index.js'
3+
import { RimrafAsyncOptions } from './index.js'
4+
import platform from './platform.js'
55

6-
const pathArg = (path: string, opt: RimrafOptions = {}) => {
6+
const pathArg = (path: string, opt: RimrafAsyncOptions = {}) => {
77
const type = typeof path
88
if (type !== 'string') {
99
const ctor = path && type === 'object' && path.constructor

src/retry-busy.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// note: max backoff is the maximum that any *single* backoff will do
22

3-
import { RimrafOptions } from '.'
3+
import { RimrafAsyncOptions, RimrafOptions } from '.'
44

55
export const MAXBACKOFF = 200
66
export const RATE = 1.2
@@ -10,7 +10,7 @@ export const codes = new Set(['EMFILE', 'ENFILE', 'EBUSY'])
1010
export const retryBusy = (fn: (path: string) => Promise<any>) => {
1111
const method = async (
1212
path: string,
13-
opt: RimrafOptions,
13+
opt: RimrafAsyncOptions,
1414
backoff = 1,
1515
total = 0
1616
) => {

src/rimraf-move-remove.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
} from './fs.js'
2626
const { rename, unlink, rmdir, chmod } = fsPromises
2727

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

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

7272
export const rimrafMoveRemove = async (
7373
path: string,
74-
opt: RimrafOptions
74+
opt: RimrafAsyncOptions
7575
): Promise<boolean> => {
7676
if (opt?.signal?.aborted) {
7777
throw opt.signal.reason
@@ -91,7 +91,7 @@ export const rimrafMoveRemove = async (
9191
if (entries.code !== 'ENOTDIR') {
9292
throw entries
9393
}
94-
if (opt.filter && !opt.filter(path)) {
94+
if (opt.filter && !(await opt.filter(path))) {
9595
return false
9696
}
9797
await ignoreENOENT(tmpUnlink(path, opt.tmp, unlinkFixEPERM))
@@ -113,7 +113,7 @@ export const rimrafMoveRemove = async (
113113
if (opt.preserveRoot === false && path === parse(path).root) {
114114
return false
115115
}
116-
if (opt.filter && !opt.filter(path)) {
116+
if (opt.filter && !(await opt.filter(path))) {
117117
return false
118118
}
119119
await ignoreENOENT(tmpUnlink(path, opt.tmp, rmdir))
@@ -132,7 +132,7 @@ const tmpUnlink = async (
132132

133133
export const rimrafMoveRemoveSync = (
134134
path: string,
135-
opt: RimrafOptions
135+
opt: RimrafSyncOptions
136136
): boolean => {
137137
if (opt?.signal?.aborted) {
138138
throw opt.signal.reason

src/rimraf-native.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { RimrafOptions } from '.'
1+
import { RimrafAsyncOptions, RimrafSyncOptions } from '.'
22
import { promises, rmSync } from './fs.js'
33
const { rm } = promises
44

55
export const rimrafNative = async (
66
path: string,
7-
opt: RimrafOptions
7+
opt: RimrafAsyncOptions
88
): Promise<boolean> => {
99
await rm(path, {
1010
...opt,
@@ -14,7 +14,10 @@ export const rimrafNative = async (
1414
return true
1515
}
1616

17-
export const rimrafNativeSync = (path: string, opt: RimrafOptions): boolean => {
17+
export const rimrafNativeSync = (
18+
path: string,
19+
opt: RimrafSyncOptions
20+
): boolean => {
1821
rmSync(path, {
1922
...opt,
2023
force: true,

src/rimraf-posix.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ import { parse, resolve } from 'path'
1212

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

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

1818
export const rimrafPosix = async (
1919
path: string,
20-
opt: RimrafOptions
20+
opt: RimrafAsyncOptions
2121
): Promise<boolean> => {
2222
if (opt?.signal?.aborted) {
2323
throw opt.signal.reason
@@ -30,7 +30,7 @@ export const rimrafPosix = async (
3030
if (entries.code !== 'ENOTDIR') {
3131
throw entries
3232
}
33-
if (opt.filter && !opt.filter(path)) {
33+
if (opt.filter && !(await opt.filter(path))) {
3434
return false
3535
}
3636
await ignoreENOENT(unlink(path))
@@ -54,15 +54,18 @@ export const rimrafPosix = async (
5454
return false
5555
}
5656

57-
if (opt.filter && !opt.filter(path)) {
57+
if (opt.filter && !(await opt.filter(path))) {
5858
return false
5959
}
6060

6161
await ignoreENOENT(rmdir(path))
6262
return true
6363
}
6464

65-
export const rimrafPosixSync = (path: string, opt: RimrafOptions): boolean => {
65+
export const rimrafPosixSync = (
66+
path: string,
67+
opt: RimrafSyncOptions
68+
): boolean => {
6669
if (opt?.signal?.aborted) {
6770
throw opt.signal.reason
6871
}
@@ -84,11 +87,11 @@ export const rimrafPosixSync = (path: string, opt: RimrafOptions): boolean => {
8487
for (const entry of entries) {
8588
removedAll = rimrafPosixSync(resolve(path, entry), opt) && removedAll
8689
}
87-
if (!removedAll) {
90+
if (opt.preserveRoot === false && path === parse(path).root) {
8891
return false
8992
}
9093

91-
if (opt.preserveRoot === false && path === parse(path).root) {
94+
if (!removedAll) {
9295
return false
9396
}
9497

src/rimraf-windows.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// Note: "move then remove" is 2-10 times slower, and just as unreliable.
1010

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

2626
const rimrafWindowsDirMoveRemoveFallback = async (
2727
path: string,
28-
opt: RimrafOptions
28+
opt: RimrafAsyncOptions
2929
): Promise<boolean> => {
3030
/* c8 ignore start */
3131
if (opt?.signal?.aborted) {
@@ -46,7 +46,7 @@ const rimrafWindowsDirMoveRemoveFallback = async (
4646

4747
const rimrafWindowsDirMoveRemoveFallbackSync = (
4848
path: string,
49-
opt: RimrafOptions
49+
opt: RimrafSyncOptions
5050
): boolean => {
5151
if (opt?.signal?.aborted) {
5252
throw opt.signal.reason
@@ -71,7 +71,7 @@ const states = new Set([START, CHILD, FINISH])
7171

7272
export const rimrafWindows = async (
7373
path: string,
74-
opt: RimrafOptions,
74+
opt: RimrafAsyncOptions,
7575
state = START
7676
): Promise<boolean> => {
7777
if (opt?.signal?.aborted) {
@@ -89,7 +89,7 @@ export const rimrafWindows = async (
8989
if (entries.code !== 'ENOTDIR') {
9090
throw entries
9191
}
92-
if (opt.filter && !opt.filter(path)) {
92+
if (opt.filter && !(await opt.filter(path))) {
9393
return false
9494
}
9595
// is a file
@@ -110,10 +110,10 @@ export const rimrafWindows = async (
110110
if (opt.preserveRoot === false && path === parse(path).root) {
111111
return false
112112
}
113-
if (opt.filter && !opt.filter(path)) {
113+
if (!removedAll) {
114114
return false
115115
}
116-
if (!removedAll) {
116+
if (opt.filter && !(await opt.filter(path))) {
117117
return false
118118
}
119119
await ignoreENOENT(rimrafWindowsDirMoveRemoveFallback(path, opt))
@@ -123,7 +123,7 @@ export const rimrafWindows = async (
123123

124124
export const rimrafWindowsSync = (
125125
path: string,
126-
opt: RimrafOptions,
126+
opt: RimrafSyncOptions,
127127
state = START
128128
): boolean => {
129129
if (!states.has(state)) {
@@ -158,10 +158,10 @@ export const rimrafWindowsSync = (
158158
if (opt.preserveRoot === false && path === parse(path).root) {
159159
return false
160160
}
161-
if (opt.filter && !opt.filter(path)) {
161+
if (!removedAll) {
162162
return false
163163
}
164-
if (!removedAll) {
164+
if (opt.filter && !opt.filter(path)) {
165165
return false
166166
}
167167
ignoreENOENTSync(() => {

src/use-native.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
const version = process.env.__TESTING_RIMRAF_NODE_VERSION__ || process.version
22
const versArr = version.replace(/^v/, '').split('.')
33
const hasNative = +versArr[0] > 14 || (+versArr[0] === 14 && +versArr[1] >= 14)
4-
import { RimrafOptions } from './index.js'
4+
import { RimrafAsyncOptions, RimrafOptions } from './index.js'
55
// we do NOT use native by default on Windows, because Node's native
66
// rm implementation is less advanced. Change this code if that changes.
77
import platform from './platform.js'
8-
export const useNative: (opt?: RimrafOptions) => boolean =
8+
export const useNative: (opt?: RimrafAsyncOptions) => boolean =
99
!hasNative || platform === 'win32'
1010
? () => false
1111
: opt => !opt?.signal && !opt?.filter

0 commit comments

Comments
 (0)