index.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. const { dirname, join, resolve, relative, isAbsolute } = require('path')
  2. const rimraf_ = require('rimraf')
  3. const { promisify } = require('util')
  4. const {
  5. access: access_,
  6. accessSync,
  7. copyFile: copyFile_,
  8. copyFileSync,
  9. readdir: readdir_,
  10. readdirSync,
  11. rename: rename_,
  12. renameSync,
  13. stat: stat_,
  14. statSync,
  15. lstat: lstat_,
  16. lstatSync,
  17. symlink: symlink_,
  18. symlinkSync,
  19. readlink: readlink_,
  20. readlinkSync,
  21. } = require('fs')
  22. const access = promisify(access_)
  23. const copyFile = promisify(copyFile_)
  24. const readdir = promisify(readdir_)
  25. const rename = promisify(rename_)
  26. const stat = promisify(stat_)
  27. const lstat = promisify(lstat_)
  28. const symlink = promisify(symlink_)
  29. const readlink = promisify(readlink_)
  30. const rimraf = promisify(rimraf_)
  31. const rimrafSync = rimraf_.sync
  32. const mkdirp = require('mkdirp')
  33. const pathExists = async path => {
  34. try {
  35. await access(path)
  36. return true
  37. } catch (er) {
  38. return er.code !== 'ENOENT'
  39. }
  40. }
  41. const pathExistsSync = path => {
  42. try {
  43. accessSync(path)
  44. return true
  45. } catch (er) {
  46. return er.code !== 'ENOENT'
  47. }
  48. }
  49. const moveFile = async (source, destination, options = {}, root = true, symlinks = []) => {
  50. if (!source || !destination) {
  51. throw new TypeError('`source` and `destination` file required')
  52. }
  53. options = {
  54. overwrite: true,
  55. ...options,
  56. }
  57. if (!options.overwrite && await pathExists(destination)) {
  58. throw new Error(`The destination file exists: ${destination}`)
  59. }
  60. await mkdirp(dirname(destination))
  61. try {
  62. await rename(source, destination)
  63. } catch (error) {
  64. if (error.code === 'EXDEV' || error.code === 'EPERM') {
  65. const sourceStat = await lstat(source)
  66. if (sourceStat.isDirectory()) {
  67. const files = await readdir(source)
  68. await Promise.all(files.map((file) =>
  69. moveFile(join(source, file), join(destination, file), options, false, symlinks)
  70. ))
  71. } else if (sourceStat.isSymbolicLink()) {
  72. symlinks.push({ source, destination })
  73. } else {
  74. await copyFile(source, destination)
  75. }
  76. } else {
  77. throw error
  78. }
  79. }
  80. if (root) {
  81. await Promise.all(symlinks.map(async ({ source: symSource, destination: symDestination }) => {
  82. let target = await readlink(symSource)
  83. // junction symlinks in windows will be absolute paths, so we need to
  84. // make sure they point to the symlink destination
  85. if (isAbsolute(target)) {
  86. target = resolve(symDestination, relative(symSource, target))
  87. }
  88. // try to determine what the actual file is so we can create the correct
  89. // type of symlink in windows
  90. let targetStat = 'file'
  91. try {
  92. targetStat = await stat(resolve(dirname(symSource), target))
  93. if (targetStat.isDirectory()) {
  94. targetStat = 'junction'
  95. }
  96. } catch {
  97. // targetStat remains 'file'
  98. }
  99. await symlink(
  100. target,
  101. symDestination,
  102. targetStat
  103. )
  104. }))
  105. await rimraf(source)
  106. }
  107. }
  108. const moveFileSync = (source, destination, options = {}, root = true, symlinks = []) => {
  109. if (!source || !destination) {
  110. throw new TypeError('`source` and `destination` file required')
  111. }
  112. options = {
  113. overwrite: true,
  114. ...options,
  115. }
  116. if (!options.overwrite && pathExistsSync(destination)) {
  117. throw new Error(`The destination file exists: ${destination}`)
  118. }
  119. mkdirp.sync(dirname(destination))
  120. try {
  121. renameSync(source, destination)
  122. } catch (error) {
  123. if (error.code === 'EXDEV' || error.code === 'EPERM') {
  124. const sourceStat = lstatSync(source)
  125. if (sourceStat.isDirectory()) {
  126. const files = readdirSync(source)
  127. for (const file of files) {
  128. moveFileSync(join(source, file), join(destination, file), options, false, symlinks)
  129. }
  130. } else if (sourceStat.isSymbolicLink()) {
  131. symlinks.push({ source, destination })
  132. } else {
  133. copyFileSync(source, destination)
  134. }
  135. } else {
  136. throw error
  137. }
  138. }
  139. if (root) {
  140. for (const { source: symSource, destination: symDestination } of symlinks) {
  141. let target = readlinkSync(symSource)
  142. // junction symlinks in windows will be absolute paths, so we need to
  143. // make sure they point to the symlink destination
  144. if (isAbsolute(target)) {
  145. target = resolve(symDestination, relative(symSource, target))
  146. }
  147. // try to determine what the actual file is so we can create the correct
  148. // type of symlink in windows
  149. let targetStat = 'file'
  150. try {
  151. targetStat = statSync(resolve(dirname(symSource), target))
  152. if (targetStat.isDirectory()) {
  153. targetStat = 'junction'
  154. }
  155. } catch {
  156. // targetStat remains 'file'
  157. }
  158. symlinkSync(
  159. target,
  160. symDestination,
  161. targetStat
  162. )
  163. }
  164. rimrafSync(source)
  165. }
  166. }
  167. module.exports = moveFile
  168. module.exports.sync = moveFileSync