@jsenv/filesystem
Advanced tools
Comparing version 1.0.0 to 2.0.0
{ | ||
"name": "@jsenv/filesystem", | ||
"version": "1.0.0", | ||
"version": "2.0.0", | ||
"description": "Collection of functions to interact with filesystem in Node.js", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -6,3 +6,3 @@ # Jsenv filesystem | ||
[![npm package](https://img.shields.io/npm/v/@jsenv/filesystem.svg?logo=npm&label=package)](https://www.npmjs.com/package/@jsenv/filesystem) | ||
[![github main](https://github.com/jsenv/filesystem/workflows/main/badge.svg)](https://github.com/jsenv/jsenv-filesystem/actions?workflow=main) | ||
[![github main](https://github.com/jsenv/filesystem/workflows/main/badge.svg)](https://github.com/jsenv/filesystem/actions?workflow=main) | ||
[![codecov coverage](https://codecov.io/gh/jsenv/filesystem/branch/main/graph/badge.svg)](https://codecov.io/gh/jsenv/filesystem) | ||
@@ -9,0 +9,0 @@ |
/* eslint-disable import/max-dependencies */ | ||
import { copyFile as copyFileNode } from "fs" | ||
import { copyFile as copyFileNode } from "node:fs" | ||
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js" | ||
@@ -22,19 +23,18 @@ import { statsToType } from "./internal/statsToType.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) | ||
export const copyFileSystemNode = async ({ | ||
from, | ||
to, | ||
overwrite = false, | ||
preserveStat = true, | ||
preserveMtime = preserveStat, | ||
preservePermissions = preserveStat, | ||
allowUseless = false, | ||
followLink = true, | ||
}) => { | ||
const fromUrl = assertAndNormalizeFileUrl(from) | ||
const fromPath = urlToFileSystemPath(fromUrl) | ||
let toUrl = assertAndNormalizeFileUrl(to) | ||
let toPath = urlToFileSystemPath(toUrl) | ||
const sourceStats = await readFileSystemNodeStat(sourceUrl, { | ||
const sourceStats = await readFileSystemNodeStat(fromUrl, { | ||
nullIfNotFound: true, | ||
@@ -44,6 +44,6 @@ followLink: false, | ||
if (!sourceStats) { | ||
throw new Error(`nothing to copy at ${sourcePath}`) | ||
throw new Error(`nothing to copy at ${fromPath}`) | ||
} | ||
let destinationStats = await readFileSystemNodeStat(destinationUrl, { | ||
let destinationStats = await readFileSystemNodeStat(toUrl, { | ||
nullIfNotFound: true, | ||
@@ -56,13 +56,13 @@ // we force false here but in fact we will follow the destination link | ||
if (followLink && destinationStats && destinationStats.isSymbolicLink()) { | ||
const target = await readSymbolicLink(destinationUrl) | ||
destinationUrl = resolveUrl(target, destinationUrl) | ||
destinationStats = await readFileSystemNodeStat(destinationUrl, { nullIfNotFound: true }) | ||
const linkTarget = await readSymbolicLink(toUrl) | ||
toUrl = resolveUrl(linkTarget, toUrl) | ||
toPath = urlToFileSystemPath(toUrl) | ||
destinationStats = await readFileSystemNodeStat(toUrl, { nullIfNotFound: true }) | ||
} | ||
const destinationPath = urlToFileSystemPath(destinationUrl) | ||
if (urlTargetsSameFileSystemPath(sourceUrl, destinationUrl)) { | ||
if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) { | ||
if (allowUseless) { | ||
return | ||
} | ||
throw new Error(`cannot copy ${sourcePath} because destination and source are the same`) | ||
throw new Error(`cannot copy ${fromPath} because destination and source are the same`) | ||
} | ||
@@ -76,3 +76,3 @@ | ||
throw new Error( | ||
`cannot copy ${sourceType} from ${sourcePath} to ${destinationPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`, | ||
`cannot copy ${sourceType} from ${fromPath} to ${toPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`, | ||
) | ||
@@ -82,3 +82,3 @@ } | ||
throw new Error( | ||
`cannot copy ${sourceType} from ${sourcePath} to ${destinationPath} because destination exists and overwrite option is disabled`, | ||
`cannot copy ${sourceType} from ${fromPath} to ${toPath} because destination exists and overwrite option is disabled`, | ||
) | ||
@@ -88,9 +88,9 @@ } | ||
// remove file, link, directory... | ||
await removeFileSystemNode(destinationUrl, { recursive: true, allowUseless: true }) | ||
await removeFileSystemNode(toUrl, { recursive: true, allowUseless: true }) | ||
} else { | ||
await ensureParentDirectories(destinationUrl) | ||
await ensureParentDirectories(toUrl) | ||
} | ||
if (sourceStats.isDirectory()) { | ||
destinationUrl = ensureUrlTrailingSlash(destinationUrl) | ||
toUrl = ensureUrlTrailingSlash(toUrl) | ||
} | ||
@@ -109,4 +109,4 @@ | ||
const visitFile = async (fileUrl, fileStats) => { | ||
const fileRelativeUrl = urlToRelativeUrl(fileUrl, sourceUrl) | ||
const fileCopyUrl = resolveUrl(fileRelativeUrl, destinationUrl) | ||
const fileRelativeUrl = urlToRelativeUrl(fileUrl, fromUrl) | ||
const fileCopyUrl = resolveUrl(fileRelativeUrl, toUrl) | ||
@@ -118,3 +118,3 @@ await copyFileContentNaive(urlToFileSystemPath(fileUrl), urlToFileSystemPath(fileCopyUrl)) | ||
const visitSymbolicLink = async (symbolicLinkUrl) => { | ||
const symbolicLinkRelativeUrl = urlToRelativeUrl(symbolicLinkUrl, sourceUrl) | ||
const symbolicLinkRelativeUrl = urlToRelativeUrl(symbolicLinkUrl, fromUrl) | ||
const symbolicLinkTarget = await readSymbolicLink(symbolicLinkUrl) | ||
@@ -126,11 +126,11 @@ const symbolicLinkTargetUrl = resolveUrl(symbolicLinkTarget, symbolicLinkUrl) | ||
let symbolicLinkCopyTarget | ||
if (symbolicLinkTargetUrl === sourceUrl) { | ||
symbolicLinkCopyTarget = linkIsRelative ? symbolicLinkTarget : destinationUrl | ||
} else if (urlIsInsideOf(symbolicLinkTargetUrl, sourceUrl)) { | ||
if (symbolicLinkTargetUrl === fromUrl) { | ||
symbolicLinkCopyTarget = linkIsRelative ? symbolicLinkTarget : toUrl | ||
} else if (urlIsInsideOf(symbolicLinkTargetUrl, fromUrl)) { | ||
// symbolic link targets something inside the directory we want to copy | ||
// reflects it inside the copied directory structure | ||
const linkCopyTargetRelative = urlToRelativeUrl(symbolicLinkTargetUrl, sourceUrl) | ||
const linkCopyTargetRelative = urlToRelativeUrl(symbolicLinkTargetUrl, fromUrl) | ||
symbolicLinkCopyTarget = linkIsRelative | ||
? `./${linkCopyTargetRelative}` | ||
: resolveUrl(linkCopyTargetRelative, destinationUrl) | ||
: resolveUrl(linkCopyTargetRelative, toUrl) | ||
} else { | ||
@@ -150,14 +150,18 @@ // symbolic link targets something outside the directory we want to copy | ||
const symbolicLinkCopyUrl = resolveUrl(symbolicLinkRelativeUrl, destinationUrl) | ||
await writeSymbolicLink(symbolicLinkCopyUrl, symbolicLinkCopyTarget, { type: linkType }) | ||
const symbolicLinkCopyUrl = resolveUrl(symbolicLinkRelativeUrl, toUrl) | ||
await writeSymbolicLink({ | ||
from: symbolicLinkCopyUrl, | ||
to: symbolicLinkCopyTarget, | ||
type: linkType, | ||
}) | ||
} | ||
const copyStats = async (destinationUrl, stats) => { | ||
const copyStats = async (toUrl, stats) => { | ||
if (preservePermissions || preserveMtime) { | ||
const { mode, mtimeMs } = stats | ||
if (preservePermissions) { | ||
await writeFileSystemNodePermissions(destinationUrl, binaryFlagsToPermissions(mode)) | ||
await writeFileSystemNodePermissions(toUrl, binaryFlagsToPermissions(mode)) | ||
} | ||
if (preserveMtime) { | ||
await writeFileSystemNodeModificationTime(destinationUrl, mtimeMs) | ||
await writeFileSystemNodeModificationTime(toUrl, mtimeMs) | ||
} | ||
@@ -168,4 +172,4 @@ } | ||
const visitDirectory = async (directoryUrl, directoryStats) => { | ||
const directoryRelativeUrl = urlToRelativeUrl(directoryUrl, sourceUrl) | ||
const directoryCopyUrl = resolveUrl(directoryRelativeUrl, destinationUrl) | ||
const directoryRelativeUrl = urlToRelativeUrl(directoryUrl, fromUrl) | ||
const directoryCopyUrl = resolveUrl(directoryRelativeUrl, toUrl) | ||
@@ -188,3 +192,3 @@ await writeDirectory(directoryCopyUrl) | ||
await visit(sourceUrl, sourceStats) | ||
await visit(fromUrl, sourceStats) | ||
} | ||
@@ -191,0 +195,0 @@ |
@@ -6,3 +6,6 @@ export const isFileSystemPath = (value) => { | ||
if (value[0] === "/") return true | ||
if (value[0] === "/") { | ||
return true | ||
} | ||
return startsWithWindowsDriveLetter(value) | ||
@@ -9,0 +12,0 @@ } |
@@ -12,12 +12,9 @@ import { assertAndNormalizeDirectoryUrl } from "./assertAndNormalizeDirectoryUrl.js" | ||
export const moveDirectoryContent = async ( | ||
source, | ||
destination, | ||
{ overwrite, followLink = true } = {}, | ||
) => { | ||
const sourceUrl = assertAndNormalizeDirectoryUrl(source) | ||
let destinationUrl = assertAndNormalizeDirectoryUrl(destination) | ||
const sourcePath = urlToFileSystemPath(sourceUrl) | ||
export const moveDirectoryContent = async ({ from, to, overwrite, followLink = true } = {}) => { | ||
const fromUrl = assertAndNormalizeDirectoryUrl(from) | ||
const fromPath = urlToFileSystemPath(fromUrl) | ||
let toUrl = assertAndNormalizeDirectoryUrl(to) | ||
let toPath = urlToFileSystemPath(toUrl) | ||
const sourceStats = await readFileSystemNodeStat(sourceUrl, { | ||
const sourceStats = await readFileSystemNodeStat(fromUrl, { | ||
nullIfNotFound: true, | ||
@@ -27,10 +24,10 @@ followLink: false, | ||
if (!sourceStats) { | ||
throw new Error(`no directory to move content from at ${sourcePath}`) | ||
throw new Error(`no directory to move content from at ${fromPath}`) | ||
} | ||
if (!sourceStats.isDirectory()) { | ||
const sourceType = statsToType(sourceStats) | ||
throw new Error(`found a ${sourceType} instead of a directory at ${sourcePath}`) | ||
throw new Error(`found a ${sourceType} instead of a directory at ${fromPath}`) | ||
} | ||
let destinationStats = await readFileSystemNodeStat(destinationUrl, { | ||
let destinationStats = await readFileSystemNodeStat(toUrl, { | ||
nullIfNotFound: true, | ||
@@ -42,31 +39,35 @@ // we force false here but in fact we will follow the destination link | ||
if (followLink && destinationStats && destinationStats.isSymbolicLink()) { | ||
const target = await readSymbolicLink(destinationUrl) | ||
destinationUrl = resolveUrl(target, destinationUrl) | ||
destinationStats = await readFileSystemNodeStat(destinationUrl, { nullIfNotFound: true }) | ||
const linkTarget = await readSymbolicLink(toUrl) | ||
toUrl = resolveUrl(linkTarget, toUrl) | ||
toPath = urlToFileSystemPath(toUrl) | ||
destinationStats = await readFileSystemNodeStat(toUrl, { nullIfNotFound: true }) | ||
} | ||
const destinationPath = urlToFileSystemPath(destinationUrl) | ||
if (destinationStats === null) { | ||
throw new Error(`no directory to move content into at ${destinationPath}`) | ||
throw new Error(`no directory to move content into at ${toPath}`) | ||
} | ||
if (!destinationStats.isDirectory()) { | ||
const destinationType = statsToType(destinationStats) | ||
throw new Error( | ||
`destination leads to a ${destinationType} instead of a directory at ${destinationPath}`, | ||
) | ||
throw new Error(`destination leads to a ${destinationType} instead of a directory at ${toPath}`) | ||
} | ||
if (urlTargetsSameFileSystemPath(sourceUrl, destinationUrl)) { | ||
if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) { | ||
throw new Error( | ||
`cannot move directory content, source and destination are the same (${sourcePath})`, | ||
`cannot move directory content, source and destination are the same (${fromPath})`, | ||
) | ||
} | ||
const directoryEntries = await readDirectory(sourceUrl) | ||
const directoryEntries = await readDirectory(fromUrl) | ||
await Promise.all( | ||
directoryEntries.map(async (directoryEntry) => { | ||
const from = resolveUrl(directoryEntry, sourceUrl) | ||
const to = resolveUrl(directoryEntry, destinationUrl) | ||
await moveFileSystemNode(from, to, { overwrite, followLink }) | ||
const from = resolveUrl(directoryEntry, fromUrl) | ||
const to = resolveUrl(directoryEntry, toUrl) | ||
await moveFileSystemNode({ | ||
from, | ||
to, | ||
overwrite, | ||
followLink, | ||
}) | ||
}), | ||
) | ||
} |
/* eslint-disable import/max-dependencies */ | ||
import { rename } from "fs" | ||
import { rename } from "node:fs" | ||
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js" | ||
@@ -14,12 +15,15 @@ import { statsToType } from "./internal/statsToType.js" | ||
export const moveFileSystemNode = async ( | ||
source, | ||
destination, | ||
{ overwrite = false, allowUseless = false, followLink = true } = {}, | ||
) => { | ||
const sourceUrl = assertAndNormalizeFileUrl(source) | ||
let destinationUrl = assertAndNormalizeFileUrl(destination) | ||
const sourcePath = urlToFileSystemPath(sourceUrl) | ||
export const moveFileSystemNode = async ({ | ||
from, | ||
to, | ||
overwrite = false, | ||
allowUseless = false, | ||
followLink = true, | ||
}) => { | ||
const fromUrl = assertAndNormalizeFileUrl(from) | ||
const fromPath = urlToFileSystemPath(fromUrl) | ||
let toUrl = assertAndNormalizeFileUrl(to) | ||
let toPath = urlToFileSystemPath(toUrl) | ||
const sourceStats = await readFileSystemNodeStat(sourceUrl, { | ||
const sourceStats = await readFileSystemNodeStat(fromUrl, { | ||
nullIfNotFound: true, | ||
@@ -29,6 +33,6 @@ followLink: false, | ||
if (!sourceStats) { | ||
throw new Error(`nothing to move from ${sourcePath}`) | ||
throw new Error(`nothing to move from ${fromPath}`) | ||
} | ||
let destinationStats = await readFileSystemNodeStat(destinationUrl, { | ||
let destinationStats = await readFileSystemNodeStat(toUrl, { | ||
nullIfNotFound: true, | ||
@@ -41,13 +45,13 @@ // we force false here but in fact we will follow the destination link | ||
if (followLink && destinationStats && destinationStats.isSymbolicLink()) { | ||
const target = await readSymbolicLink(destinationUrl) | ||
destinationUrl = resolveUrl(target, destinationUrl) | ||
destinationStats = await readFileSystemNodeStat(destinationUrl, { nullIfNotFound: true }) | ||
const linkTarget = await readSymbolicLink(toUrl) | ||
toUrl = resolveUrl(linkTarget, toUrl) | ||
toPath = urlToFileSystemPath(toUrl) | ||
destinationStats = await readFileSystemNodeStat(toUrl, { nullIfNotFound: true }) | ||
} | ||
const destinationPath = urlToFileSystemPath(destinationUrl) | ||
if (urlTargetsSameFileSystemPath(sourceUrl, destinationUrl)) { | ||
if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) { | ||
if (allowUseless) { | ||
return | ||
} | ||
throw new Error(`no move needed for ${sourcePath} because destination and source are the same`) | ||
throw new Error(`no move needed for ${fromPath} because destination and source are the same`) | ||
} | ||
@@ -61,3 +65,3 @@ | ||
throw new Error( | ||
`cannot move ${sourceType} from ${sourcePath} to ${destinationPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`, | ||
`cannot move ${sourceType} from ${fromPath} to ${toPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`, | ||
) | ||
@@ -67,3 +71,3 @@ } | ||
throw new Error( | ||
`cannot move ${sourceType} from ${sourcePath} to ${destinationPath} because destination exists and overwrite option is disabled`, | ||
`cannot move ${sourceType} from ${fromPath} to ${toPath} because destination exists and overwrite option is disabled`, | ||
) | ||
@@ -73,11 +77,15 @@ } | ||
// remove file, link, directory... | ||
await removeFileSystemNode(destinationUrl, { recursive: true }) | ||
await removeFileSystemNode(toUrl, { recursive: true }) | ||
} else { | ||
await ensureParentDirectories(destinationUrl) | ||
await ensureParentDirectories(toUrl) | ||
} | ||
await moveNaive(sourcePath, destinationPath, { | ||
await moveNaive(fromPath, toPath, { | ||
handleCrossDeviceError: async () => { | ||
await copyFileSystemNode(sourceUrl, destinationUrl, { preserveStat: true }) | ||
await removeFileSystemNode(sourceUrl, { recursive: true }) | ||
await copyFileSystemNode({ | ||
from: fromUrl, | ||
to: toUrl, | ||
preserveStat: true, | ||
}) | ||
await removeFileSystemNode(fromUrl, { recursive: true }) | ||
}, | ||
@@ -87,5 +95,5 @@ }) | ||
const moveNaive = (sourcePath, destinationPath, { handleCrossDeviceError = null } = {}) => { | ||
const moveNaive = (fromPath, destinationPath, { handleCrossDeviceError = null } = {}) => { | ||
return new Promise((resolve, reject) => { | ||
rename(sourcePath, destinationPath, (error) => { | ||
rename(fromPath, destinationPath, (error) => { | ||
if (error) { | ||
@@ -92,0 +100,0 @@ if (handleCrossDeviceError && error.code === "EXDEV") { |
@@ -1,11 +0,11 @@ | ||
export const urlIsInsideOf = (urlValue, otherUrlValue) => { | ||
const url = new URL(urlValue) | ||
const otherUrl = new URL(otherUrlValue) | ||
export const urlIsInsideOf = (url, otherUrl) => { | ||
const urlObject = new URL(url) | ||
const otherUrlObject = new URL(otherUrl) | ||
if (url.origin !== otherUrl.origin) { | ||
if (urlObject.origin !== otherUrlObject.origin) { | ||
return false | ||
} | ||
const urlPathname = url.pathname | ||
const otherUrlPathname = otherUrl.pathname | ||
const urlPathname = urlObject.pathname | ||
const otherUrlPathname = otherUrlObject.pathname | ||
if (urlPathname === otherUrlPathname) { | ||
@@ -15,3 +15,4 @@ return false | ||
return urlPathname.startsWith(otherUrlPathname) | ||
const isInside = urlPathname.startsWith(otherUrlPathname) | ||
return isInside | ||
} |
import { fileURLToPath } from "url" | ||
export const urlToFileSystemPath = (fileUrl) => { | ||
if (fileUrl[fileUrl.length - 1] === "/") { | ||
export const urlToFileSystemPath = (url) => { | ||
let urlString = String(url) | ||
if (urlString[urlString.length - 1] === "/") { | ||
// remove trailing / so that nodejs path becomes predictable otherwise it logs | ||
// the trailing slash on linux but does not on windows | ||
fileUrl = fileUrl.slice(0, -1) | ||
urlString = urlString.slice(0, -1) | ||
} | ||
const fileSystemPath = fileURLToPath(fileUrl) | ||
const fileSystemPath = fileURLToPath(urlString) | ||
return fileSystemPath | ||
} |
export const urlToOrigin = (url) => { | ||
if (url.startsWith("file://")) { | ||
const urlString = String(url) | ||
if (urlString.startsWith("file://")) { | ||
return `file://` | ||
} | ||
return new URL(url).origin | ||
return new URL(urlString).origin | ||
} |
@@ -8,3 +8,4 @@ import { urlToOrigin } from "./urlToOrigin.js" | ||
if (slashLastIndex === -1) { | ||
return url | ||
const urlAsString = String(url) | ||
return urlAsString | ||
} | ||
@@ -16,12 +17,14 @@ | ||
if (slashPreviousIndex === -1) { | ||
return url | ||
const urlAsString = String(url) | ||
return urlAsString | ||
} | ||
const origin = urlToOrigin(url) | ||
return `${origin}${ressource.slice(0, slashPreviousIndex + 1)}` | ||
const parentUrl = `${origin}${ressource.slice(0, slashPreviousIndex + 1)}` | ||
return parentUrl | ||
} | ||
const origin = urlToOrigin(url) | ||
return `${origin}${ressource.slice(0, slashLastIndex + 1)}` | ||
const parentUrl = `${origin}${ressource.slice(0, slashLastIndex + 1)}` | ||
return parentUrl | ||
} |
import { urlToRessource } from "./urlToRessource.js" | ||
export const urlToPathname = (urlString) => { | ||
const ressource = urlToRessource(urlString) | ||
export const urlToPathname = (url) => { | ||
const ressource = urlToRessource(url) | ||
const pathname = ressourceToPathname(ressource) | ||
@@ -6,0 +6,0 @@ return pathname |
import { getCommonPathname } from "./internal/getCommonPathname.js" | ||
import { pathnameToParentPathname } from "./internal/pathnameToParentPathname.js" | ||
export const urlToRelativeUrl = (urlArg, baseUrlArg) => { | ||
const url = new URL(urlArg) | ||
const baseUrl = new URL(baseUrlArg) | ||
export const urlToRelativeUrl = (url, baseUrl) => { | ||
const urlObject = new URL(url) | ||
const baseUrlObject = new URL(baseUrl) | ||
if (url.protocol !== baseUrl.protocol) { | ||
return urlArg | ||
if (urlObject.protocol !== baseUrlObject.protocol) { | ||
const urlAsString = String(url) | ||
return urlAsString | ||
} | ||
if (url.username !== baseUrl.username || url.password !== baseUrl.password) { | ||
return urlArg.slice(url.protocol.length) | ||
if ( | ||
urlObject.username !== baseUrlObject.username || | ||
urlObject.password !== baseUrlObject.password || | ||
urlObject.host !== baseUrlObject.host | ||
) { | ||
const afterUrlScheme = String(url).slice(urlObject.protocol.length) | ||
return afterUrlScheme | ||
} | ||
if (url.host !== baseUrl.host) { | ||
return urlArg.slice(url.protocol.length) | ||
} | ||
const { pathname, hash, search } = url | ||
const { pathname, hash, search } = urlObject | ||
if (pathname === "/") { | ||
return baseUrl.pathname.slice(1) | ||
const baseUrlRessourceWithoutLeadingSlash = baseUrlObject.pathname.slice(1) | ||
return baseUrlRessourceWithoutLeadingSlash | ||
} | ||
const { pathname: basePathname } = baseUrl | ||
const basePathname = baseUrlObject.pathname | ||
const commonPathname = getCommonPathname(pathname, basePathname) | ||
if (!commonPathname) { | ||
return urlArg | ||
const urlAsString = String(url) | ||
return urlAsString | ||
} | ||
@@ -37,5 +40,8 @@ | ||
const relativeDirectoriesNotation = baseSpecificParentPathname.replace(/.*?\//g, "../") | ||
return `${relativeDirectoriesNotation}${specificPathname}${search}${hash}` | ||
const relativeUrl = `${relativeDirectoriesNotation}${specificPathname}${search}${hash}` | ||
return relativeUrl | ||
} | ||
return `${specificPathname}${search}${hash}` | ||
const relativeUrl = `${specificPathname}${search}${hash}` | ||
return relativeUrl | ||
} |
import { urlToScheme } from "./urlToScheme.js" | ||
export const urlToRessource = (urlString) => { | ||
const scheme = urlToScheme(urlString) | ||
export const urlToRessource = (url) => { | ||
const scheme = urlToScheme(url) | ||
if (scheme === "file") { | ||
return urlString.slice("file://".length) | ||
const urlAsStringWithoutFileProtocol = String(url).slice("file://".length) | ||
return urlAsStringWithoutFileProtocol | ||
} | ||
@@ -12,8 +13,10 @@ | ||
// remove origin | ||
const afterProtocol = urlString.slice(scheme.length + "://".length) | ||
const afterProtocol = String(url).slice(scheme.length + "://".length) | ||
const pathnameSlashIndex = afterProtocol.indexOf("/", "://".length) | ||
return afterProtocol.slice(pathnameSlashIndex) | ||
const urlAsStringWithoutOrigin = afterProtocol.slice(pathnameSlashIndex) | ||
return urlAsStringWithoutOrigin | ||
} | ||
return urlString.slice(scheme.length + 1) | ||
const urlAsStringWithoutProtocol = String(url).slice(scheme.length + 1) | ||
return urlAsStringWithoutProtocol | ||
} |
@@ -1,2 +0,3 @@ | ||
export const urlToScheme = (urlString) => { | ||
export const urlToScheme = (url) => { | ||
const urlString = String(url) | ||
const colonIndex = urlString.indexOf(":") | ||
@@ -3,0 +4,0 @@ if (colonIndex === -1) { |
@@ -1,2 +0,3 @@ | ||
import { promises } from "fs" | ||
import { promises } from "node:fs" | ||
import { assertAndNormalizeFileUrl } from "./assertAndNormalizeFileUrl.js" | ||
@@ -13,24 +14,24 @@ import { urlToFileSystemPath } from "./urlToFileSystemPath.js" | ||
export const writeSymbolicLink = async (destination, target, { type } = {}) => { | ||
const destinationUrl = assertAndNormalizeFileUrl(destination) | ||
export const writeSymbolicLink = async ({ from, to, type }) => { | ||
const fromUrl = assertAndNormalizeFileUrl(from) | ||
let targetValue | ||
if (typeof target === "string") { | ||
let toValue | ||
if (typeof to === "string") { | ||
// absolute filesystem path | ||
if (isFileSystemPath(target)) { | ||
targetValue = target | ||
if (isFileSystemPath(to)) { | ||
toValue = to | ||
} | ||
// relative url | ||
else if (target.startsWith("./") || target.startsWith("../")) { | ||
targetValue = target | ||
else if (to.startsWith("./") || to.startsWith("../")) { | ||
toValue = to | ||
} | ||
// absolute url | ||
else { | ||
const targetUrl = String(new URL(target, destinationUrl)) | ||
targetValue = urlToFileSystemPath(targetUrl) | ||
const toUrl = String(new URL(to, fromUrl)) | ||
toValue = urlToFileSystemPath(toUrl) | ||
} | ||
} else if (target instanceof URL) { | ||
targetValue = urlToFileSystemPath(target) | ||
} else if (to instanceof URL) { | ||
toValue = urlToFileSystemPath(to) | ||
} else { | ||
throw new TypeError(`symbolic link target must be a string or an url, received ${target}`) | ||
throw new TypeError(`symbolic link to must be a string or an url, received ${to}`) | ||
} | ||
@@ -41,14 +42,14 @@ | ||
// you later get EPERM when doing stat on the symlink | ||
const targetUrl = resolveUrl(targetValue, destinationUrl) | ||
const targetStats = await readFileSystemNodeStat(targetUrl, { nullIfNotFound: true }) | ||
type = targetStats && targetStats.isDirectory() ? "dir" : "file" | ||
const toUrl = resolveUrl(toValue, fromUrl) | ||
const toStats = await readFileSystemNodeStat(toUrl, { nullIfNotFound: true }) | ||
type = toStats && toStats.isDirectory() ? "dir" : "file" | ||
} | ||
const symbolicLinkPath = urlToFileSystemPath(destinationUrl) | ||
const symbolicLinkPath = urlToFileSystemPath(fromUrl) | ||
try { | ||
await symlink(targetValue, symbolicLinkPath, type) | ||
await symlink(toValue, symbolicLinkPath, type) | ||
} catch (error) { | ||
if (error.code === "ENOENT") { | ||
await ensureParentDirectories(destinationUrl) | ||
await symlink(targetValue, symbolicLinkPath, type) | ||
await ensureParentDirectories(fromUrl) | ||
await symlink(toValue, symbolicLinkPath, type) | ||
return | ||
@@ -55,0 +56,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
321494
4474
12