• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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