webpack 5.48.0 → 5.51.1

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.

Potentially problematic release.


This version of webpack might be problematic. Click here for more details.

Files changed (50) hide show
  1. package/README.md +4 -16
  2. package/hot/only-dev-server.js +1 -1
  3. package/hot/poll.js +1 -1
  4. package/hot/signal.js +1 -1
  5. package/lib/CompatibilityPlugin.js +21 -4
  6. package/lib/Compilation.js +8 -3
  7. package/lib/EvalSourceMapDevToolPlugin.js +2 -2
  8. package/lib/ExternalModuleFactoryPlugin.js +1 -1
  9. package/lib/FileSystemInfo.js +665 -193
  10. package/lib/HotModuleReplacementPlugin.js +4 -4
  11. package/lib/Module.js +1 -0
  12. package/lib/MultiCompiler.js +0 -2
  13. package/lib/NormalModule.js +51 -20
  14. package/lib/NormalModuleFactory.js +137 -74
  15. package/lib/Parser.js +1 -0
  16. package/lib/RuntimeGlobals.js +5 -0
  17. package/lib/SourceMapDevToolPlugin.js +2 -2
  18. package/lib/WebpackOptionsApply.js +8 -0
  19. package/lib/asset/AssetModulesPlugin.js +0 -1
  20. package/lib/config/defaults.js +27 -6
  21. package/lib/config/normalization.js +6 -1
  22. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +10 -1
  23. package/lib/hmr/HotModuleReplacement.runtime.js +5 -1
  24. package/lib/index.js +0 -3
  25. package/lib/javascript/JavascriptParser.js +2 -0
  26. package/lib/library/ModuleLibraryPlugin.js +4 -0
  27. package/lib/node/ReadFileChunkLoadingRuntimeModule.js +7 -1
  28. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +2 -2
  29. package/lib/node/ReadFileCompileWasmPlugin.js +2 -1
  30. package/lib/node/RequireChunkLoadingRuntimeModule.js +7 -1
  31. package/lib/optimize/ConcatenatedModule.js +3 -3
  32. package/lib/optimize/SplitChunksPlugin.js +4 -4
  33. package/lib/schemes/HttpUriPlugin.js +942 -25
  34. package/lib/serialization/BinaryMiddleware.js +293 -267
  35. package/lib/util/fs.js +40 -0
  36. package/lib/util/identifier.js +26 -8
  37. package/lib/wasm-async/{AsyncWasmChunkLoadingRuntimeModule.js → AsyncWasmLoadingRuntimeModule.js} +3 -3
  38. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +18 -2
  39. package/lib/web/FetchCompileAsyncWasmPlugin.js +2 -2
  40. package/lib/web/FetchCompileWasmPlugin.js +2 -1
  41. package/lib/web/JsonpChunkLoadingRuntimeModule.js +21 -8
  42. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +7 -1
  43. package/package.json +1 -1
  44. package/schemas/WebpackOptions.check.js +1 -1
  45. package/schemas/WebpackOptions.json +43 -0
  46. package/schemas/plugins/schemes/HttpUriPlugin.check.d.ts +7 -0
  47. package/schemas/plugins/schemes/HttpUriPlugin.check.js +6 -0
  48. package/schemas/plugins/schemes/HttpUriPlugin.json +42 -0
  49. package/types.d.ts +110 -14
  50. package/lib/schemes/HttpsUriPlugin.js +0 -63
@@ -10,12 +10,13 @@ const asyncLib = require("neo-async");
10
10
  const AsyncQueue = require("./util/AsyncQueue");
11
11
  const StackedCacheMap = require("./util/StackedCacheMap");
12
12
  const createHash = require("./util/createHash");
13
- const { join, dirname, relative } = require("./util/fs");
13
+ const { join, dirname, relative, lstatReadlinkAbsolute } = require("./util/fs");
14
14
  const makeSerializable = require("./util/makeSerializable");
15
15
  const processAsyncTree = require("./util/processAsyncTree");
16
16
 
17
17
  /** @typedef {import("./WebpackError")} WebpackError */
18
18
  /** @typedef {import("./logging/Logger").Logger} Logger */
19
+ /** @typedef {import("./util/fs").IStats} IStats */
19
20
  /** @typedef {import("./util/fs").InputFileSystem} InputFileSystem */
20
21
 
21
22
  const supportsEsm = +process.versions.modules >= 83;
@@ -41,17 +42,52 @@ const INVALID = Symbol("invalid");
41
42
  * @typedef {Object} FileSystemInfoEntry
42
43
  * @property {number} safeTime
43
44
  * @property {number=} timestamp
45
+ */
46
+
47
+ /**
48
+ * @typedef {Object} ResolvedContextFileSystemInfoEntry
49
+ * @property {number} safeTime
50
+ * @property {string=} timestampHash
51
+ */
52
+
53
+ /**
54
+ * @typedef {Object} ContextFileSystemInfoEntry
55
+ * @property {number} safeTime
44
56
  * @property {string=} timestampHash
57
+ * @property {ResolvedContextFileSystemInfoEntry=} resolved
58
+ * @property {Set<string>=} symlinks
45
59
  */
46
60
 
47
61
  /**
48
62
  * @typedef {Object} TimestampAndHash
49
63
  * @property {number} safeTime
50
64
  * @property {number=} timestamp
65
+ * @property {string} hash
66
+ */
67
+
68
+ /**
69
+ * @typedef {Object} ResolvedContextTimestampAndHash
70
+ * @property {number} safeTime
51
71
  * @property {string=} timestampHash
52
72
  * @property {string} hash
53
73
  */
54
74
 
