webpack 5.49.0 → 5.51.2

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 (41) hide show
  1. package/README.md +4 -16
  2. package/bin/webpack.js +0 -0
  3. package/lib/ChunkGraph.js +75 -1
  4. package/lib/CompatibilityPlugin.js +21 -4
  5. package/lib/Compilation.js +10 -1
  6. package/lib/Compiler.js +7 -0
  7. package/lib/EvalSourceMapDevToolPlugin.js +2 -2
  8. package/lib/FileSystemInfo.js +660 -191
  9. package/lib/HotModuleReplacementPlugin.js +14 -0
  10. package/lib/NormalModule.js +13 -3
  11. package/lib/Parser.js +1 -0
  12. package/lib/RuntimeGlobals.js +5 -0
  13. package/lib/RuntimeModule.js +2 -1
  14. package/lib/SourceMapDevToolPlugin.js +2 -2
  15. package/lib/Watching.js +8 -10
  16. package/lib/config/defaults.js +1 -1
  17. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +6 -2
  18. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +10 -1
  19. package/lib/javascript/JavascriptParser.js +2 -0
  20. package/lib/library/ModuleLibraryPlugin.js +4 -0
  21. package/lib/node/ReadFileChunkLoadingRuntimeModule.js +7 -1
  22. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +2 -2
  23. package/lib/node/ReadFileCompileWasmPlugin.js +2 -1
  24. package/lib/node/RequireChunkLoadingRuntimeModule.js +7 -1
  25. package/lib/optimize/ConcatenatedModule.js +3 -3
  26. package/lib/optimize/SplitChunksPlugin.js +1 -1
  27. package/lib/runtime/GetChunkFilenameRuntimeModule.js +1 -0
  28. package/lib/serialization/BinaryMiddleware.js +293 -265
  29. package/lib/util/fs.js +40 -0
  30. package/lib/util/identifier.js +26 -8
  31. package/lib/util/propertyAccess.js +54 -1
  32. package/lib/wasm-async/{AsyncWasmChunkLoadingRuntimeModule.js → AsyncWasmLoadingRuntimeModule.js} +3 -3
  33. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +18 -2
  34. package/lib/web/FetchCompileAsyncWasmPlugin.js +2 -2
  35. package/lib/web/FetchCompileWasmPlugin.js +2 -1
  36. package/lib/web/JsonpChunkLoadingRuntimeModule.js +21 -8
  37. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +7 -1
  38. package/package.json +2 -1
  39. package/schemas/WebpackOptions.json +1 -1
  40. package/schemas/plugins/schemes/HttpUriPlugin.json +1 -1
  41. package/types.d.ts +63 -9
