source: node_modules/@npmcli/fs/lib/rm/polyfill.js@ 81bc7da

finki-main main
Last change on this file since 81bc7da was 81bc7da, checked in by Klimentina Efremova <klimentina08642@…>, 3 months ago

Initial commit

  • Property mode set to 100644
File size: 6.4 KB
Line 
1// this file is a modified version of the code in node core >=14.14.0
2// which is, in turn, a modified version of the rimraf module on npm
3// node core changes:
4// - Use of the assert module has been replaced with core's error system.
5// - All code related to the glob dependency has been removed.
6// - Bring your own custom fs module is not currently supported.
7// - Some basic code cleanup.
8// changes here:
9// - remove all callback related code
10// - drop sync support
11// - change assertions back to non-internal methods (see options.js)
12// - throws ENOTDIR when rmdir gets an ENOENT for a path that exists in Windows
13const errnos = require('os').constants.errno
14const { join } = require('path')
15const fs = require('../fs.js')
16
17// error codes that mean we need to remove contents
18const notEmptyCodes = new Set([
19 'ENOTEMPTY',
20 'EEXIST',
21 'EPERM',
22])
23
24// error codes we can retry later
25const retryCodes = new Set([
26 'EBUSY',
27 'EMFILE',
28 'ENFILE',
29 'ENOTEMPTY',
30 'EPERM',
31])
32
33const isWindows = process.platform === 'win32'
34
35const defaultOptions = {
36 retryDelay: 100,
37 maxRetries: 0,
38 recursive: false,
39 force: false,
40}
41
42// this is drastically simplified, but should be roughly equivalent to what
43// node core throws
44class ERR_FS_EISDIR extends Error {
45 constructor (path) {
46 super()
47 this.info = {
48 code: 'EISDIR',
49 message: 'is a directory',
50 path,
51 syscall: 'rm',
52 errno: errnos.EISDIR,
53 }
54 this.name = 'SystemError'
55 this.code = 'ERR_FS_EISDIR'
56 this.errno = errnos.EISDIR
57 this.syscall = 'rm'
58 this.path = path
59 this.message = `Path is a directory: ${this.syscall} returned ` +
60 `${this.info.code} (is a directory) ${path}`
61 }
62
63 toString () {
64 return `${this.name} [${this.code}]: ${this.message}`
65 }
66}
67
68class ENOTDIR extends Error {
69 constructor (path) {
70 super()
71 this.name = 'Error'
72 this.code = 'ENOTDIR'
73 this.errno = errnos.ENOTDIR
74 this.syscall = 'rmdir'
75 this.path = path
76 this.message = `not a directory, ${this.syscall} '${this.path}'`
77 }
78
79 toString () {
80 return `${this.name}: ${this.code}: ${this.message}`
81 }
82}
83
84// force is passed separately here because we respect it for the first entry
85// into rimraf only, any further calls that are spawned as a result (i.e. to
86// delete content within the target) will ignore ENOENT errors
87const rimraf = async (path, options, isTop = false) => {
88 const force = isTop ? options.force : true
89 const stat = await fs.lstat(path)
90 .catch((err) => {
91 // we only ignore ENOENT if we're forcing this call
92 if (err.code === 'ENOENT' && force) {
93 return
94 }
95
96 if (isWindows && err.code === 'EPERM') {
97 return fixEPERM(path, options, err, isTop)
98 }
99
100 throw err
101 })
102
103 // no stat object here means either lstat threw an ENOENT, or lstat threw
104 // an EPERM and the fixPERM function took care of things. either way, we're
105 // already done, so return early
106 if (!stat) {
107 return
108 }
109
110 if (stat.isDirectory()) {
111 return rmdir(path, options, null, isTop)
112 }
113
114 return fs.unlink(path)
115 .catch((err) => {
116 if (err.code === 'ENOENT' && force) {
117 return
118 }
119
120 if (err.code === 'EISDIR') {
121 return rmdir(path, options, err, isTop)
122 }
123
124 if (err.code === 'EPERM') {
125 // in windows, we handle this through fixEPERM which will also try to
126 // delete things again. everywhere else since deleting the target as a
127 // file didn't work we go ahead and try to delete it as a directory
128 return isWindows
129 ? fixEPERM(path, options, err, isTop)
130 : rmdir(path, options, err, isTop)
131 }
132
133 throw err
134 })
135}
136
137const fixEPERM = async (path, options, originalErr, isTop) => {
138 const force = isTop ? options.force : true
139 const targetMissing = await fs.chmod(path, 0o666)
140 .catch((err) => {
141 if (err.code === 'ENOENT' && force) {
142 return true
143 }
144
145 throw originalErr
146 })
147
148 // got an ENOENT above, return now. no file = no problem
149 if (targetMissing) {
150 return
151 }
152
153 // this function does its own lstat rather than calling rimraf again to avoid
154 // infinite recursion for a repeating EPERM
155 const stat = await fs.lstat(path)
156 .catch((err) => {
157 if (err.code === 'ENOENT' && force) {
158 return
159 }
160
161 throw originalErr
162 })
163
164 if (!stat) {
165 return
166 }
167
168 if (stat.isDirectory()) {
169 return rmdir(path, options, originalErr, isTop)
170 }
171
172 return fs.unlink(path)
173}
174
175const rmdir = async (path, options, originalErr, isTop) => {
176 if (!options.recursive && isTop) {
177 throw originalErr || new ERR_FS_EISDIR(path)
178 }
179 const force = isTop ? options.force : true
180
181 return fs.rmdir(path)
182 .catch(async (err) => {
183 // in Windows, calling rmdir on a file path will fail with ENOENT rather
184 // than ENOTDIR. to determine if that's what happened, we have to do
185 // another lstat on the path. if the path isn't actually gone, we throw
186 // away the ENOENT and replace it with our own ENOTDIR
187 if (isWindows && err.code === 'ENOENT') {
188 const stillExists = await fs.lstat(path).then(() => true, () => false)
189 if (stillExists) {
190 err = new ENOTDIR(path)
191 }
192 }
193
194 // not there, not a problem
195 if (err.code === 'ENOENT' && force) {
196 return
197 }
198
199 // we may not have originalErr if lstat tells us our target is a
200 // directory but that changes before we actually remove it, so
201 // only throw it here if it's set
202 if (originalErr && err.code === 'ENOTDIR') {
203 throw originalErr
204 }
205
206 // the directory isn't empty, remove the contents and try again
207 if (notEmptyCodes.has(err.code)) {
208 const files = await fs.readdir(path)
209 await Promise.all(files.map((file) => {
210 const target = join(path, file)
211 return rimraf(target, options)
212 }))
213 return fs.rmdir(path)
214 }
215
216 throw err
217 })
218}
219
220const rm = async (path, opts) => {
221 const options = { ...defaultOptions, ...opts }
222 let retries = 0
223
224 const errHandler = async (err) => {
225 if (retryCodes.has(err.code) && ++retries < options.maxRetries) {
226 const delay = retries * options.retryDelay
227 await promiseTimeout(delay)
228 return rimraf(path, options, true).catch(errHandler)
229 }
230
231 throw err
232 }
233
234 return rimraf(path, options, true).catch(errHandler)
235}
236
237const promiseTimeout = (ms) => new Promise((r) => setTimeout(r, ms))
238
239module.exports = rm
Note: See TracBrowser for help on using the repository browser.