| 65 | // --------------------------------------------------------------------------- |
| 66 | |
| 67 | function makeMemFs(): { |
| 68 | store: Map<string, string>; |
| 69 | fs: AgentFs; |
| 70 | writeCalls: string[]; |
| 71 | mkdirCalls: string[]; |
| 72 | } { |
| 73 | const store = new Map<string, string>(); |
| 74 | const dirs = new Set<string>(); |
| 75 | const writeCalls: string[] = []; |
| 76 | const mkdirCalls: string[] = []; |
| 77 | |
| 78 | const addAncestors = (p: string) => { |
| 79 | let cur = path.dirname(p); |
| 80 | while (cur !== path.dirname(cur)) { |
| 81 | dirs.add(cur); |
| 82 | cur = path.dirname(cur); |
| 83 | } |
| 84 | dirs.add(cur); |
| 85 | }; |
| 86 | |
| 87 | const agentFs: AgentFs = { |
| 88 | async lstat(p: string) { |
| 89 | if (store.has(p)) return { isFile: true, isSymbolicLink: false }; |
| 90 | if (dirs.has(p)) return { isFile: false, isSymbolicLink: false }; |
| 91 | return null; |
| 92 | }, |
| 93 | async readFile(p: string) { |
| 94 | const v = store.get(p); |
| 95 | if (v === undefined) throw Object.assign(new Error(`ENOENT: ${p}`), { code: 'ENOENT' }); |
| 96 | return v; |
| 97 | }, |
| 98 | async writeFile(p: string, data: string, opts?: { exclusive?: boolean }) { |
| 99 | if (opts?.exclusive && (store.has(p) || dirs.has(p))) { |
| 100 | throw Object.assign(new Error(`EEXIST: ${p}`), { code: 'EEXIST' }); |
| 101 | } |
| 102 | writeCalls.push(p); |
| 103 | store.set(p, data); |
| 104 | addAncestors(p); |
| 105 | }, |
| 106 | async mkdir(p: string) { |
| 107 | mkdirCalls.push(p); |
| 108 | dirs.add(p); |
| 109 | addAncestors(p); |
| 110 | }, |
| 111 | }; |
| 112 | |
| 113 | return { store, fs: agentFs, writeCalls, mkdirCalls }; |
| 114 | } |
| 115 | |
| 116 | // --------------------------------------------------------------------------- |
| 117 | // Output capture |