@jsenv/filesystem 2.0.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -38
- package/dist/commonjs/jsenv_filesystem.cjs +174 -26
- package/dist/commonjs/jsenv_filesystem.cjs.map +50 -36
- package/main.js +2 -0
- package/package.json +11 -10
- package/src/assertAndNormalizeDirectoryUrl.js +6 -2
- package/src/assertDirectoryPresence.js +3 -1
- package/src/assertFilePresence.js +5 -1
- package/src/collectDirectoryMatchReport.js +19 -4
- package/src/collectFiles.js +13 -3
- package/src/copyDirectoryContent.js +81 -0
- package/src/copyFileSystemNode.js +27 -8
- package/src/ensureEmptyDirectory.js +4 -1
- package/src/ensureParentDirectories.js +4 -1
- package/src/ensureWindowsDriveLetter.js +6 -2
- package/src/internal/getCommonPathname.js +4 -1
- package/src/internal/replaceBackSlashesWithSlashes.js +2 -1
- package/src/isFileSystemPath.js +3 -1
- package/src/listFilesMatching.js +34 -0
- package/src/moveDirectoryContent.js +15 -4
- package/src/moveFileSystemNode.js +11 -3
- package/src/readDirectory.js +8 -2
- package/src/readFileSystemNodeStat.js +8 -2
- package/src/registerDirectoryLifecycle.js +52 -27
- package/src/registerFileLifecycle.js +32 -10
- package/src/removeFileSystemNode.js +18 -7
- package/src/testFileSystemNodePermissions.js +6 -1
- package/src/urlToBasename.js +2 -1
- package/src/urlToFilename.js +3 -1
- package/src/urlToParentUrl.js +4 -1
- package/src/urlToPathname.js +3 -1
- package/src/urlToRelativeUrl.js +6 -2
- package/src/writeFileSystemNodeModificationTime.js +2 -1
- package/src/writeFileSystemNodePermissions.js +5 -1
- package/src/writeSymbolicLink.js +82 -26
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/* eslint-disable import/max-dependencies */
|
|
2
2
|
import { readdirSync } from "fs"
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
normalizeStructuredMetaMap,
|
|
5
|
+
urlCanContainsMetaMatching,
|
|
6
|
+
urlToMeta,
|
|
7
|
+
} from "@jsenv/url-meta"
|
|
4
8
|
import { replaceBackSlashesWithSlashes } from "./internal/replaceBackSlashesWithSlashes.js"
|
|
5
9
|
import { fileSystemNodeToTypeOrNull } from "./internal/fileSystemNodeToTypeOrNull.js"
|
|
6
10
|
import { createWatcher } from "./internal/createWatcher.js"
|
|
@@ -34,13 +38,20 @@ export const registerDirectoryLifecycle = (
|
|
|
34
38
|
throw new TypeError(`added must be a function or undefined, got ${added}`)
|
|
35
39
|
}
|
|
36
40
|
if (!undefinedOrFunction(updated)) {
|
|
37
|
-
throw new TypeError(
|
|
41
|
+
throw new TypeError(
|
|
42
|
+
`updated must be a function or undefined, got ${updated}`,
|
|
43
|
+
)
|
|
38
44
|
}
|
|
39
45
|
if (!undefinedOrFunction(removed)) {
|
|
40
|
-
throw new TypeError(
|
|
46
|
+
throw new TypeError(
|
|
47
|
+
`removed must be a function or undefined, got ${removed}`,
|
|
48
|
+
)
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
const structuredMetaMap = normalizeStructuredMetaMap(
|
|
51
|
+
const structuredMetaMap = normalizeStructuredMetaMap(
|
|
52
|
+
{ watch: watchDescription },
|
|
53
|
+
sourceUrl,
|
|
54
|
+
)
|
|
44
55
|
const entryShouldBeWatched = ({ relativeUrl, type }) => {
|
|
45
56
|
const entryUrl = resolveUrl(relativeUrl, sourceUrl)
|
|
46
57
|
|
|
@@ -65,7 +76,11 @@ export const registerDirectoryLifecycle = (
|
|
|
65
76
|
|
|
66
77
|
const contentMap = new Map()
|
|
67
78
|
|
|
68
|
-
const handleDirectoryEvent = ({
|
|
79
|
+
const handleDirectoryEvent = ({
|
|
80
|
+
directoryRelativeUrl,
|
|
81
|
+
filename,
|
|
82
|
+
eventType,
|
|
83
|
+
}) => {
|
|
69
84
|
if (filename) {
|
|
70
85
|
if (directoryRelativeUrl) {
|
|
71
86
|
handleChange(`${directoryRelativeUrl}/${filename}`)
|
|
@@ -79,29 +94,36 @@ export const registerDirectoryLifecycle = (
|
|
|
79
94
|
let relativeUrlCandidateArray = Array.from(contentMap.keys())
|
|
80
95
|
|
|
81
96
|
if (recursive && !fsWatchSupportsRecursive) {
|
|
82
|
-
relativeUrlCandidateArray = relativeUrlCandidateArray.filter(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
relativeUrlCandidateArray = relativeUrlCandidateArray.filter(
|
|
98
|
+
(relativeUrlCandidate) => {
|
|
99
|
+
if (!directoryRelativeUrl) {
|
|
100
|
+
// ensure entry is top level
|
|
101
|
+
if (relativeUrlCandidate.includes("/")) return false
|
|
102
|
+
return true
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// entry not inside this directory
|
|
106
|
+
if (!relativeUrlCandidate.startsWith(directoryRelativeUrl))
|
|
107
|
+
return false
|
|
108
|
+
|
|
109
|
+
const afterDirectory = relativeUrlCandidate.slice(
|
|
110
|
+
directoryRelativeUrl.length + 1,
|
|
111
|
+
)
|
|
112
|
+
// deep inside this directory
|
|
113
|
+
if (afterDirectory.includes("/")) return false
|
|
95
114
|
|
|
96
|
-
|
|
97
|
-
|
|
115
|
+
return true
|
|
116
|
+
},
|
|
117
|
+
)
|
|
98
118
|
}
|
|
99
119
|
|
|
100
|
-
const removedEntryRelativeUrl = relativeUrlCandidateArray.find(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
120
|
+
const removedEntryRelativeUrl = relativeUrlCandidateArray.find(
|
|
121
|
+
(relativeUrlCandidate) => {
|
|
122
|
+
const entryUrl = resolveUrl(relativeUrlCandidate, sourceUrl)
|
|
123
|
+
const type = fileSystemNodeToTypeOrNull(entryUrl)
|
|
124
|
+
return type === null
|
|
125
|
+
},
|
|
126
|
+
)
|
|
105
127
|
|
|
106
128
|
if (removedEntryRelativeUrl) {
|
|
107
129
|
handleEntryLost({
|
|
@@ -201,7 +223,9 @@ export const registerDirectoryLifecycle = (
|
|
|
201
223
|
|
|
202
224
|
// we must watch manually every directory we find
|
|
203
225
|
if (!fsWatchSupportsRecursive && type === "directory") {
|
|
204
|
-
const watcher = createWatcher(urlToFileSystemPath(entryUrl), {
|
|
226
|
+
const watcher = createWatcher(urlToFileSystemPath(entryUrl), {
|
|
227
|
+
persistent: keepProcessAlive,
|
|
228
|
+
})
|
|
205
229
|
tracker.registerCleanupCallback(() => {
|
|
206
230
|
watcher.close()
|
|
207
231
|
})
|
|
@@ -246,7 +270,8 @@ export const registerDirectoryLifecycle = (
|
|
|
246
270
|
return tracker.cleanup
|
|
247
271
|
}
|
|
248
272
|
|
|
249
|
-
const undefinedOrFunction = (value) =>
|
|
273
|
+
const undefinedOrFunction = (value) =>
|
|
274
|
+
typeof value === "undefined" || typeof value === "function"
|
|
250
275
|
|
|
251
276
|
const visitDirectory = ({ directoryUrl, entryFound }) => {
|
|
252
277
|
const directoryPath = urlToFileSystemPath(directoryUrl)
|
|
@@ -14,10 +14,14 @@ export const registerFileLifecycle = (
|
|
|
14
14
|
throw new TypeError(`added must be a function or undefined, got ${added}`)
|
|
15
15
|
}
|
|
16
16
|
if (!undefinedOrFunction(updated)) {
|
|
17
|
-
throw new TypeError(
|
|
17
|
+
throw new TypeError(
|
|
18
|
+
`updated must be a function or undefined, got ${updated}`,
|
|
19
|
+
)
|
|
18
20
|
}
|
|
19
21
|
if (!undefinedOrFunction(removed)) {
|
|
20
|
-
throw new TypeError(
|
|
22
|
+
throw new TypeError(
|
|
23
|
+
`removed must be a function or undefined, got ${removed}`,
|
|
24
|
+
)
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
const tracker = trackRessources()
|
|
@@ -34,7 +38,9 @@ export const registerFileLifecycle = (
|
|
|
34
38
|
},
|
|
35
39
|
keepProcessAlive,
|
|
36
40
|
})
|
|
37
|
-
const fileMutationStopTracking = tracker.registerCleanupCallback(
|
|
41
|
+
const fileMutationStopTracking = tracker.registerCleanupCallback(
|
|
42
|
+
fileMutationStopWatching,
|
|
43
|
+
)
|
|
38
44
|
|
|
39
45
|
if (added) {
|
|
40
46
|
if (existent) {
|
|
@@ -56,7 +62,9 @@ export const registerFileLifecycle = (
|
|
|
56
62
|
},
|
|
57
63
|
keepProcessAlive,
|
|
58
64
|
)
|
|
59
|
-
const fileCreationgStopTracking = tracker.registerCleanupCallback(
|
|
65
|
+
const fileCreationgStopTracking = tracker.registerCleanupCallback(
|
|
66
|
+
fileCreationStopWatching,
|
|
67
|
+
)
|
|
60
68
|
}
|
|
61
69
|
|
|
62
70
|
const sourceType = fileSystemNodeToTypeOrNull(sourceUrl)
|
|
@@ -64,24 +72,33 @@ export const registerFileLifecycle = (
|
|
|
64
72
|
if (added) {
|
|
65
73
|
watchFileAdded()
|
|
66
74
|
} else {
|
|
67
|
-
throw new Error(
|
|
75
|
+
throw new Error(
|
|
76
|
+
`${urlToFileSystemPath(sourceUrl)} must lead to a file, found nothing`,
|
|
77
|
+
)
|
|
68
78
|
}
|
|
69
79
|
} else if (sourceType === "file") {
|
|
70
80
|
handleFileFound({ existent: true })
|
|
71
81
|
} else {
|
|
72
|
-
throw new Error(
|
|
82
|
+
throw new Error(
|
|
83
|
+
`${urlToFileSystemPath(
|
|
84
|
+
sourceUrl,
|
|
85
|
+
)} must lead to a file, type found instead`,
|
|
86
|
+
)
|
|
73
87
|
}
|
|
74
88
|
|
|
75
89
|
return tracker.cleanup
|
|
76
90
|
}
|
|
77
91
|
|
|
78
|
-
const undefinedOrFunction = (value) =>
|
|
92
|
+
const undefinedOrFunction = (value) =>
|
|
93
|
+
typeof value === "undefined" || typeof value === "function"
|
|
79
94
|
|
|
80
95
|
const watchFileCreation = (source, callback, keepProcessAlive) => {
|
|
81
96
|
const sourcePath = urlToFileSystemPath(source)
|
|
82
97
|
const sourceFilename = basename(sourcePath)
|
|
83
98
|
const directoryPath = dirname(sourcePath)
|
|
84
|
-
let directoryWatcher = createWatcher(directoryPath, {
|
|
99
|
+
let directoryWatcher = createWatcher(directoryPath, {
|
|
100
|
+
persistent: keepProcessAlive,
|
|
101
|
+
})
|
|
85
102
|
directoryWatcher.on("change", (eventType, filename) => {
|
|
86
103
|
if (filename && filename !== sourceFilename) return
|
|
87
104
|
|
|
@@ -102,8 +119,13 @@ const watchFileCreation = (source, callback, keepProcessAlive) => {
|
|
|
102
119
|
}
|
|
103
120
|
}
|
|
104
121
|
|
|
105
|
-
const watchFileMutation = (
|
|
106
|
-
|
|
122
|
+
const watchFileMutation = (
|
|
123
|
+
sourceUrl,
|
|
124
|
+
{ updated, removed, keepProcessAlive },
|
|
125
|
+
) => {
|
|
126
|
+
let watcher = createWatcher(urlToFileSystemPath(sourceUrl), {
|
|
127
|
+
persistent: keepProcessAlive,
|
|
128
|
+
})
|
|
107
129
|
|
|
108
130
|
watcher.on("change", () => {
|
|
109
131
|
const sourceType = fileSystemNodeToTypeOrNull(sourceUrl)
|
|
@@ -39,10 +39,13 @@ export const removeFileSystemNode = async (
|
|
|
39
39
|
sourceStats.isCharacterDevice() ||
|
|
40
40
|
sourceStats.isBlockDevice()
|
|
41
41
|
) {
|
|
42
|
-
await removeNonDirectory(
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
await removeNonDirectory(
|
|
43
|
+
sourceUrl.endsWith("/") ? sourceUrl.slice(0, -1) : sourceUrl,
|
|
44
|
+
{
|
|
45
|
+
maxRetries,
|
|
46
|
+
retryDelay,
|
|
47
|
+
},
|
|
48
|
+
)
|
|
46
49
|
} else if (sourceStats.isDirectory()) {
|
|
47
50
|
await removeDirectory(ensureUrlTrailingSlash(sourceUrl), {
|
|
48
51
|
recursive,
|
|
@@ -115,7 +118,11 @@ const removeDirectory = async (
|
|
|
115
118
|
return
|
|
116
119
|
}
|
|
117
120
|
|
|
118
|
-
if (
|
|
121
|
+
if (
|
|
122
|
+
sourceStats.isFile() ||
|
|
123
|
+
sourceStats.isCharacterDevice() ||
|
|
124
|
+
sourceStats.isBlockDevice()
|
|
125
|
+
) {
|
|
119
126
|
await visitFile(sourceUrl)
|
|
120
127
|
} else if (sourceStats.isSymbolicLink()) {
|
|
121
128
|
await visitSymbolicLink(sourceUrl)
|
|
@@ -140,7 +147,9 @@ const removeDirectory = async (
|
|
|
140
147
|
...(process.platform === "win32"
|
|
141
148
|
? {
|
|
142
149
|
handlePermissionError: async (error) => {
|
|
143
|
-
console.error(
|
|
150
|
+
console.error(
|
|
151
|
+
`trying to fix windows EPERM after readir on ${directoryPath}`,
|
|
152
|
+
)
|
|
144
153
|
|
|
145
154
|
let openOrCloseError
|
|
146
155
|
try {
|
|
@@ -160,7 +169,9 @@ const removeDirectory = async (
|
|
|
160
169
|
throw error
|
|
161
170
|
}
|
|
162
171
|
|
|
163
|
-
await removeDirectoryNaive(directoryPath, {
|
|
172
|
+
await removeDirectoryNaive(directoryPath, {
|
|
173
|
+
...optionsFromRecursive,
|
|
174
|
+
})
|
|
164
175
|
},
|
|
165
176
|
}
|
|
166
177
|
: {}),
|
|
@@ -12,7 +12,12 @@ const {
|
|
|
12
12
|
|
|
13
13
|
export const testFileSystemNodePermissions = async (
|
|
14
14
|
source,
|
|
15
|
-
{
|
|
15
|
+
{
|
|
16
|
+
read = false,
|
|
17
|
+
write = false,
|
|
18
|
+
execute = false,
|
|
19
|
+
allowedIfNotFound = false,
|
|
20
|
+
} = {},
|
|
16
21
|
) => {
|
|
17
22
|
const sourceUrl = assertAndNormalizeFileUrl(source)
|
|
18
23
|
const sourcePath = urlToFileSystemPath(sourceUrl)
|
package/src/urlToBasename.js
CHANGED
|
@@ -3,6 +3,7 @@ import { urlToFilename } from "./urlToFilename.js"
|
|
|
3
3
|
export const urlToBasename = (url) => {
|
|
4
4
|
const filename = urlToFilename(url)
|
|
5
5
|
const dotLastIndex = filename.lastIndexOf(".")
|
|
6
|
-
const basename =
|
|
6
|
+
const basename =
|
|
7
|
+
dotLastIndex === -1 ? filename : filename.slice(0, dotLastIndex)
|
|
7
8
|
return basename
|
|
8
9
|
}
|
package/src/urlToFilename.js
CHANGED
|
@@ -2,7 +2,9 @@ import { urlToPathname } from "./urlToPathname.js"
|
|
|
2
2
|
|
|
3
3
|
export const urlToFilename = (url) => {
|
|
4
4
|
const pathname = urlToPathname(url)
|
|
5
|
-
const pathnameBeforeLastSlash = pathname.endsWith("/")
|
|
5
|
+
const pathnameBeforeLastSlash = pathname.endsWith("/")
|
|
6
|
+
? pathname.slice(0, -1)
|
|
7
|
+
: pathname
|
|
6
8
|
const slashLastIndex = pathnameBeforeLastSlash.lastIndexOf("/")
|
|
7
9
|
const filename =
|
|
8
10
|
slashLastIndex === -1
|
package/src/urlToParentUrl.js
CHANGED
|
@@ -11,7 +11,10 @@ export const urlToParentUrl = (url) => {
|
|
|
11
11
|
|
|
12
12
|
const lastCharacterIndex = ressource.length - 1
|
|
13
13
|
if (slashLastIndex === lastCharacterIndex) {
|
|
14
|
-
const slashPreviousIndex = ressource.lastIndexOf(
|
|
14
|
+
const slashPreviousIndex = ressource.lastIndexOf(
|
|
15
|
+
"/",
|
|
16
|
+
lastCharacterIndex - 1,
|
|
17
|
+
)
|
|
15
18
|
if (slashPreviousIndex === -1) {
|
|
16
19
|
const urlAsString = String(url)
|
|
17
20
|
return urlAsString
|
package/src/urlToPathname.js
CHANGED
|
@@ -8,5 +8,7 @@ export const urlToPathname = (url) => {
|
|
|
8
8
|
|
|
9
9
|
const ressourceToPathname = (ressource) => {
|
|
10
10
|
const searchSeparatorIndex = ressource.indexOf("?")
|
|
11
|
-
return searchSeparatorIndex === -1
|
|
11
|
+
return searchSeparatorIndex === -1
|
|
12
|
+
? ressource
|
|
13
|
+
: ressource.slice(0, searchSeparatorIndex)
|
|
12
14
|
}
|
package/src/urlToRelativeUrl.js
CHANGED
|
@@ -35,8 +35,12 @@ export const urlToRelativeUrl = (url, baseUrl) => {
|
|
|
35
35
|
const specificPathname = pathname.slice(commonPathname.length)
|
|
36
36
|
const baseSpecificPathname = basePathname.slice(commonPathname.length)
|
|
37
37
|
if (baseSpecificPathname.includes("/")) {
|
|
38
|
-
const baseSpecificParentPathname =
|
|
39
|
-
|
|
38
|
+
const baseSpecificParentPathname =
|
|
39
|
+
pathnameToParentPathname(baseSpecificPathname)
|
|
40
|
+
const relativeDirectoriesNotation = baseSpecificParentPathname.replace(
|
|
41
|
+
/.*?\//g,
|
|
42
|
+
"../",
|
|
43
|
+
)
|
|
40
44
|
const relativeUrl = `${relativeDirectoriesNotation}${specificPathname}${search}${hash}`
|
|
41
45
|
return relativeUrl
|
|
42
46
|
}
|
|
@@ -5,7 +5,8 @@ import { urlToFileSystemPath } from "./urlToFileSystemPath.js"
|
|
|
5
5
|
export const writeFileSystemNodeModificationTime = (source, mtime) => {
|
|
6
6
|
const sourceUrl = assertAndNormalizeFileUrl(source)
|
|
7
7
|
const sourcePath = urlToFileSystemPath(sourceUrl)
|
|
8
|
-
const mtimeValue =
|
|
8
|
+
const mtimeValue =
|
|
9
|
+
typeof mtime === "number" ? new Date(Math.floor(mtime)) : mtime
|
|
9
10
|
// reading atime mutates its value so there is no use case I can think of
|
|
10
11
|
// where we want to modify it
|
|
11
12
|
const atimeValue = mtimeValue
|
|
@@ -23,7 +23,11 @@ export const writeFileSystemNodePermissions = async (source, permissions) => {
|
|
|
23
23
|
others: {
|
|
24
24
|
read: getPermissionOrComputeDefault("read", "others", permissions),
|
|
25
25
|
write: getPermissionOrComputeDefault("write", "others", permissions),
|
|
26
|
-
execute: getPermissionOrComputeDefault(
|
|
26
|
+
execute: getPermissionOrComputeDefault(
|
|
27
|
+
"execute",
|
|
28
|
+
"others",
|
|
29
|
+
permissions,
|
|
30
|
+
),
|
|
27
31
|
},
|
|
28
32
|
}
|
|
29
33
|
binaryFlags = permissionsToBinaryFlags(permissions)
|
package/src/writeSymbolicLink.js
CHANGED
|
@@ -1,57 +1,113 @@
|
|
|
1
1
|
import { promises } from "node:fs"
|
|
2
2
|
|
|
3
3
|
import { assertAndNormalizeFileUrl } from "./assertAndNormalizeFileUrl.js"
|
|
4
|
+
import { fileSystemPathToUrl } from "./fileSystemPathToUrl.js"
|
|
4
5
|
import { urlToFileSystemPath } from "./urlToFileSystemPath.js"
|
|
5
6
|
import { resolveUrl } from "./resolveUrl.js"
|
|
6
7
|
import { isFileSystemPath } from "./isFileSystemPath.js"
|
|
7
8
|
import { ensureParentDirectories } from "./ensureParentDirectories.js"
|
|
8
9
|
import { readFileSystemNodeStat } from "./readFileSystemNodeStat.js"
|
|
10
|
+
import { readSymbolicLink } from "./readSymbolicLink.js"
|
|
11
|
+
import { removeFileSystemNode } from "./removeFileSystemNode.js"
|
|
9
12
|
|
|
10
13
|
// https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_fspromises_symlink_target_path_type
|
|
11
14
|
const { symlink } = promises
|
|
12
15
|
const isWindows = process.platform === "win32"
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Writes a symbolic link pointing from a filesystem node to an other
|
|
19
|
+
* @param {string} from Where symlink is written
|
|
20
|
+
* @param {string} to The symlink target
|
|
21
|
+
* @param {"file" | "dir"} [type] Symlink type if you know it before hand
|
|
22
|
+
* @param {boolean} [allowUseless=false] Prevent error when symlink already exists with the same target
|
|
23
|
+
* @param {boolean} [allowOverwrite=false] Will replace any existing symlink
|
|
24
|
+
*/
|
|
25
|
+
export const writeSymbolicLink = async ({
|
|
26
|
+
from,
|
|
27
|
+
to,
|
|
28
|
+
type,
|
|
29
|
+
allowUseless = false,
|
|
30
|
+
allowOverwrite = false,
|
|
31
|
+
}) => {
|
|
15
32
|
const fromUrl = assertAndNormalizeFileUrl(from)
|
|
16
|
-
|
|
17
|
-
let toValue
|
|
18
|
-
if (typeof to === "string") {
|
|
19
|
-
// absolute filesystem path
|
|
20
|
-
if (isFileSystemPath(to)) {
|
|
21
|
-
toValue = to
|
|
22
|
-
}
|
|
23
|
-
// relative url
|
|
24
|
-
else if (to.startsWith("./") || to.startsWith("../")) {
|
|
25
|
-
toValue = to
|
|
26
|
-
}
|
|
27
|
-
// absolute url
|
|
28
|
-
else {
|
|
29
|
-
const toUrl = String(new URL(to, fromUrl))
|
|
30
|
-
toValue = urlToFileSystemPath(toUrl)
|
|
31
|
-
}
|
|
32
|
-
} else if (to instanceof URL) {
|
|
33
|
-
toValue = urlToFileSystemPath(to)
|
|
34
|
-
} else {
|
|
35
|
-
throw new TypeError(`symbolic link to must be a string or an url, received ${to}`)
|
|
36
|
-
}
|
|
33
|
+
const toInfo = getToInfo(to, fromUrl)
|
|
37
34
|
|
|
38
35
|
if (isWindows && typeof type === "undefined") {
|
|
39
36
|
// without this if you write a symbolic link without specifying the type on windows
|
|
40
37
|
// you later get EPERM when doing stat on the symlink
|
|
41
|
-
const
|
|
42
|
-
|
|
38
|
+
const toStats = await readFileSystemNodeStat(toInfo.url, {
|
|
39
|
+
nullIfNotFound: true,
|
|
40
|
+
})
|
|
43
41
|
type = toStats && toStats.isDirectory() ? "dir" : "file"
|
|
44
42
|
}
|
|
45
43
|
|
|
46
44
|
const symbolicLinkPath = urlToFileSystemPath(fromUrl)
|
|
47
45
|
try {
|
|
48
|
-
await symlink(
|
|
46
|
+
await symlink(toInfo.value, symbolicLinkPath, type)
|
|
49
47
|
} catch (error) {
|
|
50
48
|
if (error.code === "ENOENT") {
|
|
51
49
|
await ensureParentDirectories(fromUrl)
|
|
52
|
-
await symlink(
|
|
50
|
+
await symlink(toInfo.value, symbolicLinkPath, type)
|
|
53
51
|
return
|
|
54
52
|
}
|
|
53
|
+
if (error.code === "EEXIST") {
|
|
54
|
+
if (allowUseless) {
|
|
55
|
+
const existingSymbolicLinkUrl = await readSymbolicLink(fromUrl)
|
|
56
|
+
if (existingSymbolicLinkUrl === toInfo.url) {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (allowOverwrite) {
|
|
61
|
+
await removeFileSystemNode(fromUrl)
|
|
62
|
+
await symlink(toInfo.value, symbolicLinkPath, type)
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
}
|
|
55
66
|
throw error
|
|
56
67
|
}
|
|
57
68
|
}
|
|
69
|
+
|
|
70
|
+
const getToInfo = (to, fromUrl) => {
|
|
71
|
+
if (typeof to === "string") {
|
|
72
|
+
// absolute filesystem path
|
|
73
|
+
if (isFileSystemPath(to)) {
|
|
74
|
+
const url = fileSystemPathToUrl(to)
|
|
75
|
+
const value = to
|
|
76
|
+
return {
|
|
77
|
+
url,
|
|
78
|
+
value,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// relative url
|
|
83
|
+
if (to.startsWith("./") || to.startsWith("../")) {
|
|
84
|
+
const url = resolveUrl(to, fromUrl)
|
|
85
|
+
const value = to
|
|
86
|
+
return {
|
|
87
|
+
url,
|
|
88
|
+
value,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// absolute url
|
|
93
|
+
const url = resolveUrl(to, fromUrl)
|
|
94
|
+
const value = urlToFileSystemPath(url)
|
|
95
|
+
return {
|
|
96
|
+
url,
|
|
97
|
+
value,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (to instanceof URL) {
|
|
102
|
+
const url = String(to)
|
|
103
|
+
const value = urlToFileSystemPath(url)
|
|
104
|
+
return {
|
|
105
|
+
url,
|
|
106
|
+
value,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
throw new TypeError(
|
|
111
|
+
`symbolic link to must be a string or an url, received ${to}`,
|
|
112
|
+
)
|
|
113
|
+
}
|