Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/@npmcli/fs/lib/rm/polyfill.js')
-rw-r--r--node_modules/@npmcli/fs/lib/rm/polyfill.js238
1 files changed, 238 insertions, 0 deletions
diff --git a/node_modules/@npmcli/fs/lib/rm/polyfill.js b/node_modules/@npmcli/fs/lib/rm/polyfill.js
new file mode 100644
index 000000000..77196b76b
--- /dev/null
+++ b/node_modules/@npmcli/fs/lib/rm/polyfill.js
@@ -0,0 +1,238 @@
+// 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