shell-dsl 0.0.30 → 0.0.32
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 +27 -1
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/commands/mv/mv.cjs +7 -1
- package/dist/cjs/src/commands/mv/mv.cjs.map +3 -3
- package/dist/cjs/src/commands/tree/tree.cjs +64 -27
- package/dist/cjs/src/commands/tree/tree.cjs.map +3 -3
- package/dist/cjs/src/fs/memfs-adapter.cjs +32 -110
- package/dist/cjs/src/fs/memfs-adapter.cjs.map +3 -3
- package/dist/cjs/src/fs/real-fs.cjs +34 -112
- package/dist/cjs/src/fs/real-fs.cjs.map +3 -3
- package/dist/cjs/src/fs/special-files.cjs +98 -0
- package/dist/cjs/src/fs/special-files.cjs.map +10 -0
- package/dist/cjs/src/index.cjs +2 -1
- package/dist/cjs/src/index.cjs.map +3 -3
- package/dist/cjs/src/interpreter/interpreter.cjs +9 -8
- package/dist/cjs/src/interpreter/interpreter.cjs.map +3 -3
- package/dist/cjs/src/utils/glob.cjs +129 -0
- package/dist/cjs/src/utils/glob.cjs.map +10 -0
- package/dist/cjs/src/utils/index.cjs +3 -1
- package/dist/cjs/src/utils/index.cjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/src/commands/mv/mv.mjs +7 -1
- package/dist/mjs/src/commands/mv/mv.mjs.map +3 -3
- package/dist/mjs/src/commands/tree/tree.mjs +64 -27
- package/dist/mjs/src/commands/tree/tree.mjs.map +3 -3
- package/dist/mjs/src/fs/memfs-adapter.mjs +38 -110
- package/dist/mjs/src/fs/memfs-adapter.mjs.map +3 -3
- package/dist/mjs/src/fs/real-fs.mjs +40 -112
- package/dist/mjs/src/fs/real-fs.mjs.map +3 -3
- package/dist/mjs/src/fs/special-files.mjs +58 -0
- package/dist/mjs/src/fs/special-files.mjs.map +10 -0
- package/dist/mjs/src/index.mjs +3 -2
- package/dist/mjs/src/index.mjs.map +2 -2
- package/dist/mjs/src/interpreter/interpreter.mjs +9 -8
- package/dist/mjs/src/interpreter/interpreter.mjs.map +3 -3
- package/dist/mjs/src/utils/glob.mjs +89 -0
- package/dist/mjs/src/utils/glob.mjs.map +10 -0
- package/dist/mjs/src/utils/index.mjs +3 -1
- package/dist/mjs/src/utils/index.mjs.map +3 -3
- package/dist/types/src/fs/real-fs.d.ts +0 -5
- package/dist/types/src/fs/special-files.d.ts +8 -0
- package/dist/types/src/index.d.ts +2 -1
- package/dist/types/src/utils/glob.d.ts +6 -0
- package/dist/types/src/utils/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -68,6 +68,8 @@ __export(exports_real_fs, {
|
|
|
68
68
|
module.exports = __toCommonJS(exports_real_fs);
|
|
69
69
|
var path = __toESM(require("path"));
|
|
70
70
|
var nodeFs = __toESM(require("node:fs/promises"));
|
|
71
|
+
var import_glob = require("../utils/glob.cjs");
|
|
72
|
+
var import_special_files = require("./special-files.cjs");
|
|
71
73
|
var defaultFS = { promises: nodeFs };
|
|
72
74
|
var nodePathOps = {
|
|
73
75
|
separator: path.sep,
|
|
@@ -166,6 +168,10 @@ class FileSystem {
|
|
|
166
168
|
return resolved;
|
|
167
169
|
}
|
|
168
170
|
async readFile(filePath, encoding) {
|
|
171
|
+
const specialContent = import_special_files.readSpecialFile(filePath, encoding);
|
|
172
|
+
if (specialContent !== undefined) {
|
|
173
|
+
return specialContent;
|
|
174
|
+
}
|
|
169
175
|
this.checkPermission(filePath, "read");
|
|
170
176
|
const realPath = this.resolveSafePath(filePath);
|
|
171
177
|
const content = await this.underlyingFs.promises.readFile(realPath);
|
|
@@ -173,12 +179,20 @@ class FileSystem {
|
|
|
173
179
|
return encoding ? buf.toString(encoding) : buf;
|
|
174
180
|
}
|
|
175
181
|
async readdir(dirPath) {
|
|
182
|
+
const specialError = import_special_files.getSpecialPathError(dirPath, "readdir");
|
|
183
|
+
if (specialError) {
|
|
184
|
+
throw specialError;
|
|
185
|
+
}
|
|
176
186
|
this.checkPermission(dirPath, "read");
|
|
177
187
|
const realPath = this.resolveSafePath(dirPath);
|
|
178
188
|
const entries = await this.underlyingFs.promises.readdir(realPath);
|
|
179
189
|
return entries.map(String);
|
|
180
190
|
}
|
|
181
191
|
async stat(filePath) {
|
|
192
|
+
const specialStat = import_special_files.statSpecialFile(filePath);
|
|
193
|
+
if (specialStat) {
|
|
194
|
+
return specialStat;
|
|
195
|
+
}
|
|
182
196
|
this.checkPermission(filePath, "read");
|
|
183
197
|
const realPath = this.resolveSafePath(filePath);
|
|
184
198
|
const stats = await this.underlyingFs.promises.stat(realPath);
|
|
@@ -190,6 +204,10 @@ class FileSystem {
|
|
|
190
204
|
};
|
|
191
205
|
}
|
|
192
206
|
async exists(filePath) {
|
|
207
|
+
const specialExists = import_special_files.existsSpecialFile(filePath);
|
|
208
|
+
if (specialExists !== undefined) {
|
|
209
|
+
return specialExists;
|
|
210
|
+
}
|
|
193
211
|
try {
|
|
194
212
|
this.checkPermission(filePath, "read");
|
|
195
213
|
const realPath = this.resolveSafePath(filePath);
|
|
@@ -200,21 +218,35 @@ class FileSystem {
|
|
|
200
218
|
}
|
|
201
219
|
}
|
|
202
220
|
async writeFile(filePath, data) {
|
|
221
|
+
if (import_special_files.discardsSpecialFileWrites(filePath)) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
203
224
|
this.checkPermission(filePath, "write");
|
|
204
225
|
const realPath = this.resolveSafePath(filePath);
|
|
205
226
|
await this.underlyingFs.promises.writeFile(realPath, data);
|
|
206
227
|
}
|
|
207
228
|
async appendFile(filePath, data) {
|
|
229
|
+
if (import_special_files.discardsSpecialFileWrites(filePath)) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
208
232
|
this.checkPermission(filePath, "write");
|
|
209
233
|
const realPath = this.resolveSafePath(filePath);
|
|
210
234
|
await this.underlyingFs.promises.appendFile(realPath, data);
|
|
211
235
|
}
|
|
212
236
|
async mkdir(dirPath, opts) {
|
|
237
|
+
const specialError = import_special_files.getSpecialPathError(dirPath, "mkdir");
|
|
238
|
+
if (specialError) {
|
|
239
|
+
throw specialError;
|
|
240
|
+
}
|
|
213
241
|
this.checkPermission(dirPath, "write");
|
|
214
242
|
const realPath = this.resolveSafePath(dirPath);
|
|
215
243
|
await this.underlyingFs.promises.mkdir(realPath, opts);
|
|
216
244
|
}
|
|
217
245
|
async rm(filePath, opts) {
|
|
246
|
+
const specialError = import_special_files.getSpecialPathError(filePath, "rm");
|
|
247
|
+
if (specialError) {
|
|
248
|
+
throw specialError;
|
|
249
|
+
}
|
|
218
250
|
this.checkPermission(filePath, "write");
|
|
219
251
|
const realPath = this.resolveSafePath(filePath);
|
|
220
252
|
await this.underlyingFs.promises.rm(realPath, opts);
|
|
@@ -231,118 +263,8 @@ class FileSystem {
|
|
|
231
263
|
async glob(pattern, opts) {
|
|
232
264
|
const cwd = opts?.cwd ?? "/";
|
|
233
265
|
this.checkPermission(cwd, "read");
|
|
234
|
-
|
|
235
|
-
return matches.filter((p) => this.getPermission(p) !== "excluded").sort();
|
|
236
|
-
}
|
|
237
|
-
async expandGlob(pattern, cwd) {
|
|
238
|
-
const patterns = this.expandBraces(pattern);
|
|
239
|
-
const allMatches = [];
|
|
240
|
-
for (const pat of patterns) {
|
|
241
|
-
const matches = await this.matchPattern(pat, cwd);
|
|
242
|
-
allMatches.push(...matches);
|
|
243
|
-
}
|
|
244
|
-
return [...new Set(allMatches)].sort();
|
|
245
|
-
}
|
|
246
|
-
expandBraces(pattern) {
|
|
247
|
-
const braceMatch = pattern.match(/\{([^{}]+)\}/);
|
|
248
|
-
if (!braceMatch)
|
|
249
|
-
return [pattern];
|
|
250
|
-
const before = pattern.slice(0, braceMatch.index);
|
|
251
|
-
const after = pattern.slice(braceMatch.index + braceMatch[0].length);
|
|
252
|
-
const options = braceMatch[1].split(",");
|
|
253
|
-
const results = [];
|
|
254
|
-
for (const opt of options) {
|
|
255
|
-
const expanded = this.expandBraces(before + opt + after);
|
|
256
|
-
results.push(...expanded);
|
|
257
|
-
}
|
|
258
|
-
return results;
|
|
259
|
-
}
|
|
260
|
-
async matchPattern(pattern, cwd) {
|
|
261
|
-
const parts = pattern.split("/").filter((p) => p !== "");
|
|
262
|
-
const isAbsolute2 = pattern.startsWith("/");
|
|
263
|
-
const startDir = isAbsolute2 ? "/" : cwd;
|
|
264
|
-
return this.matchParts(parts, startDir);
|
|
265
|
-
}
|
|
266
|
-
async matchParts(parts, currentPath) {
|
|
267
|
-
if (parts.length === 0) {
|
|
268
|
-
return [currentPath];
|
|
269
|
-
}
|
|
270
|
-
const [part, ...rest] = parts;
|
|
271
|
-
if (part === "**") {
|
|
272
|
-
const results = [];
|
|
273
|
-
const withoutStar = await this.matchParts(rest, currentPath);
|
|
274
|
-
results.push(...withoutStar);
|
|
275
|
-
try {
|
|
276
|
-
const realPath = this.resolveSafePath(currentPath);
|
|
277
|
-
const entries = await this.underlyingFs.promises.readdir(realPath);
|
|
278
|
-
for (const entry of entries) {
|
|
279
|
-
const entryPath = this.pathOps.join(currentPath, String(entry));
|
|
280
|
-
try {
|
|
281
|
-
const entryRealPath = this.resolveSafePath(entryPath);
|
|
282
|
-
const stat = await this.underlyingFs.promises.stat(entryRealPath);
|
|
283
|
-
if (stat.isDirectory()) {
|
|
284
|
-
const subMatches = await this.matchParts(parts, entryPath);
|
|
285
|
-
results.push(...subMatches);
|
|
286
|
-
}
|
|
287
|
-
} catch {}
|
|
288
|
-
}
|
|
289
|
-
} catch {}
|
|
290
|
-
return results;
|
|
291
|
-
}
|
|
292
|
-
const regex = this.globToRegex(part);
|
|
293
|
-
try {
|
|
294
|
-
const realPath = this.resolveSafePath(currentPath);
|
|
295
|
-
const entries = await this.underlyingFs.promises.readdir(realPath);
|
|
296
|
-
const results = [];
|
|
297
|
-
for (const entry of entries) {
|
|
298
|
-
const entryName = String(entry);
|
|
299
|
-
if (regex.test(entryName)) {
|
|
300
|
-
const entryPath = this.pathOps.join(currentPath, entryName);
|
|
301
|
-
if (rest.length === 0) {
|
|
302
|
-
results.push(entryPath);
|
|
303
|
-
} else {
|
|
304
|
-
try {
|
|
305
|
-
const entryRealPath = this.resolveSafePath(entryPath);
|
|
306
|
-
const stat = await this.underlyingFs.promises.stat(entryRealPath);
|
|
307
|
-
if (stat.isDirectory()) {
|
|
308
|
-
const subMatches = await this.matchParts(rest, entryPath);
|
|
309
|
-
results.push(...subMatches);
|
|
310
|
-
}
|
|
311
|
-
} catch {}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
return results;
|
|
316
|
-
} catch {
|
|
317
|
-
return [];
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
globToRegex(pattern) {
|
|
321
|
-
let regex = "^";
|
|
322
|
-
for (let i = 0;i < pattern.length; i++) {
|
|
323
|
-
const char = pattern[i];
|
|
324
|
-
if (char === "*") {
|
|
325
|
-
regex += "[^/]*";
|
|
326
|
-
} else if (char === "?") {
|
|
327
|
-
regex += "[^/]";
|
|
328
|
-
} else if (char === "[") {
|
|
329
|
-
let j = i + 1;
|
|
330
|
-
let classContent = "";
|
|
331
|
-
while (j < pattern.length && pattern[j] !== "]") {
|
|
332
|
-
classContent += pattern[j];
|
|
333
|
-
j++;
|
|
334
|
-
}
|
|
335
|
-
regex += `[${classContent}]`;
|
|
336
|
-
i = j;
|
|
337
|
-
} else if (".+^${}()|\\".includes(char)) {
|
|
338
|
-
regex += "\\" + char;
|
|
339
|
-
} else {
|
|
340
|
-
regex += char;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
regex += "$";
|
|
344
|
-
return new RegExp(regex);
|
|
266
|
+
return import_glob.globVirtualFS(this, pattern, { cwd });
|
|
345
267
|
}
|
|
346
268
|
}
|
|
347
269
|
|
|
348
|
-
//# debugId=
|
|
270
|
+
//# debugId=58FDFA4CFF76BA5364756E2164756E21
|
|
@@ -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>;\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"
|
|
5
|
+
"import * as path from \"path\";\nimport * as nodeFs from \"node:fs/promises\";\nimport type { VirtualFS, FileStat } from \"../types.cjs\";\nimport { globVirtualFS } from \"../utils/glob.cjs\";\nimport {\n discardsSpecialFileWrites,\n existsSpecialFile,\n getSpecialPathError,\n readSpecialFile,\n statSpecialFile,\n} from \"./special-files.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 const specialContent = readSpecialFile(filePath, encoding);\n if (specialContent !== undefined) {\n return specialContent;\n }\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 const specialError = getSpecialPathError(dirPath, \"readdir\");\n if (specialError) {\n throw specialError;\n }\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 const specialStat = statSpecialFile(filePath);\n if (specialStat) {\n return specialStat;\n }\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 const specialExists = existsSpecialFile(filePath);\n if (specialExists !== undefined) {\n return specialExists;\n }\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 if (discardsSpecialFileWrites(filePath)) {\n return;\n }\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 if (discardsSpecialFileWrites(filePath)) {\n return;\n }\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 const specialError = getSpecialPathError(dirPath, \"mkdir\");\n if (specialError) {\n throw specialError;\n }\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 const specialError = getSpecialPathError(filePath, \"rm\");\n if (specialError) {\n throw specialError;\n }\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 expansion\n async glob(pattern: string, opts?: { cwd?: string }): Promise<string[]> {\n const cwd = opts?.cwd ?? \"/\";\n this.checkPermission(cwd, \"read\");\n return globVirtualFS(this, pattern, { cwd });\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAsB,IAAtB;AACwB,IAAxB;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "58FDFA4CFF76BA5364756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
function __accessProp(key) {
|
|
6
|
+
return this[key];
|
|
7
|
+
}
|
|
8
|
+
var __toCommonJS = (from) => {
|
|
9
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
10
|
+
if (entry)
|
|
11
|
+
return entry;
|
|
12
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (var key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(entry, key))
|
|
16
|
+
__defProp(entry, key, {
|
|
17
|
+
get: __accessProp.bind(from, key),
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
__moduleCache.set(from, entry);
|
|
22
|
+
return entry;
|
|
23
|
+
};
|
|
24
|
+
var __moduleCache;
|
|
25
|
+
var __returnValue = (v) => v;
|
|
26
|
+
function __exportSetter(name, newValue) {
|
|
27
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
28
|
+
}
|
|
29
|
+
var __export = (target, all) => {
|
|
30
|
+
for (var name in all)
|
|
31
|
+
__defProp(target, name, {
|
|
32
|
+
get: all[name],
|
|
33
|
+
enumerable: true,
|
|
34
|
+
configurable: true,
|
|
35
|
+
set: __exportSetter.bind(all, name)
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/fs/special-files.ts
|
|
40
|
+
var exports_special_files = {};
|
|
41
|
+
__export(exports_special_files, {
|
|
42
|
+
statSpecialFile: () => statSpecialFile,
|
|
43
|
+
readSpecialFile: () => readSpecialFile,
|
|
44
|
+
isDevNullPath: () => isDevNullPath,
|
|
45
|
+
getSpecialPathError: () => getSpecialPathError,
|
|
46
|
+
existsSpecialFile: () => existsSpecialFile,
|
|
47
|
+
discardsSpecialFileWrites: () => discardsSpecialFileWrites,
|
|
48
|
+
DEV_NULL_PATH: () => DEV_NULL_PATH
|
|
49
|
+
});
|
|
50
|
+
module.exports = __toCommonJS(exports_special_files);
|
|
51
|
+
var DEV_NULL_PATH = "/dev/null";
|
|
52
|
+
function normalizeSpecialPath(path) {
|
|
53
|
+
return path.replace(/\\/g, "/");
|
|
54
|
+
}
|
|
55
|
+
function isDevNullPath(path) {
|
|
56
|
+
return normalizeSpecialPath(path) === DEV_NULL_PATH;
|
|
57
|
+
}
|
|
58
|
+
function readSpecialFile(path, encoding) {
|
|
59
|
+
if (!isDevNullPath(path)) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
return encoding ? "" : Buffer.alloc(0);
|
|
63
|
+
}
|
|
64
|
+
function statSpecialFile(path) {
|
|
65
|
+
if (!isDevNullPath(path)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
isFile: () => true,
|
|
70
|
+
isDirectory: () => false,
|
|
71
|
+
size: 0,
|
|
72
|
+
mtime: new Date(0)
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function existsSpecialFile(path) {
|
|
76
|
+
if (!isDevNullPath(path)) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
function discardsSpecialFileWrites(path) {
|
|
82
|
+
return isDevNullPath(path);
|
|
83
|
+
}
|
|
84
|
+
function getSpecialPathError(path, operation) {
|
|
85
|
+
if (!isDevNullPath(path)) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
switch (operation) {
|
|
89
|
+
case "mkdir":
|
|
90
|
+
return new Error(`EEXIST: file already exists, mkdir '${DEV_NULL_PATH}'`);
|
|
91
|
+
case "readdir":
|
|
92
|
+
return new Error(`ENOTDIR: not a directory, scandir '${DEV_NULL_PATH}'`);
|
|
93
|
+
case "rm":
|
|
94
|
+
return new Error(`EPERM: operation not permitted, rm '${DEV_NULL_PATH}'`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
//# debugId=674232080F989C1164756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/fs/special-files.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type { FileStat } from \"../types.cjs\";\n\nexport const DEV_NULL_PATH = \"/dev/null\";\n\nfunction normalizeSpecialPath(path: string): string {\n return path.replace(/\\\\/g, \"/\");\n}\n\nexport function isDevNullPath(path: string): boolean {\n return normalizeSpecialPath(path) === DEV_NULL_PATH;\n}\n\nexport function readSpecialFile(\n path: string,\n encoding?: BufferEncoding\n): Buffer | string | undefined {\n if (!isDevNullPath(path)) {\n return undefined;\n }\n\n return encoding ? \"\" : Buffer.alloc(0);\n}\n\nexport function statSpecialFile(path: string): FileStat | undefined {\n if (!isDevNullPath(path)) {\n return undefined;\n }\n\n return {\n isFile: () => true,\n isDirectory: () => false,\n size: 0,\n mtime: new Date(0),\n };\n}\n\nexport function existsSpecialFile(path: string): boolean | undefined {\n if (!isDevNullPath(path)) {\n return undefined;\n }\n\n return true;\n}\n\nexport function discardsSpecialFileWrites(path: string): boolean {\n return isDevNullPath(path);\n}\n\nexport function getSpecialPathError(\n path: string,\n operation: \"mkdir\" | \"readdir\" | \"rm\"\n): Error | undefined {\n if (!isDevNullPath(path)) {\n return undefined;\n }\n\n switch (operation) {\n case \"mkdir\":\n return new Error(`EEXIST: file already exists, mkdir '${DEV_NULL_PATH}'`);\n case \"readdir\":\n return new Error(`ENOTDIR: not a directory, scandir '${DEV_NULL_PATH}'`);\n case \"rm\":\n return new Error(`EPERM: operation not permitted, rm '${DEV_NULL_PATH}'`);\n }\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEO,IAAM,gBAAgB;AAE7B,SAAS,oBAAoB,CAAC,MAAsB;AAAA,EAClD,OAAO,KAAK,QAAQ,OAAO,GAAG;AAAA;AAGzB,SAAS,aAAa,CAAC,MAAuB;AAAA,EACnD,OAAO,qBAAqB,IAAI,MAAM;AAAA;AAGjC,SAAS,eAAe,CAC7B,MACA,UAC6B;AAAA,EAC7B,IAAI,CAAC,cAAc,IAAI,GAAG;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,OAAO,WAAW,KAAK,OAAO,MAAM,CAAC;AAAA;AAGhC,SAAS,eAAe,CAAC,MAAoC;AAAA,EAClE,IAAI,CAAC,cAAc,IAAI,GAAG;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,aAAa,MAAM;AAAA,IACnB,MAAM;AAAA,IACN,OAAO,IAAI,KAAK,CAAC;AAAA,EACnB;AAAA;AAGK,SAAS,iBAAiB,CAAC,MAAmC;AAAA,EACnE,IAAI,CAAC,cAAc,IAAI,GAAG;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,OAAO;AAAA;AAGF,SAAS,yBAAyB,CAAC,MAAuB;AAAA,EAC/D,OAAO,cAAc,IAAI;AAAA;AAGpB,SAAS,mBAAmB,CACjC,MACA,WACmB;AAAA,EACnB,IAAI,CAAC,cAAc,IAAI,GAAG;AAAA,IACxB;AAAA,EACF;AAAA,EAEA,QAAQ;AAAA,SACD;AAAA,MACH,OAAO,IAAI,MAAM,uCAAuC,gBAAgB;AAAA,SACrE;AAAA,MACH,OAAO,IAAI,MAAM,sCAAsC,gBAAgB;AAAA,SACpE;AAAA,MACH,OAAO,IAAI,MAAM,uCAAuC,gBAAgB;AAAA;AAAA;",
|
|
8
|
+
"debugId": "674232080F989C1164756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
package/dist/cjs/src/index.cjs
CHANGED
|
@@ -59,6 +59,7 @@ __export(exports_src, {
|
|
|
59
59
|
isCaseNode: () => import_parser2.isCaseNode,
|
|
60
60
|
isArithmeticNode: () => import_parser2.isArithmeticNode,
|
|
61
61
|
isAndNode: () => import_parser2.isAndNode,
|
|
62
|
+
globVirtualFS: () => import_utils.globVirtualFS,
|
|
62
63
|
escapeForInterpolation: () => import_utils.escapeForInterpolation,
|
|
63
64
|
escape: () => import_utils.escape,
|
|
64
65
|
createWebUnderlyingFS: () => import_fs2.createWebUnderlyingFS,
|
|
@@ -100,4 +101,4 @@ var import_io = require("./io/index.cjs");
|
|
|
100
101
|
var import_io2 = require("./io/index.cjs");
|
|
101
102
|
var import_utils = require("./utils/index.cjs");
|
|
102
103
|
|
|
103
|
-
//# debugId=
|
|
104
|
+
//# debugId=76B4D83DA9C2C40964756E2164756E21
|
|
@@ -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 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"
|
|
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, globVirtualFS } from \"./utils/index.cjs\";\nexport type { GlobVirtualFS, GlobOptions } from \"./utils/index.cjs\";\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
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;AAG8D,IAA9D;",
|
|
8
|
+
"debugId": "76B4D83DA9C2C40964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -47,6 +47,7 @@ module.exports = __toCommonJS(exports_interpreter);
|
|
|
47
47
|
var import_context = require("./context.cjs");
|
|
48
48
|
var import_stdin = require("../io/stdin.cjs");
|
|
49
49
|
var import_stdout = require("../io/stdout.cjs");
|
|
50
|
+
var import_special_files = require("../fs/special-files.cjs");
|
|
50
51
|
|
|
51
52
|
class BreakException extends Error {
|
|
52
53
|
levels;
|
|
@@ -284,7 +285,7 @@ class Interpreter {
|
|
|
284
285
|
stderr
|
|
285
286
|
};
|
|
286
287
|
}
|
|
287
|
-
if (target
|
|
288
|
+
if (import_special_files.isDevNullPath(target)) {
|
|
288
289
|
return {
|
|
289
290
|
stdin: async function* () {}(),
|
|
290
291
|
stdout,
|
|
@@ -303,7 +304,7 @@ class Interpreter {
|
|
|
303
304
|
}
|
|
304
305
|
case ">": {
|
|
305
306
|
const collector = import_stdout.createStdout();
|
|
306
|
-
if (target
|
|
307
|
+
if (import_special_files.isDevNullPath(target)) {
|
|
307
308
|
return { stdin, stdout: collector, stderr };
|
|
308
309
|
}
|
|
309
310
|
const path = this.fs.resolve(this.cwd, target);
|
|
@@ -315,7 +316,7 @@ class Interpreter {
|
|
|
315
316
|
}
|
|
316
317
|
case ">>": {
|
|
317
318
|
const collector = import_stdout.createStdout();
|
|
318
|
-
if (target
|
|
319
|
+
if (import_special_files.isDevNullPath(target)) {
|
|
319
320
|
return { stdin, stdout: collector, stderr };
|
|
320
321
|
}
|
|
321
322
|
const path = this.fs.resolve(this.cwd, target);
|
|
@@ -327,7 +328,7 @@ class Interpreter {
|
|
|
327
328
|
}
|
|
328
329
|
case "2>": {
|
|
329
330
|
const collector = import_stdout.createStderr();
|
|
330
|
-
if (target
|
|
331
|
+
if (import_special_files.isDevNullPath(target)) {
|
|
331
332
|
return { stdin, stdout, stderr: collector };
|
|
332
333
|
}
|
|
333
334
|
const path = this.fs.resolve(this.cwd, target);
|
|
@@ -339,7 +340,7 @@ class Interpreter {
|
|
|
339
340
|
}
|
|
340
341
|
case "2>>": {
|
|
341
342
|
const collector = import_stdout.createStderr();
|
|
342
|
-
if (target
|
|
343
|
+
if (import_special_files.isDevNullPath(target)) {
|
|
343
344
|
return { stdin, stdout, stderr: collector };
|
|
344
345
|
}
|
|
345
346
|
const path = this.fs.resolve(this.cwd, target);
|
|
@@ -351,7 +352,7 @@ class Interpreter {
|
|
|
351
352
|
}
|
|
352
353
|
case "&>": {
|
|
353
354
|
const collector = import_stdout.createStdout();
|
|
354
|
-
if (target
|
|
355
|
+
if (import_special_files.isDevNullPath(target)) {
|
|
355
356
|
return { stdin, stdout: collector, stderr: collector };
|
|
356
357
|
}
|
|
357
358
|
const path = this.fs.resolve(this.cwd, target);
|
|
@@ -363,7 +364,7 @@ class Interpreter {
|
|
|
363
364
|
}
|
|
364
365
|
case "&>>": {
|
|
365
366
|
const collector = import_stdout.createStdout();
|
|
366
|
-
if (target
|
|
367
|
+
if (import_special_files.isDevNullPath(target)) {
|
|
367
368
|
return { stdin, stdout: collector, stderr: collector };
|
|
368
369
|
}
|
|
369
370
|
const path = this.fs.resolve(this.cwd, target);
|
|
@@ -866,4 +867,4 @@ class Interpreter {
|
|
|
866
867
|
}
|
|
867
868
|
}
|
|
868
869
|
|
|
869
|
-
//# debugId=
|
|
870
|
+
//# debugId=9752A1F1CEADD56864756E2164756E21
|