move-file.js 2.2 KB

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