source: trip-planner-front/node_modules/webpack/lib/FileSystemInfo.js@ e29cc2e

Last change on this file since e29cc2e was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 85.9 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const { create: createResolver } = require("enhanced-resolve");
9const asyncLib = require("neo-async");
10const AsyncQueue = require("./util/AsyncQueue");
11const StackedCacheMap = require("./util/StackedCacheMap");
12const createHash = require("./util/createHash");
13const { join, dirname, relative } = require("./util/fs");
14const makeSerializable = require("./util/makeSerializable");
15const processAsyncTree = require("./util/processAsyncTree");
16
17/** @typedef {import("./WebpackError")} WebpackError */
18/** @typedef {import("./logging/Logger").Logger} Logger */
19/** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
20
21const supportsEsm = +process.versions.modules >= 83;
22
23let FS_ACCURACY = 2000;
24
25const EMPTY_SET = new Set();
26
27const RBDT_RESOLVE_CJS = 0;
28const RBDT_RESOLVE_ESM = 1;
29const RBDT_RESOLVE_DIRECTORY = 2;
30const RBDT_RESOLVE_CJS_FILE = 3;
31const RBDT_RESOLVE_CJS_FILE_AS_CHILD = 4;
32const RBDT_RESOLVE_ESM_FILE = 5;
33const RBDT_DIRECTORY = 6;
34const RBDT_FILE = 7;
35const RBDT_DIRECTORY_DEPENDENCIES = 8;
36const RBDT_FILE_DEPENDENCIES = 9;
37
38const INVALID = Symbol("invalid");
39
40/**
41 * @typedef {Object} FileSystemInfoEntry
42 * @property {number} safeTime
43 * @property {number=} timestamp
44 * @property {string=} timestampHash
45 */
46
47/**
48 * @typedef {Object} TimestampAndHash
49 * @property {number} safeTime
50 * @property {number=} timestamp
51 * @property {string=} timestampHash
52 * @property {string} hash
53 */
54
55/**
56 * @typedef {Object} SnapshotOptimizationEntry
57 * @property {Snapshot} snapshot
58 * @property {number} shared
59 * @property {Set<string>} snapshotContent
60 * @property {Set<SnapshotOptimizationEntry>} children
61 */
62
63/**
64 * @typedef {Object} ResolveBuildDependenciesResult
65 * @property {Set<string>} files list of files
66 * @property {Set<string>} directories list of directories
67 * @property {Set<string>} missing list of missing entries
68 * @property {Map<string, string | false>} resolveResults stored resolve results
69 * @property {Object} resolveDependencies dependencies of the resolving
70 * @property {Set<string>} resolveDependencies.files list of files
71 * @property {Set<string>} resolveDependencies.directories list of directories
72 * @property {Set<string>} resolveDependencies.missing list of missing entries
73 */
74
75const DONE_ITERATOR_RESULT = new Set().keys().next();
76
77// cspell:word tshs
78// Tsh = Timestamp + Hash
79// Tshs = Timestamp + Hash combinations
80
81class SnapshotIterator {
82 constructor(next) {
83 this.next = next;
84 }
85}
86
87class SnapshotIterable {
88 constructor(snapshot, getMaps) {
89 this.snapshot = snapshot;
90 this.getMaps = getMaps;
91 }
92
93 [Symbol.iterator]() {
94 let state = 0;
95 /** @type {IterableIterator<string>} */
96 let it;
97 /** @type {(Snapshot) => (Map<string, any> | Set<string>)[]} */
98 let getMaps;
99 /** @type {(Map<string, any> | Set<string>)[]} */
100 let maps;
101 /** @type {Snapshot} */
102 let snapshot;
103 let queue;
104 return new SnapshotIterator(() => {
105 for (;;) {
106 switch (state) {
107 case 0:
108 snapshot = this.snapshot;
109 getMaps = this.getMaps;
110 maps = getMaps(snapshot);
111 state = 1;
112 /* falls through */
113 case 1:
114 if (maps.length > 0) {
115 const map = maps.pop();
116 if (map !== undefined) {
117 it = map.keys();
118 state = 2;
119 } else {
120 break;
121 }
122 } else {
123 state = 3;
124 break;
125 }
126 /* falls through */
127 case 2: {
128 const result = it.next();
129 if (!result.done) return result;
130 state = 1;
131 break;
132 }
133 case 3: {
134 const children = snapshot.children;
135 if (children !== undefined) {
136 if (children.size === 1) {
137 // shortcut for a single child
138 // avoids allocation of queue
139 for (const child of children) snapshot = child;
140 maps = getMaps(snapshot);
141 state = 1;
142 break;
143 }
144 if (queue === undefined) queue = [];
145 for (const child of children) {
146 queue.push(child);
147 }
148 }
149 if (queue !== undefined && queue.length > 0) {
150 snapshot = queue.pop();
151 maps = getMaps(snapshot);
152 state = 1;
153 break;
154 } else {
155 state = 4;
156 }
157 }
158 /* falls through */
159 case 4:
160 return DONE_ITERATOR_RESULT;
161 }
162 }
163 });
164 }
165}
166
167class Snapshot {
168 constructor() {
169 this._flags = 0;
170 /** @type {number | undefined} */
171 this.startTime = undefined;
172 /** @type {Map<string, FileSystemInfoEntry> | undefined} */
173 this.fileTimestamps = undefined;
174 /** @type {Map<string, string> | undefined} */
175 this.fileHashes = undefined;
176 /** @type {Map<string, TimestampAndHash | string> | undefined} */
177 this.fileTshs = undefined;
178 /** @type {Map<string, FileSystemInfoEntry> | undefined} */
179 this.contextTimestamps = undefined;
180 /** @type {Map<string, string> | undefined} */
181 this.contextHashes = undefined;
182 /** @type {Map<string, TimestampAndHash | string> | undefined} */
183 this.contextTshs = undefined;
184 /** @type {Map<string, boolean> | undefined} */
185 this.missingExistence = undefined;
186 /** @type {Map<string, string> | undefined} */
187 this.managedItemInfo = undefined;
188 /** @type {Set<string> | undefined} */
189 this.managedFiles = undefined;
190 /** @type {Set<string> | undefined} */
191 this.managedContexts = undefined;
192 /** @type {Set<string> | undefined} */
193 this.managedMissing = undefined;
194 /** @type {Set<Snapshot> | undefined} */
195 this.children = undefined;
196 }
197
198 hasStartTime() {
199 return (this._flags & 1) !== 0;
200 }
201
202 setStartTime(value) {
203 this._flags = this._flags | 1;
204 this.startTime = value;
205 }
206
207 setMergedStartTime(value, snapshot) {
208 if (value) {
209 if (snapshot.hasStartTime()) {
210 this.setStartTime(Math.min(value, snapshot.startTime));
211 } else {
212 this.setStartTime(value);
213 }
214 } else {
215 if (snapshot.hasStartTime()) this.setStartTime(snapshot.startTime);
216 }
217 }
218
219 hasFileTimestamps() {
220 return (this._flags & 2) !== 0;
221 }
222
223 setFileTimestamps(value) {
224 this._flags = this._flags | 2;
225 this.fileTimestamps = value;
226 }
227
228 hasFileHashes() {
229 return (this._flags & 4) !== 0;
230 }
231
232 setFileHashes(value) {
233 this._flags = this._flags | 4;
234 this.fileHashes = value;
235 }
236
237 hasFileTshs() {
238 return (this._flags & 8) !== 0;
239 }
240
241 setFileTshs(value) {
242 this._flags = this._flags | 8;
243 this.fileTshs = value;
244 }
245
246 hasContextTimestamps() {
247 return (this._flags & 0x10) !== 0;
248 }
249
250 setContextTimestamps(value) {
251 this._flags = this._flags | 0x10;
252 this.contextTimestamps = value;
253 }
254
255 hasContextHashes() {
256 return (this._flags & 0x20) !== 0;
257 }
258
259 setContextHashes(value) {
260 this._flags = this._flags | 0x20;
261 this.contextHashes = value;
262 }
263
264 hasContextTshs() {
265 return (this._flags & 0x40) !== 0;
266 }
267
268 setContextTshs(value) {
269 this._flags = this._flags | 0x40;
270 this.contextTshs = value;
271 }
272
273 hasMissingExistence() {
274 return (this._flags & 0x80) !== 0;
275 }
276
277 setMissingExistence(value) {
278 this._flags = this._flags | 0x80;
279 this.missingExistence = value;
280 }
281
282 hasManagedItemInfo() {
283 return (this._flags & 0x100) !== 0;
284 }
285
286 setManagedItemInfo(value) {
287 this._flags = this._flags | 0x100;
288 this.managedItemInfo = value;
289 }
290
291 hasManagedFiles() {
292 return (this._flags & 0x200) !== 0;
293 }
294
295 setManagedFiles(value) {
296 this._flags = this._flags | 0x200;
297 this.managedFiles = value;
298 }
299
300 hasManagedContexts() {
301 return (this._flags & 0x400) !== 0;
302 }
303
304 setManagedContexts(value) {
305 this._flags = this._flags | 0x400;
306 this.managedContexts = value;
307 }
308
309 hasManagedMissing() {
310 return (this._flags & 0x800) !== 0;
311 }
312
313 setManagedMissing(value) {
314 this._flags = this._flags | 0x800;
315 this.managedMissing = value;
316 }
317
318 hasChildren() {
319 return (this._flags & 0x1000) !== 0;
320 }
321
322 setChildren(value) {
323 this._flags = this._flags | 0x1000;
324 this.children = value;
325 }
326
327 addChild(child) {
328 if (!this.hasChildren()) {
329 this.setChildren(new Set());
330 }
331 this.children.add(child);
332 }
333
334 serialize({ write }) {
335 write(this._flags);
336 if (this.hasStartTime()) write(this.startTime);
337 if (this.hasFileTimestamps()) write(this.fileTimestamps);
338 if (this.hasFileHashes()) write(this.fileHashes);
339 if (this.hasFileTshs()) write(this.fileTshs);
340 if (this.hasContextTimestamps()) write(this.contextTimestamps);
341 if (this.hasContextHashes()) write(this.contextHashes);
342 if (this.hasContextTshs()) write(this.contextTshs);
343 if (this.hasMissingExistence()) write(this.missingExistence);
344 if (this.hasManagedItemInfo()) write(this.managedItemInfo);
345 if (this.hasManagedFiles()) write(this.managedFiles);
346 if (this.hasManagedContexts()) write(this.managedContexts);
347 if (this.hasManagedMissing()) write(this.managedMissing);
348 if (this.hasChildren()) write(this.children);
349 }
350
351 deserialize({ read }) {
352 this._flags = read();
353 if (this.hasStartTime()) this.startTime = read();
354 if (this.hasFileTimestamps()) this.fileTimestamps = read();
355 if (this.hasFileHashes()) this.fileHashes = read();
356 if (this.hasFileTshs()) this.fileTshs = read();
357 if (this.hasContextTimestamps()) this.contextTimestamps = read();
358 if (this.hasContextHashes()) this.contextHashes = read();
359 if (this.hasContextTshs()) this.contextTshs = read();
360 if (this.hasMissingExistence()) this.missingExistence = read();
361 if (this.hasManagedItemInfo()) this.managedItemInfo = read();
362 if (this.hasManagedFiles()) this.managedFiles = read();
363 if (this.hasManagedContexts()) this.managedContexts = read();
364 if (this.hasManagedMissing()) this.managedMissing = read();
365 if (this.hasChildren()) this.children = read();
366 }
367
368 /**
369 * @param {function(Snapshot): (ReadonlyMap<string, any> | ReadonlySet<string>)[]} getMaps first
370 * @returns {Iterable<string>} iterable
371 */
372 _createIterable(getMaps) {
373 return new SnapshotIterable(this, getMaps);
374 }
375
376 /**
377 * @returns {Iterable<string>} iterable
378 */
379 getFileIterable() {
380 return this._createIterable(s => [
381 s.fileTimestamps,
382 s.fileHashes,
383 s.fileTshs,
384 s.managedFiles
385 ]);
386 }
387
388 /**
389 * @returns {Iterable<string>} iterable
390 */
391 getContextIterable() {
392 return this._createIterable(s => [
393 s.contextTimestamps,
394 s.contextHashes,
395 s.contextTshs,
396 s.managedContexts
397 ]);
398 }
399
400 /**
401 * @returns {Iterable<string>} iterable
402 */
403 getMissingIterable() {
404 return this._createIterable(s => [s.missingExistence, s.managedMissing]);
405 }
406}
407
408makeSerializable(Snapshot, "webpack/lib/FileSystemInfo", "Snapshot");
409
410const MIN_COMMON_SNAPSHOT_SIZE = 3;
411
412/**
413 * @template T
414 */
415class SnapshotOptimization {
416 /**
417 * @param {function(Snapshot): boolean} has has value
418 * @param {function(Snapshot): Map<string, T> | Set<string>} get get value
419 * @param {function(Snapshot, Map<string, T> | Set<string>): void} set set value
420 * @param {boolean=} isSet value is an Set instead of a Map
421 */
422 constructor(has, get, set, isSet = false) {
423 this._has = has;
424 this._get = get;
425 this._set = set;
426 this._isSet = isSet;
427 /** @type {Map<string, SnapshotOptimizationEntry>} */
428 this._map = new Map();
429 this._statItemsShared = 0;
430 this._statItemsUnshared = 0;
431 this._statSharedSnapshots = 0;
432 this._statReusedSharedSnapshots = 0;
433 }
434
435 getStatisticMessage() {
436 const total = this._statItemsShared + this._statItemsUnshared;
437 if (total === 0) return undefined;
438 return `${
439 this._statItemsShared && Math.round((this._statItemsShared * 100) / total)
440 }% (${this._statItemsShared}/${total}) entries shared via ${
441 this._statSharedSnapshots
442 } shared snapshots (${
443 this._statReusedSharedSnapshots + this._statSharedSnapshots
444 } times referenced)`;
445 }
446
447 clear() {
448 this._map.clear();
449 this._statItemsShared = 0;
450 this._statItemsUnshared = 0;
451 this._statSharedSnapshots = 0;
452 this._statReusedSharedSnapshots = 0;
453 }
454
455 storeUnsharedSnapshot(snapshot, locations) {
456 if (locations === undefined) return;
457 const optimizationEntry = {
458 snapshot,
459 shared: 0,
460 snapshotContent: undefined,
461 children: undefined
462 };
463 for (const path of locations) {
464 this._map.set(path, optimizationEntry);
465 }
466 }
467
468 optimize(capturedFiles, startTime, children) {
469 /** @type {Set<string>} */
470 const unsetOptimizationEntries = new Set();
471 /** @type {Set<SnapshotOptimizationEntry>} */
472 const checkedOptimizationEntries = new Set();
473 /**
474 * @param {SnapshotOptimizationEntry} entry optimization entry
475 * @returns {void}
476 */
477 const increaseSharedAndStoreOptimizationEntry = entry => {
478 if (entry.children !== undefined) {
479 entry.children.forEach(increaseSharedAndStoreOptimizationEntry);
480 }
481 entry.shared++;
482 storeOptimizationEntry(entry);
483 };
484 /**
485 * @param {SnapshotOptimizationEntry} entry optimization entry
486 * @returns {void}
487 */
488 const storeOptimizationEntry = entry => {
489 for (const path of entry.snapshotContent) {
490 const old = this._map.get(path);
491 if (old.shared < entry.shared) {
492 this._map.set(path, entry);
493 }
494 capturedFiles.delete(path);
495 }
496 };
497 const capturedFilesSize = capturedFiles.size;
498 capturedFiles: for (const path of capturedFiles) {
499 const optimizationEntry = this._map.get(path);
500 if (optimizationEntry === undefined) {
501 unsetOptimizationEntries.add(path);
502 continue;
503 }
504 if (checkedOptimizationEntries.has(optimizationEntry)) continue;
505 const snapshot = optimizationEntry.snapshot;
506 if (optimizationEntry.shared > 0) {
507 // It's a shared snapshot
508 // We can't change it, so we can only use it when all files match
509 // and startTime is compatible
510 if (
511 startTime &&
512 (!snapshot.startTime || snapshot.startTime > startTime)
513 ) {
514 continue;
515 }
516 const nonSharedFiles = new Set();
517 const snapshotContent = optimizationEntry.snapshotContent;
518 const snapshotEntries = this._get(snapshot);
519 for (const path of snapshotContent) {
520 if (!capturedFiles.has(path)) {
521 if (!snapshotEntries.has(path)) {
522 // File is not shared and can't be removed from the snapshot
523 // because it's in a child of the snapshot
524 checkedOptimizationEntries.add(optimizationEntry);
525 continue capturedFiles;
526 }
527 nonSharedFiles.add(path);
528 continue;
529 }
530 }
531 if (nonSharedFiles.size === 0) {
532 // The complete snapshot is shared
533 // add it as child
534 children.add(snapshot);
535 increaseSharedAndStoreOptimizationEntry(optimizationEntry);
536 this._statReusedSharedSnapshots++;
537 } else {
538 // Only a part of the snapshot is shared
539 const sharedCount = snapshotContent.size - nonSharedFiles.size;
540 if (sharedCount < MIN_COMMON_SNAPSHOT_SIZE) {
541 // Common part it too small
542 checkedOptimizationEntries.add(optimizationEntry);
543 continue capturedFiles;
544 }
545 // Extract common timestamps from both snapshots
546 let commonMap;
547 if (this._isSet) {
548 commonMap = new Set();
549 for (const path of /** @type {Set<string>} */ (snapshotEntries)) {
550 if (nonSharedFiles.has(path)) continue;
551 commonMap.add(path);
552 snapshotEntries.delete(path);
553 }
554 } else {
555 commonMap = new Map();
556 const map = /** @type {Map<string, T>} */ (snapshotEntries);
557 for (const [path, value] of map) {
558 if (nonSharedFiles.has(path)) continue;
559 commonMap.set(path, value);
560 snapshotEntries.delete(path);
561 }
562 }
563 // Create and attach snapshot
564 const commonSnapshot = new Snapshot();
565 commonSnapshot.setMergedStartTime(startTime, snapshot);
566 this._set(commonSnapshot, commonMap);
567 children.add(commonSnapshot);
568 snapshot.addChild(commonSnapshot);
569 // Create optimization entry
570 const newEntry = {
571 snapshot: commonSnapshot,
572 shared: optimizationEntry.shared + 1,
573 snapshotContent: new Set(commonMap.keys()),
574 children: undefined
575 };
576 if (optimizationEntry.children === undefined)
577 optimizationEntry.children = new Set();
578 optimizationEntry.children.add(newEntry);
579 storeOptimizationEntry(newEntry);
580 this._statSharedSnapshots++;
581 }
582 } else {
583 // It's a unshared snapshot
584 // We can extract a common shared snapshot
585 // with all common files
586 const snapshotEntries = this._get(snapshot);
587 let commonMap;
588 if (this._isSet) {
589 commonMap = new Set();
590 const set = /** @type {Set<string>} */ (snapshotEntries);
591 if (capturedFiles.size < set.size) {
592 for (const path of capturedFiles) {
593 if (set.has(path)) commonMap.add(path);
594 }
595 } else {
596 for (const path of set) {
597 if (capturedFiles.has(path)) commonMap.add(path);
598 }
599 }
600 } else {
601 commonMap = new Map();
602 const map = /** @type {Map<string, T>} */ (snapshotEntries);
603 for (const path of capturedFiles) {
604 const ts = map.get(path);
605 if (ts === undefined) continue;
606 commonMap.set(path, ts);
607 }
608 }
609
610 if (commonMap.size < MIN_COMMON_SNAPSHOT_SIZE) {
611 // Common part it too small
612 checkedOptimizationEntries.add(optimizationEntry);
613 continue capturedFiles;
614 }
615 // Create and attach snapshot
616 const commonSnapshot = new Snapshot();
617 commonSnapshot.setMergedStartTime(startTime, snapshot);
618 this._set(commonSnapshot, commonMap);
619 children.add(commonSnapshot);
620 snapshot.addChild(commonSnapshot);
621 // Remove files from snapshot
622 for (const path of commonMap.keys()) snapshotEntries.delete(path);
623 const sharedCount = commonMap.size;
624 this._statItemsUnshared -= sharedCount;
625 this._statItemsShared += sharedCount;
626 // Create optimization entry
627 storeOptimizationEntry({
628 snapshot: commonSnapshot,
629 shared: 2,
630 snapshotContent: new Set(commonMap.keys()),
631 children: undefined
632 });
633 this._statSharedSnapshots++;
634 }
635 checkedOptimizationEntries.add(optimizationEntry);
636 }
637 const unshared = capturedFiles.size;
638 this._statItemsUnshared += unshared;
639 this._statItemsShared += capturedFilesSize - unshared;
640 return unsetOptimizationEntries;
641 }
642}
643
644/* istanbul ignore next */
645/**
646 * @param {number} mtime mtime
647 */
648const applyMtime = mtime => {
649 if (FS_ACCURACY > 1 && mtime % 2 !== 0) FS_ACCURACY = 1;
650 else if (FS_ACCURACY > 10 && mtime % 20 !== 0) FS_ACCURACY = 10;
651 else if (FS_ACCURACY > 100 && mtime % 200 !== 0) FS_ACCURACY = 100;
652 else if (FS_ACCURACY > 1000 && mtime % 2000 !== 0) FS_ACCURACY = 1000;
653};
654
655/**
656 * @template T
657 * @template K
658 * @param {Map<T, K>} a source map
659 * @param {Map<T, K>} b joining map
660 * @returns {Map<T, K>} joined map
661 */
662const mergeMaps = (a, b) => {
663 if (!b || b.size === 0) return a;
664 if (!a || a.size === 0) return b;
665 const map = new Map(a);
666 for (const [key, value] of b) {
667 map.set(key, value);
668 }
669 return map;
670};
671
672/**
673 * @template T
674 * @template K
675 * @param {Set<T, K>} a source map
676 * @param {Set<T, K>} b joining map
677 * @returns {Set<T, K>} joined map
678 */
679const mergeSets = (a, b) => {
680 if (!b || b.size === 0) return a;
681 if (!a || a.size === 0) return b;
682 const map = new Set(a);
683 for (const item of b) {
684 map.add(item);
685 }
686 return map;
687};
688
689/**
690 * Finding file or directory to manage
691 * @param {string} managedPath path that is managing by {@link FileSystemInfo}
692 * @param {string} path path to file or directory
693 * @returns {string|null} managed item
694 * @example
695 * getManagedItem(
696 * '/Users/user/my-project/node_modules/',
697 * '/Users/user/my-project/node_modules/package/index.js'
698 * ) === '/Users/user/my-project/node_modules/package'
699 * getManagedItem(
700 * '/Users/user/my-project/node_modules/',
701 * '/Users/user/my-project/node_modules/package1/node_modules/package2'
702 * ) === '/Users/user/my-project/node_modules/package1/node_modules/package2'
703 * getManagedItem(
704 * '/Users/user/my-project/node_modules/',
705 * '/Users/user/my-project/node_modules/.bin/script.js'
706 * ) === null // hidden files are disallowed as managed items
707 * getManagedItem(
708 * '/Users/user/my-project/node_modules/',
709 * '/Users/user/my-project/node_modules/package'
710 * ) === '/Users/user/my-project/node_modules/package'
711 */
712const getManagedItem = (managedPath, path) => {
713 let i = managedPath.length;
714 let slashes = 1;
715 let startingPosition = true;
716 loop: while (i < path.length) {
717 switch (path.charCodeAt(i)) {
718 case 47: // slash
719 case 92: // backslash
720 if (--slashes === 0) break loop;
721 startingPosition = true;
722 break;
723 case 46: // .
724 // hidden files are disallowed as managed items
725 // it's probably .yarn-integrity or .cache
726 if (startingPosition) return null;
727 break;
728 case 64: // @
729 if (!startingPosition) return null;
730 slashes++;
731 break;
732 default:
733 startingPosition = false;
734 break;
735 }
736 i++;
737 }
738 if (i === path.length) slashes--;
739 // return null when path is incomplete
740 if (slashes !== 0) return null;
741 // if (path.slice(i + 1, i + 13) === "node_modules")
742 if (
743 path.length >= i + 13 &&
744 path.charCodeAt(i + 1) === 110 &&
745 path.charCodeAt(i + 2) === 111 &&
746 path.charCodeAt(i + 3) === 100 &&
747 path.charCodeAt(i + 4) === 101 &&
748 path.charCodeAt(i + 5) === 95 &&
749 path.charCodeAt(i + 6) === 109 &&
750 path.charCodeAt(i + 7) === 111 &&
751 path.charCodeAt(i + 8) === 100 &&
752 path.charCodeAt(i + 9) === 117 &&
753 path.charCodeAt(i + 10) === 108 &&
754 path.charCodeAt(i + 11) === 101 &&
755 path.charCodeAt(i + 12) === 115
756 ) {
757 // if this is the end of the path
758 if (path.length === i + 13) {
759 // return the node_modules directory
760 // it's special
761 return path;
762 }
763 const c = path.charCodeAt(i + 13);
764 // if next symbol is slash or backslash
765 if (c === 47 || c === 92) {
766 // Managed subpath
767 return getManagedItem(path.slice(0, i + 14), path);
768 }
769 }
770 return path.slice(0, i);
771};
772
773/**
774 * @param {FileSystemInfoEntry} entry file system info entry
775 * @returns {boolean} existence flag
776 */
777const toExistence = entry => {
778 return Boolean(entry);
779};
780
781/**
782 * Used to access information about the filesystem in a cached way
783 */
784class FileSystemInfo {
785 /**
786 * @param {InputFileSystem} fs file system
787 * @param {Object} options options
788 * @param {Iterable<string>=} options.managedPaths paths that are only managed by a package manager
789 * @param {Iterable<string>=} options.immutablePaths paths that are immutable
790 * @param {Logger=} options.logger logger used to log invalid snapshots
791 */
792 constructor(fs, { managedPaths = [], immutablePaths = [], logger } = {}) {
793 this.fs = fs;
794 this.logger = logger;
795 this._remainingLogs = logger ? 40 : 0;
796 this._loggedPaths = logger ? new Set() : undefined;
797 /** @type {WeakMap<Snapshot, boolean | (function(WebpackError=, boolean=): void)[]>} */
798 this._snapshotCache = new WeakMap();
799 this._fileTimestampsOptimization = new SnapshotOptimization(
800 s => s.hasFileTimestamps(),
801 s => s.fileTimestamps,
802 (s, v) => s.setFileTimestamps(v)
803 );
804 this._fileHashesOptimization = new SnapshotOptimization(
805 s => s.hasFileHashes(),
806 s => s.fileHashes,
807 (s, v) => s.setFileHashes(v)
808 );
809 this._fileTshsOptimization = new SnapshotOptimization(
810 s => s.hasFileTshs(),
811 s => s.fileTshs,
812 (s, v) => s.setFileTshs(v)
813 );
814 this._contextTimestampsOptimization = new SnapshotOptimization(
815 s => s.hasContextTimestamps(),
816 s => s.contextTimestamps,
817 (s, v) => s.setContextTimestamps(v)
818 );
819 this._contextHashesOptimization = new SnapshotOptimization(
820 s => s.hasContextHashes(),
821 s => s.contextHashes,
822 (s, v) => s.setContextHashes(v)
823 );
824 this._contextTshsOptimization = new SnapshotOptimization(
825 s => s.hasContextTshs(),
826 s => s.contextTshs,
827 (s, v) => s.setContextTshs(v)
828 );
829 this._missingExistenceOptimization = new SnapshotOptimization(
830 s => s.hasMissingExistence(),
831 s => s.missingExistence,
832 (s, v) => s.setMissingExistence(v)
833 );
834 this._managedItemInfoOptimization = new SnapshotOptimization(
835 s => s.hasManagedItemInfo(),
836 s => s.managedItemInfo,
837 (s, v) => s.setManagedItemInfo(v)
838 );
839 this._managedFilesOptimization = new SnapshotOptimization(
840 s => s.hasManagedFiles(),
841 s => s.managedFiles,
842 (s, v) => s.setManagedFiles(v),
843 true
844 );
845 this._managedContextsOptimization = new SnapshotOptimization(
846 s => s.hasManagedContexts(),
847 s => s.managedContexts,
848 (s, v) => s.setManagedContexts(v),
849 true
850 );
851 this._managedMissingOptimization = new SnapshotOptimization(
852 s => s.hasManagedMissing(),
853 s => s.managedMissing,
854 (s, v) => s.setManagedMissing(v),
855 true
856 );
857 /** @type {StackedCacheMap<string, FileSystemInfoEntry | "ignore" | null>} */
858 this._fileTimestamps = new StackedCacheMap();
859 /** @type {Map<string, string>} */
860 this._fileHashes = new Map();
861 /** @type {Map<string, TimestampAndHash | string>} */
862 this._fileTshs = new Map();
863 /** @type {StackedCacheMap<string, FileSystemInfoEntry | "ignore" | null>} */
864 this._contextTimestamps = new StackedCacheMap();
865 /** @type {Map<string, string>} */
866 this._contextHashes = new Map();
867 /** @type {Map<string, TimestampAndHash | string>} */
868 this._contextTshs = new Map();
869 /** @type {Map<string, string>} */
870 this._managedItems = new Map();
871 /** @type {AsyncQueue<string, string, FileSystemInfoEntry | null>} */
872 this.fileTimestampQueue = new AsyncQueue({
873 name: "file timestamp",
874 parallelism: 30,
875 processor: this._readFileTimestamp.bind(this)
876 });
877 /** @type {AsyncQueue<string, string, string | null>} */
878 this.fileHashQueue = new AsyncQueue({
879 name: "file hash",
880 parallelism: 10,
881 processor: this._readFileHash.bind(this)
882 });
883 /** @type {AsyncQueue<string, string, FileSystemInfoEntry | null>} */
884 this.contextTimestampQueue = new AsyncQueue({
885 name: "context timestamp",
886 parallelism: 2,
887 processor: this._readContextTimestamp.bind(this)
888 });
889 /** @type {AsyncQueue<string, string, string | null>} */
890 this.contextHashQueue = new AsyncQueue({
891 name: "context hash",
892 parallelism: 2,
893 processor: this._readContextHash.bind(this)
894 });
895 /** @type {AsyncQueue<string, string, string | null>} */
896 this.managedItemQueue = new AsyncQueue({
897 name: "managed item info",
898 parallelism: 10,
899 processor: this._getManagedItemInfo.bind(this)
900 });
901 /** @type {AsyncQueue<string, string, Set<string>>} */
902 this.managedItemDirectoryQueue = new AsyncQueue({
903 name: "managed item directory info",
904 parallelism: 10,
905 processor: this._getManagedItemDirectoryInfo.bind(this)
906 });
907 this.managedPaths = Array.from(managedPaths);
908 this.managedPathsWithSlash = this.managedPaths.map(p =>
909 join(fs, p, "_").slice(0, -1)
910 );
911 this.immutablePaths = Array.from(immutablePaths);
912 this.immutablePathsWithSlash = this.immutablePaths.map(p =>
913 join(fs, p, "_").slice(0, -1)
914 );
915
916 this._cachedDeprecatedFileTimestamps = undefined;
917 this._cachedDeprecatedContextTimestamps = undefined;
918
919 this._warnAboutExperimentalEsmTracking = false;
920
921 this._statCreatedSnapshots = 0;
922 this._statTestedSnapshotsCached = 0;
923 this._statTestedSnapshotsNotCached = 0;
924 this._statTestedChildrenCached = 0;
925 this._statTestedChildrenNotCached = 0;
926 this._statTestedEntries = 0;
927 }
928
929 logStatistics() {
930 const logWhenMessage = (header, message) => {
931 if (message) {
932 this.logger.log(`${header}: ${message}`);
933 }
934 };
935 this.logger.log(`${this._statCreatedSnapshots} new snapshots created`);
936 this.logger.log(
937 `${
938 this._statTestedSnapshotsNotCached &&
939 Math.round(
940 (this._statTestedSnapshotsNotCached * 100) /
941 (this._statTestedSnapshotsCached +
942 this._statTestedSnapshotsNotCached)
943 )
944 }% root snapshot uncached (${this._statTestedSnapshotsNotCached} / ${
945 this._statTestedSnapshotsCached + this._statTestedSnapshotsNotCached
946 })`
947 );
948 this.logger.log(
949 `${
950 this._statTestedChildrenNotCached &&
951 Math.round(
952 (this._statTestedChildrenNotCached * 100) /
953 (this._statTestedChildrenCached + this._statTestedChildrenNotCached)
954 )
955 }% children snapshot uncached (${this._statTestedChildrenNotCached} / ${
956 this._statTestedChildrenCached + this._statTestedChildrenNotCached
957 })`
958 );
959 this.logger.log(`${this._statTestedEntries} entries tested`);
960 this.logger.log(
961 `File info in cache: ${this._fileTimestamps.size} timestamps ${this._fileHashes.size} hashes ${this._fileTshs.size} timestamp hash combinations`
962 );
963 logWhenMessage(
964 `File timestamp snapshot optimization`,
965 this._fileTimestampsOptimization.getStatisticMessage()
966 );
967 logWhenMessage(
968 `File hash snapshot optimization`,
969 this._fileHashesOptimization.getStatisticMessage()
970 );
971 logWhenMessage(
972 `File timestamp hash combination snapshot optimization`,
973 this._fileTshsOptimization.getStatisticMessage()
974 );
975 this.logger.log(
976 `Directory info in cache: ${this._contextTimestamps.size} timestamps ${this._contextHashes.size} hashes ${this._contextTshs.size} timestamp hash combinations`
977 );
978 logWhenMessage(
979 `Directory timestamp snapshot optimization`,
980 this._contextTimestampsOptimization.getStatisticMessage()
981 );
982 logWhenMessage(
983 `Directory hash snapshot optimization`,
984 this._contextHashesOptimization.getStatisticMessage()
985 );
986 logWhenMessage(
987 `Directory timestamp hash combination snapshot optimization`,
988 this._contextTshsOptimization.getStatisticMessage()
989 );
990 logWhenMessage(
991 `Missing items snapshot optimization`,
992 this._missingExistenceOptimization.getStatisticMessage()
993 );
994 this.logger.log(
995 `Managed items info in cache: ${this._managedItems.size} items`
996 );
997 logWhenMessage(
998 `Managed items snapshot optimization`,
999 this._managedItemInfoOptimization.getStatisticMessage()
1000 );
1001 logWhenMessage(
1002 `Managed files snapshot optimization`,
1003 this._managedFilesOptimization.getStatisticMessage()
1004 );
1005 logWhenMessage(
1006 `Managed contexts snapshot optimization`,
1007 this._managedContextsOptimization.getStatisticMessage()
1008 );
1009 logWhenMessage(
1010 `Managed missing snapshot optimization`,
1011 this._managedMissingOptimization.getStatisticMessage()
1012 );
1013 }
1014
1015 _log(path, reason, ...args) {
1016 const key = path + reason;
1017 if (this._loggedPaths.has(key)) return;
1018 this._loggedPaths.add(key);
1019 this.logger.debug(`${path} invalidated because ${reason}`, ...args);
1020 if (--this._remainingLogs === 0) {
1021 this.logger.debug(
1022 "Logging limit has been reached and no further logging will be emitted by FileSystemInfo"
1023 );
1024 }
1025 }
1026
1027 clear() {
1028 this._remainingLogs = this.logger ? 40 : 0;
1029 if (this._loggedPaths !== undefined) this._loggedPaths.clear();
1030
1031 this._snapshotCache = new WeakMap();
1032 this._fileTimestampsOptimization.clear();
1033 this._fileHashesOptimization.clear();
1034 this._fileTshsOptimization.clear();
1035 this._contextTimestampsOptimization.clear();
1036 this._contextHashesOptimization.clear();
1037 this._contextTshsOptimization.clear();
1038 this._missingExistenceOptimization.clear();
1039 this._managedItemInfoOptimization.clear();
1040 this._managedFilesOptimization.clear();
1041 this._managedContextsOptimization.clear();
1042 this._managedMissingOptimization.clear();
1043 this._fileTimestamps.clear();
1044 this._fileHashes.clear();
1045 this._fileTshs.clear();
1046 this._contextTimestamps.clear();
1047 this._contextHashes.clear();
1048 this._contextTshs.clear();
1049 this._managedItems.clear();
1050 this._managedItems.clear();
1051
1052 this._cachedDeprecatedFileTimestamps = undefined;
1053 this._cachedDeprecatedContextTimestamps = undefined;
1054
1055 this._statCreatedSnapshots = 0;
1056 this._statTestedSnapshotsCached = 0;
1057 this._statTestedSnapshotsNotCached = 0;
1058 this._statTestedChildrenCached = 0;
1059 this._statTestedChildrenNotCached = 0;
1060 this._statTestedEntries = 0;
1061 }
1062
1063 /**
1064 * @param {ReadonlyMap<string, FileSystemInfoEntry | "ignore" | null>} map timestamps
1065 * @param {boolean=} immutable if 'map' is immutable and FileSystemInfo can keep referencing it
1066 * @returns {void}
1067 */
1068 addFileTimestamps(map, immutable) {
1069 this._fileTimestamps.addAll(map, immutable);
1070 this._cachedDeprecatedFileTimestamps = undefined;
1071 }
1072
1073 /**
1074 * @param {ReadonlyMap<string, FileSystemInfoEntry | "ignore" | null>} map timestamps
1075 * @param {boolean=} immutable if 'map' is immutable and FileSystemInfo can keep referencing it
1076 * @returns {void}
1077 */
1078 addContextTimestamps(map, immutable) {
1079 this._contextTimestamps.addAll(map, immutable);
1080 this._cachedDeprecatedContextTimestamps = undefined;
1081 }
1082
1083 /**
1084 * @param {string} path file path
1085 * @param {function(WebpackError=, (FileSystemInfoEntry | "ignore" | null)=): void} callback callback function
1086 * @returns {void}
1087 */
1088 getFileTimestamp(path, callback) {
1089 const cache = this._fileTimestamps.get(path);
1090 if (cache !== undefined) return callback(null, cache);
1091 this.fileTimestampQueue.add(path, callback);
1092 }
1093
1094 /**
1095 * @param {string} path context path
1096 * @param {function(WebpackError=, (FileSystemInfoEntry | "ignore" | null)=): void} callback callback function
1097 * @returns {void}
1098 */
1099 getContextTimestamp(path, callback) {
1100 const cache = this._contextTimestamps.get(path);
1101 if (cache !== undefined) return callback(null, cache);
1102 this.contextTimestampQueue.add(path, callback);
1103 }
1104
1105 /**
1106 * @param {string} path file path
1107 * @param {function(WebpackError=, string=): void} callback callback function
1108 * @returns {void}
1109 */
1110 getFileHash(path, callback) {
1111 const cache = this._fileHashes.get(path);
1112 if (cache !== undefined) return callback(null, cache);
1113 this.fileHashQueue.add(path, callback);
1114 }
1115
1116 /**
1117 * @param {string} path context path
1118 * @param {function(WebpackError=, string=): void} callback callback function
1119 * @returns {void}
1120 */
1121 getContextHash(path, callback) {
1122 const cache = this._contextHashes.get(path);
1123 if (cache !== undefined) return callback(null, cache);
1124 this.contextHashQueue.add(path, callback);
1125 }
1126
1127 _createBuildDependenciesResolvers() {
1128 const resolveContext = createResolver({
1129 resolveToContext: true,
1130 exportsFields: [],
1131 fileSystem: this.fs
1132 });
1133 const resolveCjs = createResolver({
1134 extensions: [".js", ".json", ".node"],
1135 conditionNames: ["require", "node"],
1136 exportsFields: ["exports"],
1137 fileSystem: this.fs
1138 });
1139 const resolveCjsAsChild = createResolver({
1140 extensions: [".js", ".json", ".node"],
1141 conditionNames: ["require", "node"],
1142 exportsFields: [],
1143 fileSystem: this.fs
1144 });
1145 const resolveEsm = createResolver({
1146 extensions: [".js", ".json", ".node"],
1147 fullySpecified: true,
1148 conditionNames: ["import", "node"],
1149 exportsFields: ["exports"],
1150 fileSystem: this.fs
1151 });
1152 return { resolveContext, resolveEsm, resolveCjs, resolveCjsAsChild };
1153 }
1154
1155 /**
1156 * @param {string} context context directory
1157 * @param {Iterable<string>} deps dependencies
1158 * @param {function(Error=, ResolveBuildDependenciesResult=): void} callback callback function
1159 * @returns {void}
1160 */
1161 resolveBuildDependencies(context, deps, callback) {
1162 const { resolveContext, resolveEsm, resolveCjs, resolveCjsAsChild } =
1163 this._createBuildDependenciesResolvers();
1164
1165 /** @type {Set<string>} */
1166 const files = new Set();
1167 /** @type {Set<string>} */
1168 const fileSymlinks = new Set();
1169 /** @type {Set<string>} */
1170 const directories = new Set();
1171 /** @type {Set<string>} */
1172 const directorySymlinks = new Set();
1173 /** @type {Set<string>} */
1174 const missing = new Set();
1175 /** @type {Set<string>} */
1176 const resolveFiles = new Set();
1177 /** @type {Set<string>} */
1178 const resolveDirectories = new Set();
1179 /** @type {Set<string>} */
1180 const resolveMissing = new Set();
1181 /** @type {Map<string, string | false>} */
1182 const resolveResults = new Map();
1183 const invalidResolveResults = new Set();
1184 const resolverContext = {
1185 fileDependencies: resolveFiles,
1186 contextDependencies: resolveDirectories,
1187 missingDependencies: resolveMissing
1188 };
1189 const expectedToString = expected => {
1190 return expected ? ` (expected ${expected})` : "";
1191 };
1192 const jobToString = job => {
1193 switch (job.type) {
1194 case RBDT_RESOLVE_CJS:
1195 return `resolve commonjs ${job.path}${expectedToString(
1196 job.expected
1197 )}`;
1198 case RBDT_RESOLVE_ESM:
1199 return `resolve esm ${job.path}${expectedToString(job.expected)}`;
1200 case RBDT_RESOLVE_DIRECTORY:
1201 return `resolve directory ${job.path}`;
1202 case RBDT_RESOLVE_CJS_FILE:
1203 return `resolve commonjs file ${job.path}${expectedToString(
1204 job.expected
1205 )}`;
1206 case RBDT_RESOLVE_ESM_FILE:
1207 return `resolve esm file ${job.path}${expectedToString(
1208 job.expected
1209 )}`;
1210 case RBDT_DIRECTORY:
1211 return `directory ${job.path}`;
1212 case RBDT_FILE:
1213 return `file ${job.path}`;
1214 case RBDT_DIRECTORY_DEPENDENCIES:
1215 return `directory dependencies ${job.path}`;
1216 case RBDT_FILE_DEPENDENCIES:
1217 return `file dependencies ${job.path}`;
1218 }
1219 return `unknown ${job.type} ${job.path}`;
1220 };
1221 const pathToString = job => {
1222 let result = ` at ${jobToString(job)}`;
1223 job = job.issuer;
1224 while (job !== undefined) {
1225 result += `\n at ${jobToString(job)}`;
1226 job = job.issuer;
1227 }
1228 return result;
1229 };
1230 processAsyncTree(
1231 Array.from(deps, dep => ({
1232 type: RBDT_RESOLVE_CJS,
1233 context,
1234 path: dep,
1235 expected: undefined,
1236 issuer: undefined
1237 })),
1238 20,
1239 (job, push, callback) => {
1240 const { type, context, path, expected } = job;
1241 const resolveDirectory = path => {
1242 const key = `d\n${context}\n${path}`;
1243 if (resolveResults.has(key)) {
1244 return callback();
1245 }
1246 resolveResults.set(key, undefined);
1247 resolveContext(context, path, resolverContext, (err, _, result) => {
1248 if (err) {
1249 if (expected === false) {
1250 resolveResults.set(key, false);
1251 return callback();
1252 }
1253 invalidResolveResults.add(key);
1254 err.message += `\nwhile resolving '${path}' in ${context} to a directory`;
1255 return callback(err);
1256 }
1257 const resultPath = result.path;
1258 resolveResults.set(key, resultPath);
1259 push({
1260 type: RBDT_DIRECTORY,
1261 context: undefined,
1262 path: resultPath,
1263 expected: undefined,
1264 issuer: job
1265 });
1266 callback();
1267 });
1268 };
1269 const resolveFile = (path, symbol, resolve) => {
1270 const key = `${symbol}\n${context}\n${path}`;
1271 if (resolveResults.has(key)) {
1272 return callback();
1273 }
1274 resolveResults.set(key, undefined);
1275 resolve(context, path, resolverContext, (err, _, result) => {
1276 if (typeof expected === "string") {
1277 if (!err && result && result.path === expected) {
1278 resolveResults.set(key, result.path);
1279 } else {
1280 invalidResolveResults.add(key);
1281 this.logger.warn(
1282 `Resolving '${path}' in ${context} for build dependencies doesn't lead to expected result '${expected}', but to '${
1283 err || (result && result.path)
1284 }' instead. Resolving dependencies are ignored for this path.\n${pathToString(
1285 job
1286 )}`
1287 );
1288 }
1289 } else {
1290 if (err) {
1291 if (expected === false) {
1292 resolveResults.set(key, false);
1293 return callback();
1294 }
1295 invalidResolveResults.add(key);
1296 err.message += `\nwhile resolving '${path}' in ${context} as file\n${pathToString(
1297 job
1298 )}`;
1299 return callback(err);
1300 }
1301 const resultPath = result.path;
1302 resolveResults.set(key, resultPath);
1303 push({
1304 type: RBDT_FILE,
1305 context: undefined,
1306 path: resultPath,
1307 expected: undefined,
1308 issuer: job
1309 });
1310 }
1311 callback();
1312 });
1313 };
1314 switch (type) {
1315 case RBDT_RESOLVE_CJS: {
1316 const isDirectory = /[\\/]$/.test(path);
1317 if (isDirectory) {
1318 resolveDirectory(path.slice(0, path.length - 1));
1319 } else {
1320 resolveFile(path, "f", resolveCjs);
1321 }
1322 break;
1323 }
1324 case RBDT_RESOLVE_ESM: {
1325 const isDirectory = /[\\/]$/.test(path);
1326 if (isDirectory) {
1327 resolveDirectory(path.slice(0, path.length - 1));
1328 } else {
1329 resolveFile(path);
1330 }
1331 break;
1332 }
1333 case RBDT_RESOLVE_DIRECTORY: {
1334 resolveDirectory(path);
1335 break;
1336 }
1337 case RBDT_RESOLVE_CJS_FILE: {
1338 resolveFile(path, "f", resolveCjs);
1339 break;
1340 }
1341 case RBDT_RESOLVE_CJS_FILE_AS_CHILD: {
1342 resolveFile(path, "c", resolveCjsAsChild);
1343 break;
1344 }
1345 case RBDT_RESOLVE_ESM_FILE: {
1346 resolveFile(path, "e", resolveEsm);
1347 break;
1348 }
1349 case RBDT_FILE: {
1350 if (files.has(path)) {
1351 callback();
1352 break;
1353 }
1354 files.add(path);
1355 this.fs.realpath(path, (err, _realPath) => {
1356 if (err) return callback(err);
1357 const realPath = /** @type {string} */ (_realPath);
1358 if (realPath !== path) {
1359 fileSymlinks.add(path);
1360 resolveFiles.add(path);
1361 if (files.has(realPath)) return callback();
1362 files.add(realPath);
1363 }
1364 push({
1365 type: RBDT_FILE_DEPENDENCIES,
1366 context: undefined,
1367 path: realPath,
1368 expected: undefined,
1369 issuer: job
1370 });
1371 callback();
1372 });
1373 break;
1374 }
1375 case RBDT_DIRECTORY: {
1376 if (directories.has(path)) {
1377 callback();
1378 break;
1379 }
1380 directories.add(path);
1381 this.fs.realpath(path, (err, _realPath) => {
1382 if (err) return callback(err);
1383 const realPath = /** @type {string} */ (_realPath);
1384 if (realPath !== path) {
1385 directorySymlinks.add(path);
1386 resolveFiles.add(path);
1387 if (directories.has(realPath)) return callback();
1388 directories.add(realPath);
1389 }
1390 push({
1391 type: RBDT_DIRECTORY_DEPENDENCIES,
1392 context: undefined,
1393 path: realPath,
1394 expected: undefined,
1395 issuer: job
1396 });
1397 callback();
1398 });
1399 break;
1400 }
1401 case RBDT_FILE_DEPENDENCIES: {
1402 // Check for known files without dependencies
1403 if (/\.json5?$|\.yarn-integrity$|yarn\.lock$|\.ya?ml/.test(path)) {
1404 process.nextTick(callback);
1405 break;
1406 }
1407 // Check commonjs cache for the module
1408 /** @type {NodeModule} */
1409 const module = require.cache[path];
1410 if (module && Array.isArray(module.children)) {
1411 children: for (const child of module.children) {
1412 let childPath = child.filename;
1413 if (childPath) {
1414 push({
1415 type: RBDT_FILE,
1416 context: undefined,
1417 path: childPath,
1418 expected: undefined,
1419 issuer: job
1420 });
1421 const context = dirname(this.fs, path);
1422 for (const modulePath of module.paths) {
1423 if (childPath.startsWith(modulePath)) {
1424 let subPath = childPath.slice(modulePath.length + 1);
1425 const packageMatch = /^(@[^\\/]+[\\/])[^\\/]+/.exec(
1426 subPath
1427 );
1428 if (packageMatch) {
1429 push({
1430 type: RBDT_FILE,
1431 context: undefined,
1432 path:
1433 modulePath +
1434 childPath[modulePath.length] +
1435 packageMatch[0] +
1436 childPath[modulePath.length] +
1437 "package.json",
1438 expected: false,
1439 issuer: job
1440 });
1441 }
1442 let request = subPath.replace(/\\/g, "/");
1443 if (request.endsWith(".js"))
1444 request = request.slice(0, -3);
1445 push({
1446 type: RBDT_RESOLVE_CJS_FILE_AS_CHILD,
1447 context,
1448 path: request,
1449 expected: child.filename,
1450 issuer: job
1451 });
1452 continue children;
1453 }
1454 }
1455 let request = relative(this.fs, context, childPath);
1456 if (request.endsWith(".js")) request = request.slice(0, -3);
1457 request = request.replace(/\\/g, "/");
1458 if (!request.startsWith("../")) request = `./${request}`;
1459 push({
1460 type: RBDT_RESOLVE_CJS_FILE,
1461 context,
1462 path: request,
1463 expected: child.filename,
1464 issuer: job
1465 });
1466 }
1467 }
1468 } else if (supportsEsm && /\.m?js$/.test(path)) {
1469 if (!this._warnAboutExperimentalEsmTracking) {
1470 this.logger.log(
1471 "Node.js doesn't offer a (nice) way to introspect the ESM dependency graph yet.\n" +
1472 "Until a full solution is available webpack uses an experimental ESM tracking based on parsing.\n" +
1473 "As best effort webpack parses the ESM files to guess dependencies. But this can lead to expensive and incorrect tracking."
1474 );
1475 this._warnAboutExperimentalEsmTracking = true;
1476 }
1477 const lexer = require("es-module-lexer");
1478 lexer.init.then(() => {
1479 this.fs.readFile(path, (err, content) => {
1480 if (err) return callback(err);
1481 try {
1482 const context = dirname(this.fs, path);
1483 const source = content.toString();
1484 const [imports] = lexer.parse(source);
1485 for (const imp of imports) {
1486 try {
1487 let dependency;
1488 if (imp.d === -1) {
1489 // import ... from "..."
1490 dependency = JSON.parse(
1491 source.substring(imp.s - 1, imp.e + 1)
1492 );
1493 } else if (imp.d > -1) {
1494 // import()
1495 let expr = source.substring(imp.s, imp.e).trim();
1496 if (expr[0] === "'")
1497 expr = `"${expr
1498 .slice(1, -1)
1499 .replace(/"/g, '\\"')}"`;
1500 dependency = JSON.parse(expr);
1501 } else {
1502 // e.g. import.meta
1503 continue;
1504 }
1505 push({
1506 type: RBDT_RESOLVE_ESM_FILE,
1507 context,
1508 path: dependency,
1509 expected: undefined,
1510 issuer: job
1511 });
1512 } catch (e) {
1513 this.logger.warn(
1514 `Parsing of ${path} for build dependencies failed at 'import(${source.substring(
1515 imp.s,
1516 imp.e
1517 )})'.\n` +
1518 "Build dependencies behind this expression are ignored and might cause incorrect cache invalidation."
1519 );
1520 this.logger.debug(pathToString(job));
1521 this.logger.debug(e.stack);
1522 }
1523 }
1524 } catch (e) {
1525 this.logger.warn(
1526 `Parsing of ${path} for build dependencies failed and all dependencies of this file are ignored, which might cause incorrect cache invalidation..`
1527 );
1528 this.logger.debug(pathToString(job));
1529 this.logger.debug(e.stack);
1530 }
1531 process.nextTick(callback);
1532 });
1533 }, callback);
1534 break;
1535 } else {
1536 this.logger.log(
1537 `Assuming ${path} has no dependencies as we were unable to assign it to any module system.`
1538 );
1539 this.logger.debug(pathToString(job));
1540 }
1541 process.nextTick(callback);
1542 break;
1543 }
1544 case RBDT_DIRECTORY_DEPENDENCIES: {
1545 const match =
1546 /(^.+[\\/]node_modules[\\/](?:@[^\\/]+[\\/])?[^\\/]+)/.exec(path);
1547 const packagePath = match ? match[1] : path;
1548 const packageJson = join(this.fs, packagePath, "package.json");
1549 this.fs.readFile(packageJson, (err, content) => {
1550 if (err) {
1551 if (err.code === "ENOENT") {
1552 resolveMissing.add(packageJson);
1553 const parent = dirname(this.fs, packagePath);
1554 if (parent !== packagePath) {
1555 push({
1556 type: RBDT_DIRECTORY_DEPENDENCIES,
1557 context: undefined,
1558 path: parent,
1559 expected: undefined,
1560 issuer: job
1561 });
1562 }
1563 callback();
1564 return;
1565 }
1566 return callback(err);
1567 }
1568 resolveFiles.add(packageJson);
1569 let packageData;
1570 try {
1571 packageData = JSON.parse(content.toString("utf-8"));
1572 } catch (e) {
1573 return callback(e);
1574 }
1575 const depsObject = packageData.dependencies;
1576 const optionalDepsObject = packageData.optionalDependencies;
1577 const allDeps = new Set();
1578 const optionalDeps = new Set();
1579 if (typeof depsObject === "object" && depsObject) {
1580 for (const dep of Object.keys(depsObject)) {
1581 allDeps.add(dep);
1582 }
1583 }
1584 if (
1585 typeof optionalDepsObject === "object" &&
1586 optionalDepsObject
1587 ) {
1588 for (const dep of Object.keys(optionalDepsObject)) {
1589 allDeps.add(dep);
1590 optionalDeps.add(dep);
1591 }
1592 }
1593 for (const dep of allDeps) {
1594 push({
1595 type: RBDT_RESOLVE_DIRECTORY,
1596 context: packagePath,
1597 path: dep,
1598 expected: !optionalDeps.has(dep),
1599 issuer: job
1600 });
1601 }
1602 callback();
1603 });
1604 break;
1605 }
1606 }
1607 },
1608 err => {
1609 if (err) return callback(err);
1610 for (const l of fileSymlinks) files.delete(l);
1611 for (const l of directorySymlinks) directories.delete(l);
1612 for (const k of invalidResolveResults) resolveResults.delete(k);
1613 callback(null, {
1614 files,
1615 directories,
1616 missing,
1617 resolveResults,
1618 resolveDependencies: {
1619 files: resolveFiles,
1620 directories: resolveDirectories,
1621 missing: resolveMissing
1622 }
1623 });
1624 }
1625 );
1626 }
1627
1628 /**
1629 * @param {Map<string, string | false>} resolveResults results from resolving
1630 * @param {function(Error=, boolean=): void} callback callback with true when resolveResults resolve the same way
1631 * @returns {void}
1632 */
1633 checkResolveResultsValid(resolveResults, callback) {
1634 const { resolveCjs, resolveCjsAsChild, resolveEsm, resolveContext } =
1635 this._createBuildDependenciesResolvers();
1636 asyncLib.eachLimit(
1637 resolveResults,
1638 20,
1639 ([key, expectedResult], callback) => {
1640 const [type, context, path] = key.split("\n");
1641 switch (type) {
1642 case "d":
1643 resolveContext(context, path, {}, (err, _, result) => {
1644 if (expectedResult === false)
1645 return callback(err ? undefined : INVALID);
1646 if (err) return callback(err);
1647 const resultPath = result.path;
1648 if (resultPath !== expectedResult) return callback(INVALID);
1649 callback();
1650 });
1651 break;
1652 case "f":
1653 resolveCjs(context, path, {}, (err, _, result) => {
1654 if (expectedResult === false)
1655 return callback(err ? undefined : INVALID);
1656 if (err) return callback(err);
1657 const resultPath = result.path;
1658 if (resultPath !== expectedResult) return callback(INVALID);
1659 callback();
1660 });
1661 break;
1662 case "c":
1663 resolveCjsAsChild(context, path, {}, (err, _, result) => {
1664 if (expectedResult === false)
1665 return callback(err ? undefined : INVALID);
1666 if (err) return callback(err);
1667 const resultPath = result.path;
1668 if (resultPath !== expectedResult) return callback(INVALID);
1669 callback();
1670 });
1671 break;
1672 case "e":
1673 resolveEsm(context, path, {}, (err, _, result) => {
1674 if (expectedResult === false)
1675 return callback(err ? undefined : INVALID);
1676 if (err) return callback(err);
1677 const resultPath = result.path;
1678 if (resultPath !== expectedResult) return callback(INVALID);
1679 callback();
1680 });
1681 break;
1682 default:
1683 callback(new Error("Unexpected type in resolve result key"));
1684 break;
1685 }
1686 },
1687 /**
1688 * @param {Error | typeof INVALID=} err error or invalid flag
1689 * @returns {void}
1690 */
1691 err => {
1692 if (err === INVALID) {
1693 return callback(null, false);
1694 }
1695 if (err) {
1696 return callback(err);
1697 }
1698 return callback(null, true);
1699 }
1700 );
1701 }
1702
1703 /**
1704 *
1705 * @param {number} startTime when processing the files has started
1706 * @param {Iterable<string>} files all files
1707 * @param {Iterable<string>} directories all directories
1708 * @param {Iterable<string>} missing all missing files or directories
1709 * @param {Object} options options object (for future extensions)
1710 * @param {boolean=} options.hash should use hash to snapshot
1711 * @param {boolean=} options.timestamp should use timestamp to snapshot
1712 * @param {function(WebpackError=, Snapshot=): void} callback callback function
1713 * @returns {void}
1714 */
1715 createSnapshot(startTime, files, directories, missing, options, callback) {
1716 /** @type {Map<string, FileSystemInfoEntry>} */
1717 const fileTimestamps = new Map();
1718 /** @type {Map<string, string>} */
1719 const fileHashes = new Map();
1720 /** @type {Map<string, TimestampAndHash | string>} */
1721 const fileTshs = new Map();
1722 /** @type {Map<string, FileSystemInfoEntry>} */
1723 const contextTimestamps = new Map();
1724 /** @type {Map<string, string>} */
1725 const contextHashes = new Map();
1726 /** @type {Map<string, TimestampAndHash | string>} */
1727 const contextTshs = new Map();
1728 /** @type {Map<string, boolean>} */
1729 const missingExistence = new Map();
1730 /** @type {Map<string, string>} */
1731 const managedItemInfo = new Map();
1732 /** @type {Set<string>} */
1733 const managedFiles = new Set();
1734 /** @type {Set<string>} */
1735 const managedContexts = new Set();
1736 /** @type {Set<string>} */
1737 const managedMissing = new Set();
1738 /** @type {Set<Snapshot>} */
1739 const children = new Set();
1740
1741 /** @type {Set<string>} */
1742 let unsharedFileTimestamps;
1743 /** @type {Set<string>} */
1744 let unsharedFileHashes;
1745 /** @type {Set<string>} */
1746 let unsharedFileTshs;
1747 /** @type {Set<string>} */
1748 let unsharedContextTimestamps;
1749 /** @type {Set<string>} */
1750 let unsharedContextHashes;
1751 /** @type {Set<string>} */
1752 let unsharedContextTshs;
1753 /** @type {Set<string>} */
1754 let unsharedMissingExistence;
1755 /** @type {Set<string>} */
1756 let unsharedManagedItemInfo;
1757
1758 /** @type {Set<string>} */
1759 const managedItems = new Set();
1760
1761 /** 1 = timestamp, 2 = hash, 3 = timestamp + hash */
1762 const mode = options && options.hash ? (options.timestamp ? 3 : 2) : 1;
1763
1764 let jobs = 1;
1765 const jobDone = () => {
1766 if (--jobs === 0) {
1767 const snapshot = new Snapshot();
1768 if (startTime) snapshot.setStartTime(startTime);
1769 if (fileTimestamps.size !== 0) {
1770 snapshot.setFileTimestamps(fileTimestamps);
1771 this._fileTimestampsOptimization.storeUnsharedSnapshot(
1772 snapshot,
1773 unsharedFileTimestamps
1774 );
1775 }
1776 if (fileHashes.size !== 0) {
1777 snapshot.setFileHashes(fileHashes);
1778 this._fileHashesOptimization.storeUnsharedSnapshot(
1779 snapshot,
1780 unsharedFileHashes
1781 );
1782 }
1783 if (fileTshs.size !== 0) {
1784 snapshot.setFileTshs(fileTshs);
1785 this._fileTshsOptimization.storeUnsharedSnapshot(
1786 snapshot,
1787 unsharedFileTshs
1788 );
1789 }
1790 if (contextTimestamps.size !== 0) {
1791 snapshot.setContextTimestamps(contextTimestamps);
1792 this._contextTimestampsOptimization.storeUnsharedSnapshot(
1793 snapshot,
1794 unsharedContextTimestamps
1795 );
1796 }
1797 if (contextHashes.size !== 0) {
1798 snapshot.setContextHashes(contextHashes);
1799 this._contextHashesOptimization.storeUnsharedSnapshot(
1800 snapshot,
1801 unsharedContextHashes
1802 );
1803 }
1804 if (contextTshs.size !== 0) {
1805 snapshot.setContextTshs(contextTshs);
1806 this._contextTshsOptimization.storeUnsharedSnapshot(
1807 snapshot,
1808 unsharedContextTshs
1809 );
1810 }
1811 if (missingExistence.size !== 0) {
1812 snapshot.setMissingExistence(missingExistence);
1813 this._missingExistenceOptimization.storeUnsharedSnapshot(
1814 snapshot,
1815 unsharedMissingExistence
1816 );
1817 }
1818 if (managedItemInfo.size !== 0) {
1819 snapshot.setManagedItemInfo(managedItemInfo);
1820 this._managedItemInfoOptimization.storeUnsharedSnapshot(
1821 snapshot,
1822 unsharedManagedItemInfo
1823 );
1824 }
1825 const unsharedManagedFiles = this._managedFilesOptimization.optimize(
1826 managedFiles,
1827 undefined,
1828 children
1829 );
1830 if (managedFiles.size !== 0) {
1831 snapshot.setManagedFiles(managedFiles);
1832 this._managedFilesOptimization.storeUnsharedSnapshot(
1833 snapshot,
1834 unsharedManagedFiles
1835 );
1836 }
1837 const unsharedManagedContexts =
1838 this._managedContextsOptimization.optimize(
1839 managedContexts,
1840 undefined,
1841 children
1842 );
1843 if (managedContexts.size !== 0) {
1844 snapshot.setManagedContexts(managedContexts);
1845 this._managedContextsOptimization.storeUnsharedSnapshot(
1846 snapshot,
1847 unsharedManagedContexts
1848 );
1849 }
1850 const unsharedManagedMissing =
1851 this._managedMissingOptimization.optimize(
1852 managedMissing,
1853 undefined,
1854 children
1855 );
1856 if (managedMissing.size !== 0) {
1857 snapshot.setManagedMissing(managedMissing);
1858 this._managedMissingOptimization.storeUnsharedSnapshot(
1859 snapshot,
1860 unsharedManagedMissing
1861 );
1862 }
1863 if (children.size !== 0) {
1864 snapshot.setChildren(children);
1865 }
1866 this._snapshotCache.set(snapshot, true);
1867 this._statCreatedSnapshots++;
1868
1869 callback(null, snapshot);
1870 }
1871 };
1872 const jobError = () => {
1873 if (jobs > 0) {
1874 // large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
1875 jobs = -100000000;
1876 callback(null, null);
1877 }
1878 };
1879 const checkManaged = (path, managedSet) => {
1880 for (const immutablePath of this.immutablePathsWithSlash) {
1881 if (path.startsWith(immutablePath)) {
1882 managedSet.add(path);
1883 return true;
1884 }
1885 }
1886 for (const managedPath of this.managedPathsWithSlash) {
1887 if (path.startsWith(managedPath)) {
1888 const managedItem = getManagedItem(managedPath, path);
1889 if (managedItem) {
1890 managedItems.add(managedItem);
1891 managedSet.add(path);
1892 return true;
1893 }
1894 }
1895 }
1896 return false;
1897 };
1898 const captureNonManaged = (items, managedSet) => {
1899 const capturedItems = new Set();
1900 for (const path of items) {
1901 if (!checkManaged(path, managedSet)) capturedItems.add(path);
1902 }
1903 return capturedItems;
1904 };
1905 if (files) {
1906 const capturedFiles = captureNonManaged(files, managedFiles);
1907 switch (mode) {
1908 case 3:
1909 unsharedFileTshs = this._fileTshsOptimization.optimize(
1910 capturedFiles,
1911 undefined,
1912 children
1913 );
1914 for (const path of capturedFiles) {
1915 const cache = this._fileTshs.get(path);
1916 if (cache !== undefined) {
1917 fileTshs.set(path, cache);
1918 } else {
1919 jobs++;
1920 this._getFileTimestampAndHash(path, (err, entry) => {
1921 if (err) {
1922 if (this.logger) {
1923 this.logger.debug(
1924 `Error snapshotting file timestamp hash combination of ${path}: ${err.stack}`
1925 );
1926 }
1927 jobError();
1928 } else {
1929 fileTshs.set(path, entry);
1930 jobDone();
1931 }
1932 });
1933 }
1934 }
1935 break;
1936 case 2:
1937 unsharedFileHashes = this._fileHashesOptimization.optimize(
1938 capturedFiles,
1939 undefined,
1940 children
1941 );
1942 for (const path of capturedFiles) {
1943 const cache = this._fileHashes.get(path);
1944 if (cache !== undefined) {
1945 fileHashes.set(path, cache);
1946 } else {
1947 jobs++;
1948 this.fileHashQueue.add(path, (err, entry) => {
1949 if (err) {
1950 if (this.logger) {
1951 this.logger.debug(
1952 `Error snapshotting file hash of ${path}: ${err.stack}`
1953 );
1954 }
1955 jobError();
1956 } else {
1957 fileHashes.set(path, entry);
1958 jobDone();
1959 }
1960 });
1961 }
1962 }
1963 break;
1964 case 1:
1965 unsharedFileTimestamps = this._fileTimestampsOptimization.optimize(
1966 capturedFiles,
1967 startTime,
1968 children
1969 );
1970 for (const path of capturedFiles) {
1971 const cache = this._fileTimestamps.get(path);
1972 if (cache !== undefined) {
1973 if (cache !== "ignore") {
1974 fileTimestamps.set(path, cache);
1975 }
1976 } else {
1977 jobs++;
1978 this.fileTimestampQueue.add(path, (err, entry) => {
1979 if (err) {
1980 if (this.logger) {
1981 this.logger.debug(
1982 `Error snapshotting file timestamp of ${path}: ${err.stack}`
1983 );
1984 }
1985 jobError();
1986 } else {
1987 fileTimestamps.set(path, entry);
1988 jobDone();
1989 }
1990 });
1991 }
1992 }
1993 break;
1994 }
1995 }
1996 if (directories) {
1997 const capturedDirectories = captureNonManaged(
1998 directories,
1999 managedContexts
2000 );
2001 switch (mode) {
2002 case 3:
2003 unsharedContextTshs = this._contextTshsOptimization.optimize(
2004 capturedDirectories,
2005 undefined,
2006 children
2007 );
2008 for (const path of capturedDirectories) {
2009 const cache = this._contextTshs.get(path);
2010 if (cache !== undefined) {
2011 contextTshs.set(path, cache);
2012 } else {
2013 jobs++;
2014 this._getContextTimestampAndHash(path, (err, entry) => {
2015 if (err) {
2016 if (this.logger) {
2017 this.logger.debug(
2018 `Error snapshotting context timestamp hash combination of ${path}: ${err.stack}`
2019 );
2020 }
2021 jobError();
2022 } else {
2023 contextTshs.set(path, entry);
2024 jobDone();
2025 }
2026 });
2027 }
2028 }
2029 break;
2030 case 2:
2031 unsharedContextHashes = this._contextHashesOptimization.optimize(
2032 capturedDirectories,
2033 undefined,
2034 children
2035 );
2036 for (const path of capturedDirectories) {
2037 const cache = this._contextHashes.get(path);
2038 if (cache !== undefined) {
2039 contextHashes.set(path, cache);
2040 } else {
2041 jobs++;
2042 this.contextHashQueue.add(path, (err, entry) => {
2043 if (err) {
2044 if (this.logger) {
2045 this.logger.debug(
2046 `Error snapshotting context hash of ${path}: ${err.stack}`
2047 );
2048 }
2049 jobError();
2050 } else {
2051 contextHashes.set(path, entry);
2052 jobDone();
2053 }
2054 });
2055 }
2056 }
2057 break;
2058 case 1:
2059 unsharedContextTimestamps =
2060 this._contextTimestampsOptimization.optimize(
2061 capturedDirectories,
2062 startTime,
2063 children
2064 );
2065 for (const path of capturedDirectories) {
2066 const cache = this._contextTimestamps.get(path);
2067 if (cache !== undefined) {
2068 if (cache !== "ignore") {
2069 contextTimestamps.set(path, cache);
2070 }
2071 } else {
2072 jobs++;
2073 this.contextTimestampQueue.add(path, (err, entry) => {
2074 if (err) {
2075 if (this.logger) {
2076 this.logger.debug(
2077 `Error snapshotting context timestamp of ${path}: ${err.stack}`
2078 );
2079 }
2080 jobError();
2081 } else {
2082 contextTimestamps.set(path, entry);
2083 jobDone();
2084 }
2085 });
2086 }
2087 }
2088 break;
2089 }
2090 }
2091 if (missing) {
2092 const capturedMissing = captureNonManaged(missing, managedMissing);
2093 unsharedMissingExistence = this._missingExistenceOptimization.optimize(
2094 capturedMissing,
2095 startTime,
2096 children
2097 );
2098 for (const path of capturedMissing) {
2099 const cache = this._fileTimestamps.get(path);
2100 if (cache !== undefined) {
2101 if (cache !== "ignore") {
2102 missingExistence.set(path, toExistence(cache));
2103 }
2104 } else {
2105 jobs++;
2106 this.fileTimestampQueue.add(path, (err, entry) => {
2107 if (err) {
2108 if (this.logger) {
2109 this.logger.debug(
2110 `Error snapshotting missing timestamp of ${path}: ${err.stack}`
2111 );
2112 }
2113 jobError();
2114 } else {
2115 missingExistence.set(path, toExistence(entry));
2116 jobDone();
2117 }
2118 });
2119 }
2120 }
2121 }
2122 unsharedManagedItemInfo = this._managedItemInfoOptimization.optimize(
2123 managedItems,
2124 undefined,
2125 children
2126 );
2127 for (const path of managedItems) {
2128 const cache = this._managedItems.get(path);
2129 if (cache !== undefined) {
2130 managedItemInfo.set(path, cache);
2131 } else {
2132 jobs++;
2133 this.managedItemQueue.add(path, (err, entry) => {
2134 if (err) {
2135 if (this.logger) {
2136 this.logger.debug(
2137 `Error snapshotting managed item ${path}: ${err.stack}`
2138 );
2139 }
2140 jobError();
2141 } else {
2142 managedItemInfo.set(path, entry);
2143 jobDone();
2144 }
2145 });
2146 }
2147 }
2148 jobDone();
2149 }
2150
2151 /**
2152 * @param {Snapshot} snapshot1 a snapshot
2153 * @param {Snapshot} snapshot2 a snapshot
2154 * @returns {Snapshot} merged snapshot
2155 */
2156 mergeSnapshots(snapshot1, snapshot2) {
2157 const snapshot = new Snapshot();
2158 if (snapshot1.hasStartTime() && snapshot2.hasStartTime())
2159 snapshot.setStartTime(Math.min(snapshot1.startTime, snapshot2.startTime));
2160 else if (snapshot2.hasStartTime()) snapshot.startTime = snapshot2.startTime;
2161 else if (snapshot1.hasStartTime()) snapshot.startTime = snapshot1.startTime;
2162 if (snapshot1.hasFileTimestamps() || snapshot2.hasFileTimestamps()) {
2163 snapshot.setFileTimestamps(
2164 mergeMaps(snapshot1.fileTimestamps, snapshot2.fileTimestamps)
2165 );
2166 }
2167 if (snapshot1.hasFileHashes() || snapshot2.hasFileHashes()) {
2168 snapshot.setFileHashes(
2169 mergeMaps(snapshot1.fileHashes, snapshot2.fileHashes)
2170 );
2171 }
2172 if (snapshot1.hasFileTshs() || snapshot2.hasFileTshs()) {
2173 snapshot.setFileTshs(mergeMaps(snapshot1.fileTshs, snapshot2.fileTshs));
2174 }
2175 if (snapshot1.hasContextTimestamps() || snapshot2.hasContextTimestamps()) {
2176 snapshot.setContextTimestamps(
2177 mergeMaps(snapshot1.contextTimestamps, snapshot2.contextTimestamps)
2178 );
2179 }
2180 if (snapshot1.hasContextHashes() || snapshot2.hasContextHashes()) {
2181 snapshot.setContextHashes(
2182 mergeMaps(snapshot1.contextHashes, snapshot2.contextHashes)
2183 );
2184 }
2185 if (snapshot1.hasContextTshs() || snapshot2.hasContextTshs()) {
2186 snapshot.setContextTshs(
2187 mergeMaps(snapshot1.contextTshs, snapshot2.contextTshs)
2188 );
2189 }
2190 if (snapshot1.hasMissingExistence() || snapshot2.hasMissingExistence()) {
2191 snapshot.setMissingExistence(
2192 mergeMaps(snapshot1.missingExistence, snapshot2.missingExistence)
2193 );
2194 }
2195 if (snapshot1.hasManagedItemInfo() || snapshot2.hasManagedItemInfo()) {
2196 snapshot.setManagedItemInfo(
2197 mergeMaps(snapshot1.managedItemInfo, snapshot2.managedItemInfo)
2198 );
2199 }
2200 if (snapshot1.hasManagedFiles() || snapshot2.hasManagedFiles()) {
2201 snapshot.setManagedFiles(
2202 mergeSets(snapshot1.managedFiles, snapshot2.managedFiles)
2203 );
2204 }
2205 if (snapshot1.hasManagedContexts() || snapshot2.hasManagedContexts()) {
2206 snapshot.setManagedContexts(
2207 mergeSets(snapshot1.managedContexts, snapshot2.managedContexts)
2208 );
2209 }
2210 if (snapshot1.hasManagedMissing() || snapshot2.hasManagedMissing()) {
2211 snapshot.setManagedMissing(
2212 mergeSets(snapshot1.managedMissing, snapshot2.managedMissing)
2213 );
2214 }
2215 if (snapshot1.hasChildren() || snapshot2.hasChildren()) {
2216 snapshot.setChildren(mergeSets(snapshot1.children, snapshot2.children));
2217 }
2218 if (
2219 this._snapshotCache.get(snapshot1) === true &&
2220 this._snapshotCache.get(snapshot2) === true
2221 ) {
2222 this._snapshotCache.set(snapshot, true);
2223 }
2224 return snapshot;
2225 }
2226
2227 /**
2228 * @param {Snapshot} snapshot the snapshot made
2229 * @param {function(WebpackError=, boolean=): void} callback callback function
2230 * @returns {void}
2231 */
2232 checkSnapshotValid(snapshot, callback) {
2233 const cachedResult = this._snapshotCache.get(snapshot);
2234 if (cachedResult !== undefined) {
2235 this._statTestedSnapshotsCached++;
2236 if (typeof cachedResult === "boolean") {
2237 callback(null, cachedResult);
2238 } else {
2239 cachedResult.push(callback);
2240 }
2241 return;
2242 }
2243 this._statTestedSnapshotsNotCached++;
2244 this._checkSnapshotValidNoCache(snapshot, callback);
2245 }
2246
2247 /**
2248 * @param {Snapshot} snapshot the snapshot made
2249 * @param {function(WebpackError=, boolean=): void} callback callback function
2250 * @returns {void}
2251 */
2252 _checkSnapshotValidNoCache(snapshot, callback) {
2253 /** @type {number | undefined} */
2254 let startTime = undefined;
2255 if (snapshot.hasStartTime()) {
2256 startTime = snapshot.startTime;
2257 }
2258 let jobs = 1;
2259 const jobDone = () => {
2260 if (--jobs === 0) {
2261 this._snapshotCache.set(snapshot, true);
2262 callback(null, true);
2263 }
2264 };
2265 const invalid = () => {
2266 if (jobs > 0) {
2267 // large negative number instead of NaN or something else to keep jobs to stay a SMI (v8)
2268 jobs = -100000000;
2269 this._snapshotCache.set(snapshot, false);
2270 callback(null, false);
2271 }
2272 };
2273 const invalidWithError = (path, err) => {
2274 if (this._remainingLogs > 0) {
2275 this._log(path, `error occurred: %s`, err);
2276 }
2277 invalid();
2278 };
2279 /**
2280 * @param {string} path file path
2281 * @param {string} current current hash
2282 * @param {string} snap snapshot hash
2283 * @returns {boolean} true, if ok
2284 */
2285 const checkHash = (path, current, snap) => {
2286 if (current !== snap) {
2287 // If hash differ it's invalid
2288 if (this._remainingLogs > 0) {
2289 this._log(path, `hashes differ (%s != %s)`, current, snap);
2290 }
2291 return false;
2292 }
2293 return true;
2294 };
2295 /**
2296 * @param {string} path file path
2297 * @param {boolean} current current entry
2298 * @param {boolean} snap entry from snapshot
2299 * @returns {boolean} true, if ok
2300 */
2301 const checkExistence = (path, current, snap) => {
2302 if (!current !== !snap) {
2303 // If existence of item differs
2304 // it's invalid
2305 if (this._remainingLogs > 0) {
2306 this._log(
2307 path,
2308 current ? "it didn't exist before" : "it does no longer exist"
2309 );
2310 }
2311 return false;
2312 }
2313 return true;
2314 };
2315 /**
2316 * @param {string} path file path
2317 * @param {FileSystemInfoEntry} current current entry
2318 * @param {FileSystemInfoEntry} snap entry from snapshot
2319 * @param {boolean} log log reason
2320 * @returns {boolean} true, if ok
2321 */
2322 const checkFile = (path, current, snap, log = true) => {
2323 if (current === snap) return true;
2324 if (!current !== !snap) {
2325 // If existence of item differs
2326 // it's invalid
2327 if (log && this._remainingLogs > 0) {
2328 this._log(
2329 path,
2330 current ? "it didn't exist before" : "it does no longer exist"
2331 );
2332 }
2333 return false;
2334 }
2335 if (current) {
2336 // For existing items only
2337 if (typeof startTime === "number" && current.safeTime > startTime) {
2338 // If a change happened after starting reading the item
2339 // this may no longer be valid
2340 if (log && this._remainingLogs > 0) {
2341 this._log(
2342 path,
2343 `it may have changed (%d) after the start time of the snapshot (%d)`,
2344 current.safeTime,
2345 startTime
2346 );
2347 }
2348 return false;
2349 }
2350 if (
2351 snap.timestamp !== undefined &&
2352 current.timestamp !== snap.timestamp
2353 ) {
2354 // If we have a timestamp (it was a file or symlink) and it differs from current timestamp
2355 // it's invalid
2356 if (log && this._remainingLogs > 0) {
2357 this._log(
2358 path,
2359 `timestamps differ (%d != %d)`,
2360 current.timestamp,
2361 snap.timestamp
2362 );
2363 }
2364 return false;
2365 }
2366 if (
2367 snap.timestampHash !== undefined &&
2368 current.timestampHash !== snap.timestampHash
2369 ) {
2370 // If we have a timestampHash (it was a directory) and it differs from current timestampHash
2371 // it's invalid
2372 if (log && this._remainingLogs > 0) {
2373 this._log(
2374 path,
2375 `timestamps hashes differ (%s != %s)`,
2376 current.timestampHash,
2377 snap.timestampHash
2378 );
2379 }
2380 return false;
2381 }
2382 }
2383 return true;
2384 };
2385 if (snapshot.hasChildren()) {
2386 const childCallback = (err, result) => {
2387 if (err || !result) return invalid();
2388 else jobDone();
2389 };
2390 for (const child of snapshot.children) {
2391 const cache = this._snapshotCache.get(child);
2392 if (cache !== undefined) {
2393 this._statTestedChildrenCached++;
2394 /* istanbul ignore else */
2395 if (typeof cache === "boolean") {
2396 if (cache === false) {
2397 invalid();
2398 return;
2399 }
2400 } else {
2401 jobs++;
2402 cache.push(childCallback);
2403 }
2404 } else {
2405 this._statTestedChildrenNotCached++;
2406 jobs++;
2407 this._checkSnapshotValidNoCache(child, childCallback);
2408 }
2409 }
2410 }
2411 if (snapshot.hasFileTimestamps()) {
2412 const { fileTimestamps } = snapshot;
2413 this._statTestedEntries += fileTimestamps.size;
2414 for (const [path, ts] of fileTimestamps) {
2415 const cache = this._fileTimestamps.get(path);
2416 if (cache !== undefined) {
2417 if (cache !== "ignore" && !checkFile(path, cache, ts)) {
2418 invalid();
2419 return;
2420 }
2421 } else {
2422 jobs++;
2423 this.fileTimestampQueue.add(path, (err, entry) => {
2424 if (err) return invalidWithError(path, err);
2425 if (!checkFile(path, entry, ts)) {
2426 invalid();
2427 } else {
2428 jobDone();
2429 }
2430 });
2431 }
2432 }
2433 }
2434 const processFileHashSnapshot = (path, hash) => {
2435 const cache = this._fileHashes.get(path);
2436 if (cache !== undefined) {
2437 if (cache !== "ignore" && !checkHash(path, cache, hash)) {
2438 invalid();
2439 return;
2440 }
2441 } else {
2442 jobs++;
2443 this.fileHashQueue.add(path, (err, entry) => {
2444 if (err) return invalidWithError(path, err);
2445 if (!checkHash(path, entry, hash)) {
2446 invalid();
2447 } else {
2448 jobDone();
2449 }
2450 });
2451 }
2452 };
2453 if (snapshot.hasFileHashes()) {
2454 const { fileHashes } = snapshot;
2455 this._statTestedEntries += fileHashes.size;
2456 for (const [path, hash] of fileHashes) {
2457 processFileHashSnapshot(path, hash);
2458 }
2459 }
2460 if (snapshot.hasFileTshs()) {
2461 const { fileTshs } = snapshot;
2462 this._statTestedEntries += fileTshs.size;
2463 for (const [path, tsh] of fileTshs) {
2464 if (typeof tsh === "string") {
2465 processFileHashSnapshot(path, tsh);
2466 } else {
2467 const cache = this._fileTimestamps.get(path);
2468 if (cache !== undefined) {
2469 if (cache === "ignore" || !checkFile(path, cache, tsh, false)) {
2470 processFileHashSnapshot(path, tsh.hash);
2471 }
2472 } else {
2473 jobs++;
2474 this.fileTimestampQueue.add(path, (err, entry) => {
2475 if (err) return invalidWithError(path, err);
2476 if (!checkFile(path, entry, tsh, false)) {
2477 processFileHashSnapshot(path, tsh.hash);
2478 }
2479 jobDone();
2480 });
2481 }
2482 }
2483 }
2484 }
2485 if (snapshot.hasContextTimestamps()) {
2486 const { contextTimestamps } = snapshot;
2487 this._statTestedEntries += contextTimestamps.size;
2488 for (const [path, ts] of contextTimestamps) {
2489 const cache = this._contextTimestamps.get(path);
2490 if (cache !== undefined) {
2491 if (cache !== "ignore" && !checkFile(path, cache, ts)) {
2492 invalid();
2493 return;
2494 }
2495 } else {
2496 jobs++;
2497 this.contextTimestampQueue.add(path, (err, entry) => {
2498 if (err) return invalidWithError(path, err);
2499 if (!checkFile(path, entry, ts)) {
2500 invalid();
2501 } else {
2502 jobDone();
2503 }
2504 });
2505 }
2506 }
2507 }
2508 const processContextHashSnapshot = (path, hash) => {
2509 const cache = this._contextHashes.get(path);
2510 if (cache !== undefined) {
2511 if (cache !== "ignore" && !checkHash(path, cache, hash)) {
2512 invalid();
2513 return;
2514 }
2515 } else {
2516 jobs++;
2517 this.contextHashQueue.add(path, (err, entry) => {
2518 if (err) return invalidWithError(path, err);
2519 if (!checkHash(path, entry, hash)) {
2520 invalid();
2521 } else {
2522 jobDone();
2523 }
2524 });
2525 }
2526 };
2527 if (snapshot.hasContextHashes()) {
2528 const { contextHashes } = snapshot;
2529 this._statTestedEntries += contextHashes.size;
2530 for (const [path, hash] of contextHashes) {
2531 processContextHashSnapshot(path, hash);
2532 }
2533 }
2534 if (snapshot.hasContextTshs()) {
2535 const { contextTshs } = snapshot;
2536 this._statTestedEntries += contextTshs.size;
2537 for (const [path, tsh] of contextTshs) {
2538 if (typeof tsh === "string") {
2539 processContextHashSnapshot(path, tsh);
2540 } else {
2541 const cache = this._contextTimestamps.get(path);
2542 if (cache !== undefined) {
2543 if (cache === "ignore" || !checkFile(path, cache, tsh, false)) {
2544 processContextHashSnapshot(path, tsh.hash);
2545 }
2546 } else {
2547 jobs++;
2548 this.contextTimestampQueue.add(path, (err, entry) => {
2549 if (err) return invalidWithError(path, err);
2550 if (!checkFile(path, entry, tsh, false)) {
2551 processContextHashSnapshot(path, tsh.hash);
2552 }
2553 jobDone();
2554 });
2555 }
2556 }
2557 }
2558 }
2559 if (snapshot.hasMissingExistence()) {
2560 const { missingExistence } = snapshot;
2561 this._statTestedEntries += missingExistence.size;
2562 for (const [path, existence] of missingExistence) {
2563 const cache = this._fileTimestamps.get(path);
2564 if (cache !== undefined) {
2565 if (
2566 cache !== "ignore" &&
2567 !checkExistence(path, toExistence(cache), existence)
2568 ) {
2569 invalid();
2570 return;
2571 }
2572 } else {
2573 jobs++;
2574 this.fileTimestampQueue.add(path, (err, entry) => {
2575 if (err) return invalidWithError(path, err);
2576 if (!checkExistence(path, toExistence(entry), existence)) {
2577 invalid();
2578 } else {
2579 jobDone();
2580 }
2581 });
2582 }
2583 }
2584 }
2585 if (snapshot.hasManagedItemInfo()) {
2586 const { managedItemInfo } = snapshot;
2587 this._statTestedEntries += managedItemInfo.size;
2588 for (const [path, info] of managedItemInfo) {
2589 const cache = this._managedItems.get(path);
2590 if (cache !== undefined) {
2591 if (!checkHash(path, cache, info)) {
2592 invalid();
2593 return;
2594 }
2595 } else {
2596 jobs++;
2597 this.managedItemQueue.add(path, (err, entry) => {
2598 if (err) return invalidWithError(path, err);
2599 if (!checkHash(path, entry, info)) {
2600 invalid();
2601 } else {
2602 jobDone();
2603 }
2604 });
2605 }
2606 }
2607 }
2608 jobDone();
2609
2610 // if there was an async action
2611 // try to join multiple concurrent request for this snapshot
2612 if (jobs > 0) {
2613 const callbacks = [callback];
2614 callback = (err, result) => {
2615 for (const callback of callbacks) callback(err, result);
2616 };
2617 this._snapshotCache.set(snapshot, callbacks);
2618 }
2619 }
2620
2621 _readFileTimestamp(path, callback) {
2622 this.fs.stat(path, (err, stat) => {
2623 if (err) {
2624 if (err.code === "ENOENT") {
2625 this._fileTimestamps.set(path, null);
2626 this._cachedDeprecatedFileTimestamps = undefined;
2627 return callback(null, null);
2628 }
2629 return callback(err);
2630 }
2631
2632 let ts;
2633 if (stat.isDirectory()) {
2634 ts = {
2635 safeTime: 0,
2636 timestamp: undefined
2637 };
2638 } else {
2639 const mtime = +stat.mtime;
2640
2641 if (mtime) applyMtime(mtime);
2642
2643 ts = {
2644 safeTime: mtime ? mtime + FS_ACCURACY : Infinity,
2645 timestamp: mtime
2646 };
2647 }
2648
2649 this._fileTimestamps.set(path, ts);
2650 this._cachedDeprecatedFileTimestamps = undefined;
2651
2652 callback(null, ts);
2653 });
2654 }
2655
2656 _readFileHash(path, callback) {
2657 this.fs.readFile(path, (err, content) => {
2658 if (err) {
2659 if (err.code === "EISDIR") {
2660 this._fileHashes.set(path, "directory");
2661 return callback(null, "directory");
2662 }
2663 if (err.code === "ENOENT") {
2664 this._fileHashes.set(path, null);
2665 return callback(null, null);
2666 }
2667 if (err.code === "ERR_FS_FILE_TOO_LARGE") {
2668 this.logger.warn(`Ignoring ${path} for hashing as it's very large`);
2669 this._fileHashes.set(path, "too large");
2670 return callback(null, "too large");
2671 }
2672 return callback(err);
2673 }
2674
2675 const hash = createHash("md4");
2676
2677 hash.update(content);
2678
2679 const digest = /** @type {string} */ (hash.digest("hex"));
2680
2681 this._fileHashes.set(path, digest);
2682
2683 callback(null, digest);
2684 });
2685 }
2686
2687 _getFileTimestampAndHash(path, callback) {
2688 const continueWithHash = hash => {
2689 const cache = this._fileTimestamps.get(path);
2690 if (cache !== undefined) {
2691 if (cache !== "ignore") {
2692 const result = {
2693 ...cache,
2694 hash
2695 };
2696 this._fileTshs.set(path, result);
2697 return callback(null, result);
2698 } else {
2699 this._fileTshs.set(path, hash);
2700 return callback(null, hash);
2701 }
2702 } else {
2703 this.fileTimestampQueue.add(path, (err, entry) => {
2704 if (err) {
2705 return callback(err);
2706 }
2707 const result = {
2708 ...entry,
2709 hash
2710 };
2711 this._fileTshs.set(path, result);
2712 return callback(null, result);
2713 });
2714 }
2715 };
2716
2717 const cache = this._fileHashes.get(path);
2718 if (cache !== undefined) {
2719 continueWithHash(cache);
2720 } else {
2721 this.fileHashQueue.add(path, (err, entry) => {
2722 if (err) {
2723 return callback(err);
2724 }
2725 continueWithHash(entry);
2726 });
2727 }
2728 }
2729
2730 _readContextTimestamp(path, callback) {
2731 this.fs.readdir(path, (err, _files) => {
2732 if (err) {
2733 if (err.code === "ENOENT") {
2734 this._contextTimestamps.set(path, null);
2735 this._cachedDeprecatedContextTimestamps = undefined;
2736 return callback(null, null);
2737 }
2738 return callback(err);
2739 }
2740 const files = /** @type {string[]} */ (_files)
2741 .map(file => file.normalize("NFC"))
2742 .filter(file => !/^\./.test(file))
2743 .sort();
2744 asyncLib.map(
2745 files,
2746 (file, callback) => {
2747 const child = join(this.fs, path, file);
2748 this.fs.stat(child, (err, stat) => {
2749 if (err) return callback(err);
2750
2751 for (const immutablePath of this.immutablePathsWithSlash) {
2752 if (path.startsWith(immutablePath)) {
2753 // ignore any immutable path for timestamping
2754 return callback(null, null);
2755 }
2756 }
2757 for (const managedPath of this.managedPathsWithSlash) {
2758 if (path.startsWith(managedPath)) {
2759 const managedItem = getManagedItem(managedPath, child);
2760 if (managedItem) {
2761 // construct timestampHash from managed info
2762 return this.managedItemQueue.add(managedItem, (err, info) => {
2763 if (err) return callback(err);
2764 return callback(null, {
2765 safeTime: 0,
2766 timestampHash: info
2767 });
2768 });
2769 }
2770 }
2771 }
2772
2773 if (stat.isFile()) {
2774 return this.getFileTimestamp(child, callback);
2775 }
2776 if (stat.isDirectory()) {
2777 this.contextTimestampQueue.increaseParallelism();
2778 this.getContextTimestamp(child, (err, tsEntry) => {
2779 this.contextTimestampQueue.decreaseParallelism();
2780 callback(err, tsEntry);
2781 });
2782 return;
2783 }
2784 callback(null, null);
2785 });
2786 },
2787 (err, tsEntries) => {
2788 if (err) return callback(err);
2789 const hash = createHash("md4");
2790
2791 for (const file of files) hash.update(file);
2792 let safeTime = 0;
2793 for (const entry of tsEntries) {
2794 if (!entry) {
2795 hash.update("n");
2796 continue;
2797 }
2798 if (entry.timestamp) {
2799 hash.update("f");
2800 hash.update(`${entry.timestamp}`);
2801 } else if (entry.timestampHash) {
2802 hash.update("d");
2803 hash.update(`${entry.timestampHash}`);
2804 }
2805 if (entry.safeTime) {
2806 safeTime = Math.max(safeTime, entry.safeTime);
2807 }
2808 }
2809
2810 const digest = /** @type {string} */ (hash.digest("hex"));
2811
2812 const result = {
2813 safeTime,
2814 timestampHash: digest
2815 };
2816
2817 this._contextTimestamps.set(path, result);
2818 this._cachedDeprecatedContextTimestamps = undefined;
2819
2820 callback(null, result);
2821 }
2822 );
2823 });
2824 }
2825
2826 _readContextHash(path, callback) {
2827 this.fs.readdir(path, (err, _files) => {
2828 if (err) {
2829 if (err.code === "ENOENT") {
2830 this._contextHashes.set(path, null);
2831 return callback(null, null);
2832 }
2833 return callback(err);
2834 }
2835 const files = /** @type {string[]} */ (_files)
2836 .map(file => file.normalize("NFC"))
2837 .filter(file => !/^\./.test(file))
2838 .sort();
2839 asyncLib.map(
2840 files,
2841 (file, callback) => {
2842 const child = join(this.fs, path, file);
2843 this.fs.stat(child, (err, stat) => {
2844 if (err) return callback(err);
2845
2846 for (const immutablePath of this.immutablePathsWithSlash) {
2847 if (path.startsWith(immutablePath)) {
2848 // ignore any immutable path for hashing
2849 return callback(null, "");
2850 }
2851 }
2852 for (const managedPath of this.managedPathsWithSlash) {
2853 if (path.startsWith(managedPath)) {
2854 const managedItem = getManagedItem(managedPath, child);
2855 if (managedItem) {
2856 // construct hash from managed info
2857 return this.managedItemQueue.add(managedItem, (err, info) => {
2858 if (err) return callback(err);
2859 callback(null, info || "");
2860 });
2861 }
2862 }
2863 }
2864
2865 if (stat.isFile()) {
2866 return this.getFileHash(child, (err, hash) => {
2867 callback(err, hash || "");
2868 });
2869 }
2870 if (stat.isDirectory()) {
2871 this.contextHashQueue.increaseParallelism();
2872 this.getContextHash(child, (err, hash) => {
2873 this.contextHashQueue.decreaseParallelism();
2874 callback(err, hash || "");
2875 });
2876 return;
2877 }
2878 callback(null, "");
2879 });
2880 },
2881 (err, fileHashes) => {
2882 if (err) return callback(err);
2883 const hash = createHash("md4");
2884
2885 for (const file of files) hash.update(file);
2886 for (const h of fileHashes) hash.update(h);
2887
2888 const digest = /** @type {string} */ (hash.digest("hex"));
2889
2890 this._contextHashes.set(path, digest);
2891
2892 callback(null, digest);
2893 }
2894 );
2895 });
2896 }
2897
2898 _getContextTimestampAndHash(path, callback) {
2899 const continueWithHash = hash => {
2900 const cache = this._contextTimestamps.get(path);
2901 if (cache !== undefined) {
2902 if (cache !== "ignore") {
2903 const result = {
2904 ...cache,
2905 hash
2906 };
2907 this._contextTshs.set(path, result);
2908 return callback(null, result);
2909 } else {
2910 this._contextTshs.set(path, hash);
2911 return callback(null, hash);
2912 }
2913 } else {
2914 this.contextTimestampQueue.add(path, (err, entry) => {
2915 if (err) {
2916 return callback(err);
2917 }
2918 const result = {
2919 ...entry,
2920 hash
2921 };
2922 this._contextTshs.set(path, result);
2923 return callback(null, result);
2924 });
2925 }
2926 };
2927
2928 const cache = this._contextHashes.get(path);
2929 if (cache !== undefined) {
2930 continueWithHash(cache);
2931 } else {
2932 this.contextHashQueue.add(path, (err, entry) => {
2933 if (err) {
2934 return callback(err);
2935 }
2936 continueWithHash(entry);
2937 });
2938 }
2939 }
2940
2941 _getManagedItemDirectoryInfo(path, callback) {
2942 this.fs.readdir(path, (err, elements) => {
2943 if (err) {
2944 if (err.code === "ENOENT" || err.code === "ENOTDIR") {
2945 return callback(null, EMPTY_SET);
2946 }
2947 return callback(err);
2948 }
2949 const set = new Set(
2950 /** @type {string[]} */ (elements).map(element =>
2951 join(this.fs, path, element)
2952 )
2953 );
2954 callback(null, set);
2955 });
2956 }
2957
2958 _getManagedItemInfo(path, callback) {
2959 const dir = dirname(this.fs, path);
2960 this.managedItemDirectoryQueue.add(dir, (err, elements) => {
2961 if (err) {
2962 return callback(err);
2963 }
2964 if (!elements.has(path)) {
2965 // file or directory doesn't exist
2966 this._managedItems.set(path, "missing");
2967 return callback(null, "missing");
2968 }
2969 // something exists
2970 // it may be a file or directory
2971 if (
2972 path.endsWith("node_modules") &&
2973 (path.endsWith("/node_modules") || path.endsWith("\\node_modules"))
2974 ) {
2975 // we are only interested in existence of this special directory
2976 this._managedItems.set(path, "exists");
2977 return callback(null, "exists");
2978 }
2979
2980 // we assume it's a directory, as files shouldn't occur in managed paths
2981 const packageJsonPath = join(this.fs, path, "package.json");
2982 this.fs.readFile(packageJsonPath, (err, content) => {
2983 if (err) {
2984 if (err.code === "ENOENT" || err.code === "ENOTDIR") {
2985 // no package.json or path is not a directory
2986 this.fs.readdir(path, (err, elements) => {
2987 if (
2988 !err &&
2989 elements.length === 1 &&
2990 elements[0] === "node_modules"
2991 ) {
2992 // This is only a grouping folder e. g. used by yarn
2993 // we are only interested in existence of this special directory
2994 this._managedItems.set(path, "nested");
2995 return callback(null, "nested");
2996 }
2997 const problem = `Managed item ${path} isn't a directory or doesn't contain a package.json`;
2998 this.logger.warn(problem);
2999 return callback(new Error(problem));
3000 });
3001 return;
3002 }
3003 return callback(err);
3004 }
3005 let data;
3006 try {
3007 data = JSON.parse(content.toString("utf-8"));
3008 } catch (e) {
3009 return callback(e);
3010 }
3011 const info = `${data.name || ""}@${data.version || ""}`;
3012 this._managedItems.set(path, info);
3013 callback(null, info);
3014 });
3015 });
3016 }
3017
3018 getDeprecatedFileTimestamps() {
3019 if (this._cachedDeprecatedFileTimestamps !== undefined)
3020 return this._cachedDeprecatedFileTimestamps;
3021 const map = new Map();
3022 for (const [path, info] of this._fileTimestamps) {
3023 if (info) map.set(path, typeof info === "object" ? info.safeTime : null);
3024 }
3025 return (this._cachedDeprecatedFileTimestamps = map);
3026 }
3027
3028 getDeprecatedContextTimestamps() {
3029 if (this._cachedDeprecatedContextTimestamps !== undefined)
3030 return this._cachedDeprecatedContextTimestamps;
3031 const map = new Map();
3032 for (const [path, info] of this._contextTimestamps) {
3033 if (info) map.set(path, typeof info === "object" ? info.safeTime : null);
3034 }
3035 return (this._cachedDeprecatedContextTimestamps = map);
3036 }
3037}
3038
3039module.exports = FileSystemInfo;
3040module.exports.Snapshot = Snapshot;
Note: See TracBrowser for help on using the repository browser.