0
# Filesystem Implementations
1
2
Concrete filesystem implementations that provide different behaviors and capabilities built on the @yarnpkg/fslib abstractions.
3
4
## Overview
5
6
The package provides several filesystem implementations for different use cases:
7
- **NodeFS**: Direct access to the real filesystem
8
- **VirtualFS**: In-memory filesystem for testing and isolation
9
- **JailFS**: Security-restricted filesystem
10
- **CwdFS**: Working directory-relative filesystem
11
- **MountFS**: Multi-filesystem composition
12
- **ProxiedFS**: Base for filesystem decorators
13
- **Specialized implementations**: AliasFS, LazyFS, PosixFS, NoFS
14
15
## Real Filesystem Access
16
17
### NodeFS
18
19
Direct interface to the Node.js filesystem with portable path support.
20
21
```typescript { .api }
22
import { NodeFS, type PortablePath } from '@yarnpkg/fslib';
23
24
// Create a NodeFS instance
25
const nodeFs = new NodeFS();
26
27
// Standard filesystem operations
28
const content = await nodeFs.readFilePromise('/path/to/file.txt' as PortablePath, 'utf8');
29
await nodeFs.writeFilePromise('/path/to/output.txt' as PortablePath, content);
30
31
// Directory operations
32
await nodeFs.mkdirPromise('/new/directory' as PortablePath, { recursive: true });
33
const files = await nodeFs.readdirPromise('/directory' as PortablePath);
34
35
// Metadata operations
36
const stats = await nodeFs.statPromise('/file.txt' as PortablePath);
37
console.log(`File size: ${stats.size}, Modified: ${stats.mtime}`);
38
```
39
40
### NodePathFS
41
42
Compatibility wrapper that adds support for file URLs and Buffers to any filesystem. Primarily used internally for Node.js compatibility.
43
44
```typescript { .api }
45
import { NodePathFS, NodeFS, type NativePath } from '@yarnpkg/fslib';
46
47
/**
48
* Adds support for file URLs and Buffers to the wrapped baseFs
49
* Only exists for compatibility with Node's behavior - avoid direct use
50
*/
51
class NodePathFS extends ProxiedFS<NativePath, NativePath> {
52
constructor(baseFs: FakeFS<NativePath>);
53
}
54
55
// Usage (typically internal)
56
const nodeFs = new NodeFS();
57
const pathFs = new NodePathFS(nodeFs);
58
59
// Now supports file URLs and Buffers (compatibility only)
60
const fileUrl = new URL('file:///path/to/file.txt');
61
const content = await pathFs.readFilePromise(fileUrl, 'utf8');
62
63
// Also supports Buffer paths (compatibility only)
64
const bufferPath = Buffer.from('/path/to/file.txt');
65
const data = await pathFs.readFilePromise(bufferPath, 'utf8');
66
```
67
68
## Virtual and Memory-Based Filesystems
69
70
### VirtualFS
71
72
In-memory filesystem implementation for testing and isolated operations.
73
74
```typescript { .api }
75
import { VirtualFS, ppath, type PortablePath } from '@yarnpkg/fslib';
76
77
// Create a virtual filesystem
78
const vfs = new VirtualFS();
79
80
// Create directory structure
81
await vfs.mkdirPromise('/project' as PortablePath, { recursive: true });
82
await vfs.mkdirPromise('/project/src' as PortablePath);
83
84
// Write virtual files
85
await vfs.writeFilePromise('/project/package.json' as PortablePath, JSON.stringify({
86
name: 'my-project',
87
version: '1.0.0'
88
}, null, 2));
89
90
await vfs.writeFilePromise('/project/src/index.ts' as PortablePath, 'console.log("Hello World");');
91
92
// Read from virtual filesystem
93
const packageContent = await vfs.readFilePromise('/project/package.json' as PortablePath, 'utf8');
94
const packageData = JSON.parse(packageContent);
95
console.log(`Project: ${packageData.name} v${packageData.version}`);
96
97
// List virtual directory contents
98
const sourceFiles = await vfs.readdirPromise('/project/src' as PortablePath);
99
console.log('Source files:', sourceFiles);
100
```
101
102
## Security and Isolation Filesystems
103
104
### JailFS
105
106
Filesystem that restricts access to within a specific directory tree.
107
108
```typescript { .api }
109
import { JailFS, NodeFS, type PortablePath } from '@yarnpkg/fslib';
110
111
// Create a jailed filesystem
112
const baseFs = new NodeFS();
113
const jailPath = '/safe/sandbox' as PortablePath;
114
const jailedFs = new JailFS(jailPath, { baseFs });
115
116
// All operations are restricted to the jail directory
117
await jailedFs.writeFilePromise('/file.txt' as PortablePath, 'content');
118
// Actually writes to /safe/sandbox/file.txt
119
120
await jailedFs.mkdirPromise('/subdir' as PortablePath);
121
// Actually creates /safe/sandbox/subdir
122
123
// Attempts to access outside the jail will fail
124
try {
125
await jailedFs.readFilePromise('/../../../etc/passwd' as PortablePath);
126
// This will throw an error or be blocked
127
} catch (error) {
128
console.log('Access denied outside jail');
129
}
130
131
// Safe operations within the jail
132
const files = await jailedFs.readdirPromise('/' as PortablePath);
133
console.log('Files in jail:', files);
134
```
135
136
### JailFS Options
137
138
```typescript { .api }
139
import { JailFS, type PortablePath } from '@yarnpkg/fslib';
140
141
interface JailFSOptions {
142
baseFs?: FakeFS<PortablePath>;
143
}
144
145
// Custom configuration
146
const jailedFs = new JailFS('/restricted/area' as PortablePath, {
147
baseFs: customFileSystem
148
});
149
```
150
151
## Working Directory Filesystems
152
153
### CwdFS
154
155
Filesystem that operates relative to a specific working directory.
156
157
```typescript { .api }
158
import { CwdFS, NodeFS, type PortablePath } from '@yarnpkg/fslib';
159
160
// Create a filesystem rooted at a specific directory
161
const baseFs = new NodeFS();
162
const projectRoot = '/path/to/project' as PortablePath;
163
const cwdFs = new CwdFS(projectRoot, { baseFs });
164
165
// All relative paths are resolved relative to the project root
166
await cwdFs.writeFilePromise('config.json' as PortablePath, '{}');
167
// Actually writes to /path/to/project/config.json
168
169
await cwdFs.mkdirPromise('src/components' as PortablePath, { recursive: true });
170
// Creates /path/to/project/src/components
171
172
// Read files relative to the working directory
173
const configContent = await cwdFs.readFilePromise('config.json' as PortablePath, 'utf8');
174
175
// Absolute paths still work but are resolved from the working directory
176
const absoluteInCwd = await cwdFs.readFilePromise('/README.md' as PortablePath, 'utf8');
177
// Reads /path/to/project/README.md
178
```
179
180
### CwdFS Options
181
182
```typescript { .api }
183
import { CwdFS, type PortablePath } from '@yarnpkg/fslib';
184
185
interface CwdFSOptions {
186
baseFs?: FakeFS<PortablePath>;
187
}
188
189
// Custom configuration
190
const cwdFs = new CwdFS('/working/directory' as PortablePath, {
191
baseFs: virtualFileSystem
192
});
193
```
194
195
## Composite and Mounting Filesystems
196
197
### MountFS
198
199
Filesystem that can mount other filesystems at specific paths.
200
201
```typescript { .api }
202
import { MountFS, NodeFS, VirtualFS, type PortablePath } from '@yarnpkg/fslib';
203
204
// Create mount filesystem
205
const mountFs = new MountFS({ baseFs: new NodeFS() });
206
207
// Mount virtual filesystem at /virtual
208
const virtualFs = new VirtualFS();
209
await virtualFs.writeFilePromise('/data.txt' as PortablePath, 'virtual content');
210
mountFs.mount('/virtual' as PortablePath, virtualFs);
211
212
// Mount real directory at /real
213
const realFs = new NodeFS();
214
mountFs.mount('/real' as PortablePath, realFs);
215
216
// Access mounted filesystems
217
const virtualData = await mountFs.readFilePromise('/virtual/data.txt' as PortablePath, 'utf8');
218
console.log('Virtual content:', virtualData);
219
220
const realFiles = await mountFs.readdirPromise('/real' as PortablePath);
221
console.log('Real files:', realFiles);
222
223
// Unmount when done
224
mountFs.unmount('/virtual' as PortablePath);
225
```
226
227
### MountFS Types and Options
228
229
```typescript { .api }
230
import { type MountableFS, type GetMountPointFn, type MountFSOptions } from '@yarnpkg/fslib';
231
232
// Mountable filesystem interface
233
interface MountableFS extends FakeFS<PortablePath> {
234
// Additional requirements for mountable filesystems
235
}
236
237
// Function to determine mount points
238
type GetMountPointFn<T extends MountableFS> = (
239
fs: T,
240
path: PortablePath
241
) => PortablePath | null;
242
243
// Mount filesystem options
244
interface MountFSOptions<T extends MountableFS> {
245
baseFs?: FakeFS<PortablePath>;
246
getMountPoint?: GetMountPointFn<T>;
247
}
248
```
249
250
## Specialized Filesystem Implementations
251
252
### AliasFS
253
254
Filesystem that redirects specific paths to other locations.
255
256
```typescript { .api }
257
import { AliasFS, NodeFS, type PortablePath } from '@yarnpkg/fslib';
258
259
// Create filesystem with path aliasing
260
const baseFs = new NodeFS();
261
const aliasMap = new Map<PortablePath, PortablePath>();
262
aliasMap.set('/alias/target' as PortablePath, '/real/location' as PortablePath);
263
264
const aliasFs = new AliasFS('/base/path' as PortablePath, aliasMap, { baseFs });
265
266
// Access through alias
267
const content = await aliasFs.readFilePromise('/alias/target/file.txt' as PortablePath, 'utf8');
268
// Actually reads from /real/location/file.txt
269
```
270
271
### LazyFS
272
273
Filesystem with deferred initialization.
274
275
```typescript { .api }
276
import { LazyFS, NodeFS, type PortablePath } from '@yarnpkg/fslib';
277
278
// Create lazy filesystem that initializes on first use
279
const lazyFs = new LazyFS<PortablePath>(() => {
280
console.log('Initializing filesystem...');
281
return new NodeFS();
282
}, { baseFs: undefined });
283
284
// Filesystem is not initialized until first operation
285
const content = await lazyFs.readFilePromise('/file.txt' as PortablePath, 'utf8');
286
// Now the underlying NodeFS is created and used
287
```
288
289
### PosixFS
290
291
Filesystem that handles conversion between native and portable paths.
292
293
```typescript { .api }
294
import { PosixFS, type NativePath, type PortablePath } from '@yarnpkg/fslib';
295
296
// Create POSIX filesystem for cross-platform compatibility
297
const posixFs = new PosixFS();
298
299
// Works with native paths, converts internally
300
const nativePath = process.platform === 'win32' ? 'C:\\Windows\\System32' : '/usr/bin';
301
const files = await posixFs.readdirPromise(nativePath as NativePath);
302
console.log('System files:', files);
303
```
304
305
### NoFS
306
307
Filesystem that denies all operations.
308
309
```typescript { .api }
310
import { NoFS, type PortablePath } from '@yarnpkg/fslib';
311
312
// Create filesystem that throws errors for all operations
313
const noFs = new NoFS();
314
315
try {
316
await noFs.readFilePromise('/any/file.txt' as PortablePath, 'utf8');
317
} catch (error) {
318
console.log('Operation denied:', error.message);
319
}
320
321
// Useful as a placeholder or for security
322
```
323
324
## Base Classes for Custom Implementations
325
326
### ProxiedFS
327
328
Abstract base class for filesystems that proxy to another filesystem.
329
330
```typescript { .api }
331
import { ProxiedFS, type PortablePath, type NativePath } from '@yarnpkg/fslib';
332
333
// Create a custom proxied filesystem
334
class LoggingFS extends ProxiedFS<PortablePath, PortablePath> {
335
constructor(baseFs: FakeFS<PortablePath>) {
336
super(baseFs);
337
}
338
339
async readFilePromise(p: PortablePath, encoding?: BufferEncoding): Promise<Buffer | string> {
340
console.log(`Reading file: ${p}`);
341
const result = await this.baseFs.readFilePromise(p, encoding);
342
console.log(`Read ${Buffer.isBuffer(result) ? result.length : result.length} bytes/characters`);
343
return result;
344
}
345
346
async writeFilePromise(p: PortablePath, content: string | Buffer): Promise<void> {
347
console.log(`Writing file: ${p}`);
348
await this.baseFs.writeFilePromise(p, content);
349
console.log(`Write completed`);
350
}
351
352
// Other methods automatically proxy to baseFs
353
}
354
355
// Usage
356
const baseFs = new NodeFS();
357
const loggingFs = new LoggingFS(baseFs);
358
359
await loggingFs.writeFilePromise('/log/file.txt' as PortablePath, 'content');
360
// Logs: Writing file: /log/file.txt
361
// Logs: Write completed
362
```
363
364
## Advanced Usage Patterns
365
366
### Filesystem Composition
367
368
```typescript { .api }
369
import {
370
MountFS, JailFS, VirtualFS, NodeFS,
371
type PortablePath
372
} from '@yarnpkg/fslib';
373
374
// Create a complex filesystem setup
375
async function createDevelopmentEnvironment(): Promise<MountFS> {
376
const mountFs = new MountFS({ baseFs: new NodeFS() });
377
378
// Mount real project directory at /project (jailed for security)
379
const projectFs = new JailFS('/real/project/path' as PortablePath, {
380
baseFs: new NodeFS()
381
});
382
mountFs.mount('/project' as PortablePath, projectFs);
383
384
// Mount virtual filesystem for temporary files at /tmp
385
const tmpFs = new VirtualFS();
386
mountFs.mount('/tmp' as PortablePath, tmpFs);
387
388
// Mount system tools (read-only jail) at /tools
389
const toolsFs = new JailFS('/usr/local/bin' as PortablePath, {
390
baseFs: new NodeFS()
391
});
392
mountFs.mount('/tools' as PortablePath, toolsFs);
393
394
return mountFs;
395
}
396
397
// Usage
398
const devEnv = await createDevelopmentEnvironment();
399
await devEnv.writeFilePromise('/project/src/main.ts' as PortablePath, 'code');
400
await devEnv.writeFilePromise('/tmp/cache.json' as PortablePath, '{}');
401
const tools = await devEnv.readdirPromise('/tools' as PortablePath);
402
```
403
404
### Testing with Virtual Filesystems
405
406
```typescript { .api }
407
import { VirtualFS, type PortablePath } from '@yarnpkg/fslib';
408
409
// Set up test filesystem
410
async function setupTestEnvironment(): Promise<VirtualFS> {
411
const testFs = new VirtualFS();
412
413
// Create test project structure
414
await testFs.mkdirPromise('/test-project' as PortablePath, { recursive: true });
415
await testFs.mkdirPromise('/test-project/src' as PortablePath);
416
await testFs.mkdirPromise('/test-project/tests' as PortablePath);
417
418
// Add test files
419
await testFs.writeFilePromise(
420
'/test-project/package.json' as PortablePath,
421
JSON.stringify({ name: 'test-pkg', version: '1.0.0' }, null, 2)
422
);
423
424
await testFs.writeFilePromise(
425
'/test-project/src/index.ts' as PortablePath,
426
'export function hello() { return "world"; }'
427
);
428
429
await testFs.writeFilePromise(
430
'/test-project/tests/index.test.ts' as PortablePath,
431
'import { hello } from "../src"; console.assert(hello() === "world");'
432
);
433
434
return testFs;
435
}
436
437
// Use in tests
438
describe('File operations', () => {
439
let testFs: VirtualFS;
440
441
beforeEach(async () => {
442
testFs = await setupTestEnvironment();
443
});
444
445
it('should read package.json', async () => {
446
const content = await testFs.readFilePromise('/test-project/package.json' as PortablePath, 'utf8');
447
const pkg = JSON.parse(content);
448
expect(pkg.name).toBe('test-pkg');
449
});
450
});
451
```
452
453
### Migration Between Filesystems
454
455
```typescript { .api }
456
import {
457
VirtualFS, NodeFS,
458
type FakeFS, type PortablePath
459
} from '@yarnpkg/fslib';
460
461
// Copy directory tree between filesystems
462
async function copyDirectoryTree(
463
sourceFs: FakeFS<PortablePath>,
464
targetFs: FakeFS<PortablePath>,
465
sourcePath: PortablePath,
466
targetPath: PortablePath
467
): Promise<void> {
468
const entries = await sourceFs.readdirPromise(sourcePath, { withFileTypes: true });
469
470
await targetFs.mkdirPromise(targetPath, { recursive: true });
471
472
for (const entry of entries) {
473
const srcPath = ppath.join(sourcePath, entry.name);
474
const dstPath = ppath.join(targetPath, entry.name);
475
476
if (entry.isDirectory()) {
477
await copyDirectoryTree(sourceFs, targetFs, srcPath, dstPath);
478
} else if (entry.isFile()) {
479
const content = await sourceFs.readFilePromise(srcPath);
480
await targetFs.writeFilePromise(dstPath, content);
481
}
482
}
483
}
484
485
// Usage: migrate from virtual to real filesystem
486
const virtualFs = new VirtualFS();
487
const nodeFs = new NodeFS();
488
489
// ... populate virtualFs ...
490
491
await copyDirectoryTree(
492
virtualFs,
493
nodeFs,
494
'/virtual/project' as PortablePath,
495
'/real/backup' as PortablePath
496
);
497
```
498
499
## Related Documentation
500
501
- [Filesystem Abstractions](./filesystem-abstractions.md) - Base classes and interfaces these implementations extend
502
- [Path Handling](./path-handling.md) - Path utilities used by filesystem operations
503
- [Constants and Utilities](./constants-and-utilities.md) - Error handling and statistics utilities
504
- [Advanced Features](./advanced-features.md) - Advanced operations and filesystem patching