This repository has been archived by the owner on Aug 15, 2021. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
copyFileSystemNode.js
190 lines (168 loc) · 7.36 KB
/
copyFileSystemNode.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/* eslint-disable import/max-dependencies */
import { copyFile as copyFileNode } from "fs"
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js"
import { statsToType } from "./internal/statsToType.js"
import { ensureUrlTrailingSlash } from "./internal/ensureUrlTrailingSlash.js"
import { resolveUrl } from "./resolveUrl.js"
import { binaryFlagsToPermissions } from "./internal/permissions.js"
import { assertAndNormalizeFileUrl } from "./assertAndNormalizeFileUrl.js"
import { writeDirectory } from "./writeDirectory.js"
import { urlToRelativeUrl } from "./urlToRelativeUrl.js"
import { readFileSystemNodeStat } from "./readFileSystemNodeStat.js"
import { ensureParentDirectories } from "./ensureParentDirectories.js"
import { writeFileSystemNodePermissions } from "./writeFileSystemNodePermissions.js"
import { writeFileSystemNodeModificationTime } from "./writeFileSystemNodeModificationTime.js"
import { readDirectory } from "./readDirectory.js"
import { readSymbolicLink } from "./readSymbolicLink.js"
import { writeSymbolicLink } from "./writeSymbolicLink.js"
import { urlIsInsideOf } from "./urlIsInsideOf.js"
import { removeFileSystemNode } from "./removeFileSystemNode.js"
import { urlToFileSystemPath } from "./urlToFileSystemPath.js"
export const copyFileSystemNode = async (
source,
destination,
{
overwrite = false,
preserveStat = true,
preserveMtime = preserveStat,
preservePermissions = preserveStat,
allowUseless = false,
followLink = true,
} = {},
) => {
const sourceUrl = assertAndNormalizeFileUrl(source)
let destinationUrl = assertAndNormalizeFileUrl(destination)
const sourcePath = urlToFileSystemPath(sourceUrl)
const sourceStats = await readFileSystemNodeStat(sourceUrl, {
nullIfNotFound: true,
followLink: false,
})
if (!sourceStats) {
throw new Error(`nothing to copy at ${sourcePath}`)
}
let destinationStats = await readFileSystemNodeStat(destinationUrl, {
nullIfNotFound: true,
// we force false here but in fact we will follow the destination link
// to know where we will actually move and detect useless move overrite etc..
followLink: false,
})
if (followLink && destinationStats && destinationStats.isSymbolicLink()) {
const target = await readSymbolicLink(destinationUrl)
destinationUrl = resolveUrl(target, destinationUrl)
destinationStats = await readFileSystemNodeStat(destinationUrl, { nullIfNotFound: true })
}
const destinationPath = urlToFileSystemPath(destinationUrl)
if (urlTargetsSameFileSystemPath(sourceUrl, destinationUrl)) {
if (allowUseless) {
return
}
throw new Error(`cannot copy ${sourcePath} because destination and source are the same`)
}
if (destinationStats) {
const sourceType = statsToType(sourceStats)
const destinationType = statsToType(destinationStats)
if (sourceType !== destinationType) {
throw new Error(
`cannot copy ${sourceType} from ${sourcePath} to ${destinationPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`,
)
}
if (!overwrite) {
throw new Error(
`cannot copy ${sourceType} from ${sourcePath} to ${destinationPath} because destination exists and overwrite option is disabled`,
)
}
// remove file, link, directory...
await removeFileSystemNode(destinationUrl, { recursive: true, allowUseless: true })
} else {
await ensureParentDirectories(destinationUrl)
}
if (sourceStats.isDirectory()) {
destinationUrl = ensureUrlTrailingSlash(destinationUrl)
}
const visit = async (url, stats) => {
if (stats.isFile() || stats.isCharacterDevice() || stats.isBlockDevice()) {
await visitFile(url, stats)
} else if (stats.isSymbolicLink()) {
await visitSymbolicLink(url, stats)
} else if (stats.isDirectory()) {
await visitDirectory(ensureUrlTrailingSlash(url), stats)
}
}
const visitFile = async (fileUrl, fileStats) => {
const fileRelativeUrl = urlToRelativeUrl(fileUrl, sourceUrl)
const fileCopyUrl = resolveUrl(fileRelativeUrl, destinationUrl)
await copyFileContentNaive(urlToFileSystemPath(fileUrl), urlToFileSystemPath(fileCopyUrl))
await copyStats(fileCopyUrl, fileStats)
}
const visitSymbolicLink = async (symbolicLinkUrl) => {
const symbolicLinkRelativeUrl = urlToRelativeUrl(symbolicLinkUrl, sourceUrl)
const symbolicLinkTarget = await readSymbolicLink(symbolicLinkUrl)
const symbolicLinkTargetUrl = resolveUrl(symbolicLinkTarget, symbolicLinkUrl)
const linkIsRelative =
symbolicLinkTarget.startsWith("./") || symbolicLinkTarget.startsWith("../")
let symbolicLinkCopyTarget
if (symbolicLinkTargetUrl === sourceUrl) {
symbolicLinkCopyTarget = linkIsRelative ? symbolicLinkTarget : destinationUrl
} else if (urlIsInsideOf(symbolicLinkTargetUrl, sourceUrl)) {
// symbolic link targets something inside the directory we want to copy
// reflects it inside the copied directory structure
const linkCopyTargetRelative = urlToRelativeUrl(symbolicLinkTargetUrl, sourceUrl)
symbolicLinkCopyTarget = linkIsRelative
? `./${linkCopyTargetRelative}`
: resolveUrl(linkCopyTargetRelative, destinationUrl)
} else {
// symbolic link targets something outside the directory we want to copy
symbolicLinkCopyTarget = symbolicLinkTarget
}
// we must guess ourself the type of the symlink
// because the destination might not exists because not yet copied
// https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_fs_symlink_target_path_type_callback
const targetStats = await readFileSystemNodeStat(symbolicLinkTargetUrl, {
nullIfNotFound: true,
followLink: false,
})
const linkType = targetStats && targetStats.isDirectory() ? "dir" : "file"
const symbolicLinkCopyUrl = resolveUrl(symbolicLinkRelativeUrl, destinationUrl)
await writeSymbolicLink(symbolicLinkCopyUrl, symbolicLinkCopyTarget, { type: linkType })
}
const copyStats = async (destinationUrl, stats) => {
if (preservePermissions || preserveMtime) {
const { mode, mtimeMs } = stats
if (preservePermissions) {
await writeFileSystemNodePermissions(destinationUrl, binaryFlagsToPermissions(mode))
}
if (preserveMtime) {
await writeFileSystemNodeModificationTime(destinationUrl, mtimeMs)
}
}
}
const visitDirectory = async (directoryUrl, directoryStats) => {
const directoryRelativeUrl = urlToRelativeUrl(directoryUrl, sourceUrl)
const directoryCopyUrl = resolveUrl(directoryRelativeUrl, destinationUrl)
await writeDirectory(directoryCopyUrl)
await copyDirectoryContent(directoryUrl)
await copyStats(directoryCopyUrl, directoryStats)
}
const copyDirectoryContent = async (directoryUrl) => {
const names = await readDirectory(directoryUrl)
await Promise.all(
names.map(async (name) => {
const fileSystemNodeUrl = resolveUrl(name, directoryUrl)
const stats = await readFileSystemNodeStat(fileSystemNodeUrl, { followLink: false })
await visit(fileSystemNodeUrl, stats)
}),
)
}
await visit(sourceUrl, sourceStats)
}
const copyFileContentNaive = (filePath, fileDestinationPath) => {
return new Promise((resolve, reject) => {
copyFileNode(filePath, fileDestinationPath, (error) => {
if (error) {
reject(error)
} else {
resolve()
}
})
})
}