@jsenv/util
Version:
Set of functions often needed when using Node.js.
95 lines • 154 kB
Source Map (JSON)
{
"version": 3,
"file": "jsenv_util.cjs",
"sources": [
"../../src/internal/ensureUrlTrailingSlash.js",
"../../src/isFileSystemPath.js",
"../../src/fileSystemPathToUrl.js",
"../../src/assertAndNormalizeDirectoryUrl.js",
"../../src/assertAndNormalizeFileUrl.js",
"../../src/internal/statsToType.js",
"../../src/urlToFileSystemPath.js",
"../../src/internal/permissions.js",
"../../src/writeFileSystemNodePermissions.js",
"../../src/readFileSystemNodeStat.js",
"../../src/assertDirectoryPresence.js",
"../../src/assertFilePresence.js",
"../../src/bufferToEtag.js",
"../../src/readDirectory.js",
"../../src/internal/getCommonPathname.js",
"../../src/internal/pathnameToParentPathname.js",
"../../src/urlToRelativeUrl.js",
"../../src/comparePathnames.js",
"../../src/collectDirectoryMatchReport.js",
"../../src/collectFiles.js",
"../../src/writeDirectory.js",
"../../src/resolveUrl.js",
"../../src/removeFileSystemNode.js",
"../../src/ensureEmptyDirectory.js",
"../../src/ensureParentDirectories.js",
"../../src/ensureWindowsDriveLetter.js",
"../../src/internal/urlTargetsSameFileSystemPath.js",
"../../src/writeFileSystemNodeModificationTime.js",
"../../src/internal/replaceBackSlashesWithSlashes.js",
"../../src/readSymbolicLink.js",
"../../src/writeSymbolicLink.js",
"../../src/urlIsInsideOf.js",
"../../src/copyFileSystemNode.js",
"../../src/readFileSystemNodePermissions.js",
"../../src/grantPermissionsOnFileSystemNode.js",
"../../src/memoize.js",
"../../src/moveFileSystemNode.js",
"../../src/moveDirectoryContent.js",
"../../src/readFile.js",
"../../src/readFileSystemNodeModificationTime.js",
"../../src/internal/fileSystemNodeToTypeOrNull.js",
"../../src/internal/createWatcher.js",
"../../src/internal/trackRessources.js",
"../../src/registerDirectoryLifecycle.js",
"../../src/registerFileLifecycle.js",
"../../src/resolveDirectoryUrl.js",
"../../src/testFileSystemNodePermissions.js",
"../../src/urlToScheme.js",
"../../src/urlToRessource.js",
"../../src/urlToPathname.js",
"../../src/urlToFilename.js",
"../../src/urlToBasename.js",
"../../src/urlToExtension.js",
"../../src/urlToOrigin.js",
"../../src/urlToParentUrl.js",
"../../src/writeFile.js"
],
"sourcesContent": [
"export const ensureUrlTrailingSlash = (url) => {\n return url.endsWith(\"/\") ? url : `${url}/`\n}\n",
"export const isFileSystemPath = (value) => {\n if (typeof value !== \"string\") {\n throw new TypeError(`isFileSystemPath first arg must be a string, got ${value}`)\n }\n\n if (value[0] === \"/\") return true\n return startsWithWindowsDriveLetter(value)\n}\n\nconst startsWithWindowsDriveLetter = (string) => {\n const firstChar = string[0]\n if (!/[a-zA-Z]/.test(firstChar)) return false\n\n const secondChar = string[1]\n if (secondChar !== \":\") return false\n\n return true\n}\n",
"import { pathToFileURL } from \"url\"\nimport { isFileSystemPath } from \"./isFileSystemPath.js\"\n\nexport const fileSystemPathToUrl = (value) => {\n if (!isFileSystemPath(value)) {\n throw new Error(`received an invalid value for fileSystemPath: ${value}`)\n }\n return String(pathToFileURL(value))\n}\n",
"import { ensureUrlTrailingSlash } from \"./internal/ensureUrlTrailingSlash.js\"\nimport { isFileSystemPath } from \"./isFileSystemPath.js\"\nimport { fileSystemPathToUrl } from \"./fileSystemPathToUrl.js\"\n\nexport const assertAndNormalizeDirectoryUrl = (value) => {\n let urlString\n\n if (value instanceof URL) {\n urlString = value.href\n } else if (typeof value === \"string\") {\n if (isFileSystemPath(value)) {\n urlString = fileSystemPathToUrl(value)\n } else {\n try {\n urlString = String(new URL(value))\n } catch (e) {\n throw new TypeError(`directoryUrl must be a valid url, received ${value}`)\n }\n }\n } else {\n throw new TypeError(`directoryUrl must be a string or an url, received ${value}`)\n }\n\n if (!urlString.startsWith(\"file://\")) {\n throw new Error(`directoryUrl must starts with file://, received ${value}`)\n }\n\n return ensureUrlTrailingSlash(urlString)\n}\n",
"import { isFileSystemPath } from \"./isFileSystemPath.js\"\nimport { fileSystemPathToUrl } from \"./fileSystemPathToUrl.js\"\n\nexport const assertAndNormalizeFileUrl = (value, baseUrl) => {\n let urlString\n\n if (value instanceof URL) {\n urlString = value.href\n } else if (typeof value === \"string\") {\n if (isFileSystemPath(value)) {\n urlString = fileSystemPathToUrl(value)\n } else {\n try {\n urlString = String(new URL(value, baseUrl))\n } catch (e) {\n throw new TypeError(`fileUrl must be a valid url, received ${value}`)\n }\n }\n } else {\n throw new TypeError(`fileUrl must be a string or an url, received ${value}`)\n }\n\n if (!urlString.startsWith(\"file://\")) {\n throw new Error(`fileUrl must starts with file://, received ${value}`)\n }\n\n return urlString\n}\n",
"export const statsToType = (stats) => {\n if (stats.isFile()) return \"file\"\n if (stats.isDirectory()) return \"directory\"\n if (stats.isSymbolicLink()) return \"symbolic-link\"\n if (stats.isFIFO()) return \"fifo\"\n if (stats.isSocket()) return \"socket\"\n if (stats.isCharacterDevice()) return \"character-device\"\n if (stats.isBlockDevice()) return \"block-device\"\n return undefined\n}\n",
"import { fileURLToPath } from \"url\"\n\nexport const urlToFileSystemPath = (fileUrl) => {\n if (fileUrl[fileUrl.length - 1] === \"/\") {\n // remove trailing / so that nodejs path becomes predictable otherwise it logs\n // the trailing slash on linux but does not on windows\n fileUrl = fileUrl.slice(0, -1)\n }\n const fileSystemPath = fileURLToPath(fileUrl)\n return fileSystemPath\n}\n",
"// https://github.com/coderaiser/cloudcmd/issues/63#issuecomment-195478143\n// https://nodejs.org/api/fs.html#fs_file_modes\n// https://github.com/TooTallNate/stat-mode\n\n// cannot get from fs.constants because they are not available on windows\nconst S_IRUSR = 256 /* 0000400 read permission, owner */\nconst S_IWUSR = 128 /* 0000200 write permission, owner */\nconst S_IXUSR = 64 /* 0000100 execute/search permission, owner */\nconst S_IRGRP = 32 /* 0000040 read permission, group */\nconst S_IWGRP = 16 /* 0000020 write permission, group */\nconst S_IXGRP = 8 /* 0000010 execute/search permission, group */\nconst S_IROTH = 4 /* 0000004 read permission, others */\nconst S_IWOTH = 2 /* 0000002 write permission, others */\nconst S_IXOTH = 1 /* 0000001 execute/search permission, others */\n\n/*\nhere we could warn that on windows only 0o444 or 0o666 will work\n\n0o444 (readonly)\n{\n owner: {read: true, write: false, execute: false},\n group: {read: true, write: false, execute: false},\n others: {read: true, write: false, execute: false},\n}\n\n0o666 (read and write)\n{\n owner: {read: true, write: true, execute: false},\n group: {read: true, write: true, execute: false},\n others: {read: true, write: true, execute: false},\n}\n*/\nexport const binaryFlagsToPermissions = (binaryFlags) => {\n const owner = {\n read: Boolean(binaryFlags & S_IRUSR),\n write: Boolean(binaryFlags & S_IWUSR),\n execute: Boolean(binaryFlags & S_IXUSR),\n }\n\n const group = {\n read: Boolean(binaryFlags & S_IRGRP),\n write: Boolean(binaryFlags & S_IWGRP),\n execute: Boolean(binaryFlags & S_IXGRP),\n }\n\n const others = {\n read: Boolean(binaryFlags & S_IROTH),\n write: Boolean(binaryFlags & S_IWOTH),\n execute: Boolean(binaryFlags & S_IXOTH),\n }\n\n return {\n owner,\n group,\n others,\n }\n}\n\nexport const permissionsToBinaryFlags = ({ owner, group, others }) => {\n let binaryFlags = 0\n\n if (owner.read) binaryFlags |= S_IRUSR\n if (owner.write) binaryFlags |= S_IWUSR\n if (owner.execute) binaryFlags |= S_IXUSR\n\n if (group.read) binaryFlags |= S_IRGRP\n if (group.write) binaryFlags |= S_IWGRP\n if (group.execute) binaryFlags |= S_IXGRP\n\n if (others.read) binaryFlags |= S_IROTH\n if (others.write) binaryFlags |= S_IWOTH\n if (others.execute) binaryFlags |= S_IXOTH\n\n return binaryFlags\n}\n",
"import { chmod } from \"fs\"\nimport { permissionsToBinaryFlags } from \"./internal/permissions.js\"\nimport { assertAndNormalizeFileUrl } from \"./assertAndNormalizeFileUrl.js\"\nimport { urlToFileSystemPath } from \"./urlToFileSystemPath.js\"\n\nexport const writeFileSystemNodePermissions = async (source, permissions) => {\n const sourceUrl = assertAndNormalizeFileUrl(source)\n const sourcePath = urlToFileSystemPath(sourceUrl)\n\n let binaryFlags\n if (typeof permissions === \"object\") {\n permissions = {\n owner: {\n read: getPermissionOrComputeDefault(\"read\", \"owner\", permissions),\n write: getPermissionOrComputeDefault(\"write\", \"owner\", permissions),\n execute: getPermissionOrComputeDefault(\"execute\", \"owner\", permissions),\n },\n group: {\n read: getPermissionOrComputeDefault(\"read\", \"group\", permissions),\n write: getPermissionOrComputeDefault(\"write\", \"group\", permissions),\n execute: getPermissionOrComputeDefault(\"execute\", \"group\", permissions),\n },\n others: {\n read: getPermissionOrComputeDefault(\"read\", \"others\", permissions),\n write: getPermissionOrComputeDefault(\"write\", \"others\", permissions),\n execute: getPermissionOrComputeDefault(\"execute\", \"others\", permissions),\n },\n }\n binaryFlags = permissionsToBinaryFlags(permissions)\n } else {\n binaryFlags = permissions\n }\n\n return chmodNaive(sourcePath, binaryFlags)\n}\n\nconst chmodNaive = (fileSystemPath, binaryFlags) => {\n return new Promise((resolve, reject) => {\n chmod(fileSystemPath, binaryFlags, (error) => {\n if (error) {\n reject(error)\n } else {\n resolve()\n }\n })\n })\n}\n\nconst actionLevels = { read: 0, write: 1, execute: 2 }\nconst subjectLevels = { others: 0, group: 1, owner: 2 }\n\nconst getPermissionOrComputeDefault = (action, subject, permissions) => {\n if (subject in permissions) {\n const subjectPermissions = permissions[subject]\n if (action in subjectPermissions) {\n return subjectPermissions[action]\n }\n\n const actionLevel = actionLevels[action]\n const actionFallback = Object.keys(actionLevels).find(\n (actionFallbackCandidate) =>\n actionLevels[actionFallbackCandidate] > actionLevel &&\n actionFallbackCandidate in subjectPermissions,\n )\n if (actionFallback) {\n return subjectPermissions[actionFallback]\n }\n }\n\n const subjectLevel = subjectLevels[subject]\n // do we have a subject with a stronger level (group or owner)\n // where we could read the action permission ?\n const subjectFallback = Object.keys(subjectLevels).find(\n (subjectFallbackCandidate) =>\n subjectLevels[subjectFallbackCandidate] > subjectLevel &&\n subjectFallbackCandidate in permissions,\n )\n if (subjectFallback) {\n const subjectPermissions = permissions[subjectFallback]\n return action in subjectPermissions\n ? subjectPermissions[action]\n : getPermissionOrComputeDefault(action, subjectFallback, permissions)\n }\n\n return false\n}\n",
"import { lstat, stat } from \"fs\"\nimport { assertAndNormalizeFileUrl } from \"./assertAndNormalizeFileUrl.js\"\nimport { urlToFileSystemPath } from \"./urlToFileSystemPath.js\"\nimport { writeFileSystemNodePermissions } from \"./writeFileSystemNodePermissions.js\"\n\nconst isWindows = process.platform === \"win32\"\n\nexport const readFileSystemNodeStat = async (\n source,\n { nullIfNotFound = false, followLink = true } = {},\n) => {\n if (source.endsWith(\"/\")) source = source.slice(0, -1)\n\n const sourceUrl = assertAndNormalizeFileUrl(source)\n const sourcePath = urlToFileSystemPath(sourceUrl)\n\n const handleNotFoundOption = nullIfNotFound\n ? {\n handleNotFoundError: () => null,\n }\n : {}\n\n return readStat(sourcePath, {\n followLink,\n ...handleNotFoundOption,\n ...(isWindows\n ? {\n // Windows can EPERM on stat\n handlePermissionDeniedError: async (error) => {\n console.error(`trying to fix windows EPERM after stats on ${sourcePath}`)\n\n try {\n // unfortunately it means we mutate the permissions\n // without being able to restore them to the previous value\n // (because reading current permission would also throw)\n await writeFileSystemNodePermissions(sourceUrl, 0o666)\n const stats = await readStat(sourcePath, {\n followLink,\n ...handleNotFoundOption,\n // could not fix the permission error, give up and throw original error\n handlePermissionDeniedError: () => {\n console.error(`still got EPERM after stats on ${sourcePath}`)\n throw error\n },\n })\n return stats\n } catch (e) {\n console.error(\n `error while trying to fix windows EPERM after stats on ${sourcePath}: ${e.stack}`,\n )\n throw error\n }\n },\n }\n : {}),\n })\n}\n\nconst readStat = (\n sourcePath,\n { followLink, handleNotFoundError = null, handlePermissionDeniedError = null } = {},\n) => {\n const nodeMethod = followLink ? stat : lstat\n\n return new Promise((resolve, reject) => {\n nodeMethod(sourcePath, (error, statsObject) => {\n if (error) {\n if (handleNotFoundError && error.code === \"ENOENT\") {\n resolve(handleNotFoundError(error))\n } else if (\n handlePermissionDeniedError &&\n (error.code === \"EPERM\" || error.code === \"EACCES\")\n ) {\n resolve(handlePermissionDeniedError(error))\n } else {\n reject(error)\n }\n } else {\n resolve(statsObject)\n }\n })\n })\n}\n",
"import { statsToType } from \"./internal/statsToType.js\"\nimport { assertAndNormalizeFileUrl } from \"./assertAndNormalizeFileUrl.js\"\nimport { urlToFileSystemPath } from \"./urlToFileSystemPath.js\"\nimport { readFileSystemNodeStat } from \"./readFileSystemNodeStat.js\"\n\nexport const assertDirectoryPresence = async (source) => {\n const sourceUrl = assertAndNormalizeFileUrl(source)\n const sourcePath = urlToFileSystemPath(sourceUrl)\n\n const sourceStats = await readFileSystemNodeStat(sourceUrl, {\n nullIfNotFound: true,\n })\n if (!sourceStats) {\n throw new Error(`directory not found at ${sourcePath}`)\n }\n if (!sourceStats.isDirectory()) {\n throw new Error(\n `directory expected at ${sourcePath} and found ${statsToType(sourceStats)} instead`,\n )\n }\n}\n",
"import { statsToType } from \"./internal/statsToType.js\"\nimport { assertAndNormalizeFileUrl } from \"./assertAndNormalizeFileUrl.js\"\nimport { urlToFileSystemPath } from \"./urlToFileSystemPath.js\"\nimport { readFileSystemNodeStat } from \"./readFileSystemNodeStat.js\"\n\nexport const assertFilePresence = async (source) => {\n const sourceUrl = assertAndNormalizeFileUrl(source)\n const sourcePath = urlToFileSystemPath(sourceUrl)\n\n const sourceStats = await readFileSystemNodeStat(sourceUrl, {\n nullIfNotFound: true,\n })\n if (!sourceStats) {\n throw new Error(`file not found at ${sourcePath}`)\n }\n if (!sourceStats.isFile()) {\n throw new Error(`file expected at ${sourcePath} and found ${statsToType(sourceStats)} instead`)\n }\n}\n",
"import { createHash } from \"crypto\"\n\nconst ETAG_FOR_EMPTY_CONTENT = '\"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk\"'\n\nexport const bufferToEtag = (buffer) => {\n if (!Buffer.isBuffer(buffer)) {\n throw new TypeError(`buffer expected, got ${buffer}`)\n }\n\n if (buffer.length === 0) {\n return ETAG_FOR_EMPTY_CONTENT\n }\n\n const hash = createHash(\"sha1\")\n hash.update(buffer, \"utf8\")\n\n const hashBase64String = hash.digest(\"base64\")\n const hashBase64StringSubset = hashBase64String.slice(0, 27)\n const length = buffer.length\n\n return `\"${length.toString(16)}-${hashBase64StringSubset}\"`\n}\n",
"import { readdir } from \"fs\"\nimport { assertAndNormalizeDirectoryUrl } from \"./assertAndNormalizeDirectoryUrl.js\"\nimport { urlToFileSystemPath } from \"./urlToFileSystemPath.js\"\n\nexport const readDirectory = async (url, { emfileMaxWait = 1000 } = {}) => {\n const directoryUrl = assertAndNormalizeDirectoryUrl(url)\n const directoryPath = urlToFileSystemPath(directoryUrl)\n const startMs = Date.now()\n let attemptCount = 0\n\n const attempt = () => {\n return readdirNaive(directoryPath, {\n handleTooManyFilesOpenedError: async (error) => {\n attemptCount++\n const nowMs = Date.now()\n const timeSpentWaiting = nowMs - startMs\n if (timeSpentWaiting > emfileMaxWait) {\n throw error\n }\n\n return new Promise((resolve) => {\n setTimeout(() => {\n resolve(attempt())\n }, attemptCount)\n })\n },\n })\n }\n\n return attempt()\n}\n\nconst readdirNaive = (directoryPath, { handleTooManyFilesOpenedError = null } = {}) => {\n return new Promise((resolve, reject) => {\n readdir(directoryPath, (error, names) => {\n if (error) {\n // https://nodejs.org/dist/latest-v13.x/docs/api/errors.html#errors_common_system_errors\n if (handleTooManyFilesOpenedError && (error.code === \"EMFILE\" || error.code === \"ENFILE\")) {\n resolve(handleTooManyFilesOpenedError(error))\n } else {\n reject(error)\n }\n } else {\n resolve(names)\n }\n })\n })\n}\n",
"export const getCommonPathname = (pathname, otherPathname) => {\n const firstDifferentCharacterIndex = findFirstDifferentCharacterIndex(pathname, otherPathname)\n\n // pathname and otherpathname are exactly the same\n if (firstDifferentCharacterIndex === -1) {\n return pathname\n }\n\n const commonString = pathname.slice(0, firstDifferentCharacterIndex + 1)\n\n // the first different char is at firstDifferentCharacterIndex\n if (pathname.charAt(firstDifferentCharacterIndex) === \"/\") {\n return commonString\n }\n\n if (otherPathname.charAt(firstDifferentCharacterIndex) === \"/\") {\n return commonString\n }\n\n const firstDifferentSlashIndex = commonString.lastIndexOf(\"/\")\n return pathname.slice(0, firstDifferentSlashIndex + 1)\n}\n\nconst findFirstDifferentCharacterIndex = (string, otherString) => {\n const maxCommonLength = Math.min(string.length, otherString.length)\n let i = 0\n while (i < maxCommonLength) {\n const char = string.charAt(i)\n const otherChar = otherString.charAt(i)\n if (char !== otherChar) {\n return i\n }\n i++\n }\n if (string.length === otherString.length) {\n return -1\n }\n // they differ at maxCommonLength\n return maxCommonLength\n}\n",
"export const pathnameToParentPathname = (pathname) => {\n const slashLastIndex = pathname.lastIndexOf(\"/\")\n if (slashLastIndex === -1) {\n return \"/\"\n }\n\n return pathname.slice(0, slashLastIndex + 1)\n}\n",
"import { getCommonPathname } from \"./internal/getCommonPathname.js\"\nimport { pathnameToParentPathname } from \"./internal/pathnameToParentPathname.js\"\n\nexport const urlToRelativeUrl = (urlArg, baseUrlArg) => {\n const url = new URL(urlArg)\n const baseUrl = new URL(baseUrlArg)\n\n if (url.protocol !== baseUrl.protocol) {\n return urlArg\n }\n\n if (url.username !== baseUrl.username || url.password !== baseUrl.password) {\n return urlArg.slice(url.protocol.length)\n }\n\n if (url.host !== baseUrl.host) {\n return urlArg.slice(url.protocol.length)\n }\n\n const { pathname, hash, search } = url\n if (pathname === \"/\") {\n return baseUrl.pathname.slice(1)\n }\n\n const { pathname: basePathname } = baseUrl\n\n const commonPathname = getCommonPathname(pathname, basePathname)\n if (!commonPathname) {\n return urlArg\n }\n\n const specificPathname = pathname.slice(commonPathname.length)\n const baseSpecificPathname = basePathname.slice(commonPathname.length)\n if (baseSpecificPathname.includes(\"/\")) {\n const baseSpecificParentPathname = pathnameToParentPathname(baseSpecificPathname)\n const relativeDirectoriesNotation = baseSpecificParentPathname.replace(/.*?\\//g, \"../\")\n return `${relativeDirectoriesNotation}${specificPathname}${search}${hash}`\n }\n return `${specificPathname}${search}${hash}`\n}\n",
"export const comparePathnames = (leftPathame, rightPathname) => {\n const leftPartArray = leftPathame.split(\"/\")\n const rightPartArray = rightPathname.split(\"/\")\n\n const leftLength = leftPartArray.length\n const rightLength = rightPartArray.length\n\n const maxLength = Math.max(leftLength, rightLength)\n let i = 0\n while (i < maxLength) {\n const leftPartExists = i in leftPartArray\n const rightPartExists = i in rightPartArray\n\n // longer comes first\n if (!leftPartExists) {\n return +1\n }\n if (!rightPartExists) {\n return -1\n }\n\n const leftPartIsLast = i === leftPartArray.length - 1\n const rightPartIsLast = i === rightPartArray.length - 1\n // folder comes first\n if (leftPartIsLast && !rightPartIsLast) {\n return +1\n }\n if (!leftPartIsLast && rightPartIsLast) {\n return -1\n }\n\n const leftPart = leftPartArray[i]\n const rightPart = rightPartArray[i]\n i++\n // local comparison comes first\n const comparison = leftPart.localeCompare(rightPart)\n if (comparison !== 0) {\n return comparison\n }\n }\n\n if (leftLength < rightLength) {\n return +1\n }\n if (leftLength > rightLength) {\n return -1\n }\n return 0\n}\n",
"import { createCancellationToken, createOperation } from \"@jsenv/cancellation\"\nimport { normalizeStructuredMetaMap, urlCanContainsMetaMatching, urlToMeta } from \"@jsenv/url-meta\"\nimport { ensureUrlTrailingSlash } from \"./internal/ensureUrlTrailingSlash.js\"\nimport { assertAndNormalizeDirectoryUrl } from \"./assertAndNormalizeDirectoryUrl.js\"\nimport { readDirectory } from \"./readDirectory.js\"\nimport { readFileSystemNodeStat } from \"./readFileSystemNodeStat.js\"\nimport { urlToRelativeUrl } from \"./urlToRelativeUrl.js\"\nimport { comparePathnames } from \"./comparePathnames.js\"\n\nexport const collectDirectoryMatchReport = async ({\n cancellationToken = createCancellationToken(),\n directoryUrl,\n structuredMetaMap,\n predicate,\n}) => {\n const matchingArray = []\n const ignoredArray = []\n\n const rootDirectoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl)\n if (typeof predicate !== \"function\") {\n throw new TypeError(`predicate must be a function, got ${predicate}`)\n }\n const structuredMetaMapNormalized = normalizeStructuredMetaMap(\n structuredMetaMap,\n rootDirectoryUrl,\n )\n\n const visitDirectory = async (directoryUrl) => {\n const directoryItems = await createOperation({\n cancellationToken,\n start: () => readDirectory(directoryUrl),\n })\n\n await Promise.all(\n directoryItems.map(async (directoryItem) => {\n const directoryChildNodeUrl = `${directoryUrl}${directoryItem}`\n const relativeUrl = urlToRelativeUrl(directoryChildNodeUrl, rootDirectoryUrl)\n\n const directoryChildNodeStats = await createOperation({\n cancellationToken,\n start: () =>\n readFileSystemNodeStat(directoryChildNodeUrl, {\n // we ignore symlink because recursively traversed\n // so symlinked file will be discovered.\n // Moreover if they lead outside of directoryPath it can become a problem\n // like infinite recursion of whatever.\n // that we could handle using an object of pathname already seen but it will be useless\n // because directoryPath is recursively traversed\n followLink: false,\n }),\n })\n\n if (directoryChildNodeStats.isDirectory()) {\n const subDirectoryUrl = `${directoryChildNodeUrl}/`\n\n if (\n !urlCanContainsMetaMatching({\n url: subDirectoryUrl,\n structuredMetaMap: structuredMetaMapNormalized,\n predicate,\n })\n ) {\n ignoredArray.push({\n relativeUrl: ensureUrlTrailingSlash(relativeUrl),\n fileStats: directoryChildNodeStats,\n })\n\n return\n }\n\n await visitDirectory(subDirectoryUrl)\n return\n }\n\n if (directoryChildNodeStats.isFile()) {\n const meta = urlToMeta({\n url: directoryChildNodeUrl,\n structuredMetaMap: structuredMetaMapNormalized,\n })\n if (!predicate(meta)) {\n ignoredArray.push({ relativeUrl, meta, fileStats: directoryChildNodeStats })\n return\n }\n\n matchingArray.push({ relativeUrl, meta, fileStats: directoryChildNodeStats })\n return\n }\n }),\n )\n }\n await visitDirectory(rootDirectoryUrl)\n\n return {\n matchingArray: sortByRelativeUrl(matchingArray),\n ignoredArray: sortByRelativeUrl(ignoredArray),\n }\n}\n\nconst sortByRelativeUrl = (array) =>\n array.sort((left, right) => {\n return comparePathnames(left.relativeUrl, right.relativeUrl)\n })\n",
"import { createCancellationToken, createOperation } from \"@jsenv/cancellation\"\nimport { normalizeStructuredMetaMap, urlCanContainsMetaMatching, urlToMeta } from \"@jsenv/url-meta\"\nimport { assertAndNormalizeDirectoryUrl } from \"./assertAndNormalizeDirectoryUrl.js\"\nimport { readDirectory } from \"./readDirectory.js\"\nimport { readFileSystemNodeStat } from \"./readFileSystemNodeStat.js\"\nimport { urlToRelativeUrl } from \"./urlToRelativeUrl.js\"\nimport { comparePathnames } from \"./comparePathnames.js\"\n\nexport const collectFiles = async ({\n cancellationToken = createCancellationToken(),\n directoryUrl,\n structuredMetaMap,\n predicate,\n matchingFileOperation = () => null,\n}) => {\n const rootDirectoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl)\n if (typeof predicate !== \"function\") {\n throw new TypeError(`predicate must be a function, got ${predicate}`)\n }\n if (typeof matchingFileOperation !== \"function\") {\n throw new TypeError(`matchingFileOperation must be a function, got ${matchingFileOperation}`)\n }\n const structuredMetaMapNormalized = normalizeStructuredMetaMap(\n structuredMetaMap,\n rootDirectoryUrl,\n )\n\n const matchingFileResultArray = []\n const visitDirectory = async (directoryUrl) => {\n const directoryItems = await createOperation({\n cancellationToken,\n start: () => readDirectory(directoryUrl),\n })\n\n await Promise.all(\n directoryItems.map(async (directoryItem) => {\n const directoryChildNodeUrl = `${directoryUrl}${directoryItem}`\n\n const directoryChildNodeStats = await createOperation({\n cancellationToken,\n start: () =>\n readFileSystemNodeStat(directoryChildNodeUrl, {\n // we ignore symlink because recursively traversed\n // so symlinked file will be discovered.\n // Moreover if they lead outside of directoryPath it can become a problem\n // like infinite recursion of whatever.\n // that we could handle using an object of pathname already seen but it will be useless\n // because directoryPath is recursively traversed\n followLink: false,\n }),\n })\n\n if (directoryChildNodeStats.isDirectory()) {\n const subDirectoryUrl = `${directoryChildNodeUrl}/`\n\n if (\n !urlCanContainsMetaMatching({\n url: subDirectoryUrl,\n structuredMetaMap: structuredMetaMapNormalized,\n predicate,\n })\n ) {\n return\n }\n\n await visitDirectory(subDirectoryUrl)\n return\n }\n\n if (directoryChildNodeStats.isFile()) {\n const meta = urlToMeta({\n url: directoryChildNodeUrl,\n structuredMetaMap: structuredMetaMapNormalized,\n })\n if (!predicate(meta)) return\n\n const relativeUrl = urlToRelativeUrl(directoryChildNodeUrl, rootDirectoryUrl)\n const operationResult = await createOperation({\n cancellationToken,\n start: () =>\n matchingFileOperation({\n cancellationToken,\n relativeUrl,\n meta,\n fileStats: directoryChildNodeStats,\n }),\n })\n matchingFileResultArray.push({\n relativeUrl,\n meta,\n fileStats: directoryChildNodeStats,\n operationResult,\n })\n return\n }\n }),\n )\n }\n await visitDirectory(rootDirectoryUrl)\n\n // When we operate on thoose files later it feels more natural\n // to perform operation in the same order they appear in the filesystem.\n // It also allow to get a predictable return value.\n // For that reason we sort matchingFileResultArray\n matchingFileResultArray.sort((leftFile, rightFile) => {\n return comparePathnames(leftFile.relativeUrl, rightFile.relativeUrl)\n })\n return matchingFileResultArray\n}\n",
"import { promises } from \"fs\"\nimport { statsToType } from \"./internal/statsToType.js\"\nimport { assertAndNormalizeDirectoryUrl } from \"./assertAndNormalizeDirectoryUrl.js\"\nimport { urlToFileSystemPath } from \"./urlToFileSystemPath.js\"\nimport { readFileSystemNodeStat } from \"./readFileSystemNodeStat.js\"\n\n// https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_fspromises_mkdir_path_options\nconst { mkdir } = promises\n\nexport const writeDirectory = async (\n destination,\n { recursive = true, allowUseless = false } = {},\n) => {\n const destinationUrl = assertAndNormalizeDirectoryUrl(destination)\n const destinationPath = urlToFileSystemPath(destinationUrl)\n\n const destinationStats = await readFileSystemNodeStat(destinationUrl, {\n nullIfNotFound: true,\n followLink: false,\n })\n\n if (destinationStats) {\n if (destinationStats.isDirectory()) {\n if (allowUseless) {\n return\n }\n throw new Error(`directory already exists at ${destinationPath}`)\n }\n\n const destinationType = statsToType(destinationStats)\n throw new Error(\n `cannot write directory at ${destinationPath} because there is a ${destinationType}`,\n )\n }\n\n try {\n await mkdir(destinationPath, { recursive })\n } catch (error) {\n if (allowUseless && error.code === \"EEXIST\") {\n return\n }\n throw error\n }\n}\n",
"export const resolveUrl = (specifier, baseUrl) => {\n if (typeof baseUrl === \"undefined\") {\n throw new TypeError(`baseUrl missing to resolve ${specifier}`)\n }\n\n return String(new URL(specifier, baseUrl))\n}\n",
"import { unlink, rmdir, openSync, closeSync } from \"fs\"\nimport { ensureUrlTrailingSlash } from \"./internal/ensureUrlTrailingSlash.js\"\nimport { assertAndNormalizeFileUrl } from \"./assertAndNormalizeFileUrl.js\"\nimport { urlToFileSystemPath } from \"./urlToFileSystemPath.js\"\nimport { readFileSystemNodeStat } from \"./readFileSystemNodeStat.js\"\nimport { readDirectory } from \"./readDirectory.js\"\nimport { resolveUrl } from \"./resolveUrl.js\"\n\nexport const removeFileSystemNode = async (\n source,\n {\n allowUseless = false,\n recursive = false,\n maxRetries = 3,\n retryDelay = 100,\n onlyContent = false,\n } = {},\n) => {\n const sourceUrl = assertAndNormalizeFileUrl(source)\n\n const sourceStats = await readFileSystemNodeStat(sourceUrl, {\n nullIfNotFound: true,\n followLink: false,\n })\n if (!sourceStats) {\n if (allowUseless) {\n return\n }\n throw new Error(`nothing to remove at ${urlToFileSystemPath(sourceUrl)}`)\n }\n\n // https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_class_fs_stats\n // FIFO and socket are ignored, not sure what they are exactly and what to do with them\n // other libraries ignore them, let's do the same.\n if (\n sourceStats.isFile() ||\n sourceStats.isSymbolicLink() ||\n sourceStats.isCharacterDevice() ||\n sourceStats.isBlockDevice()\n ) {\n await removeNonDirectory(sourceUrl.endsWith(\"/\") ? sourceUrl.slice(0, -1) : sourceUrl, {\n maxRetries,\n retryDelay,\n })\n } else if (sourceStats.isDirectory()) {\n await removeDirectory(ensureUrlTrailingSlash(sourceUrl), {\n recursive,\n maxRetries,\n retryDelay,\n onlyContent,\n })\n }\n}\n\nconst removeNonDirectory = (sourceUrl, { maxRetries, retryDelay }) => {\n const sourcePath = urlToFileSystemPath(sourceUrl)\n\n let retryCount = 0\n const attempt = () => {\n return unlinkNaive(sourcePath, {\n ...(retryCount >= maxRetries\n ? {}\n : {\n handleTemporaryError: async () => {\n retryCount++\n return new Promise((resolve) => {\n setTimeout(() => {\n resolve(attempt())\n }, retryCount * retryDelay)\n })\n },\n }),\n })\n }\n return attempt()\n}\n\nconst unlinkNaive = (sourcePath, { handleTemporaryError = null } = {}) => {\n return new Promise((resolve, reject) => {\n unlink(sourcePath, (error) => {\n if (error) {\n if (error.code === \"ENOENT\") {\n resolve()\n } else if (\n handleTemporaryError &&\n (error.code === \"EBUSY\" ||\n error.code === \"EMFILE\" ||\n error.code === \"ENFILE\" ||\n error.code === \"ENOENT\")\n ) {\n resolve(handleTemporaryError(error))\n } else {\n reject(error)\n }\n } else {\n resolve()\n }\n })\n })\n}\n\nconst removeDirectory = async (\n rootDirectoryUrl,\n { maxRetries, retryDelay, recursive, onlyContent },\n) => {\n const visit = async (sourceUrl) => {\n const sourceStats = await readFileSystemNodeStat(sourceUrl, {\n nullIfNotFound: true,\n followLink: false,\n })\n\n // file/directory not found\n if (sourceStats === null) {\n return\n }\n\n if (sourceStats.isFile() || sourceStats.isCharacterDevice() || sourceStats.isBlockDevice()) {\n await visitFile(sourceUrl)\n } else if (sourceStats.isSymbolicLink()) {\n await visitSymbolicLink(sourceUrl)\n } else if (sourceStats.isDirectory()) {\n await visitDirectory(`${sourceUrl}/`)\n }\n }\n\n const visitDirectory = async (directoryUrl) => {\n const directoryPath = urlToFileSystemPath(directoryUrl)\n const optionsFromRecursive = recursive\n ? {\n handleNotEmptyError: async () => {\n await removeDirectoryContent(directoryUrl)\n await visitDirectory(directoryUrl)\n },\n }\n : {}\n await removeDirectoryNaive(directoryPath, {\n ...optionsFromRecursive,\n // Workaround for https://github.com/joyent/node/issues/4337\n ...(process.platform === \"win32\"\n ? {\n handlePermissionError: async (error) => {\n console.error(`trying to fix windows EPERM after readir on ${directoryPath}`)\n\n let openOrCloseError\n try {\n const fd = openSync(directoryPath)\n closeSync(fd)\n } catch (e) {\n openOrCloseError = e\n }\n\n if (openOrCloseError) {\n if (openOrCloseError.code === \"ENOENT\") {\n return\n }\n console.error(\n `error while trying to fix windows EPERM after readir on ${directoryPath}: ${openOrCloseError.stack}`,\n )\n throw error\n }\n\n await removeDirectoryNaive(directoryPath, { ...optionsFromRecursive })\n },\n }\n : {}),\n })\n }\n\n const removeDirectoryContent = async (directoryUrl) => {\n const names = await readDirectory(directoryUrl)\n await Promise.all(\n names.map(async (name) => {\n const url = resolveUrl(name, directoryUrl)\n await visit(url)\n }),\n )\n }\n\n const visitFile = async (fileUrl) => {\n await removeNonDirectory(fileUrl, { maxRetries, retryDelay })\n }\n\n const visitSymbolicLink = async (symbolicLinkUrl) => {\n await removeNonDirectory(symbolicLinkUrl, { maxRetries, retryDelay })\n }\n\n if (onlyContent) {\n await removeDirectoryContent(rootDirectoryUrl)\n } else {\n await visitDirectory(rootDirectoryUrl)\n }\n}\n\nconst removeDirectoryNaive = (\n directoryPath,\n { handleNotEmptyError = null, handlePermissionError = null } = {},\n) => {\n return new Promise((resolve, reject) => {\n rmdir(directoryPath, (error, lstatObject) => {\n if (error) {\n if (handlePermissionError && error.code === \"EPERM\") {\n resolve(handlePermissionError(error))\n } else if (error.code === \"ENOENT\") {\n resolve()\n } else if (\n handleNotEmptyError &&\n // linux os\n (error.code === \"ENOTEMPTY\" ||\n // SunOS\n error.code === \"EEXIST\")\n ) {\n resolve(handleNotEmptyError(error))\n } else {\n reject(error)\n }\n } else {\n resolve(lstatObject)\n }\n })\n })\n}\n",
"import { statsToType } from \"./internal/statsToType.js\"\nimport { assertAndNormalizeFileUrl } from \"./assertAndNormalizeFileUrl.js\"\nimport { urlToFileSystemPath } from \"./urlToFileSystemPath.js\"\nimport { writeDirectory } from \"./writeDirectory.js\"\nimport { readFileSystemNodeStat } from \"./readFileSystemNodeStat.js\"\nimport { removeFileSystemNode } from \"./removeFileSystemNode.js\"\n\nexport const ensureEmptyDirectory = async (source) => {\n const stats = await readFileSystemNodeStat(source, { nullIfNotFound: true, followLink: false })\n if (stats === null) {\n // if there is nothing, create a directory\n return writeDirectory(source, { allowUseless: true })\n }\n if (stats.isDirectory()) {\n // if there is a directory remove its content and done\n return removeFileSystemNode(source, {\n allowUseless: true,\n recursive: true,\n onlyContent: true,\n })\n }\n\n const sourceType = statsToType(stats)\n const sourcePath = urlToFileSystemPath(assertAndNormalizeFileUrl(source))\n throw new Error(\n `ensureEmptyDirectory expect directory at ${sourcePath}, found ${sourceType} instead`,\n )\n}\n",
"import { dirname } from \"path\"\nimport { assertAndNormalizeFileUrl } from \"./assertAndNormalizeFileUrl.js\"\nimport { urlToFileSystemPath } from \"./urlToFileSystemPath.js\"\nimport { writeDirectory } from \"./writeDirectory.js\"\n\nexport const ensureParentDirectories = async (destination) => {\n const destinationUrl = assertAndNormalizeFileUrl(destination)\n const destinationPath = urlToFileSystemPath(destinationUrl)\n const destinationParentPath = dirname(destinationPath)\n\n return writeDirectory(destinationParentPath, { recursive: true, allowUseless: true })\n}\n",
"import { fileSystemPathToUrl } from \"./fileSystemPathToUrl.js\"\n\nconst isWindows = process.platform === \"win32\"\nconst baseUrlFallback = fileSystemPathToUrl(process.cwd())\n\n/**\n * Some url might be resolved or remapped to url without the windows drive letter.\n * For instance\n * new URL('/foo.js', 'file:///C:/dir/file.js')\n * resolves to\n * 'file:///foo.js'\n *\n * But on windows it becomes a problem because we need the drive letter otherwise\n * url cannot be converted to a filesystem path.\n *\n * ensureWindowsDriveLetter ensure a resolved url still contains the drive letter.\n */\n\nexport const ensureWindowsDriveLetter = (url, baseUrl) => {\n try {\n url = String(new URL(url))\n } catch (e) {\n throw new Error(`absolute url expected but got ${url}`)\n }\n\n if (!isWindows) {\n return url\n }\n\n try {\n baseUrl = String(new URL(baseUrl))\n } catch (e) {\n throw new Error(\n `absolute baseUrl expected but got ${baseUrl} to ensure windows drive letter on ${url}`,\n )\n }\n\n if (!url.startsWith(\"file://\")) {\n return url\n }\n const afterProtocol = url.slice(\"file://\".length)\n // we still have the windows drive letter\n if (extractDriveLetter(afterProtocol)) {\n return url\n }\n\n // drive letter was lost, restore it\n const baseUrlOrFallback = baseUrl.startsWith(\"file://\") ? baseUrl : baseUrlFallback\n const driveLetter = extractDriveLetter(baseUrlOrFallback.slice(\"file://\".length))\n if (!driveLetter) {\n throw new Error(\n `drive letter expected on baseUrl but got ${baseUrl} to ensure windows drive letter on ${url}`,\n )\n }\n return `file:///${driveLetter}:${afterProtocol}`\n}\n\nconst extractDriveLetter = (ressource) => {\n // we still have the windows drive letter\n if (/[a-zA-Z]/.test(ressource[1]) && ressource[2] === \":\") {\n return ressource[1]\n }\n return null\n}\n",
"export const urlTargetsSameFileSystemPath = (leftUrl, rightUrl) => {\n if (leftUrl.endsWith(\"/\")) leftUrl = leftUrl.slice(0, -1)\n if (rightUrl.endsWith(\"/\")) rightUrl = rightUrl.slice(0, -1)\n return leftUrl === rightUrl\n}\n",
"import { utimes } from \"fs\"\nimport { assertAndNormalizeFileUrl } from \"./assertAndNormalizeFileUrl.js\"\nimport { urlToFileSystemPath } from \"./urlToFileSystemPath.js\"\n\nexport const writeFileSystemNodeModificationTime = (source, mtime) => {\n const sourceUrl = assertAndNormalizeFileUrl(source)\n const sourcePath = urlToFileSystemPath(sourceUrl)\n const mtimeValue = typeof mtime === \"number\" ? new Date(Math.floor(mtime)) : mtime\n // reading atime mutates its value so there is no use case I can think of\n // where we want to modify it\n const atimeValue = mtimeValue\n\n return new Promise((resolve, reject) => {\n utimes(sourcePath, atimeValue, mtimeValue, (error) => {\n if (error) {\n reject(error)\n } else {\n resolve()\n }\n })\n })\n}\n",
"export const replaceBackSlashesWithSlashes = (string) => string.replace(/\\\\/g, \"/\")\n",
"import { readlink } from \"fs\"\nimport { replaceBackSlashesWithSlashes } from \"./internal/replaceBackSlashesWithSlashes.js\"\nimport { assertAndNormalizeFileUrl } from \"./assertAndNormalizeFileUrl.js\"\nimport { urlToFileSystemPath } from \"./urlToFileSystemPath.js\"\nimport { fileSystemPathToUrl } from \"./fileSystemPathToUrl.js\"\nimport { isFileSystemPath } from \"./isFileSystemPath.js\"\n\nexport const readSymbolicLink = (url) => {\n const symbolicLinkUrl = assertAndNormalizeFileUrl(url)\n const symbolicLinkPath = urlToFileSystemPath(symbolicLinkUrl)\n\n return new Promise((resolve, reject) => {\n readlink(symbolicLinkPath, (error, resolvedPath) => {\n if (error) {\n reject(error)\n } else {\n resolve(\n isFileSystemPath(resolvedPath)\n ? fileSystemPathToUrl(resolvedPath)\n : replaceBackSlashesWithSlashes(resolvedPath),\n )\n }\n })\n })\n}\n",
"import { promises } from \"fs\"\nimport { assertAndNormalizeFileUrl } from \"./assertAndNormalizeFileUrl.js\"\nimport { urlToFileSystemPath } from \"./urlToFileSystemPath.js\"\nimport { resolveUrl } from \"./resolveUrl.js\"\nimport { isFileSystemPath } from \"./isFileSystemPath.js\"\nimport { ensureParentDirectories } from \"./ensureParentDirectories.js\"\nimport { readFileSystemNodeStat } from \"./readFileSystemNodeStat.js\"\n\n// https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_fspromises_symlink_target_path_type\nconst { symlink } = promises\nconst isWindows = process.platform === \"win32\"\n\nexport const writeSymbolicLink = async (destination, target, { type } = {}) => {\n const destinationUrl = assertAndNormalizeFileUrl(destination)\n\n let targetValue\n if (typeof target === \"string\") {\n // absolute filesystem path\n if (isFileSystemPath(target)) {\n targetValue = target\n }\n // relative url\n else if (target.startsWith(\"./\") || target.startsWith(\"../\")) {\n targetValue = target\n }\n // absolute url\n else {\n const targetUrl = String(new URL(target, destinationUrl))\n targetValue = urlToFileSystemPath(targetUrl)\n }\n } else if (target instanceof URL) {\n targetValue = urlToFileSystemPath(target)\n } else {\n throw new TypeError(`symbolic link target must be a string or an url, received ${target}`)\n }\n\n if (isWindows && typeof type === \"undefined\") {\n // without this if you write a symbolic link without specifying the type on windows\n // you later get EPERM when doing stat on the symlink\n const targetUrl = resolveUrl(targetValue, destinationUrl)\n const targetStats = await readFileSystemNodeStat(targetUrl, { nullIfNotFound: true })\n type = targetStats && targetStats.isDirectory() ? \"dir\" : \"file\"\n }\n\n const symbolicLinkPath = urlToFileSystemPath(destinationUrl)\n try {\n await symlink(targetValue, symbolicLinkPath, type)\n } catch (error) {\n if (error.code === \"ENOENT\") {\n await ensureParentDirectories(destinationUrl)\n await symlink(targetValue, symbolicLinkPath, type)\n return\n }\n throw error\n }\n}\n",
"export const urlIsInsideOf = (urlValue, otherUrlValue) => {\n const url = new URL(urlValue)\n const otherUrl = new URL(otherUrlValue)\n\n if (url.origin !== otherUrl.origin) {\n return false\n }\n\n const urlPathname = url.pathname\n const otherUrlPathname = otherUrl.pathname\n if (urlPathname === otherUrlPathname) {\n return false\n }\n\n return urlPathname.startsWith(otherUrlPathname)\n}\n",
"/* eslint-disable import/max-dependencies */\nimport { copyFile as copyFileNode } from \"fs\"\nimport { urlTargetsSameFileSystemPath } from \"./internal/urlTargetsSameFileSystemPath.js\"\nimport { statsToType } from \"./internal/statsToType.js\"\nimport { ensureUrlTrailingSlash } from \"./internal/ensureUrlTrailingSlash.js\"\nimport { resolveUrl } from \"./resolveUrl.js\"\nimport { binaryFlagsToPermissions } from \"./internal/permissions.js\"\nimport { assertAndNormalizeFileUrl } from \"./assertAndNormalizeFileUrl.js\"\nimport { writeDirectory } from \"./writeDirectory.js\"\nimport { urlToRelativeUrl } from \"./urlToRelativeUrl.js\"\nimport { readFileSystemNodeStat } from \"./readFileSystemNodeStat.js\"\nimport { ensureParentDirectories } from \"./ensureParentDirectories.js\"\nimport { writeFileSystemNodePermissions } from \"./writeFileSystemNodePermissions.js\"\nimport { writeFileSystemNodeModificationTime } from \"./writeFileSystemNodeModificationTime.js\"\nimport { readDirectory } from \"./readDirectory.js\"\nimport { readSymbolicLink } from \"./readSymbolicLink.js\"\nimport { writeSymbolicLink } from \"./writeSymbolicLink.js\"\nimport { urlIsInsideOf } from \"./urlIsInsideOf.js\"\nimport { removeFileSystemNode } from \"./removeFileSystemNode.js\"\nimport { urlToFileSystemPath } from \"./urlToFileSystemPath.js\"\n\nexport const copyFileSystemNode = async (\n source,\n destination,\n {\n overwrite = false,\n preserveStat = true,\n preserveMtime = preserveStat,\n preservePermissions = preserveStat,\n allowUseless = false,\n followLink = true,\n } = {},\n) => {\n const sourceUrl = assertAndNormalizeFileUrl(source)\n let destinationUrl = assertAndNormalizeFileUrl(destination)\n const sourcePath = urlToFileSystemPath(sourceUrl)\n\n const sourceStats = await readFileSystemNodeStat(sourceUrl, {\n nullIfNotFound: true,\n followLink: false,\n })\n if (!sourceStats) {\n throw new Error(`nothing to copy at ${sourcePath}`)\n }\n\n let destinationStats = await readFileSystemNodeStat(destinationUrl, {\n nullIfNotFound: true,\n // we force false here but in fact we will follow the destination link\n // to know where we will actually move and detect useless move overrite etc..\n followLink: false,\n })\n\n if (followLink && destinationStats && destinationStats.isSymbolicLink()) {\n const target = await readSymbolicLink(destinationUrl)\n destinationUrl = resolveUrl(target, destinationUrl)\n destinationStats = await readFileSystemNodeStat(destinationUrl, { nullIfNotFound: true })\n }\n const destinationPath = urlToFileSystemPath(destinationUrl)\n\n if (urlTargetsSameFileSystemPath(sourceUrl, destinationUrl)) {\n if (allowUseless) {\n return\n }\n throw new Error(`cannot copy ${sourcePath} because destination and source are the same`)\n }\n\n if (destinationStats) {\n const sourceType = statsToType(sourceStats)\n const destinationType = statsToType(destinationStats)\n\n if (sourceType !== destinationType) {\n throw new Error(\n `cannot copy ${sourceType} from ${sourcePath} to ${destinationPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`,\n )\n }\n if (!overwrite) {\n throw new Error(\n `cannot copy ${sourceType} from ${sourcePath} to ${destinationPath} because destination exists and overwrite option is disabled`,\n )\n }\n\n // remove file, link