metro-file-map 0.84.2 → 0.84.3
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/package.json +1 -1
- package/src/Watcher.d.ts +6 -9
- package/src/Watcher.js +66 -39
- package/src/Watcher.js.flow +84 -51
- package/src/crawlers/node/index.d.ts +3 -5
- package/src/crawlers/node/index.js +4 -1
- package/src/crawlers/node/index.js.flow +8 -6
- package/src/crawlers/watchman/index.d.ts +5 -12
- package/src/crawlers/watchman/index.js.flow +2 -6
- package/src/flow-types.d.ts +81 -32
- package/src/flow-types.js.flow +89 -29
- package/src/index.d.ts +4 -4
- package/src/index.js +145 -120
- package/src/index.js.flow +199 -149
- package/src/lib/FileSystemChangeAggregator.d.ts +40 -0
- package/src/lib/FileSystemChangeAggregator.js +89 -0
- package/src/lib/FileSystemChangeAggregator.js.flow +143 -0
- package/src/lib/TreeFS.d.ts +16 -8
- package/src/lib/TreeFS.js +67 -16
- package/src/lib/TreeFS.js.flow +89 -16
- package/src/plugins/DependencyPlugin.d.ts +2 -13
- package/src/plugins/DependencyPlugin.js +1 -3
- package/src/plugins/DependencyPlugin.js.flow +1 -16
- package/src/plugins/HastePlugin.d.ts +3 -11
- package/src/plugins/HastePlugin.js +11 -11
- package/src/plugins/HastePlugin.js.flow +12 -12
- package/src/plugins/MockPlugin.d.ts +3 -5
- package/src/plugins/MockPlugin.js +17 -20
- package/src/plugins/MockPlugin.js.flow +18 -22
- package/src/watchers/FallbackWatcher.js +19 -3
- package/src/watchers/FallbackWatcher.js.flow +28 -5
- package/src/watchers/NativeWatcher.d.ts +2 -2
- package/src/watchers/NativeWatcher.js +27 -5
- package/src/watchers/NativeWatcher.js.flow +33 -6
- package/src/watchers/common.d.ts +3 -1
- package/src/watchers/common.js +6 -1
- package/src/watchers/common.js.flow +1 -0
package/src/index.js.flow
CHANGED
|
@@ -17,12 +17,13 @@ import type {
|
|
|
17
17
|
CacheManagerFactory,
|
|
18
18
|
CacheManagerFactoryOptions,
|
|
19
19
|
CanonicalPath,
|
|
20
|
+
ChangedFileMetadata,
|
|
20
21
|
ChangeEvent,
|
|
21
22
|
ChangeEventClock,
|
|
22
23
|
ChangeEventMetadata,
|
|
23
24
|
Console,
|
|
24
25
|
CrawlerOptions,
|
|
25
|
-
|
|
26
|
+
CrawlResult,
|
|
26
27
|
FileData,
|
|
27
28
|
FileMapPlugin,
|
|
28
29
|
FileMapPluginWorker,
|
|
@@ -31,6 +32,7 @@ import type {
|
|
|
31
32
|
HasteMapData,
|
|
32
33
|
HasteMapItem,
|
|
33
34
|
HType,
|
|
35
|
+
InputFileMapPlugin,
|
|
34
36
|
MutableFileSystem,
|
|
35
37
|
Path,
|
|
36
38
|
PerfLogger,
|
|
@@ -44,6 +46,7 @@ import {DiskCacheManager} from './cache/DiskCacheManager';
|
|
|
44
46
|
import H from './constants';
|
|
45
47
|
import checkWatchmanCapabilities from './lib/checkWatchmanCapabilities';
|
|
46
48
|
import {FileProcessor} from './lib/FileProcessor';
|
|
49
|
+
import {FileSystemChangeAggregator} from './lib/FileSystemChangeAggregator';
|
|
47
50
|
import normalizePathSeparatorsToPosix from './lib/normalizePathSeparatorsToPosix';
|
|
48
51
|
import normalizePathSeparatorsToSystem from './lib/normalizePathSeparatorsToSystem';
|
|
49
52
|
import {RootPathUtils} from './lib/RootPathUtils';
|
|
@@ -52,7 +55,6 @@ import {Watcher} from './Watcher';
|
|
|
52
55
|
import EventEmitter from 'events';
|
|
53
56
|
import {promises as fsPromises} from 'fs';
|
|
54
57
|
import invariant from 'invariant';
|
|
55
|
-
import nullthrows from 'nullthrows';
|
|
56
58
|
import * as path from 'path';
|
|
57
59
|
import {performance} from 'perf_hooks';
|
|
58
60
|
|
|
@@ -69,6 +71,7 @@ export type {
|
|
|
69
71
|
FileSystem,
|
|
70
72
|
HasteMapData,
|
|
71
73
|
HasteMapItem,
|
|
74
|
+
InputFileMapPlugin,
|
|
72
75
|
};
|
|
73
76
|
|
|
74
77
|
export type InputOptions = Readonly<{
|
|
@@ -77,7 +80,7 @@ export type InputOptions = Readonly<{
|
|
|
77
80
|
extensions: ReadonlyArray<string>,
|
|
78
81
|
forceNodeFilesystemAPI?: ?boolean,
|
|
79
82
|
ignorePattern?: ?RegExp,
|
|
80
|
-
plugins?: ReadonlyArray<
|
|
83
|
+
plugins?: ReadonlyArray<InputFileMapPlugin>,
|
|
81
84
|
retainAllFiles: boolean,
|
|
82
85
|
rootDir: string,
|
|
83
86
|
roots: ReadonlyArray<string>,
|
|
@@ -111,12 +114,24 @@ type InternalOptions = Readonly<{
|
|
|
111
114
|
watchmanDeferStates: ReadonlyArray<string>,
|
|
112
115
|
}>;
|
|
113
116
|
|
|
114
|
-
// $FlowFixMe[unclear-type] Plugin types cannot be known statically
|
|
115
|
-
type AnyFileMapPlugin = FileMapPlugin<any, any>;
|
|
116
117
|
type IndexedPlugin = Readonly<{
|
|
117
|
-
|
|
118
|
+
// $FlowFixMe[unclear-type] Plugin types cannot be known statically
|
|
119
|
+
plugin: FileMapPlugin<any, any>,
|
|
118
120
|
dataIdx: ?number,
|
|
119
121
|
}>;
|
|
122
|
+
type InternalEnqueuedEvent = Readonly<
|
|
123
|
+
| {
|
|
124
|
+
clock: ?ChangeEventClock,
|
|
125
|
+
relativeFilePath: string,
|
|
126
|
+
metadata: FileMetadata,
|
|
127
|
+
type: 'touch',
|
|
128
|
+
}
|
|
129
|
+
| {
|
|
130
|
+
clock: ?ChangeEventClock,
|
|
131
|
+
relativeFilePath: string,
|
|
132
|
+
type: 'delete',
|
|
133
|
+
},
|
|
134
|
+
>;
|
|
120
135
|
|
|
121
136
|
export {DiskCacheManager} from './cache/DiskCacheManager';
|
|
122
137
|
export {default as DependencyPlugin} from './plugins/DependencyPlugin';
|
|
@@ -421,7 +436,7 @@ export default class FileMap extends EventEmitter {
|
|
|
421
436
|
};
|
|
422
437
|
},
|
|
423
438
|
fileIterator: opts =>
|
|
424
|
-
|
|
439
|
+
mapIterable(
|
|
425
440
|
fileSystem.metadataIterator(opts),
|
|
426
441
|
({baseName, canonicalPath, metadata}) => ({
|
|
427
442
|
baseName,
|
|
@@ -437,7 +452,13 @@ export default class FileMap extends EventEmitter {
|
|
|
437
452
|
]);
|
|
438
453
|
|
|
439
454
|
// Update `fileSystem` and plugins based on the file delta.
|
|
440
|
-
await this.#applyFileDelta(
|
|
455
|
+
const actualChanges = await this.#applyFileDelta(
|
|
456
|
+
fileSystem,
|
|
457
|
+
plugins,
|
|
458
|
+
fileDelta,
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
const changeSize = actualChanges.getSize();
|
|
441
462
|
|
|
442
463
|
// Validate plugins before persisting them.
|
|
443
464
|
plugins.forEach(({plugin}) => plugin.assertValid());
|
|
@@ -447,14 +468,9 @@ export default class FileMap extends EventEmitter {
|
|
|
447
468
|
fileSystem,
|
|
448
469
|
watchmanClocks,
|
|
449
470
|
plugins,
|
|
450
|
-
|
|
451
|
-
fileDelta.removedFiles,
|
|
452
|
-
);
|
|
453
|
-
debug(
|
|
454
|
-
'Finished mapping files (%d changes, %d removed).',
|
|
455
|
-
fileDelta.changedFiles.size,
|
|
456
|
-
fileDelta.removedFiles.size,
|
|
471
|
+
changeSize > 0,
|
|
457
472
|
);
|
|
473
|
+
debug('Finished mapping files (%d changes).', changeSize);
|
|
458
474
|
|
|
459
475
|
await this.#watch(fileSystem, watchmanClocks, plugins);
|
|
460
476
|
return {fileSystem};
|
|
@@ -492,11 +508,7 @@ export default class FileMap extends EventEmitter {
|
|
|
492
508
|
*/
|
|
493
509
|
async #buildFileDelta(
|
|
494
510
|
previousState: CrawlerOptions['previousState'],
|
|
495
|
-
): Promise<{
|
|
496
|
-
removedFiles: Set<CanonicalPath>,
|
|
497
|
-
changedFiles: FileData,
|
|
498
|
-
clocks?: WatchmanClocks,
|
|
499
|
-
}> {
|
|
511
|
+
): Promise<CrawlResult> {
|
|
500
512
|
this.#startupPerfLogger?.point('buildFileDelta_start');
|
|
501
513
|
|
|
502
514
|
const {
|
|
@@ -540,10 +552,9 @@ export default class FileMap extends EventEmitter {
|
|
|
540
552
|
|
|
541
553
|
watcher.on('status', status => this.emit('status', status));
|
|
542
554
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
});
|
|
555
|
+
const result = await watcher.crawl();
|
|
556
|
+
this.#startupPerfLogger?.point('buildFileDelta_end');
|
|
557
|
+
return result;
|
|
547
558
|
}
|
|
548
559
|
|
|
549
560
|
#maybeReadLink(normalPath: Path, fileMetadata: FileMetadata): ?Promise<void> {
|
|
@@ -568,25 +579,20 @@ export default class FileMap extends EventEmitter {
|
|
|
568
579
|
removedFiles: ReadonlySet<CanonicalPath>,
|
|
569
580
|
clocks?: WatchmanClocks,
|
|
570
581
|
}>,
|
|
571
|
-
): Promise<
|
|
582
|
+
): Promise<FileSystemChangeAggregator> {
|
|
572
583
|
this.#startupPerfLogger?.point('applyFileDelta_start');
|
|
573
584
|
const {changedFiles, removedFiles} = delta;
|
|
574
585
|
this.#startupPerfLogger?.point('applyFileDelta_preprocess_start');
|
|
575
|
-
const missingFiles: Set<string> = new Set();
|
|
576
|
-
|
|
577
586
|
// Remove files first so that we don't mistake moved modules
|
|
578
587
|
// modules as duplicates.
|
|
579
588
|
this.#startupPerfLogger?.point('applyFileDelta_remove_start');
|
|
580
|
-
const
|
|
589
|
+
const changeAggregator = new FileSystemChangeAggregator();
|
|
581
590
|
for (const relativeFilePath of removedFiles) {
|
|
582
|
-
|
|
583
|
-
if (metadata) {
|
|
584
|
-
removed.push([relativeFilePath, metadata]);
|
|
585
|
-
}
|
|
591
|
+
fileSystem.remove(relativeFilePath, changeAggregator);
|
|
586
592
|
}
|
|
587
593
|
this.#startupPerfLogger?.point('applyFileDelta_remove_end');
|
|
588
594
|
|
|
589
|
-
const readLinkPromises = [];
|
|
595
|
+
const readLinkPromises: Array<Promise<void>> = [];
|
|
590
596
|
const readLinkErrors: Array<{
|
|
591
597
|
normalFilePath: string,
|
|
592
598
|
error: Error & {code?: string},
|
|
@@ -606,9 +612,9 @@ export default class FileMap extends EventEmitter {
|
|
|
606
612
|
const maybeReadLink = this.#maybeReadLink(normalFilePath, fileData);
|
|
607
613
|
if (maybeReadLink) {
|
|
608
614
|
readLinkPromises.push(
|
|
609
|
-
maybeReadLink.catch(error =>
|
|
610
|
-
readLinkErrors.push({normalFilePath, error})
|
|
611
|
-
),
|
|
615
|
+
maybeReadLink.catch(error => {
|
|
616
|
+
readLinkErrors.push({normalFilePath, error});
|
|
617
|
+
}),
|
|
612
618
|
);
|
|
613
619
|
}
|
|
614
620
|
}
|
|
@@ -647,38 +653,32 @@ export default class FileMap extends EventEmitter {
|
|
|
647
653
|
/* $FlowFixMe[incompatible-type] Error exposed after improved typing of
|
|
648
654
|
* Array.{includes,indexOf,lastIndexOf} */
|
|
649
655
|
if (['ENOENT', 'EACCESS'].includes(error.code)) {
|
|
650
|
-
|
|
656
|
+
delta.changedFiles.delete(normalFilePath);
|
|
657
|
+
fileSystem.remove(normalFilePath, changeAggregator);
|
|
651
658
|
} else {
|
|
652
659
|
// Anything else is fatal.
|
|
653
660
|
throw error;
|
|
654
661
|
}
|
|
655
662
|
}
|
|
656
|
-
|
|
657
|
-
changedFiles.delete(relativeFilePath);
|
|
658
|
-
const metadata = fileSystem.remove(relativeFilePath);
|
|
659
|
-
if (metadata) {
|
|
660
|
-
removed.push([relativeFilePath, metadata]);
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
+
|
|
663
664
|
this.#startupPerfLogger?.point('applyFileDelta_missing_end');
|
|
664
665
|
|
|
665
666
|
this.#startupPerfLogger?.point('applyFileDelta_add_start');
|
|
666
|
-
fileSystem.bulkAddOrModify(changedFiles);
|
|
667
|
+
fileSystem.bulkAddOrModify(changedFiles, changeAggregator);
|
|
667
668
|
this.#startupPerfLogger?.point('applyFileDelta_add_end');
|
|
668
669
|
|
|
669
670
|
this.#startupPerfLogger?.point('applyFileDelta_updatePlugins_start');
|
|
670
|
-
plugins.forEach(({plugin, dataIdx}) => {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
?
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
addedOrModified: mapIterator(changedFiles.entries(), mapFn),
|
|
677
|
-
removed: mapIterator(removed.values(), mapFn),
|
|
678
|
-
});
|
|
671
|
+
this.#plugins.forEach(({plugin, dataIdx}) => {
|
|
672
|
+
plugin.onChanged(
|
|
673
|
+
changeAggregator.getMappedView(
|
|
674
|
+
dataIdx != null ? metadata => metadata[dataIdx] : () => null,
|
|
675
|
+
),
|
|
676
|
+
);
|
|
679
677
|
});
|
|
680
678
|
this.#startupPerfLogger?.point('applyFileDelta_updatePlugins_end');
|
|
681
679
|
this.#startupPerfLogger?.point('applyFileDelta_end');
|
|
680
|
+
|
|
681
|
+
return changeAggregator;
|
|
682
682
|
}
|
|
683
683
|
|
|
684
684
|
/**
|
|
@@ -688,8 +688,7 @@ export default class FileMap extends EventEmitter {
|
|
|
688
688
|
fileSystem: FileSystem,
|
|
689
689
|
clocks: WatchmanClocks,
|
|
690
690
|
plugins: ReadonlyArray<IndexedPlugin>,
|
|
691
|
-
|
|
692
|
-
removed: Set<CanonicalPath>,
|
|
691
|
+
changedSinceCacheRead: boolean,
|
|
693
692
|
) {
|
|
694
693
|
this.#startupPerfLogger?.point('persist_start');
|
|
695
694
|
await this.#cacheManager.write(
|
|
@@ -704,7 +703,7 @@ export default class FileMap extends EventEmitter {
|
|
|
704
703
|
),
|
|
705
704
|
}),
|
|
706
705
|
{
|
|
707
|
-
changedSinceCacheRead
|
|
706
|
+
changedSinceCacheRead,
|
|
708
707
|
eventSource: {
|
|
709
708
|
onChange: cb => {
|
|
710
709
|
// Inform the cache about changes to internal state, including:
|
|
@@ -743,20 +742,68 @@ export default class FileMap extends EventEmitter {
|
|
|
743
742
|
const hasWatchedExtension = (filePath: string) =>
|
|
744
743
|
this.#options.extensions.some(ext => filePath.endsWith(ext));
|
|
745
744
|
|
|
746
|
-
let changeQueue: Promise<null | void> = Promise.resolve();
|
|
747
745
|
let nextEmit: ?{
|
|
748
|
-
|
|
746
|
+
events: Array<InternalEnqueuedEvent>,
|
|
749
747
|
firstEventTimestamp: number,
|
|
750
748
|
firstEnqueuedTimestamp: number,
|
|
751
749
|
} = null;
|
|
752
750
|
|
|
753
751
|
const emitChange = () => {
|
|
754
|
-
if (nextEmit == null
|
|
752
|
+
if (nextEmit == null) {
|
|
755
753
|
// Nothing to emit
|
|
756
754
|
return;
|
|
757
755
|
}
|
|
758
|
-
const {
|
|
759
|
-
|
|
756
|
+
const {events, firstEventTimestamp, firstEnqueuedTimestamp} = nextEmit;
|
|
757
|
+
|
|
758
|
+
const changeAggregator = new FileSystemChangeAggregator();
|
|
759
|
+
|
|
760
|
+
// Process a sequence of events. Note that preserving ordering is
|
|
761
|
+
// important here - a file may be both removed and added in the same
|
|
762
|
+
// batch.
|
|
763
|
+
// `changeAggregator` flattens this over time into the net change from
|
|
764
|
+
// this sequence.
|
|
765
|
+
for (const event of events) {
|
|
766
|
+
const {relativeFilePath, clock} = event;
|
|
767
|
+
if (event.type === 'delete') {
|
|
768
|
+
fileSystem.remove(relativeFilePath, changeAggregator);
|
|
769
|
+
} else {
|
|
770
|
+
fileSystem.addOrModify(
|
|
771
|
+
relativeFilePath,
|
|
772
|
+
event.metadata,
|
|
773
|
+
changeAggregator,
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
this.#updateClock(clocks, clock);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const changeSize = changeAggregator.getSize();
|
|
780
|
+
|
|
781
|
+
if (changeSize === 0) {
|
|
782
|
+
// We had events, but they've exactly cancelled each other out, reset
|
|
783
|
+
// so that timers are correct for the next change.
|
|
784
|
+
nextEmit = null;
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
const _netChange = changeAggregator.getView();
|
|
789
|
+
this.#plugins.forEach(({plugin, dataIdx}) => {
|
|
790
|
+
plugin.onChanged(
|
|
791
|
+
changeAggregator.getMappedView(
|
|
792
|
+
dataIdx != null ? metadata => metadata[dataIdx] : () => null,
|
|
793
|
+
),
|
|
794
|
+
);
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
const toPublicMetadata = (
|
|
798
|
+
metadata: Readonly<FileMetadata>,
|
|
799
|
+
): ChangedFileMetadata => ({
|
|
800
|
+
isSymlink: metadata[H.SYMLINK] !== 0,
|
|
801
|
+
modifiedTime: metadata[H.MTIME] ?? null,
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
const changesWithMetadata =
|
|
805
|
+
changeAggregator.getMappedView(toPublicMetadata);
|
|
806
|
+
|
|
760
807
|
const hmrPerfLogger = this.#options.perfLoggerFactory?.('HMR', {
|
|
761
808
|
key: this.#getNextChangeID(),
|
|
762
809
|
});
|
|
@@ -766,21 +813,24 @@ export default class FileMap extends EventEmitter {
|
|
|
766
813
|
timestamp: firstEnqueuedTimestamp,
|
|
767
814
|
});
|
|
768
815
|
hmrPerfLogger.point('waitingForChangeInterval_end');
|
|
769
|
-
hmrPerfLogger.annotate({
|
|
770
|
-
int: {eventsQueueLength: eventsQueue.length},
|
|
771
|
-
});
|
|
816
|
+
hmrPerfLogger.annotate({int: {changeSize}});
|
|
772
817
|
hmrPerfLogger.point('fileChange_start');
|
|
773
818
|
}
|
|
774
819
|
const changeEvent: ChangeEvent = {
|
|
775
|
-
|
|
820
|
+
changes: changesWithMetadata,
|
|
776
821
|
logger: hmrPerfLogger,
|
|
822
|
+
rootDir: this.#options.rootDir,
|
|
777
823
|
};
|
|
778
824
|
this.emit('change', changeEvent);
|
|
779
825
|
nextEmit = null;
|
|
780
826
|
};
|
|
781
827
|
|
|
828
|
+
let changeQueue: Promise<null | void> = Promise.resolve();
|
|
829
|
+
|
|
782
830
|
const onChange = (change: WatcherBackendChangeEvent) => {
|
|
831
|
+
// Recrawl events bypass normal filtering - they trigger a full subdirectory scan
|
|
783
832
|
if (
|
|
833
|
+
change.event !== 'recrawl' &&
|
|
784
834
|
change.metadata &&
|
|
785
835
|
// Ignore all directory events
|
|
786
836
|
(change.metadata.type === 'd' ||
|
|
@@ -806,73 +856,38 @@ export default class FileMap extends EventEmitter {
|
|
|
806
856
|
|
|
807
857
|
const relativeFilePath =
|
|
808
858
|
this.#pathUtils.absoluteToNormal(absoluteFilePath);
|
|
809
|
-
const linkStats = fileSystem.linkStats(relativeFilePath);
|
|
810
|
-
|
|
811
|
-
// The file has been accessed, not modified. If the modified time is
|
|
812
|
-
// null, then it is assumed that the watcher does not have capabilities
|
|
813
|
-
// to detect modified time, and change processing proceeds.
|
|
814
|
-
if (
|
|
815
|
-
change.event === 'touch' &&
|
|
816
|
-
linkStats != null &&
|
|
817
|
-
change.metadata.modifiedTime != null &&
|
|
818
|
-
linkStats.modifiedTime === change.metadata.modifiedTime
|
|
819
|
-
) {
|
|
820
|
-
return;
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// Emitted events, unlike memoryless backend events, specify 'add' or
|
|
824
|
-
// 'change' instead of 'touch'.
|
|
825
|
-
const eventTypeToEmit =
|
|
826
|
-
change.event === 'touch'
|
|
827
|
-
? linkStats == null
|
|
828
|
-
? 'add'
|
|
829
|
-
: 'change'
|
|
830
|
-
: 'delete';
|
|
831
859
|
|
|
832
860
|
const onChangeStartTime = performance.timeOrigin + performance.now();
|
|
833
861
|
|
|
862
|
+
const enqueueEvent = (event: InternalEnqueuedEvent) => {
|
|
863
|
+
nextEmit ??= {
|
|
864
|
+
events: [],
|
|
865
|
+
firstEnqueuedTimestamp: performance.timeOrigin + performance.now(),
|
|
866
|
+
firstEventTimestamp: onChangeStartTime,
|
|
867
|
+
};
|
|
868
|
+
nextEmit.events.push(event);
|
|
869
|
+
};
|
|
870
|
+
|
|
834
871
|
changeQueue = changeQueue
|
|
835
872
|
.then(async () => {
|
|
836
873
|
// If we get duplicate events for the same file, ignore them.
|
|
837
874
|
if (
|
|
838
875
|
nextEmit != null &&
|
|
839
|
-
nextEmit.
|
|
876
|
+
nextEmit.events.find(
|
|
840
877
|
event =>
|
|
841
|
-
event.type ===
|
|
842
|
-
event.
|
|
878
|
+
event.type === change.event &&
|
|
879
|
+
event.relativeFilePath === relativeFilePath &&
|
|
843
880
|
((!event.metadata && !change.metadata) ||
|
|
844
881
|
(event.metadata &&
|
|
845
882
|
change.metadata &&
|
|
846
|
-
event.metadata.
|
|
883
|
+
event.metadata[H.MTIME] != null &&
|
|
847
884
|
change.metadata.modifiedTime != null &&
|
|
848
|
-
event.metadata.
|
|
849
|
-
change.metadata.modifiedTime)),
|
|
885
|
+
event.metadata[H.MTIME] === change.metadata.modifiedTime)),
|
|
850
886
|
)
|
|
851
887
|
) {
|
|
852
888
|
return null;
|
|
853
889
|
}
|
|
854
890
|
|
|
855
|
-
const linkStats = fileSystem.linkStats(relativeFilePath);
|
|
856
|
-
|
|
857
|
-
const enqueueEvent = (metadata: ChangeEventMetadata) => {
|
|
858
|
-
const event = {
|
|
859
|
-
filePath: absoluteFilePath,
|
|
860
|
-
metadata,
|
|
861
|
-
type: eventTypeToEmit,
|
|
862
|
-
};
|
|
863
|
-
if (nextEmit == null) {
|
|
864
|
-
nextEmit = {
|
|
865
|
-
eventsQueue: [event],
|
|
866
|
-
firstEnqueuedTimestamp:
|
|
867
|
-
performance.timeOrigin + performance.now(),
|
|
868
|
-
firstEventTimestamp: onChangeStartTime,
|
|
869
|
-
};
|
|
870
|
-
} else {
|
|
871
|
-
nextEmit.eventsQueue.push(event);
|
|
872
|
-
}
|
|
873
|
-
return null;
|
|
874
|
-
};
|
|
875
|
-
|
|
876
891
|
// If the file was added or modified,
|
|
877
892
|
// parse it and update the file map.
|
|
878
893
|
if (change.event === 'touch') {
|
|
@@ -902,17 +917,12 @@ export default class FileMap extends EventEmitter {
|
|
|
902
917
|
},
|
|
903
918
|
);
|
|
904
919
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
fileMetadata[dataIdx],
|
|
912
|
-
)
|
|
913
|
-
: plugin.onNewOrModifiedFile(relativeFilePath),
|
|
914
|
-
);
|
|
915
|
-
enqueueEvent(change.metadata);
|
|
920
|
+
enqueueEvent({
|
|
921
|
+
clock: change.clock,
|
|
922
|
+
relativeFilePath,
|
|
923
|
+
metadata: fileMetadata,
|
|
924
|
+
type: change.event,
|
|
925
|
+
});
|
|
916
926
|
} catch (e) {
|
|
917
927
|
if (!['ENOENT', 'EACCESS'].includes(e.code)) {
|
|
918
928
|
throw e;
|
|
@@ -925,26 +935,68 @@ export default class FileMap extends EventEmitter {
|
|
|
925
935
|
// point.
|
|
926
936
|
}
|
|
927
937
|
} else if (change.event === 'delete') {
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
938
|
+
enqueueEvent({
|
|
939
|
+
clock: change.clock,
|
|
940
|
+
relativeFilePath,
|
|
941
|
+
type: 'delete',
|
|
942
|
+
});
|
|
943
|
+
} else if (change.event === 'recrawl') {
|
|
944
|
+
// Recrawl event: flush pending changes and re-crawl the directory
|
|
945
|
+
emitChange();
|
|
946
|
+
|
|
947
|
+
// The relativePath is relative to the watcher root (change.root),
|
|
948
|
+
// but we need a path relative to rootDir for the recrawl.
|
|
949
|
+
const absoluteDirPath = path.join(
|
|
950
|
+
change.root,
|
|
951
|
+
normalizePathSeparatorsToSystem(change.relativePath),
|
|
952
|
+
);
|
|
953
|
+
const subpath = this.#pathUtils.absoluteToNormal(absoluteDirPath);
|
|
954
|
+
|
|
955
|
+
// Crawl the specific subdirectory
|
|
956
|
+
const watcher = this.#watcher;
|
|
957
|
+
invariant(watcher != null, 'Watcher must be initialized');
|
|
958
|
+
const crawlResult = await watcher.recrawl(subpath, fileSystem);
|
|
959
|
+
|
|
960
|
+
// Skip if no changes
|
|
961
|
+
if (
|
|
962
|
+
crawlResult.changedFiles.size === 0 &&
|
|
963
|
+
crawlResult.removedFiles.size === 0
|
|
964
|
+
) {
|
|
931
965
|
return null;
|
|
932
966
|
}
|
|
933
|
-
|
|
934
|
-
//
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
? plugin.onRemovedFile(relativeFilePath, metadata[dataIdx])
|
|
940
|
-
: plugin.onRemovedFile(relativeFilePath),
|
|
967
|
+
|
|
968
|
+
// Reuse the same batch processing logic as build()
|
|
969
|
+
const recrawlChangeAggregator = await this.#applyFileDelta(
|
|
970
|
+
fileSystem,
|
|
971
|
+
this.#plugins,
|
|
972
|
+
crawlResult,
|
|
941
973
|
);
|
|
942
974
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
975
|
+
// Update clock if provided
|
|
976
|
+
this.#updateClock(clocks, change.clock);
|
|
977
|
+
|
|
978
|
+
// Skip emit if no changes after processing
|
|
979
|
+
if (recrawlChangeAggregator.getSize() === 0) {
|
|
980
|
+
return null;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Emit changes directly
|
|
984
|
+
const toPublicMetadata = (
|
|
985
|
+
metadata: Readonly<FileMetadata>,
|
|
986
|
+
): ChangedFileMetadata => ({
|
|
987
|
+
isSymlink: metadata[H.SYMLINK] !== 0,
|
|
988
|
+
modifiedTime: metadata[H.MTIME] ?? null,
|
|
947
989
|
});
|
|
990
|
+
|
|
991
|
+
const changesWithMetadata =
|
|
992
|
+
recrawlChangeAggregator.getMappedView(toPublicMetadata);
|
|
993
|
+
|
|
994
|
+
const changeEvent: ChangeEvent = {
|
|
995
|
+
changes: changesWithMetadata,
|
|
996
|
+
logger: null,
|
|
997
|
+
rootDir: this.#options.rootDir,
|
|
998
|
+
};
|
|
999
|
+
this.emit('change', changeEvent);
|
|
948
1000
|
} else {
|
|
949
1001
|
throw new Error(
|
|
950
1002
|
`metro-file-map: Unrecognized event type from watcher: ${change.event}`,
|
|
@@ -1055,11 +1107,9 @@ export default class FileMap extends EventEmitter {
|
|
|
1055
1107
|
}
|
|
1056
1108
|
|
|
1057
1109
|
// TODO: Replace with it.map() from Node 22+
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
}
|
|
1065
|
-
})();
|
|
1110
|
+
const mapIterable: <T, S>(Iterable<T>, (T) => S) => Iterator<S> = (it, fn) =>
|
|
1111
|
+
(function* mapped() {
|
|
1112
|
+
for (const item of it) {
|
|
1113
|
+
yield fn(item);
|
|
1114
|
+
}
|
|
1115
|
+
})();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*
|
|
7
|
+
* @noformat
|
|
8
|
+
* @oncall react_native
|
|
9
|
+
* @generated SignedSource<<5feda1b197530a9a5fdbc57200633ac5>>
|
|
10
|
+
*
|
|
11
|
+
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
|
|
12
|
+
* Original file: packages/metro-file-map/src/lib/FileSystemChangeAggregator.js
|
|
13
|
+
* To regenerate, run:
|
|
14
|
+
* js1 build metro-ts-defs (internal) OR
|
|
15
|
+
* yarn run build-ts-defs (OSS)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type {
|
|
19
|
+
CanonicalPath,
|
|
20
|
+
FileMetadata,
|
|
21
|
+
FileSystemListener,
|
|
22
|
+
ReadonlyFileSystemChanges,
|
|
23
|
+
} from '../flow-types';
|
|
24
|
+
|
|
25
|
+
export declare class FileSystemChangeAggregator implements FileSystemListener {
|
|
26
|
+
directoryAdded(canonicalPath: CanonicalPath): void;
|
|
27
|
+
directoryRemoved(canonicalPath: CanonicalPath): void;
|
|
28
|
+
fileAdded(canonicalPath: CanonicalPath, data: FileMetadata): void;
|
|
29
|
+
fileModified(
|
|
30
|
+
canonicalPath: CanonicalPath,
|
|
31
|
+
oldData: FileMetadata,
|
|
32
|
+
newData: FileMetadata,
|
|
33
|
+
): void;
|
|
34
|
+
fileRemoved(canonicalPath: CanonicalPath, data: FileMetadata): void;
|
|
35
|
+
getSize(): number;
|
|
36
|
+
getView(): ReadonlyFileSystemChanges<FileMetadata>;
|
|
37
|
+
getMappedView<T>(
|
|
38
|
+
metadataMapFn: (metadata: FileMetadata) => T,
|
|
39
|
+
): ReadonlyFileSystemChanges<T>;
|
|
40
|
+
}
|