@atlaspack/fs 2.12.1-canary.3354

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.
@@ -0,0 +1,1036 @@
1
+ // @flow
2
+
3
+ import type {
4
+ FilePath,
5
+ FileSystem,
6
+ FileOptions,
7
+ ReaddirOptions,
8
+ Encoding,
9
+ } from '@atlaspack/types-internal';
10
+ import type {
11
+ Event,
12
+ Options as WatcherOptions,
13
+ AsyncSubscription,
14
+ } from '@parcel/watcher';
15
+
16
+ import path from 'path';
17
+ import {Readable, Writable} from 'stream';
18
+ import {registerSerializableClass} from '@atlaspack/core';
19
+ import {SharedBuffer} from '@atlaspack/utils';
20
+ import packageJSON from '../package.json';
21
+ import WorkerFarm, {Handle} from '@atlaspack/workers';
22
+ import nullthrows from 'nullthrows';
23
+ import EventEmitter from 'events';
24
+ import {findAncestorFile, findNodeModule, findFirstFile} from './find';
25
+
26
+ const instances: Map<number, MemoryFS> = new Map();
27
+ let id = 0;
28
+
29
+ type HandleFunction = (...args: Array<any>) => any;
30
+ type SerializedMemoryFS = {
31
+ id: number,
32
+ handle: any,
33
+ dirs: Map<FilePath, Directory>,
34
+ files: Map<FilePath, File>,
35
+ symlinks: Map<FilePath, FilePath>,
36
+ ...
37
+ };
38
+
39
+ type WorkerEvent = {|
40
+ type: 'writeFile' | 'unlink' | 'mkdir' | 'symlink',
41
+ path: FilePath,
42
+ entry?: Entry,
43
+ target?: FilePath,
44
+ |};
45
+
46
+ type ResolveFunction = () => mixed;
47
+
48
+ export class MemoryFS implements FileSystem {
49
+ dirs: Map<FilePath, Directory>;
50
+ files: Map<FilePath, File>;
51
+ symlinks: Map<FilePath, FilePath>;
52
+ watchers: Map<FilePath, Set<Watcher>>;
53
+ events: Array<Event>;
54
+ id: number;
55
+ handle: Handle;
56
+ farm: WorkerFarm;
57
+ _cwd: FilePath;
58
+ _eventQueue: Array<Event>;
59
+ _watcherTimer: TimeoutID;
60
+ _numWorkerInstances: number = 0;
61
+ _workerHandles: Array<Handle>;
62
+ _workerRegisterResolves: Array<ResolveFunction> = [];
63
+ _emitter: EventEmitter = new EventEmitter();
64
+
65
+ constructor(workerFarm: WorkerFarm) {
66
+ this.farm = workerFarm;
67
+ this._cwd = path.resolve(path.sep);
68
+ this.dirs = new Map([[this._cwd, new Directory()]]);
69
+ this.files = new Map();
70
+ this.symlinks = new Map();
71
+ this.watchers = new Map();
72
+ this.events = [];
73
+ this.id = id++;
74
+ this._workerHandles = [];
75
+ this._eventQueue = [];
76
+ instances.set(this.id, this);
77
+ this._emitter.on('allWorkersRegistered', () => {
78
+ for (let resolve of this._workerRegisterResolves) {
79
+ resolve();
80
+ }
81
+ this._workerRegisterResolves = [];
82
+ });
83
+ }
84
+
85
+ static deserialize(opts: SerializedMemoryFS): MemoryFS | WorkerFS {
86
+ let existing = instances.get(opts.id);
87
+ if (existing != null) {
88
+ // Correct the count of worker instances since serialization assumes a new instance is created
89
+ WorkerFarm.getWorkerApi().runHandle(opts.handle, [
90
+ 'decrementWorkerInstance',
91
+ [],
92
+ ]);
93
+ return existing;
94
+ }
95
+
96
+ let fs = new WorkerFS(opts.id, nullthrows(opts.handle));
97
+ fs.dirs = opts.dirs;
98
+ fs.files = opts.files;
99
+ fs.symlinks = opts.symlinks;
100
+ return fs;
101
+ }
102
+
103
+ serialize(): SerializedMemoryFS {
104
+ if (!this.handle) {
105
+ this.handle = this.farm.createReverseHandle(
106
+ (fn: string, args: Array<mixed>) => {
107
+ // $FlowFixMe
108
+ return this[fn](...args);
109
+ },
110
+ );
111
+ }
112
+
113
+ // If a worker instance already exists, it will decrement this number
114
+ this._numWorkerInstances++;
115
+
116
+ return {
117
+ $$raw: false,
118
+ id: this.id,
119
+ handle: this.handle,
120
+ dirs: this.dirs,
121
+ files: this.files,
122
+ symlinks: this.symlinks,
123
+ };
124
+ }
125
+
126
+ decrementWorkerInstance() {
127
+ this._numWorkerInstances--;
128
+ if (this._numWorkerInstances === this._workerHandles.length) {
129
+ this._emitter.emit('allWorkersRegistered');
130
+ }
131
+ }
132
+
133
+ cwd(): FilePath {
134
+ return this._cwd;
135
+ }
136
+
137
+ chdir(dir: FilePath) {
138
+ this._cwd = dir;
139
+ }
140
+
141
+ _normalizePath(filePath: FilePath, realpath: boolean = true): FilePath {
142
+ filePath = path.normalize(filePath);
143
+ if (!filePath.startsWith(this.cwd())) {
144
+ filePath = path.resolve(this.cwd(), filePath);
145
+ }
146
+
147
+ // get realpath by following symlinks
148
+ if (realpath) {
149
+ let {root, dir, base} = path.parse(filePath);
150
+ let parts = dir.slice(root.length).split(path.sep).concat(base);
151
+ let res = root;
152
+ for (let part of parts) {
153
+ res = path.join(res, part);
154
+ let symlink = this.symlinks.get(res);
155
+ if (symlink) {
156
+ res = symlink;
157
+ }
158
+ }
159
+
160
+ return res;
161
+ }
162
+
163
+ return filePath;
164
+ }
165
+
166
+ async writeFile(
167
+ filePath: FilePath,
168
+ contents: Buffer | string,
169
+ options?: ?FileOptions,
170
+ ) {
171
+ filePath = this._normalizePath(filePath);
172
+ if (this.dirs.has(filePath)) {
173
+ throw new FSError('EISDIR', filePath, 'is a directory');
174
+ }
175
+
176
+ let dir = path.dirname(filePath);
177
+ if (!this.dirs.has(dir)) {
178
+ throw new FSError('ENOENT', dir, 'does not exist');
179
+ }
180
+
181
+ let buffer = makeShared(contents);
182
+ let file = this.files.get(filePath);
183
+ let mode = (options && options.mode) || 0o666;
184
+ if (file) {
185
+ file.write(buffer, mode);
186
+ this.files.set(filePath, file);
187
+ } else {
188
+ this.files.set(filePath, new File(buffer, mode));
189
+ }
190
+
191
+ await this._sendWorkerEvent({
192
+ type: 'writeFile',
193
+ path: filePath,
194
+ entry: this.files.get(filePath),
195
+ });
196
+
197
+ this._triggerEvent({
198
+ type: file ? 'update' : 'create',
199
+ path: filePath,
200
+ });
201
+ }
202
+
203
+ // eslint-disable-next-line require-await
204
+ async readFile(filePath: FilePath, encoding?: Encoding): Promise<any> {
205
+ return this.readFileSync(filePath, encoding);
206
+ }
207
+
208
+ readFileSync(filePath: FilePath, encoding?: Encoding): any {
209
+ filePath = this._normalizePath(filePath);
210
+ let file = this.files.get(filePath);
211
+ if (file == null) {
212
+ throw new FSError('ENOENT', filePath, 'does not exist');
213
+ }
214
+
215
+ let buffer = file.read();
216
+ if (encoding) {
217
+ return buffer.toString(encoding);
218
+ }
219
+
220
+ return buffer;
221
+ }
222
+
223
+ async copyFile(source: FilePath, destination: FilePath) {
224
+ let contents = await this.readFile(source);
225
+ await this.writeFile(destination, contents);
226
+ }
227
+
228
+ statSync(filePath: FilePath): Stat {
229
+ filePath = this._normalizePath(filePath);
230
+
231
+ let dir = this.dirs.get(filePath);
232
+ if (dir) {
233
+ return dir.stat();
234
+ }
235
+
236
+ let file = this.files.get(filePath);
237
+ if (file == null) {
238
+ throw new FSError('ENOENT', filePath, 'does not exist');
239
+ }
240
+
241
+ return file.stat();
242
+ }
243
+
244
+ // eslint-disable-next-line require-await
245
+ async stat(filePath: FilePath): Promise<Stat> {
246
+ return this.statSync(filePath);
247
+ }
248
+
249
+ readdirSync(dir: FilePath, opts?: ReaddirOptions): any {
250
+ dir = this._normalizePath(dir);
251
+ if (!this.dirs.has(dir)) {
252
+ throw new FSError('ENOENT', dir, 'does not exist');
253
+ }
254
+
255
+ if (!dir.endsWith(path.sep)) {
256
+ dir += path.sep;
257
+ }
258
+
259
+ let res = [];
260
+ for (let [filePath, entry] of this.dirs) {
261
+ if (filePath === dir) {
262
+ continue;
263
+ }
264
+ if (
265
+ filePath.startsWith(dir) &&
266
+ filePath.indexOf(path.sep, dir.length) === -1
267
+ ) {
268
+ let name = filePath.slice(dir.length);
269
+ if (opts?.withFileTypes) {
270
+ res.push(new Dirent(name, entry));
271
+ } else {
272
+ res.push(name);
273
+ }
274
+ }
275
+ }
276
+
277
+ for (let [filePath, entry] of this.files) {
278
+ if (
279
+ filePath.startsWith(dir) &&
280
+ filePath.indexOf(path.sep, dir.length) === -1
281
+ ) {
282
+ let name = filePath.slice(dir.length);
283
+ if (opts?.withFileTypes) {
284
+ res.push(new Dirent(name, entry));
285
+ } else {
286
+ res.push(name);
287
+ }
288
+ }
289
+ }
290
+
291
+ for (let [from] of this.symlinks) {
292
+ if (from.startsWith(dir) && from.indexOf(path.sep, dir.length) === -1) {
293
+ let name = from.slice(dir.length);
294
+ if (opts?.withFileTypes) {
295
+ res.push(new Dirent(name, {mode: S_IFLNK}));
296
+ } else {
297
+ res.push(name);
298
+ }
299
+ }
300
+ }
301
+
302
+ return res;
303
+ }
304
+
305
+ // eslint-disable-next-line require-await
306
+ async readdir(dir: FilePath, opts?: ReaddirOptions): Promise<any> {
307
+ return this.readdirSync(dir, opts);
308
+ }
309
+
310
+ async unlink(filePath: FilePath): Promise<void> {
311
+ filePath = this._normalizePath(filePath);
312
+ if (!this.files.has(filePath) && !this.dirs.has(filePath)) {
313
+ throw new FSError('ENOENT', filePath, 'does not exist');
314
+ }
315
+
316
+ this.files.delete(filePath);
317
+ this.dirs.delete(filePath);
318
+ this.watchers.delete(filePath);
319
+
320
+ await this._sendWorkerEvent({
321
+ type: 'unlink',
322
+ path: filePath,
323
+ });
324
+
325
+ this._triggerEvent({
326
+ type: 'delete',
327
+ path: filePath,
328
+ });
329
+
330
+ return Promise.resolve();
331
+ }
332
+
333
+ async mkdirp(dir: FilePath): Promise<void> {
334
+ dir = this._normalizePath(dir);
335
+ if (this.dirs.has(dir)) {
336
+ return Promise.resolve();
337
+ }
338
+
339
+ if (this.files.has(dir)) {
340
+ throw new FSError('ENOENT', dir, 'is not a directory');
341
+ }
342
+
343
+ let root = path.parse(dir).root;
344
+ while (dir !== root) {
345
+ if (this.dirs.has(dir)) {
346
+ break;
347
+ }
348
+
349
+ this.dirs.set(dir, new Directory());
350
+ await this._sendWorkerEvent({
351
+ type: 'mkdir',
352
+ path: dir,
353
+ });
354
+
355
+ this._triggerEvent({
356
+ type: 'create',
357
+ path: dir,
358
+ });
359
+
360
+ dir = path.dirname(dir);
361
+ }
362
+
363
+ return Promise.resolve();
364
+ }
365
+
366
+ async rimraf(filePath: FilePath): Promise<void> {
367
+ filePath = this._normalizePath(filePath);
368
+
369
+ if (this.dirs.has(filePath)) {
370
+ let dir = filePath + path.sep;
371
+ for (let filePath of this.files.keys()) {
372
+ if (filePath.startsWith(dir)) {
373
+ this.files.delete(filePath);
374
+ await this._sendWorkerEvent({
375
+ type: 'unlink',
376
+ path: filePath,
377
+ });
378
+
379
+ this._triggerEvent({
380
+ type: 'delete',
381
+ path: filePath,
382
+ });
383
+ }
384
+ }
385
+
386
+ for (let dirPath of this.dirs.keys()) {
387
+ if (dirPath.startsWith(dir)) {
388
+ this.dirs.delete(dirPath);
389
+ this.watchers.delete(dirPath);
390
+ await this._sendWorkerEvent({
391
+ type: 'unlink',
392
+ path: filePath,
393
+ });
394
+
395
+ this._triggerEvent({
396
+ type: 'delete',
397
+ path: dirPath,
398
+ });
399
+ }
400
+ }
401
+
402
+ for (let filePath of this.symlinks.keys()) {
403
+ if (filePath.startsWith(dir)) {
404
+ this.symlinks.delete(filePath);
405
+ await this._sendWorkerEvent({
406
+ type: 'unlink',
407
+ path: filePath,
408
+ });
409
+ }
410
+ }
411
+
412
+ this.dirs.delete(filePath);
413
+ await this._sendWorkerEvent({
414
+ type: 'unlink',
415
+ path: filePath,
416
+ });
417
+
418
+ this._triggerEvent({
419
+ type: 'delete',
420
+ path: filePath,
421
+ });
422
+ } else if (this.files.has(filePath)) {
423
+ this.files.delete(filePath);
424
+ await this._sendWorkerEvent({
425
+ type: 'unlink',
426
+ path: filePath,
427
+ });
428
+
429
+ this._triggerEvent({
430
+ type: 'delete',
431
+ path: filePath,
432
+ });
433
+ }
434
+
435
+ return Promise.resolve();
436
+ }
437
+
438
+ async ncp(source: FilePath, destination: FilePath) {
439
+ source = this._normalizePath(source);
440
+
441
+ if (this.dirs.has(source)) {
442
+ if (!this.dirs.has(destination)) {
443
+ this.dirs.set(destination, new Directory());
444
+ await this._sendWorkerEvent({
445
+ type: 'mkdir',
446
+ path: destination,
447
+ });
448
+
449
+ this._triggerEvent({
450
+ type: 'create',
451
+ path: destination,
452
+ });
453
+ }
454
+
455
+ let dir = source + path.sep;
456
+ for (let dirPath of this.dirs.keys()) {
457
+ if (dirPath.startsWith(dir)) {
458
+ let destName = path.join(destination, dirPath.slice(dir.length));
459
+ if (!this.dirs.has(destName)) {
460
+ this.dirs.set(destName, new Directory());
461
+ await this._sendWorkerEvent({
462
+ type: 'mkdir',
463
+ path: destination,
464
+ });
465
+ this._triggerEvent({
466
+ type: 'create',
467
+ path: destName,
468
+ });
469
+ }
470
+ }
471
+ }
472
+
473
+ for (let [filePath, file] of this.files) {
474
+ if (filePath.startsWith(dir)) {
475
+ let destName = path.join(destination, filePath.slice(dir.length));
476
+ let exists = this.files.has(destName);
477
+ this.files.set(destName, file);
478
+ await this._sendWorkerEvent({
479
+ type: 'writeFile',
480
+ path: destName,
481
+ entry: file,
482
+ });
483
+
484
+ this._triggerEvent({
485
+ type: exists ? 'update' : 'create',
486
+ path: destName,
487
+ });
488
+ }
489
+ }
490
+ } else {
491
+ await this.copyFile(source, destination);
492
+ }
493
+ }
494
+
495
+ createReadStream(filePath: FilePath): ReadStream {
496
+ return new ReadStream(this, filePath);
497
+ }
498
+
499
+ createWriteStream(filePath: FilePath, options: ?FileOptions): WriteStream {
500
+ return new WriteStream(this, filePath, options);
501
+ }
502
+
503
+ realpathSync(filePath: FilePath): FilePath {
504
+ return this._normalizePath(filePath);
505
+ }
506
+
507
+ // eslint-disable-next-line require-await
508
+ async realpath(filePath: FilePath): Promise<FilePath> {
509
+ return this.realpathSync(filePath);
510
+ }
511
+
512
+ async symlink(target: FilePath, path: FilePath) {
513
+ target = this._normalizePath(target);
514
+ path = this._normalizePath(path);
515
+ this.symlinks.set(path, target);
516
+ await this._sendWorkerEvent({
517
+ type: 'symlink',
518
+ path,
519
+ target,
520
+ });
521
+ }
522
+
523
+ existsSync(filePath: FilePath): boolean {
524
+ filePath = this._normalizePath(filePath);
525
+ return this.files.has(filePath) || this.dirs.has(filePath);
526
+ }
527
+
528
+ // eslint-disable-next-line require-await
529
+ async exists(filePath: FilePath): Promise<boolean> {
530
+ return this.existsSync(filePath);
531
+ }
532
+
533
+ _triggerEvent(event: Event) {
534
+ this.events.push(event);
535
+ if (this.watchers.size === 0) {
536
+ return;
537
+ }
538
+
539
+ // Batch events
540
+ this._eventQueue.push(event);
541
+ clearTimeout(this._watcherTimer);
542
+
543
+ this._watcherTimer = setTimeout(() => {
544
+ let events = this._eventQueue;
545
+ this._eventQueue = [];
546
+
547
+ for (let [dir, watchers] of this.watchers) {
548
+ if (!dir.endsWith(path.sep)) {
549
+ dir += path.sep;
550
+ }
551
+
552
+ if (event.path.startsWith(dir)) {
553
+ for (let watcher of watchers) {
554
+ watcher.trigger(events);
555
+ }
556
+ }
557
+ }
558
+ }, 50);
559
+ }
560
+
561
+ _registerWorker(handle: Handle) {
562
+ this._workerHandles.push(handle);
563
+ if (this._numWorkerInstances === this._workerHandles.length) {
564
+ this._emitter.emit('allWorkersRegistered');
565
+ }
566
+ }
567
+
568
+ async _sendWorkerEvent(event: WorkerEvent) {
569
+ // Wait for worker instances to register their handles
570
+ while (this._workerHandles.length < this._numWorkerInstances) {
571
+ await new Promise(resolve => this._workerRegisterResolves.push(resolve));
572
+ }
573
+
574
+ await Promise.all(
575
+ this._workerHandles.map(workerHandle =>
576
+ this.farm.workerApi.runHandle(workerHandle, [event]),
577
+ ),
578
+ );
579
+ }
580
+
581
+ watch(
582
+ dir: FilePath,
583
+ fn: (err: ?Error, events: Array<Event>) => mixed,
584
+ opts: WatcherOptions,
585
+ ): Promise<AsyncSubscription> {
586
+ dir = this._normalizePath(dir);
587
+ let watcher = new Watcher(fn, opts);
588
+ let watchers = this.watchers.get(dir);
589
+ if (!watchers) {
590
+ watchers = new Set();
591
+ this.watchers.set(dir, watchers);
592
+ }
593
+
594
+ watchers.add(watcher);
595
+
596
+ return Promise.resolve({
597
+ unsubscribe: () => {
598
+ watchers = nullthrows(watchers);
599
+ watchers.delete(watcher);
600
+
601
+ if (watchers.size === 0) {
602
+ this.watchers.delete(dir);
603
+ }
604
+
605
+ return Promise.resolve();
606
+ },
607
+ });
608
+ }
609
+
610
+ async getEventsSince(
611
+ dir: FilePath,
612
+ snapshot: FilePath,
613
+ opts: WatcherOptions,
614
+ ): Promise<Array<Event>> {
615
+ let contents = await this.readFile(snapshot, 'utf8');
616
+ let len = Number(contents);
617
+ let events = this.events.slice(len);
618
+ let ignore = opts.ignore;
619
+ if (ignore) {
620
+ events = events.filter(
621
+ event => !ignore.some(i => event.path.startsWith(i + path.sep)),
622
+ );
623
+ }
624
+
625
+ return events;
626
+ }
627
+
628
+ async writeSnapshot(dir: FilePath, snapshot: FilePath): Promise<void> {
629
+ await this.writeFile(snapshot, '' + this.events.length);
630
+ }
631
+
632
+ findAncestorFile(
633
+ fileNames: Array<string>,
634
+ fromDir: FilePath,
635
+ root: FilePath,
636
+ ): ?FilePath {
637
+ return findAncestorFile(this, fileNames, fromDir, root);
638
+ }
639
+
640
+ findNodeModule(moduleName: string, fromDir: FilePath): ?FilePath {
641
+ return findNodeModule(this, moduleName, fromDir);
642
+ }
643
+
644
+ findFirstFile(filePaths: Array<FilePath>): ?FilePath {
645
+ return findFirstFile(this, filePaths);
646
+ }
647
+ }
648
+
649
+ class Watcher {
650
+ fn: (err: ?Error, events: Array<Event>) => mixed;
651
+ options: WatcherOptions;
652
+
653
+ constructor(
654
+ fn: (err: ?Error, events: Array<Event>) => mixed,
655
+ options: WatcherOptions,
656
+ ) {
657
+ this.fn = fn;
658
+ this.options = options;
659
+ }
660
+
661
+ trigger(events: Array<Event>) {
662
+ let ignore = this.options.ignore;
663
+ if (ignore) {
664
+ events = events.filter(
665
+ event => !ignore.some(i => event.path.startsWith(i + path.sep)),
666
+ );
667
+ }
668
+
669
+ if (events.length > 0) {
670
+ this.fn(null, events);
671
+ }
672
+ }
673
+ }
674
+
675
+ export class FSError extends Error {
676
+ code: string;
677
+ path: FilePath;
678
+ constructor(code: string, path: FilePath, message: string) {
679
+ super(`${code}: ${path} ${message}`);
680
+ this.name = 'FSError';
681
+ this.code = code;
682
+ this.path = path;
683
+ Error.captureStackTrace?.(this, this.constructor);
684
+ }
685
+ }
686
+
687
+ class ReadStream extends Readable {
688
+ fs: FileSystem;
689
+ filePath: FilePath;
690
+ reading: boolean;
691
+ bytesRead: number;
692
+ constructor(fs: FileSystem, filePath: FilePath) {
693
+ super();
694
+ this.fs = fs;
695
+ this.filePath = filePath;
696
+ this.reading = false;
697
+ this.bytesRead = 0;
698
+ }
699
+
700
+ _read() {
701
+ if (this.reading) {
702
+ return;
703
+ }
704
+
705
+ this.reading = true;
706
+ this.fs.readFile(this.filePath).then(
707
+ res => {
708
+ this.bytesRead += res.byteLength;
709
+ this.push(res);
710
+ this.push(null);
711
+ },
712
+ err => {
713
+ this.emit('error', err);
714
+ },
715
+ );
716
+ }
717
+ }
718
+
719
+ class WriteStream extends Writable {
720
+ fs: FileSystem;
721
+ filePath: FilePath;
722
+ options: ?FileOptions;
723
+ buffer: Buffer;
724
+
725
+ constructor(fs: FileSystem, filePath: FilePath, options: ?FileOptions) {
726
+ super({emitClose: true, autoDestroy: true});
727
+ this.fs = fs;
728
+ this.filePath = filePath;
729
+ this.options = options;
730
+ this.buffer = Buffer.alloc(0);
731
+ }
732
+
733
+ _write(
734
+ chunk: Buffer | string,
735
+ encoding: any,
736
+ callback: (error?: Error) => void,
737
+ ) {
738
+ let c = typeof chunk === 'string' ? Buffer.from(chunk, encoding) : chunk;
739
+ this.buffer = Buffer.concat([this.buffer, c]);
740
+ callback();
741
+ }
742
+
743
+ _final(callback: (error?: Error) => void) {
744
+ this.fs
745
+ .writeFile(this.filePath, this.buffer, this.options)
746
+ .then(callback)
747
+ .catch(callback);
748
+ }
749
+ }
750
+
751
+ const S_IFREG = 0o100000;
752
+ const S_IFDIR = 0o040000;
753
+ const S_IFLNK = 0o120000;
754
+ const S_IFMT = 0o170000;
755
+
756
+ class Entry {
757
+ mode: number;
758
+ atime: number;
759
+ mtime: number;
760
+ ctime: number;
761
+ birthtime: number;
762
+ constructor(mode: number) {
763
+ this.mode = mode;
764
+ let now = Date.now();
765
+ this.atime = now;
766
+ this.mtime = now;
767
+ this.ctime = now;
768
+ this.birthtime = now;
769
+ }
770
+
771
+ access() {
772
+ let now = Date.now();
773
+ this.atime = now;
774
+ this.ctime = now;
775
+ }
776
+
777
+ modify(mode: number) {
778
+ let now = Date.now();
779
+ this.mtime = now;
780
+ this.ctime = now;
781
+ this.mode = mode;
782
+ }
783
+
784
+ getSize(): number {
785
+ return 0;
786
+ }
787
+
788
+ stat(): Stat {
789
+ return new Stat(this);
790
+ }
791
+ }
792
+
793
+ class Stat {
794
+ dev: number = 0;
795
+ ino: number = 0;
796
+ mode: number;
797
+ nlink: number = 0;
798
+ uid: number = 0;
799
+ gid: number = 0;
800
+ rdev: number = 0;
801
+ size: number;
802
+ blksize: number = 0;
803
+ blocks: number = 0;
804
+ atimeMs: number;
805
+ mtimeMs: number;
806
+ ctimeMs: number;
807
+ birthtimeMs: number;
808
+ atime: Date;
809
+ mtime: Date;
810
+ ctime: Date;
811
+ birthtime: Date;
812
+
813
+ constructor(entry: Entry) {
814
+ this.mode = entry.mode;
815
+ this.size = entry.getSize();
816
+ this.atimeMs = entry.atime;
817
+ this.mtimeMs = entry.mtime;
818
+ this.ctimeMs = entry.ctime;
819
+ this.birthtimeMs = entry.birthtime;
820
+ this.atime = new Date(entry.atime);
821
+ this.mtime = new Date(entry.mtime);
822
+ this.ctime = new Date(entry.ctime);
823
+ this.birthtime = new Date(entry.birthtime);
824
+ }
825
+
826
+ isFile(): boolean {
827
+ return Boolean(this.mode & S_IFREG);
828
+ }
829
+
830
+ isDirectory(): boolean {
831
+ return Boolean(this.mode & S_IFDIR);
832
+ }
833
+
834
+ isBlockDevice(): boolean {
835
+ return false;
836
+ }
837
+
838
+ isCharacterDevice(): boolean {
839
+ return false;
840
+ }
841
+
842
+ isSymbolicLink(): boolean {
843
+ return false;
844
+ }
845
+
846
+ isFIFO(): boolean {
847
+ return false;
848
+ }
849
+
850
+ isSocket(): boolean {
851
+ return false;
852
+ }
853
+ }
854
+
855
+ class Dirent {
856
+ name: string;
857
+ #mode: number;
858
+
859
+ constructor(name: string, entry: interface {mode: number}) {
860
+ this.name = name;
861
+ this.#mode = entry.mode;
862
+ }
863
+
864
+ isFile(): boolean {
865
+ return (this.#mode & S_IFMT) === S_IFREG;
866
+ }
867
+
868
+ isDirectory(): boolean {
869
+ return (this.#mode & S_IFMT) === S_IFDIR;
870
+ }
871
+
872
+ isBlockDevice(): boolean {
873
+ return false;
874
+ }
875
+
876
+ isCharacterDevice(): boolean {
877
+ return false;
878
+ }
879
+
880
+ isSymbolicLink(): boolean {
881
+ return (this.#mode & S_IFMT) === S_IFLNK;
882
+ }
883
+
884
+ isFIFO(): boolean {
885
+ return false;
886
+ }
887
+
888
+ isSocket(): boolean {
889
+ return false;
890
+ }
891
+ }
892
+
893
+ export class File extends Entry {
894
+ buffer: Buffer;
895
+ constructor(buffer: Buffer, mode: number) {
896
+ super(S_IFREG | mode);
897
+ this.buffer = buffer;
898
+ }
899
+
900
+ read(): Buffer {
901
+ super.access();
902
+ return Buffer.from(this.buffer);
903
+ }
904
+
905
+ write(buffer: Buffer, mode: number) {
906
+ super.modify(S_IFREG | mode);
907
+ this.buffer = buffer;
908
+ }
909
+
910
+ getSize(): number {
911
+ return this.buffer.byteLength;
912
+ }
913
+ }
914
+
915
+ class Directory extends Entry {
916
+ constructor() {
917
+ super(S_IFDIR);
918
+ }
919
+ }
920
+
921
+ export function makeShared(contents: Buffer | string): Buffer {
922
+ if (typeof contents !== 'string' && contents.buffer instanceof SharedBuffer) {
923
+ return contents;
924
+ }
925
+
926
+ let contentsBuffer: Buffer | string = contents;
927
+ // $FlowFixMe
928
+ if (process.browser) {
929
+ // For the polyfilled buffer module, it's faster to always convert once so that the subsequent
930
+ // operations are fast (.byteLength and using .set instead of .write)
931
+ contentsBuffer =
932
+ contentsBuffer instanceof Buffer
933
+ ? contentsBuffer
934
+ : Buffer.from(contentsBuffer);
935
+ }
936
+
937
+ let length = Buffer.byteLength(contentsBuffer);
938
+ let shared = new SharedBuffer(length);
939
+ let buffer = Buffer.from(shared);
940
+ if (length > 0) {
941
+ if (typeof contentsBuffer === 'string') {
942
+ buffer.write(contentsBuffer);
943
+ } else {
944
+ buffer.set(contentsBuffer);
945
+ }
946
+ }
947
+
948
+ return buffer;
949
+ }
950
+
951
+ class WorkerFS extends MemoryFS {
952
+ id: number;
953
+ handleFn: HandleFunction;
954
+
955
+ constructor(id: number, handle: Handle) {
956
+ // TODO Make this not a subclass
957
+ // $FlowFixMe
958
+ super();
959
+ this.id = id;
960
+ this.handleFn = (methodName, args) =>
961
+ WorkerFarm.getWorkerApi().runHandle(handle, [methodName, args]);
962
+
963
+ this.handleFn('_registerWorker', [
964
+ WorkerFarm.getWorkerApi().createReverseHandle(event => {
965
+ switch (event.type) {
966
+ case 'writeFile':
967
+ this.files.set(event.path, event.entry);
968
+ break;
969
+ case 'unlink':
970
+ this.files.delete(event.path);
971
+ this.dirs.delete(event.path);
972
+ this.symlinks.delete(event.path);
973
+ break;
974
+ case 'mkdir':
975
+ this.dirs.set(event.path, new Directory());
976
+ break;
977
+ case 'symlink':
978
+ this.symlinks.set(event.path, event.target);
979
+ break;
980
+ }
981
+ }),
982
+ ]);
983
+ }
984
+
985
+ static deserialize(opts: SerializedMemoryFS): MemoryFS {
986
+ return nullthrows(instances.get(opts.id));
987
+ }
988
+
989
+ serialize(): SerializedMemoryFS {
990
+ // $FlowFixMe
991
+ return {
992
+ id: this.id,
993
+ };
994
+ }
995
+
996
+ writeFile(
997
+ filePath: FilePath,
998
+ contents: Buffer | string,
999
+ options: ?FileOptions,
1000
+ ): Promise<void> {
1001
+ super.writeFile(filePath, contents, options);
1002
+ let buffer = makeShared(contents);
1003
+ return this.handleFn('writeFile', [filePath, buffer, options]);
1004
+ }
1005
+
1006
+ unlink(filePath: FilePath): Promise<void> {
1007
+ super.unlink(filePath);
1008
+ return this.handleFn('unlink', [filePath]);
1009
+ }
1010
+
1011
+ mkdirp(dir: FilePath): Promise<void> {
1012
+ super.mkdirp(dir);
1013
+ return this.handleFn('mkdirp', [dir]);
1014
+ }
1015
+
1016
+ rimraf(filePath: FilePath): Promise<void> {
1017
+ super.rimraf(filePath);
1018
+ return this.handleFn('rimraf', [filePath]);
1019
+ }
1020
+
1021
+ ncp(source: FilePath, destination: FilePath): Promise<void> {
1022
+ super.ncp(source, destination);
1023
+ return this.handleFn('ncp', [source, destination]);
1024
+ }
1025
+
1026
+ symlink(target: FilePath, path: FilePath): Promise<void> {
1027
+ super.symlink(target, path);
1028
+ return this.handleFn('symlink', [target, path]);
1029
+ }
1030
+ }
1031
+
1032
+ registerSerializableClass(`${packageJSON.version}:MemoryFS`, MemoryFS);
1033
+ registerSerializableClass(`${packageJSON.version}:WorkerFS`, WorkerFS);
1034
+ registerSerializableClass(`${packageJSON.version}:Stat`, Stat);
1035
+ registerSerializableClass(`${packageJSON.version}:File`, File);
1036
+ registerSerializableClass(`${packageJSON.version}:Directory`, Directory);