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
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`) |
|
|
@@ -616,6 +616,32 @@ interface VirtualFS {
|
|
|
616
616
|
}
|
|
617
617
|
```
|
|
618
618
|
|
|
619
|
+
### `globVirtualFS` Helper
|
|
620
|
+
|
|
621
|
+
If you're implementing a custom `VirtualFS`, especially a composite or mounted filesystem, you can reuse `globVirtualFS()` instead of writing glob traversal yourself:
|
|
622
|
+
|
|
623
|
+
```ts
|
|
624
|
+
import { globVirtualFS, type VirtualFS } from "shell-dsl";
|
|
625
|
+
|
|
626
|
+
class CompositeFileSystem implements VirtualFS {
|
|
627
|
+
// ... implement readFile/readdir/stat/etc.
|
|
628
|
+
|
|
629
|
+
async glob(pattern: string, opts?: { cwd?: string }): Promise<string[]> {
|
|
630
|
+
return globVirtualFS(this, pattern, opts);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
`globVirtualFS()` walks the visible virtual tree using only `readdir()`, `stat()`, and `resolve()`, so it works correctly for filesystems that mount different host directories under one virtual namespace.
|
|
636
|
+
|
|
637
|
+
It supports the same shell-style patterns used by the interpreter:
|
|
638
|
+
|
|
639
|
+
- `*.txt` for segment wildcards
|
|
640
|
+
- `**/*.ts` for recursive matches
|
|
641
|
+
- `file-?.md` for single-character matches
|
|
642
|
+
- `{a,b}.json` for brace expansion
|
|
643
|
+
- `[ab].txt` for character classes
|
|
644
|
+
|
|
619
645
|
## Real Filesystem Access
|
|
620
646
|
|
|
621
647
|
For scenarios where you need to access the real filesystem with sandboxing, use `FileSystem` or `ReadOnlyFileSystem`:
|
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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACsD,IAAtD;AAC6C,IAA7C;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,MAEJ,IAAI,mCAAc,OAAO,KAAK,mCAAc,SAAS,GAAG;AAAA,QACtD,MAAM,IAAI,OAAO,UAAU,4CAA4C;AAAA,CAAkB;AAAA,QACzF,OAAO;AAAA,MACT;AAAA,MAGA,IAAI,WAAW;AAAA,QACb,MAAM,SAAS,MAAM,IAAI,GAAG,OAAO,SAAS;AAAA,QAC5C,IAAI;AAAA,UAAQ;AAAA,MACd;AAAA,MAGA,MAAM,UAAU,MAAM,IAAI,GAAG,KAAK,OAAO;AAAA,MACzC,IAAI,QAAQ,YAAY,GAAG;AAAA,QACzB,MAAM,cAAc,KAAK,SAAS,WAAW,SAAS;AAAA,MACxD,EAAO;AAAA,QACL,MAAM,UAAU,MAAM,IAAI,GAAG,SAAS,OAAO;AAAA,QAC7C,MAAM,IAAI,GAAG,UAAU,WAAW,OAAO;AAAA,QACzC,MAAM,IAAI,GAAG,GAAG,OAAO;AAAA;AAAA,MAEzB,OAAO,KAAK;AAAA,MACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MAC/D,MAAM,IAAI,OAAO,UAAU,oBAAoB,YAAY;AAAA,CAAW;AAAA,MACtE,OAAO;AAAA;AAAA,EAEX;AAAA,EAEA,OAAO;AAAA;AAGT,eAAe,aAAa,CAC1B,KACA,KACA,MACA,WACe;AAAA,EAEf,MAAM,IAAI,GAAG,MAAM,MAAM,EAAE,WAAW,KAAK,CAAC;AAAA,EAG5C,MAAM,UAAU,MAAM,IAAI,GAAG,QAAQ,GAAG;AAAA,EAExC,WAAW,SAAS,SAAS;AAAA,IAC3B,MAAM,UAAU,IAAI,GAAG,QAAQ,KAAK,KAAK;AAAA,IACzC,MAAM,WAAW,IAAI,GAAG,QAAQ,MAAM,KAAK;AAAA,IAE3C,MAAM,OAAO,MAAM,IAAI,GAAG,KAAK,OAAO;AAAA,IAEtC,IAAI,KAAK,YAAY,GAAG;AAAA,MACtB,MAAM,cAAc,KAAK,SAAS,UAAU,SAAS;AAAA,IACvD,EAAO;AAAA,MACL,IAAI,WAAW;AAAA,QACb,MAAM,SAAS,MAAM,IAAI,GAAG,OAAO,QAAQ;AAAA,QAC3C,IAAI;AAAA,UAAQ;AAAA,MACd;AAAA,MACA,MAAM,UAAU,MAAM,IAAI,GAAG,SAAS,OAAO;AAAA,MAC7C,MAAM,IAAI,GAAG,UAAU,UAAU,OAAO;AAAA;AAAA,EAE5C;AAAA,EAGA,MAAM,IAAI,GAAG,GAAG,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA;",
|
|
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": "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",
|
|
8
|
+
"debugId": "F4832ADA9F283A9964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -67,21 +67,30 @@ __export(exports_memfs_adapter, {
|
|
|
67
67
|
});
|
|
68
68
|
module.exports = __toCommonJS(exports_memfs_adapter);
|
|
69
69
|
var pathModule = __toESM(require("path"));
|
|
70
|
+
var import_glob = require("../utils/glob.cjs");
|
|
71
|
+
var import_special_files = require("./special-files.cjs");
|
|
70
72
|
function createVirtualFS(memfs) {
|
|
71
73
|
const { promises: fs } = memfs;
|
|
72
74
|
return {
|
|
73
75
|
readFile: async (path, encoding) => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
const specialContent = import_special_files.readSpecialFile(path, encoding);
|
|
77
|
+
if (specialContent !== undefined)
|
|
78
|
+
return specialContent;
|
|
76
79
|
const content = await fs.readFile(path);
|
|
77
80
|
const buf = Buffer.from(content);
|
|
78
81
|
return encoding ? buf.toString(encoding) : buf;
|
|
79
82
|
},
|
|
80
83
|
async readdir(path) {
|
|
84
|
+
const specialError = import_special_files.getSpecialPathError(path, "readdir");
|
|
85
|
+
if (specialError)
|
|
86
|
+
throw specialError;
|
|
81
87
|
const entries = await fs.readdir(path);
|
|
82
88
|
return entries.map(String);
|
|
83
89
|
},
|
|
84
90
|
async stat(path) {
|
|
91
|
+
const specialStat = import_special_files.statSpecialFile(path);
|
|
92
|
+
if (specialStat)
|
|
93
|
+
return specialStat;
|
|
85
94
|
const stats = await fs.stat(path);
|
|
86
95
|
return {
|
|
87
96
|
isFile: () => stats.isFile(),
|
|
@@ -91,6 +100,9 @@ function createVirtualFS(memfs) {
|
|
|
91
100
|
};
|
|
92
101
|
},
|
|
93
102
|
async exists(path) {
|
|
103
|
+
const specialExists = import_special_files.existsSpecialFile(path);
|
|
104
|
+
if (specialExists !== undefined)
|
|
105
|
+
return specialExists;
|
|
94
106
|
try {
|
|
95
107
|
await fs.stat(path);
|
|
96
108
|
return true;
|
|
@@ -99,15 +111,27 @@ function createVirtualFS(memfs) {
|
|
|
99
111
|
}
|
|
100
112
|
},
|
|
101
113
|
async writeFile(path, data) {
|
|
114
|
+
if (import_special_files.discardsSpecialFileWrites(path)) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
102
117
|
await fs.writeFile(path, data);
|
|
103
118
|
},
|
|
104
119
|
async appendFile(path, data) {
|
|
120
|
+
if (import_special_files.discardsSpecialFileWrites(path)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
105
123
|
await fs.appendFile(path, data);
|
|
106
124
|
},
|
|
107
125
|
async mkdir(path, opts) {
|
|
126
|
+
const specialError = import_special_files.getSpecialPathError(path, "mkdir");
|
|
127
|
+
if (specialError)
|
|
128
|
+
throw specialError;
|
|
108
129
|
await fs.mkdir(path, opts);
|
|
109
130
|
},
|
|
110
131
|
async rm(path, opts) {
|
|
132
|
+
const specialError = import_special_files.getSpecialPathError(path, "rm");
|
|
133
|
+
if (specialError)
|
|
134
|
+
throw specialError;
|
|
111
135
|
try {
|
|
112
136
|
const stats = await fs.stat(path);
|
|
113
137
|
if (stats.isDirectory()) {
|
|
@@ -131,115 +155,13 @@ function createVirtualFS(memfs) {
|
|
|
131
155
|
},
|
|
132
156
|
async glob(pattern, opts) {
|
|
133
157
|
const cwd = opts?.cwd ?? "/";
|
|
134
|
-
return
|
|
158
|
+
return import_glob.globVirtualFS({
|
|
159
|
+
readdir: (filePath) => this.readdir(filePath),
|
|
160
|
+
stat: (filePath) => this.stat(filePath),
|
|
161
|
+
resolve: (...paths) => this.resolve(...paths)
|
|
162
|
+
}, pattern, { cwd });
|
|
135
163
|
}
|
|
136
164
|
};
|
|
137
165
|
}
|
|
138
|
-
async function expandGlob(memfs, pattern, cwd) {
|
|
139
|
-
const { promises: fs } = memfs;
|
|
140
|
-
const patterns = expandBraces(pattern);
|
|
141
|
-
const allMatches = [];
|
|
142
|
-
for (const pat of patterns) {
|
|
143
|
-
const matches = await matchPattern(fs, pat, cwd);
|
|
144
|
-
allMatches.push(...matches);
|
|
145
|
-
}
|
|
146
|
-
return [...new Set(allMatches)].sort();
|
|
147
|
-
}
|
|
148
|
-
function expandBraces(pattern) {
|
|
149
|
-
const braceMatch = pattern.match(/\{([^{}]+)\}/);
|
|
150
|
-
if (!braceMatch)
|
|
151
|
-
return [pattern];
|
|
152
|
-
const before = pattern.slice(0, braceMatch.index);
|
|
153
|
-
const after = pattern.slice(braceMatch.index + braceMatch[0].length);
|
|
154
|
-
const options = braceMatch[1].split(",");
|
|
155
|
-
const results = [];
|
|
156
|
-
for (const opt of options) {
|
|
157
|
-
const expanded = expandBraces(before + opt + after);
|
|
158
|
-
results.push(...expanded);
|
|
159
|
-
}
|
|
160
|
-
return results;
|
|
161
|
-
}
|
|
162
|
-
async function matchPattern(fs, pattern, cwd) {
|
|
163
|
-
const parts = pattern.split("/").filter((p) => p !== "");
|
|
164
|
-
const isAbsolute = pattern.startsWith("/");
|
|
165
|
-
const startDir = isAbsolute ? "/" : cwd;
|
|
166
|
-
return matchParts(fs, parts, startDir, isAbsolute);
|
|
167
|
-
}
|
|
168
|
-
async function matchParts(fs, parts, currentPath, isAbsolute) {
|
|
169
|
-
if (parts.length === 0) {
|
|
170
|
-
return [currentPath];
|
|
171
|
-
}
|
|
172
|
-
const [part, ...rest] = parts;
|
|
173
|
-
if (part === "**") {
|
|
174
|
-
const results = [];
|
|
175
|
-
const withoutStar = await matchParts(fs, rest, currentPath, isAbsolute);
|
|
176
|
-
results.push(...withoutStar);
|
|
177
|
-
try {
|
|
178
|
-
const entries = await fs.readdir(currentPath);
|
|
179
|
-
for (const entry of entries) {
|
|
180
|
-
const entryPath = pathModule.join(currentPath, String(entry));
|
|
181
|
-
try {
|
|
182
|
-
const stat = await fs.stat(entryPath);
|
|
183
|
-
if (stat.isDirectory()) {
|
|
184
|
-
const subMatches = await matchParts(fs, parts, entryPath, isAbsolute);
|
|
185
|
-
results.push(...subMatches);
|
|
186
|
-
}
|
|
187
|
-
} catch {}
|
|
188
|
-
}
|
|
189
|
-
} catch {}
|
|
190
|
-
return results;
|
|
191
|
-
}
|
|
192
|
-
const regex = globToRegex(part);
|
|
193
|
-
try {
|
|
194
|
-
const entries = await fs.readdir(currentPath);
|
|
195
|
-
const results = [];
|
|
196
|
-
for (const entry of entries) {
|
|
197
|
-
const entryName = String(entry);
|
|
198
|
-
if (regex.test(entryName)) {
|
|
199
|
-
const entryPath = pathModule.join(currentPath, entryName);
|
|
200
|
-
if (rest.length === 0) {
|
|
201
|
-
results.push(entryPath);
|
|
202
|
-
} else {
|
|
203
|
-
try {
|
|
204
|
-
const stat = await fs.stat(entryPath);
|
|
205
|
-
if (stat.isDirectory()) {
|
|
206
|
-
const subMatches = await matchParts(fs, rest, entryPath, isAbsolute);
|
|
207
|
-
results.push(...subMatches);
|
|
208
|
-
}
|
|
209
|
-
} catch {}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return results;
|
|
214
|
-
} catch {
|
|
215
|
-
return [];
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
function globToRegex(pattern) {
|
|
219
|
-
let regex = "^";
|
|
220
|
-
for (let i = 0;i < pattern.length; i++) {
|
|
221
|
-
const char = pattern[i];
|
|
222
|
-
if (char === "*") {
|
|
223
|
-
regex += "[^/]*";
|
|
224
|
-
} else if (char === "?") {
|
|
225
|
-
regex += "[^/]";
|
|
226
|
-
} else if (char === "[") {
|
|
227
|
-
let j = i + 1;
|
|
228
|
-
let classContent = "";
|
|
229
|
-
while (j < pattern.length && pattern[j] !== "]") {
|
|
230
|
-
classContent += pattern[j];
|
|
231
|
-
j++;
|
|
232
|
-
}
|
|
233
|
-
regex += `[${classContent}]`;
|
|
234
|
-
i = j;
|
|
235
|
-
} else if (".+^${}()|\\".includes(char)) {
|
|
236
|
-
regex += "\\" + char;
|
|
237
|
-
} else {
|
|
238
|
-
regex += char;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
regex += "$";
|
|
242
|
-
return new RegExp(regex);
|
|
243
|
-
}
|
|
244
166
|
|
|
245
|
-
//# 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\";\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;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "F25975C12E3475B564756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|