UNPKG

6.38 kBJavaScriptView Raw
1/* This is an adaption of https://github.com/isaacs/rimraf */
2
3'use strict'
4
5const path = require('path')
6const assert = require('assert');
7const fs = require('fs');
8
9const isWindows = (process.platform === 'win32')
10
11function defaults (options) {
12 const methods = [
13 'unlink',
14 'chmod',
15 'stat',
16 'lstat',
17 'rmdir',
18 'readdir'
19 ]
20 methods.forEach(m => {
21 options[m] = options[m] || fs[m]
22 m = m + 'Sync'
23 options[m] = options[m] || fs[m]
24 })
25
26 options.maxBusyTries = options.maxBusyTries || 3
27}
28
29function rimraf (p, options, cb) {
30 let busyTries = 0
31
32 if (typeof options === 'function') {
33 cb = options
34 options = {}
35 }
36
37 assert(p, 'rimraf: missing path')
38 assert.equal(typeof p, 'string', 'rimraf: path should be a string')
39 assert.equal(typeof cb, 'function', 'rimraf: callback function required')
40 assert(options, 'rimraf: invalid options argument provided')
41 assert.equal(typeof options, 'object', 'rimraf: options should be object')
42
43 defaults(options)
44
45 rimraf_(p, options, function CB (er) {
46 if (er) {
47 if (isWindows && (er.code === 'EBUSY' || er.code === 'ENOTEMPTY' || er.code === 'EPERM') &&
48 busyTries < options.maxBusyTries) {
49 busyTries++
50 let time = busyTries * 100
51 // try again, with the same exact callback as this one.
52 return setTimeout(() => rimraf_(p, options, CB), time)
53 }
54
55 // already gone
56 if (er.code === 'ENOENT') er = null
57 }
58
59 cb(er)
60 })
61}
62
63// Two possible strategies.
64// 1. Assume it's a file. unlink it, then do the dir stuff on EPERM or EISDIR
65// 2. Assume it's a directory. readdir, then do the file stuff on ENOTDIR
66//
67// Both result in an extra syscall when you guess wrong. However, there
68// are likely far more normal files in the world than directories. This
69// is based on the assumption that a the average number of files per
70// directory is >= 1.
71//
72// If anyone ever complains about this, then I guess the strategy could
73// be made configurable somehow. But until then, YAGNI.
74function rimraf_ (p, options, cb) {
75 assert(p)
76 assert(options)
77 assert(typeof cb === 'function')
78
79 // sunos lets the root user unlink directories, which is... weird.
80 // so we have to lstat here and make sure it's not a dir.
81 options.lstat(p, (er, st) => {
82 if (er && er.code === 'ENOENT') {
83 return cb(null)
84 }
85
86 // Windows can EPERM on stat. Life is suffering.
87 if (er && er.code === 'EPERM' && isWindows) {
88 return fixWinEPERM(p, options, er, cb)
89 }
90
91 if (st && st.isDirectory()) {
92 return rmdir(p, options, er, cb)
93 }
94
95 options.unlink(p, er => {
96 if (er) {
97 if (er.code === 'ENOENT') {
98 return cb(null)
99 }
100 if (er.code === 'EPERM') {
101 return (isWindows)
102 ? fixWinEPERM(p, options, er, cb)
103 : rmdir(p, options, er, cb)
104 }
105 if (er.code === 'EISDIR') {
106 return rmdir(p, options, er, cb)
107 }
108 }
109 return cb(er)
110 })
111 })
112}
113
114function fixWinEPERM (p, options, er, cb) {
115 assert(p)
116 assert(options)
117 assert(typeof cb === 'function')
118 if (er) {
119 assert(er instanceof Error)
120 }
121
122 options.chmod(p, 666, er2 => {
123 if (er2) {
124 cb(er2.code === 'ENOENT' ? null : er)
125 } else {
126 options.stat(p, (er3, stats) => {
127 if (er3) {
128 cb(er3.code === 'ENOENT' ? null : er)
129 } else if (stats.isDirectory()) {
130 rmdir(p, options, er, cb)
131 } else {
132 options.unlink(p, cb)
133 }
134 })
135 }
136 })
137}
138
139function fixWinEPERMSync (p, options, er) {
140 let stats
141
142 assert(p)
143 assert(options)
144 if (er) {
145 assert(er instanceof Error)
146 }
147
148 try {
149 options.chmodSync(p, 666)
150 } catch (er2) {
151 if (er2.code === 'ENOENT') {
152 return
153 } else {
154 throw er
155 }
156 }
157
158 try {
159 stats = options.statSync(p)
160 } catch (er3) {
161 if (er3.code === 'ENOENT') {
162 return
163 } else {
164 throw er
165 }
166 }
167
168 if (stats.isDirectory()) {
169 rmdirSync(p, options, er)
170 } else {
171 options.unlinkSync(p)
172 }
173}
174
175function rmdir (p, options, originalEr, cb) {
176 assert(p)
177 assert(options)
178 if (originalEr) {
179 assert(originalEr instanceof Error)
180 }
181 assert(typeof cb === 'function')
182
183 // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS)
184 // if we guessed wrong, and it's not a directory, then
185 // raise the original error.
186 options.rmdir(p, er => {
187 if (er && (er.code === 'ENOTEMPTY' || er.code === 'EEXIST' || er.code === 'EPERM')) {
188 rmkids(p, options, cb)
189 } else if (er && er.code === 'ENOTDIR') {
190 cb(originalEr)
191 } else {
192 cb(er)
193 }
194 })
195}
196
197function rmkids (p, options, cb) {
198 assert(p)
199 assert(options)
200 assert(typeof cb === 'function')
201
202 options.readdir(p, (er, files) => {
203 if (er) return cb(er)
204
205 let n = files.length
206 let errState
207
208 if (n === 0) return options.rmdir(p, cb)
209
210 files.forEach(f => {
211 rimraf(path.join(p, f), options, er => {
212 if (errState) {
213 return
214 }
215 if (er) return cb(errState = er)
216 if (--n === 0) {
217 options.rmdir(p, cb)
218 }
219 })
220 })
221 })
222}
223
224// this looks simpler, and is strictly *faster*, but will
225// tie up the JavaScript thread and fail on excessively
226// deep directory trees.
227function rimrafSync (p, options) {
228 let st
229
230 options = options || {}
231 defaults(options)
232
233 assert(p, 'rimraf: missing path')
234 assert.equal(typeof p, 'string', 'rimraf: path should be a string')
235 assert(options, 'rimraf: missing options')
236 assert.equal(typeof options, 'object', 'rimraf: options should be object')
237
238 try {
239 st = options.lstatSync(p)
240 } catch (er) {
241 if (er.code === 'ENOENT') {
242 return
243 }
244
245 // Windows can EPERM on stat. Life is suffering.
246 if (er.code === 'EPERM' && isWindows) {
247 fixWinEPERMSync(p, options, er)
248 }
249 }
250
251 try {
252 // sunos lets the root user unlink directories, which is... weird.
253 if (st && st.isDirectory()) {
254 rmdirSync(p, options, null)
255 } else {
256 options.unlinkSync(p)
257 }
258 } catch (er) {
259 if (er.code === 'ENOENT') {
260 return
261 } else if (er.code === 'EPERM') {
262 return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er)
263 } else if (er.code !== 'EISDIR') {
264 throw er
265 }
266 rmdirSync(p, options, er)
267 }
268}
269
270exports.rimraf = rimraf