shell-dsl 0.0.31 → 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 +1 -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 +26 -3
- package/dist/cjs/src/fs/memfs-adapter.cjs.map +3 -3
- package/dist/cjs/src/fs/real-fs.cjs +32 -1
- 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/interpreter/interpreter.cjs +9 -8
- package/dist/cjs/src/interpreter/interpreter.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 +32 -3
- package/dist/mjs/src/fs/memfs-adapter.mjs.map +3 -3
- package/dist/mjs/src/fs/real-fs.mjs +38 -1
- 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/interpreter/interpreter.mjs +9 -8
- package/dist/mjs/src/interpreter/interpreter.mjs.map +3 -3
- package/dist/types/src/fs/special-files.d.ts +8 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -564,7 +564,7 @@ import { echo, cat, grep, wc, cp, mv, touch, tee, tree, find, sed, awk, cut } fr
|
|
|
564
564
|
| `mv` | Move/rename files/directories (`-n` no-clobber) |
|
|
565
565
|
| `touch` | Create empty files or update timestamps (`-c` no-create) |
|
|
566
566
|
| `tee` | Duplicate stdin to stdout and files (`-a` append) |
|
|
567
|
-
| `tree` | Display directory structure as tree (`-a` all, `-d` dirs only, `-L <n>` depth) |
|
|
567
|
+
| `tree` | Display directory structure as tree (`-a` all, `-d` dirs only, `-L <n>` depth, `-I <pattern>` ignore, `--prune` remove empty dirs) |
|
|
568
568
|
| `find` | Search for files (`-name`, `-iname`, `-type f\|d`, `-maxdepth`, `-mindepth`) |
|
|
569
569
|
| `sed` | Stream editor (`s///`, `d`, `p`, `-n`, `-e`) |
|
|
570
570
|
| `awk` | Pattern scanning (`{print $1}`, `-F`, `NF`, `NR`) |
|
package/dist/cjs/package.json
CHANGED
|
@@ -43,6 +43,7 @@ __export(exports_mv, {
|
|
|
43
43
|
});
|
|
44
44
|
module.exports = __toCommonJS(exports_mv);
|
|
45
45
|
var import_flag_parser = require("../../utils/flag-parser.cjs");
|
|
46
|
+
var import_special_files = require("../../fs/special-files.cjs");
|
|
46
47
|
var spec = {
|
|
47
48
|
name: "mv",
|
|
48
49
|
flags: [
|
|
@@ -88,6 +89,11 @@ var mv = async (ctx) => {
|
|
|
88
89
|
try {
|
|
89
90
|
await ctx.fs.stat(srcPath);
|
|
90
91
|
const finalDest = destIsDir ? ctx.fs.resolve(destPath, ctx.fs.basename(srcPath)) : destPath;
|
|
92
|
+
if (import_special_files.isDevNullPath(srcPath) || import_special_files.isDevNullPath(finalDest)) {
|
|
93
|
+
await ctx.stderr.writeText(`mv: cannot move to or from special file '${import_special_files.DEV_NULL_PATH}'
|
|
94
|
+
`);
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
91
97
|
if (noClobber) {
|
|
92
98
|
const exists = await ctx.fs.exists(finalDest);
|
|
93
99
|
if (exists)
|
|
@@ -132,4 +138,4 @@ async function moveDirectory(ctx, src, dest, noClobber) {
|
|
|
132
138
|
await ctx.fs.rm(src, { recursive: true });
|
|
133
139
|
}
|
|
134
140
|
|
|
135
|
-
//# debugId=
|
|
141
|
+
//# debugId=C4CE62A58C3EA07364756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/commands/mv/mv.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { Command } from \"../../types.cjs\";\nimport { createFlagParser, type FlagDefinition } from \"../../utils/flag-parser.cjs\";\n\ninterface MvFlags {\n noClobber: boolean;\n}\n\nconst spec = {\n name: \"mv\",\n flags: [\n { short: \"n\", long: \"no-clobber\" },\n { short: \"f\", long: \"force\" },\n ] as FlagDefinition[],\n usage: \"mv [-nf] source ... dest\",\n};\n\nconst defaults: MvFlags = { noClobber: false };\n\nconst handler = (flags: MvFlags, flag: FlagDefinition) => {\n if (flag.short === \"n\") flags.noClobber = true;\n // -f is default behavior, so we don't need to do anything\n};\n\nconst parser = createFlagParser(spec, defaults, handler);\n\nexport const mv: Command = async (ctx) => {\n const result = parser.parse(ctx.args);\n\n if (result.error) {\n await parser.writeError(result.error, ctx.stderr);\n return 1;\n }\n\n const { noClobber } = result.flags;\n const paths = result.args;\n\n if (paths.length < 2) {\n await ctx.stderr.writeText(\"mv: missing destination file operand\\n\");\n return 1;\n }\n\n const sources = paths.slice(0, -1);\n const dest = paths[paths.length - 1]!;\n const destPath = ctx.fs.resolve(ctx.cwd, dest);\n\n // Check if destination is a directory\n let destIsDir = false;\n try {\n const stat = await ctx.fs.stat(destPath);\n destIsDir = stat.isDirectory();\n } catch {\n // Destination doesn't exist\n }\n\n // If multiple sources, dest must be a directory\n if (sources.length > 1 && !destIsDir) {\n await ctx.stderr.writeText(`mv: target '${dest}' is not a directory\\n`);\n return 1;\n }\n\n for (const source of sources) {\n const srcPath = ctx.fs.resolve(ctx.cwd, source);\n\n try {\n // Check source exists\n await ctx.fs.stat(srcPath);\n\n // Determine final destination path\n const finalDest = destIsDir\n ? ctx.fs.resolve(destPath, ctx.fs.basename(srcPath))\n : destPath;\n\n // Check if dest exists and noClobber\n if (noClobber) {\n const exists = await ctx.fs.exists(finalDest);\n if (exists) continue; // Skip silently\n }\n\n // Move: copy then delete\n const srcStat = await ctx.fs.stat(srcPath);\n if (srcStat.isDirectory()) {\n await moveDirectory(ctx, srcPath, finalDest, noClobber);\n } else {\n const content = await ctx.fs.readFile(srcPath);\n await ctx.fs.writeFile(finalDest, content);\n await ctx.fs.rm(srcPath);\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n await ctx.stderr.writeText(`mv: cannot stat '${source}': ${message}\\n`);\n return 1;\n }\n }\n\n return 0;\n};\n\nasync function moveDirectory(\n ctx: Parameters<Command>[0],\n src: string,\n dest: string,\n noClobber: boolean\n): Promise<void> {\n // Create destination directory\n await ctx.fs.mkdir(dest, { recursive: true });\n\n // Read source directory contents\n const entries = await ctx.fs.readdir(src);\n\n for (const entry of entries) {\n const srcPath = ctx.fs.resolve(src, entry);\n const destPath = ctx.fs.resolve(dest, entry);\n\n const stat = await ctx.fs.stat(srcPath);\n\n if (stat.isDirectory()) {\n await moveDirectory(ctx, srcPath, destPath, noClobber);\n } else {\n if (noClobber) {\n const exists = await ctx.fs.exists(destPath);\n if (exists) continue;\n }\n const content = await ctx.fs.readFile(srcPath);\n await ctx.fs.writeFile(destPath, content);\n }\n }\n\n // Remove source directory after copying\n await ctx.fs.rm(src, { recursive: true });\n}\n"
|
|
5
|
+
"import type { Command } from \"../../types.cjs\";\nimport { createFlagParser, type FlagDefinition } from \"../../utils/flag-parser.cjs\";\nimport { DEV_NULL_PATH, isDevNullPath } from \"../../fs/special-files.cjs\";\n\ninterface MvFlags {\n noClobber: boolean;\n}\n\nconst spec = {\n name: \"mv\",\n flags: [\n { short: \"n\", long: \"no-clobber\" },\n { short: \"f\", long: \"force\" },\n ] as FlagDefinition[],\n usage: \"mv [-nf] source ... dest\",\n};\n\nconst defaults: MvFlags = { noClobber: false };\n\nconst handler = (flags: MvFlags, flag: FlagDefinition) => {\n if (flag.short === \"n\") flags.noClobber = true;\n // -f is default behavior, so we don't need to do anything\n};\n\nconst parser = createFlagParser(spec, defaults, handler);\n\nexport const mv: Command = async (ctx) => {\n const result = parser.parse(ctx.args);\n\n if (result.error) {\n await parser.writeError(result.error, ctx.stderr);\n return 1;\n }\n\n const { noClobber } = result.flags;\n const paths = result.args;\n\n if (paths.length < 2) {\n await ctx.stderr.writeText(\"mv: missing destination file operand\\n\");\n return 1;\n }\n\n const sources = paths.slice(0, -1);\n const dest = paths[paths.length - 1]!;\n const destPath = ctx.fs.resolve(ctx.cwd, dest);\n\n // Check if destination is a directory\n let destIsDir = false;\n try {\n const stat = await ctx.fs.stat(destPath);\n destIsDir = stat.isDirectory();\n } catch {\n // Destination doesn't exist\n }\n\n // If multiple sources, dest must be a directory\n if (sources.length > 1 && !destIsDir) {\n await ctx.stderr.writeText(`mv: target '${dest}' is not a directory\\n`);\n return 1;\n }\n\n for (const source of sources) {\n const srcPath = ctx.fs.resolve(ctx.cwd, source);\n\n try {\n // Check source exists\n await ctx.fs.stat(srcPath);\n\n // Determine final destination path\n const finalDest = destIsDir\n ? ctx.fs.resolve(destPath, ctx.fs.basename(srcPath))\n : destPath;\n\n if (isDevNullPath(srcPath) || isDevNullPath(finalDest)) {\n await ctx.stderr.writeText(`mv: cannot move to or from special file '${DEV_NULL_PATH}'\\n`);\n return 1;\n }\n\n // Check if dest exists and noClobber\n if (noClobber) {\n const exists = await ctx.fs.exists(finalDest);\n if (exists) continue; // Skip silently\n }\n\n // Move: copy then delete\n const srcStat = await ctx.fs.stat(srcPath);\n if (srcStat.isDirectory()) {\n await moveDirectory(ctx, srcPath, finalDest, noClobber);\n } else {\n const content = await ctx.fs.readFile(srcPath);\n await ctx.fs.writeFile(finalDest, content);\n await ctx.fs.rm(srcPath);\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n await ctx.stderr.writeText(`mv: cannot stat '${source}': ${message}\\n`);\n return 1;\n }\n }\n\n return 0;\n};\n\nasync function moveDirectory(\n ctx: Parameters<Command>[0],\n src: string,\n dest: string,\n noClobber: boolean\n): Promise<void> {\n // Create destination directory\n await ctx.fs.mkdir(dest, { recursive: true });\n\n // Read source directory contents\n const entries = await ctx.fs.readdir(src);\n\n for (const entry of entries) {\n const srcPath = ctx.fs.resolve(src, entry);\n const destPath = ctx.fs.resolve(dest, entry);\n\n const stat = await ctx.fs.stat(srcPath);\n\n if (stat.isDirectory()) {\n await moveDirectory(ctx, srcPath, destPath, noClobber);\n } else {\n if (noClobber) {\n const exists = await ctx.fs.exists(destPath);\n if (exists) continue;\n }\n const content = await ctx.fs.readFile(srcPath);\n await ctx.fs.writeFile(destPath, content);\n }\n }\n\n // Remove source directory after copying\n await ctx.fs.rm(src, { recursive: true });\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACsD,IAAtD;AAMA,IAAM,OAAO;AAAA,EACX,MAAM;AAAA,EACN,OAAO;AAAA,IACL,EAAE,OAAO,KAAK,MAAM,aAAa;AAAA,IACjC,EAAE,OAAO,KAAK,MAAM,QAAQ;AAAA,EAC9B;AAAA,EACA,OAAO;AACT;AAEA,IAAM,WAAoB,EAAE,WAAW,MAAM;AAE7C,IAAM,UAAU,CAAC,OAAgB,SAAyB;AAAA,EACxD,IAAI,KAAK,UAAU;AAAA,IAAK,MAAM,YAAY;AAAA;AAI5C,IAAM,SAAS,oCAAiB,MAAM,UAAU,OAAO;AAEhD,IAAM,KAAc,OAAO,QAAQ;AAAA,EACxC,MAAM,SAAS,OAAO,MAAM,IAAI,IAAI;AAAA,EAEpC,IAAI,OAAO,OAAO;AAAA,IAChB,MAAM,OAAO,WAAW,OAAO,OAAO,IAAI,MAAM;AAAA,IAChD,OAAO;AAAA,EACT;AAAA,EAEA,QAAQ,cAAc,OAAO;AAAA,EAC7B,MAAM,QAAQ,OAAO;AAAA,EAErB,IAAI,MAAM,SAAS,GAAG;AAAA,IACpB,MAAM,IAAI,OAAO,UAAU;AAAA,CAAwC;AAAA,IACnE,OAAO;AAAA,EACT;AAAA,EAEA,MAAM,UAAU,MAAM,MAAM,GAAG,EAAE;AAAA,EACjC,MAAM,OAAO,MAAM,MAAM,SAAS;AAAA,EAClC,MAAM,WAAW,IAAI,GAAG,QAAQ,IAAI,KAAK,IAAI;AAAA,EAG7C,IAAI,YAAY;AAAA,EAChB,IAAI;AAAA,IACF,MAAM,OAAO,MAAM,IAAI,GAAG,KAAK,QAAQ;AAAA,IACvC,YAAY,KAAK,YAAY;AAAA,IAC7B,MAAM;AAAA,EAKR,IAAI,QAAQ,SAAS,KAAK,CAAC,WAAW;AAAA,IACpC,MAAM,IAAI,OAAO,UAAU,eAAe;AAAA,CAA4B;AAAA,IACtE,OAAO;AAAA,EACT;AAAA,EAEA,WAAW,UAAU,SAAS;AAAA,IAC5B,MAAM,UAAU,IAAI,GAAG,QAAQ,IAAI,KAAK,MAAM;AAAA,IAE9C,IAAI;AAAA,MAEF,MAAM,IAAI,GAAG,KAAK,OAAO;AAAA,MAGzB,MAAM,YAAY,YACd,IAAI,GAAG,QAAQ,UAAU,IAAI,GAAG,SAAS,OAAO,CAAC,IACjD;AAAA,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "C4CE62A58C3EA07364756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -51,11 +51,19 @@ var spec = {
|
|
|
51
51
|
{ short: "d" },
|
|
52
52
|
{ short: "L", takesValue: true },
|
|
53
53
|
{ short: "I", takesValue: true },
|
|
54
|
-
{ long: "dirsfirst" }
|
|
54
|
+
{ long: "dirsfirst" },
|
|
55
|
+
{ long: "prune" }
|
|
55
56
|
],
|
|
56
|
-
usage: "tree [-adI] [-L level] [-I pattern] [--dirsfirst] [directory ...]"
|
|
57
|
+
usage: "tree [-adI] [-L level] [-I pattern] [--dirsfirst] [--prune] [directory ...]"
|
|
58
|
+
};
|
|
59
|
+
var defaults = {
|
|
60
|
+
all: false,
|
|
61
|
+
directoriesOnly: false,
|
|
62
|
+
maxDepth: Infinity,
|
|
63
|
+
dirsfirst: true,
|
|
64
|
+
prune: false,
|
|
65
|
+
ignorePatterns: []
|
|
57
66
|
};
|
|
58
|
-
var defaults = { all: false, directoriesOnly: false, maxDepth: Infinity, dirsfirst: true, ignorePatterns: [] };
|
|
59
67
|
var handlerResult = {};
|
|
60
68
|
var handler = (flags, flag, value) => {
|
|
61
69
|
if (flag.short === "a")
|
|
@@ -64,6 +72,8 @@ var handler = (flags, flag, value) => {
|
|
|
64
72
|
flags.directoriesOnly = true;
|
|
65
73
|
if (flag.long === "dirsfirst")
|
|
66
74
|
flags.dirsfirst = true;
|
|
75
|
+
if (flag.long === "prune")
|
|
76
|
+
flags.prune = true;
|
|
67
77
|
if (flag.short === "I" && value) {
|
|
68
78
|
if (flags.ignorePatterns === defaults.ignorePatterns) {
|
|
69
79
|
flags.ignorePatterns = [];
|
|
@@ -93,7 +103,7 @@ var tree = async (ctx) => {
|
|
|
93
103
|
await ctx.stderr.writeText(handlerResult.error);
|
|
94
104
|
return 1;
|
|
95
105
|
}
|
|
96
|
-
const { all: showAll, directoriesOnly, maxDepth, ignorePatterns } = result.flags;
|
|
106
|
+
const { all: showAll, directoriesOnly, maxDepth, prune, ignorePatterns } = result.flags;
|
|
97
107
|
const targetPath = result.args[0] ?? ".";
|
|
98
108
|
if (maxDepth < 1) {
|
|
99
109
|
await ctx.stderr.writeText(`tree: Invalid level, must be greater than 0
|
|
@@ -118,11 +128,14 @@ var tree = async (ctx) => {
|
|
|
118
128
|
}
|
|
119
129
|
let dirCount = 0;
|
|
120
130
|
let fileCount = 0;
|
|
131
|
+
const entriesCache = new Map;
|
|
132
|
+
const visibleContentCache = new Map;
|
|
121
133
|
await ctx.stdout.writeText(targetPath + `
|
|
122
134
|
`);
|
|
123
|
-
async function
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
async function getEntries(path) {
|
|
136
|
+
const cached = entriesCache.get(path);
|
|
137
|
+
if (cached)
|
|
138
|
+
return cached;
|
|
126
139
|
let entries = await ctx.fs.readdir(path);
|
|
127
140
|
if (!showAll) {
|
|
128
141
|
entries = entries.filter((e) => !e.startsWith("."));
|
|
@@ -131,39 +144,63 @@ var tree = async (ctx) => {
|
|
|
131
144
|
entries = entries.filter((e) => !ignorePatterns.some((p) => import_match_glob.matchGlob(p, e)));
|
|
132
145
|
}
|
|
133
146
|
entries.sort();
|
|
147
|
+
const resolvedEntries = [];
|
|
148
|
+
for (const name of entries) {
|
|
149
|
+
const entryPath = ctx.fs.resolve(path, name);
|
|
150
|
+
try {
|
|
151
|
+
const entryStat = await ctx.fs.stat(entryPath);
|
|
152
|
+
resolvedEntries.push({ name, path: entryPath, isDir: entryStat.isDirectory() });
|
|
153
|
+
} catch {}
|
|
154
|
+
}
|
|
155
|
+
entriesCache.set(path, resolvedEntries);
|
|
156
|
+
return resolvedEntries;
|
|
157
|
+
}
|
|
158
|
+
async function hasVisibleContent(path) {
|
|
159
|
+
const cached = visibleContentCache.get(path);
|
|
160
|
+
if (cached !== undefined)
|
|
161
|
+
return cached;
|
|
162
|
+
const entries = await getEntries(path);
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
if (!entry.isDir) {
|
|
165
|
+
visibleContentCache.set(path, true);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
if (await hasVisibleContent(entry.path)) {
|
|
169
|
+
visibleContentCache.set(path, true);
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
visibleContentCache.set(path, false);
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
async function printTree(path, prefix, depth) {
|
|
177
|
+
if (depth > maxDepth)
|
|
178
|
+
return;
|
|
179
|
+
const entries = await getEntries(path);
|
|
134
180
|
const dirEntries = [];
|
|
135
181
|
const fileEntries = [];
|
|
136
182
|
for (const entry of entries) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (entryStat.isDirectory()) {
|
|
141
|
-
dirEntries.push(entry);
|
|
142
|
-
} else {
|
|
143
|
-
fileEntries.push(entry);
|
|
183
|
+
if (entry.isDir) {
|
|
184
|
+
if (prune && !await hasVisibleContent(entry.path)) {
|
|
185
|
+
continue;
|
|
144
186
|
}
|
|
145
|
-
|
|
187
|
+
dirEntries.push(entry);
|
|
188
|
+
} else {
|
|
189
|
+
fileEntries.push(entry);
|
|
190
|
+
}
|
|
146
191
|
}
|
|
147
192
|
const sortedEntries = directoriesOnly ? dirEntries : [...dirEntries, ...fileEntries];
|
|
148
193
|
for (let i = 0;i < sortedEntries.length; i++) {
|
|
149
194
|
const entry = sortedEntries[i];
|
|
150
195
|
const isLast = i === sortedEntries.length - 1;
|
|
151
196
|
const connector = isLast ? "└── " : "├── ";
|
|
152
|
-
|
|
153
|
-
let isDir = false;
|
|
154
|
-
try {
|
|
155
|
-
const entryStat = await ctx.fs.stat(entryPath);
|
|
156
|
-
isDir = entryStat.isDirectory();
|
|
157
|
-
} catch {
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
await ctx.stdout.writeText(prefix + connector + entry + `
|
|
197
|
+
await ctx.stdout.writeText(prefix + connector + entry.name + `
|
|
161
198
|
`);
|
|
162
|
-
if (isDir) {
|
|
199
|
+
if (entry.isDir) {
|
|
163
200
|
dirCount++;
|
|
164
201
|
if (depth < maxDepth) {
|
|
165
202
|
const newPrefix = prefix + (isLast ? " " : "│ ");
|
|
166
|
-
await printTree(
|
|
203
|
+
await printTree(entry.path, newPrefix, depth + 1);
|
|
167
204
|
}
|
|
168
205
|
} else {
|
|
169
206
|
fileCount++;
|
|
@@ -179,4 +216,4 @@ ${dirCount} ${dirWord}, ${fileCount} ${fileWord}
|
|
|
179
216
|
return 0;
|
|
180
217
|
};
|
|
181
218
|
|
|
182
|
-
//# debugId=
|
|
219
|
+
//# debugId=F4832ADA9F283A9964756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/commands/tree/tree.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { Command } from \"../../types.cjs\";\nimport { createFlagParser, type FlagDefinition } from \"../../utils/flag-parser.cjs\";\nimport { matchGlob } from \"../../utils/match-glob.cjs\";\n\ninterface TreeFlags {\n all: boolean;\n directoriesOnly: boolean;\n maxDepth: number;\n dirsfirst: boolean;\n ignorePatterns: string[];\n}\n\nconst spec = {\n name: \"tree\",\n flags: [\n { short: \"a\", long: \"all\" },\n { short: \"d\" },\n { short: \"L\", takesValue: true },\n { short: \"I\", takesValue: true },\n { long: \"dirsfirst\" },\n ] as FlagDefinition[],\n usage: \"tree [-adI] [-L level] [-I pattern] [--dirsfirst] [directory ...]\",\n};\n\nconst defaults: TreeFlags = {
|
|
5
|
+
"import type { Command } from \"../../types.cjs\";\nimport { createFlagParser, type FlagDefinition } from \"../../utils/flag-parser.cjs\";\nimport { matchGlob } from \"../../utils/match-glob.cjs\";\n\ninterface TreeFlags {\n all: boolean;\n directoriesOnly: boolean;\n maxDepth: number;\n dirsfirst: boolean;\n prune: boolean;\n ignorePatterns: string[];\n}\n\nconst spec = {\n name: \"tree\",\n flags: [\n { short: \"a\", long: \"all\" },\n { short: \"d\" },\n { short: \"L\", takesValue: true },\n { short: \"I\", takesValue: true },\n { long: \"dirsfirst\" },\n { long: \"prune\" },\n ] as FlagDefinition[],\n usage: \"tree [-adI] [-L level] [-I pattern] [--dirsfirst] [--prune] [directory ...]\",\n};\n\nconst defaults: TreeFlags = {\n all: false,\n directoriesOnly: false,\n maxDepth: Infinity,\n dirsfirst: true,\n prune: false,\n ignorePatterns: [],\n};\n\ninterface HandlerResult {\n error?: string;\n}\n\nlet handlerResult: HandlerResult = {};\n\nconst handler = (flags: TreeFlags, flag: FlagDefinition, value?: string) => {\n if (flag.short === \"a\") flags.all = true;\n if (flag.short === \"d\") flags.directoriesOnly = true;\n if (flag.long === \"dirsfirst\") flags.dirsfirst = true;\n if (flag.long === \"prune\") flags.prune = true;\n if (flag.short === \"I\" && value) {\n if (flags.ignorePatterns === defaults.ignorePatterns) {\n flags.ignorePatterns = [];\n }\n flags.ignorePatterns.push(...value.split(\"|\"));\n }\n if (flag.short === \"L\" && value) {\n const depth = parseInt(value, 10);\n if (isNaN(depth) || !/^\\d+$/.test(value)) {\n handlerResult.error = `tree: -L option requires a numeric argument\\nusage: ${spec.usage}\\n`;\n } else {\n flags.maxDepth = depth;\n }\n }\n};\n\nconst parser = createFlagParser(spec, defaults, handler);\n\nexport const tree: Command = async (ctx) => {\n // Reset handler result for each invocation\n handlerResult = {};\n\n const result = parser.parse(ctx.args);\n\n if (result.error) {\n await parser.writeError(result.error, ctx.stderr);\n return 1;\n }\n\n if (handlerResult.error) {\n await ctx.stderr.writeText(handlerResult.error);\n return 1;\n }\n\n const { all: showAll, directoriesOnly, maxDepth, prune, ignorePatterns } = result.flags;\n const targetPath = result.args[0] ?? \".\";\n\n // Validate maxDepth\n if (maxDepth < 1) {\n await ctx.stderr.writeText(\"tree: Invalid level, must be greater than 0\\n\");\n return 1;\n }\n\n const resolvedPath = ctx.fs.resolve(ctx.cwd, targetPath);\n\n // Check if path exists\n let stat;\n try {\n stat = await ctx.fs.stat(resolvedPath);\n } catch {\n await ctx.stderr.writeText(`tree: ${targetPath}: No such file or directory\\n`);\n return 1;\n }\n\n // If it's a file, just print the filename\n if (stat.isFile()) {\n await ctx.stdout.writeText(targetPath + \"\\n\\n0 directories, 1 file\\n\");\n return 0;\n }\n\n let dirCount = 0;\n let fileCount = 0;\n const entriesCache = new Map<string, { name: string; path: string; isDir: boolean }[]>();\n const visibleContentCache = new Map<string, boolean>();\n\n // Print root\n await ctx.stdout.writeText(targetPath + \"\\n\");\n\n async function getEntries(path: string): Promise<{ name: string; path: string; isDir: boolean }[]> {\n const cached = entriesCache.get(path);\n if (cached) return cached;\n\n let entries = await ctx.fs.readdir(path);\n\n // Filter hidden files unless -a\n if (!showAll) {\n entries = entries.filter((e) => !e.startsWith(\".\"));\n }\n\n // Filter by -I ignore patterns\n if (ignorePatterns.length > 0) {\n entries = entries.filter((e) => !ignorePatterns.some((p) => matchGlob(p, e)));\n }\n\n // Sort entries\n entries.sort();\n\n const resolvedEntries: { name: string; path: string; isDir: boolean }[] = [];\n\n for (const name of entries) {\n const entryPath = ctx.fs.resolve(path, name);\n try {\n const entryStat = await ctx.fs.stat(entryPath);\n resolvedEntries.push({ name, path: entryPath, isDir: entryStat.isDirectory() });\n } catch {\n // Skip entries we can't stat\n }\n }\n\n entriesCache.set(path, resolvedEntries);\n return resolvedEntries;\n }\n\n async function hasVisibleContent(path: string): Promise<boolean> {\n const cached = visibleContentCache.get(path);\n if (cached !== undefined) return cached;\n\n const entries = await getEntries(path);\n\n for (const entry of entries) {\n if (!entry.isDir) {\n visibleContentCache.set(path, true);\n return true;\n }\n\n if (await hasVisibleContent(entry.path)) {\n visibleContentCache.set(path, true);\n return true;\n }\n }\n\n visibleContentCache.set(path, false);\n return false;\n }\n\n // Recursive function to build tree\n async function printTree(path: string, prefix: string, depth: number): Promise<void> {\n if (depth > maxDepth) return;\n\n const entries = await getEntries(path);\n\n // Separate dirs and files, dirs first\n const dirEntries: { name: string; path: string; isDir: boolean }[] = [];\n const fileEntries: { name: string; path: string; isDir: boolean }[] = [];\n\n for (const entry of entries) {\n if (entry.isDir) {\n if (prune && !(await hasVisibleContent(entry.path))) {\n continue;\n }\n dirEntries.push(entry);\n } else {\n fileEntries.push(entry);\n }\n }\n\n // Combine: directories first, then files (unless directoriesOnly)\n const sortedEntries = directoriesOnly\n ? dirEntries\n : [...dirEntries, ...fileEntries];\n\n for (let i = 0; i < sortedEntries.length; i++) {\n const entry = sortedEntries[i]!;\n const isLast = i === sortedEntries.length - 1;\n const connector = isLast ? \"└── \" : \"├── \";\n\n await ctx.stdout.writeText(prefix + connector + entry.name + \"\\n\");\n\n if (entry.isDir) {\n dirCount++;\n if (depth < maxDepth) {\n const newPrefix = prefix + (isLast ? \" \" : \"│ \");\n await printTree(entry.path, newPrefix, depth + 1);\n }\n } else {\n fileCount++;\n }\n }\n }\n\n await printTree(resolvedPath, \"\", 1);\n\n // Print summary\n const dirWord = dirCount === 1 ? \"directory\" : \"directories\";\n const fileWord = fileCount === 1 ? \"file\" : \"files\";\n await ctx.stdout.writeText(`\\n${dirCount} ${dirWord}, ${fileCount} ${fileWord}\\n`);\n\n return 0;\n};\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACsD,IAAtD;AAC0B,IAA1B;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACsD,IAAtD;AAC0B,IAA1B;AAWA,IAAM,OAAO;AAAA,EACX,MAAM;AAAA,EACN,OAAO;AAAA,IACL,EAAE,OAAO,KAAK,MAAM,MAAM;AAAA,IAC1B,EAAE,OAAO,IAAI;AAAA,IACb,EAAE,OAAO,KAAK,YAAY,KAAK;AAAA,IAC/B,EAAE,OAAO,KAAK,YAAY,KAAK;AAAA,IAC/B,EAAE,MAAM,YAAY;AAAA,IACpB,EAAE,MAAM,QAAQ;AAAA,EAClB;AAAA,EACA,OAAO;AACT;AAEA,IAAM,WAAsB;AAAA,EAC1B,KAAK;AAAA,EACL,iBAAiB;AAAA,EACjB,UAAU;AAAA,EACV,WAAW;AAAA,EACX,OAAO;AAAA,EACP,gBAAgB,CAAC;AACnB;AAMA,IAAI,gBAA+B,CAAC;AAEpC,IAAM,UAAU,CAAC,OAAkB,MAAsB,UAAmB;AAAA,EAC1E,IAAI,KAAK,UAAU;AAAA,IAAK,MAAM,MAAM;AAAA,EACpC,IAAI,KAAK,UAAU;AAAA,IAAK,MAAM,kBAAkB;AAAA,EAChD,IAAI,KAAK,SAAS;AAAA,IAAa,MAAM,YAAY;AAAA,EACjD,IAAI,KAAK,SAAS;AAAA,IAAS,MAAM,QAAQ;AAAA,EACzC,IAAI,KAAK,UAAU,OAAO,OAAO;AAAA,IAC/B,IAAI,MAAM,mBAAmB,SAAS,gBAAgB;AAAA,MACpD,MAAM,iBAAiB,CAAC;AAAA,IAC1B;AAAA,IACA,MAAM,eAAe,KAAK,GAAG,MAAM,MAAM,GAAG,CAAC;AAAA,EAC/C;AAAA,EACA,IAAI,KAAK,UAAU,OAAO,OAAO;AAAA,IAC/B,MAAM,QAAQ,SAAS,OAAO,EAAE;AAAA,IAChC,IAAI,MAAM,KAAK,KAAK,CAAC,QAAQ,KAAK,KAAK,GAAG;AAAA,MACxC,cAAc,QAAQ;AAAA,SAAuD,KAAK;AAAA;AAAA,IACpF,EAAO;AAAA,MACL,MAAM,WAAW;AAAA;AAAA,EAErB;AAAA;AAGF,IAAM,SAAS,oCAAiB,MAAM,UAAU,OAAO;AAEhD,IAAM,OAAgB,OAAO,QAAQ;AAAA,EAE1C,gBAAgB,CAAC;AAAA,EAEjB,MAAM,SAAS,OAAO,MAAM,IAAI,IAAI;AAAA,EAEpC,IAAI,OAAO,OAAO;AAAA,IAChB,MAAM,OAAO,WAAW,OAAO,OAAO,IAAI,MAAM;AAAA,IAChD,OAAO;AAAA,EACT;AAAA,EAEA,IAAI,cAAc,OAAO;AAAA,IACvB,MAAM,IAAI,OAAO,UAAU,cAAc,KAAK;AAAA,IAC9C,OAAO;AAAA,EACT;AAAA,EAEA,QAAQ,KAAK,SAAS,iBAAiB,UAAU,OAAO,mBAAmB,OAAO;AAAA,EAClF,MAAM,aAAa,OAAO,KAAK,MAAM;AAAA,EAGrC,IAAI,WAAW,GAAG;AAAA,IAChB,MAAM,IAAI,OAAO,UAAU;AAAA,CAA+C;AAAA,IAC1E,OAAO;AAAA,EACT;AAAA,EAEA,MAAM,eAAe,IAAI,GAAG,QAAQ,IAAI,KAAK,UAAU;AAAA,EAGvD,IAAI;AAAA,EACJ,IAAI;AAAA,IACF,OAAO,MAAM,IAAI,GAAG,KAAK,YAAY;AAAA,IACrC,MAAM;AAAA,IACN,MAAM,IAAI,OAAO,UAAU,SAAS;AAAA,CAAyC;AAAA,IAC7E,OAAO;AAAA;AAAA,EAIT,IAAI,KAAK,OAAO,GAAG;AAAA,IACjB,MAAM,IAAI,OAAO,UAAU,aAAa;AAAA;AAAA;AAAA,CAA6B;AAAA,IACrE,OAAO;AAAA,EACT;AAAA,EAEA,IAAI,WAAW;AAAA,EACf,IAAI,YAAY;AAAA,EAChB,MAAM,eAAe,IAAI;AAAA,EACzB,MAAM,sBAAsB,IAAI;AAAA,EAGhC,MAAM,IAAI,OAAO,UAAU,aAAa;AAAA,CAAI;AAAA,EAE5C,eAAe,UAAU,CAAC,MAAyE;AAAA,IACjG,MAAM,SAAS,aAAa,IAAI,IAAI;AAAA,IACpC,IAAI;AAAA,MAAQ,OAAO;AAAA,IAEnB,IAAI,UAAU,MAAM,IAAI,GAAG,QAAQ,IAAI;AAAA,IAGvC,IAAI,CAAC,SAAS;AAAA,MACZ,UAAU,QAAQ,OAAO,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,CAAC;AAAA,IACpD;AAAA,IAGA,IAAI,eAAe,SAAS,GAAG;AAAA,MAC7B,UAAU,QAAQ,OAAO,CAAC,MAAM,CAAC,eAAe,KAAK,CAAC,MAAM,4BAAU,GAAG,CAAC,CAAC,CAAC;AAAA,IAC9E;AAAA,IAGA,QAAQ,KAAK;AAAA,IAEb,MAAM,kBAAoE,CAAC;AAAA,IAE3E,WAAW,QAAQ,SAAS;AAAA,MAC1B,MAAM,YAAY,IAAI,GAAG,QAAQ,MAAM,IAAI;AAAA,MAC3C,IAAI;AAAA,QACF,MAAM,YAAY,MAAM,IAAI,GAAG,KAAK,SAAS;AAAA,QAC7C,gBAAgB,KAAK,EAAE,MAAM,MAAM,WAAW,OAAO,UAAU,YAAY,EAAE,CAAC;AAAA,QAC9E,MAAM;AAAA,IAGV;AAAA,IAEA,aAAa,IAAI,MAAM,eAAe;AAAA,IACtC,OAAO;AAAA;AAAA,EAGT,eAAe,iBAAiB,CAAC,MAAgC;AAAA,IAC/D,MAAM,SAAS,oBAAoB,IAAI,IAAI;AAAA,IAC3C,IAAI,WAAW;AAAA,MAAW,OAAO;AAAA,IAEjC,MAAM,UAAU,MAAM,WAAW,IAAI;AAAA,IAErC,WAAW,SAAS,SAAS;AAAA,MAC3B,IAAI,CAAC,MAAM,OAAO;AAAA,QAChB,oBAAoB,IAAI,MAAM,IAAI;AAAA,QAClC,OAAO;AAAA,MACT;AAAA,MAEA,IAAI,MAAM,kBAAkB,MAAM,IAAI,GAAG;AAAA,QACvC,oBAAoB,IAAI,MAAM,IAAI;AAAA,QAClC,OAAO;AAAA,MACT;AAAA,IACF;AAAA,IAEA,oBAAoB,IAAI,MAAM,KAAK;AAAA,IACnC,OAAO;AAAA;AAAA,EAIT,eAAe,SAAS,CAAC,MAAc,QAAgB,OAA8B;AAAA,IACnF,IAAI,QAAQ;AAAA,MAAU;AAAA,IAEtB,MAAM,UAAU,MAAM,WAAW,IAAI;AAAA,IAGrC,MAAM,aAA+D,CAAC;AAAA,IACtE,MAAM,cAAgE,CAAC;AAAA,IAEvE,WAAW,SAAS,SAAS;AAAA,MAC3B,IAAI,MAAM,OAAO;AAAA,QACf,IAAI,SAAS,CAAE,MAAM,kBAAkB,MAAM,IAAI,GAAI;AAAA,UACnD;AAAA,QACF;AAAA,QACA,WAAW,KAAK,KAAK;AAAA,MACvB,EAAO;AAAA,QACL,YAAY,KAAK,KAAK;AAAA;AAAA,IAE1B;AAAA,IAGA,MAAM,gBAAgB,kBAClB,aACA,CAAC,GAAG,YAAY,GAAG,WAAW;AAAA,IAElC,SAAS,IAAI,EAAG,IAAI,cAAc,QAAQ,KAAK;AAAA,MAC7C,MAAM,QAAQ,cAAc;AAAA,MAC5B,MAAM,SAAS,MAAM,cAAc,SAAS;AAAA,MAC5C,MAAM,YAAY,SAAS,SAAQ;AAAA,MAEnC,MAAM,IAAI,OAAO,UAAU,SAAS,YAAY,MAAM,OAAO;AAAA,CAAI;AAAA,MAEjE,IAAI,MAAM,OAAO;AAAA,QACf;AAAA,QACA,IAAI,QAAQ,UAAU;AAAA,UACpB,MAAM,YAAY,UAAU,SAAS,SAAS;AAAA,UAC9C,MAAM,UAAU,MAAM,MAAM,WAAW,QAAQ,CAAC;AAAA,QAClD;AAAA,MACF,EAAO;AAAA,QACL;AAAA;AAAA,IAEJ;AAAA;AAAA,EAGF,MAAM,UAAU,cAAc,IAAI,CAAC;AAAA,EAGnC,MAAM,UAAU,aAAa,IAAI,cAAc;AAAA,EAC/C,MAAM,WAAW,cAAc,IAAI,SAAS;AAAA,EAC5C,MAAM,IAAI,OAAO,UAAU;AAAA,EAAK,YAAY,YAAY,aAAa;AAAA,CAAY;AAAA,EAEjF,OAAO;AAAA;",
|
|
8
|
+
"debugId": "F4832ADA9F283A9964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -68,21 +68,29 @@ __export(exports_memfs_adapter, {
|
|
|
68
68
|
module.exports = __toCommonJS(exports_memfs_adapter);
|
|
69
69
|
var pathModule = __toESM(require("path"));
|
|
70
70
|
var import_glob = require("../utils/glob.cjs");
|
|
71
|
+
var import_special_files = require("./special-files.cjs");
|
|
71
72
|
function createVirtualFS(memfs) {
|
|
72
73
|
const { promises: fs } = memfs;
|
|
73
74
|
return {
|
|
74
75
|
readFile: async (path, encoding) => {
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
const specialContent = import_special_files.readSpecialFile(path, encoding);
|
|
77
|
+
if (specialContent !== undefined)
|
|
78
|
+
return specialContent;
|
|
77
79
|
const content = await fs.readFile(path);
|
|
78
80
|
const buf = Buffer.from(content);
|
|
79
81
|
return encoding ? buf.toString(encoding) : buf;
|
|
80
82
|
},
|
|
81
83
|
async readdir(path) {
|
|
84
|
+
const specialError = import_special_files.getSpecialPathError(path, "readdir");
|
|
85
|
+
if (specialError)
|
|
86
|
+
throw specialError;
|
|
82
87
|
const entries = await fs.readdir(path);
|
|
83
88
|
return entries.map(String);
|
|
84
89
|
},
|
|
85
90
|
async stat(path) {
|
|
91
|
+
const specialStat = import_special_files.statSpecialFile(path);
|
|
92
|
+
if (specialStat)
|
|
93
|
+
return specialStat;
|
|
86
94
|
const stats = await fs.stat(path);
|
|
87
95
|
return {
|
|
88
96
|
isFile: () => stats.isFile(),
|
|
@@ -92,6 +100,9 @@ function createVirtualFS(memfs) {
|
|
|
92
100
|
};
|
|
93
101
|
},
|
|
94
102
|
async exists(path) {
|
|
103
|
+
const specialExists = import_special_files.existsSpecialFile(path);
|
|
104
|
+
if (specialExists !== undefined)
|
|
105
|
+
return specialExists;
|
|
95
106
|
try {
|
|
96
107
|
await fs.stat(path);
|
|
97
108
|
return true;
|
|
@@ -100,15 +111,27 @@ function createVirtualFS(memfs) {
|
|
|
100
111
|
}
|
|
101
112
|
},
|
|
102
113
|
async writeFile(path, data) {
|
|
114
|
+
if (import_special_files.discardsSpecialFileWrites(path)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
103
117
|
await fs.writeFile(path, data);
|
|
104
118
|
},
|
|
105
119
|
async appendFile(path, data) {
|
|
120
|
+
if (import_special_files.discardsSpecialFileWrites(path)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
106
123
|
await fs.appendFile(path, data);
|
|
107
124
|
},
|
|
108
125
|
async mkdir(path, opts) {
|
|
126
|
+
const specialError = import_special_files.getSpecialPathError(path, "mkdir");
|
|
127
|
+
if (specialError)
|
|
128
|
+
throw specialError;
|
|
109
129
|
await fs.mkdir(path, opts);
|
|
110
130
|
},
|
|
111
131
|
async rm(path, opts) {
|
|
132
|
+
const specialError = import_special_files.getSpecialPathError(path, "rm");
|
|
133
|
+
if (specialError)
|
|
134
|
+
throw specialError;
|
|
112
135
|
try {
|
|
113
136
|
const stats = await fs.stat(path);
|
|
114
137
|
if (stats.isDirectory()) {
|
|
@@ -141,4 +164,4 @@ function createVirtualFS(memfs) {
|
|
|
141
164
|
};
|
|
142
165
|
}
|
|
143
166
|
|
|
144
|
-
//# debugId=
|
|
167
|
+
//# debugId=F25975C12E3475B564756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/fs/memfs-adapter.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { IFs } from \"memfs\";\nimport type { VirtualFS, FileStat } from \"../types.cjs\";\nimport * as pathModule from \"path\";\nimport { globVirtualFS } from \"../utils/glob.cjs\";\n\nexport function createVirtualFS(memfs: IFs): VirtualFS {\n const { promises: fs } = memfs;\n\n return {\n readFile: (async (path: string, encoding?: BufferEncoding): Promise<Buffer | string> => {\n
|
|
5
|
+
"import type { IFs } from \"memfs\";\nimport type { VirtualFS, FileStat } from \"../types.cjs\";\nimport * as pathModule from \"path\";\nimport { globVirtualFS } from \"../utils/glob.cjs\";\nimport {\n discardsSpecialFileWrites,\n existsSpecialFile,\n getSpecialPathError,\n readSpecialFile,\n statSpecialFile,\n} from \"./special-files.cjs\";\n\nexport function createVirtualFS(memfs: IFs): VirtualFS {\n const { promises: fs } = memfs;\n\n return {\n readFile: (async (path: string, encoding?: BufferEncoding): Promise<Buffer | string> => {\n const specialContent = readSpecialFile(path, encoding);\n if (specialContent !== undefined) return specialContent;\n const content = await fs.readFile(path);\n const buf = Buffer.from(content);\n return encoding ? buf.toString(encoding) : buf;\n }) as VirtualFS[\"readFile\"],\n\n async readdir(path: string): Promise<string[]> {\n const specialError = getSpecialPathError(path, \"readdir\");\n if (specialError) throw specialError;\n const entries = await fs.readdir(path);\n return entries.map(String);\n },\n\n async stat(path: string): Promise<FileStat> {\n const specialStat = statSpecialFile(path);\n if (specialStat) return specialStat;\n const stats = await fs.stat(path);\n return {\n isFile: () => stats.isFile(),\n isDirectory: () => stats.isDirectory(),\n size: Number(stats.size),\n mtime: new Date(stats.mtime),\n };\n },\n\n async exists(path: string): Promise<boolean> {\n const specialExists = existsSpecialFile(path);\n if (specialExists !== undefined) return specialExists;\n try {\n await fs.stat(path);\n return true;\n } catch {\n return false;\n }\n },\n\n async writeFile(path: string, data: Buffer | string): Promise<void> {\n if (discardsSpecialFileWrites(path)) {\n return;\n }\n await fs.writeFile(path, data);\n },\n\n async appendFile(path: string, data: Buffer | string): Promise<void> {\n if (discardsSpecialFileWrites(path)) {\n return;\n }\n await fs.appendFile(path, data);\n },\n\n async mkdir(path: string, opts?: { recursive?: boolean }): Promise<void> {\n const specialError = getSpecialPathError(path, \"mkdir\");\n if (specialError) throw specialError;\n await fs.mkdir(path, opts);\n },\n\n async rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\n const specialError = getSpecialPathError(path, \"rm\");\n if (specialError) throw specialError;\n try {\n const stats = await fs.stat(path);\n if (stats.isDirectory()) {\n await fs.rmdir(path, { recursive: opts?.recursive });\n } else {\n await fs.unlink(path);\n }\n } catch (err) {\n if (!opts?.force) throw err;\n }\n },\n\n resolve(...paths: string[]): string {\n return pathModule.resolve(...paths);\n },\n\n dirname(path: string): string {\n return pathModule.dirname(path);\n },\n\n basename(path: string): string {\n return pathModule.basename(path);\n },\n\n async glob(pattern: string, opts?: { cwd?: string }): Promise<string[]> {\n const cwd = opts?.cwd ?? \"/\";\n return globVirtualFS(\n {\n readdir: (filePath: string) => this.readdir(filePath),\n stat: (filePath: string) => this.stat(filePath),\n resolve: (...paths: string[]) => this.resolve(...paths),\n },\n pattern,\n { cwd }\n );\n },\n };\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAE4B,IAA5B;AAC8B,IAA9B;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "F25975C12E3475B564756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -69,6 +69,7 @@ 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 import_glob = require("../utils/glob.cjs");
|
|
72
|
+
var import_special_files = require("./special-files.cjs");
|
|
72
73
|
var defaultFS = { promises: nodeFs };
|
|
73
74
|
var nodePathOps = {
|
|
74
75
|
separator: path.sep,
|
|
@@ -167,6 +168,10 @@ class FileSystem {
|
|
|
167
168
|
return resolved;
|
|
168
169
|
}
|
|
169
170
|
async readFile(filePath, encoding) {
|
|
171
|
+
const specialContent = import_special_files.readSpecialFile(filePath, encoding);
|
|
172
|
+
if (specialContent !== undefined) {
|
|
173
|
+
return specialContent;
|
|
174
|
+
}
|
|
170
175
|
this.checkPermission(filePath, "read");
|
|
171
176
|
const realPath = this.resolveSafePath(filePath);
|
|
172
177
|
const content = await this.underlyingFs.promises.readFile(realPath);
|
|
@@ -174,12 +179,20 @@ class FileSystem {
|
|
|
174
179
|
return encoding ? buf.toString(encoding) : buf;
|
|
175
180
|
}
|
|
176
181
|
async readdir(dirPath) {
|
|
182
|
+
const specialError = import_special_files.getSpecialPathError(dirPath, "readdir");
|
|
183
|
+
if (specialError) {
|
|
184
|
+
throw specialError;
|
|
185
|
+
}
|
|
177
186
|
this.checkPermission(dirPath, "read");
|
|
178
187
|
const realPath = this.resolveSafePath(dirPath);
|
|
179
188
|
const entries = await this.underlyingFs.promises.readdir(realPath);
|
|
180
189
|
return entries.map(String);
|
|
181
190
|
}
|
|
182
191
|
async stat(filePath) {
|
|
192
|
+
const specialStat = import_special_files.statSpecialFile(filePath);
|
|
193
|
+
if (specialStat) {
|
|
194
|
+
return specialStat;
|
|
195
|
+
}
|
|
183
196
|
this.checkPermission(filePath, "read");
|
|
184
197
|
const realPath = this.resolveSafePath(filePath);
|
|
185
198
|
const stats = await this.underlyingFs.promises.stat(realPath);
|
|
@@ -191,6 +204,10 @@ class FileSystem {
|
|
|
191
204
|
};
|
|
192
205
|
}
|
|
193
206
|
async exists(filePath) {
|
|
207
|
+
const specialExists = import_special_files.existsSpecialFile(filePath);
|
|
208
|
+
if (specialExists !== undefined) {
|
|
209
|
+
return specialExists;
|
|
210
|
+
}
|
|
194
211
|
try {
|
|
195
212
|
this.checkPermission(filePath, "read");
|
|
196
213
|
const realPath = this.resolveSafePath(filePath);
|
|
@@ -201,21 +218,35 @@ class FileSystem {
|
|
|
201
218
|
}
|
|
202
219
|
}
|
|
203
220
|
async writeFile(filePath, data) {
|
|
221
|
+
if (import_special_files.discardsSpecialFileWrites(filePath)) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
204
224
|
this.checkPermission(filePath, "write");
|
|
205
225
|
const realPath = this.resolveSafePath(filePath);
|
|
206
226
|
await this.underlyingFs.promises.writeFile(realPath, data);
|
|
207
227
|
}
|
|
208
228
|
async appendFile(filePath, data) {
|
|
229
|
+
if (import_special_files.discardsSpecialFileWrites(filePath)) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
209
232
|
this.checkPermission(filePath, "write");
|
|
210
233
|
const realPath = this.resolveSafePath(filePath);
|
|
211
234
|
await this.underlyingFs.promises.appendFile(realPath, data);
|
|
212
235
|
}
|
|
213
236
|
async mkdir(dirPath, opts) {
|
|
237
|
+
const specialError = import_special_files.getSpecialPathError(dirPath, "mkdir");
|
|
238
|
+
if (specialError) {
|
|
239
|
+
throw specialError;
|
|
240
|
+
}
|
|
214
241
|
this.checkPermission(dirPath, "write");
|
|
215
242
|
const realPath = this.resolveSafePath(dirPath);
|
|
216
243
|
await this.underlyingFs.promises.mkdir(realPath, opts);
|
|
217
244
|
}
|
|
218
245
|
async rm(filePath, opts) {
|
|
246
|
+
const specialError = import_special_files.getSpecialPathError(filePath, "rm");
|
|
247
|
+
if (specialError) {
|
|
248
|
+
throw specialError;
|
|
249
|
+
}
|
|
219
250
|
this.checkPermission(filePath, "write");
|
|
220
251
|
const realPath = this.resolveSafePath(filePath);
|
|
221
252
|
await this.underlyingFs.promises.rm(realPath, opts);
|
|
@@ -236,4 +267,4 @@ class FileSystem {
|
|
|
236
267
|
}
|
|
237
268
|
}
|
|
238
269
|
|
|
239
|
-
//# 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\";\nimport { globVirtualFS } from \"../utils/glob.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 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"
|
|
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;AAE8B,IAA9B;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "58FDFA4CFF76BA5364756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|