75
+ /**
76
+ * @typedef {Object} ContextTimestampAndHash
77
+ * @property {number} safeTime
78
+ * @property {string=} timestampHash
79
+ * @property {string} hash
80
+ * @property {ResolvedContextTimestampAndHash=} resolved
81
+ * @property {Set<string>=} symlinks
82
+ */
83
+
84
+ /**
85
+ * @typedef {Object} ContextHash
86
+ * @property {string} hash
87
+ * @property {string=} resolved
88
+ * @property {Set<string>=} symlinks
89
+ */
90
+
55
91
  /**
56
92
  * @typedef {Object} SnapshotOptimizationEntry
57
93
  * @property {Snapshot} snapshot
@@ -175,11 +211,11 @@ class Snapshot {
175
211
  this.fileHashes = undefined;
176
212
  /** @type {Map<string, TimestampAndHash | string> | undefined} */
177
213
  this.fileTshs = undefined;
178
- /** @type {Map<string, FileSystemInfoEntry> | undefined} */
214
+ /** @type {Map<string, ResolvedContextFileSystemInfoEntry> | undefined} */
179
215
  this.contextTimestamps = undefined;
180
216
  /** @type {Map<string, string> | undefined} */
181
217
  this.contextHashes = undefined;
182
- /** @type {Map<string, TimestampAndHash | string> | undefined} */
218
+ /** @type {Map<string, ResolvedContextTimestampAndHash> | undefined} */
183
219
  this.contextTshs = undefined;
184
220
  /** @type {Map<string, boolean> | undefined} */
185
221
  this.missingExistence = undefined;
@@ -771,11 +807,29 @@ const getManagedItem = (managedPath, path) => {
771
807
  };
772
808
 
773
809
  /**
774
- * @param {FileSystemInfoEntry} entry file system info entry
775
- * @returns {boolean} existence flag
810
+ * @template {ContextFileSystemInfoEntry | ContextTimestampAndHash} T
811
+ * @param {T | "ignore"} entry entry
812
+ * @returns {T["resolved"] | undefined} the resolved entry
776
813
  */
777
- const toExistence = entry => {
778
- return Boolean(entry);
814
+ const getResolvedTimestamp = entry => {
815
+ if (entry === "ignore") return undefined;
816
+ if (entry === null) return null;
817
+ if (entry.resolved !== undefined) return entry.resolved;
818
+ return entry.symlinks === undefined ? entry : undefined;
819
+ };
820
+
821
+ /**
822
+ * @param {ContextHash} entry entry
823
+ * @returns {string | undefined} the resolved entry
824
+ */
825
+ const getResolvedHash = entry => {
826
+ if (entry === null) return null;
827
+ if (entry.resolved !== undefined) return entry.resolved;
828
+ return entry.symlinks === undefined ? entry.hash : undefined;
829
+ };
830
+
831
+ const addAll = (source, target) => {
832
+ for (const key of source) target.add(key);
779
833
  };
780
834
 
781
835
  /**
@@ -860,11 +914,11 @@ class FileSystemInfo {
860
914
  this._fileHashes = new Map();
861
915
  /** @type {Map<string, TimestampAndHash | string>} */
862
916
  this._fileTshs = new Map();
863
- /** @type {StackedCacheMap<string, FileSystemInfoEntry | "ignore" | null>} */
917
+ /** @type {StackedCacheMap<string, ContextFileSystemInfoEntry | "ignore" | null>} */
864
918
  this._contextTimestamps = new StackedCacheMap();
865
- /** @type {Map<string, string>} */
919
+ /** @type {Map<string, ContextHash>} */
866
920
  this._contextHashes = new Map();
867
- /** @type {Map<string, TimestampAndHash | string>} */
921
+ /** @type {Map<string, ContextTimestampAndHash>} */
868
922
  this._contextTshs = new Map();
869
923
  /** @type {Map<string, string>} */
870
924
  this._managedItems = new Map();
@@ -880,18 +934,24 @@ class FileSystemInfo {
880
934
  parallelism: 10,
881
935
  processor: this._readFileHash.bind(this)
882
936
  });
883
- /** @type {AsyncQueue<string, string, FileSystemInfoEntry | null>} */
937
+ /** @type {AsyncQueue<string, string, ContextFileSystemInfoEntry | null>} */
884
938
  this.contextTimestampQueue = new AsyncQueue({
885
939
  name: "context timestamp",
886
940
  parallelism: 2,
887
941
  processor: this._readContextTimestamp.bind(this)
888
942
  });
889
- /** @type {AsyncQueue<string, string, string | null>} */
943
+ /** @type {AsyncQueue<string, string, ContextHash | null>} */
890
944
  this.contextHashQueue = new AsyncQueue({
891
945
  name: "context hash",
892
946
  parallelism: 2,
893
947
  processor: this._readContextHash.bind(this)
894
948
  });
949
+ /** @type {AsyncQueue<string, string, ContextTimestampAndHash | null>} */
950
+ this.contextTshQueue = new AsyncQueue({
951
+ name: "context hash and timestamp",
952
+ parallelism: 2,
953
+ processor: this._readContextTimestampAndHash.bind(this)
954
+ });
895
955
  /** @type {AsyncQueue<string, string, string | null>} */
