sandbox-fs 1.0.0
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/LICENSE +21 -0
- package/README.md +418 -0
- package/dist/FSModule.d.ts +153 -0
- package/dist/FSModule.d.ts.map +1 -0
- package/dist/FSModule.js +555 -0
- package/dist/FSModule.js.map +1 -0
- package/dist/PathMapper.d.ts +30 -0
- package/dist/PathMapper.d.ts.map +1 -0
- package/dist/PathMapper.js +122 -0
- package/dist/PathMapper.js.map +1 -0
- package/dist/PathModule.d.ts +69 -0
- package/dist/PathModule.d.ts.map +1 -0
- package/dist/PathModule.js +159 -0
- package/dist/PathModule.js.map +1 -0
- package/dist/ResourceTracker.d.ts +74 -0
- package/dist/ResourceTracker.d.ts.map +1 -0
- package/dist/ResourceTracker.js +175 -0
- package/dist/ResourceTracker.js.map +1 -0
- package/dist/VirtualFileSystem.d.ts +145 -0
- package/dist/VirtualFileSystem.d.ts.map +1 -0
- package/dist/VirtualFileSystem.js +155 -0
- package/dist/VirtualFileSystem.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/operations/newer.d.ts +36 -0
- package/dist/operations/newer.d.ts.map +1 -0
- package/dist/operations/newer.js +239 -0
- package/dist/operations/newer.js.map +1 -0
- package/dist/operations/read.d.ts +24 -0
- package/dist/operations/read.d.ts.map +1 -0
- package/dist/operations/read.js +313 -0
- package/dist/operations/read.js.map +1 -0
- package/dist/operations/symlink.d.ts +8 -0
- package/dist/operations/symlink.d.ts.map +1 -0
- package/dist/operations/symlink.js +33 -0
- package/dist/operations/symlink.js.map +1 -0
- package/dist/operations/write.d.ts +29 -0
- package/dist/operations/write.d.ts.map +1 -0
- package/dist/operations/write.js +191 -0
- package/dist/operations/write.js.map +1 -0
- package/dist/utils/ErrorFilter.d.ts +6 -0
- package/dist/utils/ErrorFilter.d.ts.map +1 -0
- package/dist/utils/ErrorFilter.js +57 -0
- package/dist/utils/ErrorFilter.js.map +1 -0
- package/dist/utils/callbackify.d.ts +9 -0
- package/dist/utils/callbackify.d.ts.map +1 -0
- package/dist/utils/callbackify.js +48 -0
- package/dist/utils/callbackify.js.map +1 -0
- package/dist/wrappers/VirtualDir.d.ts +34 -0
- package/dist/wrappers/VirtualDir.d.ts.map +1 -0
- package/dist/wrappers/VirtualDir.js +72 -0
- package/dist/wrappers/VirtualDir.js.map +1 -0
- package/dist/wrappers/VirtualDirent.d.ts +21 -0
- package/dist/wrappers/VirtualDirent.d.ts.map +1 -0
- package/dist/wrappers/VirtualDirent.js +50 -0
- package/dist/wrappers/VirtualDirent.js.map +1 -0
- package/example.js +95 -0
- package/example.ts +32 -0
- package/package.json +29 -0
- package/src/FSModule.ts +546 -0
- package/src/PathMapper.ts +102 -0
- package/src/PathModule.ts +142 -0
- package/src/ResourceTracker.ts +162 -0
- package/src/VirtualFileSystem.ts +172 -0
- package/src/index.ts +9 -0
- package/src/operations/newer.ts +223 -0
- package/src/operations/read.ts +319 -0
- package/src/operations/symlink.ts +31 -0
- package/src/operations/write.ts +189 -0
- package/src/utils/ErrorFilter.ts +57 -0
- package/src/utils/callbackify.ts +54 -0
- package/src/wrappers/VirtualDir.ts +84 -0
- package/src/wrappers/VirtualDirent.ts +60 -0
- package/test-data/example.txt +1 -0
- package/test-data/subdir/nested.txt +1 -0
- package/tsconfig.example.json +8 -0
- package/tsconfig.json +21 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
# sandbox-fs
|
|
2
|
+
|
|
3
|
+
A read-only virtual file system for Node.js that maps Unix-style virtual paths to a real directory root. Provides a secure sandbox environment where write operations are blocked and real filesystem paths are hidden from error messages.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔒 **Read-only**: All write operations return `EACCES` (access denied)
|
|
8
|
+
- 🌍 **Cross-platform**: Works with both Windows and Unix paths as root
|
|
9
|
+
- 🔀 **Unix-style virtual paths**: All virtual paths use forward slashes (`/`)
|
|
10
|
+
- 🚫 **Path traversal protection**: Prevents escaping the VFS root with `..`
|
|
11
|
+
- 🎭 **Error filtering**: Real paths in error messages are replaced with virtual paths
|
|
12
|
+
- 📦 **Full fs API**: Compatible with Node.js `fs` module (both callback and promise APIs)
|
|
13
|
+
- 🛤️ **Path module**: Virtual `path` module with fixed CWD of `/`
|
|
14
|
+
- 🧹 **Resource cleanup**: Proper cleanup of file descriptors and streams
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install sandbox-fs
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Requirements
|
|
23
|
+
|
|
24
|
+
- Node.js >= 18.0.0
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { VirtualFileSystem } from 'sandbox-fs';
|
|
30
|
+
|
|
31
|
+
// Create a VFS instance with a real directory as root
|
|
32
|
+
const vfs = new VirtualFileSystem({ root: '/opt/data' });
|
|
33
|
+
|
|
34
|
+
// Get fs and path modules
|
|
35
|
+
const fs = vfs.createNodeFSModule();
|
|
36
|
+
const path = vfs.createNodePathModule();
|
|
37
|
+
|
|
38
|
+
// Read files using virtual paths
|
|
39
|
+
// Virtual path: /config.json
|
|
40
|
+
// Real path: /opt/data/config.json
|
|
41
|
+
const config = fs.readFileSync('/config.json', 'utf8');
|
|
42
|
+
|
|
43
|
+
// Write operations are denied
|
|
44
|
+
try {
|
|
45
|
+
fs.writeFileSync('/output.txt', 'data');
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.log(err.code); // 'EACCES'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Path operations use virtual CWD of '/'
|
|
51
|
+
console.log(path.resolve('.')); // '/'
|
|
52
|
+
console.log(path.resolve('foo/bar.txt')); // '/foo/bar.txt'
|
|
53
|
+
|
|
54
|
+
// Clean up when done
|
|
55
|
+
vfs.close();
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## API Reference
|
|
59
|
+
|
|
60
|
+
### `VirtualFileSystem`
|
|
61
|
+
|
|
62
|
+
The main class for creating a virtual file system instance.
|
|
63
|
+
|
|
64
|
+
#### Constructor
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
new VirtualFileSystem(options: VFSOptions)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Creates a new VFS instance.
|
|
71
|
+
|
|
72
|
+
**Parameters:**
|
|
73
|
+
- `options.root` (string): The real filesystem path to use as the VFS root. Can be a Windows path (`C:\data`) or Unix path (`/opt/data`).
|
|
74
|
+
|
|
75
|
+
**Throws:**
|
|
76
|
+
- Error if the root path doesn't exist or is not a directory
|
|
77
|
+
|
|
78
|
+
**Example:**
|
|
79
|
+
```typescript
|
|
80
|
+
// Unix
|
|
81
|
+
const vfs = new VirtualFileSystem({ root: '/opt/data' });
|
|
82
|
+
|
|
83
|
+
// Windows
|
|
84
|
+
const vfs = new VirtualFileSystem({ root: 'C:\\data' });
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### `toRealPath(virtualPath: string): string`
|
|
88
|
+
|
|
89
|
+
Converts a virtual path to a real filesystem path.
|
|
90
|
+
|
|
91
|
+
**Parameters:**
|
|
92
|
+
- `virtualPath` (string): Virtual path starting with `/` (Unix-style)
|
|
93
|
+
|
|
94
|
+
**Returns:** Real filesystem path (platform-native)
|
|
95
|
+
|
|
96
|
+
**Throws:**
|
|
97
|
+
- Error if path traversal is detected (e.g., `/../etc/passwd`)
|
|
98
|
+
|
|
99
|
+
**Example:**
|
|
100
|
+
```typescript
|
|
101
|
+
const vfs = new VirtualFileSystem({ root: 'C:\\data' });
|
|
102
|
+
vfs.toRealPath('/foo/bar.txt'); // Returns: 'C:\data\foo\bar.txt'
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### `toVirtualPath(realPath: string): string`
|
|
106
|
+
|
|
107
|
+
Converts a real filesystem path to a virtual path.
|
|
108
|
+
|
|
109
|
+
**Parameters:**
|
|
110
|
+
- `realPath` (string): Real filesystem path (platform-native)
|
|
111
|
+
|
|
112
|
+
**Returns:** Virtual path starting with `/` (Unix-style)
|
|
113
|
+
|
|
114
|
+
**Throws:**
|
|
115
|
+
- Error if real path is outside the VFS root
|
|
116
|
+
|
|
117
|
+
**Example:**
|
|
118
|
+
```typescript
|
|
119
|
+
const vfs = new VirtualFileSystem({ root: 'C:\\data' });
|
|
120
|
+
vfs.toVirtualPath('C:\\data\\foo\\bar.txt'); // Returns: '/foo/bar.txt'
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### `createNodeFSModule(): any`
|
|
124
|
+
|
|
125
|
+
Creates a Node.js `fs`-compatible module.
|
|
126
|
+
|
|
127
|
+
**Returns:** An object compatible with the Node.js `fs` module
|
|
128
|
+
|
|
129
|
+
The returned object includes:
|
|
130
|
+
- **Callback API**: `readFile()`, `stat()`, `readdir()`, etc.
|
|
131
|
+
- **Promise API**: `fs.promises.readFile()`, `fs.promises.stat()`, etc.
|
|
132
|
+
- **Sync API**: `readFileSync()`, `statSync()`, `readdirSync()`, etc.
|
|
133
|
+
- **Stream API**: `createReadStream()` (write streams throw EACCES)
|
|
134
|
+
- **Constants**: `fs.constants`
|
|
135
|
+
|
|
136
|
+
**Behavior:**
|
|
137
|
+
- ✅ **Read operations**: Work normally (readFile, stat, readdir, open, createReadStream, etc.)
|
|
138
|
+
- ❌ **Write operations**: Throw `EACCES` (writeFile, mkdir, unlink, etc.)
|
|
139
|
+
- ❌ **Symlink operations**: Throw `EACCES` (symlink, link, readlink)
|
|
140
|
+
- 🔒 **Error filtering**: All error messages replace real paths with virtual paths
|
|
141
|
+
|
|
142
|
+
**Example:**
|
|
143
|
+
```typescript
|
|
144
|
+
const fs = vfs.createNodeFSModule();
|
|
145
|
+
|
|
146
|
+
// Callback API
|
|
147
|
+
fs.readFile('/file.txt', 'utf8', (err, data) => {
|
|
148
|
+
if (err) throw err;
|
|
149
|
+
console.log(data);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Promise API
|
|
153
|
+
const data = await fs.promises.readFile('/file.txt', 'utf8');
|
|
154
|
+
|
|
155
|
+
// Sync API
|
|
156
|
+
const data = fs.readFileSync('/file.txt', 'utf8');
|
|
157
|
+
|
|
158
|
+
// Stream API
|
|
159
|
+
const stream = fs.createReadStream('/large-file.txt');
|
|
160
|
+
stream.on('data', chunk => console.log(chunk));
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
#### `createNodePathModule(): any`
|
|
164
|
+
|
|
165
|
+
Creates a Node.js `path`-compatible module.
|
|
166
|
+
|
|
167
|
+
**Returns:** An object compatible with the Node.js `path` module
|
|
168
|
+
|
|
169
|
+
The returned object includes all `path` methods operating in virtual path space:
|
|
170
|
+
- `resolve()`, `join()`, `normalize()`, `relative()`
|
|
171
|
+
- `dirname()`, `basename()`, `extname()`
|
|
172
|
+
- `parse()`, `format()`, `isAbsolute()`
|
|
173
|
+
- `sep`, `delimiter`, `posix`, `win32`
|
|
174
|
+
|
|
175
|
+
**Behavior:**
|
|
176
|
+
- All paths use Unix-style forward slashes (`/`)
|
|
177
|
+
- Virtual CWD is always `/`
|
|
178
|
+
- `path.sep` is always `/`
|
|
179
|
+
- `path.delimiter` is always `:`
|
|
180
|
+
- Both `path.posix` and `path.win32` reference the same virtual implementation
|
|
181
|
+
|
|
182
|
+
**Example:**
|
|
183
|
+
```typescript
|
|
184
|
+
const path = vfs.createNodePathModule();
|
|
185
|
+
|
|
186
|
+
path.resolve('.'); // Returns: '/'
|
|
187
|
+
path.resolve('foo/bar.txt'); // Returns: '/foo/bar.txt'
|
|
188
|
+
path.join('/foo', 'bar'); // Returns: '/foo/bar'
|
|
189
|
+
path.dirname('/foo/bar.txt'); // Returns: '/foo'
|
|
190
|
+
path.basename('/foo/bar.txt'); // Returns: 'bar.txt'
|
|
191
|
+
path.sep; // Returns: '/'
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### `close(): void`
|
|
195
|
+
|
|
196
|
+
Closes the VFS and releases all resources.
|
|
197
|
+
|
|
198
|
+
This method:
|
|
199
|
+
- Closes all tracked file descriptors
|
|
200
|
+
- Destroys all active streams
|
|
201
|
+
- Marks the VFS as closed
|
|
202
|
+
- Causes all subsequent fs operations to throw `EBADF`
|
|
203
|
+
|
|
204
|
+
**Example:**
|
|
205
|
+
```typescript
|
|
206
|
+
const vfs = new VirtualFileSystem({ root: '/opt/data' });
|
|
207
|
+
const fs = vfs.createNodeFSModule();
|
|
208
|
+
|
|
209
|
+
const fd = fs.openSync('/file.txt', 'r');
|
|
210
|
+
|
|
211
|
+
vfs.close(); // Closes fd and all other resources
|
|
212
|
+
|
|
213
|
+
// All operations now throw EBADF
|
|
214
|
+
fs.readFileSync('/file.txt'); // Throws: EBADF (bad file descriptor)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### `closed` (getter)
|
|
218
|
+
|
|
219
|
+
**Returns:** `boolean` - Whether the VFS has been closed
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
if (!vfs.closed) {
|
|
223
|
+
vfs.close();
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Security Features
|
|
228
|
+
|
|
229
|
+
### Path Traversal Protection
|
|
230
|
+
|
|
231
|
+
The VFS validates all paths to prevent escaping the root directory:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
const vfs = new VirtualFileSystem({ root: '/opt/data' });
|
|
235
|
+
const fs = vfs.createNodeFSModule();
|
|
236
|
+
|
|
237
|
+
// These throw errors:
|
|
238
|
+
fs.readFileSync('/../etc/passwd'); // Error: Path traversal detected
|
|
239
|
+
fs.readFileSync('/foo/../../etc/passwd'); // Error: Path traversal detected
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Error Message Filtering
|
|
243
|
+
|
|
244
|
+
Real filesystem paths are automatically filtered from error messages:
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
const vfs = new VirtualFileSystem({ root: '/opt/data' });
|
|
248
|
+
const fs = vfs.createNodeFSModule();
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
fs.readFileSync('/nonexistent.txt');
|
|
252
|
+
} catch (err) {
|
|
253
|
+
// Real error: ENOENT: no such file or directory, open '/opt/data/nonexistent.txt'
|
|
254
|
+
// Filtered error: ENOENT: no such file or directory, open '/nonexistent.txt'
|
|
255
|
+
console.log(err.message); // Real path is replaced with virtual path
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### Read-Only Enforcement
|
|
260
|
+
|
|
261
|
+
All write operations return `EACCES`:
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
const fs = vfs.createNodeFSModule();
|
|
265
|
+
|
|
266
|
+
// All of these throw EACCES:
|
|
267
|
+
fs.writeFileSync('/file.txt', 'data');
|
|
268
|
+
fs.appendFileSync('/file.txt', 'data');
|
|
269
|
+
fs.mkdirSync('/newdir');
|
|
270
|
+
fs.unlinkSync('/file.txt');
|
|
271
|
+
fs.rmdirSync('/dir');
|
|
272
|
+
fs.renameSync('/old.txt', '/new.txt');
|
|
273
|
+
fs.chmodSync('/file.txt', 0o644);
|
|
274
|
+
fs.createWriteStream('/file.txt'); // Throws immediately
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Symlink Blocking
|
|
278
|
+
|
|
279
|
+
All symlink operations return `EACCES`:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
const fs = vfs.createNodeFSModule();
|
|
283
|
+
|
|
284
|
+
// All of these throw EACCES:
|
|
285
|
+
fs.symlinkSync('/target', '/link');
|
|
286
|
+
fs.linkSync('/existing', '/new');
|
|
287
|
+
fs.readlinkSync('/symlink');
|
|
288
|
+
|
|
289
|
+
// stat/lstat also throw EACCES if the target is a symlink
|
|
290
|
+
const stats = fs.statSync('/symlink'); // Throws EACCES
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Platform Support
|
|
294
|
+
|
|
295
|
+
### Windows
|
|
296
|
+
- VFS root can be a Windows path: `C:\data`, `D:\projects`, etc.
|
|
297
|
+
- Virtual paths always use forward slashes: `/foo/bar.txt`
|
|
298
|
+
- UNC paths are supported: `\\server\share`
|
|
299
|
+
|
|
300
|
+
### Unix/Linux/macOS
|
|
301
|
+
- VFS root is a standard Unix path: `/opt/data`, `/home/user/sandbox`, etc.
|
|
302
|
+
- Virtual paths use forward slashes: `/foo/bar.txt`
|
|
303
|
+
|
|
304
|
+
## Advanced Usage
|
|
305
|
+
|
|
306
|
+
### AbortSignal Support
|
|
307
|
+
|
|
308
|
+
Many fs methods support `AbortSignal` for cancellation:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
const controller = new AbortController();
|
|
312
|
+
const fs = vfs.createNodeFSModule();
|
|
313
|
+
|
|
314
|
+
// Cancel operation after 1 second
|
|
315
|
+
setTimeout(() => controller.abort(), 1000);
|
|
316
|
+
|
|
317
|
+
try {
|
|
318
|
+
const data = await fs.promises.readFile('/large-file.txt', {
|
|
319
|
+
signal: controller.signal
|
|
320
|
+
});
|
|
321
|
+
} catch (err) {
|
|
322
|
+
if (err.name === 'AbortError') {
|
|
323
|
+
console.log('Operation cancelled');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### File Descriptor Tracking
|
|
329
|
+
|
|
330
|
+
The VFS tracks all open file descriptors:
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
const fs = vfs.createNodeFSModule();
|
|
334
|
+
|
|
335
|
+
const fd = fs.openSync('/file.txt', 'r');
|
|
336
|
+
const buffer = Buffer.alloc(100);
|
|
337
|
+
|
|
338
|
+
// Read using the file descriptor
|
|
339
|
+
fs.readSync(fd, buffer, 0, 100, 0);
|
|
340
|
+
|
|
341
|
+
// VFS tracks the FD and ensures it's valid
|
|
342
|
+
fs.closeSync(fd);
|
|
343
|
+
|
|
344
|
+
// After close, the FD is invalid
|
|
345
|
+
fs.readSync(fd, buffer, 0, 100, 0); // Throws EBADF
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### Stream Path Override
|
|
349
|
+
|
|
350
|
+
Streams have their `path` property overridden to return virtual paths:
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
const fs = vfs.createNodeFSModule();
|
|
354
|
+
const stream = fs.createReadStream('/data.txt');
|
|
355
|
+
|
|
356
|
+
console.log(stream.path); // '/data.txt' (virtual path)
|
|
357
|
+
// Real path is hidden
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### Working with Newer Node.js APIs
|
|
361
|
+
|
|
362
|
+
The VFS supports newer Node.js APIs (when available):
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
const fs = vfs.createNodeFSModule();
|
|
366
|
+
|
|
367
|
+
// glob (Node.js 22+)
|
|
368
|
+
if (fs.promises.glob) {
|
|
369
|
+
for await (const file of fs.promises.glob('**/*.txt')) {
|
|
370
|
+
console.log(file); // Virtual paths
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// statfs (Node.js 19+)
|
|
375
|
+
if (fs.promises.statfs) {
|
|
376
|
+
const stats = await fs.promises.statfs('/');
|
|
377
|
+
console.log(stats);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// openAsBlob (Node.js 19+)
|
|
381
|
+
if (fs.openAsBlob) {
|
|
382
|
+
const blob = await fs.openAsBlob('/file.txt');
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## TypeScript Support
|
|
387
|
+
|
|
388
|
+
Full TypeScript support with type definitions:
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
import { VirtualFileSystem, VFSOptions } from 'sandbox-fs';
|
|
392
|
+
|
|
393
|
+
const options: VFSOptions = { root: '/opt/data' };
|
|
394
|
+
const vfs: VirtualFileSystem = new VirtualFileSystem(options);
|
|
395
|
+
|
|
396
|
+
const fs = vfs.createNodeFSModule();
|
|
397
|
+
const path = vfs.createNodePathModule();
|
|
398
|
+
|
|
399
|
+
// Full type inference for fs and path modules
|
|
400
|
+
const content: string = fs.readFileSync('/file.txt', 'utf8');
|
|
401
|
+
const resolved: string = path.resolve('foo', 'bar');
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Limitations
|
|
405
|
+
|
|
406
|
+
1. **Read-only**: Write operations are not supported and will throw `EACCES`
|
|
407
|
+
2. **Symlinks**: Symlink operations and reading symlink targets throw `EACCES`
|
|
408
|
+
3. **Virtual CWD**: The path module always uses `/` as the current working directory
|
|
409
|
+
4. **Performance**: Path conversion adds minimal overhead to each operation
|
|
410
|
+
5. **Node.js version**: Newer APIs (glob, statfs) require recent Node.js versions
|
|
411
|
+
|
|
412
|
+
## License
|
|
413
|
+
|
|
414
|
+
MIT
|
|
415
|
+
|
|
416
|
+
## Contributing
|
|
417
|
+
|
|
418
|
+
Contributions are welcome! Please feel free to submit issues or pull requests.
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import type { PathMapper } from './PathMapper';
|
|
3
|
+
import type { ResourceTracker } from './ResourceTracker';
|
|
4
|
+
/**
|
|
5
|
+
* Create a Node.js fs-compatible module
|
|
6
|
+
*/
|
|
7
|
+
export declare function createFSModule(pathMapper: PathMapper, resourceTracker: ResourceTracker): {
|
|
8
|
+
promises: {
|
|
9
|
+
readFile: (virtualPath: string, options?: any) => Promise<Buffer | string>;
|
|
10
|
+
readdir: (virtualPath: string, options?: any) => Promise<string[] | Buffer[] | fs.Dirent[]>;
|
|
11
|
+
stat: (virtualPath: string, options?: any) => Promise<fs.Stats>;
|
|
12
|
+
lstat: (virtualPath: string, options?: any) => Promise<fs.Stats>;
|
|
13
|
+
access: (virtualPath: string, mode?: number) => Promise<void>;
|
|
14
|
+
realpath: (virtualPath: string, options?: any) => Promise<string | Buffer>;
|
|
15
|
+
open: (virtualPath: string, flags?: string | number, mode?: number) => Promise<number>;
|
|
16
|
+
read: (fd: number, buffer: NodeJS.ArrayBufferView, offset: number, length: number, position: number | null) => Promise<{
|
|
17
|
+
bytesRead: number;
|
|
18
|
+
buffer: NodeJS.ArrayBufferView;
|
|
19
|
+
}>;
|
|
20
|
+
close: (fd: number) => Promise<void>;
|
|
21
|
+
fstat: (fd: number, options?: any) => Promise<fs.Stats>;
|
|
22
|
+
opendir: (virtualPath: string, options?: any) => Promise<import("./wrappers/VirtualDir").VirtualDir>;
|
|
23
|
+
exists: (virtualPath: string) => Promise<boolean>;
|
|
24
|
+
writeFile: (path: string, data: any, options?: any) => Promise<void>;
|
|
25
|
+
appendFile: (path: string, data: any, options?: any) => Promise<void>;
|
|
26
|
+
mkdir: (path: string, options?: any) => Promise<void | string>;
|
|
27
|
+
rmdir: (path: string, options?: any) => Promise<void>;
|
|
28
|
+
rm: (path: string, options?: any) => Promise<void>;
|
|
29
|
+
unlink: (path: string) => Promise<void>;
|
|
30
|
+
rename: (oldPath: string, newPath: string) => Promise<void>;
|
|
31
|
+
copyFile: (src: string, dest: string, mode?: number) => Promise<void>;
|
|
32
|
+
truncate: (path: string, len?: number) => Promise<void>;
|
|
33
|
+
ftruncate: (fd: number, len?: number) => Promise<void>;
|
|
34
|
+
chmod: (path: string, mode: string | number) => Promise<void>;
|
|
35
|
+
fchmod: (fd: number, mode: string | number) => Promise<void>;
|
|
36
|
+
lchmod: (path: string, mode: string | number) => Promise<void>;
|
|
37
|
+
chown: (path: string, uid: number, gid: number) => Promise<void>;
|
|
38
|
+
fchown: (fd: number, uid: number, gid: number) => Promise<void>;
|
|
39
|
+
lchown: (path: string, uid: number, gid: number) => Promise<void>;
|
|
40
|
+
utimes: (path: string, atime: string | number | Date, mtime: string | number | Date) => Promise<void>;
|
|
41
|
+
futimes: (fd: number, atime: string | number | Date, mtime: string | number | Date) => Promise<void>;
|
|
42
|
+
lutimes: (path: string, atime: string | number | Date, mtime: string | number | Date) => Promise<void>;
|
|
43
|
+
write: (fd: number, buffer: any, offset?: any, length?: any, position?: any) => Promise<any>;
|
|
44
|
+
writev: (fd: number, buffers: NodeJS.ArrayBufferView[], position?: number) => Promise<any>;
|
|
45
|
+
fsync: (fd: number) => Promise<void>;
|
|
46
|
+
fdatasync: (fd: number) => Promise<void>;
|
|
47
|
+
symlink: (target: string, path: string, type?: string) => Promise<void>;
|
|
48
|
+
link: (existingPath: string, newPath: string) => Promise<void>;
|
|
49
|
+
readlink: (path: string, options?: any) => Promise<string | Buffer>;
|
|
50
|
+
glob: (pattern: string, options?: any) => AsyncIterableIterator<string>;
|
|
51
|
+
statfs: (virtualPath: string, options?: any) => Promise<fs.StatsFsBase<any>>;
|
|
52
|
+
cp: (source: string, destination: string, options?: any) => Promise<void>;
|
|
53
|
+
openAsBlob: (virtualPath: string, options?: any) => Promise<Blob>;
|
|
54
|
+
readv: (fd: number, buffers: NodeJS.ArrayBufferView[], position?: number) => Promise<number>;
|
|
55
|
+
};
|
|
56
|
+
constants: typeof fs.constants;
|
|
57
|
+
Stats: typeof fs.Stats;
|
|
58
|
+
Dirent: typeof fs.Dirent;
|
|
59
|
+
ReadStream: typeof fs.ReadStream;
|
|
60
|
+
WriteStream: typeof fs.WriteStream;
|
|
61
|
+
watch: (virtualPath: string, options?: any) => fs.FSWatcher;
|
|
62
|
+
watchFile: (virtualPath: string, options: any, listener?: any) => void;
|
|
63
|
+
unwatchFile: (virtualPath: string, listener?: any) => void;
|
|
64
|
+
createReadStream: (virtualPath: string, options?: any) => fs.ReadStream;
|
|
65
|
+
createWriteStream: (path: string, options?: any) => never;
|
|
66
|
+
readFileSync: (path: string, options?: any) => NonSharedBuffer;
|
|
67
|
+
readdirSync: (path: string, options?: any) => string[];
|
|
68
|
+
statSync: (path: string, options?: any) => fs.Stats;
|
|
69
|
+
lstatSync: (path: string, options?: any) => fs.Stats;
|
|
70
|
+
accessSync: (path: string, mode?: number) => void;
|
|
71
|
+
realpathSync: (path: string, options?: any) => string;
|
|
72
|
+
openSync: (path: string, flags?: string | number, mode?: number) => number;
|
|
73
|
+
readSync: (fd: number, buffer: NodeJS.ArrayBufferView, offset: number, length: number, position: number | null) => number;
|
|
74
|
+
closeSync: (fd: number) => void;
|
|
75
|
+
fstatSync: (fd: number, options?: any) => fs.Stats;
|
|
76
|
+
opendirSync: (path: string, options?: any) => any;
|
|
77
|
+
existsSync: (path: string) => boolean;
|
|
78
|
+
writeFileSync: (path: string) => never;
|
|
79
|
+
appendFileSync: (path: string) => never;
|
|
80
|
+
mkdirSync: (path: string) => never;
|
|
81
|
+
rmdirSync: (path: string) => never;
|
|
82
|
+
rmSync: (path: string) => never;
|
|
83
|
+
unlinkSync: (path: string) => never;
|
|
84
|
+
renameSync: (oldPath: string) => never;
|
|
85
|
+
copyFileSync: (src: string, dest: string) => never;
|
|
86
|
+
truncateSync: (path: string) => never;
|
|
87
|
+
ftruncateSync: (fd: number) => never;
|
|
88
|
+
chmodSync: (path: string) => never;
|
|
89
|
+
fchmodSync: (fd: number) => never;
|
|
90
|
+
lchmodSync: (path: string) => never;
|
|
91
|
+
chownSync: (path: string) => never;
|
|
92
|
+
fchownSync: (fd: number) => never;
|
|
93
|
+
lchownSync: (path: string) => never;
|
|
94
|
+
utimesSync: (path: string) => never;
|
|
95
|
+
futimesSync: (fd: number) => never;
|
|
96
|
+
lutimesSync: (path: string) => never;
|
|
97
|
+
writeSync: (fd: number) => never;
|
|
98
|
+
writevSync: (fd: number) => never;
|
|
99
|
+
fsyncSync: (fd: number) => never;
|
|
100
|
+
fdatasyncSync: (fd: number) => never;
|
|
101
|
+
symlinkSync: (target: string, path: string) => never;
|
|
102
|
+
linkSync: (existingPath: string, newPath: string) => never;
|
|
103
|
+
readlinkSync: (path: string) => never;
|
|
104
|
+
cpSync: (source: string, destination: string) => never;
|
|
105
|
+
statfsSync: (path: string, options?: any) => fs.StatsFs;
|
|
106
|
+
readvSync: (fd: number, buffers: NodeJS.ArrayBufferView[], position?: number) => any;
|
|
107
|
+
readFile: (virtualPath: string, options: any, args_2: (err: Error | null, result?: string | Buffer<ArrayBufferLike> | undefined) => void) => void;
|
|
108
|
+
readdir: (virtualPath: string, options: any, args_2: (err: Error | null, result?: string[] | Buffer<ArrayBufferLike>[] | fs.Dirent<string>[] | undefined) => void) => void;
|
|
109
|
+
stat: (virtualPath: string, options: any, args_2: (err: Error | null, result?: fs.Stats | undefined) => void) => void;
|
|
110
|
+
lstat: (virtualPath: string, options: any, args_2: (err: Error | null, result?: fs.Stats | undefined) => void) => void;
|
|
111
|
+
access: (virtualPath: string, mode: number | undefined, args_2: (err: Error | null) => void) => void;
|
|
112
|
+
realpath: (virtualPath: string, options: any, args_2: (err: Error | null, result?: string | Buffer<ArrayBufferLike> | undefined) => void) => void;
|
|
113
|
+
open: (virtualPath: string, flags: string | number | undefined, mode: number | undefined, args_3: (err: Error | null, result?: number | undefined) => void) => void;
|
|
114
|
+
read: (fd: number, buffer: NodeJS.ArrayBufferView<ArrayBufferLike>, offset: number, length: number, position: number | null, args_5: (err: Error | null, result?: {
|
|
115
|
+
bytesRead: number;
|
|
116
|
+
buffer: NodeJS.ArrayBufferView;
|
|
117
|
+
} | undefined) => void) => void;
|
|
118
|
+
close: (fd: number, args_1: (err: Error | null) => void) => void;
|
|
119
|
+
fstat: (fd: number, options: any, args_2: (err: Error | null, result?: fs.Stats | undefined) => void) => void;
|
|
120
|
+
opendir: (virtualPath: string, options: any, args_2: (err: Error | null, result?: import("./wrappers/VirtualDir").VirtualDir | undefined) => void) => void;
|
|
121
|
+
exists: (path: string, callback: (exists: boolean) => void) => void;
|
|
122
|
+
writeFile: (path: string, data: any, options: any, args_3: (err: Error | null) => void) => void;
|
|
123
|
+
appendFile: (path: string, data: any, options: any, args_3: (err: Error | null) => void) => void;
|
|
124
|
+
mkdir: (path: string, options: any, args_2: (err: Error | null, result?: string | void | undefined) => void) => void;
|
|
125
|
+
rmdir: (path: string, options: any, args_2: (err: Error | null) => void) => void;
|
|
126
|
+
rm: (path: string, options: any, args_2: (err: Error | null) => void) => void;
|
|
127
|
+
unlink: (path: string, args_1: (err: Error | null) => void) => void;
|
|
128
|
+
rename: (oldPath: string, newPath: string, args_2: (err: Error | null) => void) => void;
|
|
129
|
+
copyFile: (src: string, dest: string, mode: number | undefined, args_3: (err: Error | null) => void) => void;
|
|
130
|
+
truncate: (path: string, len: number | undefined, args_2: (err: Error | null) => void) => void;
|
|
131
|
+
ftruncate: (fd: number, len: number | undefined, args_2: (err: Error | null) => void) => void;
|
|
132
|
+
chmod: (path: string, mode: string | number, args_2: (err: Error | null) => void) => void;
|
|
133
|
+
fchmod: (fd: number, mode: string | number, args_2: (err: Error | null) => void) => void;
|
|
134
|
+
lchmod: (path: string, mode: string | number, args_2: (err: Error | null) => void) => void;
|
|
135
|
+
chown: (path: string, uid: number, gid: number, args_3: (err: Error | null) => void) => void;
|
|
136
|
+
fchown: (fd: number, uid: number, gid: number, args_3: (err: Error | null) => void) => void;
|
|
137
|
+
lchown: (path: string, uid: number, gid: number, args_3: (err: Error | null) => void) => void;
|
|
138
|
+
utimes: (path: string, atime: string | number | Date, mtime: string | number | Date, args_3: (err: Error | null) => void) => void;
|
|
139
|
+
futimes: (fd: number, atime: string | number | Date, mtime: string | number | Date, args_3: (err: Error | null) => void) => void;
|
|
140
|
+
lutimes: (path: string, atime: string | number | Date, mtime: string | number | Date, args_3: (err: Error | null) => void) => void;
|
|
141
|
+
write: (fd: number, buffer: any, offset: any, length: any, position: any, args_5: (err: Error | null, result?: any) => void) => void;
|
|
142
|
+
writev: (fd: number, buffers: NodeJS.ArrayBufferView<ArrayBufferLike>[], position: number | undefined, args_3: (err: Error | null, result?: any) => void) => void;
|
|
143
|
+
fsync: (fd: number, args_1: (err: Error | null) => void) => void;
|
|
144
|
+
fdatasync: (fd: number, args_1: (err: Error | null) => void) => void;
|
|
145
|
+
symlink: (target: string, path: string, type: string | undefined, args_3: (err: Error | null) => void) => void;
|
|
146
|
+
link: (existingPath: string, newPath: string, args_2: (err: Error | null) => void) => void;
|
|
147
|
+
readlink: (path: string, options: any, args_2: (err: Error | null, result?: string | Buffer<ArrayBufferLike> | undefined) => void) => void;
|
|
148
|
+
statfs: (virtualPath: string, options: any, args_2: (err: Error | null, result?: fs.StatsFsBase<any> | undefined) => void) => void;
|
|
149
|
+
cp: (source: string, destination: string, options: any, args_3: (err: Error | null) => void) => void;
|
|
150
|
+
openAsBlob: (virtualPath: string, options: any, args_2: (err: Error | null, result?: Blob | undefined) => void) => void;
|
|
151
|
+
readv: (fd: number, buffers: NodeJS.ArrayBufferView<ArrayBufferLike>[], position: number | undefined, args_3: (err: Error | null, result?: number | undefined) => void) => void;
|
|
152
|
+
};
|
|
153
|
+
//# sourceMappingURL=FSModule.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FSModule.d.ts","sourceRoot":"","sources":["../src/FSModule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AASzD;;GAEG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,UAAU,EAAE,eAAe,EAAE,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAgH9D,MAAM,YAAY,GAAG;wBAUtB,MAAM,YAAY,GAAG;qBAUxB,MAAM,YAAY,GAAG;sBAmBpB,MAAM,YAAY,GAAG;uBAmBpB,MAAM,SAAS,MAAM;yBAUnB,MAAM,YAAY,GAAG;qBAYzB,MAAM,UAAU,MAAM,GAAG,MAAM,SAAS,MAAM;mBA8BhD,MAAM,UAAU,MAAM,CAAC,eAAe,UAAU,MAAM,UAAU,MAAM,YAAY,MAAM,GAAG,IAAI;oBAY9F,MAAM;oBAMN,MAAM,YAAY,GAAG;wBAYjB,MAAM,YAAY,GAAG;uBAWtB,MAAM;0BAWH,MAAM;2BAOL,MAAM;sBAOX,MAAM;sBAON,MAAM;mBAOT,MAAM;uBAOF,MAAM;0BAOH,MAAM;wBAOR,MAAM,QAAQ,MAAM;yBAOnB,MAAM;wBAOP,MAAM;sBAMR,MAAM;qBAOP,MAAM;uBAMJ,MAAM;sBAOP,MAAM;qBAOP,MAAM;uBAMJ,MAAM;uBAON,MAAM;sBAOP,MAAM;wBAMJ,MAAM;oBAOV,MAAM;qBAML,MAAM;oBAMP,MAAM;wBAMF,MAAM;0BAQJ,MAAM,QAAQ,MAAM;6BAOjB,MAAM,WAAW,MAAM;yBAO3B,MAAM;qBASV,MAAM,eAAe,MAAM;uBAQzB,MAAM,YAAY,GAAG;oBAaxB,MAAM,WAAW,MAAM,CAAC,eAAe,EAAE,aAAa,MAAM;;;;;;;;;;;;;;;mBApZ7D,MAAM,YAAY,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA8c7D"}
|