1 | const assert = require("assert")
|
2 | const path = require("path")
|
3 | const fs = require("fs")
|
4 | let glob = undefined
|
5 | try {
|
6 | glob = require("glob")
|
7 | } catch (_err) {
|
8 |
|
9 | }
|
10 |
|
11 | const defaultGlobOpts = {
|
12 | nosort: true,
|
13 | silent: true
|
14 | }
|
15 |
|
16 |
|
17 | let timeout = 0
|
18 |
|
19 | const isWindows = (process.platform === "win32")
|
20 |
|
21 | const defaults = options => {
|
22 | const methods = [
|
23 | 'unlink',
|
24 | 'chmod',
|
25 | 'stat',
|
26 | 'lstat',
|
27 | 'rmdir',
|
28 | 'readdir'
|
29 | ]
|
30 | methods.forEach(m => {
|
31 | options[m] = options[m] || fs[m]
|
32 | m = m + 'Sync'
|
33 | options[m] = options[m] || fs[m]
|
34 | })
|
35 |
|
36 | options.maxBusyTries = options.maxBusyTries || 3
|
37 | options.emfileWait = options.emfileWait || 1000
|
38 | if (options.glob === false) {
|
39 | options.disableGlob = true
|
40 | }
|
41 | if (options.disableGlob !== true && glob === undefined) {
|
42 | throw Error('glob dependency not found, set `options.disableGlob = true` if intentional')
|
43 | }
|
44 | options.disableGlob = options.disableGlob || false
|
45 | options.glob = options.glob || defaultGlobOpts
|
46 | }
|
47 |
|
48 | const rimraf = (p, options, cb) => {
|
49 | if (typeof options === 'function') {
|
50 | cb = options
|
51 | options = {}
|
52 | }
|
53 |
|
54 | assert(p, 'rimraf: missing path')
|
55 | assert.equal(typeof p, 'string', 'rimraf: path should be a string')
|
56 | assert.equal(typeof cb, 'function', 'rimraf: callback function required')
|
57 | assert(options, 'rimraf: invalid options argument provided')
|
58 | assert.equal(typeof options, 'object', 'rimraf: options should be object')
|
59 |
|
60 | defaults(options)
|
61 |
|
62 | let busyTries = 0
|
63 | let errState = null
|
64 | let n = 0
|
65 |
|
66 | const next = (er) => {
|
67 | errState = errState || er
|
68 | if (--n === 0)
|
69 | cb(errState)
|
70 | }
|
71 |
|
72 | const afterGlob = (er, results) => {
|
73 | if (er)
|
74 | return cb(er)
|
75 |
|
76 | n = results.length
|
77 | if (n === 0)
|
78 | return cb()
|
79 |
|
80 | results.forEach(p => {
|
81 | const CB = (er) => {
|
82 | if (er) {
|
83 | if ((er.code === "EBUSY" || er.code === "ENOTEMPTY" || er.code === "EPERM") &&
|
84 | busyTries < options.maxBusyTries) {
|
85 | busyTries ++
|
86 |
|
87 | return setTimeout(() => rimraf_(p, options, CB), busyTries * 100)
|
88 | }
|
89 |
|
90 |
|
91 | if (er.code === "EMFILE" && timeout < options.emfileWait) {
|
92 | return setTimeout(() => rimraf_(p, options, CB), timeout ++)
|
93 | }
|
94 |
|
95 |
|
96 | if (er.code === "ENOENT") er = null
|
97 | }
|
98 |
|
99 | timeout = 0
|
100 | next(er)
|
101 | }
|
102 | rimraf_(p, options, CB)
|
103 | })
|
104 | }
|
105 |
|
106 | if (options.disableGlob || !glob.hasMagic(p))
|
107 | return afterGlob(null, [p])
|
108 |
|
109 | options.lstat(p, (er, stat) => {
|
110 | if (!er)
|
111 | return afterGlob(null, [p])
|
112 |
|
113 | glob(p, options.glob, afterGlob)
|
114 | })
|
115 |
|
116 | }
|
117 |
|
118 |
|
119 |
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 | const rimraf_ = (p, options, cb) => {
|
130 | assert(p)
|
131 | assert(options)
|
132 | assert(typeof cb === 'function')
|
133 |
|
134 |
|
135 |
|
136 | options.lstat(p, (er, st) => {
|
137 | if (er && er.code === "ENOENT")
|
138 | return cb(null)
|
139 |
|
140 |
|
141 | if (er && er.code === "EPERM" && isWindows)
|
142 | fixWinEPERM(p, options, er, cb)
|
143 |
|
144 | if (st && st.isDirectory())
|
145 | return rmdir(p, options, er, cb)
|
146 |
|
147 | options.unlink(p, er => {
|
148 | if (er) {
|
149 | if (er.code === "ENOENT")
|
150 | return cb(null)
|
151 | if (er.code === "EPERM")
|
152 | return (isWindows)
|
153 | ? fixWinEPERM(p, options, er, cb)
|
154 | : rmdir(p, options, er, cb)
|
155 | if (er.code === "EISDIR")
|
156 | return rmdir(p, options, er, cb)
|
157 | }
|
158 | return cb(er)
|
159 | })
|
160 | })
|
161 | }
|
162 |
|
163 | const fixWinEPERM = (p, options, er, cb) => {
|
164 | assert(p)
|
165 | assert(options)
|
166 | assert(typeof cb === 'function')
|
167 |
|
168 | options.chmod(p, 0o666, er2 => {
|
169 | if (er2)
|
170 | cb(er2.code === "ENOENT" ? null : er)
|
171 | else
|
172 | options.stat(p, (er3, stats) => {
|
173 | if (er3)
|
174 | cb(er3.code === "ENOENT" ? null : er)
|
175 | else if (stats.isDirectory())
|
176 | rmdir(p, options, er, cb)
|
177 | else
|
178 | options.unlink(p, cb)
|
179 | })
|
180 | })
|
181 | }
|
182 |
|
183 | const fixWinEPERMSync = (p, options, er) => {
|
184 | assert(p)
|
185 | assert(options)
|
186 |
|
187 | try {
|
188 | options.chmodSync(p, 0o666)
|
189 | } catch (er2) {
|
190 | if (er2.code === "ENOENT")
|
191 | return
|
192 | else
|
193 | throw er
|
194 | }
|
195 |
|
196 | let stats
|
197 | try {
|
198 | stats = options.statSync(p)
|
199 | } catch (er3) {
|
200 | if (er3.code === "ENOENT")
|
201 | return
|
202 | else
|
203 | throw er
|
204 | }
|
205 |
|
206 | if (stats.isDirectory())
|
207 | rmdirSync(p, options, er)
|
208 | else
|
209 | options.unlinkSync(p)
|
210 | }
|
211 |
|
212 | const rmdir = (p, options, originalEr, cb) => {
|
213 | assert(p)
|
214 | assert(options)
|
215 | assert(typeof cb === 'function')
|
216 |
|
217 |
|
218 |
|
219 |
|
220 | options.rmdir(p, er => {
|
221 | if (er && (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM"))
|
222 | rmkids(p, options, cb)
|
223 | else if (er && er.code === "ENOTDIR")
|
224 | cb(originalEr)
|
225 | else
|
226 | cb(er)
|
227 | })
|
228 | }
|
229 |
|
230 | const rmkids = (p, options, cb) => {
|
231 | assert(p)
|
232 | assert(options)
|
233 | assert(typeof cb === 'function')
|
234 |
|
235 | options.readdir(p, (er, files) => {
|
236 | if (er)
|
237 | return cb(er)
|
238 | let n = files.length
|
239 | if (n === 0)
|
240 | return options.rmdir(p, cb)
|
241 | let errState
|
242 | files.forEach(f => {
|
243 | rimraf(path.join(p, f), options, er => {
|
244 | if (errState)
|
245 | return
|
246 | if (er)
|
247 | return cb(errState = er)
|
248 | if (--n === 0)
|
249 | options.rmdir(p, cb)
|
250 | })
|
251 | })
|
252 | })
|
253 | }
|
254 |
|
255 |
|
256 |
|
257 |
|
258 | const rimrafSync = (p, options) => {
|
259 | options = options || {}
|
260 | defaults(options)
|
261 |
|
262 | assert(p, 'rimraf: missing path')
|
263 | assert.equal(typeof p, 'string', 'rimraf: path should be a string')
|
264 | assert(options, 'rimraf: missing options')
|
265 | assert.equal(typeof options, 'object', 'rimraf: options should be object')
|
266 |
|
267 | let results
|
268 |
|
269 | if (options.disableGlob || !glob.hasMagic(p)) {
|
270 | results = [p]
|
271 | } else {
|
272 | try {
|
273 | options.lstatSync(p)
|
274 | results = [p]
|
275 | } catch (er) {
|
276 | results = glob.sync(p, options.glob)
|
277 | }
|
278 | }
|
279 |
|
280 | if (!results.length)
|
281 | return
|
282 |
|
283 | for (let i = 0; i < results.length; i++) {
|
284 | const p = results[i]
|
285 |
|
286 | let st
|
287 | try {
|
288 | st = options.lstatSync(p)
|
289 | } catch (er) {
|
290 | if (er.code === "ENOENT")
|
291 | return
|
292 |
|
293 |
|
294 | if (er.code === "EPERM" && isWindows)
|
295 | fixWinEPERMSync(p, options, er)
|
296 | }
|
297 |
|
298 | try {
|
299 |
|
300 | if (st && st.isDirectory())
|
301 | rmdirSync(p, options, null)
|
302 | else
|
303 | options.unlinkSync(p)
|
304 | } catch (er) {
|
305 | if (er.code === "ENOENT")
|
306 | return
|
307 | if (er.code === "EPERM")
|
308 | return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er)
|
309 | if (er.code !== "EISDIR")
|
310 | throw er
|
311 |
|
312 | rmdirSync(p, options, er)
|
313 | }
|
314 | }
|
315 | }
|
316 |
|
317 | const rmdirSync = (p, options, originalEr) => {
|
318 | assert(p)
|
319 | assert(options)
|
320 |
|
321 | try {
|
322 | options.rmdirSync(p)
|
323 | } catch (er) {
|
324 | if (er.code === "ENOENT")
|
325 | return
|
326 | if (er.code === "ENOTDIR")
|
327 | throw originalEr
|
328 | if (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM")
|
329 | rmkidsSync(p, options)
|
330 | }
|
331 | }
|
332 |
|
333 | const rmkidsSync = (p, options) => {
|
334 | assert(p)
|
335 | assert(options)
|
336 | options.readdirSync(p).forEach(f => rimrafSync(path.join(p, f), options))
|
337 |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 |
|
343 |
|
344 | const retries = isWindows ? 100 : 1
|
345 | let i = 0
|
346 | do {
|
347 | let threw = true
|
348 | try {
|
349 | const ret = options.rmdirSync(p, options)
|
350 | threw = false
|
351 | return ret
|
352 | } finally {
|
353 | if (++i < retries && threw)
|
354 | continue
|
355 | }
|
356 | } while (true)
|
357 | }
|
358 |
|
359 | module.exports = rimraf
|
360 | rimraf.sync = rimrafSync
|