@@ -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,10 +1153,30 @@ 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
+ return this._resolveContextTimestamp(cache, callback);
1165
+ }
1166
+ this.contextTimestampQueue.add(path, (err, entry) => {
1167
+ if (err) return callback(err);
1168
+ const resolved = getResolvedTimestamp(entry);
1169
+ if (resolved !== undefined) return callback(null, resolved);
1170
+ this._resolveContextTimestamp(entry, callback);
1171
+ });
1172
+ }
1173
+
1174
+ /**
1175
+ * @param {string} path context path
1176
+ * @param {function(WebpackError=, (ContextFileSystemInfoEntry | "ignore" | null)=): void} callback callback function
1177
+ * @returns {void}
1178
+ */
1179
+ _getUnresolvedContextTimestamp(path, callback) {
1100
1180
  const cache = this._contextTimestamps.get(path);
1101
1181
  if (cache !== undefined) return callback(null, cache);
1102
1182
  this.contextTimestampQueue.add(path, callback);
@@ -1119,11 +1199,62 @@ class FileSystemInfo {
1119
1199
  * @returns {void}
1120
1200
  */
1121
1201
  getContextHash(path, callback) {
1202
+ const cache = this._contextHashes.get(path);
1203
+ if (cache !== undefined) {
1204
+ const resolved = getResolvedHash(cache);
1205
+ if (resolved !== undefined) return callback(null, resolved);
1206
+ return this._resolveContextHash(cache, callback);
1207
+ }
1208
+ this.contextHashQueue.add(path, (err, entry) => {
1209
+ if (err) return callback(err);
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) {
1122
1222
  const cache = this._contextHashes.get(path);
1123
1223
  if (cache !== undefined) return callback(null, cache);
1124
1224
  this.contextHashQueue.add(path, callback);
1125
1225
  }
1126
1226
 
1227
+ /**
1228
+ * @param {string} path context path
1229
+ * @param {function(WebpackError=, ResolvedContextTimestampAndHash=): void} callback callback function
1230
+ * @returns {void}
1231
+ */
1232
+ getContextTsh(path, callback) {
1233
+ const cache = this._contextTshs.get(path);
1234
+ if (cache !== undefined) {
1235
+ const resolved = getResolvedTimestamp(cache);
1236
+ if (resolved !== undefined) return callback(null, resolved);
1237
+ return this._resolveContextTsh(cache, callback);
1238
+ }
1239
+ this.contextTshQueue.add(path, (err, entry) => {
1240
+ if (err) return callback(err);
1241
+ const resolved = getResolvedTimestamp(entry);
1242
+ if (resolved !== undefined) return callback(null, resolved);
1243
+ this._resolveContextTsh(entry, callback);
1244
+ });
1245
+ }
1246
+
1247
+ /**
1248
+ * @param {string} path context path
1249
+ * @param {function(WebpackError=, ContextTimestampAndHash=): void} callback callback function
1250
+ * @returns {void}
1251
+ */
1252
+ _getUnresolvedContextTsh(path, callback) {
1253
+ const cache = this._contextTshs.get(path);
1254
+ if (cache !== undefined) return callback(null, cache);
1255
+ this.contextTshQueue.add(path, callback);
1256
+ }
1257
+
1127
1258
  _createBuildDependenciesResolvers() {
1128
1259
  const resolveContext = createResolver({
1129
1260
  resolveToContext: true,
@@ -2007,11 +2138,15 @@ class FileSystemInfo {
2007
2138
  );
2008
2139
  for (const path of capturedDirectories) {
2009
2140
  const cache = this._contextTshs.get(path);
2010
- if (cache !== undefined) {
2011
- contextTshs.set(path, cache);
2141
+ let resolved;
2142
+ if (
2143
+ cache !== undefined &&
2144
+ (resolved = getResolvedTimestamp(cache)) !== undefined
2145
+ ) {
2146
+ contextTshs.set(path, resolved);
2012
2147
  } else {
2013
2148
  jobs++;
2014
- this._getContextTimestampAndHash(path, (err, entry) => {
2149
+ const callback = (err, entry) => {
2015
2150
  if (err) {
2016
2151
  if (this.logger) {
2017
2152
  this.logger.debug(
@@ -2023,7 +2158,12 @@ class FileSystemInfo {
2023
2158
  contextTshs.set(path, entry);
2024
2159
  jobDone();
2025
2160
  }
2026
- });
2161
+ };
2162
+ if (cache !== undefined) {
2163
+ this._resolveContextTsh(cache, callback);
2164
+ } else {
2165
+ this.getContextTsh(path, callback);
2166
+ }
2027
2167
  }
2028
2168
  }
2029
2169
  break;
@@ -2035,11 +2175,15 @@ class FileSystemInfo {
2035
2175
  );
2036
2176
  for (const path of capturedDirectories) {
2037
2177
  const cache = this._contextHashes.get(path);
2038
- if (cache !== undefined) {
2039
- contextHashes.set(path, cache);
2178
+ let resolved;
2179
+ if (
2180
+ cache !== undefined &&
2181
+ (resolved = getResolvedHash(cache)) !== undefined
2182
+ ) {
2183
+ contextHashes.set(path, resolved);
2040
2184
  } else {
2041
2185
  jobs++;
2042
- this.contextHashQueue.add(path, (err, entry) => {
2186
+ const callback = (err, entry) => {
2043
2187
  if (err) {
2044
2188
  if (this.logger) {
2045
2189
  this.logger.debug(
@@ -2051,7 +2195,12 @@ class FileSystemInfo {
2051
2195
  contextHashes.set(path, entry);
2052
2196
  jobDone();
2053
2197
  }
2054
- });
2198
+ };
2199
+ if (cache !== undefined) {
2200
+ this._resolveContextHash(cache, callback);
2201
+ } else {
2202
+ this.getContextHash(path, callback);
2203
+ }
2055
2204
  }
2056
2205
  }
2057
2206
  break;
@@ -2064,13 +2213,15 @@ class FileSystemInfo {
2064
2213
  );
2065
2214
  for (const path of capturedDirectories) {
2066
2215
  const cache = this._contextTimestamps.get(path);
2067
- if (cache !== undefined) {
2068
- if (cache !== "ignore") {
2069
- contextTimestamps.set(path, cache);
2070
- }
2071
- } else {
2216
+ let resolved;
2217
+ if (
2218
+ cache !== undefined &&
2219
+ (resolved = getResolvedTimestamp(cache)) !== undefined
2220
+ ) {
2221
+ contextTimestamps.set(path, resolved);
2222
+ } else if (cache !== "ignore") {
2072
2223
  jobs++;
2073
- this.contextTimestampQueue.add(path, (err, entry) => {
2224
+ const callback = (err, entry) => {
2074
2225
  if (err) {
2075
2226
  if (this.logger) {
2076
2227
  this.logger.debug(
@@ -2082,7 +2233,12 @@ class FileSystemInfo {
2082
2233
  contextTimestamps.set(path, entry);
2083
2234
  jobDone();
2084
2235
  }
2085
- });
2236
+ };
2237
+ if (cache !== undefined) {
2238
+ this._resolveContextTimestamp(cache, callback);
2239
+ } else {
2240
+ this.getContextTimestamp(path, callback);
2241
+ }
2086
2242
  }
2087
2243
  }
2088
2244
  break;
@@ -2099,7 +2255,7 @@ class FileSystemInfo {
2099
2255
  const cache = this._fileTimestamps.get(path);
2100
2256
  if (cache !== undefined) {
2101
2257
  if (cache !== "ignore") {
2102
- missingExistence.set(path, toExistence(cache));
2258
+ missingExistence.set(path, Boolean(cache));
2103
2259
  }
2104
2260
  } else {
2105
2261
  jobs++;
@@ -2112,7 +2268,7 @@ class FileSystemInfo {
2112
2268
  }
2113
2269
  jobError();
2114
2270
  } else {
2115
- missingExistence.set(path, toExistence(entry));
2271
+ missingExistence.set(path, Boolean(entry));
2116
2272
  jobDone();
2117
2273
  }
2118
2274
  });
@@ -2321,17 +2477,7 @@ class FileSystemInfo {
2321
2477
  */
2322
2478
  const checkFile = (path, current, snap, log = true) => {
2323
2479
  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
- }
2480
+ if (!checkExistence(path, Boolean(current), Boolean(snap))) return false;
2335
2481
  if (current) {
2336
2482
  // For existing items only
2337
2483
  if (typeof startTime === "number" && current.safeTime > startTime) {
@@ -2363,6 +2509,34 @@ class FileSystemInfo {
2363
2509
  }
2364
2510
  return false;
2365
2511
  }
2512
+ }
2513
+ return true;
2514
+ };
2515
+ /**
2516
+ * @param {string} path file path
2517
+ * @param {ResolvedContextFileSystemInfoEntry} current current entry
2518
+ * @param {ResolvedContextFileSystemInfoEntry} snap entry from snapshot
2519
+ * @param {boolean} log log reason
2520
+ * @returns {boolean} true, if ok
2521
+ */
2522
+ const checkContext = (path, current, snap, log = true) => {
2523
+ if (current === snap) return true;
2524
+ if (!checkExistence(path, Boolean(current), Boolean(snap))) return false;
2525
+ if (current) {
2526
+ // For existing items only
2527
+ if (typeof startTime === "number" && current.safeTime > startTime) {
2528
+ // If a change happened after starting reading the item
2529
+ // this may no longer be valid
2530
+ if (log && this._remainingLogs > 0) {
2531
+ this._log(
2532
+ path,
2533
+ `it may have changed (%d) after the start time of the snapshot (%d)`,
2534
+ current.safeTime,
2535
+ startTime
2536
+ );
2537
+ }
2538
+ return false;
2539
+ }
2366
2540
  if (
2367
2541
  snap.timestampHash !== undefined &&
2368
2542
  current.timestampHash !== snap.timestampHash
@@ -2487,41 +2661,59 @@ class FileSystemInfo {
2487
2661
  this._statTestedEntries += contextTimestamps.size;
2488
2662
  for (const [path, ts] of contextTimestamps) {
2489
2663
  const cache = this._contextTimestamps.get(path);
2490
- if (cache !== undefined) {
2491
- if (cache !== "ignore" && !checkFile(path, cache, ts)) {
2664
+ let resolved;
2665
+ if (
2666
+ cache !== undefined &&
2667
+ (resolved = getResolvedTimestamp(cache)) !== undefined
2668
+ ) {
2669
+ if (!checkContext(path, resolved, ts)) {
2492
2670
  invalid();
2493
2671
  return;
2494
2672
  }
2495
- } else {
2673
+ } else if (cache !== "ignore") {
2496
2674
  jobs++;
2497
- this.contextTimestampQueue.add(path, (err, entry) => {
2675
+ const callback = (err, entry) => {
2498
2676
  if (err) return invalidWithError(path, err);
2499
- if (!checkFile(path, entry, ts)) {
2677
+ if (!checkContext(path, entry, ts)) {
2500
2678
  invalid();
2501
2679
  } else {
2502
2680
  jobDone();
2503
2681
  }
2504
- });
2682
+ };
2683
+ if (cache !== undefined) {
2684
+ this._resolveContextTimestamp(cache, callback);
2685
+ } else {
2686
+ this.getContextTimestamp(path, callback);
2687
+ }
2505
2688
  }
2506
2689
  }
2507
2690
  }
2508
2691
  const processContextHashSnapshot = (path, hash) => {
2509
2692
  const cache = this._contextHashes.get(path);
2510
- if (cache !== undefined) {
2511
- if (cache !== "ignore" && !checkHash(path, cache, hash)) {
2693
+ let resolved;
2694
+ if (
2695
+ cache !== undefined &&
2696
+ (resolved = getResolvedHash(cache)) !== undefined
2697
+ ) {
2698
+ if (!checkHash(path, resolved, hash)) {
2512
2699
  invalid();
2513
2700
  return;
2514
2701
  }
2515
2702
  } else {
2516
2703
  jobs++;
2517
- this.contextHashQueue.add(path, (err, entry) => {
2704
+ const callback = (err, entry) => {
2518
2705
  if (err) return invalidWithError(path, err);
2519
2706
  if (!checkHash(path, entry, hash)) {
2520
2707
  invalid();
2521
2708
  } else {
2522
2709
  jobDone();
2523
2710
  }
2524
- });
2711
+ };
2712
+ if (cache !== undefined) {
2713
+ this._resolveContextHash(cache, callback);
2714
+ } else {
2715
+ this.getContextHash(path, callback);
2716
+ }
2525
2717
  }
2526
2718
  };
2527
2719
  if (snapshot.hasContextHashes()) {
@@ -2539,19 +2731,28 @@ class FileSystemInfo {
2539
2731
  processContextHashSnapshot(path, tsh);
2540
2732
  } else {
2541
2733
  const cache = this._contextTimestamps.get(path);
2542
- if (cache !== undefined) {
2543
- if (cache === "ignore" || !checkFile(path, cache, tsh, false)) {
2734
+ let resolved;
2735
+ if (
2736
+ cache !== undefined &&
2737
+ (resolved = getResolvedTimestamp(cache)) !== undefined
2738
+ ) {
2739
+ if (!checkContext(path, resolved, tsh, false)) {
2544
2740
  processContextHashSnapshot(path, tsh.hash);
2545
2741
  }
2546
- } else {
2742
+ } else if (cache !== "ignore") {
2547
2743
  jobs++;
2548
- this.contextTimestampQueue.add(path, (err, entry) => {
2744
+ const callback = (err, entry) => {
2549
2745
  if (err) return invalidWithError(path, err);
2550
- if (!checkFile(path, entry, tsh, false)) {
2746
+ if (!checkContext(path, entry, tsh, false)) {
2551
2747
  processContextHashSnapshot(path, tsh.hash);
2552
2748
  }
2553
2749
  jobDone();
2554
- });
2750
+ };
2751
+ if (cache !== undefined) {
2752
+ this._resolveContextTsh(cache, callback);
2753
+ } else {
2754
+ this.getContextTsh(path, callback);
2755
+ }
2555
2756
  }
2556
2757
  }
2557
2758
  }
@@ -2564,7 +2765,7 @@ class FileSystemInfo {
2564
2765
  if (cache !== undefined) {
2565
2766
  if (
2566
2767
  cache !== "ignore" &&
2567
- !checkExistence(path, toExistence(cache), existence)
2768
+ !checkExistence(path, Boolean(cache), Boolean(existence))
2568
2769
  ) {
2569
2770
  invalid();
2570
2771
  return;
@@ -2573,7 +2774,7 @@ class FileSystemInfo {
2573
2774
  jobs++;
2574
2775
  this.fileTimestampQueue.add(path, (err, entry) => {
2575
2776
  if (err) return invalidWithError(path, err);
2576
- if (!checkExistence(path, toExistence(entry), existence)) {
2777
+ if (!checkExistence(path, Boolean(entry), Boolean(existence))) {
2577
2778
  invalid();
2578
2779
  } else {
2579
2780
  jobDone();
@@ -2727,12 +2928,34 @@ class FileSystemInfo {
2727
2928
  }
2728
2929
  }
2729
2930
 
2730
- _readContextTimestamp(path, callback) {
2931
+ /**
2932
+ * @template T
2933
+ * @template ItemType
2934
+ * @param {Object} options options
2935
+ * @param {string} options.path path
2936
+ * @param {function(string): ItemType} options.fromImmutablePath called when context item is an immutable path
2937
+ * @param {function(string): ItemType} options.fromManagedItem called when context item is a managed path
2938
+ * @param {function(string, string, function(Error=, ItemType=): void): void} options.fromSymlink called when context item is a symlink
2939
+ * @param {function(string, IStats, function(Error=, ItemType=): void): void} options.fromFile called when context item is a file
2940
+ * @param {function(string, IStats, function(Error=, ItemType=): void): void} options.fromDirectory called when context item is a directory
2941
+ * @param {function(string[], ItemType[]): T} options.reduce called from all context items
2942
+ * @param {function(Error=, (T)=): void} callback callback
2943
+ */
2944
+ _readContext(
2945
+ {
2946
+ path,
2947
+ fromImmutablePath,
2948
+ fromManagedItem,
2949
+ fromSymlink,
2950
+ fromFile,
2951
+ fromDirectory,
2952
+ reduce
2953
+ },
2954
+ callback
2955
+ ) {
2731
2956
  this.fs.readdir(path, (err, _files) => {
2732
2957
  if (err) {
2733
2958
  if (err.code === "ENOENT") {
2734
- this._contextTimestamps.set(path, null);
2735
- this._cachedDeprecatedContextTimestamps = undefined;
2736
2959
  return callback(null, null);
2737
2960
  }
2738
2961
  return callback(err);
@@ -2745,47 +2968,94 @@ class FileSystemInfo {
2745
2968
  files,
2746
2969
  (file, callback) => {
2747
2970
  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
- }
2971
+ for (const immutablePath of this.immutablePathsWithSlash) {
2972
+ if (path.startsWith(immutablePath)) {
2973
+ // ignore any immutable path for timestamping
2974
+ return callback(null, fromImmutablePath(immutablePath));
2756
2975
  }
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
- }
2976
+ }
2977
+ for (const managedPath of this.managedPathsWithSlash) {
2978
+ if (path.startsWith(managedPath)) {
2979
+ const managedItem = getManagedItem(managedPath, child);
2980
+ if (managedItem) {
2981
+ // construct timestampHash from managed info
2982
+ return this.managedItemQueue.add(managedItem, (err, info) => {
2983
+ if (err) return callback(err);
2984
+ return callback(null, fromManagedItem(info));
2985
+ });
2770
2986
  }
2771
2987
  }
2988
+ }
2989
+
2990
+ lstatReadlinkAbsolute(this.fs, child, (err, stat) => {
2991
+ if (err) return callback(err);
2992
+
2993
+ if (typeof stat === "string") {
2994
+ return fromSymlink(child, stat, callback);
2995
+ }
2772
2996
 
2773
2997
  if (stat.isFile()) {
2774
- return this.getFileTimestamp(child, callback);
2998
+ return fromFile(child, stat, callback);
2775
2999
  }
2776
3000
  if (stat.isDirectory()) {
2777
- this.contextTimestampQueue.increaseParallelism();
2778
- this.getContextTimestamp(child, (err, tsEntry) => {
2779
- this.contextTimestampQueue.decreaseParallelism();
2780
- callback(err, tsEntry);
2781
- });
2782
- return;
3001
+ return fromDirectory(child, stat, callback);
2783
3002
  }
2784
3003
  callback(null, null);
2785
3004
  });
2786
3005
  },
2787
- (err, tsEntries) => {
3006
+ (err, results) => {
2788
3007
  if (err) return callback(err);
3008
+ const result = reduce(files, results);
3009
+ callback(null, result);
3010
+ }
3011
+ );
3012
+ });
3013
+ }
3014
+
3015
+ _readContextTimestamp(path, callback) {
3016
+ this._readContext(
3017
+ {
3018
+ path,
3019
+ fromImmutablePath: () => null,
3020
+ fromManagedItem: info => ({
3021
+ safeTime: 0,
3022
+ timestampHash: info
3023
+ }),
3024
+ fromSymlink: (file, target, callback) => {
3025
+ callback(null, {
3026
+ timestampHash: target,
3027
+ symlinks: new Set([target])
3028
+ });
3029
+ },
3030
+ fromFile: (file, stat, callback) => {
3031
+ // Prefer the cached value over our new stat to report consistent results
3032
+ const cache = this._fileTimestamps.get(file);
3033
+ if (cache !== undefined)
3034
+ return callback(null, cache === "ignore" ? null : cache);
3035
+
3036
+ const mtime = +stat.mtime;
3037
+
3038
+ if (mtime) applyMtime(mtime);
3039
+
3040
+ const ts = {
3041
+ safeTime: mtime ? mtime + FS_ACCURACY : Infinity,
3042
+ timestamp: mtime
3043
+ };
3044
+
3045
+ this._fileTimestamps.set(file, ts);
3046
+ this._cachedDeprecatedFileTimestamps = undefined;
3047
+ callback(null, ts);
3048
+ },
3049
+ fromDirectory: (directory, stat, callback) => {
3050
+ this.contextTimestampQueue.increaseParallelism();
3051
+ this._getUnresolvedContextTimestamp(directory, (err, tsEntry) => {
3052
+ this.contextTimestampQueue.decreaseParallelism();
3053
+ callback(err, tsEntry);
3054
+ });
3055
+ },
3056
+ reduce: (files, tsEntries) => {
3057
+ let symlinks = undefined;
3058
+
2789
3059
  const hash = createHash("md4");
2790
3060
 
2791
3061
  for (const file of files) hash.update(file);
@@ -2802,6 +3072,10 @@ class FileSystemInfo {
2802
3072
  hash.update("d");
2803
3073
  hash.update(`${entry.timestampHash}`);
2804
3074
  }
3075
+ if (entry.symlinks !== undefined) {
3076
+ if (symlinks === undefined) symlinks = new Set();
3077
+ addAll(entry.symlinks, symlinks);
3078
+ }
2805
3079
  if (entry.safeTime) {
2806
3080
  safeTime = Math.max(safeTime, entry.safeTime);
2807
3081
  }
@@ -2813,131 +3087,326 @@ class FileSystemInfo {
2813
3087
  safeTime,
2814
3088
  timestampHash: digest
2815
3089
  };
2816
-
2817
- this._contextTimestamps.set(path, result);
2818
- this._cachedDeprecatedContextTimestamps = undefined;
2819
-
2820
- callback(null, result);
3090
+ if (symlinks) result.symlinks = symlinks;
3091
+ return result;
2821
3092
  }
2822
- );
2823
- });
2824
- }
3093
+ },
3094
+ (err, result) => {
3095
+ if (err) return callback(err);
3096
+ this._contextTimestamps.set(path, result);
3097
+ this._cachedDeprecatedContextTimestamps = undefined;
2825
3098
 
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);
3099
+ callback(null, result);
2834
3100
  }
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);
3101
+ );
3102
+ }
2845
3103
 
2846
- for (const immutablePath of this.immutablePathsWithSlash) {
2847
- if (path.startsWith(immutablePath)) {
2848
- // ignore any immutable path for hashing
2849
- return callback(null, "");
2850
- }
3104
+ _resolveContextTimestamp(entry, callback) {
3105
+ const hashes = [];
3106
+ let safeTime = 0;
3107
+ processAsyncTree(
3108
+ entry.symlinks,
3109
+ 10,
3110
+ (target, push, callback) => {
3111
+ this._getUnresolvedContextTimestamp(target, (err, entry) => {
3112
+ if (err) return callback(err);
3113
+ if (entry && entry !== "ignore") {
3114
+ hashes.push(entry.timestampHash);
3115
+ if (entry.safeTime) {
3116
+ safeTime = Math.max(safeTime, entry.safeTime);
2851
3117
  }
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
- }
3118
+ if (entry.symlinks !== undefined) {
3119
+ for (const target of entry.symlinks) push(target);
2863
3120
  }
3121
+ }
3122
+ callback();
3123
+ });
3124
+ },
3125
+ err => {
3126
+ if (err) return callback(err);
3127
+ const hash = createHash("md4");
3128
+ hash.update(entry.timestampHash);
3129
+ if (entry.safeTime) {
3130
+ safeTime = Math.max(safeTime, entry.safeTime);
3131
+ }
3132
+ hashes.sort();
3133
+ for (const h of hashes) {
3134
+ hash.update(h);
3135
+ }
3136
+ callback(
3137
+ null,
3138
+ (entry.resolved = {
3139
+ safeTime,
3140
+ timestampHash: /** @type {string} */ (hash.digest("hex"))
3141
+ })
3142
+ );
3143
+ }
3144
+ );
3145
+ }
2864
3146
 
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, "");
3147
+ _readContextHash(path, callback) {
3148
+ this._readContext(
3149
+ {
3150
+ path,
3151
+ fromImmutablePath: () => "",
3152
+ fromManagedItem: info => info || "",
3153
+ fromSymlink: (file, target, callback) => {
3154
+ callback(null, {
3155
+ hash: target,
3156
+ symlinks: new Set([target])
2879
3157
  });
2880
3158
  },
2881
- (err, fileHashes) => {
2882
- if (err) return callback(err);
3159
+ fromFile: (file, stat, callback) =>
3160
+ this.getFileHash(file, (err, hash) => {
3161
+ callback(err, hash || "");
3162
+ }),
3163
+ fromDirectory: (directory, stat, callback) => {
3164
+ this.contextHashQueue.increaseParallelism();
3165
+ this._getUnresolvedContextHash(directory, (err, hash) => {
3166
+ this.contextHashQueue.decreaseParallelism();
3167
+ callback(err, hash || "");
3168
+ });
3169
+ },
3170
+ /**
3171
+ * @param {string[]} files files
3172
+ * @param {(string | ContextHash)[]} fileHashes hashes
3173
+ * @returns {ContextHash} reduced hash
3174
+ */
3175
+ reduce: (files, fileHashes) => {
3176
+ let symlinks = undefined;
2883
3177
  const hash = createHash("md4");
2884
3178
 
2885
3179
  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);
3180
+ for (const entry of fileHashes) {
3181
+ if (typeof entry === "string") {
3182
+ hash.update(entry);
3183
+ } else {
3184
+ hash.update(entry.hash);
3185
+ if (entry.symlinks) {
3186
+ if (symlinks === undefined) symlinks = new Set();
3187
+ addAll(entry.symlinks, symlinks);
3188
+ }
3189
+ }
3190
+ }
2891
3191
 
2892
- callback(null, digest);
3192
+ const result = {
3193
+ hash: /** @type {string} */ (hash.digest("hex"))
3194
+ };
3195
+ if (symlinks) result.symlinks = symlinks;
3196
+ return result;
2893
3197
  }
2894
- );
2895
- });
3198
+ },
3199
+ (err, result) => {
3200
+ if (err) return callback(err);
3201
+ this._contextHashes.set(path, result);
3202
+ return callback(null, result);
3203
+ }
3204
+ );
2896
3205
  }
2897
3206
 
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);
3207
+ _resolveContextHash(entry, callback) {
3208
+ const hashes = [];
3209
+ processAsyncTree(
3210
+ entry.symlinks,
3211
+ 10,
3212
+ (target, push, callback) => {
3213
+ this._getUnresolvedContextHash(target, (err, hash) => {
3214
+ if (err) return callback(err);
3215
+ if (hash) {
3216
+ hashes.push(hash.hash);
3217
+ if (hash.symlinks !== undefined) {
3218
+ for (const target of hash.symlinks) push(target);
3219
+ }
3220
+ }
3221
+ callback();
3222
+ });
3223
+ },
3224
+ err => {
3225
+ if (err) return callback(err);
3226
+ const hash = createHash("md4");
3227
+ hash.update(entry.hash);
3228
+ hashes.sort();
3229
+ for (const h of hashes) {
3230
+ hash.update(h);
2912
3231
  }
3232
+ callback(
3233
+ null,
3234
+ (entry.resolved = /** @type {string} */ (hash.digest("hex")))
3235
+ );
3236
+ }
3237
+ );
3238
+ }
3239
+
3240
+ _readContextTimestampAndHash(path, callback) {
3241
+ const finalize = (timestamp, hash) => {
3242
+ const result =
3243
+ timestamp === "ignore"
3244
+ ? hash
3245
+ : {
3246
+ ...timestamp,
3247
+ ...hash
3248
+ };
3249
+ this._contextTshs.set(path, result);
3250
+ callback(null, result);
3251
+ };
3252
+ const cachedHash = this._contextHashes.get(path);
3253
+ const cachedTimestamp = this._contextTimestamps.get(path);
3254
+ if (cachedHash !== undefined) {
3255
+ if (cachedTimestamp !== undefined) {
3256
+ finalize(cachedTimestamp, cachedHash);
2913
3257
  } else {
2914
3258
  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);
3259
+ if (err) return callback(err);
3260
+ finalize(entry, cachedHash);
2924
3261
  });
2925
3262
  }
2926
- };
2927
-
2928
- const cache = this._contextHashes.get(path);
2929
- if (cache !== undefined) {
2930
- continueWithHash(cache);
2931
3263
  } else {
2932
- this.contextHashQueue.add(path, (err, entry) => {
2933
- if (err) {
2934
- return callback(err);
2935
- }
2936
- continueWithHash(entry);
2937
- });
3264
+ if (cachedTimestamp !== undefined) {
3265
+ this.contextHashQueue.add(path, (err, entry) => {
3266
+ if (err) return callback(err);
3267
+ finalize(cachedTimestamp, entry);
3268
+ });
3269
+ } else {
3270
+ this._readContext(
3271
+ {
3272
+ path,
3273
+ fromImmutablePath: () => null,
3274
+ fromManagedItem: info => ({
3275
+ safeTime: 0,
3276
+ timestampHash: info,
3277
+ hash: info || ""
3278
+ }),
3279
+ fromSymlink: (fle, target, callback) => {
3280
+ callback(null, {
3281
+ timestampHash: target,
3282
+ hash: target,
3283
+ symlinks: new Set([target])
3284
+ });
3285
+ },
3286
+ fromFile: (file, stat, callback) => {
3287
+ this._getFileTimestampAndHash(file, callback);
3288
+ },
3289
+ fromDirectory: (directory, stat, callback) => {
3290
+ this.contextTshQueue.increaseParallelism();
3291
+ this.contextTshQueue.add(directory, (err, result) => {
3292
+ this.contextTshQueue.decreaseParallelism();
3293
+ callback(err, result);
3294
+ });
3295
+ },
3296
+ /**
3297
+ * @param {string[]} files files
3298
+ * @param {(Partial<TimestampAndHash> & Partial<ContextTimestampAndHash> | string | null)[]} results results
3299
+ * @returns {ContextTimestampAndHash} tsh
3300
+ */
3301
+ reduce: (files, results) => {
3302
+ let symlinks = undefined;
3303
+
3304
+ const tsHash = createHash("md4");
3305
+ const hash = createHash("md4");
3306
+
3307
+ for (const file of files) {
3308
+ tsHash.update(file);
3309
+ hash.update(file);
3310
+ }
3311
+ let safeTime = 0;
3312
+ for (const entry of results) {
3313
+ if (!entry) {
3314
+ tsHash.update("n");
3315
+ continue;
3316
+ }
3317
+ if (typeof entry === "string") {
3318
+ tsHash.update("n");
3319
+ hash.update(entry);
3320
+ continue;
3321
+ }
3322
+ if (entry.timestamp) {
3323
+ tsHash.update("f");
3324
+ tsHash.update(`${entry.timestamp}`);
3325
+ } else if (entry.timestampHash) {
3326
+ tsHash.update("d");
3327
+ tsHash.update(`${entry.timestampHash}`);
3328
+ }
3329
+ if (entry.symlinks !== undefined) {
3330
+ if (symlinks === undefined) symlinks = new Set();
3331
+ addAll(entry.symlinks, symlinks);
3332
+ }
3333
+ if (entry.safeTime) {
3334
+ safeTime = Math.max(safeTime, entry.safeTime);
3335
+ }
3336
+ hash.update(entry.hash);
3337
+ }
3338
+
3339
+ const result = {
3340
+ safeTime,
3341
+ timestampHash: /** @type {string} */ (tsHash.digest("hex")),
3342
+ hash: /** @type {string} */ (hash.digest("hex"))
3343
+ };
3344
+ if (symlinks) result.symlinks = symlinks;
3345
+ return result;
3346
+ }
3347
+ },
3348
+ (err, result) => {
3349
+ if (err) return callback(err);
3350
+ this._contextTshs.set(path, result);
3351
+ return callback(null, result);
3352
+ }
3353
+ );
3354
+ }
2938
3355
  }
2939
3356
  }
2940
3357
 
3358
+ _resolveContextTsh(entry, callback) {
3359
+ const hashes = [];
3360
+ const tsHashes = [];
3361
+ let safeTime = 0;
3362
+ processAsyncTree(
3363
+ entry.symlinks,
3364
+ 10,
3365
+ (target, push, callback) => {
3366
+ this._getUnresolvedContextTsh(target, (err, entry) => {
3367
+ if (err) return callback(err);
3368
+ if (entry) {
3369
+ hashes.push(entry.hash);
3370
+ if (entry.timestampHash) tsHashes.push(entry.timestampHash);
3371
+ if (entry.safeTime) {
3372
+ safeTime = Math.max(safeTime, entry.safeTime);
3373
+ }
3374
+ if (entry.symlinks !== undefined) {
3375
+ for (const target of entry.symlinks) push(target);
3376
+ }
3377
+ }
3378
+ callback();
3379
+ });
3380
+ },
3381
+ err => {
3382
+ if (err) return callback(err);
3383
+ const hash = createHash("md4");
3384
+ const tsHash = createHash("md4");
3385
+ hash.update(entry.hash);
3386
+ if (entry.timestampHash) tsHash.update(entry.timestampHash);
3387
+ if (entry.safeTime) {
3388
+ safeTime = Math.max(safeTime, entry.safeTime);
3389
+ }
3390
+ hashes.sort();
3391
+ for (const h of hashes) {
3392
+ hash.update(h);
3393
+ }
3394
+ tsHashes.sort();
3395
+ for (const h of tsHashes) {
3396
+ tsHash.update(h);
3397
+ }
3398
+ callback(
3399
+ null,
3400
+ (entry.resolved = {
3401
+ safeTime,
3402
+ timestampHash: /** @type {string} */ (tsHash.digest("hex")),
3403
+ hash: /** @type {string} */ (hash.digest("hex"))
3404
+ })
3405
+ );
3406
+ }
3407
+ );
3408
+ }
3409
+
2941
3410
  _getManagedItemDirectoryInfo(path, callback) {
2942
3411
  this.fs.readdir(path, (err, elements) => {
2943
3412
  if (err) {