1const { dirname, join, resolve, relative, isAbsolute } = require('path') 2const fs = require('fs/promises') 3 4const pathExists = async path => { 5 try { 6 await fs.access(path) 7 return true 8 } catch (er) { 9 return er.code !== 'ENOENT' 10 } 11} 12 13const moveFile = async (source, destination, options = {}, root = true, symlinks = []) => { 14 if (!source || !destination) { 15 throw new TypeError('`source` and `destination` file required') 16 } 17 18 options = { 19 overwrite: true, 20 ...options, 21 } 22 23 if (!options.overwrite && await pathExists(destination)) { 24 throw new Error(`The destination file exists: ${destination}`) 25 } 26 27 await fs.mkdir(dirname(destination), { recursive: true }) 28 29 try { 30 await fs.rename(source, destination) 31 } catch (error) { 32 if (error.code === 'EXDEV' || error.code === 'EPERM') { 33 const sourceStat = await fs.lstat(source) 34 if (sourceStat.isDirectory()) { 35 const files = await fs.readdir(source) 36 await Promise.all(files.map((file) => 37 moveFile(join(source, file), join(destination, file), options, false, symlinks) 38 )) 39 } else if (sourceStat.isSymbolicLink()) { 40 symlinks.push({ source, destination }) 41 } else { 42 await fs.copyFile(source, destination) 43 } 44 } else { 45 throw error 46 } 47 } 48 49 if (root) { 50 await Promise.all(symlinks.map(async ({ source: symSource, destination: symDestination }) => { 51 let target = await fs.readlink(symSource) 52 // junction symlinks in windows will be absolute paths, so we need to 53 // make sure they point to the symlink destination 54 if (isAbsolute(target)) { 55 target = resolve(symDestination, relative(symSource, target)) 56 } 57 // try to determine what the actual file is so we can create the correct 58 // type of symlink in windows 59 let targetStat = 'file' 60 try { 61 targetStat = await fs.stat(resolve(dirname(symSource), target)) 62 if (targetStat.isDirectory()) { 63 targetStat = 'junction' 64 } 65 } catch { 66 // targetStat remains 'file' 67 } 68 await fs.symlink( 69 target, 70 symDestination, 71 targetStat 72 ) 73 })) 74 await fs.rm(source, { recursive: true, force: true }) 75 } 76} 77 78module.exports = moveFile 79