@lix-js/fs
Advanced tools
Comparing version 0.6.0 to 1.0.0
import type { NodeishFilesystem } from "./NodeishFilesystemApi.js"; | ||
export type Snapshot = { | ||
fsMap: { | ||
[key: string]: string[] | string; | ||
[key: string]: string[] | string | { | ||
placeholder: true; | ||
}; | ||
}; | ||
@@ -6,0 +8,0 @@ fsStats: { |
@@ -6,6 +6,16 @@ import { FilesystemError } from "./errors/FilesystemError.js"; | ||
fsMap: Object.fromEntries([...fs._state.fsMap].map(([path, content]) => { | ||
let serializedContent; | ||
if (content instanceof Set) { | ||
serializedContent = [...content].sort(); | ||
} | ||
else if (content.placeholder) { | ||
serializedContent = content; | ||
} | ||
else { | ||
serializedContent = content.toString("base64"); | ||
} | ||
return [ | ||
path, | ||
// requires node buffers, but no web standard method exists | ||
content instanceof Set ? [...content].sort() : content.toString("base64"), | ||
serializedContent, | ||
// Alternative to try: | ||
@@ -45,3 +55,7 @@ // onst binaryData = new Uint8Array([255, 116, 79, 99 /*...*/]); | ||
return [pathPrefix + path, data]; | ||
// @ts-ignore | ||
} | ||
else if (content?.placeholder) { | ||
return [pathPrefix + path, content]; | ||
} | ||
return [pathPrefix + path, new Set(content)]; | ||
@@ -90,2 +104,3 @@ })); | ||
} | ||
// lstat is like stat, but does not follow symlinks | ||
async function lstat(path) { | ||
@@ -102,2 +117,27 @@ path = normalPath(path); | ||
_state: state, | ||
_createPlaceholder: async function (path, options) { | ||
path = normalPath(path); | ||
const dirName = getDirname(path); | ||
const baseName = getBasename(path); | ||
let parentDir = state.fsMap.get(dirName); | ||
if (!(parentDir instanceof Set)) { | ||
await this.mkdir(dirName, { recursive: true }); | ||
parentDir = state.fsMap.get(dirName); | ||
if (!(parentDir instanceof Set)) { | ||
throw new FilesystemError("ENOENT", path, "writeFile"); | ||
} | ||
} | ||
parentDir.add(baseName); | ||
const isSymbolicLink = options?.mode === 120000; | ||
newStatEntry(path, state.fsStats, isSymbolicLink ? 2 : 0, options?.mode ?? 0o644); | ||
state.fsMap.set(path, { placeholder: true }); | ||
}, | ||
_isPlaceholder: function (path) { | ||
path = normalPath(path); | ||
const entry = state.fsMap.get(path); | ||
if (entry && "placeholder" in entry) { | ||
return true; | ||
} | ||
return false; | ||
}, | ||
writeFile: async function (path, data, options) { | ||
@@ -137,2 +177,4 @@ path = normalPath(path); | ||
throw new FilesystemError("ENOENT", path, "readFile"); | ||
if ("placeholder" in file) | ||
throw new FilesystemError("EPLACEHOLDER", path, "readFile"); | ||
if (!(options?.encoding || typeof options === "string")) | ||
@@ -156,3 +198,3 @@ return file; | ||
const parentDir = state.fsMap.get(dirName); | ||
if (typeof parentDir === "string") { | ||
if (typeof parentDir === "string" || (parentDir && "palceholder" in parentDir)) { | ||
throw new FilesystemError("ENOTDIR", path, "mkdir"); | ||
@@ -162,3 +204,8 @@ } | ||
if (state.fsMap.has(path)) { | ||
throw new FilesystemError("EEXIST", path, "mkdir"); | ||
if (!options?.recursive) { | ||
throw new FilesystemError("EEXIST", path, "mkdir"); | ||
} | ||
else { | ||
return undefined; | ||
} | ||
} | ||
@@ -173,6 +220,7 @@ parentDir.add(baseName); | ||
} | ||
if (options?.recursive) { | ||
const firstPath = await mkdir(getDirname(path), options); | ||
else if (options?.recursive) { | ||
const parent = getDirname(path); | ||
const parentRes = await mkdir(parent, options); | ||
await mkdir(path, options); | ||
return firstPath; | ||
return parentRes; | ||
} | ||
@@ -189,8 +237,10 @@ throw new FilesystemError("ENOENT", path, "mkdir"); | ||
throw new FilesystemError("ENOENT", path, "rm"); | ||
if (parentDir instanceof Uint8Array) | ||
if (parentDir instanceof Uint8Array || "placeholder" in parentDir) { | ||
throw new FilesystemError("ENOTDIR", path, "rm"); | ||
if (target instanceof Uint8Array) { | ||
} | ||
if (target instanceof Uint8Array || "placeholder" in target) { | ||
parentDir.delete(baseName); | ||
state.fsStats.delete(path); | ||
state.fsMap.delete(path); | ||
// TODO: check if placeholder should skip firing | ||
for (const listener of listeners) { | ||
@@ -223,3 +273,3 @@ listener({ eventType: "rename", filename: dirName + baseName }); | ||
const watchDir = getDirname(path); | ||
const watchPath = watchDir + watchName; | ||
const watchPath = watchName === "/" ? watchDir : watchDir + watchName; | ||
// @ts-ignore | ||
@@ -236,3 +286,7 @@ if (options?.persistent || options?.encoding) { | ||
}); | ||
const listener = (event) => { | ||
const listener = ({ eventType, filename }) => { | ||
const event = { | ||
eventType, | ||
filename, | ||
}; | ||
if (event.filename === null) { | ||
@@ -248,4 +302,4 @@ throw new Error("Internal watcher error: missing filename"); | ||
} | ||
else if (changeDir === watchPath + "/") { | ||
event.filename = event.filename.replace(watchPath + "/", "") || changeName; | ||
else if (changeDir === `${watchPath}/`) { | ||
event.filename = event.filename.replace(`${watchPath}/`, "") || changeName; | ||
queue.push(event); | ||
@@ -255,4 +309,3 @@ setTimeout(() => handleNext(undefined), 0); | ||
else if (options?.recursive && event.filename.startsWith(watchPath)) { | ||
// console.log(event.filename, { watchPath, changeDir, changeName }) | ||
event.filename = event.filename.replace(watchPath + "/", "") || changeName; | ||
event.filename = event.filename.replace(`${watchPath}/`, "") || changeName; | ||
queue.push(event); | ||
@@ -274,3 +327,4 @@ setTimeout(() => handleNext(undefined), 0); | ||
} | ||
return (async function* () { | ||
// inline async definition like "return (async function* () {" are not supported by the figma api | ||
const asyncIterator = async function* () { | ||
while (!options?.signal?.aborted) { | ||
@@ -281,3 +335,5 @@ if (queue.length > 0) { | ||
else { | ||
// eslint-disable-next-line no-await-in-loop | ||
await changeEvent; | ||
// eslint-disable-next-line @typescript-eslint/no-loop-func | ||
changeEvent = new Promise((resolve, reject) => { | ||
@@ -289,3 +345,4 @@ handleNext = resolve; | ||
} | ||
})(); | ||
}; | ||
return asyncIterator(); | ||
}, | ||
@@ -302,2 +359,5 @@ rmdir: async function (path) { | ||
throw new FilesystemError("ENOTDIR", path, "rmdir"); | ||
if ("placeholder" in parentDir || "placeholder" in target) { | ||
throw new FilesystemError("ENOTDIR", path, "rmdir"); | ||
} | ||
if (target.size) | ||
@@ -320,8 +380,8 @@ throw new FilesystemError("ENOTEMPTY", path, "rmdir"); | ||
} | ||
if (parentDir instanceof Uint8Array) { | ||
throw new FilesystemError("ENOTDIR", path, "symlink", target); | ||
} | ||
if (parentDir === undefined) { | ||
throw new FilesystemError("ENOENT", path, "symlink", target); | ||
} | ||
if (parentDir instanceof Uint8Array || "placeholder" in parentDir) { | ||
throw new FilesystemError("ENOTDIR", path, "symlink", target); | ||
} | ||
if (targetInode !== undefined) { | ||
@@ -340,3 +400,3 @@ state.fsMap.set(path, targetInode); | ||
throw new FilesystemError("ENOENT", path, "unlink"); | ||
if (parentDir instanceof Uint8Array) { | ||
if (parentDir instanceof Uint8Array || "placeholder" in parentDir) { | ||
throw new FilesystemError("ENOTDIR", path, "unlink"); | ||
@@ -343,0 +403,0 @@ } |
{ | ||
"name": "@lix-js/fs", | ||
"version": "0.6.0", | ||
"version": "1.0.0", | ||
"type": "module", | ||
@@ -5,0 +5,0 @@ "publishConfig": { |
@@ -69,2 +69,6 @@ import { test, expect, afterAll, describe } from "vitest" | ||
) | ||
// should not throw | ||
await fs.mkdir(`${tempDir}/home/user1/documents/`, { recursive: true }) | ||
expect(await fs.mkdir(`${tempDir}/home/user1/downloads/`, { recursive: true })).toMatch( | ||
@@ -271,2 +275,37 @@ /^.*\/home\/user1\/downloads\/?$/ | ||
test.skipIf(isNodeFs)("placeholders", async () => { | ||
const placeholderPath = `/placeholders/subdir` | ||
await fs.mkdir(placeholderPath, { recursive: true }) | ||
await fs.writeFile(`${placeholderPath}/file`, "") | ||
await fs._createPlaceholder(placeholderPath + "/test") | ||
expect(fs._isPlaceholder(placeholderPath + "/test")).toBe(true) | ||
expect(fs._isPlaceholder(placeholderPath + "/noexists")).toBe(false) | ||
expect(fs._isPlaceholder(placeholderPath + "/file")).toBe(false) | ||
const dirents = await fs.readdir(`/placeholders/subdir`) | ||
expect(dirents).toStrictEqual(["file", "test"]) | ||
await expect(async () => await fs.readFile(`${placeholderPath}/test`)).rejects.toThrow( | ||
/EPLACEHOLDER/ | ||
) | ||
await fs.rm("/placeholders", { recursive: true }) | ||
const finalDirents = await fs.readdir(`/`) | ||
expect(finalDirents).toStrictEqual([ | ||
"home", | ||
"file2", | ||
"file3", | ||
"file1.link", | ||
"file3.link", | ||
"user1.link", | ||
]) | ||
}) | ||
test("unlink", async () => { | ||
@@ -273,0 +312,0 @@ await fs.unlink(`${tempDir}/user1.link`) |
@@ -5,7 +5,7 @@ import type { NodeishFilesystem, NodeishStats, FileChangeInfo } from "./NodeishFilesystemApi.js" | ||
type Inode = Uint8Array | Set<string> | ||
type Inode = Uint8Array | Set<string> | { placeholder: true } | ||
export type Snapshot = { | ||
fsMap: { | ||
[key: string]: string[] | string | ||
[key: string]: string[] | string | { placeholder: true } | ||
} | ||
@@ -30,6 +30,14 @@ fsStats: { | ||
[...fs._state.fsMap].map(([path, content]) => { | ||
let serializedContent | ||
if (content instanceof Set) { | ||
serializedContent = [...content].sort() | ||
} else if (content.placeholder) { | ||
serializedContent = content | ||
} else { | ||
serializedContent = content.toString("base64") | ||
} | ||
return [ | ||
path, | ||
// requires node buffers, but no web standard method exists | ||
content instanceof Set ? [...content].sort() : content.toString("base64"), | ||
serializedContent, | ||
@@ -79,2 +87,6 @@ // Alternative to try: | ||
return [pathPrefix + path, data] | ||
// @ts-ignore | ||
} else if (content?.placeholder) { | ||
return [pathPrefix + path, content] | ||
} | ||
@@ -144,2 +156,3 @@ | ||
// lstat is like stat, but does not follow symlinks | ||
async function lstat(path: Parameters<NodeishFilesystem["lstat"]>[0]) { | ||
@@ -155,2 +168,36 @@ path = normalPath(path) | ||
_state: state, | ||
_createPlaceholder: async function ( | ||
path: Parameters<NodeishFilesystem["writeFile"]>[0], | ||
options?: Parameters<NodeishFilesystem["writeFile"]>[2] | ||
) { | ||
path = normalPath(path) | ||
const dirName = getDirname(path) | ||
const baseName = getBasename(path) | ||
let parentDir: Inode | undefined = state.fsMap.get(dirName) | ||
if (!(parentDir instanceof Set)) { | ||
await this.mkdir(dirName, { recursive: true }) | ||
parentDir = state.fsMap.get(dirName) | ||
if (!(parentDir instanceof Set)) { | ||
throw new FilesystemError("ENOENT", path, "writeFile") | ||
} | ||
} | ||
parentDir.add(baseName) | ||
const isSymbolicLink = options?.mode === 120000 | ||
newStatEntry(path, state.fsStats, isSymbolicLink ? 2 : 0, options?.mode ?? 0o644) | ||
state.fsMap.set(path, { placeholder: true }) | ||
}, | ||
_isPlaceholder: function (path: Parameters<NodeishFilesystem["writeFile"]>[0]) { | ||
path = normalPath(path) | ||
const entry = state.fsMap.get(path) | ||
if (entry && "placeholder" in entry) { | ||
return true | ||
} | ||
return false | ||
}, | ||
writeFile: async function ( | ||
@@ -202,2 +249,3 @@ path: Parameters<NodeishFilesystem["writeFile"]>[0], | ||
if (file === undefined) throw new FilesystemError("ENOENT", path, "readFile") | ||
if ("placeholder" in file) throw new FilesystemError("EPLACEHOLDER", path, "readFile") | ||
if (!(options?.encoding || typeof options === "string")) return file | ||
@@ -225,3 +273,3 @@ | ||
if (typeof parentDir === "string") { | ||
if (typeof parentDir === "string" || (parentDir && "palceholder" in parentDir)) { | ||
throw new FilesystemError("ENOTDIR", path, "mkdir") | ||
@@ -232,8 +280,14 @@ } | ||
if (state.fsMap.has(path)) { | ||
throw new FilesystemError("EEXIST", path, "mkdir") | ||
if (!options?.recursive) { | ||
throw new FilesystemError("EEXIST", path, "mkdir") | ||
} else { | ||
return undefined | ||
} | ||
} | ||
parentDir.add(baseName) | ||
newStatEntry(path, state.fsStats, 1, 0o755) | ||
state.fsMap.set(path, new Set()) | ||
for (const listener of listeners) { | ||
@@ -243,8 +297,7 @@ listener({ eventType: "rename", filename: dirName + baseName }) | ||
return path | ||
} | ||
if (options?.recursive) { | ||
const firstPath = await mkdir(getDirname(path), options) | ||
} else if (options?.recursive) { | ||
const parent = getDirname(path) | ||
const parentRes = await mkdir(parent, options) | ||
await mkdir(path, options) | ||
return firstPath | ||
return parentRes | ||
} | ||
@@ -268,8 +321,11 @@ | ||
if (parentDir instanceof Uint8Array) throw new FilesystemError("ENOTDIR", path, "rm") | ||
if (parentDir instanceof Uint8Array || "placeholder" in parentDir) { | ||
throw new FilesystemError("ENOTDIR", path, "rm") | ||
} | ||
if (target instanceof Uint8Array) { | ||
if (target instanceof Uint8Array || "placeholder" in target) { | ||
parentDir.delete(baseName) | ||
state.fsStats.delete(path) | ||
state.fsMap.delete(path) | ||
// TODO: check if placeholder should skip firing | ||
for (const listener of listeners) { | ||
@@ -312,3 +368,3 @@ listener({ eventType: "rename", filename: dirName + baseName }) | ||
const watchDir = getDirname(path) | ||
const watchPath = watchDir + watchName | ||
const watchPath = watchName === "/" ? watchDir : watchDir + watchName | ||
@@ -329,3 +385,8 @@ // @ts-ignore | ||
const listener = (event: FileChangeInfo) => { | ||
const listener = ({ eventType, filename }: FileChangeInfo) => { | ||
const event: FileChangeInfo = { | ||
eventType, | ||
filename, | ||
} | ||
if (event.filename === null) { | ||
@@ -341,9 +402,8 @@ throw new Error("Internal watcher error: missing filename") | ||
setTimeout(() => handleNext(undefined), 0) | ||
} else if (changeDir === watchPath + "/") { | ||
event.filename = event.filename.replace(watchPath + "/", "") || changeName | ||
} else if (changeDir === `${watchPath}/`) { | ||
event.filename = event.filename.replace(`${watchPath}/`, "") || changeName | ||
queue.push(event) | ||
setTimeout(() => handleNext(undefined), 0) | ||
} else if (options?.recursive && event.filename.startsWith(watchPath)) { | ||
// console.log(event.filename, { watchPath, changeDir, changeName }) | ||
event.filename = event.filename.replace(watchPath + "/", "") || changeName | ||
event.filename = event.filename.replace(`${watchPath}/`, "") || changeName | ||
queue.push(event) | ||
@@ -371,3 +431,4 @@ setTimeout(() => handleNext(undefined), 0) | ||
return (async function* () { | ||
// inline async definition like "return (async function* () {" are not supported by the figma api | ||
const asyncIterator = async function* () { | ||
while (!options?.signal?.aborted) { | ||
@@ -377,3 +438,5 @@ if (queue.length > 0) { | ||
} else { | ||
// eslint-disable-next-line no-await-in-loop | ||
await changeEvent | ||
// eslint-disable-next-line @typescript-eslint/no-loop-func | ||
changeEvent = new Promise((resolve, reject) => { | ||
@@ -385,3 +448,5 @@ handleNext = resolve | ||
} | ||
})() | ||
} | ||
return asyncIterator() | ||
}, | ||
@@ -402,2 +467,6 @@ | ||
if ("placeholder" in parentDir || "placeholder" in target) { | ||
throw new FilesystemError("ENOTDIR", path, "rmdir") | ||
} | ||
if (target.size) throw new FilesystemError("ENOTEMPTY", path, "rmdir") | ||
@@ -427,6 +496,2 @@ | ||
if (parentDir instanceof Uint8Array) { | ||
throw new FilesystemError("ENOTDIR", path, "symlink", target) | ||
} | ||
if (parentDir === undefined) { | ||
@@ -436,2 +501,6 @@ throw new FilesystemError("ENOENT", path, "symlink", target) | ||
if (parentDir instanceof Uint8Array || "placeholder" in parentDir) { | ||
throw new FilesystemError("ENOTDIR", path, "symlink", target) | ||
} | ||
if (targetInode !== undefined) { | ||
@@ -454,3 +523,3 @@ state.fsMap.set(path, targetInode) | ||
if (parentDir instanceof Uint8Array) { | ||
if (parentDir instanceof Uint8Array || "placeholder" in parentDir) { | ||
throw new FilesystemError("ENOTDIR", path, "unlink") | ||
@@ -457,0 +526,0 @@ } |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
72373
1782
1