896
956
  this.managedItemQueue = new AsyncQueue({
897
957
  name: "managed item info",
@@ -1093,13 +1153,34 @@ class FileSystemInfo {
1093
1153
 
1094
1154
  /**
1095
1155
  * @param {string} path context path
1096
- * @param {function(WebpackError=, (FileSystemInfoEntry | "ignore" | null)=): void} callback callback function
1156
+ * @param {function(WebpackError=, (ResolvedContextFileSystemInfoEntry | "ignore" | null)=): void} callback callback function
1097
1157
  * @returns {void}
1098
1158
  */
1099
1159
  getContextTimestamp(path, callback) {
1160
+ const cache = this._contextTimestamps.get(path);
1161
+ if (cache !== undefined) {
1162
+ const resolved = getResolvedTimestamp(cache);
1163
+ if (resolved !== undefined) return callback(null, resolved);
1164
+ this._resolveContextTimestamp(cache, callback);
1165
+ }
1166
+ this.contextTimestampQueue.add(path, (err, entry) => {
1167
+ const resolved = getResolvedTimestamp(entry);
1168
+ if (resolved !== undefined) return callback(null, resolved);
1169
+ this._resolveContextTimestamp(entry, callback);
1170
+ });
1171
+ }
1172
+
1173
+ /**
1174
+ * @param {string} path context path
1175
+ * @param {function(WebpackError=, (ContextFileSystemInfoEntry | "ignore" | null)=): void} callback callback function
1176
+ * @returns {void}
1177
+ */
1178
+ _getUnresolvedContextTimestamp(path, callback) {
1100
1179
  const cache = this._contextTimestamps.get(path);
1101
1180
  if (cache !== undefined) return callback(null, cache);
1102
- this.contextTimestampQueue.add(path, callback);
1181
+ this.contextTimestampQueue.add(path, (err, entry) => {
1182
+ return callback(null, entry);
1183
+ });
1103
1184
  }
1104
1185
 
1105
1186
  /**
@@ -1120,8 +1201,61 @@ class FileSystemInfo {
1120
1201
  */
1121
1202
  getContextHash(path, callback) {
1122
1203
  const cache = this._contextHashes.get(path);
1204
+ if (cache !== undefined) {
1205
+ const resolved = getResolvedHash(cache);
1206
+ if (resolved !== undefined) return callback(null, resolved);
1207
+ this._resolveContextHash(cache, callback);
1208
+ }
1209
+ this.contextHashQueue.add(path, (err, entry) => {
1210
+ const resolved = getResolvedHash(entry);
1211
+ if (resolved !== undefined) return callback(null, resolved);
1212
+ this._resolveContextHash(entry, callback);
1213
+ });
1214
+ }
1215
+
1216
+ /**
1217
+ * @param {string} path context path
1218
+ * @param {function(WebpackError=, ContextHash=): void} callback callback function
1219
+ * @returns {void}
1220
+ */
1221
+ _getUnresolvedContextHash(path, callback) {
1222
+ const cache = this._contextHashes.get(path);
1223
+ if (cache !== undefined) return callback(null, cache);
1224
+ this.contextHashQueue.add(path, (err, entry) => {
1225
+ return callback(null, entry);
1226
+ });
1227
+ }
1228
+
1229
+ /**
1230
+ * @param {string} path context path
1231
+ * @param {function(WebpackError=, ResolvedContextTimestampAndHash=): void} callback callback function
1232
+ * @returns {void}
1233
+ */
1234
+ getContextTsh(path, callback) {
1235
+ const cache = this._contextTshs.get(path);
1236
+ if (cache !== undefined) {
1237
+ const resolved = getResolvedTimestamp(cache);
1238
+ if (resolved !== undefined) return callback(null, resolved);
1239
+ this._resolveContextTsh(cache, callback);
1240
+ }
1241
+ this.contextTshQueue.add(path, (err, entry) => {
1242
+ const resolved = getResolvedTimestamp(entry);
1243
+ if (resolved !== undefined) return callback(null, resolved);
1244
+ this._resolveContextTsh(entry, callback);
1245
+ });
1246
+ }
1247
+
1248
+ /**
1249
+ * @param {string} path context path
1250
+ * @param {function(WebpackError=, ContextTimestampAndHash=): void} callback callback function
1251
+ * @returns {void}
1252
+ */
1253
+ _getUnresolvedContextTsh(path, callback) {
1254
+ const cache = this._contextTshs.get(path);
1123
1255
  if (cache !== undefined) return callback(null, cache);
1124
- this.contextHashQueue.add(path, callback);
1256
+ this.contextTshQueue.add(path, (err, entry) => {
1257
+ return callback(null, entry);
1258
+ });
1125
1259
  }
1126
1260
 
1127
1261
  _createBuildDependenciesResolvers() {
@@ -2007,11 +2141,15 @@ class FileSystemInfo {
2007
2141
  );
2008
2142
  for (const path of capturedDirectories) {
2009
2143
  const cache = this._contextTshs.get(path);
2010
- if (cache !== undefined) {
2011
- contextTshs.set(path, cache);
2144
+ let resolved;
2145
+ if (
2146
+ cache !== undefined &&
2147
+ (resolved = getResolvedTimestamp(cache)) !== undefined
2148
+ ) {
2149
+ contextTshs.set(path, resolved);
2012
2150
  } else {
2013
2151
  jobs++;
2014
- this._getContextTimestampAndHash(path, (err, entry) => {
2152
+ const callback = (err, entry) => {
2015
2153
  if (err) {
2016
2154
  if (this.logger) {
2017
2155
  this.logger.debug(
@@ -2023,7 +2161,12 @@ class FileSystemInfo {
2023
2161
  contextTshs.set(path, entry);
2024
2162
  jobDone();
2025
2163
  }
2026
- });
2164
+ };
2165
+ if (cache !== undefined) {
2166
+ this._resolveContextTsh(cache, callback);
2167
+ } else {
2168
+ this.getContextTsh(path, callback);
2169
+ }
2027
2170
  }
2028
2171
  }
2029
2172
  break;
@@ -2035,11 +2178,15 @@ class FileSystemInfo {
2035
2178
  );
2036
2179
  for (const path of capturedDirectories) {
2037
2180
  const cache = this._contextHashes.get(path);
2038
- if (cache !== undefined) {
2039
- contextHashes.set(path, cache);
2181
+ let resolved;
2182
+ if (
2183
+ cache !== undefined &&
2184
+ (resolved = getResolvedHash(cache)) !== undefined
2185
+ ) {
2186
+ contextHashes.set(path, resolved);
2040
2187
  } else {
2041
2188
  jobs++;
2042
- this.contextHashQueue.add(path, (err, entry) => {
2189
+ const callback = (err, entry) => {
2043
2190
  if (err) {
2044
2191
  if (this.logger) {
2045
2192
  this.logger.debug(
@@ -2051,7 +2198,12 @@ class FileSystemInfo {
2051
2198
  contextHashes.set(path, entry);
2052
2199
  jobDone();
2053
2200
  }
2054
- });
2201
+ };
2202
+ if (cache !== undefined) {
2203
+ this._resolveContextHash(cache, callback);
2204
+ } else {
2205
+ this.getContextHash(path, callback);
2206
+ }
2055
2207
  }
2056
2208
  }
2057
2209
  break;
@@ -2064,13 +2216,15 @@ class FileSystemInfo {
2064
2216
  );
2065
2217
  for (const path of capturedDirectories) {
2066
2218
  const cache = this._contextTimestamps.get(path);
2067
- if (cache !== undefined) {
2068
- if (cache !== "ignore") {
2069
- contextTimestamps.set(path, cache);
2070
- }
2071
- } else {
2219
+ let resolved;
2220
+ if (
2221
+ cache !== undefined &&
2222
+ (resolved = getResolvedTimestamp(cache)) !== undefined
2223
+ ) {
2224
+ contextTimestamps.set(path, resolved);
2225
+ } else if (cache !== "ignore") {
2072
2226
  jobs++;
2073
- this.contextTimestampQueue.add(path, (err, entry) => {
2227
+ const callback = (err, entry) => {
2074
2228
  if (err) {
2075
2229
  if (this.logger) {
2076
2230
  this.logger.debug(
@@ -2082,7 +2236,12 @@ class FileSystemInfo {
2082
2236
  contextTimestamps.set(path, entry);
2083
2237
  jobDone();
2084
2238
  }
2085
- });
2239
+ };
2240
+ if (cache !== undefined) {
2241
+ this._resolveContextTimestamp(cache, callback);
2242
+ } else {
2243
+ this.getContextTimestamp(path, callback);
2244
+ }
2086
2245
  }
2087
2246
  }
2088
2247
  break;
@@ -2099,7 +2258,7 @@ class FileSystemInfo {
2099
2258
  const cache = this._fileTimestamps.get(path);
2100
2259
  if (cache !== undefined) {
2101
2260
  if (cache !== "ignore") {
2102
- missingExistence.set(path, toExistence(cache));
2261
+ missingExistence.set(path, Boolean(cache));
2103
2262
  }
2104
2263
  } else {
2105
2264
  jobs++;
@@ -2112,7 +2271,7 @@ class FileSystemInfo {
2112
2271
  }
2113
2272
  jobError();
2114
2273
  } else {
2115
- missingExistence.set(path, toExistence(entry));
2274
+ missingExistence.set(path, Boolean(entry));
2116
2275
  jobDone();
2117
2276
  }
2118
2277
  });
@@ -2321,17 +2480,7 @@ class FileSystemInfo {
2321
2480
  */
2322
2481
  const checkFile = (path, current, snap, log = true) => {
2323
2482
  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
- }
2483
+ if (!checkExistence(path, Boolean(current), Boolean(snap))) return false;
2335
2484
  if (current) {
2336
2485
  // For existing items only
2337
2486
  if (typeof startTime === "number" && current.safeTime > startTime) {
@@ -2363,6 +2512,34 @@ class FileSystemInfo {
2363
2512
  }
2364
2513
  return false;
2365
2514
  }
2515
+ }
2516
+ return true;
2517
+ };
2518
+ /**
2519
+ * @param {string} path file path
2520
+ * @param {ResolvedContextFileSystemInfoEntry} current current entry
2521
+ * @param {ResolvedContextFileSystemInfoEntry} snap entry from snapshot
2522
+ * @param {boolean} log log reason
2523
+ * @returns {boolean} true, if ok
2524
+ */
2525
+ const checkContext = (path, current, snap, log = true) => {
2526
+ if (current === snap) return true;
2527
+ if (!checkExistence(path, Boolean(current), Boolean(snap))) return false;
2528
+ if (current) {
2529
+ // For existing items only
2530
+ if (typeof startTime === "number" && current.safeTime > startTime) {
2531
+ // If a change happened after starting reading the item
2532
+ // this may no longer be valid
2533
+ if (log && this._remainingLogs > 0) {
2534
+ this._log(
2535
+ path,
2536
+ `it may have changed (%d) after the start time of the snapshot (%d)`,
2537
+ current.safeTime,
2538
+ startTime
2539
+ );
2540
+ }
2541
+ return false;
2542
+ }
2366
2543
  if (
2367
2544
  snap.timestampHash !== undefined &&
2368
2545
  current.timestampHash !== snap.timestampHash
@@ -2487,41 +2664,59 @@ class FileSystemInfo {
2487
2664
  this._statTestedEntries += contextTimestamps.size;
2488
2665
  for (const [path, ts] of contextTimestamps) {
2489
2666
  const cache = this._contextTimestamps.get(path);
2490
- if (cache !== undefined) {
2491
- if (cache !== "ignore" && !checkFile(path, cache, ts)) {
2667
+ let resolved;
2668
+ if (
2669
+ cache !== undefined &&
2670
+ (resolved = getResolvedTimestamp(cache)) !== undefined
2671
+ ) {
2672
+ if (!checkContext(path, resolved, ts)) {
2492
2673
  invalid();
2493
2674
  return;
2494
2675
  }
2495
- } else {
2676
+ } else if (cache !== "ignore") {
2496
2677
  jobs++;
2497
- this.contextTimestampQueue.add(path, (err, entry) => {
2678
+ const callback = (err, entry) => {
2498
2679
  if (err) return invalidWithError(path, err);
2499
- if (!checkFile(path, entry, ts)) {
2680
+ if (!checkContext(path, entry, ts)) {
2500
2681
  invalid();
2501
2682
  } else {
2502
2683
  jobDone();
2503
2684
  }
2504
- });
2685
+ };
2686
+ if (cache !== undefined) {
2687
+ this._resolveContextTimestamp(cache, callback);
2688
+ } else {
2689
+ this.getContextTimestamp(path, callback);
2690
+ }
2505
2691
  }
2506
2692
  }
2507
2693
  }
2508
2694
  const processContextHashSnapshot = (path, hash) => {
2509
2695
  const cache = this._contextHashes.get(path);
2510
- if (cache !== undefined) {
2511
- if (cache !== "ignore" && !checkHash(path, cache, hash)) {
2696
+ let resolved;
2697
+ if (
2698
+ cache !== undefined &&
2699
+ (resolved = getResolvedHash(cache)) !== undefined
2700
+ ) {
2701
+ if (!checkHash(path, resolved, hash)) {
2512
2702
  invalid();
2513
2703
  return;
2514
2704
  }
2515
2705
  } else {
2516
2706
  jobs++;
2517
- this.contextHashQueue.add(path, (err, entry) => {
2707
+ const callback = (err, entry) => {
2518
2708
  if (err) return invalidWithError(path, err);
2519
2709
  if (!checkHash(path, entry, hash)) {
2520
2710
  invalid();
2521
2711
  } else {
2522
2712
  jobDone();
2523
2713
  }
2524
- });
2714
+ };
2715
+ if (cache !== undefined) {
2716
+ this._resolveContextHash(cache, callback);
2717
+ } else {
2718
+ this.getContextHash(path, callback);
2719
+ }
2525
2720
  }
2526
2721
  };
2527
2722
  if (snapshot.hasContextHashes()) {
@@ -2539,19 +2734,28 @@ class FileSystemInfo {
2539
2734
  processContextHashSnapshot(path, tsh);
2540
2735
  } else {
2541
2736
  const cache = this._contextTimestamps.get(path);
2542
- if (cache !== undefined) {
2543
- if (cache === "ignore" || !checkFile(path, cache, tsh, false)) {
2737
+ let resolved;
2738
+ if (
2739
+ cache !== undefined &&
2740
+ (resolved = getResolvedTimestamp(cache)) !== undefined
2741
+ ) {
2742
+ if (!checkContext(path, resolved, tsh, false)) {
2544
2743
  processContextHashSnapshot(path, tsh.hash);
2545
2744
  }
2546
- } else {
2745
+ } else if (cache !== "ignore") {
2547
2746
  jobs++;
2548
- this.contextTimestampQueue.add(path, (err, entry) => {
2747
+ const callback = (err, entry) => {
2549
2748
  if (err) return invalidWithError(path, err);
2550
- if (!checkFile(path, entry, tsh, false)) {
2749
+ if (!checkContext(path, entry, tsh, false)) {
2551
2750
  processContextHashSnapshot(path, tsh.hash);
2552
2751
  }
2553
2752
  jobDone();
2554
- });
2753
+ };
2754
+ if (cache !== undefined) {
2755
+ this._resolveContextTsh(cache, callback);
2756
+ } else {
2757
+ this.getContextTsh(path, callback);
2758
+ }
2555
2759
  }
2556
2760
  }
2557
2761
  }
@@ -2564,7 +2768,7 @@ class FileSystemInfo {
2564
2768
  if (cache !== undefined) {
2565
2769
  if (
2566
2770
  cache !== "ignore" &&
2567
- !checkExistence(path, toExistence(cache), existence)
2771
+ !checkExistence(path, Boolean(cache), Boolean(existence))
2568
2772
  ) {
2569
2773
  invalid();
2570
2774
  return;
@@ -2573,7 +2777,7 @@ class FileSystemInfo {
2573
2777
  jobs++;
2574
2778
  this.fileTimestampQueue.add(path, (err, entry) => {
2575
2779
  if (err) return invalidWithError(path, err);
2576
- if (!checkExistence(path, toExistence(entry), existence)) {
2780
+ if (!checkExistence(path, Boolean(entry), Boolean(existence))) {
2577
2781
  invalid();
2578
2782
  } else {
2579
2783
  jobDone();
@@ -2727,12 +2931,34 @@ class FileSystemInfo {
2727
2931
  }
2728
2932
  }
2729
2933
 
2730
- _readContextTimestamp(path, callback) {
2934
+ /**
2935
+ * @template T
2936
+ * @template ItemType
2937
+ * @param {Object} options options
2938
+ * @param {string} options.path path
2939
+ * @param {function(string): ItemType} options.fromImmutablePath called when context item is an immutable path
2940
+ * @param {function(string): ItemType} options.fromManagedItem called when context item is a managed path
2941
+ * @param {function(string, string, function(Error=, ItemType=): void): void} options.fromSymlink called when context item is a symlink
2942
+ * @param {function(string, IStats, function(Error=, ItemType=): void): void} options.fromFile called when context item is a file
2943
+ * @param {function(string, IStats, function(Error=, ItemType=): void): void} options.fromDirectory called when context item is a directory
2944
+ * @param {function(string[], ItemType[]): T} options.reduce called from all context items
2945
+ * @param {function(Error=, (T)=): void} callback callback
2946
+ */
2947
+ _readContext(
2948
+ {
2949
+ path,
2950
+ fromImmutablePath,
2951
+ fromManagedItem,
2952
+ fromSymlink,
2953
+ fromFile,
2954
+ fromDirectory,
2955
+ reduce
2956
+ },
2957
+ callback
2958
+ ) {
2731
2959
  this.fs.readdir(path, (err, _files) => {
2732
2960
  if (err) {
2733
2961
  if (err.code === "ENOENT") {
2734
- this._contextTimestamps.set(path, null);
2735
- this._cachedDeprecatedContextTimestamps = undefined;
2736
2962
  return callback(null, null);
2737
2963
  }
2738
2964
  return callback(err);
@@ -2745,47 +2971,94 @@ class FileSystemInfo {
2745
2971
  files,
2746
2972
  (file, callback) => {
2747
2973
  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
- }
2974
+ for (const immutablePath of this.immutablePathsWithSlash) {
2975
+ if (path.startsWith(immutablePath)) {
2976
+ // ignore any immutable path for timestamping
2977
+ return callback(null, fromImmutablePath(immutablePath));
2756
2978
  }
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
- }
2979
+ }
2980
+ for (const managedPath of this.managedPathsWithSlash) {
2981
+ if (path.startsWith(managedPath)) {
2982
+ const managedItem = getManagedItem(managedPath, child);
2983
+ if (managedItem) {
2984
+ // construct timestampHash from managed info
2985
+ return this.managedItemQueue.add(managedItem, (err, info) => {
2986
+ if (err) return callback(err);
2987
+ return callback(null, fromManagedItem(info));
2988
+ });
2770
2989
  }
2771
2990
  }
2991
+ }
2992
+
2993
+ lstatReadlinkAbsolute(this.fs, child, (err, stat) => {
2994
+ if (err) return callback(err);
2995
+
2996
+ if (typeof stat === "string") {
2997
+ return fromSymlink(child, stat, callback);
2998
+ }
2772
2999
 
2773
3000
  if (stat.isFile()) {
2774
- return this.getFileTimestamp(child, callback);
3001
+ return fromFile(child, stat, callback);
2775
3002
  }
2776
3003
  if (stat.isDirectory()) {
2777
- this.contextTimestampQueue.increaseParallelism();
2778
- this.getContextTimestamp(child, (err, tsEntry) => {
2779
- this.contextTimestampQueue.decreaseParallelism();
2780
- callback(err, tsEntry);
2781
- });
2782
- return;
3004
+ return fromDirectory(child, stat, callback);
2783
3005
  }
2784
3006
  callback(null, null);
2785
3007
  });
2786
3008
  },
2787
- (err, tsEntries) => {
3009
+ (err, results) => {
2788
3010
  if (err) return callback(err);
3011
+ const result = reduce(files, results);
3012
+ callback(null, result);
3013
+ }
3014
+ );
3015
+ });
3016
+ }
3017
+
3018
+ _readContextTimestamp(path, callback) {
3019
+ this._readContext(
3020
+ {
3021
+ path,
3022
+ fromImmutablePath: () => null,
3023
+ fromManagedItem: info => ({
3024
+ safeTime: 0,
3025
+ timestampHash: info
3026
+ }),
3027
+ fromSymlink: (file, target, callback) => {
3028
+ callback(null, {
3029
+ timestampHash: target,
3030
+ symlinks: new Set([target])
3031
+ });
3032
+ },
3033
+ fromFile: (file, stat, callback) => {
3034
+ // Prefer the cached value over our new stat to report consistent results
3035
+ const cache = this._fileTimestamps.get(file);
3036
+ if (cache !== undefined)
3037
+ return callback(null, cache === "ignore" ? null : cache);
3038
+
3039
+ const mtime = +stat.mtime;
3040
+
3041
+ if (mtime) applyMtime(mtime);
3042
+
3043
+ const ts = {
3044
+ safeTime: mtime ? mtime + FS_ACCURACY : Infinity,
3045
+ timestamp: mtime
3046
+ };
3047
+
3048
+ this._fileTimestamps.set(file, ts);
3049
+ this._cachedDeprecatedFileTimestamps = undefined;
3050
+ callback(null, ts);
3051
+ },
3052
+ fromDirectory: (directory, stat, callback) => {
3053
+ this.contextTimestampQueue.increaseParallelism();
3054
+ this._getUnresolvedContextTimestamp(directory, (err, tsEntry) => {
3055
+ this.contextTimestampQueue.decreaseParallelism();
3056
+ callback(err, tsEntry);
3057
+ });
3058
+ },
3059
+ reduce: (files, tsEntries) => {
3060
+ let symlinks = undefined;
3061
+
2789
3062
  const hash = createHash("md4");
2790
3063
 
2791
3064
  for (const file of files) hash.update(file);
@@ -2802,6 +3075,10 @@ class FileSystemInfo {
2802
3075
  hash.update("d");
2803
3076
  hash.update(`${entry.timestampHash}`);
2804
3077
  }
3078
+ if (entry.symlinks !== undefined) {
3079
+ if (symlinks === undefined) symlinks = new Set();
3080
+ addAll(entry.symlinks, symlinks);
3081
+ }
2805
3082
  if (entry.safeTime) {
2806
3083
  safeTime = Math.max(safeTime, entry.safeTime);
2807
3084
  }
@@ -2813,131 +3090,326 @@ class FileSystemInfo {
2813
3090
  safeTime,
2814
3091
  timestampHash: digest
2815
3092
  };
2816
-
2817
- this._contextTimestamps.set(path, result);
2818
- this._cachedDeprecatedContextTimestamps = undefined;
2819
-
2820
- callback(null, result);
3093
+ if (symlinks) result.symlinks = symlinks;
3094
+ return result;
2821
3095
  }
2822
- );
2823
- });
2824
- }
3096
+ },
3097
+ (err, result) => {
3098
+ if (err) return callback(err);
3099
+ this._contextTimestamps.set(path, result);
3100
+ this._cachedDeprecatedContextTimestamps = undefined;
2825
3101
 
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);
3102
+ callback(null, result);
2834
3103
  }
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);
3104
+ );
3105
+ }
2845
3106
 
2846
- for (const immutablePath of this.immutablePathsWithSlash) {
2847
- if (path.startsWith(immutablePath)) {
2848
- // ignore any immutable path for hashing
2849
- return callback(null, "");
2850
- }
3107
+ _resolveContextTimestamp(entry, callback) {
3108
+ const hashes = [];
3109
+ let safeTime = 0;
3110
+ processAsyncTree(
3111
+ entry.symlinks,
3112
+ 10,
3113
+ (target, push, callback) => {
3114
+ this._getUnresolvedContextTimestamp(target, (err, entry) => {
3115
+ if (err) return callback(err);
3116
+ if (entry && entry !== "ignore") {
3117
+ hashes.push(entry.timestampHash);
3118
+ if (entry.safeTime) {
3119
+ safeTime = Math.max(safeTime, entry.safeTime);
2851
3120
  }
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
- }
3121
+ if (entry.symlinks !== undefined) {
3122
+ for (const target of entry.symlinks) push(target);
2863
3123
  }
3124
+ }
3125
+ callback();
3126
+ });
3127
+ },
3128
+ err => {
3129
+ if (err) return callback(err);
3130
+ const hash = createHash("md4");
3131
+ hash.update(entry.timestampHash);
3132
+ if (entry.safeTime) {
3133
+ safeTime = Math.max(safeTime, entry.safeTime);
3134
+ }
3135
+ hashes.sort();
3136
+ for (const h of hashes) {
3137
+ hash.update(h);
3138
+ }
3139
+ callback(
3140
+ null,
3141
+ (entry.resolved = {
3142
+ safeTime,
3143
+ timestampHash: /** @type {string} */ (hash.digest("hex"))
3144
+ })
3145
+ );
3146
+ }
3147
+ );
3148
+ }
2864
3149
 
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, "");
3150
+ _readContextHash(path, callback) {
3151
+ this._readContext(
3152
+ {
3153
+ path,
3154
+ fromImmutablePath: () => "",
3155
+ fromManagedItem: info => info || "",
3156
+ fromSymlink: (file, target, callback) => {
3157
+ callback(null, {
3158
+ hash: target,
3159
+ symlinks: new Set([target])
2879
3160
  });
2880
3161
  },
2881
- (err, fileHashes) => {
2882
- if (err) return callback(err);
3162
+ fromFile: (file, stat, callback) =>
3163
+ this.getFileHash(file, (err, hash) => {
3164
+ callback(err, hash || "");
3165
+ }),
3166
+ fromDirectory: (directory, stat, callback) => {
3167
+ this.contextHashQueue.increaseParallelism();
3168
+ this._getUnresolvedContextHash(directory, (err, hash) => {
3169
+ this.contextHashQueue.decreaseParallelism();
3170
+ callback(err, hash || "");
3171
+ });
3172
+ },
3173
+ /**
3174
+ * @param {string[]} files files
3175
+ * @param {(string | ContextHash)[]} fileHashes hashes
3176
+ * @returns {ContextHash} reduced hash
3177
+ */
3178
+ reduce: (files, fileHashes) => {
3179
+ let symlinks = undefined;
2883
3180
  const hash = createHash("md4");
2884
3181
 
2885
3182
  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);
3183
+ for (const entry of fileHashes) {
3184
+ if (typeof entry === "string") {
3185
+ hash.update(entry);
3186
+ } else {
3187
+ hash.update(entry.hash);
3188
+ if (entry.symlinks) {
3189
+ if (symlinks === undefined) symlinks = new Set();
3190
+ addAll(entry.symlinks, symlinks);
3191
+ }
3192
+ }
3193
+ }
2891
3194
 
2892
- callback(null, digest);
3195
+ const result = {
3196
+ hash: /** @type {string} */ (hash.digest("hex"))
3197
+ };
3198
+ if (symlinks) result.symlinks = symlinks;
3199
+ return result;
2893
3200
  }
2894
- );
2895
- });
3201
+ },
3202
+ (err, result) => {
3203
+ if (err) return callback(err);
3204
+ this._contextHashes.set(path, result);
3205
+ return callback(null, result);
3206
+ }
3207
+ );
2896
3208
  }
2897
3209
 
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);
3210
+ _resolveContextHash(entry, callback) {
3211
+ const hashes = [];
3212
+ processAsyncTree(
3213
+ entry.symlinks,
3214
+ 10,
3215
+ (target, push, callback) => {
3216
+ this._getUnresolvedContextHash(target, (err, hash) => {
3217
+ if (err) return callback(err);
3218
+ if (hash) {
3219
+ hashes.push(hash.hash);
3220
+ if (hash.symlinks !== undefined) {
3221
+ for (const target of hash.symlinks) push(target);
3222
+ }
3223
+ }
3224
+ callback();
3225
+ });
3226
+ },
3227
+ err => {
3228
+ if (err) return callback(err);
3229
+ const hash = createHash("md4");
3230
+ hash.update(entry.hash);
3231
+ hashes.sort();
3232
+ for (const h of hashes) {
3233
+ hash.update(h);
2912
3234
  }
3235
+ callback(
3236
+ null,
3237
+ (entry.resolved = /** @type {string} */ (hash.digest("hex")))
3238
+ );
3239
+ }
3240
+ );
3241
+ }
3242
+
3243
+ _readContextTimestampAndHash(path, callback) {
3244
+ const finalize = (timestamp, hash) => {
3245
+ const result =
3246
+ timestamp === "ignore"
3247
+ ? hash
3248
+ : {
3249
+ ...timestamp,
3250
+ ...hash
3251
+ };
3252
+ this._contextTshs.set(path, result);
3253
+ callback(null, result);
3254
+ };
3255
+ const cachedHash = this._contextHashes.get(path);
3256
+ const cachedTimestamp = this._contextTimestamps.get(path);
3257
+ if (cachedHash !== undefined) {
3258
+ if (cachedTimestamp !== undefined) {
3259
+ finalize(cachedTimestamp, cachedHash);
2913
3260
  } else {
2914
3261
  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);
3262
+ if (err) return callback(err);
3263
+ finalize(entry, cachedHash);
2924
3264
  });
2925
3265
  }
2926
- };
2927
-
2928
- const cache = this._contextHashes.get(path);
2929
- if (cache !== undefined) {
2930
- continueWithHash(cache);
2931
3266
  } else {
2932
- this.contextHashQueue.add(path, (err, entry) => {
2933
- if (err) {
2934
- return callback(err);
2935
- }
2936
- continueWithHash(entry);
2937
- });
3267
+ if (cachedTimestamp !== undefined) {
3268
+ this.contextHashQueue.add(path, (err, entry) => {
3269
+ if (err) return callback(err);
3270
+ finalize(cachedTimestamp, entry);
3271
+ });
3272
+ } else {
3273
+ this._readContext(
3274
+ {
3275
+ path,
3276
+ fromImmutablePath: () => null,
3277
+ fromManagedItem: info => ({
3278
+ safeTime: 0,
3279
+ timestampHash: info,
3280
+ hash: info || ""
3281
+ }),
3282
+ fromSymlink: (fle, target, callback) => {
3283
+ callback(null, {
3284
+ timestampHash: target,
3285
+ hash: target,
3286
+ symlinks: new Set([target])
3287
+ });
3288
+ },
3289
+ fromFile: (file, stat, callback) => {
3290
+ this._getFileTimestampAndHash(file, callback);
3291
+ },
3292
+ fromDirectory: (directory, stat, callback) => {
3293
+ this.contextTshQueue.increaseParallelism();
3294
+ this.contextTshQueue.add(directory, (err, result) => {
3295
+ this.contextTshQueue.decreaseParallelism();
3296
+ callback(err, result);
3297
+ });
3298
+ },
3299
+ /**
3300
+ * @param {string[]} files files
3301
+ * @param {(Partial<TimestampAndHash> & Partial<ContextTimestampAndHash> | string | null)[]} results results
3302
+ * @returns {ContextTimestampAndHash} tsh
3303
+ */
3304
+ reduce: (files, results) => {
3305
+ let symlinks = undefined;
3306
+
3307
+ const tsHash = createHash("md4");
3308
+ const hash = createHash("md4");
3309
+
3310
+ for (const file of files) {
3311
+ tsHash.update(file);
3312
+ hash.update(file);
3313
+ }
3314
+ let safeTime = 0;
3315
+ for (const entry of results) {
3316
+ if (!entry) {
3317
+ tsHash.update("n");
3318
+ continue;
3319
+ }
3320
+ if (typeof entry === "string") {
3321
+ tsHash.update("n");
3322
+ hash.update(entry);
3323
+ continue;
3324
+ }
3325
+ if (entry.timestamp) {
3326
+ tsHash.update("f");
3327
+ tsHash.update(`${entry.timestamp}`);
3328
+ } else if (entry.timestampHash) {
3329
+ tsHash.update("d");
3330
+ tsHash.update(`${entry.timestampHash}`);
3331
+ }
3332
+ if (entry.symlinks !== undefined) {
3333
+ if (symlinks === undefined) symlinks = new Set();
3334
+ addAll(entry.symlinks, symlinks);
3335
+ }
3336
+ if (entry.safeTime) {
3337
+ safeTime = Math.max(safeTime, entry.safeTime);
3338
+ }
3339
+ hash.update(entry.hash);
3340
+ }
3341
+
3342
+ const result = {
3343
+ safeTime,
3344
+ timestampHash: /** @type {string} */ (tsHash.digest("hex")),
3345
+ hash: /** @type {string} */ (hash.digest("hex"))
3346
+ };
3347
+ if (symlinks) result.symlinks = symlinks;
3348
+ return result;
3349
+ }
3350
+ },
3351
+ (err, result) => {
3352
+ if (err) return callback(err);
3353
+ this._contextTshs.set(path, result);
3354
+ return callback(null, result);
3355
+ }
3356
+ );
3357
+ }
2938
3358
  }
2939
3359
  }
2940
3360
 
3361
+ _resolveContextTsh(entry, callback) {
3362
+ const hashes = [];
3363
+ const tsHashes = [];
3364
+ let safeTime = 0;
3365
+ processAsyncTree(
3366
+ entry.symlinks,
3367
+ 10,
3368
+ (target, push, callback) => {
3369
+ this._getUnresolvedContextTsh(target, (err, entry) => {
3370
+ if (err) return callback(err);
3371
+ if (entry) {
3372
+ hashes.push(entry.hash);
3373
+ if (entry.timestampHash) tsHashes.push(entry.timestampHash);
3374
+ if (entry.safeTime) {
3375
+ safeTime = Math.max(safeTime, entry.safeTime);
3376
+ }
3377
+ if (entry.symlinks !== undefined) {
3378
+ for (const target of entry.symlinks) push(target);
3379
+ }
3380
+ }
3381
+ callback();
3382
+ });
3383
+ },
3384
+ err => {
3385
+ if (err) return callback(err);
3386
+ const hash = createHash("md4");
3387
+ const tsHash = createHash("md4");
3388
+ hash.update(entry.hash);
3389
+ if (entry.timestampHash) tsHash.update(entry.timestampHash);
3390
+ if (entry.safeTime) {
3391
+ safeTime = Math.max(safeTime, entry.safeTime);
3392
+ }
3393
+ hashes.sort();
3394
+ for (const h of hashes) {
3395
+ hash.update(h);
3396
+ }
3397
+ tsHashes.sort();
3398
+ for (const h of tsHashes) {
3399
+ tsHash.update(h);
3400
+ }
3401
+ callback(
3402
+ null,
3403
+ (entry.resolved = {
3404
+ safeTime,
3405
+ timestampHash: /** @type {string} */ (tsHash.digest("hex")),
3406
+ hash: /** @type {string} */ (hash.digest("hex"))
3407
+ })
3408
+ );
3409
+ }
3410
+ );
3411
+ }
3412
+
2941
3413
  _getManagedItemDirectoryInfo(path, callback) {
2942
3414
  this.fs.readdir(path, (err, elements) => {
2943
3415
  if (err) {