codekingpro/portable-devtools
114k
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 