shell-dsl 0.0.30 → 0.0.31
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 +26 -0
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/fs/memfs-adapter.cjs +7 -108
- package/dist/cjs/src/fs/memfs-adapter.cjs.map +3 -3
- package/dist/cjs/src/fs/real-fs.cjs +3 -112
- package/dist/cjs/src/fs/real-fs.cjs.map +3 -3
- package/dist/cjs/src/index.cjs +2 -1
- package/dist/cjs/src/index.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/fs/memfs-adapter.mjs +7 -108
- package/dist/mjs/src/fs/memfs-adapter.mjs.map +3 -3
- package/dist/mjs/src/fs/real-fs.mjs +3 -112
- package/dist/mjs/src/fs/real-fs.mjs.map +3 -3
- package/dist/mjs/src/index.mjs +3 -2
- package/dist/mjs/src/index.mjs.map +2 -2
- 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/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
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/utils/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"export { escape, escapeForInterpolation } from \"./escape.cjs\";\nexport { expandEscapes } from \"./expand-escapes.cjs\";\nexport {\n createFlagParser,\n type FlagDefinition,\n type CommandSpec,\n type FlagError,\n type ParseResult,\n type FlagParser,\n} from \"./flag-parser.cjs\";\nexport { matchGlob } from \"./match-glob.cjs\";\n"
|
|
5
|
+
"export { escape, escapeForInterpolation } from \"./escape.cjs\";\nexport { expandEscapes } from \"./expand-escapes.cjs\";\nexport { globVirtualFS, type GlobVirtualFS, type GlobOptions } from \"./glob.cjs\";\nexport {\n createFlagParser,\n type FlagDefinition,\n type CommandSpec,\n type FlagError,\n type ParseResult,\n type FlagParser,\n} from \"./flag-parser.cjs\";\nexport { matchGlob } from \"./match-glob.cjs\";\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAA+C,IAA/C;AAC8B,IAA9B;AACoE,IAApE;AAQO,IAPP;AAQ0B,IAA1B;",
|
|
8
|
+
"debugId": "4731049858D6D56664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/mjs/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// src/fs/memfs-adapter.ts
|
|
2
2
|
import * as pathModule from "path";
|
|
3
|
+
import { globVirtualFS } from "../utils/glob.mjs";
|
|
3
4
|
function createVirtualFS(memfs) {
|
|
4
5
|
const { promises: fs } = memfs;
|
|
5
6
|
return {
|
|
@@ -64,118 +65,16 @@ function createVirtualFS(memfs) {
|
|
|
64
65
|
},
|
|
65
66
|
async glob(pattern, opts) {
|
|
66
67
|
const cwd = opts?.cwd ?? "/";
|
|
67
|
-
return
|
|
68
|
+
return globVirtualFS({
|
|
69
|
+
readdir: (filePath) => this.readdir(filePath),
|
|
70
|
+
stat: (filePath) => this.stat(filePath),
|
|
71
|
+
resolve: (...paths) => this.resolve(...paths)
|
|
72
|
+
}, pattern, { cwd });
|
|
68
73
|
}
|
|
69
74
|
};
|
|
70
75
|
}
|
|
71
|
-
async function expandGlob(memfs, pattern, cwd) {
|
|
72
|
-
const { promises: fs } = memfs;
|
|
73
|
-
const patterns = expandBraces(pattern);
|
|
74
|
-
const allMatches = [];
|
|
75
|
-
for (const pat of patterns) {
|
|
76
|
-
const matches = await matchPattern(fs, pat, cwd);
|
|
77
|
-
allMatches.push(...matches);
|
|
78
|
-
}
|
|
79
|
-
return [...new Set(allMatches)].sort();
|
|
80
|
-
}
|
|
81
|
-
function expandBraces(pattern) {
|
|
82
|
-
const braceMatch = pattern.match(/\{([^{}]+)\}/);
|
|
83
|
-
if (!braceMatch)
|
|
84
|
-
return [pattern];
|
|
85
|
-
const before = pattern.slice(0, braceMatch.index);
|
|
86
|
-
const after = pattern.slice(braceMatch.index + braceMatch[0].length);
|
|
87
|
-
const options = braceMatch[1].split(",");
|
|
88
|
-
const results = [];
|
|
89
|
-
for (const opt of options) {
|
|
90
|
-
const expanded = expandBraces(before + opt + after);
|
|
91
|
-
results.push(...expanded);
|
|
92
|
-
}
|
|
93
|
-
return results;
|
|
94
|
-
}
|
|
95
|
-
async function matchPattern(fs, pattern, cwd) {
|
|
96
|
-
const parts = pattern.split("/").filter((p) => p !== "");
|
|
97
|
-
const isAbsolute = pattern.startsWith("/");
|
|
98
|
-
const startDir = isAbsolute ? "/" : cwd;
|
|
99
|
-
return matchParts(fs, parts, startDir, isAbsolute);
|
|
100
|
-
}
|
|
101
|
-
async function matchParts(fs, parts, currentPath, isAbsolute) {
|
|
102
|
-
if (parts.length === 0) {
|
|
103
|
-
return [currentPath];
|
|
104
|
-
}
|
|
105
|
-
const [part, ...rest] = parts;
|
|
106
|
-
if (part === "**") {
|
|
107
|
-
const results = [];
|
|
108
|
-
const withoutStar = await matchParts(fs, rest, currentPath, isAbsolute);
|
|
109
|
-
results.push(...withoutStar);
|
|
110
|
-
try {
|
|
111
|
-
const entries = await fs.readdir(currentPath);
|
|
112
|
-
for (const entry of entries) {
|
|
113
|
-
const entryPath = pathModule.join(currentPath, String(entry));
|
|
114
|
-
try {
|
|
115
|
-
const stat = await fs.stat(entryPath);
|
|
116
|
-
if (stat.isDirectory()) {
|
|
117
|
-
const subMatches = await matchParts(fs, parts, entryPath, isAbsolute);
|
|
118
|
-
results.push(...subMatches);
|
|
119
|
-
}
|
|
120
|
-
} catch {}
|
|
121
|
-
}
|
|
122
|
-
} catch {}
|
|
123
|
-
return results;
|
|
124
|
-
}
|
|
125
|
-
const regex = globToRegex(part);
|
|
126
|
-
try {
|
|
127
|
-
const entries = await fs.readdir(currentPath);
|
|
128
|
-
const results = [];
|
|
129
|
-
for (const entry of entries) {
|
|
130
|
-
const entryName = String(entry);
|
|
131
|
-
if (regex.test(entryName)) {
|
|
132
|
-
const entryPath = pathModule.join(currentPath, entryName);
|
|
133
|
-
if (rest.length === 0) {
|
|
134
|
-
results.push(entryPath);
|
|
135
|
-
} else {
|
|
136
|
-
try {
|
|
137
|
-
const stat = await fs.stat(entryPath);
|
|
138
|
-
if (stat.isDirectory()) {
|
|
139
|
-
const subMatches = await matchParts(fs, rest, entryPath, isAbsolute);
|
|
140
|
-
results.push(...subMatches);
|
|
141
|
-
}
|
|
142
|
-
} catch {}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return results;
|
|
147
|
-
} catch {
|
|
148
|
-
return [];
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
function globToRegex(pattern) {
|
|
152
|
-
let regex = "^";
|
|
153
|
-
for (let i = 0;i < pattern.length; i++) {
|
|
154
|
-
const char = pattern[i];
|
|
155
|
-
if (char === "*") {
|
|
156
|
-
regex += "[^/]*";
|
|
157
|
-
} else if (char === "?") {
|
|
158
|
-
regex += "[^/]";
|
|
159
|
-
} else if (char === "[") {
|
|
160
|
-
let j = i + 1;
|
|
161
|
-
let classContent = "";
|
|
162
|
-
while (j < pattern.length && pattern[j] !== "]") {
|
|
163
|
-
classContent += pattern[j];
|
|
164
|
-
j++;
|
|
165
|
-
}
|
|
166
|
-
regex += `[${classContent}]`;
|
|
167
|
-
i = j;
|
|
168
|
-
} else if (".+^${}()|\\".includes(char)) {
|
|
169
|
-
regex += "\\" + char;
|
|
170
|
-
} else {
|
|
171
|
-
regex += char;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
regex += "$";
|
|
175
|
-
return new RegExp(regex);
|
|
176
|
-
}
|
|
177
76
|
export {
|
|
178
77
|
createVirtualFS
|
|
179
78
|
};
|
|
180
79
|
|
|
181
|
-
//# debugId=
|
|
80
|
+
//# debugId=EAE27F968F9F56F664756E2164756E21
|
|
@@ -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.mjs\";\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 if (path === \"/dev/null\") return encoding ? \"\" : Buffer.alloc(0);\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 entries = await fs.readdir(path);\n return entries.map(String);\n },\n\n async stat(path: string): Promise<FileStat> {\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 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 await fs.writeFile(path, data);\n },\n\n async appendFile(path: string, data: Buffer | string): Promise<void> {\n await fs.appendFile(path, data);\n },\n\n async mkdir(path: string, opts?: { recursive?: boolean }): Promise<void> {\n await fs.mkdir(path, opts);\n },\n\n async rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\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
|
|
5
|
+
"import type { IFs } from \"memfs\";\nimport type { VirtualFS, FileStat } from \"../types.mjs\";\nimport * as pathModule from \"path\";\nimport { globVirtualFS } from \"../utils/glob.mjs\";\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 if (path === \"/dev/null\") return encoding ? \"\" : Buffer.alloc(0);\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 entries = await fs.readdir(path);\n return entries.map(String);\n },\n\n async stat(path: string): Promise<FileStat> {\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 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 await fs.writeFile(path, data);\n },\n\n async appendFile(path: string, data: Buffer | string): Promise<void> {\n await fs.appendFile(path, data);\n },\n\n async mkdir(path: string, opts?: { recursive?: boolean }): Promise<void> {\n await fs.mkdir(path, opts);\n },\n\n async rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\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": ";AAEA;AAEO,SAAS,eAAe,CAAC,OAAuB;AAAA,EACrD,QAAQ,UAAU,OAAO;AAAA,EAEzB,OAAO;AAAA,IACL,UAAW,OAAO,MAAc,aAAwD;AAAA,MACtF,IAAI,SAAS;AAAA,QAAa,OAAO,WAAW,KAAK,OAAO,MAAM,CAAC;AAAA,MAC/D,MAAM,UAAU,MAAM,GAAG,SAAS,IAAI;AAAA,MACtC,MAAM,MAAM,OAAO,KAAK,OAAO;AAAA,MAC/B,OAAO,WAAW,IAAI,SAAS,QAAQ,IAAI;AAAA;AAAA,SAGvC,QAAO,CAAC,MAAiC;AAAA,MAC7C,MAAM,UAAU,MAAM,GAAG,QAAQ,IAAI;AAAA,MACrC,OAAO,QAAQ,IAAI,MAAM;AAAA;AAAA,SAGrB,KAAI,CAAC,MAAiC;AAAA,MAC1C,MAAM,QAAQ,MAAM,GAAG,KAAK,IAAI;AAAA,MAChC,OAAO;AAAA,QACL,QAAQ,MAAM,MAAM,OAAO;AAAA,QAC3B,aAAa,MAAM,MAAM,YAAY;AAAA,QACrC,MAAM,OAAO,MAAM,IAAI;AAAA,QACvB,OAAO,IAAI,KAAK,MAAM,KAAK;AAAA,MAC7B;AAAA;AAAA,SAGI,OAAM,CAAC,MAAgC;AAAA,MAC3C,IAAI;AAAA,QACF,MAAM,GAAG,KAAK,IAAI;AAAA,QAClB,OAAO;AAAA,QACP,MAAM;AAAA,QACN,OAAO;AAAA;AAAA;AAAA,SAIL,UAAS,CAAC,MAAc,MAAsC;AAAA,MAClE,MAAM,GAAG,UAAU,MAAM,IAAI;AAAA;AAAA,SAGzB,WAAU,CAAC,MAAc,MAAsC;AAAA,MACnE,MAAM,GAAG,WAAW,MAAM,IAAI;AAAA;AAAA,SAG1B,MAAK,CAAC,MAAc,MAA+C;AAAA,MACvE,MAAM,GAAG,MAAM,MAAM,IAAI;AAAA;AAAA,SAGrB,GAAE,CAAC,MAAc,MAAgE;AAAA,MACrF,IAAI;AAAA,QACF,MAAM,QAAQ,MAAM,GAAG,KAAK,IAAI;AAAA,QAChC,IAAI,MAAM,YAAY,GAAG;AAAA,UACvB,MAAM,GAAG,MAAM,MAAM,EAAE,WAAW,MAAM,UAAU,CAAC;AAAA,QACrD,EAAO;AAAA,UACL,MAAM,GAAG,OAAO,IAAI;AAAA;AAAA,QAEtB,OAAO,KAAK;AAAA,QACZ,IAAI,CAAC,MAAM;AAAA,UAAO,MAAM;AAAA;AAAA;AAAA,IAI5B,OAAO,IAAI,OAAyB;AAAA,MAClC,OAAkB,mBAAQ,GAAG,KAAK;AAAA;AAAA,IAGpC,OAAO,CAAC,MAAsB;AAAA,MAC5B,OAAkB,mBAAQ,IAAI;AAAA;AAAA,IAGhC,QAAQ,CAAC,MAAsB;AAAA,MAC7B,OAAkB,oBAAS,IAAI;AAAA;AAAA,SAG3B,KAAI,CAAC,SAAiB,MAA4C;AAAA,MACtE,MAAM,MAAM,MAAM,OAAO;AAAA,MACzB,OAAO,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";AAEA;AACA;AAEO,SAAS,eAAe,CAAC,OAAuB;AAAA,EACrD,QAAQ,UAAU,OAAO;AAAA,EAEzB,OAAO;AAAA,IACL,UAAW,OAAO,MAAc,aAAwD;AAAA,MACtF,IAAI,SAAS;AAAA,QAAa,OAAO,WAAW,KAAK,OAAO,MAAM,CAAC;AAAA,MAC/D,MAAM,UAAU,MAAM,GAAG,SAAS,IAAI;AAAA,MACtC,MAAM,MAAM,OAAO,KAAK,OAAO;AAAA,MAC/B,OAAO,WAAW,IAAI,SAAS,QAAQ,IAAI;AAAA;AAAA,SAGvC,QAAO,CAAC,MAAiC;AAAA,MAC7C,MAAM,UAAU,MAAM,GAAG,QAAQ,IAAI;AAAA,MACrC,OAAO,QAAQ,IAAI,MAAM;AAAA;AAAA,SAGrB,KAAI,CAAC,MAAiC;AAAA,MAC1C,MAAM,QAAQ,MAAM,GAAG,KAAK,IAAI;AAAA,MAChC,OAAO;AAAA,QACL,QAAQ,MAAM,MAAM,OAAO;AAAA,QAC3B,aAAa,MAAM,MAAM,YAAY;AAAA,QACrC,MAAM,OAAO,MAAM,IAAI;AAAA,QACvB,OAAO,IAAI,KAAK,MAAM,KAAK;AAAA,MAC7B;AAAA;AAAA,SAGI,OAAM,CAAC,MAAgC;AAAA,MAC3C,IAAI;AAAA,QACF,MAAM,GAAG,KAAK,IAAI;AAAA,QAClB,OAAO;AAAA,QACP,MAAM;AAAA,QACN,OAAO;AAAA;AAAA;AAAA,SAIL,UAAS,CAAC,MAAc,MAAsC;AAAA,MAClE,MAAM,GAAG,UAAU,MAAM,IAAI;AAAA;AAAA,SAGzB,WAAU,CAAC,MAAc,MAAsC;AAAA,MACnE,MAAM,GAAG,WAAW,MAAM,IAAI;AAAA;AAAA,SAG1B,MAAK,CAAC,MAAc,MAA+C;AAAA,MACvE,MAAM,GAAG,MAAM,MAAM,IAAI;AAAA;AAAA,SAGrB,GAAE,CAAC,MAAc,MAAgE;AAAA,MACrF,IAAI;AAAA,QACF,MAAM,QAAQ,MAAM,GAAG,KAAK,IAAI;AAAA,QAChC,IAAI,MAAM,YAAY,GAAG;AAAA,UACvB,MAAM,GAAG,MAAM,MAAM,EAAE,WAAW,MAAM,UAAU,CAAC;AAAA,QACrD,EAAO;AAAA,UACL,MAAM,GAAG,OAAO,IAAI;AAAA;AAAA,QAEtB,OAAO,KAAK;AAAA,QACZ,IAAI,CAAC,MAAM;AAAA,UAAO,MAAM;AAAA;AAAA;AAAA,IAI5B,OAAO,IAAI,OAAyB;AAAA,MAClC,OAAkB,mBAAQ,GAAG,KAAK;AAAA;AAAA,IAGpC,OAAO,CAAC,MAAsB;AAAA,MAC5B,OAAkB,mBAAQ,IAAI;AAAA;AAAA,IAGhC,QAAQ,CAAC,MAAsB;AAAA,MAC7B,OAAkB,oBAAS,IAAI;AAAA;AAAA,SAG3B,KAAI,CAAC,SAAiB,MAA4C;AAAA,MACtE,MAAM,MAAM,MAAM,OAAO;AAAA,MACzB,OAAO,cACL;AAAA,QACE,SAAS,CAAC,aAAqB,KAAK,QAAQ,QAAQ;AAAA,QACpD,MAAM,CAAC,aAAqB,KAAK,KAAK,QAAQ;AAAA,QAC9C,SAAS,IAAI,UAAoB,KAAK,QAAQ,GAAG,KAAK;AAAA,MACxD,GACA,SACA,EAAE,IAAI,CACR;AAAA;AAAA,EAEJ;AAAA;",
|
|
8
|
+
"debugId": "EAE27F968F9F56F664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/fs/real-fs.ts
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as nodeFs from "node:fs/promises";
|
|
4
|
+
import { globVirtualFS } from "../utils/glob.mjs";
|
|
4
5
|
var defaultFS = { promises: nodeFs };
|
|
5
6
|
var nodePathOps = {
|
|
6
7
|
separator: path.sep,
|
|
@@ -164,121 +165,11 @@ class FileSystem {
|
|
|
164
165
|
async glob(pattern, opts) {
|
|
165
166
|
const cwd = opts?.cwd ?? "/";
|
|
166
167
|
this.checkPermission(cwd, "read");
|
|
167
|
-
|
|
168
|
-
return matches.filter((p) => this.getPermission(p) !== "excluded").sort();
|
|
169
|
-
}
|
|
170
|
-
async expandGlob(pattern, cwd) {
|
|
171
|
-
const patterns = this.expandBraces(pattern);
|
|
172
|
-
const allMatches = [];
|
|
173
|
-
for (const pat of patterns) {
|
|
174
|
-
const matches = await this.matchPattern(pat, cwd);
|
|
175
|
-
allMatches.push(...matches);
|
|
176
|
-
}
|
|
177
|
-
return [...new Set(allMatches)].sort();
|
|
178
|
-
}
|
|
179
|
-
expandBraces(pattern) {
|
|
180
|
-
const braceMatch = pattern.match(/\{([^{}]+)\}/);
|
|
181
|
-
if (!braceMatch)
|
|
182
|
-
return [pattern];
|
|
183
|
-
const before = pattern.slice(0, braceMatch.index);
|
|
184
|
-
const after = pattern.slice(braceMatch.index + braceMatch[0].length);
|
|
185
|
-
const options = braceMatch[1].split(",");
|
|
186
|
-
const results = [];
|
|
187
|
-
for (const opt of options) {
|
|
188
|
-
const expanded = this.expandBraces(before + opt + after);
|
|
189
|
-
results.push(...expanded);
|
|
190
|
-
}
|
|
191
|
-
return results;
|
|
192
|
-
}
|
|
193
|
-
async matchPattern(pattern, cwd) {
|
|
194
|
-
const parts = pattern.split("/").filter((p) => p !== "");
|
|
195
|
-
const isAbsolute2 = pattern.startsWith("/");
|
|
196
|
-
const startDir = isAbsolute2 ? "/" : cwd;
|
|
197
|
-
return this.matchParts(parts, startDir);
|
|
198
|
-
}
|
|
199
|
-
async matchParts(parts, currentPath) {
|
|
200
|
-
if (parts.length === 0) {
|
|
201
|
-
return [currentPath];
|
|
202
|
-
}
|
|
203
|
-
const [part, ...rest] = parts;
|
|
204
|
-
if (part === "**") {
|
|
205
|
-
const results = [];
|
|
206
|
-
const withoutStar = await this.matchParts(rest, currentPath);
|
|
207
|
-
results.push(...withoutStar);
|
|
208
|
-
try {
|
|
209
|
-
const realPath = this.resolveSafePath(currentPath);
|
|
210
|
-
const entries = await this.underlyingFs.promises.readdir(realPath);
|
|
211
|
-
for (const entry of entries) {
|
|
212
|
-
const entryPath = this.pathOps.join(currentPath, String(entry));
|
|
213
|
-
try {
|
|
214
|
-
const entryRealPath = this.resolveSafePath(entryPath);
|
|
215
|
-
const stat = await this.underlyingFs.promises.stat(entryRealPath);
|
|
216
|
-
if (stat.isDirectory()) {
|
|
217
|
-
const subMatches = await this.matchParts(parts, entryPath);
|
|
218
|
-
results.push(...subMatches);
|
|
219
|
-
}
|
|
220
|
-
} catch {}
|
|
221
|
-
}
|
|
222
|
-
} catch {}
|
|
223
|
-
return results;
|
|
224
|
-
}
|
|
225
|
-
const regex = this.globToRegex(part);
|
|
226
|
-
try {
|
|
227
|
-
const realPath = this.resolveSafePath(currentPath);
|
|
228
|
-
const entries = await this.underlyingFs.promises.readdir(realPath);
|
|
229
|
-
const results = [];
|
|
230
|
-
for (const entry of entries) {
|
|
231
|
-
const entryName = String(entry);
|
|
232
|
-
if (regex.test(entryName)) {
|
|
233
|
-
const entryPath = this.pathOps.join(currentPath, entryName);
|
|
234
|
-
if (rest.length === 0) {
|
|
235
|
-
results.push(entryPath);
|
|
236
|
-
} else {
|
|
237
|
-
try {
|
|
238
|
-
const entryRealPath = this.resolveSafePath(entryPath);
|
|
239
|
-
const stat = await this.underlyingFs.promises.stat(entryRealPath);
|
|
240
|
-
if (stat.isDirectory()) {
|
|
241
|
-
const subMatches = await this.matchParts(rest, entryPath);
|
|
242
|
-
results.push(...subMatches);
|
|
243
|
-
}
|
|
244
|
-
} catch {}
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return results;
|
|
249
|
-
} catch {
|
|
250
|
-
return [];
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
globToRegex(pattern) {
|
|
254
|
-
let regex = "^";
|
|
255
|
-
for (let i = 0;i < pattern.length; i++) {
|
|
256
|
-
const char = pattern[i];
|
|
257
|
-
if (char === "*") {
|
|
258
|
-
regex += "[^/]*";
|
|
259
|
-
} else if (char === "?") {
|
|
260
|
-
regex += "[^/]";
|
|
261
|
-
} else if (char === "[") {
|
|
262
|
-
let j = i + 1;
|
|
263
|
-
let classContent = "";
|
|
264
|
-
while (j < pattern.length && pattern[j] !== "]") {
|
|
265
|
-
classContent += pattern[j];
|
|
266
|
-
j++;
|
|
267
|
-
}
|
|
268
|
-
regex += `[${classContent}]`;
|
|
269
|
-
i = j;
|
|
270
|
-
} else if (".+^${}()|\\".includes(char)) {
|
|
271
|
-
regex += "\\" + char;
|
|
272
|
-
} else {
|
|
273
|
-
regex += char;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
regex += "$";
|
|
277
|
-
return new RegExp(regex);
|
|
168
|
+
return globVirtualFS(this, pattern, { cwd });
|
|
278
169
|
}
|
|
279
170
|
}
|
|
280
171
|
export {
|
|
281
172
|
FileSystem
|
|
282
173
|
};
|
|
283
174
|
|
|
284
|
-
//# debugId=
|
|
175
|
+
//# debugId=093147B23E1E8F5A64756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/fs/real-fs.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import * as path from \"path\";\nimport * as nodeFs from \"node:fs/promises\";\nimport type { VirtualFS, FileStat } from \"../types.mjs\";\n\nexport type Permission = \"read-write\" | \"read-only\" | \"excluded\";\nexport type PermissionRules = Record<string, Permission>;\nexport interface PathOps {\n readonly separator: string;\n resolve(...paths: string[]): string;\n normalize(path: string): string;\n join(...paths: string[]): string;\n relative(from: string, to: string): string;\n isAbsolute(path: string): boolean;\n dirname(path: string): string;\n basename(path: string): string;\n}\n\n// Minimal interface for the underlying fs (compatible with node:fs and memfs)\nexport interface UnderlyingFS {\n pathOps?: PathOps;\n promises: {\n readFile(path: string): Promise<Buffer | Uint8Array | string>;\n readdir(path: string): Promise<string[]>;\n stat(path: string): Promise<{\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n }>;\n writeFile(path: string, data: Buffer | string): Promise<void>;\n appendFile(path: string, data: Buffer | string): Promise<void>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<string | undefined | void>;\n rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void>;\n };\n}\n\n// Default: use real node:fs\nconst defaultFS: UnderlyingFS = { promises: nodeFs };\nconst nodePathOps: PathOps = {\n separator: path.sep,\n resolve: (...paths) => path.resolve(...paths),\n normalize: (filePath) => path.normalize(filePath),\n join: (...paths) => path.join(...paths),\n relative: (from, to) => path.relative(from, to),\n isAbsolute: (filePath) => path.isAbsolute(filePath),\n dirname: (filePath) => path.dirname(filePath),\n basename: (filePath) => path.basename(filePath),\n};\n\ninterface CompiledRule {\n pattern: string;\n permission: Permission;\n specificity: number;\n}\n\nexport class FileSystem implements VirtualFS {\n private readonly mountBase: string | null;\n private readonly rules: CompiledRule[];\n private readonly pathOps: PathOps;\n protected readonly underlyingFs: UnderlyingFS;\n\n constructor(mountPath?: string, permissions?: PermissionRules, fs?: UnderlyingFS) {\n const underlyingFs = fs ?? defaultFS;\n this.pathOps = underlyingFs.pathOps ?? nodePathOps;\n this.mountBase = mountPath ? this.pathOps.resolve(mountPath) : null;\n this.rules = this.compileRules(permissions ?? {});\n this.underlyingFs = underlyingFs;\n }\n\n private compileRules(permissions: PermissionRules): CompiledRule[] {\n return Object.entries(permissions)\n .map(([pattern, permission]) => ({\n pattern,\n permission,\n specificity: this.calculateSpecificity(pattern),\n }))\n .sort((a, b) => b.specificity - a.specificity); // highest first\n }\n\n private calculateSpecificity(pattern: string): number {\n const segments = pattern.split(\"/\").filter(Boolean);\n let score = segments.length * 1000; // segment count is primary\n\n for (const seg of segments) {\n if (seg === \"**\") score += 0;\n else if (seg.includes(\"*\")) score += 1;\n else score += 10; // literal segment\n }\n return score;\n }\n\n public getPermission(virtualPath: string): Permission {\n const normalized = virtualPath.replace(/^\\/+/, \"\"); // strip leading slashes\n\n for (const rule of this.rules) {\n if (this.matchGlob(rule.pattern, normalized)) {\n return rule.permission;\n }\n }\n return \"read-write\"; // default\n }\n\n private matchGlob(pattern: string, filePath: string): boolean {\n // Convert glob to regex\n // ** matches any path segments, * matches within segment\n const regex = pattern\n .split(\"/\")\n .map((seg) => {\n if (seg === \"**\") return \".*\";\n return seg.replace(/\\*/g, \"[^/]*\").replace(/\\?/g, \"[^/]\");\n })\n .join(\"/\");\n return new RegExp(`^${regex}$`).test(filePath);\n }\n\n public checkPermission(virtualPath: string, operation: \"read\" | \"write\"): void {\n const perm = this.getPermission(virtualPath);\n\n if (perm === \"excluded\") {\n throw new Error(`Access denied: \"${virtualPath}\" is excluded`);\n }\n if (operation === \"write\" && perm === \"read-only\") {\n throw new Error(`Access denied: \"${virtualPath}\" is read-only`);\n }\n }\n\n private resolveSafePath(virtualPath: string): string {\n if (this.mountBase === null) {\n return this.pathOps.resolve(virtualPath);\n }\n\n // Check for path traversal by tracking depth\n const segments = virtualPath.split(\"/\").filter(Boolean);\n let depth = 0;\n for (const seg of segments) {\n if (seg === \"..\") {\n depth--;\n if (depth < 0) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n } else if (seg !== \".\") {\n depth++;\n }\n }\n\n const normalized = this.pathOps.normalize(virtualPath);\n const relativePath = normalized.startsWith(\"/\") ? normalized.slice(1) : normalized;\n const realPath = this.pathOps.join(this.mountBase, relativePath);\n const resolved = this.pathOps.resolve(realPath);\n\n // Double-check containment (defense in depth), including root mounts.\n const relativeFromMount = this.pathOps.relative(this.mountBase, resolved);\n const escapesMount =\n relativeFromMount === \"..\" ||\n relativeFromMount.startsWith(`..${this.pathOps.separator}`) ||\n this.pathOps.isAbsolute(relativeFromMount);\n if (escapesMount) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n\n return resolved;\n }\n\n // Read operations\n async readFile(filePath: string): Promise<Buffer>;\n async readFile(filePath: string, encoding: BufferEncoding): Promise<string>;\n async readFile(filePath: string, encoding?: BufferEncoding): Promise<Buffer | string> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const content = await this.underlyingFs.promises.readFile(realPath);\n const buf = Buffer.from(content);\n return encoding ? buf.toString(encoding) : buf;\n }\n\n async readdir(dirPath: string): Promise<string[]> {\n this.checkPermission(dirPath, \"read\");\n const realPath = this.resolveSafePath(dirPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n return entries.map(String);\n }\n\n async stat(filePath: string): Promise<FileStat> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const stats = await this.underlyingFs.promises.stat(realPath);\n return {\n isFile: () => stats.isFile(),\n isDirectory: () => stats.isDirectory(),\n size: stats.size,\n mtime: stats.mtime,\n };\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.stat(realPath);\n return true;\n } catch {\n return false;\n }\n }\n\n // Write operations\n async writeFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.writeFile(realPath, data);\n }\n\n async appendFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.appendFile(realPath, data);\n }\n\n async mkdir(dirPath: string, opts?: { recursive?: boolean }): Promise<void> {\n this.checkPermission(dirPath, \"write\");\n const realPath = this.resolveSafePath(dirPath);\n await this.underlyingFs.promises.mkdir(realPath, opts);\n }\n\n async rm(filePath: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.rm(realPath, opts);\n }\n\n // Path utilities (no permission check needed)\n resolve(...paths: string[]): string {\n return this.pathOps.resolve(\"/\", ...paths);\n }\n\n dirname(filePath: string): string {\n return this.pathOps.dirname(filePath);\n }\n\n basename(filePath: string): string {\n return this.pathOps.basename(filePath);\n }\n\n // Glob with permission filtering\n async glob(pattern: string, opts?: { cwd?: string }): Promise<string[]> {\n const cwd = opts?.cwd ?? \"/\";\n this.checkPermission(cwd, \"read\");\n\n const matches = await this.expandGlob(pattern, cwd);\n\n // Filter out excluded paths\n return matches.filter((p) => this.getPermission(p) !== \"excluded\").sort();\n }\n\n // Glob expansion (similar to memfs-adapter implementation)\n private async expandGlob(pattern: string, cwd: string): Promise<string[]> {\n // Handle brace expansion first\n const patterns = this.expandBraces(pattern);\n const allMatches: string[] = [];\n\n for (const pat of patterns) {\n const matches = await this.matchPattern(pat, cwd);\n allMatches.push(...matches);\n }\n\n // Remove duplicates and sort\n return [...new Set(allMatches)].sort();\n }\n\n private expandBraces(pattern: string): string[] {\n const braceMatch = pattern.match(/\\{([^{}]+)\\}/);\n if (!braceMatch) return [pattern];\n\n const before = pattern.slice(0, braceMatch.index);\n const after = pattern.slice(braceMatch.index! + braceMatch[0].length);\n const options = braceMatch[1]!.split(\",\");\n\n const results: string[] = [];\n for (const opt of options) {\n const expanded = this.expandBraces(before + opt + after);\n results.push(...expanded);\n }\n return results;\n }\n\n private async matchPattern(pattern: string, cwd: string): Promise<string[]> {\n const parts = pattern.split(\"/\").filter((p) => p !== \"\");\n const isAbsolute = pattern.startsWith(\"/\");\n const startDir = isAbsolute ? \"/\" : cwd;\n\n return this.matchParts(parts, startDir);\n }\n\n private async matchParts(parts: string[], currentPath: string): Promise<string[]> {\n if (parts.length === 0) {\n return [currentPath];\n }\n\n const [part, ...rest] = parts;\n\n // Handle ** (recursive glob)\n if (part === \"**\") {\n const results: string[] = [];\n\n // Match current directory\n const withoutStar = await this.matchParts(rest, currentPath);\n results.push(...withoutStar);\n\n // Recurse into subdirectories\n try {\n const realPath = this.resolveSafePath(currentPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n for (const entry of entries) {\n const entryPath = this.pathOps.join(currentPath, String(entry));\n try {\n const entryRealPath = this.resolveSafePath(entryPath);\n const stat = await this.underlyingFs.promises.stat(entryRealPath);\n if (stat.isDirectory()) {\n const subMatches = await this.matchParts(parts, entryPath);\n results.push(...subMatches);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n } catch {\n // Directory not readable\n }\n\n return results;\n }\n\n // Handle regular glob patterns\n const regex = this.globToRegex(part!);\n\n try {\n const realPath = this.resolveSafePath(currentPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n const results: string[] = [];\n\n for (const entry of entries) {\n const entryName = String(entry);\n if (regex.test(entryName)) {\n const entryPath = this.pathOps.join(currentPath, entryName);\n if (rest.length === 0) {\n results.push(entryPath);\n } else {\n try {\n const entryRealPath = this.resolveSafePath(entryPath);\n const stat = await this.underlyingFs.promises.stat(entryRealPath);\n if (stat.isDirectory()) {\n const subMatches = await this.matchParts(rest, entryPath);\n results.push(...subMatches);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n }\n }\n\n return results;\n } catch {\n return [];\n }\n }\n\n private globToRegex(pattern: string): RegExp {\n let regex = \"^\";\n for (let i = 0; i < pattern.length; i++) {\n const char = pattern[i]!;\n if (char === \"*\") {\n regex += \"[^/]*\";\n } else if (char === \"?\") {\n regex += \"[^/]\";\n } else if (char === \"[\") {\n // Character class\n let j = i + 1;\n let classContent = \"\";\n while (j < pattern.length && pattern[j] !== \"]\") {\n classContent += pattern[j];\n j++;\n }\n regex += `[${classContent}]`;\n i = j;\n } else if (\".+^${}()|\\\\\".includes(char)) {\n regex += \"\\\\\" + char;\n } else {\n regex += char;\n }\n }\n regex += \"$\";\n return new RegExp(regex);\n }\n}\n"
|
|
5
|
+
"import * as path from \"path\";\nimport * as nodeFs from \"node:fs/promises\";\nimport type { VirtualFS, FileStat } from \"../types.mjs\";\nimport { globVirtualFS } from \"../utils/glob.mjs\";\n\nexport type Permission = \"read-write\" | \"read-only\" | \"excluded\";\nexport type PermissionRules = Record<string, Permission>;\nexport interface PathOps {\n readonly separator: string;\n resolve(...paths: string[]): string;\n normalize(path: string): string;\n join(...paths: string[]): string;\n relative(from: string, to: string): string;\n isAbsolute(path: string): boolean;\n dirname(path: string): string;\n basename(path: string): string;\n}\n\n// Minimal interface for the underlying fs (compatible with node:fs and memfs)\nexport interface UnderlyingFS {\n pathOps?: PathOps;\n promises: {\n readFile(path: string): Promise<Buffer | Uint8Array | string>;\n readdir(path: string): Promise<string[]>;\n stat(path: string): Promise<{\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n }>;\n writeFile(path: string, data: Buffer | string): Promise<void>;\n appendFile(path: string, data: Buffer | string): Promise<void>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<string | undefined | void>;\n rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void>;\n };\n}\n\n// Default: use real node:fs\nconst defaultFS: UnderlyingFS = { promises: nodeFs };\nconst nodePathOps: PathOps = {\n separator: path.sep,\n resolve: (...paths) => path.resolve(...paths),\n normalize: (filePath) => path.normalize(filePath),\n join: (...paths) => path.join(...paths),\n relative: (from, to) => path.relative(from, to),\n isAbsolute: (filePath) => path.isAbsolute(filePath),\n dirname: (filePath) => path.dirname(filePath),\n basename: (filePath) => path.basename(filePath),\n};\n\ninterface CompiledRule {\n pattern: string;\n permission: Permission;\n specificity: number;\n}\n\nexport class FileSystem implements VirtualFS {\n private readonly mountBase: string | null;\n private readonly rules: CompiledRule[];\n private readonly pathOps: PathOps;\n protected readonly underlyingFs: UnderlyingFS;\n\n constructor(mountPath?: string, permissions?: PermissionRules, fs?: UnderlyingFS) {\n const underlyingFs = fs ?? defaultFS;\n this.pathOps = underlyingFs.pathOps ?? nodePathOps;\n this.mountBase = mountPath ? this.pathOps.resolve(mountPath) : null;\n this.rules = this.compileRules(permissions ?? {});\n this.underlyingFs = underlyingFs;\n }\n\n private compileRules(permissions: PermissionRules): CompiledRule[] {\n return Object.entries(permissions)\n .map(([pattern, permission]) => ({\n pattern,\n permission,\n specificity: this.calculateSpecificity(pattern),\n }))\n .sort((a, b) => b.specificity - a.specificity); // highest first\n }\n\n private calculateSpecificity(pattern: string): number {\n const segments = pattern.split(\"/\").filter(Boolean);\n let score = segments.length * 1000; // segment count is primary\n\n for (const seg of segments) {\n if (seg === \"**\") score += 0;\n else if (seg.includes(\"*\")) score += 1;\n else score += 10; // literal segment\n }\n return score;\n }\n\n public getPermission(virtualPath: string): Permission {\n const normalized = virtualPath.replace(/^\\/+/, \"\"); // strip leading slashes\n\n for (const rule of this.rules) {\n if (this.matchGlob(rule.pattern, normalized)) {\n return rule.permission;\n }\n }\n return \"read-write\"; // default\n }\n\n private matchGlob(pattern: string, filePath: string): boolean {\n // Convert glob to regex\n // ** matches any path segments, * matches within segment\n const regex = pattern\n .split(\"/\")\n .map((seg) => {\n if (seg === \"**\") return \".*\";\n return seg.replace(/\\*/g, \"[^/]*\").replace(/\\?/g, \"[^/]\");\n })\n .join(\"/\");\n return new RegExp(`^${regex}$`).test(filePath);\n }\n\n public checkPermission(virtualPath: string, operation: \"read\" | \"write\"): void {\n const perm = this.getPermission(virtualPath);\n\n if (perm === \"excluded\") {\n throw new Error(`Access denied: \"${virtualPath}\" is excluded`);\n }\n if (operation === \"write\" && perm === \"read-only\") {\n throw new Error(`Access denied: \"${virtualPath}\" is read-only`);\n }\n }\n\n private resolveSafePath(virtualPath: string): string {\n if (this.mountBase === null) {\n return this.pathOps.resolve(virtualPath);\n }\n\n // Check for path traversal by tracking depth\n const segments = virtualPath.split(\"/\").filter(Boolean);\n let depth = 0;\n for (const seg of segments) {\n if (seg === \"..\") {\n depth--;\n if (depth < 0) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n } else if (seg !== \".\") {\n depth++;\n }\n }\n\n const normalized = this.pathOps.normalize(virtualPath);\n const relativePath = normalized.startsWith(\"/\") ? normalized.slice(1) : normalized;\n const realPath = this.pathOps.join(this.mountBase, relativePath);\n const resolved = this.pathOps.resolve(realPath);\n\n // Double-check containment (defense in depth), including root mounts.\n const relativeFromMount = this.pathOps.relative(this.mountBase, resolved);\n const escapesMount =\n relativeFromMount === \"..\" ||\n relativeFromMount.startsWith(`..${this.pathOps.separator}`) ||\n this.pathOps.isAbsolute(relativeFromMount);\n if (escapesMount) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n\n return resolved;\n }\n\n // Read operations\n async readFile(filePath: string): Promise<Buffer>;\n async readFile(filePath: string, encoding: BufferEncoding): Promise<string>;\n async readFile(filePath: string, encoding?: BufferEncoding): Promise<Buffer | string> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const content = await this.underlyingFs.promises.readFile(realPath);\n const buf = Buffer.from(content);\n return encoding ? buf.toString(encoding) : buf;\n }\n\n async readdir(dirPath: string): Promise<string[]> {\n this.checkPermission(dirPath, \"read\");\n const realPath = this.resolveSafePath(dirPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n return entries.map(String);\n }\n\n async stat(filePath: string): Promise<FileStat> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const stats = await this.underlyingFs.promises.stat(realPath);\n return {\n isFile: () => stats.isFile(),\n isDirectory: () => stats.isDirectory(),\n size: stats.size,\n mtime: stats.mtime,\n };\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.stat(realPath);\n return true;\n } catch {\n return false;\n }\n }\n\n // Write operations\n async writeFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.writeFile(realPath, data);\n }\n\n async appendFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.appendFile(realPath, data);\n }\n\n async mkdir(dirPath: string, opts?: { recursive?: boolean }): Promise<void> {\n this.checkPermission(dirPath, \"write\");\n const realPath = this.resolveSafePath(dirPath);\n await this.underlyingFs.promises.mkdir(realPath, opts);\n }\n\n async rm(filePath: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.rm(realPath, opts);\n }\n\n // Path utilities (no permission check needed)\n resolve(...paths: string[]): string {\n return this.pathOps.resolve(\"/\", ...paths);\n }\n\n dirname(filePath: string): string {\n return this.pathOps.dirname(filePath);\n }\n\n basename(filePath: string): string {\n return this.pathOps.basename(filePath);\n }\n\n // Glob 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": ";AAAA;AACA;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";AAAA;AACA;AAEA;AAmCA,IAAM,YAA0B,EAAE,UAAU,OAAO;AACnD,IAAM,cAAuB;AAAA,EAC3B,WAAgB;AAAA,EAChB,SAAS,IAAI,UAAe,aAAQ,GAAG,KAAK;AAAA,EAC5C,WAAW,CAAC,aAAkB,eAAU,QAAQ;AAAA,EAChD,MAAM,IAAI,UAAe,UAAK,GAAG,KAAK;AAAA,EACtC,UAAU,CAAC,MAAM,OAAY,cAAS,MAAM,EAAE;AAAA,EAC9C,YAAY,CAAC,aAAkB,gBAAW,QAAQ;AAAA,EAClD,SAAS,CAAC,aAAkB,aAAQ,QAAQ;AAAA,EAC5C,UAAU,CAAC,aAAkB,cAAS,QAAQ;AAChD;AAAA;AAQO,MAAM,WAAgC;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACE;AAAA,EAEnB,WAAW,CAAC,WAAoB,aAA+B,IAAmB;AAAA,IAChF,MAAM,eAAe,MAAM;AAAA,IAC3B,KAAK,UAAU,aAAa,WAAW;AAAA,IACvC,KAAK,YAAY,YAAY,KAAK,QAAQ,QAAQ,SAAS,IAAI;AAAA,IAC/D,KAAK,QAAQ,KAAK,aAAa,eAAe,CAAC,CAAC;AAAA,IAChD,KAAK,eAAe;AAAA;AAAA,EAGd,YAAY,CAAC,aAA8C;AAAA,IACjE,OAAO,OAAO,QAAQ,WAAW,EAC9B,IAAI,EAAE,SAAS,iBAAiB;AAAA,MAC/B;AAAA,MACA;AAAA,MACA,aAAa,KAAK,qBAAqB,OAAO;AAAA,IAChD,EAAE,EACD,KAAK,CAAC,GAAG,MAAM,EAAE,cAAc,EAAE,WAAW;AAAA;AAAA,EAGzC,oBAAoB,CAAC,SAAyB;AAAA,IACpD,MAAM,WAAW,QAAQ,MAAM,GAAG,EAAE,OAAO,OAAO;AAAA,IAClD,IAAI,QAAQ,SAAS,SAAS;AAAA,IAE9B,WAAW,OAAO,UAAU;AAAA,MAC1B,IAAI,QAAQ;AAAA,QAAM,SAAS;AAAA,MACtB,SAAI,IAAI,SAAS,GAAG;AAAA,QAAG,SAAS;AAAA,MAChC;AAAA,iBAAS;AAAA,IAChB;AAAA,IACA,OAAO;AAAA;AAAA,EAGF,aAAa,CAAC,aAAiC;AAAA,IACpD,MAAM,aAAa,YAAY,QAAQ,QAAQ,EAAE;AAAA,IAEjD,WAAW,QAAQ,KAAK,OAAO;AAAA,MAC7B,IAAI,KAAK,UAAU,KAAK,SAAS,UAAU,GAAG;AAAA,QAC5C,OAAO,KAAK;AAAA,MACd;AAAA,IACF;AAAA,IACA,OAAO;AAAA;AAAA,EAGD,SAAS,CAAC,SAAiB,UAA2B;AAAA,IAG5D,MAAM,QAAQ,QACX,MAAM,GAAG,EACT,IAAI,CAAC,QAAQ;AAAA,MACZ,IAAI,QAAQ;AAAA,QAAM,OAAO;AAAA,MACzB,OAAO,IAAI,QAAQ,OAAO,OAAO,EAAE,QAAQ,OAAO,MAAM;AAAA,KACzD,EACA,KAAK,GAAG;AAAA,IACX,OAAO,IAAI,OAAO,IAAI,QAAQ,EAAE,KAAK,QAAQ;AAAA;AAAA,EAGxC,eAAe,CAAC,aAAqB,WAAmC;AAAA,IAC7E,MAAM,OAAO,KAAK,cAAc,WAAW;AAAA,IAE3C,IAAI,SAAS,YAAY;AAAA,MACvB,MAAM,IAAI,MAAM,mBAAmB,0BAA0B;AAAA,IAC/D;AAAA,IACA,IAAI,cAAc,WAAW,SAAS,aAAa;AAAA,MACjD,MAAM,IAAI,MAAM,mBAAmB,2BAA2B;AAAA,IAChE;AAAA;AAAA,EAGM,eAAe,CAAC,aAA6B;AAAA,IACnD,IAAI,KAAK,cAAc,MAAM;AAAA,MAC3B,OAAO,KAAK,QAAQ,QAAQ,WAAW;AAAA,IACzC;AAAA,IAGA,MAAM,WAAW,YAAY,MAAM,GAAG,EAAE,OAAO,OAAO;AAAA,IACtD,IAAI,QAAQ;AAAA,IACZ,WAAW,OAAO,UAAU;AAAA,MAC1B,IAAI,QAAQ,MAAM;AAAA,QAChB;AAAA,QACA,IAAI,QAAQ,GAAG;AAAA,UACb,MAAM,IAAI,MAAM,4BAA4B,kCAAkC;AAAA,QAChF;AAAA,MACF,EAAO,SAAI,QAAQ,KAAK;AAAA,QACtB;AAAA,MACF;AAAA,IACF;AAAA,IAEA,MAAM,aAAa,KAAK,QAAQ,UAAU,WAAW;AAAA,IACrD,MAAM,eAAe,WAAW,WAAW,GAAG,IAAI,WAAW,MAAM,CAAC,IAAI;AAAA,IACxE,MAAM,WAAW,KAAK,QAAQ,KAAK,KAAK,WAAW,YAAY;AAAA,IAC/D,MAAM,WAAW,KAAK,QAAQ,QAAQ,QAAQ;AAAA,IAG9C,MAAM,oBAAoB,KAAK,QAAQ,SAAS,KAAK,WAAW,QAAQ;AAAA,IACxE,MAAM,eACJ,sBAAsB,QACtB,kBAAkB,WAAW,KAAK,KAAK,QAAQ,WAAW,KAC1D,KAAK,QAAQ,WAAW,iBAAiB;AAAA,IAC3C,IAAI,cAAc;AAAA,MAChB,MAAM,IAAI,MAAM,4BAA4B,kCAAkC;AAAA,IAChF;AAAA,IAEA,OAAO;AAAA;AAAA,OAMH,SAAQ,CAAC,UAAkB,UAAqD;AAAA,IACpF,KAAK,gBAAgB,UAAU,MAAM;AAAA,IACrC,MAAM,WAAW,KAAK,gBAAgB,QAAQ;AAAA,IAC9C,MAAM,UAAU,MAAM,KAAK,aAAa,SAAS,SAAS,QAAQ;AAAA,IAClE,MAAM,MAAM,OAAO,KAAK,OAAO;AAAA,IAC/B,OAAO,WAAW,IAAI,SAAS,QAAQ,IAAI;AAAA;AAAA,OAGvC,QAAO,CAAC,SAAoC;AAAA,IAChD,KAAK,gBAAgB,SAAS,MAAM;AAAA,IACpC,MAAM,WAAW,KAAK,gBAAgB,OAAO;AAAA,IAC7C,MAAM,UAAU,MAAM,KAAK,aAAa,SAAS,QAAQ,QAAQ;AAAA,IACjE,OAAO,QAAQ,IAAI,MAAM;AAAA;AAAA,OAGrB,KAAI,CAAC,UAAqC;AAAA,IAC9C,KAAK,gBAAgB,UAAU,MAAM;AAAA,IACrC,MAAM,WAAW,KAAK,gBAAgB,QAAQ;AAAA,IAC9C,MAAM,QAAQ,MAAM,KAAK,aAAa,SAAS,KAAK,QAAQ;AAAA,IAC5D,OAAO;AAAA,MACL,QAAQ,MAAM,MAAM,OAAO;AAAA,MAC3B,aAAa,MAAM,MAAM,YAAY;AAAA,MACrC,MAAM,MAAM;AAAA,MACZ,OAAO,MAAM;AAAA,IACf;AAAA;AAAA,OAGI,OAAM,CAAC,UAAoC;AAAA,IAC/C,IAAI;AAAA,MACF,KAAK,gBAAgB,UAAU,MAAM;AAAA,MACrC,MAAM,WAAW,KAAK,gBAAgB,QAAQ;AAAA,MAC9C,MAAM,KAAK,aAAa,SAAS,KAAK,QAAQ;AAAA,MAC9C,OAAO;AAAA,MACP,MAAM;AAAA,MACN,OAAO;AAAA;AAAA;AAAA,OAKL,UAAS,CAAC,UAAkB,MAAsC;AAAA,IACtE,KAAK,gBAAgB,UAAU,OAAO;AAAA,IACtC,MAAM,WAAW,KAAK,gBAAgB,QAAQ;AAAA,IAC9C,MAAM,KAAK,aAAa,SAAS,UAAU,UAAU,IAAI;AAAA;AAAA,OAGrD,WAAU,CAAC,UAAkB,MAAsC;AAAA,IACvE,KAAK,gBAAgB,UAAU,OAAO;AAAA,IACtC,MAAM,WAAW,KAAK,gBAAgB,QAAQ;AAAA,IAC9C,MAAM,KAAK,aAAa,SAAS,WAAW,UAAU,IAAI;AAAA;AAAA,OAGtD,MAAK,CAAC,SAAiB,MAA+C;AAAA,IAC1E,KAAK,gBAAgB,SAAS,OAAO;AAAA,IACrC,MAAM,WAAW,KAAK,gBAAgB,OAAO;AAAA,IAC7C,MAAM,KAAK,aAAa,SAAS,MAAM,UAAU,IAAI;AAAA;AAAA,OAGjD,GAAE,CAAC,UAAkB,MAAgE;AAAA,IACzF,KAAK,gBAAgB,UAAU,OAAO;AAAA,IACtC,MAAM,WAAW,KAAK,gBAAgB,QAAQ;AAAA,IAC9C,MAAM,KAAK,aAAa,SAAS,GAAG,UAAU,IAAI;AAAA;AAAA,EAIpD,OAAO,IAAI,OAAyB;AAAA,IAClC,OAAO,KAAK,QAAQ,QAAQ,KAAK,GAAG,KAAK;AAAA;AAAA,EAG3C,OAAO,CAAC,UAA0B;AAAA,IAChC,OAAO,KAAK,QAAQ,QAAQ,QAAQ;AAAA;AAAA,EAGtC,QAAQ,CAAC,UAA0B;AAAA,IACjC,OAAO,KAAK,QAAQ,SAAS,QAAQ;AAAA;AAAA,OAIjC,KAAI,CAAC,SAAiB,MAA4C;AAAA,IACtE,MAAM,MAAM,MAAM,OAAO;AAAA,IACzB,KAAK,gBAAgB,KAAK,MAAM;AAAA,IAChC,OAAO,cAAc,MAAM,SAAS,EAAE,IAAI,CAAC;AAAA;AAE/C;",
|
|
8
|
+
"debugId": "093147B23E1E8F5A64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/mjs/src/index.mjs
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
} from "./fs/index.mjs";
|
|
34
34
|
import { createStdin, StdinImpl } from "./io/index.mjs";
|
|
35
35
|
import { createStdout, createStderr, createPipe, OutputCollectorImpl, PipeBuffer } from "./io/index.mjs";
|
|
36
|
-
import { escape, escapeForInterpolation } from "./utils/index.mjs";
|
|
36
|
+
import { escape, escapeForInterpolation, globVirtualFS } from "./utils/index.mjs";
|
|
37
37
|
export {
|
|
38
38
|
tokenToString,
|
|
39
39
|
parse,
|
|
@@ -55,6 +55,7 @@ export {
|
|
|
55
55
|
isCaseNode,
|
|
56
56
|
isArithmeticNode,
|
|
57
57
|
isAndNode,
|
|
58
|
+
globVirtualFS,
|
|
58
59
|
escapeForInterpolation,
|
|
59
60
|
escape,
|
|
60
61
|
createWebUnderlyingFS,
|
|
@@ -82,4 +83,4 @@ export {
|
|
|
82
83
|
BreakException
|
|
83
84
|
};
|
|
84
85
|
|
|
85
|
-
//# debugId=
|
|
86
|
+
//# debugId=6B9F05D80609142564756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"// Main class exports\nexport { ShellDSL, createShellDSL, type Program } from \"./shell-dsl.mjs\";\nexport { ShellPromise, type ShellPromiseOptions } from \"./shell-promise.mjs\";\n\n// Types\nexport type {\n VirtualFS,\n FileStat,\n Command,\n CommandContext,\n Stdin,\n Stdout,\n Stderr,\n OutputCollector,\n ExecResult,\n ShellConfig,\n RawValue,\n} from \"./types.mjs\";\nexport { isRawValue } from \"./types.mjs\";\n\n// Errors\nexport { ShellError, LexError, ParseError } from \"./errors.mjs\";\n\n// Lexer\nexport { Lexer, lex, tokenToString } from \"./lexer/index.mjs\";\nexport type { Token, RedirectMode } from \"./lexer/index.mjs\";\n\n// Parser\nexport { Parser, parse } from \"./parser/index.mjs\";\nexport type {\n ASTNode,\n Redirect,\n CommandNode,\n PipelineNode,\n AndNode,\n OrNode,\n SequenceNode,\n LiteralNode,\n VariableNode,\n SubstitutionNode,\n GlobNode,\n ConcatNode,\n IfNode,\n ForNode,\n WhileNode,\n UntilNode,\n CaseNode,\n CaseClause,\n ArithmeticNode,\n} from \"./parser/index.mjs\";\nexport {\n isCommandNode,\n isPipelineNode,\n isAndNode,\n isOrNode,\n isSequenceNode,\n isLiteralNode,\n isVariableNode,\n isSubstitutionNode,\n isGlobNode,\n isConcatNode,\n isIfNode,\n isForNode,\n isWhileNode,\n isUntilNode,\n isCaseNode,\n isArithmeticNode,\n} from \"./parser/index.mjs\";\n\n// Interpreter\nexport { Interpreter, type InterpreterOptions, BreakException, ContinueException } from \"./interpreter/index.mjs\";\n\n// Filesystem\nexport { createVirtualFS } from \"./fs/index.mjs\";\nexport {\n FileSystem,\n ReadOnlyFileSystem,\n WebFileSystem,\n createWebUnderlyingFS,\n type PathOps,\n type Permission,\n type PermissionRules,\n type UnderlyingFS,\n} from \"./fs/index.mjs\";\n\n// I/O\nexport { createStdin, StdinImpl } from \"./io/index.mjs\";\nexport { createStdout, createStderr, createPipe, OutputCollectorImpl, PipeBuffer } from \"./io/index.mjs\";\n\n// Utilities\nexport { escape, escapeForInterpolation } from \"./utils/index.mjs\";\n"
|
|
5
|
+
"// Main class exports\nexport { ShellDSL, createShellDSL, type Program } from \"./shell-dsl.mjs\";\nexport { ShellPromise, type ShellPromiseOptions } from \"./shell-promise.mjs\";\n\n// Types\nexport type {\n VirtualFS,\n FileStat,\n Command,\n CommandContext,\n Stdin,\n Stdout,\n Stderr,\n OutputCollector,\n ExecResult,\n ShellConfig,\n RawValue,\n} from \"./types.mjs\";\nexport { isRawValue } from \"./types.mjs\";\n\n// Errors\nexport { ShellError, LexError, ParseError } from \"./errors.mjs\";\n\n// Lexer\nexport { Lexer, lex, tokenToString } from \"./lexer/index.mjs\";\nexport type { Token, RedirectMode } from \"./lexer/index.mjs\";\n\n// Parser\nexport { Parser, parse } from \"./parser/index.mjs\";\nexport type {\n ASTNode,\n Redirect,\n CommandNode,\n PipelineNode,\n AndNode,\n OrNode,\n SequenceNode,\n LiteralNode,\n VariableNode,\n SubstitutionNode,\n GlobNode,\n ConcatNode,\n IfNode,\n ForNode,\n WhileNode,\n UntilNode,\n CaseNode,\n CaseClause,\n ArithmeticNode,\n} from \"./parser/index.mjs\";\nexport {\n isCommandNode,\n isPipelineNode,\n isAndNode,\n isOrNode,\n isSequenceNode,\n isLiteralNode,\n isVariableNode,\n isSubstitutionNode,\n isGlobNode,\n isConcatNode,\n isIfNode,\n isForNode,\n isWhileNode,\n isUntilNode,\n isCaseNode,\n isArithmeticNode,\n} from \"./parser/index.mjs\";\n\n// Interpreter\nexport { Interpreter, type InterpreterOptions, BreakException, ContinueException } from \"./interpreter/index.mjs\";\n\n// Filesystem\nexport { createVirtualFS } from \"./fs/index.mjs\";\nexport {\n FileSystem,\n ReadOnlyFileSystem,\n WebFileSystem,\n createWebUnderlyingFS,\n type PathOps,\n type Permission,\n type PermissionRules,\n type UnderlyingFS,\n} from \"./fs/index.mjs\";\n\n// I/O\nexport { createStdin, StdinImpl } from \"./io/index.mjs\";\nexport { createStdout, createStderr, createPipe, OutputCollectorImpl, PipeBuffer } from \"./io/index.mjs\";\n\n// Utilities\nexport { escape, escapeForInterpolation, globVirtualFS } from \"./utils/index.mjs\";\nexport type { GlobVirtualFS, GlobOptions } from \"./utils/index.mjs\";\n"
|
|
6
6
|
],
|
|
7
7
|
"mappings": ";AACA;AACA;AAgBA;AAGA;AAGA;AAIA;AAsBA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoBA;AAGA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYA;AACA;AAGA;",
|
|
8
|
-
"debugId": "
|
|
8
|
+
"debugId": "6B9F05D80609142564756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// src/utils/glob.ts
|
|
2
|
+
import { matchGlob } from "./match-glob.mjs";
|
|
3
|
+
async function globVirtualFS(fs, pattern, opts) {
|
|
4
|
+
const cwd = fs.resolve(opts?.cwd ?? "/");
|
|
5
|
+
const patterns = expandBraces(pattern);
|
|
6
|
+
const allMatches = [];
|
|
7
|
+
for (const expandedPattern of patterns) {
|
|
8
|
+
const matches = await matchPattern(fs, expandedPattern, cwd);
|
|
9
|
+
allMatches.push(...matches);
|
|
10
|
+
}
|
|
11
|
+
return [...new Set(allMatches)].sort();
|
|
12
|
+
}
|
|
13
|
+
function expandBraces(pattern) {
|
|
14
|
+
const braceMatch = pattern.match(/\{([^{}]+)\}/);
|
|
15
|
+
if (!braceMatch)
|
|
16
|
+
return [pattern];
|
|
17
|
+
const before = pattern.slice(0, braceMatch.index);
|
|
18
|
+
const after = pattern.slice(braceMatch.index + braceMatch[0].length);
|
|
19
|
+
const options = braceMatch[1].split(",");
|
|
20
|
+
const results = [];
|
|
21
|
+
for (const option of options) {
|
|
22
|
+
results.push(...expandBraces(before + option + after));
|
|
23
|
+
}
|
|
24
|
+
return results;
|
|
25
|
+
}
|
|
26
|
+
async function matchPattern(fs, pattern, cwd) {
|
|
27
|
+
const parts = pattern.split("/").filter(Boolean);
|
|
28
|
+
const startDir = pattern.startsWith("/") ? "/" : cwd;
|
|
29
|
+
return matchParts(fs, parts, startDir);
|
|
30
|
+
}
|
|
31
|
+
async function matchParts(fs, parts, currentPath) {
|
|
32
|
+
if (parts.length === 0) {
|
|
33
|
+
return await pathExists(fs, currentPath) ? [currentPath] : [];
|
|
34
|
+
}
|
|
35
|
+
const [part, ...rest] = parts;
|
|
36
|
+
if (part === "**") {
|
|
37
|
+
const results = await matchParts(fs, rest, currentPath);
|
|
38
|
+
try {
|
|
39
|
+
const entries = await fs.readdir(currentPath);
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
const entryPath = fs.resolve(currentPath, entry);
|
|
42
|
+
try {
|
|
43
|
+
const stat = await fs.stat(entryPath);
|
|
44
|
+
if (stat.isDirectory()) {
|
|
45
|
+
results.push(...await matchParts(fs, parts, entryPath));
|
|
46
|
+
}
|
|
47
|
+
} catch {}
|
|
48
|
+
}
|
|
49
|
+
} catch {}
|
|
50
|
+
return results;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const entries = await fs.readdir(currentPath);
|
|
54
|
+
const results = [];
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
if (!matchGlob(part, entry))
|
|
57
|
+
continue;
|
|
58
|
+
const entryPath = fs.resolve(currentPath, entry);
|
|
59
|
+
if (rest.length === 0) {
|
|
60
|
+
if (await pathExists(fs, entryPath)) {
|
|
61
|
+
results.push(entryPath);
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const stat = await fs.stat(entryPath);
|
|
67
|
+
if (stat.isDirectory()) {
|
|
68
|
+
results.push(...await matchParts(fs, rest, entryPath));
|
|
69
|
+
}
|
|
70
|
+
} catch {}
|
|
71
|
+
}
|
|
72
|
+
return results;
|
|
73
|
+
} catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function pathExists(fs, filePath) {
|
|
78
|
+
try {
|
|
79
|
+
await fs.stat(filePath);
|
|
80
|
+
return true;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export {
|
|
86
|
+
globVirtualFS
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
//# debugId=EF2EBEB444AD8F0064756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/utils/glob.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import type { VirtualFS } from \"../types.mjs\";\nimport { matchGlob } from \"./match-glob.mjs\";\n\nexport type GlobVirtualFS = Pick<VirtualFS, \"readdir\" | \"stat\" | \"resolve\">;\n\nexport interface GlobOptions {\n cwd?: string;\n}\n\nexport async function globVirtualFS(fs: GlobVirtualFS, pattern: string, opts?: GlobOptions): Promise<string[]> {\n const cwd = fs.resolve(opts?.cwd ?? \"/\");\n const patterns = expandBraces(pattern);\n const allMatches: string[] = [];\n\n for (const expandedPattern of patterns) {\n const matches = await matchPattern(fs, expandedPattern, cwd);\n allMatches.push(...matches);\n }\n\n return [...new Set(allMatches)].sort();\n}\n\nfunction expandBraces(pattern: string): string[] {\n const braceMatch = pattern.match(/\\{([^{}]+)\\}/);\n if (!braceMatch) return [pattern];\n\n const before = pattern.slice(0, braceMatch.index);\n const after = pattern.slice(braceMatch.index! + braceMatch[0].length);\n const options = braceMatch[1]!.split(\",\");\n const results: string[] = [];\n\n for (const option of options) {\n results.push(...expandBraces(before + option + after));\n }\n\n return results;\n}\n\nasync function matchPattern(fs: GlobVirtualFS, pattern: string, cwd: string): Promise<string[]> {\n const parts = pattern.split(\"/\").filter(Boolean);\n const startDir = pattern.startsWith(\"/\") ? \"/\" : cwd;\n return matchParts(fs, parts, startDir);\n}\n\nasync function matchParts(fs: GlobVirtualFS, parts: string[], currentPath: string): Promise<string[]> {\n if (parts.length === 0) {\n return (await pathExists(fs, currentPath)) ? [currentPath] : [];\n }\n\n const [part, ...rest] = parts;\n\n if (part === \"**\") {\n const results = await matchParts(fs, rest, currentPath);\n\n try {\n const entries = await fs.readdir(currentPath);\n for (const entry of entries) {\n const entryPath = fs.resolve(currentPath, entry);\n try {\n const stat = await fs.stat(entryPath);\n if (stat.isDirectory()) {\n results.push(...(await matchParts(fs, parts, entryPath)));\n }\n } catch {\n // Skip entries we can't stat.\n }\n }\n } catch {\n // Directory not readable.\n }\n\n return results;\n }\n\n try {\n const entries = await fs.readdir(currentPath);\n const results: string[] = [];\n\n for (const entry of entries) {\n if (!matchGlob(part!, entry)) continue;\n\n const entryPath = fs.resolve(currentPath, entry);\n\n if (rest.length === 0) {\n if (await pathExists(fs, entryPath)) {\n results.push(entryPath);\n }\n continue;\n }\n\n try {\n const stat = await fs.stat(entryPath);\n if (stat.isDirectory()) {\n results.push(...(await matchParts(fs, rest, entryPath)));\n }\n } catch {\n // Skip entries we can't stat.\n }\n }\n\n return results;\n } catch {\n return [];\n }\n}\n\nasync function pathExists(fs: GlobVirtualFS, filePath: string): Promise<boolean> {\n try {\n await fs.stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AACA;AAQA,eAAsB,aAAa,CAAC,IAAmB,SAAiB,MAAuC;AAAA,EAC7G,MAAM,MAAM,GAAG,QAAQ,MAAM,OAAO,GAAG;AAAA,EACvC,MAAM,WAAW,aAAa,OAAO;AAAA,EACrC,MAAM,aAAuB,CAAC;AAAA,EAE9B,WAAW,mBAAmB,UAAU;AAAA,IACtC,MAAM,UAAU,MAAM,aAAa,IAAI,iBAAiB,GAAG;AAAA,IAC3D,WAAW,KAAK,GAAG,OAAO;AAAA,EAC5B;AAAA,EAEA,OAAO,CAAC,GAAG,IAAI,IAAI,UAAU,CAAC,EAAE,KAAK;AAAA;AAGvC,SAAS,YAAY,CAAC,SAA2B;AAAA,EAC/C,MAAM,aAAa,QAAQ,MAAM,cAAc;AAAA,EAC/C,IAAI,CAAC;AAAA,IAAY,OAAO,CAAC,OAAO;AAAA,EAEhC,MAAM,SAAS,QAAQ,MAAM,GAAG,WAAW,KAAK;AAAA,EAChD,MAAM,QAAQ,QAAQ,MAAM,WAAW,QAAS,WAAW,GAAG,MAAM;AAAA,EACpE,MAAM,UAAU,WAAW,GAAI,MAAM,GAAG;AAAA,EACxC,MAAM,UAAoB,CAAC;AAAA,EAE3B,WAAW,UAAU,SAAS;AAAA,IAC5B,QAAQ,KAAK,GAAG,aAAa,SAAS,SAAS,KAAK,CAAC;AAAA,EACvD;AAAA,EAEA,OAAO;AAAA;AAGT,eAAe,YAAY,CAAC,IAAmB,SAAiB,KAAgC;AAAA,EAC9F,MAAM,QAAQ,QAAQ,MAAM,GAAG,EAAE,OAAO,OAAO;AAAA,EAC/C,MAAM,WAAW,QAAQ,WAAW,GAAG,IAAI,MAAM;AAAA,EACjD,OAAO,WAAW,IAAI,OAAO,QAAQ;AAAA;AAGvC,eAAe,UAAU,CAAC,IAAmB,OAAiB,aAAwC;AAAA,EACpG,IAAI,MAAM,WAAW,GAAG;AAAA,IACtB,OAAQ,MAAM,WAAW,IAAI,WAAW,IAAK,CAAC,WAAW,IAAI,CAAC;AAAA,EAChE;AAAA,EAEA,OAAO,SAAS,QAAQ;AAAA,EAExB,IAAI,SAAS,MAAM;AAAA,IACjB,MAAM,UAAU,MAAM,WAAW,IAAI,MAAM,WAAW;AAAA,IAEtD,IAAI;AAAA,MACF,MAAM,UAAU,MAAM,GAAG,QAAQ,WAAW;AAAA,MAC5C,WAAW,SAAS,SAAS;AAAA,QAC3B,MAAM,YAAY,GAAG,QAAQ,aAAa,KAAK;AAAA,QAC/C,IAAI;AAAA,UACF,MAAM,OAAO,MAAM,GAAG,KAAK,SAAS;AAAA,UACpC,IAAI,KAAK,YAAY,GAAG;AAAA,YACtB,QAAQ,KAAK,GAAI,MAAM,WAAW,IAAI,OAAO,SAAS,CAAE;AAAA,UAC1D;AAAA,UACA,MAAM;AAAA,MAGV;AAAA,MACA,MAAM;AAAA,IAIR,OAAO;AAAA,EACT;AAAA,EAEA,IAAI;AAAA,IACF,MAAM,UAAU,MAAM,GAAG,QAAQ,WAAW;AAAA,IAC5C,MAAM,UAAoB,CAAC;AAAA,IAE3B,WAAW,SAAS,SAAS;AAAA,MAC3B,IAAI,CAAC,UAAU,MAAO,KAAK;AAAA,QAAG;AAAA,MAE9B,MAAM,YAAY,GAAG,QAAQ,aAAa,KAAK;AAAA,MAE/C,IAAI,KAAK,WAAW,GAAG;AAAA,QACrB,IAAI,MAAM,WAAW,IAAI,SAAS,GAAG;AAAA,UACnC,QAAQ,KAAK,SAAS;AAAA,QACxB;AAAA,QACA;AAAA,MACF;AAAA,MAEA,IAAI;AAAA,QACF,MAAM,OAAO,MAAM,GAAG,KAAK,SAAS;AAAA,QACpC,IAAI,KAAK,YAAY,GAAG;AAAA,UACtB,QAAQ,KAAK,GAAI,MAAM,WAAW,IAAI,MAAM,SAAS,CAAE;AAAA,QACzD;AAAA,QACA,MAAM;AAAA,IAGV;AAAA,IAEA,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO,CAAC;AAAA;AAAA;AAIZ,eAAe,UAAU,CAAC,IAAmB,UAAoC;AAAA,EAC/E,IAAI;AAAA,IACF,MAAM,GAAG,KAAK,QAAQ;AAAA,IACtB,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA;AAAA;",
|
|
8
|
+
"debugId": "EF2EBEB444AD8F0064756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
// src/utils/index.ts
|
|
2
2
|
import { escape, escapeForInterpolation } from "./escape.mjs";
|
|
3
3
|
import { expandEscapes } from "./expand-escapes.mjs";
|
|
4
|
+
import { globVirtualFS } from "./glob.mjs";
|
|
4
5
|
import {
|
|
5
6
|
createFlagParser
|
|
6
7
|
} from "./flag-parser.mjs";
|
|
7
8
|
import { matchGlob } from "./match-glob.mjs";
|
|
8
9
|
export {
|
|
9
10
|
matchGlob,
|
|
11
|
+
globVirtualFS,
|
|
10
12
|
expandEscapes,
|
|
11
13
|
escapeForInterpolation,
|
|
12
14
|
escape,
|
|
13
15
|
createFlagParser
|
|
14
16
|
};
|
|
15
17
|
|
|
16
|
-
//# debugId=
|
|
18
|
+
//# debugId=7E1A8F2BD8DEF55F64756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/utils/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"export { escape, escapeForInterpolation } from \"./escape.mjs\";\nexport { expandEscapes } from \"./expand-escapes.mjs\";\nexport {\n createFlagParser,\n type FlagDefinition,\n type CommandSpec,\n type FlagError,\n type ParseResult,\n type FlagParser,\n} from \"./flag-parser.mjs\";\nexport { matchGlob } from \"./match-glob.mjs\";\n"
|
|
5
|
+
"export { escape, escapeForInterpolation } from \"./escape.mjs\";\nexport { expandEscapes } from \"./expand-escapes.mjs\";\nexport { globVirtualFS, type GlobVirtualFS, type GlobOptions } from \"./glob.mjs\";\nexport {\n createFlagParser,\n type FlagDefinition,\n type CommandSpec,\n type FlagError,\n type ParseResult,\n type FlagParser,\n} from \"./flag-parser.mjs\";\nexport { matchGlob } from \"./match-glob.mjs\";\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";AAAA;AACA;AACA;AAAA;AAAA;AAQA;",
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";AAAA;AACA;AACA;AACA;AAAA;AAAA;AAQA;",
|
|
8
|
+
"debugId": "7E1A8F2BD8DEF55F64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -13,4 +13,5 @@ export { createVirtualFS } from "./fs/index.ts";
|
|
|
13
13
|
export { FileSystem, ReadOnlyFileSystem, WebFileSystem, createWebUnderlyingFS, type PathOps, type Permission, type PermissionRules, type UnderlyingFS, } from "./fs/index.ts";
|
|
14
14
|
export { createStdin, StdinImpl } from "./io/index.ts";
|
|
15
15
|
export { createStdout, createStderr, createPipe, OutputCollectorImpl, PipeBuffer } from "./io/index.ts";
|
|
16
|
-
export { escape, escapeForInterpolation } from "./utils/index.ts";
|
|
16
|
+
export { escape, escapeForInterpolation, globVirtualFS } from "./utils/index.ts";
|
|
17
|
+
export type { GlobVirtualFS, GlobOptions } from "./utils/index.ts";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { VirtualFS } from "../types.ts";
|
|
2
|
+
export type GlobVirtualFS = Pick<VirtualFS, "readdir" | "stat" | "resolve">;
|
|
3
|
+
export interface GlobOptions {
|
|
4
|
+
cwd?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function globVirtualFS(fs: GlobVirtualFS, pattern: string, opts?: GlobOptions): Promise<string[]>;
|