UNPKG

rename-overwrite

Version:

Like `fs.rename` but overwrites existing file or directory

129 lines (126 loc) 3.63 kB
'use strict' const fs = require('fs') const { copySync, copy } = require('fs-extra') const path = require('path') const rimraf = require('@zkochan/rimraf') module.exports = async function renameOverwrite (oldPath, newPath, retry = 0) { try { await fs.promises.rename(oldPath, newPath) } catch (err) { retry++ if (retry > 3) throw err switch (err.code) { case 'ENOTEMPTY': case 'EEXIST': case 'ENOTDIR': await rimraf(newPath) await renameOverwrite(oldPath, newPath, retry) break // Windows Antivirus issues case 'EPERM': case 'EACCESS': case 'EBUSY': { await rimraf(newPath) const start = Date.now() let backoff = 0 let lastError = err while (Date.now() - start < 60000 && (lastError.code === 'EPERM' || lastError.code === 'EACCESS' || lastError.code === 'EBUSY')) { await new Promise(resolve => setTimeout(resolve, backoff)) try { await fs.promises.rename(oldPath, newPath) return } catch (err) { lastError = err } if (backoff < 100) { backoff += 10 } } throw lastError } case 'ENOENT': try { await fs.promises.stat(oldPath) } catch (statErr) { // If the source file does not exist, we cannot possible rename it if (statErr.code === 'ENOENT') { throw statErr } } await fs.promises.mkdir(path.dirname(newPath), { recursive: true }) await renameOverwrite(oldPath, newPath, retry) break // Crossing filesystem boundaries so rename is not available case 'EXDEV': try { await rimraf(newPath) } catch (rimrafErr) { if (rimrafErr.code !== 'ENOENT') { throw rimrafErr } } await copy(oldPath, newPath) await rimraf(oldPath) break default: throw err } } } module.exports.sync = function renameOverwriteSync (oldPath, newPath, retry = 0) { try { fs.renameSync(oldPath, newPath) } catch (err) { retry++ if (retry > 3) throw err switch (err.code) { // Windows Antivirus issues case 'EPERM': case 'EACCESS': case 'EBUSY': { rimraf.sync(newPath) const start = Date.now() let backoff = 0 let lastError = err while (Date.now() - start < 60000 && (lastError.code === 'EPERM' || lastError.code === 'EACCESS' || lastError.code === 'EBUSY')) { const waitUntil = Date.now() + backoff while (waitUntil > Date.now()) {} try { fs.renameSync(oldPath, newPath) return } catch (err) { lastError = err } if (backoff < 100) { backoff += 10 } } throw lastError } case 'ENOTEMPTY': case 'EEXIST': case 'ENOTDIR': rimraf.sync(newPath) fs.renameSync(oldPath, newPath) return case 'ENOENT': fs.mkdirSync(path.dirname(newPath), { recursive: true }) renameOverwriteSync(oldPath, newPath, retry) return // Crossing filesystem boundaries so rename is not available case 'EXDEV': try { rimraf.sync(newPath) } catch (rimrafErr) { if (rimrafErr.code !== 'ENOENT') { throw rimrafErr } } copySync(oldPath, newPath) rimraf.sync(oldPath) break default: throw err } } }