@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.
Files changed (35) hide show
  1. package/README.md +29 -38
  2. package/dist/commonjs/jsenv_filesystem.cjs +174 -26
  3. package/dist/commonjs/jsenv_filesystem.cjs.map +50 -36
  4. package/main.js +2 -0
  5. package/package.json +11 -10
  6. package/src/assertAndNormalizeDirectoryUrl.js +6 -2
  7. package/src/assertDirectoryPresence.js +3 -1
  8. package/src/assertFilePresence.js +5 -1
  9. package/src/collectDirectoryMatchReport.js +19 -4
  10. package/src/collectFiles.js +13 -3
  11. package/src/copyDirectoryContent.js +81 -0
  12. package/src/copyFileSystemNode.js +27 -8
  13. package/src/ensureEmptyDirectory.js +4 -1
  14. package/src/ensureParentDirectories.js +4 -1
  15. package/src/ensureWindowsDriveLetter.js +6 -2
  16. package/src/internal/getCommonPathname.js +4 -1
  17. package/src/internal/replaceBackSlashesWithSlashes.js +2 -1
  18. package/src/isFileSystemPath.js +3 -1
  19. package/src/listFilesMatching.js +34 -0
  20. package/src/moveDirectoryContent.js +15 -4
  21. package/src/moveFileSystemNode.js +11 -3
  22. package/src/readDirectory.js +8 -2
  23. package/src/readFileSystemNodeStat.js +8 -2
  24. package/src/registerDirectoryLifecycle.js +52 -27
  25. package/src/registerFileLifecycle.js +32 -10
  26. package/src/removeFileSystemNode.js +18 -7
  27. package/src/testFileSystemNodePermissions.js +6 -1
  28. package/src/urlToBasename.js +2 -1
  29. package/src/urlToFilename.js +3 -1
  30. package/src/urlToParentUrl.js +4 -1
  31. package/src/urlToPathname.js +3 -1
  32. package/src/urlToRelativeUrl.js +6 -2
  33. package/src/writeFileSystemNodeModificationTime.js +2 -1
  34. package/src/writeFileSystemNodePermissions.js +5 -1
  35. 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 { normalizeStructuredMetaMap, urlCanContainsMetaMatching, urlToMeta } from "@jsenv/url-meta"
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(`updated must be a function or undefined, got ${updated}`)
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(`removed must be a function or undefined, got ${removed}`)
46
+ throw new TypeError(
47
+ `removed must be a function or undefined, got ${removed}`,
48
+ )
41
49
  }
42
50
 
43
- const structuredMetaMap = normalizeStructuredMetaMap({ watch: watchDescription }, sourceUrl)
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 = ({ directoryRelativeUrl, filename, eventType }) => {
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((relativeUrlCandidate) => {
83
- if (!directoryRelativeUrl) {
84
- // ensure entry is top level
85
- if (relativeUrlCandidate.includes("/")) return false
86
- return true
87
- }
88
-
89
- // entry not inside this directory
90
- if (!relativeUrlCandidate.startsWith(directoryRelativeUrl)) return false
91
-
92
- const afterDirectory = relativeUrlCandidate.slice(directoryRelativeUrl.length + 1)
93
- // deep inside this directory
94
- if (afterDirectory.includes("/")) return false
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
- return true
97
- })
115
+ return true
116
+ },
117
+ )
98
118
  }
99
119
 
100
- const removedEntryRelativeUrl = relativeUrlCandidateArray.find((relativeUrlCandidate) => {
101
- const entryUrl = resolveUrl(relativeUrlCandidate, sourceUrl)
102
- const type = fileSystemNodeToTypeOrNull(entryUrl)
103
- return type === null
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), { persistent: keepProcessAlive })
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) => typeof value === "undefined" || typeof value === "function"
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(`updated must be a function or undefined, got ${updated}`)
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(`removed must be a function or undefined, got ${removed}`)
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(fileMutationStopWatching)
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(fileCreationStopWatching)
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(`${urlToFileSystemPath(sourceUrl)} must lead to a file, found nothing`)
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(`${urlToFileSystemPath(sourceUrl)} must lead to a file, type found instead`)
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) => typeof value === "undefined" || typeof value === "function"
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, { persistent: keepProcessAlive })
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 = (sourceUrl, { updated, removed, keepProcessAlive }) => {
106
- let watcher = createWatcher(urlToFileSystemPath(sourceUrl), { persistent: keepProcessAlive })
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(sourceUrl.endsWith("/") ? sourceUrl.slice(0, -1) : sourceUrl, {
43
- maxRetries,
44
- retryDelay,
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 (sourceStats.isFile() || sourceStats.isCharacterDevice() || sourceStats.isBlockDevice()) {
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(`trying to fix windows EPERM after readir on ${directoryPath}`)
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, { ...optionsFromRecursive })
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
- { read = false, write = false, execute = false, allowedIfNotFound = false } = {},
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)
@@ -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 = dotLastIndex === -1 ? filename : filename.slice(0, dotLastIndex)
6
+ const basename =
7
+ dotLastIndex === -1 ? filename : filename.slice(0, dotLastIndex)
7
8
  return basename
8
9
  }
@@ -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("/") ? pathname.slice(0, -1) : pathname
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
@@ -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("/", lastCharacterIndex - 1)
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
@@ -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 ? ressource : ressource.slice(0, searchSeparatorIndex)
11
+ return searchSeparatorIndex === -1
12
+ ? ressource
13
+ : ressource.slice(0, searchSeparatorIndex)
12
14
  }
@@ -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 = pathnameToParentPathname(baseSpecificPathname)
39
- const relativeDirectoriesNotation = baseSpecificParentPathname.replace(/.*?\//g, "../")
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 = typeof mtime === "number" ? new Date(Math.floor(mtime)) : mtime
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("execute", "others", permissions),
26
+ execute: getPermissionOrComputeDefault(
27
+ "execute",
28
+ "others",
29
+ permissions,
30
+ ),
27
31
  },
28
32
  }
29
33
  binaryFlags = permissionsToBinaryFlags(permissions)
@@ -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
- export const writeSymbolicLink = async ({ from, to, type }) => {
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 toUrl = resolveUrl(toValue, fromUrl)
42
- const toStats = await readFileSystemNodeStat(toUrl, { nullIfNotFound: true })
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(toValue, symbolicLinkPath, type)
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(toValue, symbolicLinkPath, type)
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
+ }