$
This commit is contained in:
22
node_modules/@npmcli/fs/lib/rm/index.js
generated
vendored
Normal file
22
node_modules/@npmcli/fs/lib/rm/index.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
const fs = require('../fs.js')
|
||||
const getOptions = require('../common/get-options.js')
|
||||
const node = require('../common/node.js')
|
||||
const polyfill = require('./polyfill.js')
|
||||
|
||||
// node 14.14.0 added fs.rm, which allows both the force and recursive options
|
||||
const useNative = node.satisfies('>=14.14.0')
|
||||
|
||||
const rm = async (path, opts) => {
|
||||
const options = getOptions(opts, {
|
||||
copy: ['retryDelay', 'maxRetries', 'recursive', 'force'],
|
||||
})
|
||||
|
||||
// the polyfill is tested separately from this module, no need to hack
|
||||
// process.version to try to trigger it just for coverage
|
||||
// istanbul ignore next
|
||||
return useNative
|
||||
? fs.rm(path, options)
|
||||
: polyfill(path, options)
|
||||
}
|
||||
|
||||
module.exports = rm
|
239
node_modules/@npmcli/fs/lib/rm/polyfill.js
generated
vendored
Normal file
239
node_modules/@npmcli/fs/lib/rm/polyfill.js
generated
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
// this file is a modified version of the code in node core >=14.14.0
|
||||
// which is, in turn, a modified version of the rimraf module on npm
|
||||
// node core changes:
|
||||
// - Use of the assert module has been replaced with core's error system.
|
||||
// - All code related to the glob dependency has been removed.
|
||||
// - Bring your own custom fs module is not currently supported.
|
||||
// - Some basic code cleanup.
|
||||
// changes here:
|
||||
// - remove all callback related code
|
||||
// - drop sync support
|
||||
// - change assertions back to non-internal methods (see options.js)
|
||||
// - throws ENOTDIR when rmdir gets an ENOENT for a path that exists in Windows
|
||||
const errnos = require('os').constants.errno
|
||||
const { join } = require('path')
|
||||
const fs = require('../fs.js')
|
||||
|
||||
// error codes that mean we need to remove contents
|
||||
const notEmptyCodes = new Set([
|
||||
'ENOTEMPTY',
|
||||
'EEXIST',
|
||||
'EPERM',
|
||||
])
|
||||
|
||||
// error codes we can retry later
|
||||
const retryCodes = new Set([
|
||||
'EBUSY',
|
||||
'EMFILE',
|
||||
'ENFILE',
|
||||
'ENOTEMPTY',
|
||||
'EPERM',
|
||||
])
|
||||
|
||||
const isWindows = process.platform === 'win32'
|
||||
|
||||
const defaultOptions = {
|
||||
retryDelay: 100,
|
||||
maxRetries: 0,
|
||||
recursive: false,
|
||||
force: false,
|
||||
}
|
||||
|
||||
// this is drastically simplified, but should be roughly equivalent to what
|
||||
// node core throws
|
||||
class ERR_FS_EISDIR extends Error {
|
||||
constructor (path) {
|
||||
super()
|
||||
this.info = {
|
||||
code: 'EISDIR',
|
||||
message: 'is a directory',
|
||||
path,
|
||||
syscall: 'rm',
|
||||
errno: errnos.EISDIR,
|
||||
}
|
||||
this.name = 'SystemError'
|
||||
this.code = 'ERR_FS_EISDIR'
|
||||
this.errno = errnos.EISDIR
|
||||
this.syscall = 'rm'
|
||||
this.path = path
|
||||
this.message = `Path is a directory: ${this.syscall} returned ` +
|
||||
`${this.info.code} (is a directory) ${path}`
|
||||
}
|
||||
|
||||
toString () {
|
||||
return `${this.name} [${this.code}]: ${this.message}`
|
||||
}
|
||||
}
|
||||
|
||||
class ENOTDIR extends Error {
|
||||
constructor (path) {
|
||||
super()
|
||||
this.name = 'Error'
|
||||
this.code = 'ENOTDIR'
|
||||
this.errno = errnos.ENOTDIR
|
||||
this.syscall = 'rmdir'
|
||||
this.path = path
|
||||
this.message = `not a directory, ${this.syscall} '${this.path}'`
|
||||
}
|
||||
|
||||
toString () {
|
||||
return `${this.name}: ${this.code}: ${this.message}`
|
||||
}
|
||||
}
|
||||
|
||||
// force is passed separately here because we respect it for the first entry
|
||||
// into rimraf only, any further calls that are spawned as a result (i.e. to
|
||||
// delete content within the target) will ignore ENOENT errors
|
||||
const rimraf = async (path, options, isTop = false) => {
|
||||
const force = isTop ? options.force : true
|
||||
const stat = await fs.lstat(path)
|
||||
.catch((err) => {
|
||||
// we only ignore ENOENT if we're forcing this call
|
||||
if (err.code === 'ENOENT' && force) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isWindows && err.code === 'EPERM') {
|
||||
return fixEPERM(path, options, err, isTop)
|
||||
}
|
||||
|
||||
throw err
|
||||
})
|
||||
|
||||
// no stat object here means either lstat threw an ENOENT, or lstat threw
|
||||
// an EPERM and the fixPERM function took care of things. either way, we're
|
||||
// already done, so return early
|
||||
if (!stat) {
|
||||
return
|
||||
}
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
return rmdir(path, options, null, isTop)
|
||||
}
|
||||
|
||||
return fs.unlink(path)
|
||||
.catch((err) => {
|
||||
if (err.code === 'ENOENT' && force) {
|
||||
return
|
||||
}
|
||||
|
||||
if (err.code === 'EISDIR') {
|
||||
return rmdir(path, options, err, isTop)
|
||||
}
|
||||
|
||||
if (err.code === 'EPERM') {
|
||||
// in windows, we handle this through fixEPERM which will also try to
|
||||
// delete things again. everywhere else since deleting the target as a
|
||||
// file didn't work we go ahead and try to delete it as a directory
|
||||
return isWindows
|
||||
? fixEPERM(path, options, err, isTop)
|
||||
: rmdir(path, options, err, isTop)
|
||||
}
|
||||
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
const fixEPERM = async (path, options, originalErr, isTop) => {
|
||||
const force = isTop ? options.force : true
|
||||
const targetMissing = await fs.chmod(path, 0o666)
|
||||
.catch((err) => {
|
||||
if (err.code === 'ENOENT' && force) {
|
||||
return true
|
||||
}
|
||||
|
||||
throw originalErr
|
||||
})
|
||||
|
||||
// got an ENOENT above, return now. no file = no problem
|
||||
if (targetMissing) {
|
||||
return
|
||||
}
|
||||
|
||||
// this function does its own lstat rather than calling rimraf again to avoid
|
||||
// infinite recursion for a repeating EPERM
|
||||
const stat = await fs.lstat(path)
|
||||
.catch((err) => {
|
||||
if (err.code === 'ENOENT' && force) {
|
||||
return
|
||||
}
|
||||
|
||||
throw originalErr
|
||||
})
|
||||
|
||||
if (!stat) {
|
||||
return
|
||||
}
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
return rmdir(path, options, originalErr, isTop)
|
||||
}
|
||||
|
||||
return fs.unlink(path)
|
||||
}
|
||||
|
||||
const rmdir = async (path, options, originalErr, isTop) => {
|
||||
if (!options.recursive && isTop) {
|
||||
throw originalErr || new ERR_FS_EISDIR(path)
|
||||
}
|
||||
const force = isTop ? options.force : true
|
||||
|
||||
return fs.rmdir(path)
|
||||
.catch(async (err) => {
|
||||
// in Windows, calling rmdir on a file path will fail with ENOENT rather
|
||||
// than ENOTDIR. to determine if that's what happened, we have to do
|
||||
// another lstat on the path. if the path isn't actually gone, we throw
|
||||
// away the ENOENT and replace it with our own ENOTDIR
|
||||
if (isWindows && err.code === 'ENOENT') {
|
||||
const stillExists = await fs.lstat(path).then(() => true, () => false)
|
||||
if (stillExists) {
|
||||
err = new ENOTDIR(path)
|
||||
}
|
||||
}
|
||||
|
||||
// not there, not a problem
|
||||
if (err.code === 'ENOENT' && force) {
|
||||
return
|
||||
}
|
||||
|
||||
// we may not have originalErr if lstat tells us our target is a
|
||||
// directory but that changes before we actually remove it, so
|
||||
// only throw it here if it's set
|
||||
if (originalErr && err.code === 'ENOTDIR') {
|
||||
throw originalErr
|
||||
}
|
||||
|
||||
// the directory isn't empty, remove the contents and try again
|
||||
if (notEmptyCodes.has(err.code)) {
|
||||
const files = await fs.readdir(path)
|
||||
await Promise.all(files.map((file) => {
|
||||
const target = join(path, file)
|
||||
return rimraf(target, options)
|
||||
}))
|
||||
return fs.rmdir(path)
|
||||
}
|
||||
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
const rm = async (path, opts) => {
|
||||
const options = { ...defaultOptions, ...opts }
|
||||
let retries = 0
|
||||
|
||||
const errHandler = async (err) => {
|
||||
if (retryCodes.has(err.code) && ++retries < options.maxRetries) {
|
||||
const delay = retries * options.retryDelay
|
||||
await promiseTimeout(delay)
|
||||
return rimraf(path, options, true).catch(errHandler)
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
|
||||
return rimraf(path, options, true).catch(errHandler)
|
||||
}
|
||||
|
||||
const promiseTimeout = (ms) => new Promise((r) => setTimeout(r, ms))
|
||||
|
||||
module.exports = rm
|
Reference in New Issue
Block a user