shell-dsl 0.0.28 → 0.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/src/fs/index.cjs +4 -1
- package/dist/cjs/src/fs/index.cjs.map +3 -3
- package/dist/cjs/src/fs/real-fs.cjs +30 -15
- package/dist/cjs/src/fs/real-fs.cjs.map +3 -3
- package/dist/cjs/src/fs/web-fs.cjs +295 -0
- package/dist/cjs/src/fs/web-fs.cjs.map +10 -0
- package/dist/cjs/src/index.cjs +3 -1
- package/dist/cjs/src/index.cjs.map +3 -3
- package/dist/cjs/src/shell-promise.cjs +2 -2
- package/dist/cjs/src/shell-promise.cjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/src/fs/index.mjs +4 -1
- package/dist/mjs/src/fs/index.mjs.map +3 -3
- package/dist/mjs/src/fs/real-fs.mjs +30 -15
- package/dist/mjs/src/fs/real-fs.mjs.map +3 -3
- package/dist/mjs/src/fs/web-fs.mjs +255 -0
- package/dist/mjs/src/fs/web-fs.mjs.map +10 -0
- package/dist/mjs/src/index.mjs +6 -2
- package/dist/mjs/src/index.mjs.map +3 -3
- package/dist/mjs/src/shell-promise.mjs +2 -2
- package/dist/mjs/src/shell-promise.mjs.map +3 -3
- package/dist/types/src/fs/index.d.ts +2 -1
- package/dist/types/src/fs/real-fs.d.ts +12 -0
- package/dist/types/src/fs/web-fs.d.ts +5 -0
- package/dist/types/src/index.d.ts +1 -1
- package/package.json +1 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"// Main class exports\nexport { ShellDSL, createShellDSL, type Program } from \"./shell-dsl.cjs\";\nexport { ShellPromise, type ShellPromiseOptions } from \"./shell-promise.cjs\";\n\n// Types\nexport type {\n VirtualFS,\n FileStat,\n Command,\n CommandContext,\n Stdin,\n Stdout,\n Stderr,\n OutputCollector,\n ExecResult,\n ShellConfig,\n RawValue,\n} from \"./types.cjs\";\nexport { isRawValue } from \"./types.cjs\";\n\n// Errors\nexport { ShellError, LexError, ParseError } from \"./errors.cjs\";\n\n// Lexer\nexport { Lexer, lex, tokenToString } from \"./lexer/index.cjs\";\nexport type { Token, RedirectMode } from \"./lexer/index.cjs\";\n\n// Parser\nexport { Parser, parse } from \"./parser/index.cjs\";\nexport type {\n ASTNode,\n Redirect,\n CommandNode,\n PipelineNode,\n AndNode,\n OrNode,\n SequenceNode,\n LiteralNode,\n VariableNode,\n SubstitutionNode,\n GlobNode,\n ConcatNode,\n IfNode,\n ForNode,\n WhileNode,\n UntilNode,\n CaseNode,\n CaseClause,\n ArithmeticNode,\n} from \"./parser/index.cjs\";\nexport {\n isCommandNode,\n isPipelineNode,\n isAndNode,\n isOrNode,\n isSequenceNode,\n isLiteralNode,\n isVariableNode,\n isSubstitutionNode,\n isGlobNode,\n isConcatNode,\n isIfNode,\n isForNode,\n isWhileNode,\n isUntilNode,\n isCaseNode,\n isArithmeticNode,\n} from \"./parser/index.cjs\";\n\n// Interpreter\nexport { Interpreter, type InterpreterOptions, BreakException, ContinueException } from \"./interpreter/index.cjs\";\n\n// Filesystem\nexport { createVirtualFS } from \"./fs/index.cjs\";\nexport {\n FileSystem,\n ReadOnlyFileSystem,\n type Permission,\n type PermissionRules,\n type UnderlyingFS,\n} from \"./fs/index.cjs\";\n\n// I/O\nexport { createStdin, StdinImpl } from \"./io/index.cjs\";\nexport { createStdout, createStderr, createPipe, OutputCollectorImpl, PipeBuffer } from \"./io/index.cjs\";\n\n// Utilities\nexport { escape, escapeForInterpolation } from \"./utils/index.cjs\";\n"
|
|
5
|
+
"// Main class exports\nexport { ShellDSL, createShellDSL, type Program } from \"./shell-dsl.cjs\";\nexport { ShellPromise, type ShellPromiseOptions } from \"./shell-promise.cjs\";\n\n// Types\nexport type {\n VirtualFS,\n FileStat,\n Command,\n CommandContext,\n Stdin,\n Stdout,\n Stderr,\n OutputCollector,\n ExecResult,\n ShellConfig,\n RawValue,\n} from \"./types.cjs\";\nexport { isRawValue } from \"./types.cjs\";\n\n// Errors\nexport { ShellError, LexError, ParseError } from \"./errors.cjs\";\n\n// Lexer\nexport { Lexer, lex, tokenToString } from \"./lexer/index.cjs\";\nexport type { Token, RedirectMode } from \"./lexer/index.cjs\";\n\n// Parser\nexport { Parser, parse } from \"./parser/index.cjs\";\nexport type {\n ASTNode,\n Redirect,\n CommandNode,\n PipelineNode,\n AndNode,\n OrNode,\n SequenceNode,\n LiteralNode,\n VariableNode,\n SubstitutionNode,\n GlobNode,\n ConcatNode,\n IfNode,\n ForNode,\n WhileNode,\n UntilNode,\n CaseNode,\n CaseClause,\n ArithmeticNode,\n} from \"./parser/index.cjs\";\nexport {\n isCommandNode,\n isPipelineNode,\n isAndNode,\n isOrNode,\n isSequenceNode,\n isLiteralNode,\n isVariableNode,\n isSubstitutionNode,\n isGlobNode,\n isConcatNode,\n isIfNode,\n isForNode,\n isWhileNode,\n isUntilNode,\n isCaseNode,\n isArithmeticNode,\n} from \"./parser/index.cjs\";\n\n// Interpreter\nexport { Interpreter, type InterpreterOptions, BreakException, ContinueException } from \"./interpreter/index.cjs\";\n\n// Filesystem\nexport { createVirtualFS } from \"./fs/index.cjs\";\nexport {\n FileSystem,\n ReadOnlyFileSystem,\n WebFileSystem,\n createWebUnderlyingFS,\n type PathOps,\n type Permission,\n type PermissionRules,\n type UnderlyingFS,\n} from \"./fs/index.cjs\";\n\n// I/O\nexport { createStdin, StdinImpl } from \"./io/index.cjs\";\nexport { createStdout, createStderr, createPipe, OutputCollectorImpl, PipeBuffer } from \"./io/index.cjs\";\n\n// Utilities\nexport { escape, escapeForInterpolation } from \"./utils/index.cjs\";\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACuD,IAAvD;AACuD,IAAvD;AAgB2B,IAA3B;AAGiD,IAAjD;AAG0C,IAA1C;AAI8B,IAA9B;AAuCO,IAjBP;AAoBwF,IAAxF;AAGgC,IAAhC;AAUO,IATP;AAYuC,IAAvC;AACwF,IAAxF;AAG+C,IAA/C;",
|
|
8
|
+
"debugId": "3F6524945419391564756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -101,7 +101,7 @@ class ShellPromise {
|
|
|
101
101
|
}
|
|
102
102
|
async blob() {
|
|
103
103
|
const result = await this.run();
|
|
104
|
-
return new Blob([result.stdout]);
|
|
104
|
+
return new Blob([new Uint8Array(result.stdout)]);
|
|
105
105
|
}
|
|
106
106
|
async buffer() {
|
|
107
107
|
const result = await this.run();
|
|
@@ -166,4 +166,4 @@ class ShellPromise {
|
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
-
//# debugId=
|
|
169
|
+
//# debugId=DFBD1041E32C91CE64756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/shell-promise.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type { ExecResult } from \"./types.cjs\";\nimport { ShellError } from \"./errors.cjs\";\n\nexport interface ExecuteOverrides {\n cwd?: string;\n env?: Record<string, string>;\n}\n\nexport interface ShellPromiseOptions {\n execute: (overrides: ExecuteOverrides) => Promise<ExecResult>;\n cwdOverride?: string;\n envOverride?: Record<string, string>;\n shouldThrow?: boolean;\n quiet?: boolean;\n}\n\nexport class ShellPromise implements PromiseLike<ExecResult> {\n private executor: (overrides: ExecuteOverrides) => Promise<ExecResult>;\n private cwdOverride?: string;\n private envOverride?: Record<string, string>;\n private shouldThrow: boolean;\n private isQuiet: boolean;\n private cachedResult?: Promise<ExecResult>;\n\n constructor(options: ShellPromiseOptions) {\n this.executor = options.execute;\n this.cwdOverride = options.cwdOverride;\n this.envOverride = options.envOverride;\n this.shouldThrow = options.shouldThrow ?? true;\n this.isQuiet = options.quiet ?? false;\n }\n\n private async run(): Promise<ExecResult> {\n if (!this.cachedResult) {\n this.cachedResult = this.executor({\n cwd: this.cwdOverride,\n env: this.envOverride,\n });\n }\n\n const result = await this.cachedResult;\n\n if (this.shouldThrow && result.exitCode !== 0) {\n throw new ShellError(\n `Command failed with exit code ${result.exitCode}`,\n result.stdout,\n result.stderr,\n result.exitCode\n );\n }\n\n return result;\n }\n\n then<TResult1 = ExecResult, TResult2 = never>(\n onfulfilled?: ((value: ExecResult) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null\n ): Promise<TResult1 | TResult2> {\n return this.run().then(onfulfilled, onrejected);\n }\n\n catch<TResult = never>(\n onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null\n ): Promise<ExecResult | TResult> {\n return this.run().catch(onrejected);\n }\n\n finally(onfinally?: (() => void) | null): Promise<ExecResult> {\n return this.run().finally(onfinally);\n }\n\n // Output formats\n async text(): Promise<string> {\n const result = await this.run();\n return result.stdout.toString(\"utf-8\");\n }\n\n async json<T = unknown>(): Promise<T> {\n const text = await this.text();\n return JSON.parse(text);\n }\n\n async *lines(): AsyncIterable<string> {\n const text = await this.text();\n const lines = text.split(\"\\n\");\n // Remove trailing empty line if present\n if (lines[lines.length - 1] === \"\") {\n lines.pop();\n }\n for (const line of lines) {\n yield line;\n }\n }\n\n async blob(): Promise<Blob> {\n const result = await this.run();\n return new Blob([result.stdout]);\n }\n\n async buffer(): Promise<Buffer> {\n const result = await this.run();\n return result.stdout;\n }\n\n // Behavior modifiers - return new ShellPromise with modified options\n quiet(): ShellPromise {\n return new ShellPromise({\n execute: this.executor,\n cwdOverride: this.cwdOverride,\n envOverride: this.envOverride,\n shouldThrow: this.shouldThrow,\n quiet: true,\n });\n }\n\n nothrow(): ShellPromise {\n return new ShellPromise({\n execute: this.executor,\n cwdOverride: this.cwdOverride,\n envOverride: this.envOverride,\n shouldThrow: false,\n quiet: this.isQuiet,\n });\n }\n\n throws(enable: boolean): ShellPromise {\n return new ShellPromise({\n execute: this.executor,\n cwdOverride: this.cwdOverride,\n envOverride: this.envOverride,\n shouldThrow: enable,\n quiet: this.isQuiet,\n });\n }\n\n // Context overrides - these need to be handled by the shell\n cwd(path: string): ShellPromise {\n return new ShellPromise({\n execute: this.executor,\n cwdOverride: path,\n envOverride: this.envOverride,\n shouldThrow: this.shouldThrow,\n quiet: this.isQuiet,\n });\n }\n\n env(vars: Record<string, string>): ShellPromise {\n return new ShellPromise({\n execute: this.executor,\n cwdOverride: this.cwdOverride,\n envOverride: { ...this.envOverride, ...vars },\n shouldThrow: this.shouldThrow,\n quiet: this.isQuiet,\n });\n }\n\n // Getters for internal state (used by ShellDSL)\n getCwdOverride(): string | undefined {\n return this.cwdOverride;\n }\n\n getEnvOverride(): Record<string, string> | undefined {\n return this.envOverride;\n }\n\n getShouldThrow(): boolean {\n return this.shouldThrow;\n }\n\n getIsQuiet(): boolean {\n return this.isQuiet;\n }\n}\n"
|
|
5
|
+
"import type { ExecResult } from \"./types.cjs\";\nimport { ShellError } from \"./errors.cjs\";\n\nexport interface ExecuteOverrides {\n cwd?: string;\n env?: Record<string, string>;\n}\n\nexport interface ShellPromiseOptions {\n execute: (overrides: ExecuteOverrides) => Promise<ExecResult>;\n cwdOverride?: string;\n envOverride?: Record<string, string>;\n shouldThrow?: boolean;\n quiet?: boolean;\n}\n\nexport class ShellPromise implements PromiseLike<ExecResult> {\n private executor: (overrides: ExecuteOverrides) => Promise<ExecResult>;\n private cwdOverride?: string;\n private envOverride?: Record<string, string>;\n private shouldThrow: boolean;\n private isQuiet: boolean;\n private cachedResult?: Promise<ExecResult>;\n\n constructor(options: ShellPromiseOptions) {\n this.executor = options.execute;\n this.cwdOverride = options.cwdOverride;\n this.envOverride = options.envOverride;\n this.shouldThrow = options.shouldThrow ?? true;\n this.isQuiet = options.quiet ?? false;\n }\n\n private async run(): Promise<ExecResult> {\n if (!this.cachedResult) {\n this.cachedResult = this.executor({\n cwd: this.cwdOverride,\n env: this.envOverride,\n });\n }\n\n const result = await this.cachedResult;\n\n if (this.shouldThrow && result.exitCode !== 0) {\n throw new ShellError(\n `Command failed with exit code ${result.exitCode}`,\n result.stdout,\n result.stderr,\n result.exitCode\n );\n }\n\n return result;\n }\n\n then<TResult1 = ExecResult, TResult2 = never>(\n onfulfilled?: ((value: ExecResult) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null\n ): Promise<TResult1 | TResult2> {\n return this.run().then(onfulfilled, onrejected);\n }\n\n catch<TResult = never>(\n onrejected?: ((reason: unknown) => TResult | PromiseLike<TResult>) | null\n ): Promise<ExecResult | TResult> {\n return this.run().catch(onrejected);\n }\n\n finally(onfinally?: (() => void) | null): Promise<ExecResult> {\n return this.run().finally(onfinally);\n }\n\n // Output formats\n async text(): Promise<string> {\n const result = await this.run();\n return result.stdout.toString(\"utf-8\");\n }\n\n async json<T = unknown>(): Promise<T> {\n const text = await this.text();\n return JSON.parse(text);\n }\n\n async *lines(): AsyncIterable<string> {\n const text = await this.text();\n const lines = text.split(\"\\n\");\n // Remove trailing empty line if present\n if (lines[lines.length - 1] === \"\") {\n lines.pop();\n }\n for (const line of lines) {\n yield line;\n }\n }\n\n async blob(): Promise<Blob> {\n const result = await this.run();\n return new Blob([new Uint8Array(result.stdout)]);\n }\n\n async buffer(): Promise<Buffer> {\n const result = await this.run();\n return result.stdout;\n }\n\n // Behavior modifiers - return new ShellPromise with modified options\n quiet(): ShellPromise {\n return new ShellPromise({\n execute: this.executor,\n cwdOverride: this.cwdOverride,\n envOverride: this.envOverride,\n shouldThrow: this.shouldThrow,\n quiet: true,\n });\n }\n\n nothrow(): ShellPromise {\n return new ShellPromise({\n execute: this.executor,\n cwdOverride: this.cwdOverride,\n envOverride: this.envOverride,\n shouldThrow: false,\n quiet: this.isQuiet,\n });\n }\n\n throws(enable: boolean): ShellPromise {\n return new ShellPromise({\n execute: this.executor,\n cwdOverride: this.cwdOverride,\n envOverride: this.envOverride,\n shouldThrow: enable,\n quiet: this.isQuiet,\n });\n }\n\n // Context overrides - these need to be handled by the shell\n cwd(path: string): ShellPromise {\n return new ShellPromise({\n execute: this.executor,\n cwdOverride: path,\n envOverride: this.envOverride,\n shouldThrow: this.shouldThrow,\n quiet: this.isQuiet,\n });\n }\n\n env(vars: Record<string, string>): ShellPromise {\n return new ShellPromise({\n execute: this.executor,\n cwdOverride: this.cwdOverride,\n envOverride: { ...this.envOverride, ...vars },\n shouldThrow: this.shouldThrow,\n quiet: this.isQuiet,\n });\n }\n\n // Getters for internal state (used by ShellDSL)\n getCwdOverride(): string | undefined {\n return this.cwdOverride;\n }\n\n getEnvOverride(): Record<string, string> | undefined {\n return this.envOverride;\n }\n\n getShouldThrow(): boolean {\n return this.shouldThrow;\n }\n\n getIsQuiet(): boolean {\n return this.isQuiet;\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAC2B,IAA3B;AAAA;AAeO,MAAM,aAAgD;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAER,WAAW,CAAC,SAA8B;AAAA,IACxC,KAAK,WAAW,QAAQ;AAAA,IACxB,KAAK,cAAc,QAAQ;AAAA,IAC3B,KAAK,cAAc,QAAQ;AAAA,IAC3B,KAAK,cAAc,QAAQ,eAAe;AAAA,IAC1C,KAAK,UAAU,QAAQ,SAAS;AAAA;AAAA,OAGpB,IAAG,GAAwB;AAAA,IACvC,IAAI,CAAC,KAAK,cAAc;AAAA,MACtB,KAAK,eAAe,KAAK,SAAS;AAAA,QAChC,KAAK,KAAK;AAAA,QACV,KAAK,KAAK;AAAA,MACZ,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,SAAS,MAAM,KAAK;AAAA,IAE1B,IAAI,KAAK,eAAe,OAAO,aAAa,GAAG;AAAA,MAC7C,MAAM,IAAI,yBACR,iCAAiC,OAAO,YACxC,OAAO,QACP,OAAO,QACP,OAAO,QACT;AAAA,IACF;AAAA,IAEA,OAAO;AAAA;AAAA,EAGT,IAA6C,CAC3C,aACA,YAC8B;AAAA,IAC9B,OAAO,KAAK,IAAI,EAAE,KAAK,aAAa,UAAU;AAAA;AAAA,EAGhD,KAAsB,CACpB,YAC+B;AAAA,IAC/B,OAAO,KAAK,IAAI,EAAE,MAAM,UAAU;AAAA;AAAA,EAGpC,OAAO,CAAC,WAAsD;AAAA,IAC5D,OAAO,KAAK,IAAI,EAAE,QAAQ,SAAS;AAAA;AAAA,OAI/B,KAAI,GAAoB;AAAA,IAC5B,MAAM,SAAS,MAAM,KAAK,IAAI;AAAA,IAC9B,OAAO,OAAO,OAAO,SAAS,OAAO;AAAA;AAAA,OAGjC,KAAiB,GAAe;AAAA,IACpC,MAAM,OAAO,MAAM,KAAK,KAAK;AAAA,IAC7B,OAAO,KAAK,MAAM,IAAI;AAAA;AAAA,SAGjB,KAAK,GAA0B;AAAA,IACpC,MAAM,OAAO,MAAM,KAAK,KAAK;AAAA,IAC7B,MAAM,QAAQ,KAAK,MAAM;AAAA,CAAI;AAAA,IAE7B,IAAI,MAAM,MAAM,SAAS,OAAO,IAAI;AAAA,MAClC,MAAM,IAAI;AAAA,IACZ;AAAA,IACA,WAAW,QAAQ,OAAO;AAAA,MACxB,MAAM;AAAA,IACR;AAAA;AAAA,OAGI,KAAI,GAAkB;AAAA,IAC1B,MAAM,SAAS,MAAM,KAAK,IAAI;AAAA,IAC9B,OAAO,IAAI,KAAK,CAAC,OAAO,MAAM,CAAC;AAAA;AAAA,
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "DFBD1041E32C91CE64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/mjs/package.json
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
import { createVirtualFS } from "./memfs-adapter.mjs";
|
|
3
3
|
import { FileSystem } from "./real-fs.mjs";
|
|
4
4
|
import { ReadOnlyFileSystem } from "./readonly-fs.mjs";
|
|
5
|
+
import { WebFileSystem, createWebUnderlyingFS } from "./web-fs.mjs";
|
|
5
6
|
export {
|
|
7
|
+
createWebUnderlyingFS,
|
|
6
8
|
createVirtualFS,
|
|
9
|
+
WebFileSystem,
|
|
7
10
|
ReadOnlyFileSystem,
|
|
8
11
|
FileSystem
|
|
9
12
|
};
|
|
10
13
|
|
|
11
|
-
//# debugId=
|
|
14
|
+
//# debugId=48608B4B3C7BEB9864756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/fs/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"export { createVirtualFS } from \"./memfs-adapter.mjs\";\nexport { FileSystem, type Permission, type PermissionRules, type UnderlyingFS } from \"./real-fs.mjs\";\nexport { ReadOnlyFileSystem } from \"./readonly-fs.mjs\";\n"
|
|
5
|
+
"export { createVirtualFS } from \"./memfs-adapter.mjs\";\nexport { FileSystem, type PathOps, type Permission, type PermissionRules, type UnderlyingFS } from \"./real-fs.mjs\";\nexport { ReadOnlyFileSystem } from \"./readonly-fs.mjs\";\nexport { WebFileSystem, createWebUnderlyingFS } from \"./web-fs.mjs\";\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";AAAA;AACA;AACA;",
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";AAAA;AACA;AACA;AACA;",
|
|
8
|
+
"debugId": "48608B4B3C7BEB9864756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -2,15 +2,28 @@
|
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import * as nodeFs from "node:fs/promises";
|
|
4
4
|
var defaultFS = { promises: nodeFs };
|
|
5
|
+
var nodePathOps = {
|
|
6
|
+
separator: path.sep,
|
|
7
|
+
resolve: (...paths) => path.resolve(...paths),
|
|
8
|
+
normalize: (filePath) => path.normalize(filePath),
|
|
9
|
+
join: (...paths) => path.join(...paths),
|
|
10
|
+
relative: (from, to) => path.relative(from, to),
|
|
11
|
+
isAbsolute: (filePath) => path.isAbsolute(filePath),
|
|
12
|
+
dirname: (filePath) => path.dirname(filePath),
|
|
13
|
+
basename: (filePath) => path.basename(filePath)
|
|
14
|
+
};
|
|
5
15
|
|
|
6
16
|
class FileSystem {
|
|
7
17
|
mountBase;
|
|
8
18
|
rules;
|
|
19
|
+
pathOps;
|
|
9
20
|
underlyingFs;
|
|
10
21
|
constructor(mountPath, permissions, fs) {
|
|
11
|
-
|
|
22
|
+
const underlyingFs = fs ?? defaultFS;
|
|
23
|
+
this.pathOps = underlyingFs.pathOps ?? nodePathOps;
|
|
24
|
+
this.mountBase = mountPath ? this.pathOps.resolve(mountPath) : null;
|
|
12
25
|
this.rules = this.compileRules(permissions ?? {});
|
|
13
|
-
this.underlyingFs =
|
|
26
|
+
this.underlyingFs = underlyingFs;
|
|
14
27
|
}
|
|
15
28
|
compileRules(permissions) {
|
|
16
29
|
return Object.entries(permissions).map(([pattern, permission]) => ({
|
|
@@ -60,7 +73,7 @@ class FileSystem {
|
|
|
60
73
|
}
|
|
61
74
|
resolveSafePath(virtualPath) {
|
|
62
75
|
if (this.mountBase === null) {
|
|
63
|
-
return
|
|
76
|
+
return this.pathOps.resolve(virtualPath);
|
|
64
77
|
}
|
|
65
78
|
const segments = virtualPath.split("/").filter(Boolean);
|
|
66
79
|
let depth = 0;
|
|
@@ -74,11 +87,13 @@ class FileSystem {
|
|
|
74
87
|
depth++;
|
|
75
88
|
}
|
|
76
89
|
}
|
|
77
|
-
const normalized =
|
|
90
|
+
const normalized = this.pathOps.normalize(virtualPath);
|
|
78
91
|
const relativePath = normalized.startsWith("/") ? normalized.slice(1) : normalized;
|
|
79
|
-
const realPath =
|
|
80
|
-
const resolved =
|
|
81
|
-
|
|
92
|
+
const realPath = this.pathOps.join(this.mountBase, relativePath);
|
|
93
|
+
const resolved = this.pathOps.resolve(realPath);
|
|
94
|
+
const relativeFromMount = this.pathOps.relative(this.mountBase, resolved);
|
|
95
|
+
const escapesMount = relativeFromMount === ".." || relativeFromMount.startsWith(`..${this.pathOps.separator}`) || this.pathOps.isAbsolute(relativeFromMount);
|
|
96
|
+
if (escapesMount) {
|
|
82
97
|
throw new Error(`Path traversal blocked: "${virtualPath}" escapes mount point`);
|
|
83
98
|
}
|
|
84
99
|
return resolved;
|
|
@@ -138,13 +153,13 @@ class FileSystem {
|
|
|
138
153
|
await this.underlyingFs.promises.rm(realPath, opts);
|
|
139
154
|
}
|
|
140
155
|
resolve(...paths) {
|
|
141
|
-
return
|
|
156
|
+
return this.pathOps.resolve("/", ...paths);
|
|
142
157
|
}
|
|
143
158
|
dirname(filePath) {
|
|
144
|
-
return
|
|
159
|
+
return this.pathOps.dirname(filePath);
|
|
145
160
|
}
|
|
146
161
|
basename(filePath) {
|
|
147
|
-
return
|
|
162
|
+
return this.pathOps.basename(filePath);
|
|
148
163
|
}
|
|
149
164
|
async glob(pattern, opts) {
|
|
150
165
|
const cwd = opts?.cwd ?? "/";
|
|
@@ -177,8 +192,8 @@ class FileSystem {
|
|
|
177
192
|
}
|
|
178
193
|
async matchPattern(pattern, cwd) {
|
|
179
194
|
const parts = pattern.split("/").filter((p) => p !== "");
|
|
180
|
-
const
|
|
181
|
-
const startDir =
|
|
195
|
+
const isAbsolute2 = pattern.startsWith("/");
|
|
196
|
+
const startDir = isAbsolute2 ? "/" : cwd;
|
|
182
197
|
return this.matchParts(parts, startDir);
|
|
183
198
|
}
|
|
184
199
|
async matchParts(parts, currentPath) {
|
|
@@ -194,7 +209,7 @@ class FileSystem {
|
|
|
194
209
|
const realPath = this.resolveSafePath(currentPath);
|
|
195
210
|
const entries = await this.underlyingFs.promises.readdir(realPath);
|
|
196
211
|
for (const entry of entries) {
|
|
197
|
-
const entryPath =
|
|
212
|
+
const entryPath = this.pathOps.join(currentPath, String(entry));
|
|
198
213
|
try {
|
|
199
214
|
const entryRealPath = this.resolveSafePath(entryPath);
|
|
200
215
|
const stat = await this.underlyingFs.promises.stat(entryRealPath);
|
|
@@ -215,7 +230,7 @@ class FileSystem {
|
|
|
215
230
|
for (const entry of entries) {
|
|
216
231
|
const entryName = String(entry);
|
|
217
232
|
if (regex.test(entryName)) {
|
|
218
|
-
const entryPath =
|
|
233
|
+
const entryPath = this.pathOps.join(currentPath, entryName);
|
|
219
234
|
if (rest.length === 0) {
|
|
220
235
|
results.push(entryPath);
|
|
221
236
|
} else {
|
|
@@ -266,4 +281,4 @@ export {
|
|
|
266
281
|
FileSystem
|
|
267
282
|
};
|
|
268
283
|
|
|
269
|
-
//# debugId=
|
|
284
|
+
//# debugId=FEA6FDDF849C4AB964756E2164756E21
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/fs/real-fs.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import * as path from \"path\";\nimport * as nodeFs from \"node:fs/promises\";\nimport type { VirtualFS, FileStat } from \"../types.mjs\";\n\nexport type Permission = \"read-write\" | \"read-only\" | \"excluded\";\nexport type PermissionRules = Record<string, Permission>;\n\n// Minimal interface for the underlying fs (compatible with node:fs and memfs)\nexport interface UnderlyingFS {\n promises: {\n readFile(path: string): Promise<Buffer | Uint8Array | string>;\n readdir(path: string): Promise<string[]>;\n stat(path: string): Promise<{\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n }>;\n writeFile(path: string, data: Buffer | string): Promise<void>;\n appendFile(path: string, data: Buffer | string): Promise<void>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<string | undefined | void>;\n rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void>;\n };\n}\n\n// Default: use real node:fs\nconst defaultFS: UnderlyingFS = { promises: nodeFs };\n\ninterface CompiledRule {\n pattern: string;\n permission: Permission;\n specificity: number;\n}\n\nexport class FileSystem implements VirtualFS {\n private readonly mountBase: string | null;\n private readonly rules: CompiledRule[];\n protected readonly underlyingFs: UnderlyingFS;\n\n constructor(mountPath?: string, permissions?: PermissionRules, fs?: UnderlyingFS) {\n this.mountBase = mountPath ? path.resolve(mountPath) : null;\n this.rules = this.compileRules(permissions ?? {});\n this.underlyingFs = fs ?? defaultFS;\n }\n\n private compileRules(permissions: PermissionRules): CompiledRule[] {\n return Object.entries(permissions)\n .map(([pattern, permission]) => ({\n pattern,\n permission,\n specificity: this.calculateSpecificity(pattern),\n }))\n .sort((a, b) => b.specificity - a.specificity); // highest first\n }\n\n private calculateSpecificity(pattern: string): number {\n const segments = pattern.split(\"/\").filter(Boolean);\n let score = segments.length * 1000; // segment count is primary\n\n for (const seg of segments) {\n if (seg === \"**\") score += 0;\n else if (seg.includes(\"*\")) score += 1;\n else score += 10; // literal segment\n }\n return score;\n }\n\n public getPermission(virtualPath: string): Permission {\n const normalized = virtualPath.replace(/^\\/+/, \"\"); // strip leading slashes\n\n for (const rule of this.rules) {\n if (this.matchGlob(rule.pattern, normalized)) {\n return rule.permission;\n }\n }\n return \"read-write\"; // default\n }\n\n private matchGlob(pattern: string, filePath: string): boolean {\n // Convert glob to regex\n // ** matches any path segments, * matches within segment\n const regex = pattern\n .split(\"/\")\n .map((seg) => {\n if (seg === \"**\") return \".*\";\n return seg.replace(/\\*/g, \"[^/]*\").replace(/\\?/g, \"[^/]\");\n })\n .join(\"/\");\n return new RegExp(`^${regex}$`).test(filePath);\n }\n\n public checkPermission(virtualPath: string, operation: \"read\" | \"write\"): void {\n const perm = this.getPermission(virtualPath);\n\n if (perm === \"excluded\") {\n throw new Error(`Access denied: \"${virtualPath}\" is excluded`);\n }\n if (operation === \"write\" && perm === \"read-only\") {\n throw new Error(`Access denied: \"${virtualPath}\" is read-only`);\n }\n }\n\n private resolveSafePath(virtualPath: string): string {\n if (this.mountBase === null) {\n return path.resolve(virtualPath);\n }\n\n // Check for path traversal by tracking depth\n const segments = virtualPath.split(\"/\").filter(Boolean);\n let depth = 0;\n for (const seg of segments) {\n if (seg === \"..\") {\n depth--;\n if (depth < 0) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n } else if (seg !== \".\") {\n depth++;\n }\n }\n\n const normalized = path.normalize(virtualPath);\n const relativePath = normalized.startsWith(\"/\") ? normalized.slice(1) : normalized;\n const realPath = path.join(this.mountBase, relativePath);\n const resolved = path.resolve(realPath);\n\n // Double-check containment (defense in depth)\n if (!resolved.startsWith(this.mountBase + path.sep) && resolved !== this.mountBase) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n\n return resolved;\n }\n\n // Read operations\n async readFile(filePath: string): Promise<Buffer>;\n async readFile(filePath: string, encoding: BufferEncoding): Promise<string>;\n async readFile(filePath: string, encoding?: BufferEncoding): Promise<Buffer | string> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const content = await this.underlyingFs.promises.readFile(realPath);\n const buf = Buffer.from(content);\n return encoding ? buf.toString(encoding) : buf;\n }\n\n async readdir(dirPath: string): Promise<string[]> {\n this.checkPermission(dirPath, \"read\");\n const realPath = this.resolveSafePath(dirPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n return entries.map(String);\n }\n\n async stat(filePath: string): Promise<FileStat> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const stats = await this.underlyingFs.promises.stat(realPath);\n return {\n isFile: () => stats.isFile(),\n isDirectory: () => stats.isDirectory(),\n size: stats.size,\n mtime: stats.mtime,\n };\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.stat(realPath);\n return true;\n } catch {\n return false;\n }\n }\n\n // Write operations\n async writeFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.writeFile(realPath, data);\n }\n\n async appendFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.appendFile(realPath, data);\n }\n\n async mkdir(dirPath: string, opts?: { recursive?: boolean }): Promise<void> {\n this.checkPermission(dirPath, \"write\");\n const realPath = this.resolveSafePath(dirPath);\n await this.underlyingFs.promises.mkdir(realPath, opts);\n }\n\n async rm(filePath: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.rm(realPath, opts);\n }\n\n // Path utilities (no permission check needed)\n resolve(...paths: string[]): string {\n return path.resolve(\"/\", ...paths);\n }\n\n dirname(filePath: string): string {\n return path.dirname(filePath);\n }\n\n basename(filePath: string): string {\n return path.basename(filePath);\n }\n\n // Glob with permission filtering\n async glob(pattern: string, opts?: { cwd?: string }): Promise<string[]> {\n const cwd = opts?.cwd ?? \"/\";\n this.checkPermission(cwd, \"read\");\n\n const matches = await this.expandGlob(pattern, cwd);\n\n // Filter out excluded paths\n return matches.filter((p) => this.getPermission(p) !== \"excluded\").sort();\n }\n\n // Glob expansion (similar to memfs-adapter implementation)\n private async expandGlob(pattern: string, cwd: string): Promise<string[]> {\n // Handle brace expansion first\n const patterns = this.expandBraces(pattern);\n const allMatches: string[] = [];\n\n for (const pat of patterns) {\n const matches = await this.matchPattern(pat, cwd);\n allMatches.push(...matches);\n }\n\n // Remove duplicates and sort\n return [...new Set(allMatches)].sort();\n }\n\n private expandBraces(pattern: string): string[] {\n const braceMatch = pattern.match(/\\{([^{}]+)\\}/);\n if (!braceMatch) return [pattern];\n\n const before = pattern.slice(0, braceMatch.index);\n const after = pattern.slice(braceMatch.index! + braceMatch[0].length);\n const options = braceMatch[1]!.split(\",\");\n\n const results: string[] = [];\n for (const opt of options) {\n const expanded = this.expandBraces(before + opt + after);\n results.push(...expanded);\n }\n return results;\n }\n\n private async matchPattern(pattern: string, cwd: string): Promise<string[]> {\n const parts = pattern.split(\"/\").filter((p) => p !== \"\");\n const isAbsolute = pattern.startsWith(\"/\");\n const startDir = isAbsolute ? \"/\" : cwd;\n\n return this.matchParts(parts, startDir);\n }\n\n private async matchParts(parts: string[], currentPath: string): Promise<string[]> {\n if (parts.length === 0) {\n return [currentPath];\n }\n\n const [part, ...rest] = parts;\n\n // Handle ** (recursive glob)\n if (part === \"**\") {\n const results: string[] = [];\n\n // Match current directory\n const withoutStar = await this.matchParts(rest, currentPath);\n results.push(...withoutStar);\n\n // Recurse into subdirectories\n try {\n const realPath = this.resolveSafePath(currentPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n for (const entry of entries) {\n const entryPath = path.posix.join(currentPath, String(entry));\n try {\n const entryRealPath = this.resolveSafePath(entryPath);\n const stat = await this.underlyingFs.promises.stat(entryRealPath);\n if (stat.isDirectory()) {\n const subMatches = await this.matchParts(parts, entryPath);\n results.push(...subMatches);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n } catch {\n // Directory not readable\n }\n\n return results;\n }\n\n // Handle regular glob patterns\n const regex = this.globToRegex(part!);\n\n try {\n const realPath = this.resolveSafePath(currentPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n const results: string[] = [];\n\n for (const entry of entries) {\n const entryName = String(entry);\n if (regex.test(entryName)) {\n const entryPath = path.posix.join(currentPath, entryName);\n if (rest.length === 0) {\n results.push(entryPath);\n } else {\n try {\n const entryRealPath = this.resolveSafePath(entryPath);\n const stat = await this.underlyingFs.promises.stat(entryRealPath);\n if (stat.isDirectory()) {\n const subMatches = await this.matchParts(rest, entryPath);\n results.push(...subMatches);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n }\n }\n\n return results;\n } catch {\n return [];\n }\n }\n\n private globToRegex(pattern: string): RegExp {\n let regex = \"^\";\n for (let i = 0; i < pattern.length; i++) {\n const char = pattern[i]!;\n if (char === \"*\") {\n regex += \"[^/]*\";\n } else if (char === \"?\") {\n regex += \"[^/]\";\n } else if (char === \"[\") {\n // Character class\n let j = i + 1;\n let classContent = \"\";\n while (j < pattern.length && pattern[j] !== \"]\") {\n classContent += pattern[j];\n j++;\n }\n regex += `[${classContent}]`;\n i = j;\n } else if (\".+^${}()|\\\\\".includes(char)) {\n regex += \"\\\\\" + char;\n } else {\n regex += char;\n }\n }\n regex += \"$\";\n return new RegExp(regex);\n }\n}\n"
|
|
5
|
+
"import * as path from \"path\";\nimport * as nodeFs from \"node:fs/promises\";\nimport type { VirtualFS, FileStat } from \"../types.mjs\";\n\nexport type Permission = \"read-write\" | \"read-only\" | \"excluded\";\nexport type PermissionRules = Record<string, Permission>;\nexport interface PathOps {\n readonly separator: string;\n resolve(...paths: string[]): string;\n normalize(path: string): string;\n join(...paths: string[]): string;\n relative(from: string, to: string): string;\n isAbsolute(path: string): boolean;\n dirname(path: string): string;\n basename(path: string): string;\n}\n\n// Minimal interface for the underlying fs (compatible with node:fs and memfs)\nexport interface UnderlyingFS {\n pathOps?: PathOps;\n promises: {\n readFile(path: string): Promise<Buffer | Uint8Array | string>;\n readdir(path: string): Promise<string[]>;\n stat(path: string): Promise<{\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n }>;\n writeFile(path: string, data: Buffer | string): Promise<void>;\n appendFile(path: string, data: Buffer | string): Promise<void>;\n mkdir(path: string, opts?: { recursive?: boolean }): Promise<string | undefined | void>;\n rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void>;\n };\n}\n\n// Default: use real node:fs\nconst defaultFS: UnderlyingFS = { promises: nodeFs };\nconst nodePathOps: PathOps = {\n separator: path.sep,\n resolve: (...paths) => path.resolve(...paths),\n normalize: (filePath) => path.normalize(filePath),\n join: (...paths) => path.join(...paths),\n relative: (from, to) => path.relative(from, to),\n isAbsolute: (filePath) => path.isAbsolute(filePath),\n dirname: (filePath) => path.dirname(filePath),\n basename: (filePath) => path.basename(filePath),\n};\n\ninterface CompiledRule {\n pattern: string;\n permission: Permission;\n specificity: number;\n}\n\nexport class FileSystem implements VirtualFS {\n private readonly mountBase: string | null;\n private readonly rules: CompiledRule[];\n private readonly pathOps: PathOps;\n protected readonly underlyingFs: UnderlyingFS;\n\n constructor(mountPath?: string, permissions?: PermissionRules, fs?: UnderlyingFS) {\n const underlyingFs = fs ?? defaultFS;\n this.pathOps = underlyingFs.pathOps ?? nodePathOps;\n this.mountBase = mountPath ? this.pathOps.resolve(mountPath) : null;\n this.rules = this.compileRules(permissions ?? {});\n this.underlyingFs = underlyingFs;\n }\n\n private compileRules(permissions: PermissionRules): CompiledRule[] {\n return Object.entries(permissions)\n .map(([pattern, permission]) => ({\n pattern,\n permission,\n specificity: this.calculateSpecificity(pattern),\n }))\n .sort((a, b) => b.specificity - a.specificity); // highest first\n }\n\n private calculateSpecificity(pattern: string): number {\n const segments = pattern.split(\"/\").filter(Boolean);\n let score = segments.length * 1000; // segment count is primary\n\n for (const seg of segments) {\n if (seg === \"**\") score += 0;\n else if (seg.includes(\"*\")) score += 1;\n else score += 10; // literal segment\n }\n return score;\n }\n\n public getPermission(virtualPath: string): Permission {\n const normalized = virtualPath.replace(/^\\/+/, \"\"); // strip leading slashes\n\n for (const rule of this.rules) {\n if (this.matchGlob(rule.pattern, normalized)) {\n return rule.permission;\n }\n }\n return \"read-write\"; // default\n }\n\n private matchGlob(pattern: string, filePath: string): boolean {\n // Convert glob to regex\n // ** matches any path segments, * matches within segment\n const regex = pattern\n .split(\"/\")\n .map((seg) => {\n if (seg === \"**\") return \".*\";\n return seg.replace(/\\*/g, \"[^/]*\").replace(/\\?/g, \"[^/]\");\n })\n .join(\"/\");\n return new RegExp(`^${regex}$`).test(filePath);\n }\n\n public checkPermission(virtualPath: string, operation: \"read\" | \"write\"): void {\n const perm = this.getPermission(virtualPath);\n\n if (perm === \"excluded\") {\n throw new Error(`Access denied: \"${virtualPath}\" is excluded`);\n }\n if (operation === \"write\" && perm === \"read-only\") {\n throw new Error(`Access denied: \"${virtualPath}\" is read-only`);\n }\n }\n\n private resolveSafePath(virtualPath: string): string {\n if (this.mountBase === null) {\n return this.pathOps.resolve(virtualPath);\n }\n\n // Check for path traversal by tracking depth\n const segments = virtualPath.split(\"/\").filter(Boolean);\n let depth = 0;\n for (const seg of segments) {\n if (seg === \"..\") {\n depth--;\n if (depth < 0) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n } else if (seg !== \".\") {\n depth++;\n }\n }\n\n const normalized = this.pathOps.normalize(virtualPath);\n const relativePath = normalized.startsWith(\"/\") ? normalized.slice(1) : normalized;\n const realPath = this.pathOps.join(this.mountBase, relativePath);\n const resolved = this.pathOps.resolve(realPath);\n\n // Double-check containment (defense in depth), including root mounts.\n const relativeFromMount = this.pathOps.relative(this.mountBase, resolved);\n const escapesMount =\n relativeFromMount === \"..\" ||\n relativeFromMount.startsWith(`..${this.pathOps.separator}`) ||\n this.pathOps.isAbsolute(relativeFromMount);\n if (escapesMount) {\n throw new Error(`Path traversal blocked: \"${virtualPath}\" escapes mount point`);\n }\n\n return resolved;\n }\n\n // Read operations\n async readFile(filePath: string): Promise<Buffer>;\n async readFile(filePath: string, encoding: BufferEncoding): Promise<string>;\n async readFile(filePath: string, encoding?: BufferEncoding): Promise<Buffer | string> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const content = await this.underlyingFs.promises.readFile(realPath);\n const buf = Buffer.from(content);\n return encoding ? buf.toString(encoding) : buf;\n }\n\n async readdir(dirPath: string): Promise<string[]> {\n this.checkPermission(dirPath, \"read\");\n const realPath = this.resolveSafePath(dirPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n return entries.map(String);\n }\n\n async stat(filePath: string): Promise<FileStat> {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n const stats = await this.underlyingFs.promises.stat(realPath);\n return {\n isFile: () => stats.isFile(),\n isDirectory: () => stats.isDirectory(),\n size: stats.size,\n mtime: stats.mtime,\n };\n }\n\n async exists(filePath: string): Promise<boolean> {\n try {\n this.checkPermission(filePath, \"read\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.stat(realPath);\n return true;\n } catch {\n return false;\n }\n }\n\n // Write operations\n async writeFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.writeFile(realPath, data);\n }\n\n async appendFile(filePath: string, data: Buffer | string): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.appendFile(realPath, data);\n }\n\n async mkdir(dirPath: string, opts?: { recursive?: boolean }): Promise<void> {\n this.checkPermission(dirPath, \"write\");\n const realPath = this.resolveSafePath(dirPath);\n await this.underlyingFs.promises.mkdir(realPath, opts);\n }\n\n async rm(filePath: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\n this.checkPermission(filePath, \"write\");\n const realPath = this.resolveSafePath(filePath);\n await this.underlyingFs.promises.rm(realPath, opts);\n }\n\n // Path utilities (no permission check needed)\n resolve(...paths: string[]): string {\n return this.pathOps.resolve(\"/\", ...paths);\n }\n\n dirname(filePath: string): string {\n return this.pathOps.dirname(filePath);\n }\n\n basename(filePath: string): string {\n return this.pathOps.basename(filePath);\n }\n\n // Glob with permission filtering\n async glob(pattern: string, opts?: { cwd?: string }): Promise<string[]> {\n const cwd = opts?.cwd ?? \"/\";\n this.checkPermission(cwd, \"read\");\n\n const matches = await this.expandGlob(pattern, cwd);\n\n // Filter out excluded paths\n return matches.filter((p) => this.getPermission(p) !== \"excluded\").sort();\n }\n\n // Glob expansion (similar to memfs-adapter implementation)\n private async expandGlob(pattern: string, cwd: string): Promise<string[]> {\n // Handle brace expansion first\n const patterns = this.expandBraces(pattern);\n const allMatches: string[] = [];\n\n for (const pat of patterns) {\n const matches = await this.matchPattern(pat, cwd);\n allMatches.push(...matches);\n }\n\n // Remove duplicates and sort\n return [...new Set(allMatches)].sort();\n }\n\n private expandBraces(pattern: string): string[] {\n const braceMatch = pattern.match(/\\{([^{}]+)\\}/);\n if (!braceMatch) return [pattern];\n\n const before = pattern.slice(0, braceMatch.index);\n const after = pattern.slice(braceMatch.index! + braceMatch[0].length);\n const options = braceMatch[1]!.split(\",\");\n\n const results: string[] = [];\n for (const opt of options) {\n const expanded = this.expandBraces(before + opt + after);\n results.push(...expanded);\n }\n return results;\n }\n\n private async matchPattern(pattern: string, cwd: string): Promise<string[]> {\n const parts = pattern.split(\"/\").filter((p) => p !== \"\");\n const isAbsolute = pattern.startsWith(\"/\");\n const startDir = isAbsolute ? \"/\" : cwd;\n\n return this.matchParts(parts, startDir);\n }\n\n private async matchParts(parts: string[], currentPath: string): Promise<string[]> {\n if (parts.length === 0) {\n return [currentPath];\n }\n\n const [part, ...rest] = parts;\n\n // Handle ** (recursive glob)\n if (part === \"**\") {\n const results: string[] = [];\n\n // Match current directory\n const withoutStar = await this.matchParts(rest, currentPath);\n results.push(...withoutStar);\n\n // Recurse into subdirectories\n try {\n const realPath = this.resolveSafePath(currentPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n for (const entry of entries) {\n const entryPath = this.pathOps.join(currentPath, String(entry));\n try {\n const entryRealPath = this.resolveSafePath(entryPath);\n const stat = await this.underlyingFs.promises.stat(entryRealPath);\n if (stat.isDirectory()) {\n const subMatches = await this.matchParts(parts, entryPath);\n results.push(...subMatches);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n } catch {\n // Directory not readable\n }\n\n return results;\n }\n\n // Handle regular glob patterns\n const regex = this.globToRegex(part!);\n\n try {\n const realPath = this.resolveSafePath(currentPath);\n const entries = await this.underlyingFs.promises.readdir(realPath);\n const results: string[] = [];\n\n for (const entry of entries) {\n const entryName = String(entry);\n if (regex.test(entryName)) {\n const entryPath = this.pathOps.join(currentPath, entryName);\n if (rest.length === 0) {\n results.push(entryPath);\n } else {\n try {\n const entryRealPath = this.resolveSafePath(entryPath);\n const stat = await this.underlyingFs.promises.stat(entryRealPath);\n if (stat.isDirectory()) {\n const subMatches = await this.matchParts(rest, entryPath);\n results.push(...subMatches);\n }\n } catch {\n // Skip inaccessible entries\n }\n }\n }\n }\n\n return results;\n } catch {\n return [];\n }\n }\n\n private globToRegex(pattern: string): RegExp {\n let regex = \"^\";\n for (let i = 0; i < pattern.length; i++) {\n const char = pattern[i]!;\n if (char === \"*\") {\n regex += \"[^/]*\";\n } else if (char === \"?\") {\n regex += \"[^/]\";\n } else if (char === \"[\") {\n // Character class\n let j = i + 1;\n let classContent = \"\";\n while (j < pattern.length && pattern[j] !== \"]\") {\n classContent += pattern[j];\n j++;\n }\n regex += `[${classContent}]`;\n i = j;\n } else if (\".+^${}()|\\\\\".includes(char)) {\n regex += \"\\\\\" + char;\n } else {\n regex += char;\n }\n }\n regex += \"$\";\n return new RegExp(regex);\n }\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";AAAA;AACA;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "FEA6FDDF849C4AB964756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
// src/fs/web-fs.ts
|
|
2
|
+
import { FileSystem } from "./real-fs.mjs";
|
|
3
|
+
var DIRECTORY_MTIME = new Date(0);
|
|
4
|
+
var WEB_PATH_OPS = {
|
|
5
|
+
separator: "/",
|
|
6
|
+
resolve(...paths) {
|
|
7
|
+
return normalizeWebPath(paths.join("/"));
|
|
8
|
+
},
|
|
9
|
+
normalize: normalizeWebPath,
|
|
10
|
+
join(...paths) {
|
|
11
|
+
const nonEmpty = paths.filter((segment) => segment.length > 0);
|
|
12
|
+
if (nonEmpty.length === 0) {
|
|
13
|
+
return ".";
|
|
14
|
+
}
|
|
15
|
+
return normalizeWebPath(nonEmpty.join("/"));
|
|
16
|
+
},
|
|
17
|
+
relative(from, to) {
|
|
18
|
+
const fromSegments = getPathSegments(from);
|
|
19
|
+
const toSegments = getPathSegments(to);
|
|
20
|
+
let shared = 0;
|
|
21
|
+
while (shared < fromSegments.length && shared < toSegments.length && fromSegments[shared] === toSegments[shared]) {
|
|
22
|
+
shared++;
|
|
23
|
+
}
|
|
24
|
+
const up = new Array(fromSegments.length - shared).fill("..");
|
|
25
|
+
const down = toSegments.slice(shared);
|
|
26
|
+
return [...up, ...down].join("/");
|
|
27
|
+
},
|
|
28
|
+
isAbsolute(path) {
|
|
29
|
+
return path.startsWith("/");
|
|
30
|
+
},
|
|
31
|
+
dirname(path) {
|
|
32
|
+
const normalized = normalizeWebPath(path);
|
|
33
|
+
if (normalized === "/") {
|
|
34
|
+
return "/";
|
|
35
|
+
}
|
|
36
|
+
const segments = getPathSegments(normalized);
|
|
37
|
+
if (segments.length <= 1) {
|
|
38
|
+
return "/";
|
|
39
|
+
}
|
|
40
|
+
return `/${segments.slice(0, -1).join("/")}`;
|
|
41
|
+
},
|
|
42
|
+
basename(path) {
|
|
43
|
+
const normalized = normalizeWebPath(path);
|
|
44
|
+
if (normalized === "/") {
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
const segments = getPathSegments(normalized);
|
|
48
|
+
return segments[segments.length - 1] ?? "";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
function createWebUnderlyingFS(root) {
|
|
52
|
+
return {
|
|
53
|
+
pathOps: WEB_PATH_OPS,
|
|
54
|
+
promises: {
|
|
55
|
+
async readFile(path) {
|
|
56
|
+
const { parentSegments, name } = splitParent(path);
|
|
57
|
+
const parent = await walkDirectory(root, parentSegments, false);
|
|
58
|
+
const fileHandle = await parent.getFileHandle(name, { create: false });
|
|
59
|
+
const file = await fileHandle.getFile();
|
|
60
|
+
return Buffer.from(await file.arrayBuffer());
|
|
61
|
+
},
|
|
62
|
+
async readdir(path) {
|
|
63
|
+
const dir = await walkDirectory(root, getPathSegments(path), false);
|
|
64
|
+
const entries = [];
|
|
65
|
+
for await (const [name] of dir.entries()) {
|
|
66
|
+
entries.push(name);
|
|
67
|
+
}
|
|
68
|
+
return entries;
|
|
69
|
+
},
|
|
70
|
+
async stat(path) {
|
|
71
|
+
const segments = getPathSegments(path);
|
|
72
|
+
if (segments.length === 0) {
|
|
73
|
+
return createDirectoryStat();
|
|
74
|
+
}
|
|
75
|
+
const { parentSegments, name } = splitParent(path);
|
|
76
|
+
const parent = await walkDirectory(root, parentSegments, false);
|
|
77
|
+
try {
|
|
78
|
+
const fileHandle = await parent.getFileHandle(name, { create: false });
|
|
79
|
+
const file = await fileHandle.getFile();
|
|
80
|
+
return {
|
|
81
|
+
isFile: () => true,
|
|
82
|
+
isDirectory: () => false,
|
|
83
|
+
size: file.size,
|
|
84
|
+
mtime: new Date(file.lastModified ?? 0)
|
|
85
|
+
};
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (!isNotFoundOrTypeMismatch(error))
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
await parent.getDirectoryHandle(name, { create: false });
|
|
92
|
+
return createDirectoryStat();
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (!isNotFoundOrTypeMismatch(error))
|
|
95
|
+
throw error;
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
async writeFile(path, data) {
|
|
100
|
+
const { parentSegments, name } = splitParent(path);
|
|
101
|
+
const parent = await walkDirectory(root, parentSegments, false);
|
|
102
|
+
const fileHandle = await parent.getFileHandle(name, { create: true });
|
|
103
|
+
const writable = await fileHandle.createWritable();
|
|
104
|
+
await writable.write(toWritableData(data));
|
|
105
|
+
await writable.close();
|
|
106
|
+
},
|
|
107
|
+
async appendFile(path, data) {
|
|
108
|
+
const { parentSegments, name } = splitParent(path);
|
|
109
|
+
const parent = await walkDirectory(root, parentSegments, false);
|
|
110
|
+
const fileHandle = await parent.getFileHandle(name, { create: true });
|
|
111
|
+
const file = await fileHandle.getFile();
|
|
112
|
+
const writable = await fileHandle.createWritable({ keepExistingData: true });
|
|
113
|
+
await writable.write({
|
|
114
|
+
type: "write",
|
|
115
|
+
position: file.size,
|
|
116
|
+
data: toWritableData(data)
|
|
117
|
+
});
|
|
118
|
+
await writable.close();
|
|
119
|
+
},
|
|
120
|
+
async mkdir(path, opts) {
|
|
121
|
+
const segments = getPathSegments(path);
|
|
122
|
+
if (segments.length === 0) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (opts?.recursive) {
|
|
126
|
+
await walkDirectory(root, segments, true);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const parent = await walkDirectory(root, segments.slice(0, -1), false);
|
|
130
|
+
const name = segments[segments.length - 1];
|
|
131
|
+
const exists = await entryExists(parent, name);
|
|
132
|
+
if (exists) {
|
|
133
|
+
throw new Error(`EEXIST: file already exists, mkdir '${normalizeWebPath(path)}'`);
|
|
134
|
+
}
|
|
135
|
+
await parent.getDirectoryHandle(name, { create: true });
|
|
136
|
+
},
|
|
137
|
+
async rm(path, opts) {
|
|
138
|
+
const segments = getPathSegments(path);
|
|
139
|
+
if (segments.length === 0) {
|
|
140
|
+
throw new Error("EPERM: operation not permitted, rm '/'");
|
|
141
|
+
}
|
|
142
|
+
const parent = await walkDirectory(root, segments.slice(0, -1), false);
|
|
143
|
+
const name = segments[segments.length - 1];
|
|
144
|
+
try {
|
|
145
|
+
await parent.removeEntry(name, { recursive: opts?.recursive });
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (opts?.force && isNotFoundError(error)) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
class WebFileSystem extends FileSystem {
|
|
158
|
+
constructor(root, permissions) {
|
|
159
|
+
super("/", permissions, createWebUnderlyingFS(root));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function createDirectoryStat() {
|
|
163
|
+
return {
|
|
164
|
+
isFile: () => false,
|
|
165
|
+
isDirectory: () => true,
|
|
166
|
+
size: 0,
|
|
167
|
+
mtime: DIRECTORY_MTIME
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function normalizeWebPath(path) {
|
|
171
|
+
const normalized = path.replace(/\\/g, "/");
|
|
172
|
+
const rawSegments = (normalized.startsWith("/") ? normalized : `/${normalized}`).split("/").filter(Boolean);
|
|
173
|
+
const segments = [];
|
|
174
|
+
for (const segment of rawSegments) {
|
|
175
|
+
if (segment === ".")
|
|
176
|
+
continue;
|
|
177
|
+
if (segment === "..") {
|
|
178
|
+
segments.pop();
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
segments.push(segment);
|
|
182
|
+
}
|
|
183
|
+
return `/${segments.join("/")}`;
|
|
184
|
+
}
|
|
185
|
+
function getPathSegments(path) {
|
|
186
|
+
const normalized = normalizeWebPath(path);
|
|
187
|
+
return normalized.split("/").filter(Boolean);
|
|
188
|
+
}
|
|
189
|
+
function splitParent(path) {
|
|
190
|
+
const segments = getPathSegments(path);
|
|
191
|
+
if (segments.length === 0) {
|
|
192
|
+
throw new Error(`Invalid file path: "${path}"`);
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
parentSegments: segments.slice(0, -1),
|
|
196
|
+
name: segments[segments.length - 1]
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async function walkDirectory(root, segments, create) {
|
|
200
|
+
let current = root;
|
|
201
|
+
for (const segment of segments) {
|
|
202
|
+
current = await current.getDirectoryHandle(segment, { create });
|
|
203
|
+
}
|
|
204
|
+
return current;
|
|
205
|
+
}
|
|
206
|
+
async function entryExists(dir, name) {
|
|
207
|
+
try {
|
|
208
|
+
await dir.getFileHandle(name, { create: false });
|
|
209
|
+
return true;
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (isTypeMismatchError(error))
|
|
212
|
+
return true;
|
|
213
|
+
if (!isNotFoundError(error))
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
await dir.getDirectoryHandle(name, { create: false });
|
|
218
|
+
return true;
|
|
219
|
+
} catch (error) {
|
|
220
|
+
if (isTypeMismatchError(error))
|
|
221
|
+
return true;
|
|
222
|
+
if (!isNotFoundError(error))
|
|
223
|
+
throw error;
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function isNotFoundOrTypeMismatch(error) {
|
|
228
|
+
return isNotFoundError(error) || isTypeMismatchError(error);
|
|
229
|
+
}
|
|
230
|
+
function isNotFoundError(error) {
|
|
231
|
+
return getErrorName(error) === "NotFoundError";
|
|
232
|
+
}
|
|
233
|
+
function isTypeMismatchError(error) {
|
|
234
|
+
return getErrorName(error) === "TypeMismatchError";
|
|
235
|
+
}
|
|
236
|
+
function getErrorName(error) {
|
|
237
|
+
if (!error || typeof error !== "object")
|
|
238
|
+
return;
|
|
239
|
+
const named = error;
|
|
240
|
+
return typeof named.name === "string" ? named.name : undefined;
|
|
241
|
+
}
|
|
242
|
+
function toWritableData(data) {
|
|
243
|
+
if (typeof data === "string") {
|
|
244
|
+
return data;
|
|
245
|
+
}
|
|
246
|
+
const out = new Uint8Array(data.length);
|
|
247
|
+
out.set(data);
|
|
248
|
+
return out.buffer;
|
|
249
|
+
}
|
|
250
|
+
export {
|
|
251
|
+
createWebUnderlyingFS,
|
|
252
|
+
WebFileSystem
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
//# debugId=A9C7F77B6DBC9D6064756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/fs/web-fs.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { FileSystem, type PathOps, type PermissionRules, type UnderlyingFS } from \"./real-fs.mjs\";\n\nconst DIRECTORY_MTIME = new Date(0);\nconst WEB_PATH_OPS: PathOps = {\n separator: \"/\",\n resolve(...paths: string[]): string {\n return normalizeWebPath(paths.join(\"/\"));\n },\n normalize: normalizeWebPath,\n join(...paths: string[]): string {\n const nonEmpty = paths.filter((segment) => segment.length > 0);\n if (nonEmpty.length === 0) {\n return \".\";\n }\n return normalizeWebPath(nonEmpty.join(\"/\"));\n },\n relative(from: string, to: string): string {\n const fromSegments = getPathSegments(from);\n const toSegments = getPathSegments(to);\n let shared = 0;\n\n while (\n shared < fromSegments.length &&\n shared < toSegments.length &&\n fromSegments[shared] === toSegments[shared]\n ) {\n shared++;\n }\n\n const up = new Array(fromSegments.length - shared).fill(\"..\");\n const down = toSegments.slice(shared);\n return [...up, ...down].join(\"/\");\n },\n isAbsolute(path: string): boolean {\n return path.startsWith(\"/\");\n },\n dirname(path: string): string {\n const normalized = normalizeWebPath(path);\n if (normalized === \"/\") {\n return \"/\";\n }\n\n const segments = getPathSegments(normalized);\n if (segments.length <= 1) {\n return \"/\";\n }\n\n return `/${segments.slice(0, -1).join(\"/\")}`;\n },\n basename(path: string): string {\n const normalized = normalizeWebPath(path);\n if (normalized === \"/\") {\n return \"\";\n }\n\n const segments = getPathSegments(normalized);\n return segments[segments.length - 1] ?? \"\";\n },\n};\n\nexport function createWebUnderlyingFS(root: FileSystemDirectoryHandle): UnderlyingFS {\n return {\n pathOps: WEB_PATH_OPS,\n promises: {\n async readFile(path: string): Promise<Buffer> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: false });\n const file = await fileHandle.getFile();\n return Buffer.from(await file.arrayBuffer());\n },\n\n async readdir(path: string): Promise<string[]> {\n const dir = await walkDirectory(root, getPathSegments(path), false);\n const entries: string[] = [];\n for await (const [name] of dir.entries()) {\n entries.push(name);\n }\n return entries;\n },\n\n async stat(path: string): Promise<{\n isFile(): boolean;\n isDirectory(): boolean;\n size: number;\n mtime: Date;\n }> {\n const segments = getPathSegments(path);\n\n if (segments.length === 0) {\n return createDirectoryStat();\n }\n\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n\n try {\n const fileHandle = await parent.getFileHandle(name, { create: false });\n const file = await fileHandle.getFile();\n return {\n isFile: () => true,\n isDirectory: () => false,\n size: file.size,\n mtime: new Date(file.lastModified ?? 0),\n };\n } catch (error) {\n if (!isNotFoundOrTypeMismatch(error)) throw error;\n }\n\n try {\n await parent.getDirectoryHandle(name, { create: false });\n return createDirectoryStat();\n } catch (error) {\n if (!isNotFoundOrTypeMismatch(error)) throw error;\n throw error;\n }\n },\n\n async writeFile(path: string, data: Buffer | string): Promise<void> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: true });\n const writable = await fileHandle.createWritable();\n await writable.write(toWritableData(data));\n await writable.close();\n },\n\n async appendFile(path: string, data: Buffer | string): Promise<void> {\n const { parentSegments, name } = splitParent(path);\n const parent = await walkDirectory(root, parentSegments, false);\n const fileHandle = await parent.getFileHandle(name, { create: true });\n const file = await fileHandle.getFile();\n const writable = await fileHandle.createWritable({ keepExistingData: true });\n await writable.write({\n type: \"write\",\n position: file.size,\n data: toWritableData(data),\n });\n await writable.close();\n },\n\n async mkdir(path: string, opts?: { recursive?: boolean }): Promise<void> {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n return;\n }\n\n if (opts?.recursive) {\n await walkDirectory(root, segments, true);\n return;\n }\n\n const parent = await walkDirectory(root, segments.slice(0, -1), false);\n const name = segments[segments.length - 1]!;\n const exists = await entryExists(parent, name);\n if (exists) {\n throw new Error(`EEXIST: file already exists, mkdir '${normalizeWebPath(path)}'`);\n }\n await parent.getDirectoryHandle(name, { create: true });\n },\n\n async rm(path: string, opts?: { recursive?: boolean; force?: boolean }): Promise<void> {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n throw new Error(\"EPERM: operation not permitted, rm '/'\");\n }\n\n const parent = await walkDirectory(root, segments.slice(0, -1), false);\n const name = segments[segments.length - 1]!;\n try {\n await parent.removeEntry(name, { recursive: opts?.recursive });\n } catch (error) {\n if (opts?.force && isNotFoundError(error)) {\n return;\n }\n throw error;\n }\n },\n },\n };\n}\n\nexport class WebFileSystem extends FileSystem {\n constructor(root: FileSystemDirectoryHandle, permissions?: PermissionRules) {\n super(\"/\", permissions, createWebUnderlyingFS(root));\n }\n}\n\nfunction createDirectoryStat() {\n return {\n isFile: () => false,\n isDirectory: () => true,\n size: 0,\n mtime: DIRECTORY_MTIME,\n };\n}\n\nfunction normalizeWebPath(path: string): string {\n const normalized = path.replace(/\\\\/g, \"/\");\n const rawSegments = (normalized.startsWith(\"/\") ? normalized : `/${normalized}`)\n .split(\"/\")\n .filter(Boolean);\n\n const segments: string[] = [];\n for (const segment of rawSegments) {\n if (segment === \".\") continue;\n if (segment === \"..\") {\n segments.pop();\n continue;\n }\n segments.push(segment);\n }\n\n return `/${segments.join(\"/\")}`;\n}\n\nfunction getPathSegments(path: string): string[] {\n const normalized = normalizeWebPath(path);\n return normalized.split(\"/\").filter(Boolean);\n}\n\nfunction splitParent(path: string): { parentSegments: string[]; name: string } {\n const segments = getPathSegments(path);\n if (segments.length === 0) {\n throw new Error(`Invalid file path: \"${path}\"`);\n }\n return {\n parentSegments: segments.slice(0, -1),\n name: segments[segments.length - 1]!,\n };\n}\n\nasync function walkDirectory(\n root: FileSystemDirectoryHandle,\n segments: string[],\n create: boolean\n): Promise<FileSystemDirectoryHandle> {\n let current = root;\n for (const segment of segments) {\n current = await current.getDirectoryHandle(segment, { create });\n }\n return current;\n}\n\nasync function entryExists(dir: FileSystemDirectoryHandle, name: string): Promise<boolean> {\n try {\n await dir.getFileHandle(name, { create: false });\n return true;\n } catch (error) {\n if (isTypeMismatchError(error)) return true;\n if (!isNotFoundError(error)) throw error;\n }\n\n try {\n await dir.getDirectoryHandle(name, { create: false });\n return true;\n } catch (error) {\n if (isTypeMismatchError(error)) return true;\n if (!isNotFoundError(error)) throw error;\n return false;\n }\n}\n\nfunction isNotFoundOrTypeMismatch(error: unknown): boolean {\n return isNotFoundError(error) || isTypeMismatchError(error);\n}\n\nfunction isNotFoundError(error: unknown): boolean {\n return getErrorName(error) === \"NotFoundError\";\n}\n\nfunction isTypeMismatchError(error: unknown): boolean {\n return getErrorName(error) === \"TypeMismatchError\";\n}\n\nfunction getErrorName(error: unknown): string | undefined {\n if (!error || typeof error !== \"object\") return undefined;\n const named = error as { name?: unknown };\n return typeof named.name === \"string\" ? named.name : undefined;\n}\n\nfunction toWritableData(data: Buffer | string): string | ArrayBuffer {\n if (typeof data === \"string\") {\n return data;\n }\n const out = new Uint8Array(data.length);\n out.set(data);\n return out.buffer;\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "A9C7F77B6DBC9D6064756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|