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