Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@jsenv/filesystem

Package Overview
Dependencies
Maintainers
2
Versions
95
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@jsenv/filesystem - npm Package Compare versions

Comparing version 4.2.3 to 4.2.4

8

package.json
{
"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}`,
)
}
);
};
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc