You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					315 lines
				
				7.5 KiB
			
		
		
			
		
	
	
					315 lines
				
				7.5 KiB
			| 
								 
											3 years ago
										 
									 | 
							
								'use strict'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const fs = require('graceful-fs')
							 | 
						||
| 
								 | 
							
								const path = require('path')
							 | 
						||
| 
								 | 
							
								const assert = require('assert')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const isWindows = (process.platform === 'win32')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function defaults (options) {
							 | 
						||
| 
								 | 
							
								  const methods = [
							 | 
						||
| 
								 | 
							
								    'unlink',
							 | 
						||
| 
								 | 
							
								    'chmod',
							 | 
						||
| 
								 | 
							
								    'stat',
							 | 
						||
| 
								 | 
							
								    'lstat',
							 | 
						||
| 
								 | 
							
								    'rmdir',
							 | 
						||
| 
								 | 
							
								    'readdir'
							 | 
						||
| 
								 | 
							
								  ]
							 | 
						||
| 
								 | 
							
								  methods.forEach(m => {
							 | 
						||
| 
								 | 
							
								    options[m] = options[m] || fs[m]
							 | 
						||
| 
								 | 
							
								    m = m + 'Sync'
							 | 
						||
| 
								 | 
							
								    options[m] = options[m] || fs[m]
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  options.maxBusyTries = options.maxBusyTries || 3
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function rimraf (p, options, cb) {
							 | 
						||
| 
								 | 
							
								  let busyTries = 0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (typeof options === 'function') {
							 | 
						||
| 
								 | 
							
								    cb = options
							 | 
						||
| 
								 | 
							
								    options = {}
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  assert(p, 'rimraf: missing path')
							 | 
						||
| 
								 | 
							
								  assert.strictEqual(typeof p, 'string', 'rimraf: path should be a string')
							 | 
						||
| 
								 | 
							
								  assert.strictEqual(typeof cb, 'function', 'rimraf: callback function required')
							 | 
						||
| 
								 | 
							
								  assert(options, 'rimraf: invalid options argument provided')
							 | 
						||
| 
								 | 
							
								  assert.strictEqual(typeof options, 'object', 'rimraf: options should be object')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  defaults(options)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  rimraf_(p, options, function CB (er) {
							 | 
						||
| 
								 | 
							
								    if (er) {
							 | 
						||
| 
								 | 
							
								      if ((er.code === 'EBUSY' || er.code === 'ENOTEMPTY' || er.code === 'EPERM') &&
							 | 
						||
| 
								 | 
							
								          busyTries < options.maxBusyTries) {
							 | 
						||
| 
								 | 
							
								        busyTries++
							 | 
						||
| 
								 | 
							
								        const time = busyTries * 100
							 | 
						||
| 
								 | 
							
								        // try again, with the same exact callback as this one.
							 | 
						||
| 
								 | 
							
								        return setTimeout(() => rimraf_(p, options, CB), time)
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      // already gone
							 | 
						||
| 
								 | 
							
								      if (er.code === 'ENOENT') er = null
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    cb(er)
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Two possible strategies.
							 | 
						||
| 
								 | 
							
								// 1. Assume it's a file.  unlink it, then do the dir stuff on EPERM or EISDIR
							 | 
						||
| 
								 | 
							
								// 2. Assume it's a directory.  readdir, then do the file stuff on ENOTDIR
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// Both result in an extra syscall when you guess wrong.  However, there
							 | 
						||
| 
								 | 
							
								// are likely far more normal files in the world than directories.  This
							 | 
						||
| 
								 | 
							
								// is based on the assumption that a the average number of files per
							 | 
						||
| 
								 | 
							
								// directory is >= 1.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// If anyone ever complains about this, then I guess the strategy could
							 | 
						||
| 
								 | 
							
								// be made configurable somehow.  But until then, YAGNI.
							 | 
						||
| 
								 | 
							
								function rimraf_ (p, options, cb) {
							 | 
						||
| 
								 | 
							
								  assert(p)
							 | 
						||
| 
								 | 
							
								  assert(options)
							 | 
						||
| 
								 | 
							
								  assert(typeof cb === 'function')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // sunos lets the root user unlink directories, which is... weird.
							 | 
						||
| 
								 | 
							
								  // so we have to lstat here and make sure it's not a dir.
							 | 
						||
| 
								 | 
							
								  options.lstat(p, (er, st) => {
							 | 
						||
| 
								 | 
							
								    if (er && er.code === 'ENOENT') {
							 | 
						||
| 
								 | 
							
								      return cb(null)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Windows can EPERM on stat.  Life is suffering.
							 | 
						||
| 
								 | 
							
								    if (er && er.code === 'EPERM' && isWindows) {
							 | 
						||
| 
								 | 
							
								      return fixWinEPERM(p, options, er, cb)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (st && st.isDirectory()) {
							 | 
						||
| 
								 | 
							
								      return rmdir(p, options, er, cb)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    options.unlink(p, er => {
							 | 
						||
| 
								 | 
							
								      if (er) {
							 | 
						||
| 
								 | 
							
								        if (er.code === 'ENOENT') {
							 | 
						||
| 
								 | 
							
								          return cb(null)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if (er.code === 'EPERM') {
							 | 
						||
| 
								 | 
							
								          return (isWindows)
							 | 
						||
| 
								 | 
							
								            ? fixWinEPERM(p, options, er, cb)
							 | 
						||
| 
								 | 
							
								            : rmdir(p, options, er, cb)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if (er.code === 'EISDIR') {
							 | 
						||
| 
								 | 
							
								          return rmdir(p, options, er, cb)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      return cb(er)
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function fixWinEPERM (p, options, er, cb) {
							 | 
						||
| 
								 | 
							
								  assert(p)
							 | 
						||
| 
								 | 
							
								  assert(options)
							 | 
						||
| 
								 | 
							
								  assert(typeof cb === 'function')
							 | 
						||
| 
								 | 
							
								  if (er) {
							 | 
						||
| 
								 | 
							
								    assert(er instanceof Error)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  options.chmod(p, 0o666, er2 => {
							 | 
						||
| 
								 | 
							
								    if (er2) {
							 | 
						||
| 
								 | 
							
								      cb(er2.code === 'ENOENT' ? null : er)
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      options.stat(p, (er3, stats) => {
							 | 
						||
| 
								 | 
							
								        if (er3) {
							 | 
						||
| 
								 | 
							
								          cb(er3.code === 'ENOENT' ? null : er)
							 | 
						||
| 
								 | 
							
								        } else if (stats.isDirectory()) {
							 | 
						||
| 
								 | 
							
								          rmdir(p, options, er, cb)
							 | 
						||
| 
								 | 
							
								        } else {
							 | 
						||
| 
								 | 
							
								          options.unlink(p, cb)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      })
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function fixWinEPERMSync (p, options, er) {
							 | 
						||
| 
								 | 
							
								  let stats
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  assert(p)
							 | 
						||
| 
								 | 
							
								  assert(options)
							 | 
						||
| 
								 | 
							
								  if (er) {
							 | 
						||
| 
								 | 
							
								    assert(er instanceof Error)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    options.chmodSync(p, 0o666)
							 | 
						||
| 
								 | 
							
								  } catch (er2) {
							 | 
						||
| 
								 | 
							
								    if (er2.code === 'ENOENT') {
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      throw er
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    stats = options.statSync(p)
							 | 
						||
| 
								 | 
							
								  } catch (er3) {
							 | 
						||
| 
								 | 
							
								    if (er3.code === 'ENOENT') {
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      throw er
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (stats.isDirectory()) {
							 | 
						||
| 
								 | 
							
								    rmdirSync(p, options, er)
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    options.unlinkSync(p)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function rmdir (p, options, originalEr, cb) {
							 | 
						||
| 
								 | 
							
								  assert(p)
							 | 
						||
| 
								 | 
							
								  assert(options)
							 | 
						||
| 
								 | 
							
								  if (originalEr) {
							 | 
						||
| 
								 | 
							
								    assert(originalEr instanceof Error)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								  assert(typeof cb === 'function')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS)
							 | 
						||
| 
								 | 
							
								  // if we guessed wrong, and it's not a directory, then
							 | 
						||
| 
								 | 
							
								  // raise the original error.
							 | 
						||
| 
								 | 
							
								  options.rmdir(p, er => {
							 | 
						||
| 
								 | 
							
								    if (er && (er.code === 'ENOTEMPTY' || er.code === 'EEXIST' || er.code === 'EPERM')) {
							 | 
						||
| 
								 | 
							
								      rmkids(p, options, cb)
							 | 
						||
| 
								 | 
							
								    } else if (er && er.code === 'ENOTDIR') {
							 | 
						||
| 
								 | 
							
								      cb(originalEr)
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      cb(er)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function rmkids (p, options, cb) {
							 | 
						||
| 
								 | 
							
								  assert(p)
							 | 
						||
| 
								 | 
							
								  assert(options)
							 | 
						||
| 
								 | 
							
								  assert(typeof cb === 'function')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  options.readdir(p, (er, files) => {
							 | 
						||
| 
								 | 
							
								    if (er) return cb(er)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    let n = files.length
							 | 
						||
| 
								 | 
							
								    let errState
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if (n === 0) return options.rmdir(p, cb)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    files.forEach(f => {
							 | 
						||
| 
								 | 
							
								      rimraf(path.join(p, f), options, er => {
							 | 
						||
| 
								 | 
							
								        if (errState) {
							 | 
						||
| 
								 | 
							
								          return
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								        if (er) return cb(errState = er)
							 | 
						||
| 
								 | 
							
								        if (--n === 0) {
							 | 
						||
| 
								 | 
							
								          options.rmdir(p, cb)
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      })
							 | 
						||
| 
								 | 
							
								    })
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// this looks simpler, and is strictly *faster*, but will
							 | 
						||
| 
								 | 
							
								// tie up the JavaScript thread and fail on excessively
							 | 
						||
| 
								 | 
							
								// deep directory trees.
							 | 
						||
| 
								 | 
							
								function rimrafSync (p, options) {
							 | 
						||
| 
								 | 
							
								  let st
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  options = options || {}
							 | 
						||
| 
								 | 
							
								  defaults(options)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  assert(p, 'rimraf: missing path')
							 | 
						||
| 
								 | 
							
								  assert.strictEqual(typeof p, 'string', 'rimraf: path should be a string')
							 | 
						||
| 
								 | 
							
								  assert(options, 'rimraf: missing options')
							 | 
						||
| 
								 | 
							
								  assert.strictEqual(typeof options, 'object', 'rimraf: options should be object')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    st = options.lstatSync(p)
							 | 
						||
| 
								 | 
							
								  } catch (er) {
							 | 
						||
| 
								 | 
							
								    if (er.code === 'ENOENT') {
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    // Windows can EPERM on stat.  Life is suffering.
							 | 
						||
| 
								 | 
							
								    if (er.code === 'EPERM' && isWindows) {
							 | 
						||
| 
								 | 
							
								      fixWinEPERMSync(p, options, er)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    // sunos lets the root user unlink directories, which is... weird.
							 | 
						||
| 
								 | 
							
								    if (st && st.isDirectory()) {
							 | 
						||
| 
								 | 
							
								      rmdirSync(p, options, null)
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      options.unlinkSync(p)
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  } catch (er) {
							 | 
						||
| 
								 | 
							
								    if (er.code === 'ENOENT') {
							 | 
						||
| 
								 | 
							
								      return
							 | 
						||
| 
								 | 
							
								    } else if (er.code === 'EPERM') {
							 | 
						||
| 
								 | 
							
								      return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er)
							 | 
						||
| 
								 | 
							
								    } else if (er.code !== 'EISDIR') {
							 | 
						||
| 
								 | 
							
								      throw er
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								    rmdirSync(p, options, er)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function rmdirSync (p, options, originalEr) {
							 | 
						||
| 
								 | 
							
								  assert(p)
							 | 
						||
| 
								 | 
							
								  assert(options)
							 | 
						||
| 
								 | 
							
								  if (originalEr) {
							 | 
						||
| 
								 | 
							
								    assert(originalEr instanceof Error)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  try {
							 | 
						||
| 
								 | 
							
								    options.rmdirSync(p)
							 | 
						||
| 
								 | 
							
								  } catch (er) {
							 | 
						||
| 
								 | 
							
								    if (er.code === 'ENOTDIR') {
							 | 
						||
| 
								 | 
							
								      throw originalEr
							 | 
						||
| 
								 | 
							
								    } else if (er.code === 'ENOTEMPTY' || er.code === 'EEXIST' || er.code === 'EPERM') {
							 | 
						||
| 
								 | 
							
								      rmkidsSync(p, options)
							 | 
						||
| 
								 | 
							
								    } else if (er.code !== 'ENOENT') {
							 | 
						||
| 
								 | 
							
								      throw er
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function rmkidsSync (p, options) {
							 | 
						||
| 
								 | 
							
								  assert(p)
							 | 
						||
| 
								 | 
							
								  assert(options)
							 | 
						||
| 
								 | 
							
								  options.readdirSync(p).forEach(f => rimrafSync(path.join(p, f), options))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if (isWindows) {
							 | 
						||
| 
								 | 
							
								    // We only end up here once we got ENOTEMPTY at least once, and
							 | 
						||
| 
								 | 
							
								    // at this point, we are guaranteed to have removed all the kids.
							 | 
						||
| 
								 | 
							
								    // So, we know that it won't be ENOENT or ENOTDIR or anything else.
							 | 
						||
| 
								 | 
							
								    // try really hard to delete stuff on windows, because it has a
							 | 
						||
| 
								 | 
							
								    // PROFOUNDLY annoying habit of not closing handles promptly when
							 | 
						||
| 
								 | 
							
								    // files are deleted, resulting in spurious ENOTEMPTY errors.
							 | 
						||
| 
								 | 
							
								    const startTime = Date.now()
							 | 
						||
| 
								 | 
							
								    do {
							 | 
						||
| 
								 | 
							
								      try {
							 | 
						||
| 
								 | 
							
								        const ret = options.rmdirSync(p, options)
							 | 
						||
| 
								 | 
							
								        return ret
							 | 
						||
| 
								 | 
							
								      } catch (er) { }
							 | 
						||
| 
								 | 
							
								    } while (Date.now() - startTime < 500) // give up after 500ms
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    const ret = options.rmdirSync(p, options)
							 | 
						||
| 
								 | 
							
								    return ret
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = rimraf
							 | 
						||
| 
								 | 
							
								rimraf.sync = rimrafSync
							 |