@jsenv/filesystem
Advanced tools
Comparing version 4.2.3 to 4.2.4
{ | ||
"name": "@jsenv/filesystem", | ||
"version": "4.2.3", | ||
"version": "4.2.4", | ||
"license": "MIT", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/jsenv/jsenv-core", | ||
"directory": "packages/filesystem" | ||
"url": "https://github.com/jsenv/core", | ||
"directory": "packages/independent/filesystem" | ||
}, | ||
@@ -32,3 +32,3 @@ "publishConfig": { | ||
"dependencies": { | ||
"@jsenv/urls": "2.0.0", | ||
"@jsenv/urls": "2.1.0", | ||
"@jsenv/url-meta": "8.1.0", | ||
@@ -35,0 +35,0 @@ "@jsenv/abort": "4.2.4" |
@@ -8,3 +8,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) | ||
```js | ||
import { listFilesMatching } from "@jsenv/filesystem" | ||
import { listFilesMatching } from "@jsenv/filesystem"; | ||
@@ -17,3 +17,3 @@ const jsFiles = await listFilesMatching({ | ||
}, | ||
}) | ||
}); | ||
``` | ||
@@ -31,20 +31,20 @@ | ||
```js | ||
import { readFileSync } from "node:fs" | ||
import { registerFileLifecycle } from "@jsenv/filesystem" | ||
import { readFileSync } from "node:fs"; | ||
import { registerFileLifecycle } from "@jsenv/filesystem"; | ||
const packageJSONFileUrl = new URL("./package.json", import.meta.url) | ||
let packageJSON = null | ||
const packageJSONFileUrl = new URL("./package.json", import.meta.url); | ||
let packageJSON = null; | ||
const unregister = registerFileLifecycle(packageJSONFileUrl, { | ||
added: () => { | ||
packageJSON = JSON.parse(String(readFileSync(packageJSONFileUrl))) | ||
packageJSON = JSON.parse(String(readFileSync(packageJSONFileUrl))); | ||
}, | ||
updated: () => { | ||
packageJSON = JSON.parse(String(readFileSync(packageJSONFileUrl))) | ||
packageJSON = JSON.parse(String(readFileSync(packageJSONFileUrl))); | ||
}, | ||
removed: () => { | ||
packageJSON = null | ||
packageJSON = null; | ||
}, | ||
notifyExistent: true, | ||
}) | ||
unregister() // stop watching the file changes | ||
}); | ||
unregister(); // stop watching the file changes | ||
``` | ||
@@ -55,5 +55,5 @@ | ||
```js | ||
import { registerDirectoryLifecycle } from "@jsenv/filesystem" | ||
import { registerDirectoryLifecycle } from "@jsenv/filesystem"; | ||
const directoryContentDescription = {} | ||
const directoryContentDescription = {}; | ||
const unregister = registerDirectoryLifecycle("file:///directory/", { | ||
@@ -65,9 +65,9 @@ watchPatterns: { | ||
added: ({ relativeUrl, type }) => { | ||
directoryContentDescription[relativeUrl] = type | ||
directoryContentDescription[relativeUrl] = type; | ||
}, | ||
removed: ({ relativeUrl }) => { | ||
delete directoryContentDescription[relativeUrl] | ||
delete directoryContentDescription[relativeUrl]; | ||
}, | ||
}) | ||
unregister() // stop watching the directory changes | ||
}); | ||
unregister(); // stop watching the directory changes | ||
``` | ||
@@ -74,0 +74,0 @@ |
@@ -1,16 +0,16 @@ | ||
import { urlToFileSystemPath } from "@jsenv/urls" | ||
import { urlToFileSystemPath } from "@jsenv/urls"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { statsToType } from "./internal/statsToType.js" | ||
import { readEntryStat } from "./readEntryStat.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
import { statsToType } from "./internal/statsToType.js"; | ||
import { readEntryStat } from "./readEntryStat.js"; | ||
export const assertDirectoryPresence = async (source) => { | ||
const sourceUrl = assertAndNormalizeFileUrl(source) | ||
const sourcePath = urlToFileSystemPath(sourceUrl) | ||
const sourceUrl = assertAndNormalizeFileUrl(source); | ||
const sourcePath = urlToFileSystemPath(sourceUrl); | ||
const sourceStats = await readEntryStat(sourceUrl, { | ||
nullIfNotFound: true, | ||
}) | ||
}); | ||
if (!sourceStats) { | ||
throw new Error(`directory not found at ${sourcePath}`) | ||
throw new Error(`directory not found at ${sourcePath}`); | ||
} | ||
@@ -22,4 +22,4 @@ if (!sourceStats.isDirectory()) { | ||
)} instead`, | ||
) | ||
); | ||
} | ||
} | ||
}; |
@@ -1,16 +0,16 @@ | ||
import { urlToFileSystemPath } from "@jsenv/urls" | ||
import { urlToFileSystemPath } from "@jsenv/urls"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { statsToType } from "./internal/statsToType.js" | ||
import { readEntryStat } from "./readEntryStat.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
import { statsToType } from "./internal/statsToType.js"; | ||
import { readEntryStat } from "./readEntryStat.js"; | ||
export const assertFilePresence = async (source) => { | ||
const sourceUrl = assertAndNormalizeFileUrl(source) | ||
const sourcePath = urlToFileSystemPath(sourceUrl) | ||
const sourceUrl = assertAndNormalizeFileUrl(source); | ||
const sourcePath = urlToFileSystemPath(sourceUrl); | ||
const sourceStats = await readEntryStat(sourceUrl, { | ||
nullIfNotFound: true, | ||
}) | ||
}); | ||
if (!sourceStats) { | ||
throw new Error(`file not found at ${sourcePath}`) | ||
throw new Error(`file not found at ${sourcePath}`); | ||
} | ||
@@ -22,4 +22,4 @@ if (!sourceStats.isFile()) { | ||
)} instead`, | ||
) | ||
); | ||
} | ||
} | ||
}; |
@@ -8,23 +8,23 @@ /* | ||
import { createHash } from "node:crypto" | ||
import { createHash } from "node:crypto"; | ||
const ETAG_FOR_EMPTY_CONTENT = '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"' | ||
const ETAG_FOR_EMPTY_CONTENT = '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"'; | ||
export const bufferToEtag = (buffer) => { | ||
if (!Buffer.isBuffer(buffer)) { | ||
throw new TypeError(`buffer expected, got ${buffer}`) | ||
throw new TypeError(`buffer expected, got ${buffer}`); | ||
} | ||
if (buffer.length === 0) { | ||
return ETAG_FOR_EMPTY_CONTENT | ||
return ETAG_FOR_EMPTY_CONTENT; | ||
} | ||
const hash = createHash("sha1") | ||
hash.update(buffer, "utf8") | ||
const hash = createHash("sha1"); | ||
hash.update(buffer, "utf8"); | ||
const hashBase64String = hash.digest("base64") | ||
const hashBase64StringSubset = hashBase64String.slice(0, 27) | ||
const length = buffer.length | ||
const hashBase64String = hash.digest("base64"); | ||
const hashBase64StringSubset = hashBase64String.slice(0, 27); | ||
const length = buffer.length; | ||
return `"${length.toString(16)}-${hashBase64StringSubset}"` | ||
} | ||
return `"${length.toString(16)}-${hashBase64StringSubset}"`; | ||
}; |
@@ -1,9 +0,9 @@ | ||
import { Abort } from "@jsenv/abort" | ||
import { Abort } from "@jsenv/abort"; | ||
import { URL_META } from "@jsenv/url-meta" | ||
import { urlToRelativeUrl } from "@jsenv/urls" | ||
import { assertAndNormalizeDirectoryUrl } from "./directory_url_validation.js" | ||
import { readDirectory } from "./readDirectory.js" | ||
import { readEntryStat } from "./readEntryStat.js" | ||
import { comparePathnames } from "./comparePathnames.js" | ||
import { URL_META } from "@jsenv/url-meta"; | ||
import { urlToRelativeUrl } from "@jsenv/urls"; | ||
import { assertAndNormalizeDirectoryUrl } from "./directory_url_validation.js"; | ||
import { readDirectory } from "./readDirectory.js"; | ||
import { readEntryStat } from "./readEntryStat.js"; | ||
import { comparePathnames } from "./comparePathnames.js"; | ||
@@ -16,27 +16,27 @@ export const collectDirectoryMatchReport = async ({ | ||
}) => { | ||
const matchingArray = [] | ||
const ignoredArray = [] | ||
const matchingArray = []; | ||
const ignoredArray = []; | ||
const rootDirectoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl) | ||
const rootDirectoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl); | ||
if (typeof predicate !== "function") { | ||
throw new TypeError(`predicate must be a function, got ${predicate}`) | ||
throw new TypeError(`predicate must be a function, got ${predicate}`); | ||
} | ||
associations = URL_META.resolveAssociations(associations, rootDirectoryUrl) | ||
associations = URL_META.resolveAssociations(associations, rootDirectoryUrl); | ||
const collectOperation = Abort.startOperation() | ||
collectOperation.addAbortSignal(signal) | ||
const collectOperation = Abort.startOperation(); | ||
collectOperation.addAbortSignal(signal); | ||
const visitDirectory = async (directoryUrl) => { | ||
collectOperation.throwIfAborted() | ||
const directoryItems = await readDirectory(directoryUrl) | ||
collectOperation.throwIfAborted(); | ||
const directoryItems = await readDirectory(directoryUrl); | ||
await Promise.all( | ||
directoryItems.map(async (directoryItem) => { | ||
const directoryChildNodeUrl = `${directoryUrl}${directoryItem}` | ||
const directoryChildNodeUrl = `${directoryUrl}${directoryItem}`; | ||
const relativeUrl = urlToRelativeUrl( | ||
directoryChildNodeUrl, | ||
rootDirectoryUrl, | ||
) | ||
); | ||
collectOperation.throwIfAborted() | ||
collectOperation.throwIfAborted(); | ||
const directoryChildNodeStats = await readEntryStat( | ||
@@ -53,6 +53,6 @@ directoryChildNodeUrl, | ||
}, | ||
) | ||
); | ||
if (directoryChildNodeStats.isDirectory()) { | ||
const subDirectoryUrl = `${directoryChildNodeUrl}/` | ||
const subDirectoryUrl = `${directoryChildNodeUrl}/`; | ||
@@ -71,9 +71,9 @@ if ( | ||
fileStats: directoryChildNodeStats, | ||
}) | ||
}); | ||
return | ||
return; | ||
} | ||
await visitDirectory(subDirectoryUrl) | ||
return | ||
await visitDirectory(subDirectoryUrl); | ||
return; | ||
} | ||
@@ -85,3 +85,3 @@ | ||
associations, | ||
}) | ||
}); | ||
if (!predicate(meta)) { | ||
@@ -92,4 +92,4 @@ ignoredArray.push({ | ||
fileStats: directoryChildNodeStats, | ||
}) | ||
return | ||
}); | ||
return; | ||
} | ||
@@ -101,11 +101,11 @@ | ||
fileStats: directoryChildNodeStats, | ||
}) | ||
return | ||
}); | ||
return; | ||
} | ||
}), | ||
) | ||
} | ||
); | ||
}; | ||
try { | ||
await visitDirectory(rootDirectoryUrl) | ||
await visitDirectory(rootDirectoryUrl); | ||
@@ -115,11 +115,11 @@ return { | ||
ignoredArray: sortByRelativeUrl(ignoredArray), | ||
} | ||
}; | ||
} finally { | ||
await collectOperation.end() | ||
await collectOperation.end(); | ||
} | ||
} | ||
}; | ||
const sortByRelativeUrl = (array) => | ||
array.sort((left, right) => { | ||
return comparePathnames(left.relativeUrl, right.relativeUrl) | ||
}) | ||
return comparePathnames(left.relativeUrl, right.relativeUrl); | ||
}); |
@@ -1,9 +0,9 @@ | ||
import { Abort } from "@jsenv/abort" | ||
import { URL_META } from "@jsenv/url-meta" | ||
import { urlToRelativeUrl } from "@jsenv/urls" | ||
import { Abort } from "@jsenv/abort"; | ||
import { URL_META } from "@jsenv/url-meta"; | ||
import { urlToRelativeUrl } from "@jsenv/urls"; | ||
import { assertAndNormalizeDirectoryUrl } from "./directory_url_validation.js" | ||
import { readDirectory } from "./readDirectory.js" | ||
import { readEntryStat } from "./readEntryStat.js" | ||
import { comparePathnames } from "./comparePathnames.js" | ||
import { assertAndNormalizeDirectoryUrl } from "./directory_url_validation.js"; | ||
import { readDirectory } from "./readDirectory.js"; | ||
import { readEntryStat } from "./readEntryStat.js"; | ||
import { comparePathnames } from "./comparePathnames.js"; | ||
@@ -16,20 +16,20 @@ export const collectFiles = async ({ | ||
}) => { | ||
const rootDirectoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl) | ||
const rootDirectoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl); | ||
if (typeof predicate !== "function") { | ||
throw new TypeError(`predicate must be a function, got ${predicate}`) | ||
throw new TypeError(`predicate must be a function, got ${predicate}`); | ||
} | ||
associations = URL_META.resolveAssociations(associations, rootDirectoryUrl) | ||
associations = URL_META.resolveAssociations(associations, rootDirectoryUrl); | ||
const collectOperation = Abort.startOperation() | ||
collectOperation.addAbortSignal(signal) | ||
const collectOperation = Abort.startOperation(); | ||
collectOperation.addAbortSignal(signal); | ||
const matchingFileResultArray = [] | ||
const matchingFileResultArray = []; | ||
const visitDirectory = async (directoryUrl) => { | ||
collectOperation.throwIfAborted() | ||
const directoryItems = await readDirectory(directoryUrl) | ||
collectOperation.throwIfAborted(); | ||
const directoryItems = await readDirectory(directoryUrl); | ||
await Promise.all( | ||
directoryItems.map(async (directoryItem) => { | ||
const directoryChildNodeUrl = `${directoryUrl}${directoryItem}` | ||
collectOperation.throwIfAborted() | ||
const directoryChildNodeUrl = `${directoryUrl}${directoryItem}`; | ||
collectOperation.throwIfAborted(); | ||
const directoryChildNodeStats = await readEntryStat( | ||
@@ -46,6 +46,6 @@ directoryChildNodeUrl, | ||
}, | ||
) | ||
); | ||
if (directoryChildNodeStats.isDirectory()) { | ||
const subDirectoryUrl = `${directoryChildNodeUrl}/` | ||
const subDirectoryUrl = `${directoryChildNodeUrl}/`; | ||
if ( | ||
@@ -58,6 +58,6 @@ !URL_META.urlChildMayMatch({ | ||
) { | ||
return | ||
return; | ||
} | ||
await visitDirectory(subDirectoryUrl) | ||
return | ||
await visitDirectory(subDirectoryUrl); | ||
return; | ||
} | ||
@@ -69,4 +69,4 @@ | ||
associations, | ||
}) | ||
if (!predicate(meta)) return | ||
}); | ||
if (!predicate(meta)) return; | ||
@@ -76,3 +76,3 @@ const relativeUrl = urlToRelativeUrl( | ||
rootDirectoryUrl, | ||
) | ||
); | ||
matchingFileResultArray.push({ | ||
@@ -83,11 +83,11 @@ url: new URL(relativeUrl, rootDirectoryUrl).href, | ||
fileStats: directoryChildNodeStats, | ||
}) | ||
return | ||
}); | ||
return; | ||
} | ||
}), | ||
) | ||
} | ||
); | ||
}; | ||
try { | ||
await visitDirectory(rootDirectoryUrl) | ||
await visitDirectory(rootDirectoryUrl); | ||
@@ -99,8 +99,8 @@ // When we operate on thoose files later it feels more natural | ||
matchingFileResultArray.sort((leftFile, rightFile) => { | ||
return comparePathnames(leftFile.relativeUrl, rightFile.relativeUrl) | ||
}) | ||
return matchingFileResultArray | ||
return comparePathnames(leftFile.relativeUrl, rightFile.relativeUrl); | ||
}); | ||
return matchingFileResultArray; | ||
} finally { | ||
await collectOperation.end() | ||
await collectOperation.end(); | ||
} | ||
} | ||
}; |
export const comparePathnames = (leftPathame, rightPathname) => { | ||
const leftPartArray = leftPathame.split("/") | ||
const rightPartArray = rightPathname.split("/") | ||
const leftPartArray = leftPathame.split("/"); | ||
const rightPartArray = rightPathname.split("/"); | ||
const leftLength = leftPartArray.length | ||
const rightLength = rightPartArray.length | ||
const leftLength = leftPartArray.length; | ||
const rightLength = rightPartArray.length; | ||
const maxLength = Math.max(leftLength, rightLength) | ||
let i = 0 | ||
const maxLength = Math.max(leftLength, rightLength); | ||
let i = 0; | ||
while (i < maxLength) { | ||
const leftPartExists = i in leftPartArray | ||
const rightPartExists = i in rightPartArray | ||
const leftPartExists = i in leftPartArray; | ||
const rightPartExists = i in rightPartArray; | ||
// longer comes first | ||
if (!leftPartExists) { | ||
return +1 | ||
return +1; | ||
} | ||
if (!rightPartExists) { | ||
return -1 | ||
return -1; | ||
} | ||
const leftPartIsLast = i === leftPartArray.length - 1 | ||
const rightPartIsLast = i === rightPartArray.length - 1 | ||
const leftPartIsLast = i === leftPartArray.length - 1; | ||
const rightPartIsLast = i === rightPartArray.length - 1; | ||
// folder comes first | ||
if (leftPartIsLast && !rightPartIsLast) { | ||
return +1 | ||
return +1; | ||
} | ||
if (!leftPartIsLast && rightPartIsLast) { | ||
return -1 | ||
return -1; | ||
} | ||
const leftPart = leftPartArray[i] | ||
const rightPart = rightPartArray[i] | ||
i++ | ||
const leftPart = leftPartArray[i]; | ||
const rightPart = rightPartArray[i]; | ||
i++; | ||
// local comparison comes first | ||
const comparison = leftPart.localeCompare(rightPart) | ||
const comparison = leftPart.localeCompare(rightPart); | ||
if (comparison !== 0) { | ||
return comparison | ||
return comparison; | ||
} | ||
@@ -43,8 +43,8 @@ } | ||
if (leftLength < rightLength) { | ||
return +1 | ||
return +1; | ||
} | ||
if (leftLength > rightLength) { | ||
return -1 | ||
return -1; | ||
} | ||
return 0 | ||
} | ||
return 0; | ||
}; |
@@ -1,11 +0,11 @@ | ||
import { Abort } from "@jsenv/abort" | ||
import { urlToFileSystemPath, resolveUrl } from "@jsenv/urls" | ||
import { Abort } from "@jsenv/abort"; | ||
import { urlToFileSystemPath, resolveUrl } from "@jsenv/urls"; | ||
import { assertAndNormalizeDirectoryUrl } from "./directory_url_validation.js" | ||
import { readEntryStat } from "./readEntryStat.js" | ||
import { readSymbolicLink } from "./readSymbolicLink.js" | ||
import { readDirectory } from "./readDirectory.js" | ||
import { copyEntry } from "./copyEntry.js" | ||
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js" | ||
import { statsToType } from "./internal/statsToType.js" | ||
import { assertAndNormalizeDirectoryUrl } from "./directory_url_validation.js"; | ||
import { readEntryStat } from "./readEntryStat.js"; | ||
import { readSymbolicLink } from "./readSymbolicLink.js"; | ||
import { readDirectory } from "./readDirectory.js"; | ||
import { copyEntry } from "./copyEntry.js"; | ||
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js"; | ||
import { statsToType } from "./internal/statsToType.js"; | ||
@@ -19,6 +19,6 @@ export const copyDirectoryContent = async ({ | ||
} = {}) => { | ||
const fromUrl = assertAndNormalizeDirectoryUrl(from) | ||
const fromPath = urlToFileSystemPath(fromUrl) | ||
let toUrl = assertAndNormalizeDirectoryUrl(to) | ||
let toPath = urlToFileSystemPath(toUrl) | ||
const fromUrl = assertAndNormalizeDirectoryUrl(from); | ||
const fromPath = urlToFileSystemPath(fromUrl); | ||
let toUrl = assertAndNormalizeDirectoryUrl(to); | ||
let toPath = urlToFileSystemPath(toUrl); | ||
@@ -28,11 +28,11 @@ const sourceStats = await readEntryStat(fromUrl, { | ||
followLink: false, | ||
}) | ||
}); | ||
if (!sourceStats) { | ||
throw new Error(`no directory to copy content from at ${fromPath}`) | ||
throw new Error(`no directory to copy content from at ${fromPath}`); | ||
} | ||
if (!sourceStats.isDirectory()) { | ||
const sourceType = statsToType(sourceStats) | ||
const sourceType = statsToType(sourceStats); | ||
throw new Error( | ||
`found a ${sourceType} instead of a directory at ${fromPath}`, | ||
) | ||
); | ||
} | ||
@@ -45,20 +45,20 @@ | ||
followLink: false, | ||
}) | ||
}); | ||
if (followLink && destinationStats && destinationStats.isSymbolicLink()) { | ||
const linkTarget = await readSymbolicLink(toUrl) | ||
toUrl = resolveUrl(linkTarget, toUrl) | ||
toPath = urlToFileSystemPath(toUrl) | ||
const linkTarget = await readSymbolicLink(toUrl); | ||
toUrl = resolveUrl(linkTarget, toUrl); | ||
toPath = urlToFileSystemPath(toUrl); | ||
destinationStats = await readEntryStat(toUrl, { | ||
nullIfNotFound: true, | ||
}) | ||
}); | ||
} | ||
if (destinationStats === null) { | ||
throw new Error(`no directory to copy content into at ${toPath}`) | ||
throw new Error(`no directory to copy content into at ${toPath}`); | ||
} | ||
if (!destinationStats.isDirectory()) { | ||
const destinationType = statsToType(destinationStats) | ||
const destinationType = statsToType(destinationStats); | ||
throw new Error( | ||
`destination leads to a ${destinationType} instead of a directory at ${toPath}`, | ||
) | ||
); | ||
} | ||
@@ -69,14 +69,14 @@ | ||
`cannot copy directory content, source and destination are the same (${fromPath})`, | ||
) | ||
); | ||
} | ||
const copyOperation = Abort.startOperation() | ||
copyOperation.addAbortSignal(signal) | ||
const copyOperation = Abort.startOperation(); | ||
copyOperation.addAbortSignal(signal); | ||
try { | ||
copyOperation.throwIfAborted() | ||
const directoryEntries = await readDirectory(fromUrl) | ||
copyOperation.throwIfAborted(); | ||
const directoryEntries = await readDirectory(fromUrl); | ||
await Promise.all( | ||
directoryEntries.map(async (directoryEntry) => { | ||
const from = resolveUrl(directoryEntry, fromUrl) | ||
const to = resolveUrl(directoryEntry, toUrl) | ||
const from = resolveUrl(directoryEntry, fromUrl); | ||
const to = resolveUrl(directoryEntry, toUrl); | ||
await copyOperation.withSignal(async (signal) => { | ||
@@ -89,9 +89,9 @@ await copyEntry({ | ||
followLink, | ||
}) | ||
}) | ||
}); | ||
}); | ||
}), | ||
) | ||
); | ||
} finally { | ||
await copyOperation.end() | ||
await copyOperation.end(); | ||
} | ||
} | ||
}; |
@@ -1,3 +0,3 @@ | ||
import { copyFile as copyFileNode } from "node:fs" | ||
import { Abort } from "@jsenv/abort" | ||
import { copyFile as copyFileNode } from "node:fs"; | ||
import { Abort } from "@jsenv/abort"; | ||
import { | ||
@@ -9,17 +9,17 @@ resolveUrl, | ||
urlToFileSystemPath, | ||
} from "@jsenv/urls" | ||
} from "@jsenv/urls"; | ||
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js" | ||
import { statsToType } from "./internal/statsToType.js" | ||
import { binaryFlagsToPermissions } from "./internal/permissions.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { writeDirectory } from "./writeDirectory.js" | ||
import { readEntryStat } from "./readEntryStat.js" | ||
import { ensureParentDirectories } from "./ensureParentDirectories.js" | ||
import { writeEntryPermissions } from "./writeEntryPermissions.js" | ||
import { writeEntryModificationTime } from "./writeEntryModificationTime.js" | ||
import { readDirectory } from "./readDirectory.js" | ||
import { readSymbolicLink } from "./readSymbolicLink.js" | ||
import { writeSymbolicLink } from "./writeSymbolicLink.js" | ||
import { removeEntry } from "./removeEntry.js" | ||
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js"; | ||
import { statsToType } from "./internal/statsToType.js"; | ||
import { binaryFlagsToPermissions } from "./internal/permissions.js"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
import { writeDirectory } from "./writeDirectory.js"; | ||
import { readEntryStat } from "./readEntryStat.js"; | ||
import { ensureParentDirectories } from "./ensureParentDirectories.js"; | ||
import { writeEntryPermissions } from "./writeEntryPermissions.js"; | ||
import { writeEntryModificationTime } from "./writeEntryModificationTime.js"; | ||
import { readDirectory } from "./readDirectory.js"; | ||
import { readSymbolicLink } from "./readSymbolicLink.js"; | ||
import { writeSymbolicLink } from "./writeSymbolicLink.js"; | ||
import { removeEntry } from "./removeEntry.js"; | ||
@@ -37,6 +37,6 @@ export const copyEntry = async ({ | ||
}) => { | ||
const fromUrl = assertAndNormalizeFileUrl(from) | ||
const fromPath = urlToFileSystemPath(fromUrl) | ||
let toUrl = assertAndNormalizeFileUrl(to) | ||
let toPath = urlToFileSystemPath(toUrl) | ||
const fromUrl = assertAndNormalizeFileUrl(from); | ||
const fromPath = urlToFileSystemPath(fromUrl); | ||
let toUrl = assertAndNormalizeFileUrl(to); | ||
let toPath = urlToFileSystemPath(toUrl); | ||
@@ -46,5 +46,5 @@ const sourceStats = await readEntryStat(fromUrl, { | ||
followLink: false, | ||
}) | ||
}); | ||
if (!sourceStats) { | ||
throw new Error(`nothing to copy at ${fromPath}`) | ||
throw new Error(`nothing to copy at ${fromPath}`); | ||
} | ||
@@ -57,11 +57,11 @@ | ||
followLink: false, | ||
}) | ||
}); | ||
if (followLink && destinationStats && destinationStats.isSymbolicLink()) { | ||
const linkTarget = await readSymbolicLink(toUrl) | ||
toUrl = resolveUrl(linkTarget, toUrl) | ||
toPath = urlToFileSystemPath(toUrl) | ||
const linkTarget = await readSymbolicLink(toUrl); | ||
toUrl = resolveUrl(linkTarget, toUrl); | ||
toPath = urlToFileSystemPath(toUrl); | ||
destinationStats = await readEntryStat(toUrl, { | ||
nullIfNotFound: true, | ||
}) | ||
}); | ||
} | ||
@@ -71,30 +71,30 @@ | ||
if (allowUseless) { | ||
return | ||
return; | ||
} | ||
throw new Error( | ||
`cannot copy ${fromPath} because destination and source are the same`, | ||
) | ||
); | ||
} | ||
if (sourceStats.isDirectory()) { | ||
toUrl = ensurePathnameTrailingSlash(toUrl) | ||
toUrl = ensurePathnameTrailingSlash(toUrl); | ||
} | ||
const copyOperation = Abort.startOperation() | ||
copyOperation.addAbortSignal(signal) | ||
const copyOperation = Abort.startOperation(); | ||
copyOperation.addAbortSignal(signal); | ||
const visit = async (url, stats) => { | ||
copyOperation.throwIfAborted() | ||
copyOperation.throwIfAborted(); | ||
if (stats.isFile() || stats.isCharacterDevice() || stats.isBlockDevice()) { | ||
await visitFile(url, stats) | ||
await visitFile(url, stats); | ||
} else if (stats.isSymbolicLink()) { | ||
await visitSymbolicLink(url, stats) | ||
await visitSymbolicLink(url, stats); | ||
} else if (stats.isDirectory()) { | ||
await visitDirectory(ensurePathnameTrailingSlash(url), stats) | ||
await visitDirectory(ensurePathnameTrailingSlash(url), stats); | ||
} | ||
} | ||
}; | ||
const visitFile = async (fileUrl, fileStats) => { | ||
const fileRelativeUrl = urlToRelativeUrl(fileUrl, fromUrl) | ||
const fileCopyUrl = resolveUrl(fileRelativeUrl, toUrl) | ||
const fileRelativeUrl = urlToRelativeUrl(fileUrl, fromUrl); | ||
const fileCopyUrl = resolveUrl(fileRelativeUrl, toUrl); | ||
@@ -104,20 +104,20 @@ await copyFileContentNaive( | ||
urlToFileSystemPath(fileCopyUrl), | ||
) | ||
await copyStats(fileCopyUrl, fileStats) | ||
} | ||
); | ||
await copyStats(fileCopyUrl, fileStats); | ||
}; | ||
const visitSymbolicLink = async (symbolicLinkUrl) => { | ||
const symbolicLinkRelativeUrl = urlToRelativeUrl(symbolicLinkUrl, fromUrl) | ||
const symbolicLinkTarget = await readSymbolicLink(symbolicLinkUrl) | ||
const symbolicLinkRelativeUrl = urlToRelativeUrl(symbolicLinkUrl, fromUrl); | ||
const symbolicLinkTarget = await readSymbolicLink(symbolicLinkUrl); | ||
const symbolicLinkTargetUrl = resolveUrl( | ||
symbolicLinkTarget, | ||
symbolicLinkUrl, | ||
) | ||
); | ||
const linkIsRelative = | ||
symbolicLinkTarget.startsWith("./") || | ||
symbolicLinkTarget.startsWith("../") | ||
symbolicLinkTarget.startsWith("../"); | ||
let symbolicLinkCopyTarget | ||
let symbolicLinkCopyTarget; | ||
if (symbolicLinkTargetUrl === fromUrl) { | ||
symbolicLinkCopyTarget = linkIsRelative ? symbolicLinkTarget : toUrl | ||
symbolicLinkCopyTarget = linkIsRelative ? symbolicLinkTarget : toUrl; | ||
} else if (urlIsInsideOf(symbolicLinkTargetUrl, fromUrl)) { | ||
@@ -129,9 +129,9 @@ // symbolic link targets something inside the directory we want to copy | ||
fromUrl, | ||
) | ||
); | ||
symbolicLinkCopyTarget = linkIsRelative | ||
? `./${linkCopyTargetRelative}` | ||
: resolveUrl(linkCopyTargetRelative, toUrl) | ||
: resolveUrl(linkCopyTargetRelative, toUrl); | ||
} else { | ||
// symbolic link targets something outside the directory we want to copy | ||
symbolicLinkCopyTarget = symbolicLinkTarget | ||
symbolicLinkCopyTarget = symbolicLinkTarget; | ||
} | ||
@@ -145,6 +145,6 @@ | ||
followLink: false, | ||
}) | ||
const linkType = targetStats && targetStats.isDirectory() ? "dir" : "file" | ||
}); | ||
const linkType = targetStats && targetStats.isDirectory() ? "dir" : "file"; | ||
const symbolicLinkCopyUrl = resolveUrl(symbolicLinkRelativeUrl, toUrl) | ||
const symbolicLinkCopyUrl = resolveUrl(symbolicLinkRelativeUrl, toUrl); | ||
await writeSymbolicLink({ | ||
@@ -154,43 +154,43 @@ from: symbolicLinkCopyUrl, | ||
type: linkType, | ||
}) | ||
} | ||
}); | ||
}; | ||
const copyStats = async (toUrl, stats) => { | ||
if (preservePermissions || preserveMtime) { | ||
const { mode, mtimeMs } = stats | ||
const { mode, mtimeMs } = stats; | ||
if (preservePermissions) { | ||
await writeEntryPermissions(toUrl, binaryFlagsToPermissions(mode)) | ||
await writeEntryPermissions(toUrl, binaryFlagsToPermissions(mode)); | ||
} | ||
if (preserveMtime) { | ||
await writeEntryModificationTime(toUrl, mtimeMs) | ||
await writeEntryModificationTime(toUrl, mtimeMs); | ||
} | ||
} | ||
} | ||
}; | ||
const visitDirectory = async (directoryUrl, directoryStats) => { | ||
const directoryRelativeUrl = urlToRelativeUrl(directoryUrl, fromUrl) | ||
const directoryCopyUrl = resolveUrl(directoryRelativeUrl, toUrl) | ||
const directoryRelativeUrl = urlToRelativeUrl(directoryUrl, fromUrl); | ||
const directoryCopyUrl = resolveUrl(directoryRelativeUrl, toUrl); | ||
await writeDirectory(directoryCopyUrl) | ||
await copyDirectoryContent(directoryUrl) | ||
await copyStats(directoryCopyUrl, directoryStats) | ||
} | ||
await writeDirectory(directoryCopyUrl); | ||
await copyDirectoryContent(directoryUrl); | ||
await copyStats(directoryCopyUrl, directoryStats); | ||
}; | ||
const copyDirectoryContent = async (directoryUrl) => { | ||
const names = await readDirectory(directoryUrl) | ||
const names = await readDirectory(directoryUrl); | ||
await Promise.all( | ||
names.map(async (name) => { | ||
const entryUrl = resolveUrl(name, directoryUrl) | ||
const entryUrl = resolveUrl(name, directoryUrl); | ||
const stats = await readEntryStat(entryUrl, { | ||
followLink: false, | ||
}) | ||
await visit(entryUrl, stats) | ||
}); | ||
await visit(entryUrl, stats); | ||
}), | ||
) | ||
} | ||
); | ||
}; | ||
try { | ||
if (destinationStats) { | ||
const sourceType = statsToType(sourceStats) | ||
const destinationType = statsToType(destinationStats) | ||
const sourceType = statsToType(sourceStats); | ||
const destinationType = statsToType(destinationStats); | ||
@@ -200,3 +200,3 @@ if (sourceType !== destinationType) { | ||
`cannot copy ${sourceType} from ${fromPath} to ${toPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`, | ||
) | ||
); | ||
} | ||
@@ -206,3 +206,3 @@ if (!overwrite) { | ||
`cannot copy ${sourceType} from ${fromPath} to ${toPath} because destination exists and overwrite option is disabled`, | ||
) | ||
); | ||
} | ||
@@ -215,13 +215,13 @@ | ||
allowUseless: true, | ||
}) | ||
}); | ||
} else { | ||
await ensureParentDirectories(toUrl) | ||
await ensureParentDirectories(toUrl); | ||
} | ||
copyOperation.throwIfAborted() | ||
await visit(fromUrl, sourceStats) | ||
copyOperation.throwIfAborted(); | ||
await visit(fromUrl, sourceStats); | ||
} finally { | ||
await copyOperation.end() | ||
await copyOperation.end(); | ||
} | ||
} | ||
}; | ||
@@ -232,8 +232,8 @@ const copyFileContentNaive = (filePath, fileDestinationPath) => { | ||
if (error) { | ||
reject(error) | ||
reject(error); | ||
} else { | ||
resolve() | ||
resolve(); | ||
} | ||
}) | ||
}) | ||
} | ||
}); | ||
}); | ||
}; |
@@ -5,15 +5,15 @@ import { | ||
ensurePathnameTrailingSlash, | ||
} from "@jsenv/urls" | ||
} from "@jsenv/urls"; | ||
export const validateDirectoryUrl = (value) => { | ||
let urlString | ||
let urlString; | ||
if (value instanceof URL) { | ||
urlString = value.href | ||
urlString = value.href; | ||
} else if (typeof value === "string") { | ||
if (isFileSystemPath(value)) { | ||
urlString = fileSystemPathToUrl(value) | ||
urlString = fileSystemPathToUrl(value); | ||
} else { | ||
try { | ||
urlString = String(new URL(value)) | ||
urlString = String(new URL(value)); | ||
} catch (e) { | ||
@@ -24,3 +24,3 @@ return { | ||
message: `must be a valid url`, | ||
} | ||
}; | ||
} | ||
@@ -33,3 +33,3 @@ } | ||
message: `must be a string or an url`, | ||
} | ||
}; | ||
} | ||
@@ -41,3 +41,3 @@ if (!urlString.startsWith("file://")) { | ||
message: 'must start with "file://"', | ||
} | ||
}; | ||
} | ||
@@ -47,4 +47,4 @@ return { | ||
value: ensurePathnameTrailingSlash(urlString), | ||
} | ||
} | ||
}; | ||
}; | ||
@@ -55,7 +55,7 @@ export const assertAndNormalizeDirectoryUrl = ( | ||
) => { | ||
const { valid, message, value } = validateDirectoryUrl(directoryUrl) | ||
const { valid, message, value } = validateDirectoryUrl(directoryUrl); | ||
if (!valid) { | ||
throw new TypeError(`${name} ${message}, got ${value}`) | ||
throw new TypeError(`${name} ${message}, got ${value}`); | ||
} | ||
return value | ||
} | ||
return value; | ||
}; |
@@ -1,8 +0,8 @@ | ||
import { urlToFileSystemPath } from "@jsenv/urls" | ||
import { urlToFileSystemPath } from "@jsenv/urls"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { statsToType } from "./internal/statsToType.js" | ||
import { writeDirectory } from "./writeDirectory.js" | ||
import { readEntryStat } from "./readEntryStat.js" | ||
import { removeEntry } from "./removeEntry.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
import { statsToType } from "./internal/statsToType.js"; | ||
import { writeDirectory } from "./writeDirectory.js"; | ||
import { readEntryStat } from "./readEntryStat.js"; | ||
import { removeEntry } from "./removeEntry.js"; | ||
@@ -13,6 +13,6 @@ export const ensureEmptyDirectory = async (source) => { | ||
followLink: false, | ||
}) | ||
}); | ||
if (stats === null) { | ||
// if there is nothing, create a directory | ||
return writeDirectory(source, { allowUseless: true }) | ||
return writeDirectory(source, { allowUseless: true }); | ||
} | ||
@@ -25,10 +25,10 @@ if (stats.isDirectory()) { | ||
onlyContent: true, | ||
}) | ||
}); | ||
} | ||
const sourceType = statsToType(stats) | ||
const sourcePath = urlToFileSystemPath(assertAndNormalizeFileUrl(source)) | ||
const sourceType = statsToType(stats); | ||
const sourcePath = urlToFileSystemPath(assertAndNormalizeFileUrl(source)); | ||
throw new Error( | ||
`ensureEmptyDirectory expect directory at ${sourcePath}, found ${sourceType} instead`, | ||
) | ||
} | ||
); | ||
}; |
@@ -1,11 +0,11 @@ | ||
import { dirname } from "node:path" | ||
import { urlToFileSystemPath } from "@jsenv/urls" | ||
import { dirname } from "node:path"; | ||
import { urlToFileSystemPath } from "@jsenv/urls"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { writeDirectory } from "./writeDirectory.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
import { writeDirectory } from "./writeDirectory.js"; | ||
export const ensureParentDirectories = async (destination) => { | ||
const destinationUrl = assertAndNormalizeFileUrl(destination) | ||
const destinationPath = urlToFileSystemPath(destinationUrl) | ||
const destinationParentPath = dirname(destinationPath) | ||
const destinationUrl = assertAndNormalizeFileUrl(destination); | ||
const destinationPath = urlToFileSystemPath(destinationUrl); | ||
const destinationParentPath = dirname(destinationPath); | ||
@@ -15,3 +15,3 @@ return writeDirectory(destinationParentPath, { | ||
allowUseless: true, | ||
}) | ||
} | ||
}); | ||
}; |
@@ -1,5 +0,5 @@ | ||
import { fileSystemPathToUrl } from "@jsenv/urls" | ||
import { fileSystemPathToUrl } from "@jsenv/urls"; | ||
const isWindows = process.platform === "win32" | ||
const baseUrlFallback = fileSystemPathToUrl(process.cwd()) | ||
const isWindows = process.platform === "win32"; | ||
const baseUrlFallback = fileSystemPathToUrl(process.cwd()); | ||
@@ -21,26 +21,26 @@ /** | ||
try { | ||
url = String(new URL(url)) | ||
url = String(new URL(url)); | ||
} catch (e) { | ||
throw new Error(`absolute url expected but got ${url}`) | ||
throw new Error(`absolute url expected but got ${url}`); | ||
} | ||
if (!isWindows) { | ||
return url | ||
return url; | ||
} | ||
try { | ||
baseUrl = String(new URL(baseUrl)) | ||
baseUrl = String(new URL(baseUrl)); | ||
} catch (e) { | ||
throw new Error( | ||
`absolute baseUrl expected but got ${baseUrl} to ensure windows drive letter on ${url}`, | ||
) | ||
); | ||
} | ||
if (!url.startsWith("file://")) { | ||
return url | ||
return url; | ||
} | ||
const afterProtocol = url.slice("file://".length) | ||
const afterProtocol = url.slice("file://".length); | ||
// we still have the windows drive letter | ||
if (extractDriveLetter(afterProtocol)) { | ||
return url | ||
return url; | ||
} | ||
@@ -51,13 +51,13 @@ | ||
? baseUrl | ||
: baseUrlFallback | ||
: baseUrlFallback; | ||
const driveLetter = extractDriveLetter( | ||
baseUrlOrFallback.slice("file://".length), | ||
) | ||
); | ||
if (!driveLetter) { | ||
throw new Error( | ||
`drive letter expected on baseUrl but got ${baseUrl} to ensure windows drive letter on ${url}`, | ||
) | ||
); | ||
} | ||
return `file:///${driveLetter}:${afterProtocol}` | ||
} | ||
return `file:///${driveLetter}:${afterProtocol}`; | ||
}; | ||
@@ -67,5 +67,5 @@ const extractDriveLetter = (resource) => { | ||
if (/[a-zA-Z]/.test(resource[1]) && resource[2] === ":") { | ||
return resource[1] | ||
return resource[1]; | ||
} | ||
return null | ||
} | ||
return null; | ||
}; |
@@ -1,14 +0,14 @@ | ||
import { fileSystemPathToUrl, isFileSystemPath } from "@jsenv/urls" | ||
import { fileSystemPathToUrl, isFileSystemPath } from "@jsenv/urls"; | ||
export const validateFileUrl = (value, baseUrl) => { | ||
let urlString | ||
let urlString; | ||
if (value instanceof URL) { | ||
urlString = value.href | ||
urlString = value.href; | ||
} else if (typeof value === "string") { | ||
if (isFileSystemPath(value)) { | ||
urlString = fileSystemPathToUrl(value) | ||
urlString = fileSystemPathToUrl(value); | ||
} else { | ||
try { | ||
urlString = String(new URL(value, baseUrl)) | ||
urlString = String(new URL(value, baseUrl)); | ||
} catch (e) { | ||
@@ -19,3 +19,3 @@ return { | ||
message: "must be a valid url", | ||
} | ||
}; | ||
} | ||
@@ -28,3 +28,3 @@ } | ||
message: "must be a string or an url", | ||
} | ||
}; | ||
} | ||
@@ -37,3 +37,3 @@ | ||
message: 'must start with "file://"', | ||
} | ||
}; | ||
} | ||
@@ -44,4 +44,4 @@ | ||
value: urlString, | ||
} | ||
} | ||
}; | ||
}; | ||
@@ -53,7 +53,7 @@ export const assertAndNormalizeFileUrl = ( | ||
) => { | ||
const { valid, message, value } = validateFileUrl(fileUrl, baseUrl) | ||
const { valid, message, value } = validateFileUrl(fileUrl, baseUrl); | ||
if (!valid) { | ||
throw new TypeError(`${name} ${message}, got ${fileUrl}`) | ||
throw new TypeError(`${name} ${message}, got ${fileUrl}`); | ||
} | ||
return value | ||
} | ||
return value; | ||
}; |
export const fileSystemRootUrl = | ||
process.platform === "win32" ? `file:///${process.cwd()[0]}:/` : "file:///" | ||
process.platform === "win32" ? `file:///${process.cwd()[0]}:/` : "file:///"; |
@@ -1,3 +0,3 @@ | ||
import { readdirSync, realpathSync } from "node:fs" | ||
import { fileSystemPathToUrl, urlToFileSystemPath } from "@jsenv/urls" | ||
import { readdirSync, realpathSync } from "node:fs"; | ||
import { fileSystemPathToUrl, urlToFileSystemPath } from "@jsenv/urls"; | ||
@@ -8,15 +8,15 @@ export const getRealFileSystemUrlSync = ( | ||
) => { | ||
const pathname = new URL(fileUrl).pathname | ||
const parts = pathname.slice(1).split("/") | ||
let reconstructedFileUrl = `file:///` | ||
const pathname = new URL(fileUrl).pathname; | ||
const parts = pathname.slice(1).split("/"); | ||
let reconstructedFileUrl = `file:///`; | ||
if (process.platform === "win32") { | ||
const windowsDriveLetter = parts.shift() | ||
reconstructedFileUrl += `${windowsDriveLetter}/` | ||
const windowsDriveLetter = parts.shift(); | ||
reconstructedFileUrl += `${windowsDriveLetter}/`; | ||
} | ||
let i = 0 | ||
let i = 0; | ||
// eslint-disable-next-line no-constant-condition | ||
while (true) { | ||
const name = parts[i] | ||
i++ | ||
let namesOnFileSystem | ||
const name = parts[i]; | ||
i++; | ||
let namesOnFileSystem; | ||
try { | ||
@@ -28,20 +28,20 @@ namesOnFileSystem = readdirSync( | ||
new URL(reconstructedFileUrl), | ||
) | ||
); | ||
} catch (e) { | ||
if (e && e.code === "ENOENT") { | ||
return null | ||
return null; | ||
} | ||
throw e | ||
throw e; | ||
} | ||
const foundOnFilesystem = namesOnFileSystem.includes(name) | ||
const foundOnFilesystem = namesOnFileSystem.includes(name); | ||
if (foundOnFilesystem) { | ||
reconstructedFileUrl += name | ||
reconstructedFileUrl += name; | ||
} else { | ||
const nameOnFileSystem = namesOnFileSystem.find( | ||
(nameCandidate) => nameCandidate.toLowerCase() === name.toLowerCase(), | ||
) | ||
); | ||
if (!nameOnFileSystem) { | ||
return null | ||
return null; | ||
} | ||
reconstructedFileUrl += nameOnFileSystem | ||
reconstructedFileUrl += nameOnFileSystem; | ||
} | ||
@@ -52,9 +52,9 @@ if (i === parts.length) { | ||
urlToFileSystemPath(reconstructedFileUrl), | ||
) | ||
return fileSystemPathToUrl(realPath) | ||
); | ||
return fileSystemPathToUrl(realPath); | ||
} | ||
return reconstructedFileUrl | ||
return reconstructedFileUrl; | ||
} | ||
reconstructedFileUrl += "/" | ||
reconstructedFileUrl += "/"; | ||
} | ||
} | ||
}; |
@@ -1,4 +0,4 @@ | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { readEntryPermissions } from "./readEntryPermissions.js" | ||
import { writeEntryPermissions } from "./writeEntryPermissions.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
import { readEntryPermissions } from "./readEntryPermissions.js"; | ||
import { writeEntryPermissions } from "./writeEntryPermissions.js"; | ||
@@ -9,5 +9,5 @@ export const grantPermissionsOnEntry = async ( | ||
) => { | ||
const sourceUrl = assertAndNormalizeFileUrl(source) | ||
const sourceUrl = assertAndNormalizeFileUrl(source); | ||
const filePermissions = await readEntryPermissions(sourceUrl) | ||
const filePermissions = await readEntryPermissions(sourceUrl); | ||
await writeEntryPermissions(sourceUrl, { | ||
@@ -17,6 +17,6 @@ owner: { read, write, execute }, | ||
others: { read, write, execute }, | ||
}) | ||
}); | ||
return async () => { | ||
await writeEntryPermissions(sourceUrl, filePermissions) | ||
} | ||
} | ||
await writeEntryPermissions(sourceUrl, filePermissions); | ||
}; | ||
}; |
@@ -1,7 +0,7 @@ | ||
import { watch, openSync, closeSync } from "node:fs" | ||
import { watch, openSync, closeSync } from "node:fs"; | ||
const isWindows = process.platform === "win32" | ||
const isWindows = process.platform === "win32"; | ||
export const createWatcher = (sourcePath, options) => { | ||
const watcher = watch(sourcePath, options) | ||
const watcher = watch(sourcePath, options); | ||
@@ -13,18 +13,18 @@ if (isWindows) { | ||
try { | ||
const fd = openSync(sourcePath, "r") | ||
closeSync(fd) | ||
const fd = openSync(sourcePath, "r"); | ||
closeSync(fd); | ||
} catch (e) { | ||
if (e.code === "ENOENT") { | ||
return | ||
return; | ||
} | ||
console.error(`error while fixing windows eperm: ${e.stack}`) | ||
throw error | ||
console.error(`error while fixing windows eperm: ${e.stack}`); | ||
throw error; | ||
} | ||
} else { | ||
throw error | ||
throw error; | ||
} | ||
}) | ||
}); | ||
} | ||
return watcher | ||
} | ||
return watcher; | ||
}; |
export const getCommandArgument = (argv, name) => { | ||
let i = 0 | ||
let i = 0; | ||
while (i < argv.length) { | ||
const arg = argv[i] | ||
const arg = argv[i]; | ||
@@ -12,3 +12,3 @@ if (arg === name) { | ||
value: "", | ||
} | ||
}; | ||
} | ||
@@ -21,9 +21,9 @@ | ||
value: arg.slice(`${name}=`.length), | ||
} | ||
}; | ||
} | ||
i++ | ||
i++; | ||
} | ||
return null | ||
} | ||
return null; | ||
}; |
@@ -5,16 +5,16 @@ export const guardTooFastSecondCall = ( | ||
) => { | ||
let previousCallMs | ||
let previousCallMs; | ||
return (...args) => { | ||
const nowMs = Date.now() | ||
const nowMs = Date.now(); | ||
if (previousCallMs) { | ||
const msEllapsed = nowMs - previousCallMs | ||
const msEllapsed = nowMs - previousCallMs; | ||
if (msEllapsed < cooldownBetweenFileEvents) { | ||
previousCallMs = null | ||
return | ||
previousCallMs = null; | ||
return; | ||
} | ||
} | ||
previousCallMs = nowMs | ||
callback(...args) | ||
} | ||
} | ||
previousCallMs = nowMs; | ||
callback(...args); | ||
}; | ||
}; | ||
@@ -25,17 +25,17 @@ export const guardTooFastSecondCallPerFile = ( | ||
) => { | ||
const previousCallMsMap = new Map() | ||
const previousCallMsMap = new Map(); | ||
return (fileEvent) => { | ||
const { relativeUrl } = fileEvent | ||
const previousCallMs = previousCallMsMap.get(relativeUrl) | ||
const nowMs = Date.now() | ||
const { relativeUrl } = fileEvent; | ||
const previousCallMs = previousCallMsMap.get(relativeUrl); | ||
const nowMs = Date.now(); | ||
if (previousCallMs) { | ||
const msEllapsed = nowMs - previousCallMs | ||
const msEllapsed = nowMs - previousCallMs; | ||
if (msEllapsed < cooldownBetweenFileEvents) { | ||
previousCallMsMap.delete(relativeUrl) | ||
return | ||
previousCallMsMap.delete(relativeUrl); | ||
return; | ||
} | ||
} | ||
previousCallMsMap.set(relativeUrl, nowMs) | ||
callback(fileEvent) | ||
} | ||
} | ||
previousCallMsMap.set(relativeUrl, nowMs); | ||
callback(fileEvent); | ||
}; | ||
}; |
@@ -6,11 +6,11 @@ // https://github.com/coderaiser/cloudcmd/issues/63#issuecomment-195478143 | ||
// cannot get from fs.constants because they are not available on windows | ||
const S_IRUSR = 256 /* 0000400 read permission, owner */ | ||
const S_IWUSR = 128 /* 0000200 write permission, owner */ | ||
const S_IXUSR = 64 /* 0000100 execute/search permission, owner */ | ||
const S_IRGRP = 32 /* 0000040 read permission, group */ | ||
const S_IWGRP = 16 /* 0000020 write permission, group */ | ||
const S_IXGRP = 8 /* 0000010 execute/search permission, group */ | ||
const S_IROTH = 4 /* 0000004 read permission, others */ | ||
const S_IWOTH = 2 /* 0000002 write permission, others */ | ||
const S_IXOTH = 1 /* 0000001 execute/search permission, others */ | ||
const S_IRUSR = 256; /* 0000400 read permission, owner */ | ||
const S_IWUSR = 128; /* 0000200 write permission, owner */ | ||
const S_IXUSR = 64; /* 0000100 execute/search permission, owner */ | ||
const S_IRGRP = 32; /* 0000040 read permission, group */ | ||
const S_IWGRP = 16; /* 0000020 write permission, group */ | ||
const S_IXGRP = 8; /* 0000010 execute/search permission, group */ | ||
const S_IROTH = 4; /* 0000004 read permission, others */ | ||
const S_IWOTH = 2; /* 0000002 write permission, others */ | ||
const S_IXOTH = 1; /* 0000001 execute/search permission, others */ | ||
@@ -39,3 +39,3 @@ /* | ||
execute: Boolean(binaryFlags & S_IXUSR), | ||
} | ||
}; | ||
@@ -46,3 +46,3 @@ const group = { | ||
execute: Boolean(binaryFlags & S_IXGRP), | ||
} | ||
}; | ||
@@ -53,3 +53,3 @@ const others = { | ||
execute: Boolean(binaryFlags & S_IXOTH), | ||
} | ||
}; | ||
@@ -60,21 +60,21 @@ return { | ||
others, | ||
} | ||
} | ||
}; | ||
}; | ||
export const permissionsToBinaryFlags = ({ owner, group, others }) => { | ||
let binaryFlags = 0 | ||
let binaryFlags = 0; | ||
if (owner.read) binaryFlags |= S_IRUSR | ||
if (owner.write) binaryFlags |= S_IWUSR | ||
if (owner.execute) binaryFlags |= S_IXUSR | ||
if (owner.read) binaryFlags |= S_IRUSR; | ||
if (owner.write) binaryFlags |= S_IWUSR; | ||
if (owner.execute) binaryFlags |= S_IXUSR; | ||
if (group.read) binaryFlags |= S_IRGRP | ||
if (group.write) binaryFlags |= S_IWGRP | ||
if (group.execute) binaryFlags |= S_IXGRP | ||
if (group.read) binaryFlags |= S_IRGRP; | ||
if (group.write) binaryFlags |= S_IWGRP; | ||
if (group.execute) binaryFlags |= S_IXGRP; | ||
if (others.read) binaryFlags |= S_IROTH | ||
if (others.write) binaryFlags |= S_IWOTH | ||
if (others.execute) binaryFlags |= S_IXOTH | ||
if (others.read) binaryFlags |= S_IROTH; | ||
if (others.write) binaryFlags |= S_IWOTH; | ||
if (others.execute) binaryFlags |= S_IXOTH; | ||
return binaryFlags | ||
} | ||
return binaryFlags; | ||
}; |
@@ -1,9 +0,9 @@ | ||
import { statSync } from "node:fs" | ||
import { statSync } from "node:fs"; | ||
import { statsToType } from "./statsToType.js" | ||
import { statsToType } from "./statsToType.js"; | ||
export const readEntryInfo = (url) => { | ||
try { | ||
const stats = statSync(new URL(url)) | ||
const type = statsToType(stats) | ||
const stats = statSync(new URL(url)); | ||
const type = statsToType(stats); | ||
return { | ||
@@ -13,9 +13,9 @@ type, | ||
mtimeMs: stats.mtimeMs, | ||
} | ||
}; | ||
} catch (e) { | ||
if (e.code === "ENOENT") { | ||
return null | ||
return null; | ||
} | ||
throw e | ||
throw e; | ||
} | ||
} | ||
}; |
export const statsToType = (stats) => { | ||
if (stats.isFile()) return "file" | ||
if (stats.isDirectory()) return "directory" | ||
if (stats.isSymbolicLink()) return "symbolic-link" | ||
if (stats.isFIFO()) return "fifo" | ||
if (stats.isSocket()) return "socket" | ||
if (stats.isCharacterDevice()) return "character-device" | ||
if (stats.isBlockDevice()) return "block-device" | ||
return undefined | ||
} | ||
if (stats.isFile()) return "file"; | ||
if (stats.isDirectory()) return "directory"; | ||
if (stats.isSymbolicLink()) return "symbolic-link"; | ||
if (stats.isFIFO()) return "fifo"; | ||
if (stats.isSocket()) return "socket"; | ||
if (stats.isCharacterDevice()) return "character-device"; | ||
if (stats.isBlockDevice()) return "block-device"; | ||
return undefined; | ||
}; |
export const trackResources = () => { | ||
const callbackArray = [] | ||
const callbackArray = []; | ||
@@ -7,16 +7,16 @@ const registerCleanupCallback = (callback) => { | ||
throw new TypeError(`callback must be a function | ||
callback: ${callback}`) | ||
callbackArray.push(callback) | ||
callback: ${callback}`); | ||
callbackArray.push(callback); | ||
return () => { | ||
const index = callbackArray.indexOf(callback) | ||
if (index > -1) callbackArray.splice(index, 1) | ||
} | ||
} | ||
const index = callbackArray.indexOf(callback); | ||
if (index > -1) callbackArray.splice(index, 1); | ||
}; | ||
}; | ||
const cleanup = async (reason) => { | ||
const localCallbackArray = callbackArray.slice() | ||
await Promise.all(localCallbackArray.map((callback) => callback(reason))) | ||
} | ||
const localCallbackArray = callbackArray.slice(); | ||
await Promise.all(localCallbackArray.map((callback) => callback(reason))); | ||
}; | ||
return { registerCleanupCallback, cleanup } | ||
} | ||
return { registerCleanupCallback, cleanup }; | ||
}; |
export const urlTargetsSameFileSystemPath = (leftUrl, rightUrl) => { | ||
if (leftUrl.endsWith("/")) leftUrl = leftUrl.slice(0, -1) | ||
if (rightUrl.endsWith("/")) rightUrl = rightUrl.slice(0, -1) | ||
return leftUrl === rightUrl | ||
} | ||
if (leftUrl.endsWith("/")) leftUrl = leftUrl.slice(0, -1); | ||
if (rightUrl.endsWith("/")) rightUrl = rightUrl.slice(0, -1); | ||
return leftUrl === rightUrl; | ||
}; |
@@ -1,2 +0,2 @@ | ||
import { collectFiles } from "./collectFiles.js" | ||
import { collectFiles } from "./collectFiles.js"; | ||
@@ -9,3 +9,3 @@ export const listFilesMatching = async ({ | ||
if (typeof patterns !== "object" || patterns === null) { | ||
throw new TypeError(`patterns must be an object, got ${patterns}`) | ||
throw new TypeError(`patterns must be an object, got ${patterns}`); | ||
} | ||
@@ -17,4 +17,4 @@ const fileDatas = await collectFiles({ | ||
predicate: ({ matches }) => matches, | ||
}) | ||
return fileDatas.map(({ url }) => url) | ||
} | ||
}); | ||
return fileDatas.map(({ url }) => url); | ||
}; |
export { | ||
validateDirectoryUrl, | ||
assertAndNormalizeDirectoryUrl, | ||
} from "./directory_url_validation.js" | ||
} from "./directory_url_validation.js"; | ||
export { | ||
validateFileUrl, | ||
assertAndNormalizeFileUrl, | ||
} from "./file_url_validation.js" | ||
export { assertDirectoryPresence } from "./assertDirectoryPresence.js" | ||
export { assertFilePresence } from "./assertFilePresence.js" | ||
export { bufferToEtag } from "./bufferToEtag.js" | ||
export { collectDirectoryMatchReport } from "./collectDirectoryMatchReport.js" | ||
export { collectFiles } from "./collectFiles.js" | ||
export { comparePathnames } from "./comparePathnames.js" | ||
export { ensureEmptyDirectory } from "./ensureEmptyDirectory.js" | ||
export { ensureParentDirectories } from "./ensureParentDirectories.js" | ||
export { ensureWindowsDriveLetter } from "./ensureWindowsDriveLetter.js" | ||
export { copyEntry } from "./copyEntry.js" | ||
export { copyDirectoryContent } from "./copyDirectoryContent.js" | ||
export { getRealFileSystemUrlSync } from "./getRealFileSystemUrlSync.js" | ||
export { grantPermissionsOnEntry } from "./grantPermissionsOnEntry.js" | ||
export { listFilesMatching } from "./listFilesMatching.js" | ||
export { moveDirectoryContent } from "./moveDirectoryContent.js" | ||
export { moveEntry } from "./moveEntry.js" | ||
export { readDirectory } from "./readDirectory.js" | ||
export { readFile } from "./readFile.js" | ||
export { readFileSync } from "./readFileSync.js" | ||
export { readEntryModificationTime } from "./readEntryModificationTime.js" | ||
export { readEntryPermissions } from "./readEntryPermissions.js" | ||
export { readEntryStat } from "./readEntryStat.js" | ||
export { readSymbolicLink } from "./readSymbolicLink.js" | ||
export { registerDirectoryLifecycle } from "./registerDirectoryLifecycle.js" | ||
export { registerFileLifecycle } from "./registerFileLifecycle.js" | ||
export { removeEntry } from "./removeEntry.js" | ||
export { testEntryPermissions } from "./testEntryPermissions.js" | ||
export { writeDirectory } from "./writeDirectory.js" | ||
export { writeFile } from "./writeFile.js" | ||
export { writeFileSync } from "./writeFileSync.js" | ||
export { writeEntryModificationTime } from "./writeEntryModificationTime.js" | ||
export { writeEntryPermissions } from "./writeEntryPermissions.js" | ||
export { writeSymbolicLink } from "./writeSymbolicLink.js" | ||
} from "./file_url_validation.js"; | ||
export { assertDirectoryPresence } from "./assertDirectoryPresence.js"; | ||
export { assertFilePresence } from "./assertFilePresence.js"; | ||
export { bufferToEtag } from "./bufferToEtag.js"; | ||
export { collectDirectoryMatchReport } from "./collectDirectoryMatchReport.js"; | ||
export { collectFiles } from "./collectFiles.js"; | ||
export { comparePathnames } from "./comparePathnames.js"; | ||
export { ensureEmptyDirectory } from "./ensureEmptyDirectory.js"; | ||
export { ensureParentDirectories } from "./ensureParentDirectories.js"; | ||
export { ensureWindowsDriveLetter } from "./ensureWindowsDriveLetter.js"; | ||
export { copyEntry } from "./copyEntry.js"; | ||
export { copyDirectoryContent } from "./copyDirectoryContent.js"; | ||
export { getRealFileSystemUrlSync } from "./getRealFileSystemUrlSync.js"; | ||
export { grantPermissionsOnEntry } from "./grantPermissionsOnEntry.js"; | ||
export { listFilesMatching } from "./listFilesMatching.js"; | ||
export { moveDirectoryContent } from "./moveDirectoryContent.js"; | ||
export { moveEntry } from "./moveEntry.js"; | ||
export { readDirectory } from "./readDirectory.js"; | ||
export { readFile } from "./readFile.js"; | ||
export { readFileSync } from "./readFileSync.js"; | ||
export { readEntryModificationTime } from "./readEntryModificationTime.js"; | ||
export { readEntryPermissions } from "./readEntryPermissions.js"; | ||
export { readEntryStat } from "./readEntryStat.js"; | ||
export { readSymbolicLink } from "./readSymbolicLink.js"; | ||
export { registerDirectoryLifecycle } from "./registerDirectoryLifecycle.js"; | ||
export { registerFileLifecycle } from "./registerFileLifecycle.js"; | ||
export { removeEntry } from "./removeEntry.js"; | ||
export { testEntryPermissions } from "./testEntryPermissions.js"; | ||
export { writeDirectory } from "./writeDirectory.js"; | ||
export { writeFile } from "./writeFile.js"; | ||
export { writeFileSync } from "./writeFileSync.js"; | ||
export { writeEntryModificationTime } from "./writeEntryModificationTime.js"; | ||
export { writeEntryPermissions } from "./writeEntryPermissions.js"; | ||
export { writeSymbolicLink } from "./writeSymbolicLink.js"; |
@@ -1,11 +0,11 @@ | ||
import { Abort } from "@jsenv/abort" | ||
import { urlToFileSystemPath, resolveUrl } from "@jsenv/urls" | ||
import { Abort } from "@jsenv/abort"; | ||
import { urlToFileSystemPath, resolveUrl } from "@jsenv/urls"; | ||
import { assertAndNormalizeDirectoryUrl } from "./directory_url_validation.js" | ||
import { readEntryStat } from "./readEntryStat.js" | ||
import { readSymbolicLink } from "./readSymbolicLink.js" | ||
import { readDirectory } from "./readDirectory.js" | ||
import { moveEntry } from "./moveEntry.js" | ||
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js" | ||
import { statsToType } from "./internal/statsToType.js" | ||
import { assertAndNormalizeDirectoryUrl } from "./directory_url_validation.js"; | ||
import { readEntryStat } from "./readEntryStat.js"; | ||
import { readSymbolicLink } from "./readSymbolicLink.js"; | ||
import { readDirectory } from "./readDirectory.js"; | ||
import { moveEntry } from "./moveEntry.js"; | ||
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js"; | ||
import { statsToType } from "./internal/statsToType.js"; | ||
@@ -19,6 +19,6 @@ export const moveDirectoryContent = async ({ | ||
} = {}) => { | ||
const fromUrl = assertAndNormalizeDirectoryUrl(from) | ||
const fromPath = urlToFileSystemPath(fromUrl) | ||
let toUrl = assertAndNormalizeDirectoryUrl(to) | ||
let toPath = urlToFileSystemPath(toUrl) | ||
const fromUrl = assertAndNormalizeDirectoryUrl(from); | ||
const fromPath = urlToFileSystemPath(fromUrl); | ||
let toUrl = assertAndNormalizeDirectoryUrl(to); | ||
let toPath = urlToFileSystemPath(toUrl); | ||
@@ -28,11 +28,11 @@ const sourceStats = await readEntryStat(fromUrl, { | ||
followLink: false, | ||
}) | ||
}); | ||
if (!sourceStats) { | ||
throw new Error(`no directory to move content from at ${fromPath}`) | ||
throw new Error(`no directory to move content from at ${fromPath}`); | ||
} | ||
if (!sourceStats.isDirectory()) { | ||
const sourceType = statsToType(sourceStats) | ||
const sourceType = statsToType(sourceStats); | ||
throw new Error( | ||
`found a ${sourceType} instead of a directory at ${fromPath}`, | ||
) | ||
); | ||
} | ||
@@ -45,20 +45,20 @@ | ||
followLink: false, | ||
}) | ||
}); | ||
if (followLink && destinationStats && destinationStats.isSymbolicLink()) { | ||
const linkTarget = await readSymbolicLink(toUrl) | ||
toUrl = resolveUrl(linkTarget, toUrl) | ||
toPath = urlToFileSystemPath(toUrl) | ||
const linkTarget = await readSymbolicLink(toUrl); | ||
toUrl = resolveUrl(linkTarget, toUrl); | ||
toPath = urlToFileSystemPath(toUrl); | ||
destinationStats = await readEntryStat(toUrl, { | ||
nullIfNotFound: true, | ||
}) | ||
}); | ||
} | ||
if (destinationStats === null) { | ||
throw new Error(`no directory to move content into at ${toPath}`) | ||
throw new Error(`no directory to move content into at ${toPath}`); | ||
} | ||
if (!destinationStats.isDirectory()) { | ||
const destinationType = statsToType(destinationStats) | ||
const destinationType = statsToType(destinationStats); | ||
throw new Error( | ||
`destination leads to a ${destinationType} instead of a directory at ${toPath}`, | ||
) | ||
); | ||
} | ||
@@ -69,14 +69,14 @@ | ||
`cannot move directory content, source and destination are the same (${fromPath})`, | ||
) | ||
); | ||
} | ||
const moveOperation = Abort.startOperation() | ||
moveOperation.addAbortSignal(signal) | ||
const moveOperation = Abort.startOperation(); | ||
moveOperation.addAbortSignal(signal); | ||
try { | ||
moveOperation.throwIfAborted() | ||
const directoryEntries = await readDirectory(fromUrl) | ||
moveOperation.throwIfAborted(); | ||
const directoryEntries = await readDirectory(fromUrl); | ||
await Promise.all( | ||
directoryEntries.map(async (directoryEntry) => { | ||
const from = resolveUrl(directoryEntry, fromUrl) | ||
const to = resolveUrl(directoryEntry, toUrl) | ||
const from = resolveUrl(directoryEntry, fromUrl); | ||
const to = resolveUrl(directoryEntry, toUrl); | ||
@@ -90,9 +90,9 @@ await moveOperation.withSignal(async (signal) => { | ||
followLink, | ||
}) | ||
}) | ||
}); | ||
}); | ||
}), | ||
) | ||
); | ||
} finally { | ||
await moveOperation.end() | ||
await moveOperation.end(); | ||
} | ||
} | ||
}; |
@@ -1,13 +0,13 @@ | ||
import { rename } from "node:fs" | ||
import { Abort } from "@jsenv/abort" | ||
import { urlToFileSystemPath, resolveUrl } from "@jsenv/urls" | ||
import { rename } from "node:fs"; | ||
import { Abort } from "@jsenv/abort"; | ||
import { urlToFileSystemPath, resolveUrl } from "@jsenv/urls"; | ||
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js" | ||
import { statsToType } from "./internal/statsToType.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { ensureParentDirectories } from "./ensureParentDirectories.js" | ||
import { removeEntry } from "./removeEntry.js" | ||
import { copyEntry } from "./copyEntry.js" | ||
import { readEntryStat } from "./readEntryStat.js" | ||
import { readSymbolicLink } from "./readSymbolicLink.js" | ||
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js"; | ||
import { statsToType } from "./internal/statsToType.js"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
import { ensureParentDirectories } from "./ensureParentDirectories.js"; | ||
import { removeEntry } from "./removeEntry.js"; | ||
import { copyEntry } from "./copyEntry.js"; | ||
import { readEntryStat } from "./readEntryStat.js"; | ||
import { readSymbolicLink } from "./readSymbolicLink.js"; | ||
@@ -22,6 +22,6 @@ export const moveEntry = async ({ | ||
}) => { | ||
const fromUrl = assertAndNormalizeFileUrl(from) | ||
const fromPath = urlToFileSystemPath(fromUrl) | ||
let toUrl = assertAndNormalizeFileUrl(to) | ||
let toPath = urlToFileSystemPath(toUrl) | ||
const fromUrl = assertAndNormalizeFileUrl(from); | ||
const fromPath = urlToFileSystemPath(fromUrl); | ||
let toUrl = assertAndNormalizeFileUrl(to); | ||
let toPath = urlToFileSystemPath(toUrl); | ||
@@ -31,5 +31,5 @@ const sourceStats = await readEntryStat(fromUrl, { | ||
followLink: false, | ||
}) | ||
}); | ||
if (!sourceStats) { | ||
throw new Error(`nothing to move from ${fromPath}`) | ||
throw new Error(`nothing to move from ${fromPath}`); | ||
} | ||
@@ -42,11 +42,11 @@ | ||
followLink: false, | ||
}) | ||
}); | ||
if (followLink && destinationStats && destinationStats.isSymbolicLink()) { | ||
const linkTarget = await readSymbolicLink(toUrl) | ||
toUrl = resolveUrl(linkTarget, toUrl) | ||
toPath = urlToFileSystemPath(toUrl) | ||
const linkTarget = await readSymbolicLink(toUrl); | ||
toUrl = resolveUrl(linkTarget, toUrl); | ||
toPath = urlToFileSystemPath(toUrl); | ||
destinationStats = await readEntryStat(toUrl, { | ||
nullIfNotFound: true, | ||
}) | ||
}); | ||
} | ||
@@ -56,16 +56,16 @@ | ||
if (allowUseless) { | ||
return | ||
return; | ||
} | ||
throw new Error( | ||
`no move needed for ${fromPath} because destination and source are the same`, | ||
) | ||
); | ||
} | ||
const moveOperation = Abort.startOperation() | ||
moveOperation.addAbortSignal(signal) | ||
const moveOperation = Abort.startOperation(); | ||
moveOperation.addAbortSignal(signal); | ||
try { | ||
if (destinationStats) { | ||
const sourceType = statsToType(sourceStats) | ||
const destinationType = statsToType(destinationStats) | ||
const sourceType = statsToType(sourceStats); | ||
const destinationType = statsToType(destinationStats); | ||
@@ -75,3 +75,3 @@ if (sourceType !== destinationType) { | ||
`cannot move ${sourceType} from ${fromPath} to ${toPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`, | ||
) | ||
); | ||
} | ||
@@ -81,3 +81,3 @@ if (!overwrite) { | ||
`cannot move ${sourceType} from ${fromPath} to ${toPath} because destination exists and overwrite option is disabled`, | ||
) | ||
); | ||
} | ||
@@ -89,8 +89,8 @@ | ||
recursive: true, | ||
}) | ||
}); | ||
} else { | ||
await ensureParentDirectories(toUrl) | ||
await ensureParentDirectories(toUrl); | ||
} | ||
moveOperation.throwIfAborted() | ||
moveOperation.throwIfAborted(); | ||
await moveNaive(fromPath, toPath, { | ||
@@ -102,13 +102,13 @@ handleCrossDeviceError: async () => { | ||
preserveStat: true, | ||
}) | ||
}); | ||
await removeEntry(fromUrl, { | ||
signal: moveOperation.signal, | ||
recursive: true, | ||
}) | ||
}); | ||
}, | ||
}) | ||
}); | ||
} finally { | ||
await moveOperation.end() | ||
await moveOperation.end(); | ||
} | ||
} | ||
}; | ||
@@ -124,11 +124,11 @@ const moveNaive = ( | ||
if (handleCrossDeviceError && error.code === "EXDEV") { | ||
resolve(handleCrossDeviceError(error)) | ||
resolve(handleCrossDeviceError(error)); | ||
} else { | ||
reject(error) | ||
reject(error); | ||
} | ||
} else { | ||
resolve() | ||
resolve(); | ||
} | ||
}) | ||
}) | ||
} | ||
}); | ||
}); | ||
}; |
@@ -1,9 +0,9 @@ | ||
import { readdir } from "node:fs" | ||
import { assertAndNormalizeDirectoryUrl } from "./directory_url_validation.js" | ||
import { readdir } from "node:fs"; | ||
import { assertAndNormalizeDirectoryUrl } from "./directory_url_validation.js"; | ||
export const readDirectory = async (url, { emfileMaxWait = 1000 } = {}) => { | ||
const directoryUrl = assertAndNormalizeDirectoryUrl(url) | ||
const directoryUrlObject = new URL(directoryUrl) | ||
const startMs = Date.now() | ||
let attemptCount = 0 | ||
const directoryUrl = assertAndNormalizeDirectoryUrl(url); | ||
const directoryUrlObject = new URL(directoryUrl); | ||
const startMs = Date.now(); | ||
let attemptCount = 0; | ||
@@ -15,26 +15,26 @@ const attempt = async () => { | ||
if (error) { | ||
reject(error) | ||
reject(error); | ||
} else { | ||
resolve(names) | ||
resolve(names); | ||
} | ||
}) | ||
}) | ||
return names.map(encodeURIComponent) | ||
}); | ||
}); | ||
return names.map(encodeURIComponent); | ||
} catch (e) { | ||
// https://nodejs.org/dist/latest-v13.x/docs/api/errors.html#errors_common_system_errors | ||
if (e.code === "EMFILE" || e.code === "ENFILE") { | ||
attemptCount++ | ||
const nowMs = Date.now() | ||
const timeSpentWaiting = nowMs - startMs | ||
attemptCount++; | ||
const nowMs = Date.now(); | ||
const timeSpentWaiting = nowMs - startMs; | ||
if (timeSpentWaiting > emfileMaxWait) { | ||
throw e | ||
throw e; | ||
} | ||
await new Promise((resolve) => setTimeout(resolve), attemptCount) | ||
return await attempt() | ||
await new Promise((resolve) => setTimeout(resolve), attemptCount); | ||
return await attempt(); | ||
} | ||
throw e | ||
throw e; | ||
} | ||
} | ||
}; | ||
return attempt() | ||
} | ||
return attempt(); | ||
}; |
@@ -1,6 +0,6 @@ | ||
import { readEntryStat } from "./readEntryStat.js" | ||
import { readEntryStat } from "./readEntryStat.js"; | ||
export const readEntryModificationTime = async (source) => { | ||
const stats = await readEntryStat(source) | ||
return Math.floor(stats.mtimeMs) | ||
} | ||
const stats = await readEntryStat(source); | ||
return Math.floor(stats.mtimeMs); | ||
}; |
@@ -6,16 +6,16 @@ /* | ||
import { promises } from "node:fs" | ||
import { urlToFileSystemPath } from "@jsenv/urls" | ||
import { promises } from "node:fs"; | ||
import { urlToFileSystemPath } from "@jsenv/urls"; | ||
import { binaryFlagsToPermissions } from "./internal/permissions.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { binaryFlagsToPermissions } from "./internal/permissions.js"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
const { stat } = promises | ||
const { stat } = promises; | ||
export const readEntryPermissions = async (source) => { | ||
const sourceUrl = assertAndNormalizeFileUrl(source) | ||
const sourcePath = urlToFileSystemPath(sourceUrl) | ||
const sourceUrl = assertAndNormalizeFileUrl(source); | ||
const sourcePath = urlToFileSystemPath(sourceUrl); | ||
const { mode } = await stat(sourcePath) | ||
return binaryFlagsToPermissions(mode) | ||
} | ||
const { mode } = await stat(sourcePath); | ||
return binaryFlagsToPermissions(mode); | ||
}; |
@@ -6,9 +6,9 @@ /* | ||
import { lstat, stat } from "node:fs" | ||
import { urlToFileSystemPath } from "@jsenv/urls" | ||
import { lstat, stat } from "node:fs"; | ||
import { urlToFileSystemPath } from "@jsenv/urls"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { writeEntryPermissions } from "./writeEntryPermissions.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
import { writeEntryPermissions } from "./writeEntryPermissions.js"; | ||
const isWindows = process.platform === "win32" | ||
const isWindows = process.platform === "win32"; | ||
@@ -19,6 +19,6 @@ export const readEntryStat = async ( | ||
) => { | ||
let sourceUrl = assertAndNormalizeFileUrl(source) | ||
if (sourceUrl.endsWith("/")) sourceUrl = sourceUrl.slice(0, -1) | ||
let sourceUrl = assertAndNormalizeFileUrl(source); | ||
if (sourceUrl.endsWith("/")) sourceUrl = sourceUrl.slice(0, -1); | ||
const sourcePath = urlToFileSystemPath(sourceUrl) | ||
const sourcePath = urlToFileSystemPath(sourceUrl); | ||
@@ -29,3 +29,3 @@ const handleNotFoundOption = nullIfNotFound | ||
} | ||
: {} | ||
: {}; | ||
@@ -41,3 +41,3 @@ return readStat(sourcePath, { | ||
`trying to fix windows EPERM after stats on ${sourcePath}`, | ||
) | ||
); | ||
@@ -48,3 +48,3 @@ try { | ||
// (because reading current permission would also throw) | ||
await writeEntryPermissions(sourceUrl, 0o666) | ||
await writeEntryPermissions(sourceUrl, 0o666); | ||
const stats = await readStat(sourcePath, { | ||
@@ -55,12 +55,12 @@ followLink, | ||
handlePermissionDeniedError: () => { | ||
console.error(`still got EPERM after stats on ${sourcePath}`) | ||
throw error | ||
console.error(`still got EPERM after stats on ${sourcePath}`); | ||
throw error; | ||
}, | ||
}) | ||
return stats | ||
}); | ||
return stats; | ||
} catch (e) { | ||
console.error( | ||
`error while trying to fix windows EPERM after stats on ${sourcePath}: ${e.stack}`, | ||
) | ||
throw error | ||
); | ||
throw error; | ||
} | ||
@@ -70,4 +70,4 @@ }, | ||
: {}), | ||
}) | ||
} | ||
}); | ||
}; | ||
@@ -82,3 +82,3 @@ const readStat = ( | ||
) => { | ||
const nodeMethod = followLink ? stat : lstat | ||
const nodeMethod = followLink ? stat : lstat; | ||
@@ -89,3 +89,3 @@ return new Promise((resolve, reject) => { | ||
if (handleNotFoundError && error.code === "ENOENT") { | ||
resolve(handleNotFoundError(error)) | ||
resolve(handleNotFoundError(error)); | ||
} else if ( | ||
@@ -95,11 +95,11 @@ handlePermissionDeniedError && | ||
) { | ||
resolve(handlePermissionDeniedError(error)) | ||
resolve(handlePermissionDeniedError(error)); | ||
} else { | ||
reject(error) | ||
reject(error); | ||
} | ||
} else { | ||
resolve(statsObject) | ||
resolve(statsObject); | ||
} | ||
}) | ||
}) | ||
} | ||
}); | ||
}); | ||
}; |
@@ -1,28 +0,28 @@ | ||
import { readFile as readFileNode } from "node:fs" | ||
import { readFile as readFileNode } from "node:fs"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
export const readFile = async (value, { as = "buffer" } = {}) => { | ||
const fileUrl = assertAndNormalizeFileUrl(value) | ||
const fileUrl = assertAndNormalizeFileUrl(value); | ||
const buffer = await new Promise((resolve, reject) => { | ||
readFileNode(new URL(fileUrl), (error, buffer) => { | ||
if (error) { | ||
reject(error) | ||
reject(error); | ||
} else { | ||
resolve(buffer) | ||
resolve(buffer); | ||
} | ||
}) | ||
}) | ||
}); | ||
}); | ||
if (as === "buffer") { | ||
return buffer | ||
return buffer; | ||
} | ||
if (as === "string") { | ||
return buffer.toString() | ||
return buffer.toString(); | ||
} | ||
if (as === "json") { | ||
return JSON.parse(buffer.toString()) | ||
return JSON.parse(buffer.toString()); | ||
} | ||
throw new Error( | ||
`"as" must be one of "buffer","string","json" received "${as}"`, | ||
) | ||
} | ||
); | ||
}; |
@@ -1,20 +0,20 @@ | ||
import { readFileSync as readFileSyncNode } from "node:fs" | ||
import { readFileSync as readFileSyncNode } from "node:fs"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
export const readFileSync = (value, { as = "buffer" } = {}) => { | ||
const fileUrl = assertAndNormalizeFileUrl(value) | ||
const buffer = readFileSyncNode(new URL(fileUrl)) | ||
const fileUrl = assertAndNormalizeFileUrl(value); | ||
const buffer = readFileSyncNode(new URL(fileUrl)); | ||
if (as === "buffer") { | ||
return buffer | ||
return buffer; | ||
} | ||
if (as === "string") { | ||
return buffer.toString() | ||
return buffer.toString(); | ||
} | ||
if (as === "json") { | ||
return JSON.parse(buffer.toString()) | ||
return JSON.parse(buffer.toString()); | ||
} | ||
throw new Error( | ||
`"as" must be one of "buffer","string","json" received "${as}"`, | ||
) | ||
} | ||
); | ||
}; |
@@ -6,9 +6,9 @@ /* | ||
import { readlink } from "node:fs" | ||
import { isFileSystemPath, fileSystemPathToUrl } from "@jsenv/urls" | ||
import { readlink } from "node:fs"; | ||
import { isFileSystemPath, fileSystemPathToUrl } from "@jsenv/urls"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
export const readSymbolicLink = (url) => { | ||
const symbolicLinkUrl = assertAndNormalizeFileUrl(url) | ||
const symbolicLinkUrl = assertAndNormalizeFileUrl(url); | ||
@@ -18,3 +18,3 @@ return new Promise((resolve, reject) => { | ||
if (error) { | ||
reject(error) | ||
reject(error); | ||
} else { | ||
@@ -25,6 +25,6 @@ resolve( | ||
: resolvedPath.replace(/\\/g, "/"), // replace back slashes with slashes | ||
) | ||
); | ||
} | ||
}) | ||
}) | ||
} | ||
}); | ||
}); | ||
}; |
@@ -1,14 +0,14 @@ | ||
import { readdirSync, statSync } from "node:fs" | ||
import { URL_META } from "@jsenv/url-meta" | ||
import { urlToFileSystemPath, urlToRelativeUrl } from "@jsenv/urls" | ||
import { readdirSync, statSync } from "node:fs"; | ||
import { URL_META } from "@jsenv/url-meta"; | ||
import { urlToFileSystemPath, urlToRelativeUrl } from "@jsenv/urls"; | ||
import { assertAndNormalizeDirectoryUrl } from "./directory_url_validation.js" | ||
import { statsToType } from "./internal/statsToType.js" | ||
import { guardTooFastSecondCallPerFile } from "./internal/guard_second_call.js" | ||
import { createWatcher } from "./internal/createWatcher.js" | ||
import { trackResources } from "./internal/track_resources.js" | ||
import { assertAndNormalizeDirectoryUrl } from "./directory_url_validation.js"; | ||
import { statsToType } from "./internal/statsToType.js"; | ||
import { guardTooFastSecondCallPerFile } from "./internal/guard_second_call.js"; | ||
import { createWatcher } from "./internal/createWatcher.js"; | ||
import { trackResources } from "./internal/track_resources.js"; | ||
const isLinux = process.platform === "linux" | ||
const isLinux = process.platform === "linux"; | ||
// linux does not support recursive option | ||
const fsWatchSupportsRecursive = !isLinux | ||
const fsWatchSupportsRecursive = !isLinux; | ||
@@ -36,5 +36,5 @@ export const registerDirectoryLifecycle = ( | ||
) => { | ||
const sourceUrl = assertAndNormalizeDirectoryUrl(source) | ||
const sourceUrl = assertAndNormalizeDirectoryUrl(source); | ||
if (!undefinedOrFunction(added)) { | ||
throw new TypeError(`added must be a function or undefined, got ${added}`) | ||
throw new TypeError(`added must be a function or undefined, got ${added}`); | ||
} | ||
@@ -44,3 +44,3 @@ if (!undefinedOrFunction(updated)) { | ||
`updated must be a function or undefined, got ${updated}`, | ||
) | ||
); | ||
} | ||
@@ -50,7 +50,7 @@ if (!undefinedOrFunction(removed)) { | ||
`removed must be a function or undefined, got ${removed}`, | ||
) | ||
); | ||
} | ||
if (cooldownBetweenFileEvents) { | ||
if (added) { | ||
added = guardTooFastSecondCallPerFile(added, cooldownBetweenFileEvents) | ||
added = guardTooFastSecondCallPerFile(added, cooldownBetweenFileEvents); | ||
} | ||
@@ -61,3 +61,3 @@ if (updated) { | ||
cooldownBetweenFileEvents, | ||
) | ||
); | ||
} | ||
@@ -68,3 +68,3 @@ if (removed) { | ||
cooldownBetweenFileEvents, | ||
) | ||
); | ||
} | ||
@@ -76,6 +76,6 @@ } | ||
sourceUrl, | ||
) | ||
); | ||
const getWatchPatternValue = ({ url, type }) => { | ||
if (type === "directory") { | ||
let firstMeta = false | ||
let firstMeta = false; | ||
URL_META.urlChildMayMatch({ | ||
@@ -86,23 +86,23 @@ url: `${url}/`, | ||
if (watch) { | ||
firstMeta = watch | ||
firstMeta = watch; | ||
} | ||
return watch | ||
return watch; | ||
}, | ||
}) | ||
return firstMeta | ||
}); | ||
return firstMeta; | ||
} | ||
const { watch } = URL_META.applyAssociations({ url, associations }) | ||
return watch | ||
} | ||
const tracker = trackResources() | ||
const infoMap = new Map() | ||
const { watch } = URL_META.applyAssociations({ url, associations }); | ||
return watch; | ||
}; | ||
const tracker = trackResources(); | ||
const infoMap = new Map(); | ||
const readEntryInfo = (url) => { | ||
try { | ||
const relativeUrl = urlToRelativeUrl(url, source) | ||
const previousInfo = infoMap.get(relativeUrl) | ||
const stats = statSync(new URL(url)) | ||
const type = statsToType(stats) | ||
const relativeUrl = urlToRelativeUrl(url, source); | ||
const previousInfo = infoMap.get(relativeUrl); | ||
const stats = statSync(new URL(url)); | ||
const type = statsToType(stats); | ||
const patternValue = previousInfo | ||
? previousInfo.patternValue | ||
: getWatchPatternValue({ url, type }) | ||
: getWatchPatternValue({ url, type }); | ||
return { | ||
@@ -116,10 +116,10 @@ previousInfo, | ||
patternValue, | ||
} | ||
}; | ||
} catch (e) { | ||
if (e.code === "ENOENT") { | ||
return null | ||
return null; | ||
} | ||
throw e | ||
throw e; | ||
} | ||
} | ||
}; | ||
@@ -133,15 +133,15 @@ const handleDirectoryEvent = ({ | ||
if (directoryRelativeUrl) { | ||
handleChange(`${directoryRelativeUrl}/${filename}`) | ||
return | ||
handleChange(`${directoryRelativeUrl}/${filename}`); | ||
return; | ||
} | ||
handleChange(`${filename}`) | ||
return | ||
handleChange(`${filename}`); | ||
return; | ||
} | ||
if (eventType === "rename") { | ||
if (!removed && !added) { | ||
return | ||
return; | ||
} | ||
// we might receive `rename` without filename | ||
// in that case we try to find ourselves which file was removed. | ||
let relativeUrlCandidateArray = Array.from(infoMap.keys()) | ||
let relativeUrlCandidateArray = Array.from(infoMap.keys()); | ||
if (recursive && !fsWatchSupportsRecursive) { | ||
@@ -153,20 +153,20 @@ relativeUrlCandidateArray = relativeUrlCandidateArray.filter( | ||
if (relativeUrlCandidate.includes("/")) { | ||
return false | ||
return false; | ||
} | ||
return true | ||
return true; | ||
} | ||
// entry not inside this directory | ||
if (!relativeUrlCandidate.startsWith(directoryRelativeUrl)) { | ||
return false | ||
return false; | ||
} | ||
const afterDirectory = relativeUrlCandidate.slice( | ||
directoryRelativeUrl.length + 1, | ||
) | ||
); | ||
// deep inside this directory | ||
if (afterDirectory.includes("/")) { | ||
return false | ||
return false; | ||
} | ||
return true | ||
return true; | ||
}, | ||
) | ||
); | ||
} | ||
@@ -176,41 +176,41 @@ const removedEntryRelativeUrl = relativeUrlCandidateArray.find( | ||
try { | ||
statSync(new URL(relativeUrlCandidate, sourceUrl)) | ||
return false | ||
statSync(new URL(relativeUrlCandidate, sourceUrl)); | ||
return false; | ||
} catch (e) { | ||
if (e.code === "ENOENT") { | ||
return true | ||
return true; | ||
} | ||
throw e | ||
throw e; | ||
} | ||
}, | ||
) | ||
); | ||
if (removedEntryRelativeUrl) { | ||
handleEntryLost(infoMap.get(removedEntryRelativeUrl)) | ||
handleEntryLost(infoMap.get(removedEntryRelativeUrl)); | ||
} | ||
} | ||
} | ||
}; | ||
const handleChange = (relativeUrl) => { | ||
const entryUrl = new URL(relativeUrl, sourceUrl).href | ||
const entryInfo = readEntryInfo(entryUrl) | ||
const entryUrl = new URL(relativeUrl, sourceUrl).href; | ||
const entryInfo = readEntryInfo(entryUrl); | ||
if (!entryInfo) { | ||
const previousEntryInfo = infoMap.get(relativeUrl) | ||
const previousEntryInfo = infoMap.get(relativeUrl); | ||
if (!previousEntryInfo) { | ||
// on MacOS it's possible to receive a "rename" event for | ||
// a file that does not exists... | ||
return | ||
return; | ||
} | ||
if (debug) { | ||
console.debug(`"${relativeUrl}" removed`) | ||
console.debug(`"${relativeUrl}" removed`); | ||
} | ||
handleEntryLost(previousEntryInfo) | ||
return | ||
handleEntryLost(previousEntryInfo); | ||
return; | ||
} | ||
const { previousInfo } = entryInfo | ||
const { previousInfo } = entryInfo; | ||
if (!previousInfo) { | ||
if (debug) { | ||
console.debug(`"${relativeUrl}" added`) | ||
console.debug(`"${relativeUrl}" added`); | ||
} | ||
handleEntryFound(entryInfo) | ||
return | ||
handleEntryFound(entryInfo); | ||
return; | ||
} | ||
@@ -221,5 +221,5 @@ if (entryInfo.type !== previousInfo.type) { | ||
// is lost and something else is found (call removed() then added()) | ||
handleEntryLost(previousInfo) | ||
handleEntryFound(entryInfo) | ||
return | ||
handleEntryLost(previousInfo); | ||
handleEntryFound(entryInfo); | ||
return; | ||
} | ||
@@ -231,3 +231,3 @@ if (entryInfo.type === "directory") { | ||
// we'll already be notified about what has changed | ||
return | ||
return; | ||
} | ||
@@ -247,17 +247,17 @@ // something has changed at this relativeUrl (the file existed and was not deleted) | ||
if (debug) { | ||
console.debug(`"${relativeUrl}" modified`) | ||
console.debug(`"${relativeUrl}" modified`); | ||
} | ||
handleEntryUpdated(entryInfo) | ||
} | ||
handleEntryUpdated(entryInfo); | ||
}; | ||
const handleEntryFound = (entryInfo, { notify = true } = {}) => { | ||
infoMap.set(entryInfo.relativeUrl, entryInfo) | ||
infoMap.set(entryInfo.relativeUrl, entryInfo); | ||
if (entryInfo.type === "directory") { | ||
const directoryUrl = `${entryInfo.url}/` | ||
const directoryUrl = `${entryInfo.url}/`; | ||
readdirSync(new URL(directoryUrl)).forEach((entryName) => { | ||
const childEntryUrl = new URL(entryName, directoryUrl).href | ||
const childEntryInfo = readEntryInfo(childEntryUrl) | ||
const childEntryUrl = new URL(entryName, directoryUrl).href; | ||
const childEntryInfo = readEntryInfo(childEntryUrl); | ||
if (childEntryInfo && childEntryInfo.patternValue) { | ||
handleEntryFound(childEntryInfo, { notify }) | ||
handleEntryFound(childEntryInfo, { notify }); | ||
} | ||
}) | ||
}); | ||
// we must watch manually every directory we find | ||
@@ -267,6 +267,6 @@ if (!fsWatchSupportsRecursive) { | ||
persistent: keepProcessAlive, | ||
}) | ||
}); | ||
tracker.registerCleanupCallback(() => { | ||
watcher.close() | ||
}) | ||
watcher.close(); | ||
}); | ||
watcher.on("change", (eventType, filename) => { | ||
@@ -280,4 +280,4 @@ handleDirectoryEvent({ | ||
eventType, | ||
}) | ||
}) | ||
}); | ||
}); | ||
} | ||
@@ -291,7 +291,7 @@ } | ||
mtime: entryInfo.mtimeMs, | ||
}) | ||
}); | ||
} | ||
} | ||
}; | ||
const handleEntryLost = (entryInfo) => { | ||
infoMap.delete(entryInfo.relativeUrl) | ||
infoMap.delete(entryInfo.relativeUrl); | ||
if (removed && entryInfo.patternValue) { | ||
@@ -303,7 +303,7 @@ removed({ | ||
mtime: entryInfo.mtimeMs, | ||
}) | ||
}); | ||
} | ||
} | ||
}; | ||
const handleEntryUpdated = (entryInfo) => { | ||
infoMap.set(entryInfo.relativeUrl, entryInfo) | ||
infoMap.set(entryInfo.relativeUrl, entryInfo); | ||
if (updated && entryInfo.patternValue) { | ||
@@ -316,19 +316,19 @@ updated({ | ||
previousMtime: entryInfo.previousInfo.mtimeMs, | ||
}) | ||
}); | ||
} | ||
} | ||
}; | ||
readdirSync(new URL(sourceUrl)).forEach((entry) => { | ||
const entryUrl = new URL(entry, sourceUrl).href | ||
const entryInfo = readEntryInfo(entryUrl) | ||
const entryUrl = new URL(entry, sourceUrl).href; | ||
const entryInfo = readEntryInfo(entryUrl); | ||
if (entryInfo && entryInfo.patternValue) { | ||
handleEntryFound(entryInfo, { | ||
notify: notifyExistent, | ||
}) | ||
}); | ||
} | ||
}) | ||
}); | ||
if (debug) { | ||
const relativeUrls = Array.from(infoMap.keys()) | ||
const relativeUrls = Array.from(infoMap.keys()); | ||
if (relativeUrls.length === 0) { | ||
console.debug(`No file found`) | ||
console.debug(`No file found`); | ||
} else { | ||
@@ -338,3 +338,3 @@ console.debug( | ||
${relativeUrls.join("\n")}`, | ||
) | ||
); | ||
} | ||
@@ -345,6 +345,6 @@ } | ||
persistent: keepProcessAlive, | ||
}) | ||
}); | ||
tracker.registerCleanupCallback(() => { | ||
watcher.close() | ||
}) | ||
watcher.close(); | ||
}); | ||
watcher.on("change", (eventType, fileSystemPath) => { | ||
@@ -354,11 +354,11 @@ handleDirectoryEvent({ | ||
eventType, | ||
}) | ||
}) | ||
}); | ||
}); | ||
return tracker.cleanup | ||
} | ||
return tracker.cleanup; | ||
}; | ||
const undefinedOrFunction = (value) => { | ||
return typeof value === "undefined" || typeof value === "function" | ||
} | ||
return typeof value === "undefined" || typeof value === "function"; | ||
}; | ||
@@ -370,7 +370,7 @@ const fileSystemPathToDirectoryRelativeUrlAndFilename = (path) => { | ||
filename: "", | ||
} | ||
}; | ||
} | ||
const normalizedPath = path.replace(/\\/g, "/") // replace back slashes with slashes | ||
const slashLastIndex = normalizedPath.lastIndexOf("/") | ||
const normalizedPath = path.replace(/\\/g, "/"); // replace back slashes with slashes | ||
const slashLastIndex = normalizedPath.lastIndexOf("/"); | ||
if (slashLastIndex === -1) { | ||
@@ -380,11 +380,11 @@ return { | ||
filename: normalizedPath, | ||
} | ||
}; | ||
} | ||
const directoryRelativeUrl = normalizedPath.slice(0, slashLastIndex) | ||
const filename = normalizedPath.slice(slashLastIndex + 1) | ||
const directoryRelativeUrl = normalizedPath.slice(0, slashLastIndex); | ||
const filename = normalizedPath.slice(slashLastIndex + 1); | ||
return { | ||
directoryRelativeUrl, | ||
filename, | ||
} | ||
} | ||
}; | ||
}; |
@@ -1,10 +0,10 @@ | ||
import { statSync } from "node:fs" | ||
import { dirname, basename } from "node:path" | ||
import { urlToFileSystemPath } from "@jsenv/urls" | ||
import { statSync } from "node:fs"; | ||
import { dirname, basename } from "node:path"; | ||
import { urlToFileSystemPath } from "@jsenv/urls"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { guardTooFastSecondCall } from "./internal/guard_second_call.js" | ||
import { statsToType } from "./internal/statsToType.js" | ||
import { createWatcher } from "./internal/createWatcher.js" | ||
import { trackResources } from "./internal/track_resources.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
import { guardTooFastSecondCall } from "./internal/guard_second_call.js"; | ||
import { statsToType } from "./internal/statsToType.js"; | ||
import { createWatcher } from "./internal/createWatcher.js"; | ||
import { trackResources } from "./internal/track_resources.js"; | ||
@@ -22,5 +22,5 @@ export const registerFileLifecycle = ( | ||
) => { | ||
const sourceUrl = assertAndNormalizeFileUrl(source) | ||
const sourceUrl = assertAndNormalizeFileUrl(source); | ||
if (!undefinedOrFunction(added)) { | ||
throw new TypeError(`added must be a function or undefined, got ${added}`) | ||
throw new TypeError(`added must be a function or undefined, got ${added}`); | ||
} | ||
@@ -30,3 +30,3 @@ if (!undefinedOrFunction(updated)) { | ||
`updated must be a function or undefined, got ${updated}`, | ||
) | ||
); | ||
} | ||
@@ -36,17 +36,17 @@ if (!undefinedOrFunction(removed)) { | ||
`removed must be a function or undefined, got ${removed}`, | ||
) | ||
); | ||
} | ||
if (cooldownBetweenFileEvents) { | ||
if (added) { | ||
added = guardTooFastSecondCall(added, cooldownBetweenFileEvents) | ||
added = guardTooFastSecondCall(added, cooldownBetweenFileEvents); | ||
} | ||
if (updated) { | ||
updated = guardTooFastSecondCall(updated, cooldownBetweenFileEvents) | ||
updated = guardTooFastSecondCall(updated, cooldownBetweenFileEvents); | ||
} | ||
if (removed) { | ||
removed = guardTooFastSecondCall(removed, cooldownBetweenFileEvents) | ||
removed = guardTooFastSecondCall(removed, cooldownBetweenFileEvents); | ||
} | ||
} | ||
const tracker = trackResources() | ||
const tracker = trackResources(); | ||
@@ -57,13 +57,13 @@ const handleFileFound = ({ existent }) => { | ||
removed: () => { | ||
fileMutationStopTracking() | ||
watchFileAdded() | ||
fileMutationStopTracking(); | ||
watchFileAdded(); | ||
if (removed) { | ||
removed() | ||
removed(); | ||
} | ||
}, | ||
keepProcessAlive, | ||
}) | ||
}); | ||
const fileMutationStopTracking = tracker.registerCleanupCallback( | ||
fileMutationStopWatching, | ||
) | ||
); | ||
@@ -73,9 +73,9 @@ if (added) { | ||
if (notifyExistent) { | ||
added({ existent: true }) | ||
added({ existent: true }); | ||
} | ||
} else { | ||
added({}) | ||
added({}); | ||
} | ||
} | ||
} | ||
}; | ||
@@ -86,23 +86,23 @@ const watchFileAdded = () => { | ||
() => { | ||
fileCreationgStopTracking() | ||
handleFileFound({ existent: false }) | ||
fileCreationgStopTracking(); | ||
handleFileFound({ existent: false }); | ||
}, | ||
keepProcessAlive, | ||
) | ||
); | ||
const fileCreationgStopTracking = tracker.registerCleanupCallback( | ||
fileCreationStopWatching, | ||
) | ||
} | ||
); | ||
}; | ||
const sourceType = entryToTypeOrNull(sourceUrl) | ||
const sourceType = entryToTypeOrNull(sourceUrl); | ||
if (sourceType === null) { | ||
if (added) { | ||
watchFileAdded() | ||
watchFileAdded(); | ||
} else { | ||
throw new Error( | ||
`${urlToFileSystemPath(sourceUrl)} must lead to a file, found nothing`, | ||
) | ||
); | ||
} | ||
} else if (sourceType === "file") { | ||
handleFileFound({ existent: true }) | ||
handleFileFound({ existent: true }); | ||
} else { | ||
@@ -113,49 +113,49 @@ throw new Error( | ||
)} must lead to a file, type found instead`, | ||
) | ||
); | ||
} | ||
return tracker.cleanup | ||
} | ||
return tracker.cleanup; | ||
}; | ||
const entryToTypeOrNull = (url) => { | ||
try { | ||
const stats = statSync(new URL(url)) | ||
return statsToType(stats) | ||
const stats = statSync(new URL(url)); | ||
return statsToType(stats); | ||
} catch (e) { | ||
if (e.code === "ENOENT") { | ||
return null | ||
return null; | ||
} | ||
throw e | ||
throw e; | ||
} | ||
} | ||
}; | ||
const undefinedOrFunction = (value) => | ||
typeof value === "undefined" || typeof value === "function" | ||
typeof value === "undefined" || typeof value === "function"; | ||
const watchFileCreation = (source, callback, keepProcessAlive) => { | ||
const sourcePath = urlToFileSystemPath(source) | ||
const sourceFilename = basename(sourcePath) | ||
const directoryPath = dirname(sourcePath) | ||
const sourcePath = urlToFileSystemPath(source); | ||
const sourceFilename = basename(sourcePath); | ||
const directoryPath = dirname(sourcePath); | ||
let directoryWatcher = createWatcher(directoryPath, { | ||
persistent: keepProcessAlive, | ||
}) | ||
}); | ||
directoryWatcher.on("change", (eventType, filename) => { | ||
if (filename && filename !== sourceFilename) return | ||
if (filename && filename !== sourceFilename) return; | ||
const type = entryToTypeOrNull(source) | ||
const type = entryToTypeOrNull(source); | ||
// ignore if something else with that name gets created | ||
// we are only interested into files | ||
if (type !== "file") return | ||
if (type !== "file") return; | ||
directoryWatcher.close() | ||
directoryWatcher = undefined | ||
callback() | ||
}) | ||
directoryWatcher.close(); | ||
directoryWatcher = undefined; | ||
callback(); | ||
}); | ||
return () => { | ||
if (directoryWatcher) { | ||
directoryWatcher.close() | ||
directoryWatcher.close(); | ||
} | ||
} | ||
} | ||
}; | ||
}; | ||
@@ -168,25 +168,25 @@ const watchFileMutation = ( | ||
persistent: keepProcessAlive, | ||
}) | ||
}); | ||
watcher.on("change", () => { | ||
const sourceType = entryToTypeOrNull(sourceUrl) | ||
const sourceType = entryToTypeOrNull(sourceUrl); | ||
if (sourceType === null) { | ||
watcher.close() | ||
watcher = undefined | ||
watcher.close(); | ||
watcher = undefined; | ||
if (removed) { | ||
removed() | ||
removed(); | ||
} | ||
} else if (sourceType === "file") { | ||
if (updated) { | ||
updated() | ||
updated(); | ||
} | ||
} | ||
}) | ||
}); | ||
return () => { | ||
if (watcher) { | ||
watcher.close() | ||
watcher.close(); | ||
} | ||
} | ||
} | ||
}; | ||
}; |
@@ -1,3 +0,3 @@ | ||
import { unlink, rmdir, openSync, closeSync } from "node:fs" | ||
import { Abort } from "@jsenv/abort" | ||
import { unlink, rmdir, openSync, closeSync } from "node:fs"; | ||
import { Abort } from "@jsenv/abort"; | ||
import { | ||
@@ -7,7 +7,7 @@ ensurePathnameTrailingSlash, | ||
resolveUrl, | ||
} from "@jsenv/urls" | ||
} from "@jsenv/urls"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { readEntryStat } from "./readEntryStat.js" | ||
import { readDirectory } from "./readDirectory.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
import { readEntryStat } from "./readEntryStat.js"; | ||
import { readDirectory } from "./readDirectory.js"; | ||
@@ -25,18 +25,18 @@ export const removeEntry = async ( | ||
) => { | ||
const sourceUrl = assertAndNormalizeFileUrl(source) | ||
const sourceUrl = assertAndNormalizeFileUrl(source); | ||
const removeOperation = Abort.startOperation() | ||
removeOperation.addAbortSignal(signal) | ||
const removeOperation = Abort.startOperation(); | ||
removeOperation.addAbortSignal(signal); | ||
try { | ||
removeOperation.throwIfAborted() | ||
removeOperation.throwIfAborted(); | ||
const sourceStats = await readEntryStat(sourceUrl, { | ||
nullIfNotFound: true, | ||
followLink: false, | ||
}) | ||
}); | ||
if (!sourceStats) { | ||
if (allowUseless) { | ||
return | ||
return; | ||
} | ||
throw new Error(`nothing to remove at ${urlToFileSystemPath(sourceUrl)}`) | ||
throw new Error(`nothing to remove at ${urlToFileSystemPath(sourceUrl)}`); | ||
} | ||
@@ -59,3 +59,3 @@ | ||
}, | ||
) | ||
); | ||
} else if (sourceStats.isDirectory()) { | ||
@@ -68,13 +68,13 @@ await removeDirectory(ensurePathnameTrailingSlash(sourceUrl), { | ||
onlyContent, | ||
}) | ||
}); | ||
} | ||
} finally { | ||
await removeOperation.end() | ||
await removeOperation.end(); | ||
} | ||
} | ||
}; | ||
const removeNonDirectory = (sourceUrl, { maxRetries, retryDelay }) => { | ||
const sourcePath = urlToFileSystemPath(sourceUrl) | ||
const sourcePath = urlToFileSystemPath(sourceUrl); | ||
let retryCount = 0 | ||
let retryCount = 0; | ||
const attempt = () => { | ||
@@ -86,14 +86,14 @@ return unlinkNaive(sourcePath, { | ||
handleTemporaryError: async () => { | ||
retryCount++ | ||
retryCount++; | ||
return new Promise((resolve) => { | ||
setTimeout(() => { | ||
resolve(attempt()) | ||
}, retryCount * retryDelay) | ||
}) | ||
resolve(attempt()); | ||
}, retryCount * retryDelay); | ||
}); | ||
}, | ||
}), | ||
}) | ||
} | ||
return attempt() | ||
} | ||
}); | ||
}; | ||
return attempt(); | ||
}; | ||
@@ -105,3 +105,3 @@ const unlinkNaive = (sourcePath, { handleTemporaryError = null } = {}) => { | ||
if (error.code === "ENOENT") { | ||
resolve() | ||
resolve(); | ||
} else if ( | ||
@@ -114,12 +114,12 @@ handleTemporaryError && | ||
) { | ||
resolve(handleTemporaryError(error)) | ||
resolve(handleTemporaryError(error)); | ||
} else { | ||
reject(error) | ||
reject(error); | ||
} | ||
} else { | ||
resolve() | ||
resolve(); | ||
} | ||
}) | ||
}) | ||
} | ||
}); | ||
}); | ||
}; | ||
@@ -130,15 +130,15 @@ const removeDirectory = async ( | ||
) => { | ||
const removeDirectoryOperation = Abort.startOperation() | ||
removeDirectoryOperation.addAbortSignal(signal) | ||
const removeDirectoryOperation = Abort.startOperation(); | ||
removeDirectoryOperation.addAbortSignal(signal); | ||
const visit = async (sourceUrl) => { | ||
removeDirectoryOperation.throwIfAborted() | ||
removeDirectoryOperation.throwIfAborted(); | ||
const sourceStats = await readEntryStat(sourceUrl, { | ||
nullIfNotFound: true, | ||
followLink: false, | ||
}) | ||
}); | ||
// file/directory not found | ||
if (sourceStats === null) { | ||
return | ||
return; | ||
} | ||
@@ -151,21 +151,21 @@ | ||
) { | ||
await visitFile(sourceUrl) | ||
await visitFile(sourceUrl); | ||
} else if (sourceStats.isSymbolicLink()) { | ||
await visitSymbolicLink(sourceUrl) | ||
await visitSymbolicLink(sourceUrl); | ||
} else if (sourceStats.isDirectory()) { | ||
await visitDirectory(`${sourceUrl}/`) | ||
await visitDirectory(`${sourceUrl}/`); | ||
} | ||
} | ||
}; | ||
const visitDirectory = async (directoryUrl) => { | ||
const directoryPath = urlToFileSystemPath(directoryUrl) | ||
const directoryPath = urlToFileSystemPath(directoryUrl); | ||
const optionsFromRecursive = recursive | ||
? { | ||
handleNotEmptyError: async () => { | ||
await removeDirectoryContent(directoryUrl) | ||
await visitDirectory(directoryUrl) | ||
await removeDirectoryContent(directoryUrl); | ||
await visitDirectory(directoryUrl); | ||
}, | ||
} | ||
: {} | ||
removeDirectoryOperation.throwIfAborted() | ||
: {}; | ||
removeDirectoryOperation.throwIfAborted(); | ||
await removeDirectoryNaive(directoryPath, { | ||
@@ -179,10 +179,10 @@ ...optionsFromRecursive, | ||
`trying to fix windows EPERM after readir on ${directoryPath}`, | ||
) | ||
); | ||
let openOrCloseError | ||
let openOrCloseError; | ||
try { | ||
const fd = openSync(directoryPath) | ||
closeSync(fd) | ||
const fd = openSync(directoryPath); | ||
closeSync(fd); | ||
} catch (e) { | ||
openOrCloseError = e | ||
openOrCloseError = e; | ||
} | ||
@@ -192,8 +192,8 @@ | ||
if (openOrCloseError.code === "ENOENT") { | ||
return | ||
return; | ||
} | ||
console.error( | ||
`error while trying to fix windows EPERM after readir on ${directoryPath}: ${openOrCloseError.stack}`, | ||
) | ||
throw error | ||
); | ||
throw error; | ||
} | ||
@@ -203,38 +203,38 @@ | ||
...optionsFromRecursive, | ||
}) | ||
}); | ||
}, | ||
} | ||
: {}), | ||
}) | ||
} | ||
}); | ||
}; | ||
const removeDirectoryContent = async (directoryUrl) => { | ||
removeDirectoryOperation.throwIfAborted() | ||
const names = await readDirectory(directoryUrl) | ||
removeDirectoryOperation.throwIfAborted(); | ||
const names = await readDirectory(directoryUrl); | ||
await Promise.all( | ||
names.map(async (name) => { | ||
const url = resolveUrl(name, directoryUrl) | ||
await visit(url) | ||
const url = resolveUrl(name, directoryUrl); | ||
await visit(url); | ||
}), | ||
) | ||
} | ||
); | ||
}; | ||
const visitFile = async (fileUrl) => { | ||
await removeNonDirectory(fileUrl, { maxRetries, retryDelay }) | ||
} | ||
await removeNonDirectory(fileUrl, { maxRetries, retryDelay }); | ||
}; | ||
const visitSymbolicLink = async (symbolicLinkUrl) => { | ||
await removeNonDirectory(symbolicLinkUrl, { maxRetries, retryDelay }) | ||
} | ||
await removeNonDirectory(symbolicLinkUrl, { maxRetries, retryDelay }); | ||
}; | ||
try { | ||
if (onlyContent) { | ||
await removeDirectoryContent(rootDirectoryUrl) | ||
await removeDirectoryContent(rootDirectoryUrl); | ||
} else { | ||
await visitDirectory(rootDirectoryUrl) | ||
await visitDirectory(rootDirectoryUrl); | ||
} | ||
} finally { | ||
await removeDirectoryOperation.end() | ||
await removeDirectoryOperation.end(); | ||
} | ||
} | ||
}; | ||
@@ -249,5 +249,5 @@ const removeDirectoryNaive = ( | ||
if (handlePermissionError && error.code === "EPERM") { | ||
resolve(handlePermissionError(error)) | ||
resolve(handlePermissionError(error)); | ||
} else if (error.code === "ENOENT") { | ||
resolve() | ||
resolve(); | ||
} else if ( | ||
@@ -260,11 +260,11 @@ handleNotEmptyError && | ||
) { | ||
resolve(handleNotEmptyError(error)) | ||
resolve(handleNotEmptyError(error)); | ||
} else { | ||
reject(error) | ||
reject(error); | ||
} | ||
} else { | ||
resolve(lstatObject) | ||
resolve(lstatObject); | ||
} | ||
}) | ||
}) | ||
} | ||
}); | ||
}); | ||
}; |
@@ -1,6 +0,6 @@ | ||
import { promises, constants } from "node:fs" | ||
import { promises, constants } from "node:fs"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
const { access } = promises | ||
const { access } = promises; | ||
const { | ||
@@ -11,3 +11,3 @@ // F_OK, | ||
X_OK, | ||
} = constants | ||
} = constants; | ||
@@ -23,22 +23,22 @@ export const testEntryPermissions = async ( | ||
) => { | ||
const sourceUrl = assertAndNormalizeFileUrl(source) | ||
let binaryFlags = 0 | ||
const sourceUrl = assertAndNormalizeFileUrl(source); | ||
let binaryFlags = 0; | ||
// if (visible) binaryFlags |= F_OK | ||
if (read) binaryFlags |= R_OK | ||
if (write) binaryFlags |= W_OK | ||
if (execute) binaryFlags |= X_OK | ||
if (read) binaryFlags |= R_OK; | ||
if (write) binaryFlags |= W_OK; | ||
if (execute) binaryFlags |= X_OK; | ||
try { | ||
await access(new URL(sourceUrl), binaryFlags) | ||
return true | ||
await access(new URL(sourceUrl), binaryFlags); | ||
return true; | ||
} catch (error) { | ||
if (error.code === "ENOENT") { | ||
if (allowedIfNotFound) { | ||
return true | ||
return true; | ||
} | ||
throw error | ||
throw error; | ||
} | ||
return false | ||
return false; | ||
} | ||
} | ||
}; |
@@ -1,10 +0,10 @@ | ||
import { promises } from "node:fs" | ||
import { urlToFileSystemPath } from "@jsenv/urls" | ||
import { promises } from "node:fs"; | ||
import { urlToFileSystemPath } from "@jsenv/urls"; | ||
import { assertAndNormalizeDirectoryUrl } from "./directory_url_validation.js" | ||
import { statsToType } from "./internal/statsToType.js" | ||
import { readEntryStat } from "./readEntryStat.js" | ||
import { assertAndNormalizeDirectoryUrl } from "./directory_url_validation.js"; | ||
import { statsToType } from "./internal/statsToType.js"; | ||
import { readEntryStat } from "./readEntryStat.js"; | ||
// https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_fspromises_mkdir_path_options | ||
const { mkdir } = promises | ||
const { mkdir } = promises; | ||
@@ -15,4 +15,4 @@ export const writeDirectory = async ( | ||
) => { | ||
const destinationUrl = assertAndNormalizeDirectoryUrl(destination) | ||
const destinationPath = urlToFileSystemPath(destinationUrl) | ||
const destinationUrl = assertAndNormalizeDirectoryUrl(destination); | ||
const destinationPath = urlToFileSystemPath(destinationUrl); | ||
@@ -22,3 +22,3 @@ const destinationStats = await readEntryStat(destinationUrl, { | ||
followLink: false, | ||
}) | ||
}); | ||
@@ -28,21 +28,21 @@ if (destinationStats) { | ||
if (allowUseless) { | ||
return | ||
return; | ||
} | ||
throw new Error(`directory already exists at ${destinationPath}`) | ||
throw new Error(`directory already exists at ${destinationPath}`); | ||
} | ||
const destinationType = statsToType(destinationStats) | ||
const destinationType = statsToType(destinationStats); | ||
throw new Error( | ||
`cannot write directory at ${destinationPath} because there is a ${destinationType}`, | ||
) | ||
); | ||
} | ||
try { | ||
await mkdir(destinationPath, { recursive }) | ||
await mkdir(destinationPath, { recursive }); | ||
} catch (error) { | ||
if (allowUseless && error.code === "EEXIST") { | ||
return | ||
return; | ||
} | ||
throw error | ||
throw error; | ||
} | ||
} | ||
}; |
@@ -1,12 +0,12 @@ | ||
import { utimes } from "node:fs" | ||
import { utimes } from "node:fs"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
export const writeEntryModificationTime = (source, mtime) => { | ||
const sourceUrl = assertAndNormalizeFileUrl(source) | ||
const sourceUrl = assertAndNormalizeFileUrl(source); | ||
const mtimeValue = | ||
typeof mtime === "number" ? new Date(Math.floor(mtime)) : mtime | ||
typeof mtime === "number" ? new Date(Math.floor(mtime)) : mtime; | ||
// reading atime mutates its value so there is no use case I can think of | ||
// where we want to modify it | ||
const atimeValue = mtimeValue | ||
const atimeValue = mtimeValue; | ||
@@ -16,8 +16,8 @@ return new Promise((resolve, reject) => { | ||
if (error) { | ||
reject(error) | ||
reject(error); | ||
} else { | ||
resolve() | ||
resolve(); | ||
} | ||
}) | ||
}) | ||
} | ||
}); | ||
}); | ||
}; |
@@ -1,10 +0,10 @@ | ||
import { chmod } from "node:fs" | ||
import { chmod } from "node:fs"; | ||
import { permissionsToBinaryFlags } from "./internal/permissions.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { permissionsToBinaryFlags } from "./internal/permissions.js"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
export const writeEntryPermissions = async (source, permissions) => { | ||
const sourceUrl = assertAndNormalizeFileUrl(source) | ||
const sourceUrl = assertAndNormalizeFileUrl(source); | ||
let binaryFlags | ||
let binaryFlags; | ||
if (typeof permissions === "object") { | ||
@@ -31,6 +31,6 @@ permissions = { | ||
}, | ||
} | ||
binaryFlags = permissionsToBinaryFlags(permissions) | ||
}; | ||
binaryFlags = permissionsToBinaryFlags(permissions); | ||
} else { | ||
binaryFlags = permissions | ||
binaryFlags = permissions; | ||
} | ||
@@ -41,21 +41,21 @@ | ||
if (error) { | ||
reject(error) | ||
reject(error); | ||
} else { | ||
resolve() | ||
resolve(); | ||
} | ||
}) | ||
}) | ||
} | ||
}); | ||
}); | ||
}; | ||
const actionLevels = { read: 0, write: 1, execute: 2 } | ||
const subjectLevels = { others: 0, group: 1, owner: 2 } | ||
const actionLevels = { read: 0, write: 1, execute: 2 }; | ||
const subjectLevels = { others: 0, group: 1, owner: 2 }; | ||
const getPermissionOrComputeDefault = (action, subject, permissions) => { | ||
if (subject in permissions) { | ||
const subjectPermissions = permissions[subject] | ||
const subjectPermissions = permissions[subject]; | ||
if (action in subjectPermissions) { | ||
return subjectPermissions[action] | ||
return subjectPermissions[action]; | ||
} | ||
const actionLevel = actionLevels[action] | ||
const actionLevel = actionLevels[action]; | ||
const actionFallback = Object.keys(actionLevels).find( | ||
@@ -65,9 +65,9 @@ (actionFallbackCandidate) => | ||
actionFallbackCandidate in subjectPermissions, | ||
) | ||
); | ||
if (actionFallback) { | ||
return subjectPermissions[actionFallback] | ||
return subjectPermissions[actionFallback]; | ||
} | ||
} | ||
const subjectLevel = subjectLevels[subject] | ||
const subjectLevel = subjectLevels[subject]; | ||
// do we have a subject with a stronger level (group or owner) | ||
@@ -79,11 +79,11 @@ // where we could read the action permission ? | ||
subjectFallbackCandidate in permissions, | ||
) | ||
); | ||
if (subjectFallback) { | ||
const subjectPermissions = permissions[subjectFallback] | ||
const subjectPermissions = permissions[subjectFallback]; | ||
return action in subjectPermissions | ||
? subjectPermissions[action] | ||
: getPermissionOrComputeDefault(action, subjectFallback, permissions) | ||
: getPermissionOrComputeDefault(action, subjectFallback, permissions); | ||
} | ||
return false | ||
} | ||
return false; | ||
}; |
@@ -1,20 +0,20 @@ | ||
import { writeFile as writeFileNode } from "node:fs" | ||
import { writeFile as writeFileNode } from "node:fs"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { ensureParentDirectories } from "./ensureParentDirectories.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
import { ensureParentDirectories } from "./ensureParentDirectories.js"; | ||
export const writeFile = async (destination, content = "") => { | ||
const destinationUrl = assertAndNormalizeFileUrl(destination) | ||
const destinationUrlObject = new URL(destinationUrl) | ||
const destinationUrl = assertAndNormalizeFileUrl(destination); | ||
const destinationUrlObject = new URL(destinationUrl); | ||
try { | ||
await writeFileNaive(destinationUrlObject, content) | ||
await writeFileNaive(destinationUrlObject, content); | ||
} catch (error) { | ||
if (error.code === "ENOENT") { | ||
await ensureParentDirectories(destinationUrl) | ||
await writeFileNaive(destinationUrlObject, content) | ||
return | ||
await ensureParentDirectories(destinationUrl); | ||
await writeFileNaive(destinationUrlObject, content); | ||
return; | ||
} | ||
throw error | ||
throw error; | ||
} | ||
} | ||
}; | ||
@@ -25,8 +25,8 @@ const writeFileNaive = (urlObject, content) => { | ||
if (error) { | ||
reject(error) | ||
reject(error); | ||
} else { | ||
resolve() | ||
resolve(); | ||
} | ||
}) | ||
}) | ||
} | ||
}); | ||
}); | ||
}; |
@@ -1,10 +0,10 @@ | ||
import { writeFileSync as writeFileSyncNode, mkdirSync } from "node:fs" | ||
import { writeFileSync as writeFileSyncNode, mkdirSync } from "node:fs"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
export const writeFileSync = (destination, content = "") => { | ||
const destinationUrl = assertAndNormalizeFileUrl(destination) | ||
const destinationUrlObject = new URL(destinationUrl) | ||
const destinationUrl = assertAndNormalizeFileUrl(destination); | ||
const destinationUrlObject = new URL(destinationUrl); | ||
try { | ||
writeFileSyncNode(destinationUrlObject, content) | ||
writeFileSyncNode(destinationUrlObject, content); | ||
} catch (error) { | ||
@@ -14,8 +14,8 @@ if (error.code === "ENOENT") { | ||
recursive: true, | ||
}) | ||
writeFileSyncNode(destinationUrlObject, content) | ||
return | ||
}); | ||
writeFileSyncNode(destinationUrlObject, content); | ||
return; | ||
} | ||
throw error | ||
throw error; | ||
} | ||
} | ||
}; |
@@ -1,2 +0,2 @@ | ||
import { promises } from "node:fs" | ||
import { promises } from "node:fs"; | ||
import { | ||
@@ -7,13 +7,13 @@ fileSystemPathToUrl, | ||
isFileSystemPath, | ||
} from "@jsenv/urls" | ||
} from "@jsenv/urls"; | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js" | ||
import { ensureParentDirectories } from "./ensureParentDirectories.js" | ||
import { readEntryStat } from "./readEntryStat.js" | ||
import { readSymbolicLink } from "./readSymbolicLink.js" | ||
import { removeEntry } from "./removeEntry.js" | ||
import { assertAndNormalizeFileUrl } from "./file_url_validation.js"; | ||
import { ensureParentDirectories } from "./ensureParentDirectories.js"; | ||
import { readEntryStat } from "./readEntryStat.js"; | ||
import { readSymbolicLink } from "./readSymbolicLink.js"; | ||
import { removeEntry } from "./removeEntry.js"; | ||
// https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_fspromises_symlink_target_path_type | ||
const { symlink } = promises | ||
const isWindows = process.platform === "win32" | ||
const { symlink } = promises; | ||
const isWindows = process.platform === "win32"; | ||
@@ -35,4 +35,4 @@ /** | ||
}) => { | ||
const fromUrl = assertAndNormalizeFileUrl(from) | ||
const toInfo = getToInfo(to, fromUrl) | ||
const fromUrl = assertAndNormalizeFileUrl(from); | ||
const toInfo = getToInfo(to, fromUrl); | ||
// Node.js doc at https://nodejs.org/api/fs.html#fssymlinktarget-path-type-callback | ||
@@ -47,30 +47,30 @@ // states the following: | ||
nullIfNotFound: true, | ||
}) | ||
type = toStats && toStats.isDirectory() ? "dir" : "file" | ||
}); | ||
type = toStats && toStats.isDirectory() ? "dir" : "file"; | ||
} | ||
const symbolicLinkPath = urlToFileSystemPath(fromUrl) | ||
const symbolicLinkPath = urlToFileSystemPath(fromUrl); | ||
try { | ||
await symlink(toInfo.value, symbolicLinkPath, type) | ||
await symlink(toInfo.value, symbolicLinkPath, type); | ||
} catch (error) { | ||
if (error.code === "ENOENT") { | ||
await ensureParentDirectories(fromUrl) | ||
await symlink(toInfo.value, symbolicLinkPath, type) | ||
return | ||
await ensureParentDirectories(fromUrl); | ||
await symlink(toInfo.value, symbolicLinkPath, type); | ||
return; | ||
} | ||
if (error.code === "EEXIST") { | ||
if (allowUseless) { | ||
const existingSymbolicLinkUrl = await readSymbolicLink(fromUrl) | ||
const existingSymbolicLinkUrl = await readSymbolicLink(fromUrl); | ||
if (existingSymbolicLinkUrl === toInfo.url) { | ||
return | ||
return; | ||
} | ||
} | ||
if (allowOverwrite) { | ||
await removeEntry(fromUrl) | ||
await symlink(toInfo.value, symbolicLinkPath, type) | ||
return | ||
await removeEntry(fromUrl); | ||
await symlink(toInfo.value, symbolicLinkPath, type); | ||
return; | ||
} | ||
} | ||
throw error | ||
throw error; | ||
} | ||
} | ||
}; | ||
@@ -81,8 +81,8 @@ const getToInfo = (to, fromUrl) => { | ||
if (isFileSystemPath(to)) { | ||
const url = fileSystemPathToUrl(to) | ||
const value = to | ||
const url = fileSystemPathToUrl(to); | ||
const value = to; | ||
return { | ||
url, | ||
value, | ||
} | ||
}; | ||
} | ||
@@ -92,26 +92,26 @@ | ||
if (to.startsWith("./") || to.startsWith("../")) { | ||
const url = resolveUrl(to, fromUrl) | ||
const value = to | ||
const url = resolveUrl(to, fromUrl); | ||
const value = to; | ||
return { | ||
url, | ||
value, | ||
} | ||
}; | ||
} | ||
// absolute url | ||
const url = resolveUrl(to, fromUrl) | ||
const value = urlToFileSystemPath(url) | ||
const url = resolveUrl(to, fromUrl); | ||
const value = urlToFileSystemPath(url); | ||
return { | ||
url, | ||
value, | ||
} | ||
}; | ||
} | ||
if (to instanceof URL) { | ||
const url = String(to) | ||
const value = urlToFileSystemPath(url) | ||
const url = String(to); | ||
const value = urlToFileSystemPath(url); | ||
return { | ||
url, | ||
value, | ||
} | ||
}; | ||
} | ||
@@ -121,3 +121,3 @@ | ||
`symbolic link to must be a string or an url, received ${to}`, | ||
) | ||
} | ||
); | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
88229
+ Added@jsenv/urls@2.1.0(transitive)
- Removed@jsenv/urls@2.0.0(transitive)
Updated@jsenv/urls@2.1.0