Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
index.js270 linesDownload Raw Back to lib
1'use strict'
2module.exports = writeFile
3module.exports.sync = writeFileSync
4module.exports._getTmpname = getTmpname // for testing
5module.exports._cleanupOnExit = cleanupOnExit
6
7const fs = require('fs')
8const crypto = require('node:crypto')
9const { onExit } = require('signal-exit')
10const path = require('path')
11const { promisify } = require('util')
12const activeFiles = {}
13
14// if we run inside of a worker_thread, `process.pid` is not unique
15/* istanbul ignore next */
16const threadId = (function getId () {
17  try {
18    const workerThreads = require('worker_threads')
19
20    /// if we are in main thread, this is set to `0`
21    return workerThreads.threadId
22  } catch (e) {
23    // worker_threads are not available, fallback to 0
24    return 0
25  }
26})()
27
28let invocations = 0
29function getTmpname (filename) {
30  return filename + '.' +
31    crypto.createHash('sha1')
32      .update(__filename)
33      .update(String(process.pid))
34      .update(String(threadId))
35      .update(String(++invocations))
36      .digest()
37      .readUInt32BE(0)
38}
39
40function cleanupOnExit (tmpfile) {
41  return () => {
42    try {
43      fs.unlinkSync(typeof tmpfile === 'function' ? tmpfile() : tmpfile)
44    } catch {
45      // ignore errors
46    }
47  }
48}
49
50function serializeActiveFile (absoluteName) {
51  return new Promise(resolve => {
52    // make a queue if it doesn't already exist
53    if (!activeFiles[absoluteName]) {
54      activeFiles[absoluteName] = []
55    }
56
57    activeFiles[absoluteName].push(resolve) // add this job to the queue
58    if (activeFiles[absoluteName].length === 1) {
59      resolve()
60    } // kick off the first one
61  })
62}
63
64// https://github.com/isaacs/node-graceful-fs/blob/master/polyfills.js#L315-L342
65function isChownErrOk (err) {
66  if (err.code === 'ENOSYS') {
67    return true
68  }
69
70  const nonroot = !process.getuid || process.getuid() !== 0
71  if (nonroot) {
72    if (err.code === 'EINVAL' || err.code === 'EPERM') {
73      return true
74    }
75  }
76
77  return false
78}
79
80async function writeFileAsync (filename, data, options = {}) {
81  if (typeof options === 'string') {
82    options = { encoding: options }
83  }
84
85  let fd
86  let tmpfile
87  /* istanbul ignore next -- The closure only gets called when onExit triggers */
88  const removeOnExitHandler = onExit(cleanupOnExit(() => tmpfile))
89  const absoluteName = path.resolve(filename)
90
91  try {
92    await serializeActiveFile(absoluteName)
93    const truename = await promisify(fs.realpath)(filename).catch(() => filename)
94    tmpfile = getTmpname(truename)
95
96    if (!options.mode || !options.chown) {
97      // Either mode or chown is not explicitly set
98      // Default behavior is to copy it from original file
99      const stats = await promisify(fs.stat)(truename).catch(() => {})
100      if (stats) {
101        if (options.mode == null) {
102          options.mode = stats.mode
103        }
104
105        if (options.chown == null && process.getuid) {
106          options.chown = { uid: stats.uid, gid: stats.gid }
107        }
108      }
109    }
110
111    fd = await promisify(fs.open)(tmpfile, 'w', options.mode)
112    if (options.tmpfileCreated) {
113      await options.tmpfileCreated(tmpfile)
114    }
115    if (ArrayBuffer.isView(data)) {
116      await promisify(fs.write)(fd, data, 0, data.length, 0)
117    } else if (data != null) {
118      await promisify(fs.write)(fd, String(data), 0, String(options.encoding || 'utf8'))
119    }
120
121    if (options.fsync !== false) {
122      await promisify(fs.fsync)(fd)
123    }
124
125    await promisify(fs.close)(fd)
126    fd = null
127
128    if (options.chown) {
129      await promisify(fs.chown)(tmpfile, options.chown.uid, options.chown.gid).catch(err => {
130        if (!isChownErrOk(err)) {
131          throw err
132        }
133      })
134    }
135
136    if (options.mode) {
137      await promisify(fs.chmod)(tmpfile, options.mode).catch(err => {
138        if (!isChownErrOk(err)) {
139          throw err
140        }
141      })
142    }
143
144    await promisify(fs.rename)(tmpfile, truename)
145  } finally {
146    if (fd) {
147      await promisify(fs.close)(fd).catch(
148        /* istanbul ignore next */
149        () => {}
150      )
151    }
152    removeOnExitHandler()
153    await promisify(fs.unlink)(tmpfile).catch(() => {})
154    activeFiles[absoluteName].shift() // remove the element added by serializeSameFile
155    if (activeFiles[absoluteName].length > 0) {
156      activeFiles[absoluteName][0]() // start next job if one is pending
157    } else {
158      delete activeFiles[absoluteName]
159    }
160  }
161}
162
163async function writeFile (filename, data, options, callback) {
164  if (options instanceof Function) {
165    callback = options
166    options = {}
167  }
168
169  const promise = writeFileAsync(filename, data, options)
170  if (callback) {
171    try {
172      const result = await promise
173      return callback(result)
174    } catch (err) {
175      return callback(err)
176    }
177  }
178
179  return promise
180}
181
182function writeFileSync (filename, data, options) {
183  if (typeof options === 'string') {
184    options = { encoding: options }
185  } else if (!options) {
186    options = {}
187  }
188  try {
189    filename = fs.realpathSync(filename)
190  } catch (ex) {
191    // it's ok, it'll happen on a not yet existing file
192  }
193  const tmpfile = getTmpname(filename)
194
195  if (!options.mode || !options.chown) {
196    // Either mode or chown is not explicitly set
197    // Default behavior is to copy it from original file
198    try {
199      const stats = fs.statSync(filename)
200      options = Object.assign({}, options)
201      if (!options.mode) {
202        options.mode = stats.mode
203      }
204      if (!options.chown && process.getuid) {
205        options.chown = { uid: stats.uid, gid: stats.gid }
206      }
207    } catch (ex) {
208      // ignore stat errors
209    }
210  }
211
212  let fd
213  const cleanup = cleanupOnExit(tmpfile)
214  const removeOnExitHandler = onExit(cleanup)
215
216  let threw = true
217  try {
218    fd = fs.openSync(tmpfile, 'w', options.mode || 0o666)
219    if (options.tmpfileCreated) {
220      options.tmpfileCreated(tmpfile)
221    }
222    if (ArrayBuffer.isView(data)) {
223      fs.writeSync(fd, data, 0, data.length, 0)
224    } else if (data != null) {
225      fs.writeSync(fd, String(data), 0, String(options.encoding || 'utf8'))
226    }
227    if (options.fsync !== false) {
228      fs.fsyncSync(fd)
229    }
230
231    fs.closeSync(fd)
232    fd = null
233
234    if (options.chown) {
235      try {
236        fs.chownSync(tmpfile, options.chown.uid, options.chown.gid)
237      } catch (err) {
238        if (!isChownErrOk(err)) {
239          throw err
240        }
241      }
242    }
243
244    if (options.mode) {
245      try {
246        fs.chmodSync(tmpfile, options.mode)
247      } catch (err) {
248        if (!isChownErrOk(err)) {
249          throw err
250        }
251      }
252    }
253
254    fs.renameSync(tmpfile, filename)
255    threw = false
256  } finally {
257    if (fd) {
258      try {
259        fs.closeSync(fd)
260      } catch (ex) {
261        // ignore close errors at this stage, error may have closed fd already.
262      }
263    }
264    removeOnExitHandler()
265    if (threw) {
266      cleanup()
267    }
268  }
269}
270 
codekingpro/portable-devtools · Team Ai