shell-dsl 0.0.29 → 0.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/fs/index.cjs +4 -4
- package/dist/cjs/src/fs/index.cjs.map +3 -3
- package/dist/cjs/src/fs/real-fs.cjs +27 -14
- package/dist/cjs/src/fs/real-fs.cjs.map +3 -3
- package/dist/cjs/src/fs/{opfs-fs.cjs → web-fs.cjs} +61 -13
- package/dist/cjs/src/fs/web-fs.cjs.map +10 -0
- package/dist/cjs/src/index.cjs +3 -3
- package/dist/cjs/src/index.cjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/src/fs/index.mjs +4 -4
- package/dist/mjs/src/fs/index.mjs.map +2 -2
- package/dist/mjs/src/fs/real-fs.mjs +27 -14
- package/dist/mjs/src/fs/real-fs.mjs.map +3 -3
- package/dist/mjs/src/fs/{opfs-fs.mjs → web-fs.mjs} +58 -10
- package/dist/mjs/src/fs/web-fs.mjs.map +10 -0
- package/dist/mjs/src/index.mjs +5 -5
- package/dist/mjs/src/index.mjs.map +3 -3
- package/dist/types/src/fs/index.d.ts +2 -2
- package/dist/types/src/fs/real-fs.d.ts +12 -0
- package/dist/types/src/fs/{opfs-fs.d.ts → web-fs.d.ts} +2 -2
- package/dist/types/src/index.d.ts +1 -1
- package/package.json +1 -1
- package/dist/cjs/src/fs/opfs-fs.cjs.map +0 -10
- package/dist/mjs/src/fs/opfs-fs.mjs.map +0 -10
package/README.md
CHANGED
|
@@ -695,27 +695,27 @@ Omit the mount path for unrestricted access, but this is the same as just passin
|
|
|
695
695
|
const fs = new FileSystem(); // Full filesystem access same as fs from node:fs
|
|
696
696
|
```
|
|
697
697
|
|
|
698
|
-
###
|
|
698
|
+
### Web Filesystem
|
|
699
699
|
|
|
700
|
-
Use `
|
|
700
|
+
Use `WebFileSystem` when you already have a `FileSystemDirectoryHandle` in the browser, including an OPFS root from `navigator.storage.getDirectory()`:
|
|
701
701
|
|
|
702
702
|
```ts
|
|
703
|
-
import {
|
|
703
|
+
import { WebFileSystem } from "shell-dsl";
|
|
704
704
|
|
|
705
705
|
const root = await navigator.storage.getDirectory();
|
|
706
|
-
const fs = new
|
|
706
|
+
const fs = new WebFileSystem(root, {
|
|
707
707
|
"secrets/**": "excluded",
|
|
708
708
|
"docs/**": "read-only",
|
|
709
709
|
});
|
|
710
710
|
```
|
|
711
711
|
|
|
712
|
-
For advanced use, you can inject the
|
|
712
|
+
For advanced use, you can inject the web adapter into `FileSystem` directly:
|
|
713
713
|
|
|
714
714
|
```ts
|
|
715
|
-
import { FileSystem,
|
|
715
|
+
import { FileSystem, createWebUnderlyingFS } from "shell-dsl";
|
|
716
716
|
|
|
717
717
|
const root = await navigator.storage.getDirectory();
|
|
718
|
-
const fs = new FileSystem("/", {},
|
|
718
|
+
const fs = new FileSystem("/", {}, createWebUnderlyingFS(root));
|
|
719
719
|
```
|
|
720
720
|
|
|
721
721
|
|
package/dist/cjs/package.json
CHANGED
|
@@ -39,16 +39,16 @@ var __export = (target, all) => {
|
|
|
39
39
|
// src/fs/index.ts
|
|
40
40
|
var exports_fs = {};
|
|
41
41
|
__export(exports_fs, {
|
|
42
|
+
createWebUnderlyingFS: () => import_web_fs.createWebUnderlyingFS,
|
|
42
43
|
createVirtualFS: () => import_memfs_adapter.createVirtualFS,
|
|
43
|
-
|
|
44
|
+
WebFileSystem: () => import_web_fs.WebFileSystem,
|
|
44
45
|
ReadOnlyFileSystem: () => import_readonly_fs.ReadOnlyFileSystem,
|
|
45
|
-
OPFSFileSystem: () => import_opfs_fs.OPFSFileSystem,
|
|
46
46
|
FileSystem: () => import_real_fs.FileSystem
|
|
47
47
|
});
|
|
48
48
|
module.exports = __toCommonJS(exports_fs);
|
|
49
49
|
var import_memfs_adapter = require("./memfs-adapter.cjs");
|
|
50
50
|
var import_real_fs = require("./real-fs.cjs");
|
|
51
51
|
var import_readonly_fs = require("./readonly-fs.cjs");
|
|
52
|
-
var
|
|
52
|
+
var import_web_fs = require("./web-fs.cjs");
|
|
53
53
|
|
|
54
|
-
//# debugId=
|
|
54
|
+
//# debugId=C93B6DA797F0B48764756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/fs/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"export { createVirtualFS } from \"./memfs-adapter.cjs\";\nexport { FileSystem, type Permission, type PermissionRules, type UnderlyingFS } from \"./real-fs.cjs\";\nexport { ReadOnlyFileSystem } from \"./readonly-fs.cjs\";\nexport {
|
|
5
|
+
"export { createVirtualFS } from \"./memfs-adapter.cjs\";\nexport { FileSystem, type PathOps, type Permission, type PermissionRules, type UnderlyingFS } from \"./real-fs.cjs\";\nexport { ReadOnlyFileSystem } from \"./readonly-fs.cjs\";\nexport { WebFileSystem, createWebUnderlyingFS } from \"./web-fs.cjs\";\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAgC,IAAhC;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAgC,IAAhC;AACmG,IAAnG;AACmC,IAAnC;AACqD,IAArD;",
|
|
8
|
+
"debugId": "C93B6DA797F0B48764756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -69,15 +69,28 @@ module.exports = __toCommonJS(exports_real_fs);
|
|
|
69
69
|
var path = __toESM(require("path"));
|
|
70
70
|
var nodeFs = __toESM(require("node:fs/promises"));
|
|
71
71
|
var defaultFS = { promises: nodeFs };
|
|
72
|
+
var nodePathOps = {
|
|
73
|
+
separator: path.sep,
|
|
74
|
+
resolve: (...paths) => path.resolve(...paths),
|
|
75
|
+
normalize: (filePath) => path.normalize(filePath),
|
|
76
|
+
join: (...paths) => path.join(...paths),
|
|
77
|
+
relative: (from, to) => path.relative(from, to),
|
|
78
|
+
isAbsolute: (filePath) => path.isAbsolute(filePath),
|
|
79
|
+
dirname: (filePath) => path.dirname(filePath),
|
|
80
|
+
basename: (filePath) => path.basename(filePath)
|
|
81
|
+
};
|
|
72
82
|
|
|
73
83
|
class FileSystem {
|
|
74
84
|
mountBase;
|
|
75
85
|
rules;
|
|
86
|
+
pathOps;
|
|
76
87
|
underlyingFs;
|
|
77
88
|
constructor(mountPath, permissions, fs) {
|
|
78
|
-
|
|
89
|
+
const underlyingFs = fs ?? defaultFS;
|
|
90
|
+
this.pathOps = underlyingFs.pathOps ?? nodePathOps;
|
|
91
|
+
this.mountBase = mountPath ? this.pathOps.resolve(mountPath) : null;
|
|
79
92
|
this.rules = this.compileRules(permissions ?? {});
|
|
80
|
-
this.underlyingFs =
|
|
93
|
+
this.underlyingFs = underlyingFs;
|
|
81
94
|
}
|
|
82
95
|
compileRules(permissions) {
|
|
83
96
|
return Object.entries(permissions).map(([pattern, permission]) => ({
|
|
@@ -127,7 +140,7 @@ class FileSystem {
|
|
|
127
140
|
}
|
|
128
141
|
resolveSafePath(virtualPath) {
|
|
129
142
|
if (this.mountBase === null) {
|
|
130
|
-
return
|
|
143
|
+
return this.pathOps.resolve(virtualPath);
|
|
131
144
|
}
|
|
132
145
|
const segments = virtualPath.split("/").filter(Boolean);
|
|
133
146
|
let depth = 0;
|
|
@@ -141,12 +154,12 @@ class FileSystem {
|
|
|
141
154
|
depth++;
|
|
142
155
|
}
|
|
143
156
|
}
|
|
144
|
-
const normalized =
|
|
157
|
+
const normalized = this.pathOps.normalize(virtualPath);
|
|
145
158
|
const relativePath = normalized.startsWith("/") ? normalized.slice(1) : normalized;
|
|
146
|
-
const realPath =
|
|
147
|
-
const resolved =
|
|
148
|
-
const relativeFromMount =
|
|
149
|
-
const escapesMount = relativeFromMount === ".." || relativeFromMount.startsWith(`..${
|
|
159
|
+
const realPath = this.pathOps.join(this.mountBase, relativePath);
|
|
160
|
+
const resolved = this.pathOps.resolve(realPath);
|
|
161
|
+
const relativeFromMount = this.pathOps.relative(this.mountBase, resolved);
|
|
162
|
+
const escapesMount = relativeFromMount === ".." || relativeFromMount.startsWith(`..${this.pathOps.separator}`) || this.pathOps.isAbsolute(relativeFromMount);
|
|
150
163
|
if (escapesMount) {
|
|
151
164
|
throw new Error(`Path traversal blocked: "${virtualPath}" escapes mount point`);
|
|
152
165
|
}
|
|
@@ -207,13 +220,13 @@ class FileSystem {
|
|
|
207
220
|
await this.underlyingFs.promises.rm(realPath, opts);
|
|
208
221
|
}
|
|
209
222
|
resolve(...paths) {
|
|
210
|
-
return
|
|
223
|
+
return this.pathOps.resolve("/", ...paths);
|
|
211
224
|
}
|
|
212
225
|
dirname(filePath) {
|
|
213
|
-
return
|
|
226
|
+
return this.pathOps.dirname(filePath);
|
|
214
227
|
}
|
|
215
228
|
basename(filePath) {
|
|
216
|
-
return
|
|
229
|
+
return this.pathOps.basename(filePath);
|
|
217
230
|
}
|
|
218
231
|
async glob(pattern, opts) {
|
|
219
232
|
const cwd = opts?.cwd ?? "/";
|
|
@@ -263,7 +276,7 @@ class FileSystem {
|
|
|
263
276
|
const realPath = this.resolveSafePath(currentPath);
|
|
264
277
|
const entries = await this.underlyingFs.promises.readdir(realPath);
|
|
265
278
|
for (const entry of entries) {
|
|
266
|
-
const entryPath =
|
|
279
|
+
const entryPath = this.pathOps.join(currentPath, String(entry));
|
|
267
280
|
try {
|
|
268
281
|
const entryRealPath = this.resolveSafePath(entryPath);
|
|
269
282
|
const stat = await this.underlyingFs.promises.stat(entryRealPath);
|
|
@@ -284,7 +297,7 @@ class FileSystem {
|
|
|
284
297
|
for (const entry of entries) {
|
|
285
298
|
const entryName = String(entry);
|
|
286
299
|
if (regex.test(entryName)) {
|
|
287
|
-
const entryPath =
|
|
300
|
+
const entryPath = this.pathOps.join(currentPath, entryName);
|
|
288
301
|
if (rest.length === 0) {
|
|
289
302
|
results.push(entryPath);
|
|
290
303
|
} else {
|
|
@@ -332,4 +345,4 @@ class FileSystem {
|
|
|
332
345
|
}
|
|
333
346
|
}
|
|
334
347
|
|
|
335
|
-
//# debugId=
|
|
348
|
+
//# debugId=BBDA08F815F6373F64756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/fs/real-fs.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import * as path from \"path\";\nimport * as nodeFs from \"node:fs/promises\";\nimport type { VirtualFS, FileStat } from \"../types.cjs\";\n\nexport type Permission = \"read-write\" | \"read-only\" | \"excluded\";\nexport type PermissionRules = Record<string, Permission>;\n\n// Minimal interface for the underlying fs (compatible with node:fs and memfs)\nexport interface UnderlyingFS {\n promises: {\n readFile(path: string): Promise<Buffer | Uint8Array | string>;\n readdir(path: string): Promise<string[]>;\n stat(path: string): Promise<{\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n }>;\n writeFile(path: string, data: Buffer | string): Promise<void>;\n appendFile(path: string, data: Buffer | string): Promise<void>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<string | undefined | void>;\n rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void>;\n };\n}\n\n// Default: use real node:fs\nconst defaultFS: UnderlyingFS = { promises: nodeFs };\n\ninterface CompiledRule {\n pattern: string;\n permission: Permission;\n specificity: number;\n}\n\nexport class FileSystem implements VirtualFS {\n private readonly mountBase: string | null;\n private readonly rules: CompiledRule[];\n protected readonly underlyingFs: UnderlyingFS;\n\n constructor(mountPath?: string, permissions?: PermissionRules, fs?: UnderlyingFS) {\n this.mountBase = mountPath ? path.resolve(mountPath) : null;\n this.rules = this.compileRules(permissions ?? {});\n this.underlyingFs = fs ?? defaultFS;\n }\n\n private compileRules(permissions: PermissionRules): CompiledRule[] {\n return Object.entries(permissions)\n .map(([pattern, permission]) => ({\n pattern,\n permission,\n specificity: this.calculateSpecificity(pattern),\n }))\n .sort((a, b) => b.specificity - a.specificity); // highest first\n }\n\n private calculateSpecificity(pattern: string): number {\n const segments = pattern.split(\"/\").filter(Boolean);\n let score = segments.length * 1000; // segment count is primary\n\n for (const seg of segments) {\n if (seg === \"**\") score += 0;\n else if (seg.includes(\"*\")) score += 1;\n else score += 10; // literal segment\n }\n return score;\n }\n\n public getPermission(virtualPath: string): Permission {\n const normalized = virtualPath.replace(/^\\/+/, \"\"); // strip leading slashes\n\n for (const rule of this.rules) {\n if (this.matchGlob(rule.pattern, normalized)) {\n return rule.permission;\n }\n }\n return \"read-write\"; // default\n }\n\n private matchGlob(pattern: string, filePath: string): boolean {\n // Convert glob to regex\n // ** matches any path segments, * matches within segment\n const regex = pattern\n .split(\"/\")\n .map((seg) => {\n if (seg === \"**\") return \".*\";\n return seg.replace(/\\*/g, \"[^/]*\").replace(/\\?/g, \"[^/]\");\n })\n .join(\"/\");\n return new RegExp(`^${regex}$`).test(filePath);\n }\n\n public checkPermission(virtualPath: string, operation: \"read\" | \"write\"): void {\n const perm = this.getPermission(virtualPath);\n\n if (perm === \"excluded\") {\n throw new Error(`Access denied: \"${virtualPath}\" is excluded`);\n }\n if (operation === \"write\" && perm === \"read-only\") {\n throw new Error(`Access denied: \"${virtualPath}\" is read-only`);\n }\n }\n\n private resolveSafePath(virtualPath: string): string {\n if (this.mountBase === null) {\n return path.resolve(virtualPath);\n }\n\n // Check for path traversal by tracking depth\n const segments = virtualPath.split(\"/\").filter(Boolean);\n let depth = 0;\n for (const seg of segments) {\n if (seg === \"..\") {\n depth--;\n if (depth < 0) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n } else if (seg !== \".\") {\n depth++;\n }\n }\n\n const normalized = path.normalize(virtualPath);\n const relativePath = normalized.startsWith(\"/\") ? normalized.slice(1) : normalized;\n const realPath = path.join(this.mountBase, relativePath);\n const resolved = path.resolve(realPath);\n\n // Double-check containment (defense in depth), including root mounts.\n const relativeFromMount = path.relative(this.mountBase, resolved);\n const escapesMount =\n relativeFromMount === \"..\" ||\n relativeFromMount.startsWith(`..${path.sep}`) ||\n path.isAbsolute(relativeFromMount);\n if (escapesMount) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n\n return resolved;\n }\n\n // Read operations\n async readFile(filePath: string): Promise<Buffer>;\n async readFile(filePath: string, encoding: BufferEncoding): Promise<string>;\n async readFile(filePath: string, encoding?: BufferEncoding): Promise<Buffer | string> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const content = await this.underlyingFs.promises.readFile(realPath);\n const buf = Buffer.from(content);\n return encoding ? buf.toString(encoding) : buf;\n }\n\n async readdir(dirPath: string): Promise<string[]> {\n this.checkPermission(dirPath, \"read\");\n const realPath = this.resolveSafePath(dirPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n return entries.map(String);\n }\n\n async stat(filePath: string): Promise<FileStat> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const stats = await this.underlyingFs.promises.stat(realPath);\n return {\n isFile: () => stats.isFile(),\n isDirectory: () => stats.isDirectory(),\n size: stats.size,\n mtime: stats.mtime,\n };\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.stat(realPath);\n return true;\n } catch {\n return false;\n }\n }\n\n // Write operations\n async writeFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.writeFile(realPath, data);\n }\n\n async appendFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.appendFile(realPath, data);\n }\n\n async mkdir(dirPath: string, opts?: { recursive?: boolean }): Promise<void> {\n this.checkPermission(dirPath, \"write\");\n const realPath = this.resolveSafePath(dirPath);\n await this.underlyingFs.promises.mkdir(realPath, opts);\n }\n\n async rm(filePath: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.rm(realPath, opts);\n }\n\n // Path utilities (no permission check needed)\n resolve(...paths: string[]): string {\n return path.resolve(\"/\", ...paths);\n }\n\n dirname(filePath: string): string {\n return path.dirname(filePath);\n }\n\n basename(filePath: string): string {\n return path.basename(filePath);\n }\n\n // Glob with permission filtering\n async glob(pattern: string, opts?: { cwd?: string }): Promise<string[]> {\n const cwd = opts?.cwd ?? \"/\";\n this.checkPermission(cwd, \"read\");\n\n const matches = await this.expandGlob(pattern, cwd);\n\n // Filter out excluded paths\n return matches.filter((p) => this.getPermission(p) !== \"excluded\").sort();\n }\n\n // Glob expansion (similar to memfs-adapter implementation)\n private async expandGlob(pattern: string, cwd: string): Promise<string[]> {\n // Handle brace expansion first\n const patterns = this.expandBraces(pattern);\n const allMatches: string[] = [];\n\n for (const pat of patterns) {\n const matches = await this.matchPattern(pat, cwd);\n allMatches.push(...matches);\n }\n\n // Remove duplicates and sort\n return [...new Set(allMatches)].sort();\n }\n\n private expandBraces(pattern: string): string[] {\n const braceMatch = pattern.match(/\\{([^{}]+)\\}/);\n if (!braceMatch) return [pattern];\n\n const before = pattern.slice(0, braceMatch.index);\n const after = pattern.slice(braceMatch.index! + braceMatch[0].length);\n const options = braceMatch[1]!.split(\",\");\n\n const results: string[] = [];\n for (const opt of options) {\n const expanded = this.expandBraces(before + opt + after);\n results.push(...expanded);\n }\n return results;\n }\n\n private async matchPattern(pattern: string, cwd: string): Promise<string[]> {\n const parts = pattern.split(\"/\").filter((p) => p !== \"\");\n const isAbsolute = pattern.startsWith(\"/\");\n const startDir = isAbsolute ? \"/\" : cwd;\n\n return this.matchParts(parts, startDir);\n }\n\n private async matchParts(parts: string[], currentPath: string): Promise<string[]> {\n if (parts.length === 0) {\n return [currentPath];\n }\n\n const [part, ...rest] = parts;\n\n // Handle ** (recursive glob)\n if (part === \"**\") {\n const results: string[] = [];\n\n // Match current directory\n const withoutStar = await this.matchParts(rest, currentPath);\n results.push(...withoutStar);\n\n // Recurse into subdirectories\n try {\n const realPath = this.resolveSafePath(currentPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n for (const entry of entries) {\n const entryPath = path.posix.join(currentPath, String(entry));\n try {\n const entryRealPath = this.resolveSafePath(entryPath);\n const stat = await this.underlyingFs.promises.stat(entryRealPath);\n if (stat.isDirectory()) {\n const subMatches = await this.matchParts(parts, entryPath);\n results.push(...subMatches);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n } catch {\n // Directory not readable\n }\n\n return results;\n }\n\n // Handle regular glob patterns\n const regex = this.globToRegex(part!);\n\n try {\n const realPath = this.resolveSafePath(currentPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n const results: string[] = [];\n\n for (const entry of entries) {\n const entryName = String(entry);\n if (regex.test(entryName)) {\n const entryPath = path.posix.join(currentPath, entryName);\n if (rest.length === 0) {\n results.push(entryPath);\n } else {\n try {\n const entryRealPath = this.resolveSafePath(entryPath);\n const stat = await this.underlyingFs.promises.stat(entryRealPath);\n if (stat.isDirectory()) {\n const subMatches = await this.matchParts(rest, entryPath);\n results.push(...subMatches);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n }\n }\n\n return results;\n } catch {\n return [];\n }\n }\n\n private globToRegex(pattern: string): RegExp {\n let regex = \"^\";\n for (let i = 0; i < pattern.length; i++) {\n const char = pattern[i]!;\n if (char === \"*\") {\n regex += \"[^/]*\";\n } else if (char === \"?\") {\n regex += \"[^/]\";\n } else if (char === \"[\") {\n // Character class\n let j = i + 1;\n let classContent = \"\";\n while (j < pattern.length && pattern[j] !== \"]\") {\n classContent += pattern[j];\n j++;\n }\n regex += `[${classContent}]`;\n i = j;\n } else if (\".+^${}()|\\\\\".includes(char)) {\n regex += \"\\\\\" + char;\n } else {\n regex += char;\n }\n }\n regex += \"$\";\n return new RegExp(regex);\n }\n}\n"
|
|
5
|
+
"import * as path from \"path\";\nimport * as nodeFs from \"node:fs/promises\";\nimport type { VirtualFS, FileStat } from \"../types.cjs\";\n\nexport type Permission = \"read-write\" | \"read-only\" | \"excluded\";\nexport type PermissionRules = Record<string, Permission>;\nexport interface PathOps {\n readonly separator: string;\n resolve(...paths: string[]): string;\n normalize(path: string): string;\n join(...paths: string[]): string;\n relative(from: string, to: string): string;\n isAbsolute(path: string): boolean;\n dirname(path: string): string;\n basename(path: string): string;\n}\n\n// Minimal interface for the underlying fs (compatible with node:fs and memfs)\nexport interface UnderlyingFS {\n pathOps?: PathOps;\n promises: {\n readFile(path: string): Promise<Buffer | Uint8Array | string>;\n readdir(path: string): Promise<string[]>;\n stat(path: string): Promise<{\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n }>;\n writeFile(path: string, data: Buffer | string): Promise<void>;\n appendFile(path: string, data: Buffer | string): Promise<void>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<string | undefined | void>;\n rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void>;\n };\n}\n\n// Default: use real node:fs\nconst defaultFS: UnderlyingFS = { promises: nodeFs };\nconst nodePathOps: PathOps = {\n separator: path.sep,\n resolve: (...paths) => path.resolve(...paths),\n normalize: (filePath) => path.normalize(filePath),\n join: (...paths) => path.join(...paths),\n relative: (from, to) => path.relative(from, to),\n isAbsolute: (filePath) => path.isAbsolute(filePath),\n dirname: (filePath) => path.dirname(filePath),\n basename: (filePath) => path.basename(filePath),\n};\n\ninterface CompiledRule {\n pattern: string;\n permission: Permission;\n specificity: number;\n}\n\nexport class FileSystem implements VirtualFS {\n private readonly mountBase: string | null;\n private readonly rules: CompiledRule[];\n private readonly pathOps: PathOps;\n protected readonly underlyingFs: UnderlyingFS;\n\n constructor(mountPath?: string, permissions?: PermissionRules, fs?: UnderlyingFS) {\n const underlyingFs = fs ?? defaultFS;\n this.pathOps = underlyingFs.pathOps ?? nodePathOps;\n this.mountBase = mountPath ? this.pathOps.resolve(mountPath) : null;\n this.rules = this.compileRules(permissions ?? {});\n this.underlyingFs = underlyingFs;\n }\n\n private compileRules(permissions: PermissionRules): CompiledRule[] {\n return Object.entries(permissions)\n .map(([pattern, permission]) => ({\n pattern,\n permission,\n specificity: this.calculateSpecificity(pattern),\n }))\n .sort((a, b) => b.specificity - a.specificity); // highest first\n }\n\n private calculateSpecificity(pattern: string): number {\n const segments = pattern.split(\"/\").filter(Boolean);\n let score = segments.length * 1000; // segment count is primary\n\n for (const seg of segments) {\n if (seg === \"**\") score += 0;\n else if (seg.includes(\"*\")) score += 1;\n else score += 10; // literal segment\n }\n return score;\n }\n\n public getPermission(virtualPath: string): Permission {\n const normalized = virtualPath.replace(/^\\/+/, \"\"); // strip leading slashes\n\n for (const rule of this.rules) {\n if (this.matchGlob(rule.pattern, normalized)) {\n return rule.permission;\n }\n }\n return \"read-write\"; // default\n }\n\n private matchGlob(pattern: string, filePath: string): boolean {\n // Convert glob to regex\n // ** matches any path segments, * matches within segment\n const regex = pattern\n .split(\"/\")\n .map((seg) => {\n if (seg === \"**\") return \".*\";\n return seg.replace(/\\*/g, \"[^/]*\").replace(/\\?/g, \"[^/]\");\n })\n .join(\"/\");\n return new RegExp(`^${regex}$`).test(filePath);\n }\n\n public checkPermission(virtualPath: string, operation: \"read\" | \"write\"): void {\n const perm = this.getPermission(virtualPath);\n\n if (perm === \"excluded\") {\n throw new Error(`Access denied: \"${virtualPath}\" is excluded`);\n }\n if (operation === \"write\" && perm === \"read-only\") {\n throw new Error(`Access denied: \"${virtualPath}\" is read-only`);\n }\n }\n\n private resolveSafePath(virtualPath: string): string {\n if (this.mountBase === null) {\n return this.pathOps.resolve(virtualPath);\n }\n\n // Check for path traversal by tracking depth\n const segments = virtualPath.split(\"/\").filter(Boolean);\n let depth = 0;\n for (const seg of segments) {\n if (seg === \"..\") {\n depth--;\n if (depth < 0) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n } else if (seg !== \".\") {\n depth++;\n }\n }\n\n const normalized = this.pathOps.normalize(virtualPath);\n const relativePath = normalized.startsWith(\"/\") ? normalized.slice(1) : normalized;\n const realPath = this.pathOps.join(this.mountBase, relativePath);\n const resolved = this.pathOps.resolve(realPath);\n\n // Double-check containment (defense in depth), including root mounts.\n const relativeFromMount = this.pathOps.relative(this.mountBase, resolved);\n const escapesMount =\n relativeFromMount === \"..\" ||\n relativeFromMount.startsWith(`..${this.pathOps.separator}`) ||\n this.pathOps.isAbsolute(relativeFromMount);\n if (escapesMount) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n\n return resolved;\n }\n\n // Read operations\n async readFile(filePath: string): Promise<Buffer>;\n async readFile(filePath: string, encoding: BufferEncoding): Promise<string>;\n async readFile(filePath: string, encoding?: BufferEncoding): Promise<Buffer | string> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const content = await this.underlyingFs.promises.readFile(realPath);\n const buf = Buffer.from(content);\n return encoding ? buf.toString(encoding) : buf;\n }\n\n async readdir(dirPath: string): Promise<string[]> {\n this.checkPermission(dirPath, \"read\");\n const realPath = this.resolveSafePath(dirPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n return entries.map(String);\n }\n\n async stat(filePath: string): Promise<FileStat> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const stats = await this.underlyingFs.promises.stat(realPath);\n return {\n isFile: () => stats.isFile(),\n isDirectory: () => stats.isDirectory(),\n size: stats.size,\n mtime: stats.mtime,\n };\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.stat(realPath);\n return true;\n } catch {\n return false;\n }\n }\n\n // Write operations\n async writeFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.writeFile(realPath, data);\n }\n\n async appendFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.appendFile(realPath, data);\n }\n\n async mkdir(dirPath: string, opts?: { recursive?: boolean }): Promise<void> {\n this.checkPermission(dirPath, \"write\");\n const realPath = this.resolveSafePath(dirPath);\n await this.underlyingFs.promises.mkdir(realPath, opts);\n }\n\n async rm(filePath: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.rm(realPath, opts);\n }\n\n // Path utilities (no permission check needed)\n resolve(...paths: string[]): string {\n return this.pathOps.resolve(\"/\", ...paths);\n }\n\n dirname(filePath: string): string {\n return this.pathOps.dirname(filePath);\n }\n\n basename(filePath: string): string {\n return this.pathOps.basename(filePath);\n }\n\n // Glob with permission filtering\n async glob(pattern: string, opts?: { cwd?: string }): Promise<string[]> {\n const cwd = opts?.cwd ?? \"/\";\n this.checkPermission(cwd, \"read\");\n\n const matches = await this.expandGlob(pattern, cwd);\n\n // Filter out excluded paths\n return matches.filter((p) => this.getPermission(p) !== \"excluded\").sort();\n }\n\n // Glob expansion (similar to memfs-adapter implementation)\n private async expandGlob(pattern: string, cwd: string): Promise<string[]> {\n // Handle brace expansion first\n const patterns = this.expandBraces(pattern);\n const allMatches: string[] = [];\n\n for (const pat of patterns) {\n const matches = await this.matchPattern(pat, cwd);\n allMatches.push(...matches);\n }\n\n // Remove duplicates and sort\n return [...new Set(allMatches)].sort();\n }\n\n private expandBraces(pattern: string): string[] {\n const braceMatch = pattern.match(/\\{([^{}]+)\\}/);\n if (!braceMatch) return [pattern];\n\n const before = pattern.slice(0, braceMatch.index);\n const after = pattern.slice(braceMatch.index! + braceMatch[0].length);\n const options = braceMatch[1]!.split(\",\");\n\n const results: string[] = [];\n for (const opt of options) {\n const expanded = this.expandBraces(before + opt + after);\n results.push(...expanded);\n }\n return results;\n }\n\n private async matchPattern(pattern: string, cwd: string): Promise<string[]> {\n const parts = pattern.split(\"/\").filter((p) => p !== \"\");\n const isAbsolute = pattern.startsWith(\"/\");\n const startDir = isAbsolute ? \"/\" : cwd;\n\n return this.matchParts(parts, startDir);\n }\n\n private async matchParts(parts: string[], currentPath: string): Promise<string[]> {\n if (parts.length === 0) {\n return [currentPath];\n }\n\n const [part, ...rest] = parts;\n\n // Handle ** (recursive glob)\n if (part === \"**\") {\n const results: string[] = [];\n\n // Match current directory\n const withoutStar = await this.matchParts(rest, currentPath);\n results.push(...withoutStar);\n\n // Recurse into subdirectories\n try {\n const realPath = this.resolveSafePath(currentPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n for (const entry of entries) {\n const entryPath = this.pathOps.join(currentPath, String(entry));\n try {\n const entryRealPath = this.resolveSafePath(entryPath);\n const stat = await this.underlyingFs.promises.stat(entryRealPath);\n if (stat.isDirectory()) {\n const subMatches = await this.matchParts(parts, entryPath);\n results.push(...subMatches);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n } catch {\n // Directory not readable\n }\n\n return results;\n }\n\n // Handle regular glob patterns\n const regex = this.globToRegex(part!);\n\n try {\n const realPath = this.resolveSafePath(currentPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n const results: string[] = [];\n\n for (const entry of entries) {\n const entryName = String(entry);\n if (regex.test(entryName)) {\n const entryPath = this.pathOps.join(currentPath, entryName);\n if (rest.length === 0) {\n results.push(entryPath);\n } else {\n try {\n const entryRealPath = this.resolveSafePath(entryPath);\n const stat = await this.underlyingFs.promises.stat(entryRealPath);\n if (stat.isDirectory()) {\n const subMatches = await this.matchParts(rest, entryPath);\n results.push(...subMatches);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n }\n }\n\n return results;\n } catch {\n return [];\n }\n }\n\n private globToRegex(pattern: string): RegExp {\n let regex = \"^\";\n for (let i = 0; i < pattern.length; i++) {\n const char = pattern[i]!;\n if (char === \"*\") {\n regex += \"[^/]*\";\n } else if (char === \"?\") {\n regex += \"[^/]\";\n } else if (char === \"[\") {\n // Character class\n let j = i + 1;\n let classContent = \"\";\n while (j < pattern.length && pattern[j] !== \"]\") {\n classContent += pattern[j];\n j++;\n }\n regex += `[${classContent}]`;\n i = j;\n } else if (\".+^${}()|\\\\\".includes(char)) {\n regex += \"\\\\\" + char;\n } else {\n regex += char;\n }\n }\n regex += \"$\";\n return new RegExp(regex);\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAsB,IAAtB;AACwB,IAAxB;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "BBDA08F815F6373F64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -36,17 +36,65 @@ var __export = (target, all) => {
|
|
|
36
36
|
});
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
// src/fs/
|
|
40
|
-
var
|
|
41
|
-
__export(
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
// src/fs/web-fs.ts
|
|
40
|
+
var exports_web_fs = {};
|
|
41
|
+
__export(exports_web_fs, {
|
|
42
|
+
createWebUnderlyingFS: () => createWebUnderlyingFS,
|
|
43
|
+
WebFileSystem: () => WebFileSystem
|
|
44
44
|
});
|
|
45
|
-
module.exports = __toCommonJS(
|
|
45
|
+
module.exports = __toCommonJS(exports_web_fs);
|
|
46
46
|
var import_real_fs = require("./real-fs.cjs");
|
|
47
47
|
var DIRECTORY_MTIME = new Date(0);
|
|
48
|
-
|
|
48
|
+
var WEB_PATH_OPS = {
|
|
49
|
+
separator: "/",
|
|
50
|
+
resolve(...paths) {
|
|
51
|
+
return normalizeWebPath(paths.join("/"));
|
|
52
|
+
},
|
|
53
|
+
normalize: normalizeWebPath,
|
|
54
|
+
join(...paths) {
|
|
55
|
+
const nonEmpty = paths.filter((segment) => segment.length > 0);
|
|
56
|
+
if (nonEmpty.length === 0) {
|
|
57
|
+
return ".";
|
|
58
|
+
}
|
|
59
|
+
return normalizeWebPath(nonEmpty.join("/"));
|
|
60
|
+
},
|
|
61
|
+
relative(from, to) {
|
|
62
|
+
const fromSegments = getPathSegments(from);
|
|
63
|
+
const toSegments = getPathSegments(to);
|
|
64
|
+
let shared = 0;
|
|
65
|
+
while (shared < fromSegments.length && shared < toSegments.length && fromSegments[shared] === toSegments[shared]) {
|
|
66
|
+
shared++;
|
|
67
|
+
}
|
|
68
|
+
const up = new Array(fromSegments.length - shared).fill("..");
|
|
69
|
+
const down = toSegments.slice(shared);
|
|
70
|
+
return [...up, ...down].join("/");
|
|
71
|
+
},
|
|
72
|
+
isAbsolute(path) {
|
|
73
|
+
return path.startsWith("/");
|
|
74
|
+
},
|
|
75
|
+
dirname(path) {
|
|
76
|
+
const normalized = normalizeWebPath(path);
|
|
77
|
+
if (normalized === "/") {
|
|
78
|
+
return "/";
|
|
79
|
+
}
|
|
80
|
+
const segments = getPathSegments(normalized);
|
|
81
|
+
if (segments.length <= 1) {
|
|
82
|
+
return "/";
|
|
83
|
+
}
|
|
84
|
+
return `/${segments.slice(0, -1).join("/")}`;
|
|
85
|
+
},
|
|
86
|
+
basename(path) {
|
|
87
|
+
const normalized = normalizeWebPath(path);
|
|
88
|
+
if (normalized === "/") {
|
|
89
|
+
return "";
|
|
90
|
+
}
|
|
91
|
+
const segments = getPathSegments(normalized);
|
|
92
|
+
return segments[segments.length - 1] ?? "";
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
function createWebUnderlyingFS(root) {
|
|
49
96
|
return {
|
|
97
|
+
pathOps: WEB_PATH_OPS,
|
|
50
98
|
promises: {
|
|
51
99
|
async readFile(path) {
|
|
52
100
|
const { parentSegments, name } = splitParent(path);
|
|
@@ -126,7 +174,7 @@ function createOPFSUnderlyingFS(root) {
|
|
|
126
174
|
const name = segments[segments.length - 1];
|
|
127
175
|
const exists = await entryExists(parent, name);
|
|
128
176
|
if (exists) {
|
|
129
|
-
throw new Error(`EEXIST: file already exists, mkdir '${
|
|
177
|
+
throw new Error(`EEXIST: file already exists, mkdir '${normalizeWebPath(path)}'`);
|
|
130
178
|
}
|
|
131
179
|
await parent.getDirectoryHandle(name, { create: true });
|
|
132
180
|
},
|
|
@@ -150,9 +198,9 @@ function createOPFSUnderlyingFS(root) {
|
|
|
150
198
|
};
|
|
151
199
|
}
|
|
152
200
|
|
|
153
|
-
class
|
|
201
|
+
class WebFileSystem extends import_real_fs.FileSystem {
|
|
154
202
|
constructor(root, permissions) {
|
|
155
|
-
super("/", permissions,
|
|
203
|
+
super("/", permissions, createWebUnderlyingFS(root));
|
|
156
204
|
}
|
|
157
205
|
}
|
|
158
206
|
function createDirectoryStat() {
|
|
@@ -163,7 +211,7 @@ function createDirectoryStat() {
|
|
|
163
211
|
mtime: DIRECTORY_MTIME
|
|
164
212
|
};
|
|
165
213
|
}
|
|
166
|
-
function
|
|
214
|
+
function normalizeWebPath(path) {
|
|
167
215
|
const normalized = path.replace(/\\/g, "/");
|
|
168
216
|
const rawSegments = (normalized.startsWith("/") ? normalized : `/${normalized}`).split("/").filter(Boolean);
|
|
169
217
|
const segments = [];
|
|
@@ -179,7 +227,7 @@ function normalizeOpfsPath(path) {
|
|
|
179
227
|
return `/${segments.join("/")}`;
|
|
180
228
|
}
|
|
181
229
|
function getPathSegments(path) {
|
|
182
|
-
const normalized =
|
|
230
|
+
const normalized = normalizeWebPath(path);
|
|
183
231
|
return normalized.split("/").filter(Boolean);
|
|
184
232
|
}
|
|
185
233
|
function splitParent(path) {
|
|
@@ -244,4 +292,4 @@ function toWritableData(data) {
|
|
|
244
292
|
return out.buffer;
|
|
245
293
|
}
|
|
246
294
|
|
|
247
|
-
//# debugId=
|
|
295
|
+
//# debugId=63C0A71C805BC57D64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/fs/web-fs.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { FileSystem, type PathOps, type PermissionRules, type UnderlyingFS } from \"./real-fs.cjs\";\n\nconst DIRECTORY_MTIME = new Date(0);\nconst WEB_PATH_OPS: PathOps = {\n separator: \"/\",\n resolve(...paths: string[]): string {\n return normalizeWebPath(paths.join(\"/\"));\n },\n normalize: normalizeWebPath,\n join(...paths: string[]): string {\n const nonEmpty = paths.filter((segment) => segment.length > 0);\n if (nonEmpty.length === 0) {\n return \".\";\n }\n return normalizeWebPath(nonEmpty.join(\"/\"));\n },\n relative(from: string, to: string): string {\n const fromSegments = getPathSegments(from);\n const toSegments = getPathSegments(to);\n let shared = 0;\n\n while (\n shared < fromSegments.length &&\n shared < toSegments.length &&\n fromSegments[shared] === toSegments[shared]\n ) {\n shared++;\n }\n\n const up = new Array(fromSegments.length - shared).fill(\"..\");\n const down = toSegments.slice(shared);\n return [...up, ...down].join(\"/\");\n },\n isAbsolute(path: string): boolean {\n return path.startsWith(\"/\");\n },\n dirname(path: string): string {\n const normalized = normalizeWebPath(path);\n if (normalized === \"/\") {\n return \"/\";\n }\n\n const segments = getPathSegments(normalized);\n if (segments.length <= 1) {\n return \"/\";\n }\n\n return `/${segments.slice(0, -1).join(\"/\")}`;\n },\n basename(path: string): string {\n const normalized = normalizeWebPath(path);\n if (normalized === \"/\") {\n return \"\";\n }\n\n const segments = getPathSegments(normalized);\n return segments[segments.length - 1] ?? \"\";\n },\n};\n\nexport function createWebUnderlyingFS(root: FileSystemDirectoryHandle): UnderlyingFS {\n return {\n pathOps: WEB_PATH_OPS,\n promises: {\n async readFile(path: string): Promise<Buffer> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: false });\n const file = await fileHandle.getFile();\n return Buffer.from(await file.arrayBuffer());\n },\n\n async readdir(path: string): Promise<string[]> {\n const dir = await walkDirectory(root, getPathSegments(path), false);\n const entries: string[] = [];\n for await (const [name] of dir.entries()) {\n entries.push(name);\n }\n return entries;\n },\n\n async stat(path: string): Promise<{\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n }> {\n const segments = getPathSegments(path);\n\n if (segments.length === 0) {\n return createDirectoryStat();\n }\n\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n\n try {\n const fileHandle = await parent.getFileHandle(name, { create: false });\n const file = await fileHandle.getFile();\n return {\n isFile: () => true,\n isDirectory: () => false,\n size: file.size,\n mtime: new Date(file.lastModified ?? 0),\n };\n } catch (error) {\n if (!isNotFoundOrTypeMismatch(error)) throw error;\n }\n\n try {\n await parent.getDirectoryHandle(name, { create: false });\n return createDirectoryStat();\n } catch (error) {\n if (!isNotFoundOrTypeMismatch(error)) throw error;\n throw error;\n }\n },\n\n async writeFile(path: string, data: Buffer | string): Promise<void> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: true });\n const writable = await fileHandle.createWritable();\n await writable.write(toWritableData(data));\n await writable.close();\n },\n\n async appendFile(path: string, data: Buffer | string): Promise<void> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: true });\n const file = await fileHandle.getFile();\n const writable = await fileHandle.createWritable({ keepExistingData: true });\n await writable.write({\n type: \"write\",\n position: file.size,\n data: toWritableData(data),\n });\n await writable.close();\n },\n\n async mkdir(path: string, opts?: { recursive?: boolean }): Promise<void> {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n return;\n }\n\n if (opts?.recursive) {\n await walkDirectory(root, segments, true);\n return;\n }\n\n const parent = await walkDirectory(root, segments.slice(0, -1), false);\n const name = segments[segments.length - 1]!;\n const exists = await entryExists(parent, name);\n if (exists) {\n throw new Error(`EEXIST: file already exists, mkdir '${normalizeWebPath(path)}'`);\n }\n await parent.getDirectoryHandle(name, { create: true });\n },\n\n async rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n throw new Error(\"EPERM: operation not permitted, rm '/'\");\n }\n\n const parent = await walkDirectory(root, segments.slice(0, -1), false);\n const name = segments[segments.length - 1]!;\n try {\n await parent.removeEntry(name, { recursive: opts?.recursive });\n } catch (error) {\n if (opts?.force && isNotFoundError(error)) {\n return;\n }\n throw error;\n }\n },\n },\n };\n}\n\nexport class WebFileSystem extends FileSystem {\n constructor(root: FileSystemDirectoryHandle, permissions?: PermissionRules) {\n super(\"/\", permissions, createWebUnderlyingFS(root));\n }\n}\n\nfunction createDirectoryStat() {\n return {\n isFile: () => false,\n isDirectory: () => true,\n size: 0,\n mtime: DIRECTORY_MTIME,\n };\n}\n\nfunction normalizeWebPath(path: string): string {\n const normalized = path.replace(/\\\\/g, \"/\");\n const rawSegments = (normalized.startsWith(\"/\") ? normalized : `/${normalized}`)\n .split(\"/\")\n .filter(Boolean);\n\n const segments: string[] = [];\n for (const segment of rawSegments) {\n if (segment === \".\") continue;\n if (segment === \"..\") {\n segments.pop();\n continue;\n }\n segments.push(segment);\n }\n\n return `/${segments.join(\"/\")}`;\n}\n\nfunction getPathSegments(path: string): string[] {\n const normalized = normalizeWebPath(path);\n return normalized.split(\"/\").filter(Boolean);\n}\n\nfunction splitParent(path: string): { parentSegments: string[]; name: string } {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n throw new Error(`Invalid file path: \"${path}\"`);\n }\n return {\n parentSegments: segments.slice(0, -1),\n name: segments[segments.length - 1]!,\n };\n}\n\nasync function walkDirectory(\n root: FileSystemDirectoryHandle,\n segments: string[],\n create: boolean\n): Promise<FileSystemDirectoryHandle> {\n let current = root;\n for (const segment of segments) {\n current = await current.getDirectoryHandle(segment, { create });\n }\n return current;\n}\n\nasync function entryExists(dir: FileSystemDirectoryHandle, name: string): Promise<boolean> {\n try {\n await dir.getFileHandle(name, { create: false });\n return true;\n } catch (error) {\n if (isTypeMismatchError(error)) return true;\n if (!isNotFoundError(error)) throw error;\n }\n\n try {\n await dir.getDirectoryHandle(name, { create: false });\n return true;\n } catch (error) {\n if (isTypeMismatchError(error)) return true;\n if (!isNotFoundError(error)) throw error;\n return false;\n }\n}\n\nfunction isNotFoundOrTypeMismatch(error: unknown): boolean {\n return isNotFoundError(error) || isTypeMismatchError(error);\n}\n\nfunction isNotFoundError(error: unknown): boolean {\n return getErrorName(error) === \"NotFoundError\";\n}\n\nfunction isTypeMismatchError(error: unknown): boolean {\n return getErrorName(error) === \"TypeMismatchError\";\n}\n\nfunction getErrorName(error: unknown): string | undefined {\n if (!error || typeof error !== \"object\") return undefined;\n const named = error as { name?: unknown };\n return typeof named.name === \"string\" ? named.name : undefined;\n}\n\nfunction toWritableData(data: Buffer | string): string | ArrayBuffer {\n if (typeof data === \"string\") {\n return data;\n }\n const out = new Uint8Array(data.length);\n out.set(data);\n return out.buffer;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAkF,IAAlF;AAEA,IAAM,kBAAkB,IAAI,KAAK,CAAC;AAClC,IAAM,eAAwB;AAAA,EAC5B,WAAW;AAAA,EACX,OAAO,IAAI,OAAyB;AAAA,IAClC,OAAO,iBAAiB,MAAM,KAAK,GAAG,CAAC;AAAA;AAAA,EAEzC,WAAW;AAAA,EACX,IAAI,IAAI,OAAyB;AAAA,IAC/B,MAAM,WAAW,MAAM,OAAO,CAAC,YAAY,QAAQ,SAAS,CAAC;AAAA,IAC7D,IAAI,SAAS,WAAW,GAAG;AAAA,MACzB,OAAO;AAAA,IACT;AAAA,IACA,OAAO,iBAAiB,SAAS,KAAK,GAAG,CAAC;AAAA;AAAA,EAE5C,QAAQ,CAAC,MAAc,IAAoB;AAAA,IACzC,MAAM,eAAe,gBAAgB,IAAI;AAAA,IACzC,MAAM,aAAa,gBAAgB,EAAE;AAAA,IACrC,IAAI,SAAS;AAAA,IAEb,OACE,SAAS,aAAa,UACtB,SAAS,WAAW,UACpB,aAAa,YAAY,WAAW,SACpC;AAAA,MACA;AAAA,IACF;AAAA,IAEA,MAAM,KAAK,IAAI,MAAM,aAAa,SAAS,MAAM,EAAE,KAAK,IAAI;AAAA,IAC5D,MAAM,OAAO,WAAW,MAAM,MAAM;AAAA,IACpC,OAAO,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,KAAK,GAAG;AAAA;AAAA,EAElC,UAAU,CAAC,MAAuB;AAAA,IAChC,OAAO,KAAK,WAAW,GAAG;AAAA;AAAA,EAE5B,OAAO,CAAC,MAAsB;AAAA,IAC5B,MAAM,aAAa,iBAAiB,IAAI;AAAA,IACxC,IAAI,eAAe,KAAK;AAAA,MACtB,OAAO;AAAA,IACT;AAAA,IAEA,MAAM,WAAW,gBAAgB,UAAU;AAAA,IAC3C,IAAI,SAAS,UAAU,GAAG;AAAA,MACxB,OAAO;AAAA,IACT;AAAA,IAEA,OAAO,IAAI,SAAS,MAAM,GAAG,EAAE,EAAE,KAAK,GAAG;AAAA;AAAA,EAE3C,QAAQ,CAAC,MAAsB;AAAA,IAC7B,MAAM,aAAa,iBAAiB,IAAI;AAAA,IACxC,IAAI,eAAe,KAAK;AAAA,MACtB,OAAO;AAAA,IACT;AAAA,IAEA,MAAM,WAAW,gBAAgB,UAAU;AAAA,IAC3C,OAAO,SAAS,SAAS,SAAS,MAAM;AAAA;AAE5C;AAEO,SAAS,qBAAqB,CAAC,MAA+C;AAAA,EACnF,OAAO;AAAA,IACL,SAAS;AAAA,IACT,UAAU;AAAA,WACF,SAAQ,CAAC,MAA+B;AAAA,QAC5C,QAAQ,gBAAgB,SAAS,YAAY,IAAI;AAAA,QACjD,MAAM,SAAS,MAAM,cAAc,MAAM,gBAAgB,KAAK;AAAA,QAC9D,MAAM,aAAa,MAAM,OAAO,cAAc,MAAM,EAAE,QAAQ,MAAM,CAAC;AAAA,QACrE,MAAM,OAAO,MAAM,WAAW,QAAQ;AAAA,QACtC,OAAO,OAAO,KAAK,MAAM,KAAK,YAAY,CAAC;AAAA;AAAA,WAGvC,QAAO,CAAC,MAAiC;AAAA,QAC7C,MAAM,MAAM,MAAM,cAAc,MAAM,gBAAgB,IAAI,GAAG,KAAK;AAAA,QAClE,MAAM,UAAoB,CAAC;AAAA,QAC3B,kBAAkB,SAAS,IAAI,QAAQ,GAAG;AAAA,UACxC,QAAQ,KAAK,IAAI;AAAA,QACnB;AAAA,QACA,OAAO;AAAA;AAAA,WAGH,KAAI,CAAC,MAKR;AAAA,QACD,MAAM,WAAW,gBAAgB,IAAI;AAAA,QAErC,IAAI,SAAS,WAAW,GAAG;AAAA,UACzB,OAAO,oBAAoB;AAAA,QAC7B;AAAA,QAEA,QAAQ,gBAAgB,SAAS,YAAY,IAAI;AAAA,QACjD,MAAM,SAAS,MAAM,cAAc,MAAM,gBAAgB,KAAK;AAAA,QAE9D,IAAI;AAAA,UACF,MAAM,aAAa,MAAM,OAAO,cAAc,MAAM,EAAE,QAAQ,MAAM,CAAC;AAAA,UACrE,MAAM,OAAO,MAAM,WAAW,QAAQ;AAAA,UACtC,OAAO;AAAA,YACL,QAAQ,MAAM;AAAA,YACd,aAAa,MAAM;AAAA,YACnB,MAAM,KAAK;AAAA,YACX,OAAO,IAAI,KAAK,KAAK,gBAAgB,CAAC;AAAA,UACxC;AAAA,UACA,OAAO,OAAO;AAAA,UACd,IAAI,CAAC,yBAAyB,KAAK;AAAA,YAAG,MAAM;AAAA;AAAA,QAG9C,IAAI;AAAA,UACF,MAAM,OAAO,mBAAmB,MAAM,EAAE,QAAQ,MAAM,CAAC;AAAA,UACvD,OAAO,oBAAoB;AAAA,UAC3B,OAAO,OAAO;AAAA,UACd,IAAI,CAAC,yBAAyB,KAAK;AAAA,YAAG,MAAM;AAAA,UAC5C,MAAM;AAAA;AAAA;AAAA,WAIJ,UAAS,CAAC,MAAc,MAAsC;AAAA,QAClE,QAAQ,gBAAgB,SAAS,YAAY,IAAI;AAAA,QACjD,MAAM,SAAS,MAAM,cAAc,MAAM,gBAAgB,KAAK;AAAA,QAC9D,MAAM,aAAa,MAAM,OAAO,cAAc,MAAM,EAAE,QAAQ,KAAK,CAAC;AAAA,QACpE,MAAM,WAAW,MAAM,WAAW,eAAe;AAAA,QACjD,MAAM,SAAS,MAAM,eAAe,IAAI,CAAC;AAAA,QACzC,MAAM,SAAS,MAAM;AAAA;AAAA,WAGjB,WAAU,CAAC,MAAc,MAAsC;AAAA,QACnE,QAAQ,gBAAgB,SAAS,YAAY,IAAI;AAAA,QACjD,MAAM,SAAS,MAAM,cAAc,MAAM,gBAAgB,KAAK;AAAA,QAC9D,MAAM,aAAa,MAAM,OAAO,cAAc,MAAM,EAAE,QAAQ,KAAK,CAAC;AAAA,QACpE,MAAM,OAAO,MAAM,WAAW,QAAQ;AAAA,QACtC,MAAM,WAAW,MAAM,WAAW,eAAe,EAAE,kBAAkB,KAAK,CAAC;AAAA,QAC3E,MAAM,SAAS,MAAM;AAAA,UACnB,MAAM;AAAA,UACN,UAAU,KAAK;AAAA,UACf,MAAM,eAAe,IAAI;AAAA,QAC3B,CAAC;AAAA,QACD,MAAM,SAAS,MAAM;AAAA;AAAA,WAGjB,MAAK,CAAC,MAAc,MAA+C;AAAA,QACvE,MAAM,WAAW,gBAAgB,IAAI;AAAA,QACrC,IAAI,SAAS,WAAW,GAAG;AAAA,UACzB;AAAA,QACF;AAAA,QAEA,IAAI,MAAM,WAAW;AAAA,UACnB,MAAM,cAAc,MAAM,UAAU,IAAI;AAAA,UACxC;AAAA,QACF;AAAA,QAEA,MAAM,SAAS,MAAM,cAAc,MAAM,SAAS,MAAM,GAAG,EAAE,GAAG,KAAK;AAAA,QACrE,MAAM,OAAO,SAAS,SAAS,SAAS;AAAA,QACxC,MAAM,SAAS,MAAM,YAAY,QAAQ,IAAI;AAAA,QAC7C,IAAI,QAAQ;AAAA,UACV,MAAM,IAAI,MAAM,uCAAuC,iBAAiB,IAAI,IAAI;AAAA,QAClF;AAAA,QACA,MAAM,OAAO,mBAAmB,MAAM,EAAE,QAAQ,KAAK,CAAC;AAAA;AAAA,WAGlD,GAAE,CAAC,MAAc,MAAgE;AAAA,QACrF,MAAM,WAAW,gBAAgB,IAAI;AAAA,QACrC,IAAI,SAAS,WAAW,GAAG;AAAA,UACzB,MAAM,IAAI,MAAM,wCAAwC;AAAA,QAC1D;AAAA,QAEA,MAAM,SAAS,MAAM,cAAc,MAAM,SAAS,MAAM,GAAG,EAAE,GAAG,KAAK;AAAA,QACrE,MAAM,OAAO,SAAS,SAAS,SAAS;AAAA,QACxC,IAAI;AAAA,UACF,MAAM,OAAO,YAAY,MAAM,EAAE,WAAW,MAAM,UAAU,CAAC;AAAA,UAC7D,OAAO,OAAO;AAAA,UACd,IAAI,MAAM,SAAS,gBAAgB,KAAK,GAAG;AAAA,YACzC;AAAA,UACF;AAAA,UACA,MAAM;AAAA;AAAA;AAAA,IAGZ;AAAA,EACF;AAAA;AAAA;AAGK,MAAM,sBAAsB,0BAAW;AAAA,EAC5C,WAAW,CAAC,MAAiC,aAA+B;AAAA,IAC1E,MAAM,KAAK,aAAa,sBAAsB,IAAI,CAAC;AAAA;AAEvD;AAEA,SAAS,mBAAmB,GAAG;AAAA,EAC7B,OAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,aAAa,MAAM;AAAA,IACnB,MAAM;AAAA,IACN,OAAO;AAAA,EACT;AAAA;AAGF,SAAS,gBAAgB,CAAC,MAAsB;AAAA,EAC9C,MAAM,aAAa,KAAK,QAAQ,OAAO,GAAG;AAAA,EAC1C,MAAM,eAAe,WAAW,WAAW,GAAG,IAAI,aAAa,IAAI,cAChE,MAAM,GAAG,EACT,OAAO,OAAO;AAAA,EAEjB,MAAM,WAAqB,CAAC;AAAA,EAC5B,WAAW,WAAW,aAAa;AAAA,IACjC,IAAI,YAAY;AAAA,MAAK;AAAA,IACrB,IAAI,YAAY,MAAM;AAAA,MACpB,SAAS,IAAI;AAAA,MACb;AAAA,IACF;AAAA,IACA,SAAS,KAAK,OAAO;AAAA,EACvB;AAAA,EAEA,OAAO,IAAI,SAAS,KAAK,GAAG;AAAA;AAG9B,SAAS,eAAe,CAAC,MAAwB;AAAA,EAC/C,MAAM,aAAa,iBAAiB,IAAI;AAAA,EACxC,OAAO,WAAW,MAAM,GAAG,EAAE,OAAO,OAAO;AAAA;AAG7C,SAAS,WAAW,CAAC,MAA0D;AAAA,EAC7E,MAAM,WAAW,gBAAgB,IAAI;AAAA,EACrC,IAAI,SAAS,WAAW,GAAG;AAAA,IACzB,MAAM,IAAI,MAAM,uBAAuB,OAAO;AAAA,EAChD;AAAA,EACA,OAAO;AAAA,IACL,gBAAgB,SAAS,MAAM,GAAG,EAAE;AAAA,IACpC,MAAM,SAAS,SAAS,SAAS;AAAA,EACnC;AAAA;AAGF,eAAe,aAAa,CAC1B,MACA,UACA,QACoC;AAAA,EACpC,IAAI,UAAU;AAAA,EACd,WAAW,WAAW,UAAU;AAAA,IAC9B,UAAU,MAAM,QAAQ,mBAAmB,SAAS,EAAE,OAAO,CAAC;AAAA,EAChE;AAAA,EACA,OAAO;AAAA;AAGT,eAAe,WAAW,CAAC,KAAgC,MAAgC;AAAA,EACzF,IAAI;AAAA,IACF,MAAM,IAAI,cAAc,MAAM,EAAE,QAAQ,MAAM,CAAC;AAAA,IAC/C,OAAO;AAAA,IACP,OAAO,OAAO;AAAA,IACd,IAAI,oBAAoB,KAAK;AAAA,MAAG,OAAO;AAAA,IACvC,IAAI,CAAC,gBAAgB,KAAK;AAAA,MAAG,MAAM;AAAA;AAAA,EAGrC,IAAI;AAAA,IACF,MAAM,IAAI,mBAAmB,MAAM,EAAE,QAAQ,MAAM,CAAC;AAAA,IACpD,OAAO;AAAA,IACP,OAAO,OAAO;AAAA,IACd,IAAI,oBAAoB,KAAK;AAAA,MAAG,OAAO;AAAA,IACvC,IAAI,CAAC,gBAAgB,KAAK;AAAA,MAAG,MAAM;AAAA,IACnC,OAAO;AAAA;AAAA;AAIX,SAAS,wBAAwB,CAAC,OAAyB;AAAA,EACzD,OAAO,gBAAgB,KAAK,KAAK,oBAAoB,KAAK;AAAA;AAG5D,SAAS,eAAe,CAAC,OAAyB;AAAA,EAChD,OAAO,aAAa,KAAK,MAAM;AAAA;AAGjC,SAAS,mBAAmB,CAAC,OAAyB;AAAA,EACpD,OAAO,aAAa,KAAK,MAAM;AAAA;AAGjC,SAAS,YAAY,CAAC,OAAoC;AAAA,EACxD,IAAI,CAAC,SAAS,OAAO,UAAU;AAAA,IAAU;AAAA,EACzC,MAAM,QAAQ;AAAA,EACd,OAAO,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO;AAAA;AAGvD,SAAS,cAAc,CAAC,MAA6C;AAAA,EACnE,IAAI,OAAO,SAAS,UAAU;AAAA,IAC5B,OAAO;AAAA,EACT;AAAA,EACA,MAAM,MAAM,IAAI,WAAW,KAAK,MAAM;AAAA,EACtC,IAAI,IAAI,IAAI;AAAA,EACZ,OAAO,IAAI;AAAA;",
|
|
8
|
+
"debugId": "63C0A71C805BC57D64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/dist/cjs/src/index.cjs
CHANGED
|
@@ -61,13 +61,14 @@ __export(exports_src, {
|
|
|
61
61
|
isAndNode: () => import_parser2.isAndNode,
|
|
62
62
|
escapeForInterpolation: () => import_utils.escapeForInterpolation,
|
|
63
63
|
escape: () => import_utils.escape,
|
|
64
|
+
createWebUnderlyingFS: () => import_fs2.createWebUnderlyingFS,
|
|
64
65
|
createVirtualFS: () => import_fs.createVirtualFS,
|
|
65
66
|
createStdout: () => import_io2.createStdout,
|
|
66
67
|
createStdin: () => import_io.createStdin,
|
|
67
68
|
createStderr: () => import_io2.createStderr,
|
|
68
69
|
createShellDSL: () => import_shell_dsl.createShellDSL,
|
|
69
70
|
createPipe: () => import_io2.createPipe,
|
|
70
|
-
|
|
71
|
+
WebFileSystem: () => import_fs2.WebFileSystem,
|
|
71
72
|
StdinImpl: () => import_io.StdinImpl,
|
|
72
73
|
ShellPromise: () => import_shell_promise.ShellPromise,
|
|
73
74
|
ShellError: () => import_errors.ShellError,
|
|
@@ -77,7 +78,6 @@ __export(exports_src, {
|
|
|
77
78
|
Parser: () => import_parser.Parser,
|
|
78
79
|
ParseError: () => import_errors.ParseError,
|
|
79
80
|
OutputCollectorImpl: () => import_io2.OutputCollectorImpl,
|
|
80
|
-
OPFSFileSystem: () => import_fs2.OPFSFileSystem,
|
|
81
81
|
Lexer: () => import_lexer.Lexer,
|
|
82
82
|
LexError: () => import_errors.LexError,
|
|
83
83
|
Interpreter: () => import_interpreter.Interpreter,
|
|
@@ -100,4 +100,4 @@ var import_io = require("./io/index.cjs");
|
|
|
100
100
|
var import_io2 = require("./io/index.cjs");
|
|
101
101
|
var import_utils = require("./utils/index.cjs");
|
|
102
102
|
|
|
103
|
-
//# debugId=
|
|
103
|
+
//# debugId=3F6524945419391564756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"// Main class exports\nexport { ShellDSL, createShellDSL, type Program } from \"./shell-dsl.cjs\";\nexport { ShellPromise, type ShellPromiseOptions } from \"./shell-promise.cjs\";\n\n// Types\nexport type {\n VirtualFS,\n FileStat,\n Command,\n CommandContext,\n Stdin,\n Stdout,\n Stderr,\n OutputCollector,\n ExecResult,\n ShellConfig,\n RawValue,\n} from \"./types.cjs\";\nexport { isRawValue } from \"./types.cjs\";\n\n// Errors\nexport { ShellError, LexError, ParseError } from \"./errors.cjs\";\n\n// Lexer\nexport { Lexer, lex, tokenToString } from \"./lexer/index.cjs\";\nexport type { Token, RedirectMode } from \"./lexer/index.cjs\";\n\n// Parser\nexport { Parser, parse } from \"./parser/index.cjs\";\nexport type {\n ASTNode,\n Redirect,\n CommandNode,\n PipelineNode,\n AndNode,\n OrNode,\n SequenceNode,\n LiteralNode,\n VariableNode,\n SubstitutionNode,\n GlobNode,\n ConcatNode,\n IfNode,\n ForNode,\n WhileNode,\n UntilNode,\n CaseNode,\n CaseClause,\n ArithmeticNode,\n} from \"./parser/index.cjs\";\nexport {\n isCommandNode,\n isPipelineNode,\n isAndNode,\n isOrNode,\n isSequenceNode,\n isLiteralNode,\n isVariableNode,\n isSubstitutionNode,\n isGlobNode,\n isConcatNode,\n isIfNode,\n isForNode,\n isWhileNode,\n isUntilNode,\n isCaseNode,\n isArithmeticNode,\n} from \"./parser/index.cjs\";\n\n// Interpreter\nexport { Interpreter, type InterpreterOptions, BreakException, ContinueException } from \"./interpreter/index.cjs\";\n\n// Filesystem\nexport { createVirtualFS } from \"./fs/index.cjs\";\nexport {\n FileSystem,\n ReadOnlyFileSystem,\n
|
|
5
|
+
"// Main class exports\nexport { ShellDSL, createShellDSL, type Program } from \"./shell-dsl.cjs\";\nexport { ShellPromise, type ShellPromiseOptions } from \"./shell-promise.cjs\";\n\n// Types\nexport type {\n VirtualFS,\n FileStat,\n Command,\n CommandContext,\n Stdin,\n Stdout,\n Stderr,\n OutputCollector,\n ExecResult,\n ShellConfig,\n RawValue,\n} from \"./types.cjs\";\nexport { isRawValue } from \"./types.cjs\";\n\n// Errors\nexport { ShellError, LexError, ParseError } from \"./errors.cjs\";\n\n// Lexer\nexport { Lexer, lex, tokenToString } from \"./lexer/index.cjs\";\nexport type { Token, RedirectMode } from \"./lexer/index.cjs\";\n\n// Parser\nexport { Parser, parse } from \"./parser/index.cjs\";\nexport type {\n ASTNode,\n Redirect,\n CommandNode,\n PipelineNode,\n AndNode,\n OrNode,\n SequenceNode,\n LiteralNode,\n VariableNode,\n SubstitutionNode,\n GlobNode,\n ConcatNode,\n IfNode,\n ForNode,\n WhileNode,\n UntilNode,\n CaseNode,\n CaseClause,\n ArithmeticNode,\n} from \"./parser/index.cjs\";\nexport {\n isCommandNode,\n isPipelineNode,\n isAndNode,\n isOrNode,\n isSequenceNode,\n isLiteralNode,\n isVariableNode,\n isSubstitutionNode,\n isGlobNode,\n isConcatNode,\n isIfNode,\n isForNode,\n isWhileNode,\n isUntilNode,\n isCaseNode,\n isArithmeticNode,\n} from \"./parser/index.cjs\";\n\n// Interpreter\nexport { Interpreter, type InterpreterOptions, BreakException, ContinueException } from \"./interpreter/index.cjs\";\n\n// Filesystem\nexport { createVirtualFS } from \"./fs/index.cjs\";\nexport {\n FileSystem,\n ReadOnlyFileSystem,\n WebFileSystem,\n createWebUnderlyingFS,\n type PathOps,\n type Permission,\n type PermissionRules,\n type UnderlyingFS,\n} from \"./fs/index.cjs\";\n\n// I/O\nexport { createStdin, StdinImpl } from \"./io/index.cjs\";\nexport { createStdout, createStderr, createPipe, OutputCollectorImpl, PipeBuffer } from \"./io/index.cjs\";\n\n// Utilities\nexport { escape, escapeForInterpolation } from \"./utils/index.cjs\";\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACuD,IAAvD;AACuD,IAAvD;AAgB2B,IAA3B;AAGiD,IAAjD;AAG0C,IAA1C;AAI8B,IAA9B;AAuCO,IAjBP;AAoBwF,IAAxF;AAGgC,IAAhC;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACuD,IAAvD;AACuD,IAAvD;AAgB2B,IAA3B;AAGiD,IAAjD;AAG0C,IAA1C;AAI8B,IAA9B;AAuCO,IAjBP;AAoBwF,IAAxF;AAGgC,IAAhC;AAUO,IATP;AAYuC,IAAvC;AACwF,IAAxF;AAG+C,IAA/C;",
|
|
8
|
+
"debugId": "3F6524945419391564756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/mjs/package.json
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
import { createVirtualFS } from "./memfs-adapter.mjs";
|
|
3
3
|
import { FileSystem } from "./real-fs.mjs";
|
|
4
4
|
import { ReadOnlyFileSystem } from "./readonly-fs.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { WebFileSystem, createWebUnderlyingFS } from "./web-fs.mjs";
|
|
6
6
|
export {
|
|
7
|
+
createWebUnderlyingFS,
|
|
7
8
|
createVirtualFS,
|
|
8
|
-
|
|
9
|
+
WebFileSystem,
|
|
9
10
|
ReadOnlyFileSystem,
|
|
10
|
-
OPFSFileSystem,
|
|
11
11
|
FileSystem
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
//# debugId=
|
|
14
|
+
//# debugId=48608B4B3C7BEB9864756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/fs/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"export { createVirtualFS } from \"./memfs-adapter.mjs\";\nexport { FileSystem, type Permission, type PermissionRules, type UnderlyingFS } from \"./real-fs.mjs\";\nexport { ReadOnlyFileSystem } from \"./readonly-fs.mjs\";\nexport {
|
|
5
|
+
"export { createVirtualFS } from \"./memfs-adapter.mjs\";\nexport { FileSystem, type PathOps, type Permission, type PermissionRules, type UnderlyingFS } from \"./real-fs.mjs\";\nexport { ReadOnlyFileSystem } from \"./readonly-fs.mjs\";\nexport { WebFileSystem, createWebUnderlyingFS } from \"./web-fs.mjs\";\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": ";AAAA;AACA;AACA;AACA;",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "48608B4B3C7BEB9864756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -2,15 +2,28 @@
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as nodeFs from "node:fs/promises";
|
|
4
4
|
var defaultFS = { promises: nodeFs };
|
|
5
|
+
var nodePathOps = {
|
|
6
|
+
separator: path.sep,
|
|
7
|
+
resolve: (...paths) => path.resolve(...paths),
|
|
8
|
+
normalize: (filePath) => path.normalize(filePath),
|
|
9
|
+
join: (...paths) => path.join(...paths),
|
|
10
|
+
relative: (from, to) => path.relative(from, to),
|
|
11
|
+
isAbsolute: (filePath) => path.isAbsolute(filePath),
|
|
12
|
+
dirname: (filePath) => path.dirname(filePath),
|
|
13
|
+
basename: (filePath) => path.basename(filePath)
|
|
14
|
+
};
|
|
5
15
|
|
|
6
16
|
class FileSystem {
|
|
7
17
|
mountBase;
|
|
8
18
|
rules;
|
|
19
|
+
pathOps;
|
|
9
20
|
underlyingFs;
|
|
10
21
|
constructor(mountPath, permissions, fs) {
|
|
11
|
-
|
|
22
|
+
const underlyingFs = fs ?? defaultFS;
|
|
23
|
+
this.pathOps = underlyingFs.pathOps ?? nodePathOps;
|
|
24
|
+
this.mountBase = mountPath ? this.pathOps.resolve(mountPath) : null;
|
|
12
25
|
this.rules = this.compileRules(permissions ?? {});
|
|
13
|
-
this.underlyingFs =
|
|
26
|
+
this.underlyingFs = underlyingFs;
|
|
14
27
|
}
|
|
15
28
|
compileRules(permissions) {
|
|
16
29
|
return Object.entries(permissions).map(([pattern, permission]) => ({
|
|
@@ -60,7 +73,7 @@ class FileSystem {
|
|
|
60
73
|
}
|
|
61
74
|
resolveSafePath(virtualPath) {
|
|
62
75
|
if (this.mountBase === null) {
|
|
63
|
-
return
|
|
76
|
+
return this.pathOps.resolve(virtualPath);
|
|
64
77
|
}
|
|
65
78
|
const segments = virtualPath.split("/").filter(Boolean);
|
|
66
79
|
let depth = 0;
|
|
@@ -74,12 +87,12 @@ class FileSystem {
|
|
|
74
87
|
depth++;
|
|
75
88
|
}
|
|
76
89
|
}
|
|
77
|
-
const normalized =
|
|
90
|
+
const normalized = this.pathOps.normalize(virtualPath);
|
|
78
91
|
const relativePath = normalized.startsWith("/") ? normalized.slice(1) : normalized;
|
|
79
|
-
const realPath =
|
|
80
|
-
const resolved =
|
|
81
|
-
const relativeFromMount =
|
|
82
|
-
const escapesMount = relativeFromMount === ".." || relativeFromMount.startsWith(`..${
|
|
92
|
+
const realPath = this.pathOps.join(this.mountBase, relativePath);
|
|
93
|
+
const resolved = this.pathOps.resolve(realPath);
|
|
94
|
+
const relativeFromMount = this.pathOps.relative(this.mountBase, resolved);
|
|
95
|
+
const escapesMount = relativeFromMount === ".." || relativeFromMount.startsWith(`..${this.pathOps.separator}`) || this.pathOps.isAbsolute(relativeFromMount);
|
|
83
96
|
if (escapesMount) {
|
|
84
97
|
throw new Error(`Path traversal blocked: "${virtualPath}" escapes mount point`);
|
|
85
98
|
}
|
|
@@ -140,13 +153,13 @@ class FileSystem {
|
|
|
140
153
|
await this.underlyingFs.promises.rm(realPath, opts);
|
|
141
154
|
}
|
|
142
155
|
resolve(...paths) {
|
|
143
|
-
return
|
|
156
|
+
return this.pathOps.resolve("/", ...paths);
|
|
144
157
|
}
|
|
145
158
|
dirname(filePath) {
|
|
146
|
-
return
|
|
159
|
+
return this.pathOps.dirname(filePath);
|
|
147
160
|
}
|
|
148
161
|
basename(filePath) {
|
|
149
|
-
return
|
|
162
|
+
return this.pathOps.basename(filePath);
|
|
150
163
|
}
|
|
151
164
|
async glob(pattern, opts) {
|
|
152
165
|
const cwd = opts?.cwd ?? "/";
|
|
@@ -196,7 +209,7 @@ class FileSystem {
|
|
|
196
209
|
const realPath = this.resolveSafePath(currentPath);
|
|
197
210
|
const entries = await this.underlyingFs.promises.readdir(realPath);
|
|
198
211
|
for (const entry of entries) {
|
|
199
|
-
const entryPath =
|
|
212
|
+
const entryPath = this.pathOps.join(currentPath, String(entry));
|
|
200
213
|
try {
|
|
201
214
|
const entryRealPath = this.resolveSafePath(entryPath);
|
|
202
215
|
const stat = await this.underlyingFs.promises.stat(entryRealPath);
|
|
@@ -217,7 +230,7 @@ class FileSystem {
|
|
|
217
230
|
for (const entry of entries) {
|
|
218
231
|
const entryName = String(entry);
|
|
219
232
|
if (regex.test(entryName)) {
|
|
220
|
-
const entryPath =
|
|
233
|
+
const entryPath = this.pathOps.join(currentPath, entryName);
|
|
221
234
|
if (rest.length === 0) {
|
|
222
235
|
results.push(entryPath);
|
|
223
236
|
} else {
|
|
@@ -268,4 +281,4 @@ export {
|
|
|
268
281
|
FileSystem
|
|
269
282
|
};
|
|
270
283
|
|
|
271
|
-
//# debugId=
|
|
284
|
+
//# debugId=FEA6FDDF849C4AB964756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/fs/real-fs.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import * as path from \"path\";\nimport * as nodeFs from \"node:fs/promises\";\nimport type { VirtualFS, FileStat } from \"../types.mjs\";\n\nexport type Permission = \"read-write\" | \"read-only\" | \"excluded\";\nexport type PermissionRules = Record<string, Permission>;\n\n// Minimal interface for the underlying fs (compatible with node:fs and memfs)\nexport interface UnderlyingFS {\n promises: {\n readFile(path: string): Promise<Buffer | Uint8Array | string>;\n readdir(path: string): Promise<string[]>;\n stat(path: string): Promise<{\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n }>;\n writeFile(path: string, data: Buffer | string): Promise<void>;\n appendFile(path: string, data: Buffer | string): Promise<void>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<string | undefined | void>;\n rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void>;\n };\n}\n\n// Default: use real node:fs\nconst defaultFS: UnderlyingFS = { promises: nodeFs };\n\ninterface CompiledRule {\n pattern: string;\n permission: Permission;\n specificity: number;\n}\n\nexport class FileSystem implements VirtualFS {\n private readonly mountBase: string | null;\n private readonly rules: CompiledRule[];\n protected readonly underlyingFs: UnderlyingFS;\n\n constructor(mountPath?: string, permissions?: PermissionRules, fs?: UnderlyingFS) {\n this.mountBase = mountPath ? path.resolve(mountPath) : null;\n this.rules = this.compileRules(permissions ?? {});\n this.underlyingFs = fs ?? defaultFS;\n }\n\n private compileRules(permissions: PermissionRules): CompiledRule[] {\n return Object.entries(permissions)\n .map(([pattern, permission]) => ({\n pattern,\n permission,\n specificity: this.calculateSpecificity(pattern),\n }))\n .sort((a, b) => b.specificity - a.specificity); // highest first\n }\n\n private calculateSpecificity(pattern: string): number {\n const segments = pattern.split(\"/\").filter(Boolean);\n let score = segments.length * 1000; // segment count is primary\n\n for (const seg of segments) {\n if (seg === \"**\") score += 0;\n else if (seg.includes(\"*\")) score += 1;\n else score += 10; // literal segment\n }\n return score;\n }\n\n public getPermission(virtualPath: string): Permission {\n const normalized = virtualPath.replace(/^\\/+/, \"\"); // strip leading slashes\n\n for (const rule of this.rules) {\n if (this.matchGlob(rule.pattern, normalized)) {\n return rule.permission;\n }\n }\n return \"read-write\"; // default\n }\n\n private matchGlob(pattern: string, filePath: string): boolean {\n // Convert glob to regex\n // ** matches any path segments, * matches within segment\n const regex = pattern\n .split(\"/\")\n .map((seg) => {\n if (seg === \"**\") return \".*\";\n return seg.replace(/\\*/g, \"[^/]*\").replace(/\\?/g, \"[^/]\");\n })\n .join(\"/\");\n return new RegExp(`^${regex}$`).test(filePath);\n }\n\n public checkPermission(virtualPath: string, operation: \"read\" | \"write\"): void {\n const perm = this.getPermission(virtualPath);\n\n if (perm === \"excluded\") {\n throw new Error(`Access denied: \"${virtualPath}\" is excluded`);\n }\n if (operation === \"write\" && perm === \"read-only\") {\n throw new Error(`Access denied: \"${virtualPath}\" is read-only`);\n }\n }\n\n private resolveSafePath(virtualPath: string): string {\n if (this.mountBase === null) {\n return path.resolve(virtualPath);\n }\n\n // Check for path traversal by tracking depth\n const segments = virtualPath.split(\"/\").filter(Boolean);\n let depth = 0;\n for (const seg of segments) {\n if (seg === \"..\") {\n depth--;\n if (depth < 0) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n } else if (seg !== \".\") {\n depth++;\n }\n }\n\n const normalized = path.normalize(virtualPath);\n const relativePath = normalized.startsWith(\"/\") ? normalized.slice(1) : normalized;\n const realPath = path.join(this.mountBase, relativePath);\n const resolved = path.resolve(realPath);\n\n // Double-check containment (defense in depth), including root mounts.\n const relativeFromMount = path.relative(this.mountBase, resolved);\n const escapesMount =\n relativeFromMount === \"..\" ||\n relativeFromMount.startsWith(`..${path.sep}`) ||\n path.isAbsolute(relativeFromMount);\n if (escapesMount) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n\n return resolved;\n }\n\n // Read operations\n async readFile(filePath: string): Promise<Buffer>;\n async readFile(filePath: string, encoding: BufferEncoding): Promise<string>;\n async readFile(filePath: string, encoding?: BufferEncoding): Promise<Buffer | string> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const content = await this.underlyingFs.promises.readFile(realPath);\n const buf = Buffer.from(content);\n return encoding ? buf.toString(encoding) : buf;\n }\n\n async readdir(dirPath: string): Promise<string[]> {\n this.checkPermission(dirPath, \"read\");\n const realPath = this.resolveSafePath(dirPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n return entries.map(String);\n }\n\n async stat(filePath: string): Promise<FileStat> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const stats = await this.underlyingFs.promises.stat(realPath);\n return {\n isFile: () => stats.isFile(),\n isDirectory: () => stats.isDirectory(),\n size: stats.size,\n mtime: stats.mtime,\n };\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.stat(realPath);\n return true;\n } catch {\n return false;\n }\n }\n\n // Write operations\n async writeFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.writeFile(realPath, data);\n }\n\n async appendFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.appendFile(realPath, data);\n }\n\n async mkdir(dirPath: string, opts?: { recursive?: boolean }): Promise<void> {\n this.checkPermission(dirPath, \"write\");\n const realPath = this.resolveSafePath(dirPath);\n await this.underlyingFs.promises.mkdir(realPath, opts);\n }\n\n async rm(filePath: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.rm(realPath, opts);\n }\n\n // Path utilities (no permission check needed)\n resolve(...paths: string[]): string {\n return path.resolve(\"/\", ...paths);\n }\n\n dirname(filePath: string): string {\n return path.dirname(filePath);\n }\n\n basename(filePath: string): string {\n return path.basename(filePath);\n }\n\n // Glob with permission filtering\n async glob(pattern: string, opts?: { cwd?: string }): Promise<string[]> {\n const cwd = opts?.cwd ?? \"/\";\n this.checkPermission(cwd, \"read\");\n\n const matches = await this.expandGlob(pattern, cwd);\n\n // Filter out excluded paths\n return matches.filter((p) => this.getPermission(p) !== \"excluded\").sort();\n }\n\n // Glob expansion (similar to memfs-adapter implementation)\n private async expandGlob(pattern: string, cwd: string): Promise<string[]> {\n // Handle brace expansion first\n const patterns = this.expandBraces(pattern);\n const allMatches: string[] = [];\n\n for (const pat of patterns) {\n const matches = await this.matchPattern(pat, cwd);\n allMatches.push(...matches);\n }\n\n // Remove duplicates and sort\n return [...new Set(allMatches)].sort();\n }\n\n private expandBraces(pattern: string): string[] {\n const braceMatch = pattern.match(/\\{([^{}]+)\\}/);\n if (!braceMatch) return [pattern];\n\n const before = pattern.slice(0, braceMatch.index);\n const after = pattern.slice(braceMatch.index! + braceMatch[0].length);\n const options = braceMatch[1]!.split(\",\");\n\n const results: string[] = [];\n for (const opt of options) {\n const expanded = this.expandBraces(before + opt + after);\n results.push(...expanded);\n }\n return results;\n }\n\n private async matchPattern(pattern: string, cwd: string): Promise<string[]> {\n const parts = pattern.split(\"/\").filter((p) => p !== \"\");\n const isAbsolute = pattern.startsWith(\"/\");\n const startDir = isAbsolute ? \"/\" : cwd;\n\n return this.matchParts(parts, startDir);\n }\n\n private async matchParts(parts: string[], currentPath: string): Promise<string[]> {\n if (parts.length === 0) {\n return [currentPath];\n }\n\n const [part, ...rest] = parts;\n\n // Handle ** (recursive glob)\n if (part === \"**\") {\n const results: string[] = [];\n\n // Match current directory\n const withoutStar = await this.matchParts(rest, currentPath);\n results.push(...withoutStar);\n\n // Recurse into subdirectories\n try {\n const realPath = this.resolveSafePath(currentPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n for (const entry of entries) {\n const entryPath = path.posix.join(currentPath, String(entry));\n try {\n const entryRealPath = this.resolveSafePath(entryPath);\n const stat = await this.underlyingFs.promises.stat(entryRealPath);\n if (stat.isDirectory()) {\n const subMatches = await this.matchParts(parts, entryPath);\n results.push(...subMatches);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n } catch {\n // Directory not readable\n }\n\n return results;\n }\n\n // Handle regular glob patterns\n const regex = this.globToRegex(part!);\n\n try {\n const realPath = this.resolveSafePath(currentPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n const results: string[] = [];\n\n for (const entry of entries) {\n const entryName = String(entry);\n if (regex.test(entryName)) {\n const entryPath = path.posix.join(currentPath, entryName);\n if (rest.length === 0) {\n results.push(entryPath);\n } else {\n try {\n const entryRealPath = this.resolveSafePath(entryPath);\n const stat = await this.underlyingFs.promises.stat(entryRealPath);\n if (stat.isDirectory()) {\n const subMatches = await this.matchParts(rest, entryPath);\n results.push(...subMatches);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n }\n }\n\n return results;\n } catch {\n return [];\n }\n }\n\n private globToRegex(pattern: string): RegExp {\n let regex = \"^\";\n for (let i = 0; i < pattern.length; i++) {\n const char = pattern[i]!;\n if (char === \"*\") {\n regex += \"[^/]*\";\n } else if (char === \"?\") {\n regex += \"[^/]\";\n } else if (char === \"[\") {\n // Character class\n let j = i + 1;\n let classContent = \"\";\n while (j < pattern.length && pattern[j] !== \"]\") {\n classContent += pattern[j];\n j++;\n }\n regex += `[${classContent}]`;\n i = j;\n } else if (\".+^${}()|\\\\\".includes(char)) {\n regex += \"\\\\\" + char;\n } else {\n regex += char;\n }\n }\n regex += \"$\";\n return new RegExp(regex);\n }\n}\n"
|
|
5
|
+
"import * as path from \"path\";\nimport * as nodeFs from \"node:fs/promises\";\nimport type { VirtualFS, FileStat } from \"../types.mjs\";\n\nexport type Permission = \"read-write\" | \"read-only\" | \"excluded\";\nexport type PermissionRules = Record<string, Permission>;\nexport interface PathOps {\n readonly separator: string;\n resolve(...paths: string[]): string;\n normalize(path: string): string;\n join(...paths: string[]): string;\n relative(from: string, to: string): string;\n isAbsolute(path: string): boolean;\n dirname(path: string): string;\n basename(path: string): string;\n}\n\n// Minimal interface for the underlying fs (compatible with node:fs and memfs)\nexport interface UnderlyingFS {\n pathOps?: PathOps;\n promises: {\n readFile(path: string): Promise<Buffer | Uint8Array | string>;\n readdir(path: string): Promise<string[]>;\n stat(path: string): Promise<{\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n }>;\n writeFile(path: string, data: Buffer | string): Promise<void>;\n appendFile(path: string, data: Buffer | string): Promise<void>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<string | undefined | void>;\n rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void>;\n };\n}\n\n// Default: use real node:fs\nconst defaultFS: UnderlyingFS = { promises: nodeFs };\nconst nodePathOps: PathOps = {\n separator: path.sep,\n resolve: (...paths) => path.resolve(...paths),\n normalize: (filePath) => path.normalize(filePath),\n join: (...paths) => path.join(...paths),\n relative: (from, to) => path.relative(from, to),\n isAbsolute: (filePath) => path.isAbsolute(filePath),\n dirname: (filePath) => path.dirname(filePath),\n basename: (filePath) => path.basename(filePath),\n};\n\ninterface CompiledRule {\n pattern: string;\n permission: Permission;\n specificity: number;\n}\n\nexport class FileSystem implements VirtualFS {\n private readonly mountBase: string | null;\n private readonly rules: CompiledRule[];\n private readonly pathOps: PathOps;\n protected readonly underlyingFs: UnderlyingFS;\n\n constructor(mountPath?: string, permissions?: PermissionRules, fs?: UnderlyingFS) {\n const underlyingFs = fs ?? defaultFS;\n this.pathOps = underlyingFs.pathOps ?? nodePathOps;\n this.mountBase = mountPath ? this.pathOps.resolve(mountPath) : null;\n this.rules = this.compileRules(permissions ?? {});\n this.underlyingFs = underlyingFs;\n }\n\n private compileRules(permissions: PermissionRules): CompiledRule[] {\n return Object.entries(permissions)\n .map(([pattern, permission]) => ({\n pattern,\n permission,\n specificity: this.calculateSpecificity(pattern),\n }))\n .sort((a, b) => b.specificity - a.specificity); // highest first\n }\n\n private calculateSpecificity(pattern: string): number {\n const segments = pattern.split(\"/\").filter(Boolean);\n let score = segments.length * 1000; // segment count is primary\n\n for (const seg of segments) {\n if (seg === \"**\") score += 0;\n else if (seg.includes(\"*\")) score += 1;\n else score += 10; // literal segment\n }\n return score;\n }\n\n public getPermission(virtualPath: string): Permission {\n const normalized = virtualPath.replace(/^\\/+/, \"\"); // strip leading slashes\n\n for (const rule of this.rules) {\n if (this.matchGlob(rule.pattern, normalized)) {\n return rule.permission;\n }\n }\n return \"read-write\"; // default\n }\n\n private matchGlob(pattern: string, filePath: string): boolean {\n // Convert glob to regex\n // ** matches any path segments, * matches within segment\n const regex = pattern\n .split(\"/\")\n .map((seg) => {\n if (seg === \"**\") return \".*\";\n return seg.replace(/\\*/g, \"[^/]*\").replace(/\\?/g, \"[^/]\");\n })\n .join(\"/\");\n return new RegExp(`^${regex}$`).test(filePath);\n }\n\n public checkPermission(virtualPath: string, operation: \"read\" | \"write\"): void {\n const perm = this.getPermission(virtualPath);\n\n if (perm === \"excluded\") {\n throw new Error(`Access denied: \"${virtualPath}\" is excluded`);\n }\n if (operation === \"write\" && perm === \"read-only\") {\n throw new Error(`Access denied: \"${virtualPath}\" is read-only`);\n }\n }\n\n private resolveSafePath(virtualPath: string): string {\n if (this.mountBase === null) {\n return this.pathOps.resolve(virtualPath);\n }\n\n // Check for path traversal by tracking depth\n const segments = virtualPath.split(\"/\").filter(Boolean);\n let depth = 0;\n for (const seg of segments) {\n if (seg === \"..\") {\n depth--;\n if (depth < 0) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n } else if (seg !== \".\") {\n depth++;\n }\n }\n\n const normalized = this.pathOps.normalize(virtualPath);\n const relativePath = normalized.startsWith(\"/\") ? normalized.slice(1) : normalized;\n const realPath = this.pathOps.join(this.mountBase, relativePath);\n const resolved = this.pathOps.resolve(realPath);\n\n // Double-check containment (defense in depth), including root mounts.\n const relativeFromMount = this.pathOps.relative(this.mountBase, resolved);\n const escapesMount =\n relativeFromMount === \"..\" ||\n relativeFromMount.startsWith(`..${this.pathOps.separator}`) ||\n this.pathOps.isAbsolute(relativeFromMount);\n if (escapesMount) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n\n return resolved;\n }\n\n // Read operations\n async readFile(filePath: string): Promise<Buffer>;\n async readFile(filePath: string, encoding: BufferEncoding): Promise<string>;\n async readFile(filePath: string, encoding?: BufferEncoding): Promise<Buffer | string> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const content = await this.underlyingFs.promises.readFile(realPath);\n const buf = Buffer.from(content);\n return encoding ? buf.toString(encoding) : buf;\n }\n\n async readdir(dirPath: string): Promise<string[]> {\n this.checkPermission(dirPath, \"read\");\n const realPath = this.resolveSafePath(dirPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n return entries.map(String);\n }\n\n async stat(filePath: string): Promise<FileStat> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const stats = await this.underlyingFs.promises.stat(realPath);\n return {\n isFile: () => stats.isFile(),\n isDirectory: () => stats.isDirectory(),\n size: stats.size,\n mtime: stats.mtime,\n };\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.stat(realPath);\n return true;\n } catch {\n return false;\n }\n }\n\n // Write operations\n async writeFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.writeFile(realPath, data);\n }\n\n async appendFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.appendFile(realPath, data);\n }\n\n async mkdir(dirPath: string, opts?: { recursive?: boolean }): Promise<void> {\n this.checkPermission(dirPath, \"write\");\n const realPath = this.resolveSafePath(dirPath);\n await this.underlyingFs.promises.mkdir(realPath, opts);\n }\n\n async rm(filePath: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.rm(realPath, opts);\n }\n\n // Path utilities (no permission check needed)\n resolve(...paths: string[]): string {\n return this.pathOps.resolve(\"/\", ...paths);\n }\n\n dirname(filePath: string): string {\n return this.pathOps.dirname(filePath);\n }\n\n basename(filePath: string): string {\n return this.pathOps.basename(filePath);\n }\n\n // Glob with permission filtering\n async glob(pattern: string, opts?: { cwd?: string }): Promise<string[]> {\n const cwd = opts?.cwd ?? \"/\";\n this.checkPermission(cwd, \"read\");\n\n const matches = await this.expandGlob(pattern, cwd);\n\n // Filter out excluded paths\n return matches.filter((p) => this.getPermission(p) !== \"excluded\").sort();\n }\n\n // Glob expansion (similar to memfs-adapter implementation)\n private async expandGlob(pattern: string, cwd: string): Promise<string[]> {\n // Handle brace expansion first\n const patterns = this.expandBraces(pattern);\n const allMatches: string[] = [];\n\n for (const pat of patterns) {\n const matches = await this.matchPattern(pat, cwd);\n allMatches.push(...matches);\n }\n\n // Remove duplicates and sort\n return [...new Set(allMatches)].sort();\n }\n\n private expandBraces(pattern: string): string[] {\n const braceMatch = pattern.match(/\\{([^{}]+)\\}/);\n if (!braceMatch) return [pattern];\n\n const before = pattern.slice(0, braceMatch.index);\n const after = pattern.slice(braceMatch.index! + braceMatch[0].length);\n const options = braceMatch[1]!.split(\",\");\n\n const results: string[] = [];\n for (const opt of options) {\n const expanded = this.expandBraces(before + opt + after);\n results.push(...expanded);\n }\n return results;\n }\n\n private async matchPattern(pattern: string, cwd: string): Promise<string[]> {\n const parts = pattern.split(\"/\").filter((p) => p !== \"\");\n const isAbsolute = pattern.startsWith(\"/\");\n const startDir = isAbsolute ? \"/\" : cwd;\n\n return this.matchParts(parts, startDir);\n }\n\n private async matchParts(parts: string[], currentPath: string): Promise<string[]> {\n if (parts.length === 0) {\n return [currentPath];\n }\n\n const [part, ...rest] = parts;\n\n // Handle ** (recursive glob)\n if (part === \"**\") {\n const results: string[] = [];\n\n // Match current directory\n const withoutStar = await this.matchParts(rest, currentPath);\n results.push(...withoutStar);\n\n // Recurse into subdirectories\n try {\n const realPath = this.resolveSafePath(currentPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n for (const entry of entries) {\n const entryPath = this.pathOps.join(currentPath, String(entry));\n try {\n const entryRealPath = this.resolveSafePath(entryPath);\n const stat = await this.underlyingFs.promises.stat(entryRealPath);\n if (stat.isDirectory()) {\n const subMatches = await this.matchParts(parts, entryPath);\n results.push(...subMatches);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n } catch {\n // Directory not readable\n }\n\n return results;\n }\n\n // Handle regular glob patterns\n const regex = this.globToRegex(part!);\n\n try {\n const realPath = this.resolveSafePath(currentPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n const results: string[] = [];\n\n for (const entry of entries) {\n const entryName = String(entry);\n if (regex.test(entryName)) {\n const entryPath = this.pathOps.join(currentPath, entryName);\n if (rest.length === 0) {\n results.push(entryPath);\n } else {\n try {\n const entryRealPath = this.resolveSafePath(entryPath);\n const stat = await this.underlyingFs.promises.stat(entryRealPath);\n if (stat.isDirectory()) {\n const subMatches = await this.matchParts(rest, entryPath);\n results.push(...subMatches);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n }\n }\n\n return results;\n } catch {\n return [];\n }\n }\n\n private globToRegex(pattern: string): RegExp {\n let regex = \"^\";\n for (let i = 0; i < pattern.length; i++) {\n const char = pattern[i]!;\n if (char === \"*\") {\n regex += \"[^/]*\";\n } else if (char === \"?\") {\n regex += \"[^/]\";\n } else if (char === \"[\") {\n // Character class\n let j = i + 1;\n let classContent = \"\";\n while (j < pattern.length && pattern[j] !== \"]\") {\n classContent += pattern[j];\n j++;\n }\n regex += `[${classContent}]`;\n i = j;\n } else if (\".+^${}()|\\\\\".includes(char)) {\n regex += \"\\\\\" + char;\n } else {\n regex += char;\n }\n }\n regex += \"$\";\n return new RegExp(regex);\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";AAAA;AACA;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "FEA6FDDF849C4AB964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,8 +1,56 @@
|
|
|
1
|
-
// src/fs/
|
|
1
|
+
// src/fs/web-fs.ts
|
|
2
2
|
import { FileSystem } from "./real-fs.mjs";
|
|
3
3
|
var DIRECTORY_MTIME = new Date(0);
|
|
4
|
-
|
|
4
|
+
var WEB_PATH_OPS = {
|
|
5
|
+
separator: "/",
|
|
6
|
+
resolve(...paths) {
|
|
7
|
+
return normalizeWebPath(paths.join("/"));
|
|
8
|
+
},
|
|
9
|
+
normalize: normalizeWebPath,
|
|
10
|
+
join(...paths) {
|
|
11
|
+
const nonEmpty = paths.filter((segment) => segment.length > 0);
|
|
12
|
+
if (nonEmpty.length === 0) {
|
|
13
|
+
return ".";
|
|
14
|
+
}
|
|
15
|
+
return normalizeWebPath(nonEmpty.join("/"));
|
|
16
|
+
},
|
|
17
|
+
relative(from, to) {
|
|
18
|
+
const fromSegments = getPathSegments(from);
|
|
19
|
+
const toSegments = getPathSegments(to);
|
|
20
|
+
let shared = 0;
|
|
21
|
+
while (shared < fromSegments.length && shared < toSegments.length && fromSegments[shared] === toSegments[shared]) {
|
|
22
|
+
shared++;
|
|
23
|
+
}
|
|
24
|
+
const up = new Array(fromSegments.length - shared).fill("..");
|
|
25
|
+
const down = toSegments.slice(shared);
|
|
26
|
+
return [...up, ...down].join("/");
|
|
27
|
+
},
|
|
28
|
+
isAbsolute(path) {
|
|
29
|
+
return path.startsWith("/");
|
|
30
|
+
},
|
|
31
|
+
dirname(path) {
|
|
32
|
+
const normalized = normalizeWebPath(path);
|
|
33
|
+
if (normalized === "/") {
|
|
34
|
+
return "/";
|
|
35
|
+
}
|
|
36
|
+
const segments = getPathSegments(normalized);
|
|
37
|
+
if (segments.length <= 1) {
|
|
38
|
+
return "/";
|
|
39
|
+
}
|
|
40
|
+
return `/${segments.slice(0, -1).join("/")}`;
|
|
41
|
+
},
|
|
42
|
+
basename(path) {
|
|
43
|
+
const normalized = normalizeWebPath(path);
|
|
44
|
+
if (normalized === "/") {
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
const segments = getPathSegments(normalized);
|
|
48
|
+
return segments[segments.length - 1] ?? "";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
function createWebUnderlyingFS(root) {
|
|
5
52
|
return {
|
|
53
|
+
pathOps: WEB_PATH_OPS,
|
|
6
54
|
promises: {
|
|
7
55
|
async readFile(path) {
|
|
8
56
|
const { parentSegments, name } = splitParent(path);
|
|
@@ -82,7 +130,7 @@ function createOPFSUnderlyingFS(root) {
|
|
|
82
130
|
const name = segments[segments.length - 1];
|
|
83
131
|
const exists = await entryExists(parent, name);
|
|
84
132
|
if (exists) {
|
|
85
|
-
throw new Error(`EEXIST: file already exists, mkdir '${
|
|
133
|
+
throw new Error(`EEXIST: file already exists, mkdir '${normalizeWebPath(path)}'`);
|
|
86
134
|
}
|
|
87
135
|
await parent.getDirectoryHandle(name, { create: true });
|
|
88
136
|
},
|
|
@@ -106,9 +154,9 @@ function createOPFSUnderlyingFS(root) {
|
|
|
106
154
|
};
|
|
107
155
|
}
|
|
108
156
|
|
|
109
|
-
class
|
|
157
|
+
class WebFileSystem extends FileSystem {
|
|
110
158
|
constructor(root, permissions) {
|
|
111
|
-
super("/", permissions,
|
|
159
|
+
super("/", permissions, createWebUnderlyingFS(root));
|
|
112
160
|
}
|
|
113
161
|
}
|
|
114
162
|
function createDirectoryStat() {
|
|
@@ -119,7 +167,7 @@ function createDirectoryStat() {
|
|
|
119
167
|
mtime: DIRECTORY_MTIME
|
|
120
168
|
};
|
|
121
169
|
}
|
|
122
|
-
function
|
|
170
|
+
function normalizeWebPath(path) {
|
|
123
171
|
const normalized = path.replace(/\\/g, "/");
|
|
124
172
|
const rawSegments = (normalized.startsWith("/") ? normalized : `/${normalized}`).split("/").filter(Boolean);
|
|
125
173
|
const segments = [];
|
|
@@ -135,7 +183,7 @@ function normalizeOpfsPath(path) {
|
|
|
135
183
|
return `/${segments.join("/")}`;
|
|
136
184
|
}
|
|
137
185
|
function getPathSegments(path) {
|
|
138
|
-
const normalized =
|
|
186
|
+
const normalized = normalizeWebPath(path);
|
|
139
187
|
return normalized.split("/").filter(Boolean);
|
|
140
188
|
}
|
|
141
189
|
function splitParent(path) {
|
|
@@ -200,8 +248,8 @@ function toWritableData(data) {
|
|
|
200
248
|
return out.buffer;
|
|
201
249
|
}
|
|
202
250
|
export {
|
|
203
|
-
|
|
204
|
-
|
|
251
|
+
createWebUnderlyingFS,
|
|
252
|
+
WebFileSystem
|
|
205
253
|
};
|
|
206
254
|
|
|
207
|
-
//# debugId=
|
|
255
|
+
//# debugId=A9C7F77B6DBC9D6064756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/fs/web-fs.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { FileSystem, type PathOps, type PermissionRules, type UnderlyingFS } from \"./real-fs.mjs\";\n\nconst DIRECTORY_MTIME = new Date(0);\nconst WEB_PATH_OPS: PathOps = {\n separator: \"/\",\n resolve(...paths: string[]): string {\n return normalizeWebPath(paths.join(\"/\"));\n },\n normalize: normalizeWebPath,\n join(...paths: string[]): string {\n const nonEmpty = paths.filter((segment) => segment.length > 0);\n if (nonEmpty.length === 0) {\n return \".\";\n }\n return normalizeWebPath(nonEmpty.join(\"/\"));\n },\n relative(from: string, to: string): string {\n const fromSegments = getPathSegments(from);\n const toSegments = getPathSegments(to);\n let shared = 0;\n\n while (\n shared < fromSegments.length &&\n shared < toSegments.length &&\n fromSegments[shared] === toSegments[shared]\n ) {\n shared++;\n }\n\n const up = new Array(fromSegments.length - shared).fill(\"..\");\n const down = toSegments.slice(shared);\n return [...up, ...down].join(\"/\");\n },\n isAbsolute(path: string): boolean {\n return path.startsWith(\"/\");\n },\n dirname(path: string): string {\n const normalized = normalizeWebPath(path);\n if (normalized === \"/\") {\n return \"/\";\n }\n\n const segments = getPathSegments(normalized);\n if (segments.length <= 1) {\n return \"/\";\n }\n\n return `/${segments.slice(0, -1).join(\"/\")}`;\n },\n basename(path: string): string {\n const normalized = normalizeWebPath(path);\n if (normalized === \"/\") {\n return \"\";\n }\n\n const segments = getPathSegments(normalized);\n return segments[segments.length - 1] ?? \"\";\n },\n};\n\nexport function createWebUnderlyingFS(root: FileSystemDirectoryHandle): UnderlyingFS {\n return {\n pathOps: WEB_PATH_OPS,\n promises: {\n async readFile(path: string): Promise<Buffer> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: false });\n const file = await fileHandle.getFile();\n return Buffer.from(await file.arrayBuffer());\n },\n\n async readdir(path: string): Promise<string[]> {\n const dir = await walkDirectory(root, getPathSegments(path), false);\n const entries: string[] = [];\n for await (const [name] of dir.entries()) {\n entries.push(name);\n }\n return entries;\n },\n\n async stat(path: string): Promise<{\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n }> {\n const segments = getPathSegments(path);\n\n if (segments.length === 0) {\n return createDirectoryStat();\n }\n\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n\n try {\n const fileHandle = await parent.getFileHandle(name, { create: false });\n const file = await fileHandle.getFile();\n return {\n isFile: () => true,\n isDirectory: () => false,\n size: file.size,\n mtime: new Date(file.lastModified ?? 0),\n };\n } catch (error) {\n if (!isNotFoundOrTypeMismatch(error)) throw error;\n }\n\n try {\n await parent.getDirectoryHandle(name, { create: false });\n return createDirectoryStat();\n } catch (error) {\n if (!isNotFoundOrTypeMismatch(error)) throw error;\n throw error;\n }\n },\n\n async writeFile(path: string, data: Buffer | string): Promise<void> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: true });\n const writable = await fileHandle.createWritable();\n await writable.write(toWritableData(data));\n await writable.close();\n },\n\n async appendFile(path: string, data: Buffer | string): Promise<void> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: true });\n const file = await fileHandle.getFile();\n const writable = await fileHandle.createWritable({ keepExistingData: true });\n await writable.write({\n type: \"write\",\n position: file.size,\n data: toWritableData(data),\n });\n await writable.close();\n },\n\n async mkdir(path: string, opts?: { recursive?: boolean }): Promise<void> {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n return;\n }\n\n if (opts?.recursive) {\n await walkDirectory(root, segments, true);\n return;\n }\n\n const parent = await walkDirectory(root, segments.slice(0, -1), false);\n const name = segments[segments.length - 1]!;\n const exists = await entryExists(parent, name);\n if (exists) {\n throw new Error(`EEXIST: file already exists, mkdir '${normalizeWebPath(path)}'`);\n }\n await parent.getDirectoryHandle(name, { create: true });\n },\n\n async rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n throw new Error(\"EPERM: operation not permitted, rm '/'\");\n }\n\n const parent = await walkDirectory(root, segments.slice(0, -1), false);\n const name = segments[segments.length - 1]!;\n try {\n await parent.removeEntry(name, { recursive: opts?.recursive });\n } catch (error) {\n if (opts?.force && isNotFoundError(error)) {\n return;\n }\n throw error;\n }\n },\n },\n };\n}\n\nexport class WebFileSystem extends FileSystem {\n constructor(root: FileSystemDirectoryHandle, permissions?: PermissionRules) {\n super(\"/\", permissions, createWebUnderlyingFS(root));\n }\n}\n\nfunction createDirectoryStat() {\n return {\n isFile: () => false,\n isDirectory: () => true,\n size: 0,\n mtime: DIRECTORY_MTIME,\n };\n}\n\nfunction normalizeWebPath(path: string): string {\n const normalized = path.replace(/\\\\/g, \"/\");\n const rawSegments = (normalized.startsWith(\"/\") ? normalized : `/${normalized}`)\n .split(\"/\")\n .filter(Boolean);\n\n const segments: string[] = [];\n for (const segment of rawSegments) {\n if (segment === \".\") continue;\n if (segment === \"..\") {\n segments.pop();\n continue;\n }\n segments.push(segment);\n }\n\n return `/${segments.join(\"/\")}`;\n}\n\nfunction getPathSegments(path: string): string[] {\n const normalized = normalizeWebPath(path);\n return normalized.split(\"/\").filter(Boolean);\n}\n\nfunction splitParent(path: string): { parentSegments: string[]; name: string } {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n throw new Error(`Invalid file path: \"${path}\"`);\n }\n return {\n parentSegments: segments.slice(0, -1),\n name: segments[segments.length - 1]!,\n };\n}\n\nasync function walkDirectory(\n root: FileSystemDirectoryHandle,\n segments: string[],\n create: boolean\n): Promise<FileSystemDirectoryHandle> {\n let current = root;\n for (const segment of segments) {\n current = await current.getDirectoryHandle(segment, { create });\n }\n return current;\n}\n\nasync function entryExists(dir: FileSystemDirectoryHandle, name: string): Promise<boolean> {\n try {\n await dir.getFileHandle(name, { create: false });\n return true;\n } catch (error) {\n if (isTypeMismatchError(error)) return true;\n if (!isNotFoundError(error)) throw error;\n }\n\n try {\n await dir.getDirectoryHandle(name, { create: false });\n return true;\n } catch (error) {\n if (isTypeMismatchError(error)) return true;\n if (!isNotFoundError(error)) throw error;\n return false;\n }\n}\n\nfunction isNotFoundOrTypeMismatch(error: unknown): boolean {\n return isNotFoundError(error) || isTypeMismatchError(error);\n}\n\nfunction isNotFoundError(error: unknown): boolean {\n return getErrorName(error) === \"NotFoundError\";\n}\n\nfunction isTypeMismatchError(error: unknown): boolean {\n return getErrorName(error) === \"TypeMismatchError\";\n}\n\nfunction getErrorName(error: unknown): string | undefined {\n if (!error || typeof error !== \"object\") return undefined;\n const named = error as { name?: unknown };\n return typeof named.name === \"string\" ? named.name : undefined;\n}\n\nfunction toWritableData(data: Buffer | string): string | ArrayBuffer {\n if (typeof data === \"string\") {\n return data;\n }\n const out = new Uint8Array(data.length);\n out.set(data);\n return out.buffer;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "A9C7F77B6DBC9D6064756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/dist/mjs/src/index.mjs
CHANGED
|
@@ -28,8 +28,8 @@ import { createVirtualFS } from "./fs/index.mjs";
|
|
|
28
28
|
import {
|
|
29
29
|
FileSystem,
|
|
30
30
|
ReadOnlyFileSystem,
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
WebFileSystem,
|
|
32
|
+
createWebUnderlyingFS
|
|
33
33
|
} from "./fs/index.mjs";
|
|
34
34
|
import { createStdin, StdinImpl } from "./io/index.mjs";
|
|
35
35
|
import { createStdout, createStderr, createPipe, OutputCollectorImpl, PipeBuffer } from "./io/index.mjs";
|
|
@@ -57,13 +57,14 @@ export {
|
|
|
57
57
|
isAndNode,
|
|
58
58
|
escapeForInterpolation,
|
|
59
59
|
escape,
|
|
60
|
+
createWebUnderlyingFS,
|
|
60
61
|
createVirtualFS,
|
|
61
62
|
createStdout,
|
|
62
63
|
createStdin,
|
|
63
64
|
createStderr,
|
|
64
65
|
createShellDSL,
|
|
65
66
|
createPipe,
|
|
66
|
-
|
|
67
|
+
WebFileSystem,
|
|
67
68
|
StdinImpl,
|
|
68
69
|
ShellPromise,
|
|
69
70
|
ShellError,
|
|
@@ -73,7 +74,6 @@ export {
|
|
|
73
74
|
Parser,
|
|
74
75
|
ParseError,
|
|
75
76
|
OutputCollectorImpl,
|
|
76
|
-
OPFSFileSystem,
|
|
77
77
|
Lexer,
|
|
78
78
|
LexError,
|
|
79
79
|
Interpreter,
|
|
@@ -82,4 +82,4 @@ export {
|
|
|
82
82
|
BreakException
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
-
//# debugId=
|
|
85
|
+
//# debugId=4ED32BF992AF089064756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"// Main class exports\nexport { ShellDSL, createShellDSL, type Program } from \"./shell-dsl.mjs\";\nexport { ShellPromise, type ShellPromiseOptions } from \"./shell-promise.mjs\";\n\n// Types\nexport type {\n VirtualFS,\n FileStat,\n Command,\n CommandContext,\n Stdin,\n Stdout,\n Stderr,\n OutputCollector,\n ExecResult,\n ShellConfig,\n RawValue,\n} from \"./types.mjs\";\nexport { isRawValue } from \"./types.mjs\";\n\n// Errors\nexport { ShellError, LexError, ParseError } from \"./errors.mjs\";\n\n// Lexer\nexport { Lexer, lex, tokenToString } from \"./lexer/index.mjs\";\nexport type { Token, RedirectMode } from \"./lexer/index.mjs\";\n\n// Parser\nexport { Parser, parse } from \"./parser/index.mjs\";\nexport type {\n ASTNode,\n Redirect,\n CommandNode,\n PipelineNode,\n AndNode,\n OrNode,\n SequenceNode,\n LiteralNode,\n VariableNode,\n SubstitutionNode,\n GlobNode,\n ConcatNode,\n IfNode,\n ForNode,\n WhileNode,\n UntilNode,\n CaseNode,\n CaseClause,\n ArithmeticNode,\n} from \"./parser/index.mjs\";\nexport {\n isCommandNode,\n isPipelineNode,\n isAndNode,\n isOrNode,\n isSequenceNode,\n isLiteralNode,\n isVariableNode,\n isSubstitutionNode,\n isGlobNode,\n isConcatNode,\n isIfNode,\n isForNode,\n isWhileNode,\n isUntilNode,\n isCaseNode,\n isArithmeticNode,\n} from \"./parser/index.mjs\";\n\n// Interpreter\nexport { Interpreter, type InterpreterOptions, BreakException, ContinueException } from \"./interpreter/index.mjs\";\n\n// Filesystem\nexport { createVirtualFS } from \"./fs/index.mjs\";\nexport {\n FileSystem,\n ReadOnlyFileSystem,\n
|
|
5
|
+
"// Main class exports\nexport { ShellDSL, createShellDSL, type Program } from \"./shell-dsl.mjs\";\nexport { ShellPromise, type ShellPromiseOptions } from \"./shell-promise.mjs\";\n\n// Types\nexport type {\n VirtualFS,\n FileStat,\n Command,\n CommandContext,\n Stdin,\n Stdout,\n Stderr,\n OutputCollector,\n ExecResult,\n ShellConfig,\n RawValue,\n} from \"./types.mjs\";\nexport { isRawValue } from \"./types.mjs\";\n\n// Errors\nexport { ShellError, LexError, ParseError } from \"./errors.mjs\";\n\n// Lexer\nexport { Lexer, lex, tokenToString } from \"./lexer/index.mjs\";\nexport type { Token, RedirectMode } from \"./lexer/index.mjs\";\n\n// Parser\nexport { Parser, parse } from \"./parser/index.mjs\";\nexport type {\n ASTNode,\n Redirect,\n CommandNode,\n PipelineNode,\n AndNode,\n OrNode,\n SequenceNode,\n LiteralNode,\n VariableNode,\n SubstitutionNode,\n GlobNode,\n ConcatNode,\n IfNode,\n ForNode,\n WhileNode,\n UntilNode,\n CaseNode,\n CaseClause,\n ArithmeticNode,\n} from \"./parser/index.mjs\";\nexport {\n isCommandNode,\n isPipelineNode,\n isAndNode,\n isOrNode,\n isSequenceNode,\n isLiteralNode,\n isVariableNode,\n isSubstitutionNode,\n isGlobNode,\n isConcatNode,\n isIfNode,\n isForNode,\n isWhileNode,\n isUntilNode,\n isCaseNode,\n isArithmeticNode,\n} from \"./parser/index.mjs\";\n\n// Interpreter\nexport { Interpreter, type InterpreterOptions, BreakException, ContinueException } from \"./interpreter/index.mjs\";\n\n// Filesystem\nexport { createVirtualFS } from \"./fs/index.mjs\";\nexport {\n FileSystem,\n ReadOnlyFileSystem,\n WebFileSystem,\n createWebUnderlyingFS,\n type PathOps,\n type Permission,\n type PermissionRules,\n type UnderlyingFS,\n} from \"./fs/index.mjs\";\n\n// I/O\nexport { createStdin, StdinImpl } from \"./io/index.mjs\";\nexport { createStdout, createStderr, createPipe, OutputCollectorImpl, PipeBuffer } from \"./io/index.mjs\";\n\n// Utilities\nexport { escape, escapeForInterpolation } from \"./utils/index.mjs\";\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";AACA;AACA;AAgBA;AAGA;AAGA;AAIA;AAsBA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBA;AAGA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";AACA;AACA;AAgBA;AAGA;AAGA;AAIA;AAsBA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBA;AAGA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYA;AACA;AAGA;",
|
|
8
|
+
"debugId": "4ED32BF992AF089064756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { createVirtualFS } from "./memfs-adapter.ts";
|
|
2
|
-
export { FileSystem, type Permission, type PermissionRules, type UnderlyingFS } from "./real-fs.ts";
|
|
2
|
+
export { FileSystem, type PathOps, type Permission, type PermissionRules, type UnderlyingFS } from "./real-fs.ts";
|
|
3
3
|
export { ReadOnlyFileSystem } from "./readonly-fs.ts";
|
|
4
|
-
export {
|
|
4
|
+
export { WebFileSystem, createWebUnderlyingFS } from "./web-fs.ts";
|
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
import type { VirtualFS, FileStat } from "../types.ts";
|
|
2
2
|
export type Permission = "read-write" | "read-only" | "excluded";
|
|
3
3
|
export type PermissionRules = Record<string, Permission>;
|
|
4
|
+
export interface PathOps {
|
|
5
|
+
readonly separator: string;
|
|
6
|
+
resolve(...paths: string[]): string;
|
|
7
|
+
normalize(path: string): string;
|
|
8
|
+
join(...paths: string[]): string;
|
|
9
|
+
relative(from: string, to: string): string;
|
|
10
|
+
isAbsolute(path: string): boolean;
|
|
11
|
+
dirname(path: string): string;
|
|
12
|
+
basename(path: string): string;
|
|
13
|
+
}
|
|
4
14
|
export interface UnderlyingFS {
|
|
15
|
+
pathOps?: PathOps;
|
|
5
16
|
promises: {
|
|
6
17
|
readFile(path: string): Promise<Buffer | Uint8Array | string>;
|
|
7
18
|
readdir(path: string): Promise<string[]>;
|
|
@@ -25,6 +36,7 @@ export interface UnderlyingFS {
|
|
|
25
36
|
export declare class FileSystem implements VirtualFS {
|
|
26
37
|
private readonly mountBase;
|
|
27
38
|
private readonly rules;
|
|
39
|
+
private readonly pathOps;
|
|
28
40
|
protected readonly underlyingFs: UnderlyingFS;
|
|
29
41
|
constructor(mountPath?: string, permissions?: PermissionRules, fs?: UnderlyingFS);
|
|
30
42
|
private compileRules;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FileSystem, type PermissionRules, type UnderlyingFS } from "./real-fs.ts";
|
|
2
|
-
export declare function
|
|
3
|
-
export declare class
|
|
2
|
+
export declare function createWebUnderlyingFS(root: FileSystemDirectoryHandle): UnderlyingFS;
|
|
3
|
+
export declare class WebFileSystem extends FileSystem {
|
|
4
4
|
constructor(root: FileSystemDirectoryHandle, permissions?: PermissionRules);
|
|
5
5
|
}
|
|
@@ -10,7 +10,7 @@ export type { ASTNode, Redirect, CommandNode, PipelineNode, AndNode, OrNode, Seq
|
|
|
10
10
|
export { isCommandNode, isPipelineNode, isAndNode, isOrNode, isSequenceNode, isLiteralNode, isVariableNode, isSubstitutionNode, isGlobNode, isConcatNode, isIfNode, isForNode, isWhileNode, isUntilNode, isCaseNode, isArithmeticNode, } from "./parser/index.ts";
|
|
11
11
|
export { Interpreter, type InterpreterOptions, BreakException, ContinueException } from "./interpreter/index.ts";
|
|
12
12
|
export { createVirtualFS } from "./fs/index.ts";
|
|
13
|
-
export { FileSystem, ReadOnlyFileSystem,
|
|
13
|
+
export { FileSystem, ReadOnlyFileSystem, WebFileSystem, createWebUnderlyingFS, type PathOps, type Permission, type PermissionRules, type UnderlyingFS, } from "./fs/index.ts";
|
|
14
14
|
export { createStdin, StdinImpl } from "./io/index.ts";
|
|
15
15
|
export { createStdout, createStderr, createPipe, OutputCollectorImpl, PipeBuffer } from "./io/index.ts";
|
|
16
16
|
export { escape, escapeForInterpolation } from "./utils/index.ts";
|
package/package.json
CHANGED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/fs/opfs-fs.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import { FileSystem, type PermissionRules, type UnderlyingFS } from \"./real-fs.cjs\";\n\nconst DIRECTORY_MTIME = new Date(0);\n\nexport function createOPFSUnderlyingFS(root: FileSystemDirectoryHandle): UnderlyingFS {\n return {\n promises: {\n async readFile(path: string): Promise<Buffer> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: false });\n const file = await fileHandle.getFile();\n return Buffer.from(await file.arrayBuffer());\n },\n\n async readdir(path: string): Promise<string[]> {\n const dir = await walkDirectory(root, getPathSegments(path), false);\n const entries: string[] = [];\n for await (const [name] of dir.entries()) {\n entries.push(name);\n }\n return entries;\n },\n\n async stat(path: string): Promise<{\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n }> {\n const segments = getPathSegments(path);\n\n if (segments.length === 0) {\n return createDirectoryStat();\n }\n\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n\n try {\n const fileHandle = await parent.getFileHandle(name, { create: false });\n const file = await fileHandle.getFile();\n return {\n isFile: () => true,\n isDirectory: () => false,\n size: file.size,\n mtime: new Date(file.lastModified ?? 0),\n };\n } catch (error) {\n if (!isNotFoundOrTypeMismatch(error)) throw error;\n }\n\n try {\n await parent.getDirectoryHandle(name, { create: false });\n return createDirectoryStat();\n } catch (error) {\n if (!isNotFoundOrTypeMismatch(error)) throw error;\n throw error;\n }\n },\n\n async writeFile(path: string, data: Buffer | string): Promise<void> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: true });\n const writable = await fileHandle.createWritable();\n await writable.write(toWritableData(data));\n await writable.close();\n },\n\n async appendFile(path: string, data: Buffer | string): Promise<void> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: true });\n const file = await fileHandle.getFile();\n const writable = await fileHandle.createWritable({ keepExistingData: true });\n await writable.write({\n type: \"write\",\n position: file.size,\n data: toWritableData(data),\n });\n await writable.close();\n },\n\n async mkdir(path: string, opts?: { recursive?: boolean }): Promise<void> {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n return;\n }\n\n if (opts?.recursive) {\n await walkDirectory(root, segments, true);\n return;\n }\n\n const parent = await walkDirectory(root, segments.slice(0, -1), false);\n const name = segments[segments.length - 1]!;\n const exists = await entryExists(parent, name);\n if (exists) {\n throw new Error(`EEXIST: file already exists, mkdir '${normalizeOpfsPath(path)}'`);\n }\n await parent.getDirectoryHandle(name, { create: true });\n },\n\n async rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n throw new Error(\"EPERM: operation not permitted, rm '/'\");\n }\n\n const parent = await walkDirectory(root, segments.slice(0, -1), false);\n const name = segments[segments.length - 1]!;\n try {\n await parent.removeEntry(name, { recursive: opts?.recursive });\n } catch (error) {\n if (opts?.force && isNotFoundError(error)) {\n return;\n }\n throw error;\n }\n },\n },\n };\n}\n\nexport class OPFSFileSystem extends FileSystem {\n constructor(root: FileSystemDirectoryHandle, permissions?: PermissionRules) {\n super(\"/\", permissions, createOPFSUnderlyingFS(root));\n }\n}\n\nfunction createDirectoryStat() {\n return {\n isFile: () => false,\n isDirectory: () => true,\n size: 0,\n mtime: DIRECTORY_MTIME,\n };\n}\n\nfunction normalizeOpfsPath(path: string): string {\n const normalized = path.replace(/\\\\/g, \"/\");\n const rawSegments = (normalized.startsWith(\"/\") ? normalized : `/${normalized}`)\n .split(\"/\")\n .filter(Boolean);\n\n const segments: string[] = [];\n for (const segment of rawSegments) {\n if (segment === \".\") continue;\n if (segment === \"..\") {\n segments.pop();\n continue;\n }\n segments.push(segment);\n }\n\n return `/${segments.join(\"/\")}`;\n}\n\nfunction getPathSegments(path: string): string[] {\n const normalized = normalizeOpfsPath(path);\n return normalized.split(\"/\").filter(Boolean);\n}\n\nfunction splitParent(path: string): { parentSegments: string[]; name: string } {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n throw new Error(`Invalid file path: \"${path}\"`);\n }\n return {\n parentSegments: segments.slice(0, -1),\n name: segments[segments.length - 1]!,\n };\n}\n\nasync function walkDirectory(\n root: FileSystemDirectoryHandle,\n segments: string[],\n create: boolean\n): Promise<FileSystemDirectoryHandle> {\n let current = root;\n for (const segment of segments) {\n current = await current.getDirectoryHandle(segment, { create });\n }\n return current;\n}\n\nasync function entryExists(dir: FileSystemDirectoryHandle, name: string): Promise<boolean> {\n try {\n await dir.getFileHandle(name, { create: false });\n return true;\n } catch (error) {\n if (isTypeMismatchError(error)) return true;\n if (!isNotFoundError(error)) throw error;\n }\n\n try {\n await dir.getDirectoryHandle(name, { create: false });\n return true;\n } catch (error) {\n if (isTypeMismatchError(error)) return true;\n if (!isNotFoundError(error)) throw error;\n return false;\n }\n}\n\nfunction isNotFoundOrTypeMismatch(error: unknown): boolean {\n return isNotFoundError(error) || isTypeMismatchError(error);\n}\n\nfunction isNotFoundError(error: unknown): boolean {\n return getErrorName(error) === \"NotFoundError\";\n}\n\nfunction isTypeMismatchError(error: unknown): boolean {\n return getErrorName(error) === \"TypeMismatchError\";\n}\n\nfunction getErrorName(error: unknown): string | undefined {\n if (!error || typeof error !== \"object\") return undefined;\n const named = error as { name?: unknown };\n return typeof named.name === \"string\" ? named.name : undefined;\n}\n\nfunction toWritableData(data: Buffer | string): string | ArrayBuffer {\n if (typeof data === \"string\") {\n return data;\n }\n const out = new Uint8Array(data.length);\n out.set(data);\n return out.buffer;\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "A8F1730E4A45379564756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/fs/opfs-fs.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"import { FileSystem, type PermissionRules, type UnderlyingFS } from \"./real-fs.mjs\";\n\nconst DIRECTORY_MTIME = new Date(0);\n\nexport function createOPFSUnderlyingFS(root: FileSystemDirectoryHandle): UnderlyingFS {\n return {\n promises: {\n async readFile(path: string): Promise<Buffer> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: false });\n const file = await fileHandle.getFile();\n return Buffer.from(await file.arrayBuffer());\n },\n\n async readdir(path: string): Promise<string[]> {\n const dir = await walkDirectory(root, getPathSegments(path), false);\n const entries: string[] = [];\n for await (const [name] of dir.entries()) {\n entries.push(name);\n }\n return entries;\n },\n\n async stat(path: string): Promise<{\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n }> {\n const segments = getPathSegments(path);\n\n if (segments.length === 0) {\n return createDirectoryStat();\n }\n\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n\n try {\n const fileHandle = await parent.getFileHandle(name, { create: false });\n const file = await fileHandle.getFile();\n return {\n isFile: () => true,\n isDirectory: () => false,\n size: file.size,\n mtime: new Date(file.lastModified ?? 0),\n };\n } catch (error) {\n if (!isNotFoundOrTypeMismatch(error)) throw error;\n }\n\n try {\n await parent.getDirectoryHandle(name, { create: false });\n return createDirectoryStat();\n } catch (error) {\n if (!isNotFoundOrTypeMismatch(error)) throw error;\n throw error;\n }\n },\n\n async writeFile(path: string, data: Buffer | string): Promise<void> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: true });\n const writable = await fileHandle.createWritable();\n await writable.write(toWritableData(data));\n await writable.close();\n },\n\n async appendFile(path: string, data: Buffer | string): Promise<void> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: true });\n const file = await fileHandle.getFile();\n const writable = await fileHandle.createWritable({ keepExistingData: true });\n await writable.write({\n type: \"write\",\n position: file.size,\n data: toWritableData(data),\n });\n await writable.close();\n },\n\n async mkdir(path: string, opts?: { recursive?: boolean }): Promise<void> {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n return;\n }\n\n if (opts?.recursive) {\n await walkDirectory(root, segments, true);\n return;\n }\n\n const parent = await walkDirectory(root, segments.slice(0, -1), false);\n const name = segments[segments.length - 1]!;\n const exists = await entryExists(parent, name);\n if (exists) {\n throw new Error(`EEXIST: file already exists, mkdir '${normalizeOpfsPath(path)}'`);\n }\n await parent.getDirectoryHandle(name, { create: true });\n },\n\n async rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n throw new Error(\"EPERM: operation not permitted, rm '/'\");\n }\n\n const parent = await walkDirectory(root, segments.slice(0, -1), false);\n const name = segments[segments.length - 1]!;\n try {\n await parent.removeEntry(name, { recursive: opts?.recursive });\n } catch (error) {\n if (opts?.force && isNotFoundError(error)) {\n return;\n }\n throw error;\n }\n },\n },\n };\n}\n\nexport class OPFSFileSystem extends FileSystem {\n constructor(root: FileSystemDirectoryHandle, permissions?: PermissionRules) {\n super(\"/\", permissions, createOPFSUnderlyingFS(root));\n }\n}\n\nfunction createDirectoryStat() {\n return {\n isFile: () => false,\n isDirectory: () => true,\n size: 0,\n mtime: DIRECTORY_MTIME,\n };\n}\n\nfunction normalizeOpfsPath(path: string): string {\n const normalized = path.replace(/\\\\/g, \"/\");\n const rawSegments = (normalized.startsWith(\"/\") ? normalized : `/${normalized}`)\n .split(\"/\")\n .filter(Boolean);\n\n const segments: string[] = [];\n for (const segment of rawSegments) {\n if (segment === \".\") continue;\n if (segment === \"..\") {\n segments.pop();\n continue;\n }\n segments.push(segment);\n }\n\n return `/${segments.join(\"/\")}`;\n}\n\nfunction getPathSegments(path: string): string[] {\n const normalized = normalizeOpfsPath(path);\n return normalized.split(\"/\").filter(Boolean);\n}\n\nfunction splitParent(path: string): { parentSegments: string[]; name: string } {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n throw new Error(`Invalid file path: \"${path}\"`);\n }\n return {\n parentSegments: segments.slice(0, -1),\n name: segments[segments.length - 1]!,\n };\n}\n\nasync function walkDirectory(\n root: FileSystemDirectoryHandle,\n segments: string[],\n create: boolean\n): Promise<FileSystemDirectoryHandle> {\n let current = root;\n for (const segment of segments) {\n current = await current.getDirectoryHandle(segment, { create });\n }\n return current;\n}\n\nasync function entryExists(dir: FileSystemDirectoryHandle, name: string): Promise<boolean> {\n try {\n await dir.getFileHandle(name, { create: false });\n return true;\n } catch (error) {\n if (isTypeMismatchError(error)) return true;\n if (!isNotFoundError(error)) throw error;\n }\n\n try {\n await dir.getDirectoryHandle(name, { create: false });\n return true;\n } catch (error) {\n if (isTypeMismatchError(error)) return true;\n if (!isNotFoundError(error)) throw error;\n return false;\n }\n}\n\nfunction isNotFoundOrTypeMismatch(error: unknown): boolean {\n return isNotFoundError(error) || isTypeMismatchError(error);\n}\n\nfunction isNotFoundError(error: unknown): boolean {\n return getErrorName(error) === \"NotFoundError\";\n}\n\nfunction isTypeMismatchError(error: unknown): boolean {\n return getErrorName(error) === \"TypeMismatchError\";\n}\n\nfunction getErrorName(error: unknown): string | undefined {\n if (!error || typeof error !== \"object\") return undefined;\n const named = error as { name?: unknown };\n return typeof named.name === \"string\" ? named.name : undefined;\n}\n\nfunction toWritableData(data: Buffer | string): string | ArrayBuffer {\n if (typeof data === \"string\") {\n return data;\n }\n const out = new Uint8Array(data.length);\n out.set(data);\n return out.buffer;\n}\n"
|
|
6
|
-
],
|
|
7
|
-
"mappings": "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",
|
|
8
|
-
"debugId": "00CBC00BD7A1121364756E2164756E21",
|
|
9
|
-
"names": []
|
|
10
|
-
}
|