@atlaspack/core 2.12.1-canary.3354

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/LICENSE +201 -0
  2. package/index.d.ts +22 -0
  3. package/lib/AssetGraph.js +518 -0
  4. package/lib/Atlaspack.js +587 -0
  5. package/lib/AtlaspackConfig.js +298 -0
  6. package/lib/AtlaspackConfig.schema.js +125 -0
  7. package/lib/BundleGraph.js +1445 -0
  8. package/lib/CommittedAsset.js +133 -0
  9. package/lib/Dependency.js +88 -0
  10. package/lib/Environment.js +128 -0
  11. package/lib/InternalConfig.js +48 -0
  12. package/lib/PackagerRunner.js +519 -0
  13. package/lib/ReporterRunner.js +151 -0
  14. package/lib/RequestTracker.js +1089 -0
  15. package/lib/SymbolPropagation.js +625 -0
  16. package/lib/TargetDescriptor.schema.js +115 -0
  17. package/lib/Transformation.js +536 -0
  18. package/lib/UncommittedAsset.js +342 -0
  19. package/lib/Validation.js +215 -0
  20. package/lib/applyRuntimes.js +279 -0
  21. package/lib/assetUtils.js +189 -0
  22. package/lib/atlaspack-v3/AtlaspackV3.js +74 -0
  23. package/lib/atlaspack-v3/fs.js +30 -0
  24. package/lib/atlaspack-v3/index.js +19 -0
  25. package/lib/atlaspack-v3/jsCallable.js +15 -0
  26. package/lib/atlaspack-v3/plugins/Resolver.js +12 -0
  27. package/lib/atlaspack-v3/plugins/index.js +16 -0
  28. package/lib/atlaspack-v3/worker/index.js +3 -0
  29. package/lib/atlaspack-v3/worker/worker.js +30 -0
  30. package/lib/buildCache.js +18 -0
  31. package/lib/constants.js +21 -0
  32. package/lib/dumpGraphToGraphViz.js +206 -0
  33. package/lib/index.js +106 -0
  34. package/lib/loadAtlaspackPlugin.js +148 -0
  35. package/lib/loadDotEnv.js +54 -0
  36. package/lib/projectPath.js +86 -0
  37. package/lib/public/Asset.js +259 -0
  38. package/lib/public/Bundle.js +231 -0
  39. package/lib/public/BundleGraph.js +193 -0
  40. package/lib/public/BundleGroup.js +44 -0
  41. package/lib/public/Config.js +202 -0
  42. package/lib/public/Dependency.js +131 -0
  43. package/lib/public/Environment.js +244 -0
  44. package/lib/public/MutableBundleGraph.js +184 -0
  45. package/lib/public/PluginOptions.js +71 -0
  46. package/lib/public/Symbols.js +247 -0
  47. package/lib/public/Target.js +64 -0
  48. package/lib/registerCoreWithSerializer.js +45 -0
  49. package/lib/requests/AssetGraphRequest.js +427 -0
  50. package/lib/requests/AssetGraphRequestRust.js +187 -0
  51. package/lib/requests/AssetRequest.js +137 -0
  52. package/lib/requests/AtlaspackBuildRequest.js +78 -0
  53. package/lib/requests/AtlaspackConfigRequest.js +473 -0
  54. package/lib/requests/BundleGraphRequest.js +419 -0
  55. package/lib/requests/ConfigRequest.js +198 -0
  56. package/lib/requests/DevDepRequest.js +166 -0
  57. package/lib/requests/EntryRequest.js +295 -0
  58. package/lib/requests/PackageRequest.js +88 -0
  59. package/lib/requests/PathRequest.js +349 -0
  60. package/lib/requests/TargetRequest.js +1177 -0
  61. package/lib/requests/ValidationRequest.js +66 -0
  62. package/lib/requests/WriteBundleRequest.js +252 -0
  63. package/lib/requests/WriteBundlesRequest.js +153 -0
  64. package/lib/resolveOptions.js +234 -0
  65. package/lib/serializer.js +216 -0
  66. package/lib/serializerCore.browser.js +29 -0
  67. package/lib/serializerCore.js +16 -0
  68. package/lib/summarizeRequest.js +55 -0
  69. package/lib/types.js +35 -0
  70. package/lib/utils.js +160 -0
  71. package/lib/worker.js +170 -0
  72. package/package.json +59 -0
  73. package/src/AssetGraph.js +646 -0
  74. package/src/Atlaspack.js +632 -0
  75. package/src/AtlaspackConfig.js +490 -0
  76. package/src/AtlaspackConfig.schema.js +141 -0
  77. package/src/BundleGraph.js +2193 -0
  78. package/src/CommittedAsset.js +143 -0
  79. package/src/Dependency.js +142 -0
  80. package/src/Environment.js +151 -0
  81. package/src/InternalConfig.js +72 -0
  82. package/src/PackagerRunner.js +790 -0
  83. package/src/ReporterRunner.js +158 -0
  84. package/src/RequestTracker.js +1697 -0
  85. package/src/SymbolPropagation.js +794 -0
  86. package/src/TargetDescriptor.schema.js +136 -0
  87. package/src/Transformation.js +752 -0
  88. package/src/UncommittedAsset.js +426 -0
  89. package/src/Validation.js +223 -0
  90. package/src/applyRuntimes.js +341 -0
  91. package/src/assetUtils.js +254 -0
  92. package/src/atlaspack-v3/AtlaspackV3.js +73 -0
  93. package/src/atlaspack-v3/fs.js +36 -0
  94. package/src/atlaspack-v3/index.js +5 -0
  95. package/src/atlaspack-v3/jsCallable.js +13 -0
  96. package/src/atlaspack-v3/plugins/Resolver.js +9 -0
  97. package/src/atlaspack-v3/plugins/index.js +3 -0
  98. package/src/atlaspack-v3/worker/index.js +8 -0
  99. package/src/atlaspack-v3/worker/worker.js +14 -0
  100. package/src/buildCache.js +15 -0
  101. package/src/constants.js +22 -0
  102. package/src/dumpGraphToGraphViz.js +244 -0
  103. package/src/index.js +22 -0
  104. package/src/loadAtlaspackPlugin.js +239 -0
  105. package/src/loadDotEnv.js +56 -0
  106. package/src/projectPath.js +87 -0
  107. package/src/public/Asset.js +359 -0
  108. package/src/public/Bundle.js +337 -0
  109. package/src/public/BundleGraph.js +335 -0
  110. package/src/public/BundleGroup.js +55 -0
  111. package/src/public/Config.js +261 -0
  112. package/src/public/Dependency.js +176 -0
  113. package/src/public/Environment.js +316 -0
  114. package/src/public/MutableBundleGraph.js +320 -0
  115. package/src/public/PluginOptions.js +99 -0
  116. package/src/public/Symbols.js +315 -0
  117. package/src/public/Target.js +71 -0
  118. package/src/registerCoreWithSerializer.js +34 -0
  119. package/src/requests/AssetGraphRequest.js +577 -0
  120. package/src/requests/AssetGraphRequestRust.js +222 -0
  121. package/src/requests/AssetRequest.js +179 -0
  122. package/src/requests/AtlaspackBuildRequest.js +109 -0
  123. package/src/requests/AtlaspackConfigRequest.js +709 -0
  124. package/src/requests/BundleGraphRequest.js +547 -0
  125. package/src/requests/ConfigRequest.js +287 -0
  126. package/src/requests/DevDepRequest.js +246 -0
  127. package/src/requests/EntryRequest.js +385 -0
  128. package/src/requests/PackageRequest.js +105 -0
  129. package/src/requests/PathRequest.js +454 -0
  130. package/src/requests/TargetRequest.js +1632 -0
  131. package/src/requests/ValidationRequest.js +79 -0
  132. package/src/requests/WriteBundleRequest.js +362 -0
  133. package/src/requests/WriteBundlesRequest.js +209 -0
  134. package/src/resolveOptions.js +278 -0
  135. package/src/serializer.js +255 -0
  136. package/src/serializerCore.browser.js +8 -0
  137. package/src/serializerCore.js +5 -0
  138. package/src/summarizeRequest.js +47 -0
  139. package/src/types.js +581 -0
  140. package/src/utils.js +211 -0
  141. package/src/worker.js +189 -0
  142. package/test/AssetGraph.test.js +679 -0
  143. package/test/Atlaspack.test.js +142 -0
  144. package/test/AtlaspackConfig.test.js +405 -0
  145. package/test/AtlaspackConfigRequest.test.js +993 -0
  146. package/test/BundleGraph.test.js +121 -0
  147. package/test/EntryRequest.test.js +215 -0
  148. package/test/Environment.test.js +99 -0
  149. package/test/InternalAsset.test.js +76 -0
  150. package/test/PackagerRunner.test.js +27 -0
  151. package/test/PublicAsset.test.js +40 -0
  152. package/test/PublicBundle.test.js +68 -0
  153. package/test/PublicDependency.test.js +22 -0
  154. package/test/PublicEnvironment.test.js +27 -0
  155. package/test/PublicMutableBundleGraph.test.js +192 -0
  156. package/test/RequestTracker.test.js +448 -0
  157. package/test/SymbolPropagation.test.js +716 -0
  158. package/test/TargetRequest.test.js +1715 -0
  159. package/test/fixtures/application-targets/package.json +3 -0
  160. package/test/fixtures/atlaspack/index.js +1 -0
  161. package/test/fixtures/atlaspack/other.js +3 -0
  162. package/test/fixtures/atlaspack/package.json +1 -0
  163. package/test/fixtures/atlaspack/yarn.lock +0 -0
  164. package/test/fixtures/bundle.js +10 -0
  165. package/test/fixtures/common-targets/package.json +24 -0
  166. package/test/fixtures/common-targets-ignore/package.json +13 -0
  167. package/test/fixtures/config/.atlaspackrc +6 -0
  168. package/test/fixtures/config/subfolder/.atlaspackrc +6 -0
  169. package/test/fixtures/config-extends-not-found/.atlaspackrc +3 -0
  170. package/test/fixtures/config-extends-not-found/.atlaspackrc-json5 +3 -0
  171. package/test/fixtures/config-extends-not-found/.atlaspackrc-multiple +3 -0
  172. package/test/fixtures/config-extends-not-found/.atlaspackrc-node-modules +3 -0
  173. package/test/fixtures/config-malformed/.atlaspackrc +3 -0
  174. package/test/fixtures/config-node-pipeline/.atlaspackrc +6 -0
  175. package/test/fixtures/config-plugin-not-found/.atlaspackrc +6 -0
  176. package/test/fixtures/context/package.json +8 -0
  177. package/test/fixtures/custom-format-infer-ext/package.json +6 -0
  178. package/test/fixtures/custom-format-infer-type/package.json +7 -0
  179. package/test/fixtures/custom-format-mismatch/package.json +8 -0
  180. package/test/fixtures/custom-targets/package.json +20 -0
  181. package/test/fixtures/custom-targets-distdir/package.json +11 -0
  182. package/test/fixtures/duplicate-targets/package.json +4 -0
  183. package/test/fixtures/glob-like/[entry].js +0 -0
  184. package/test/fixtures/invalid-distpath/package.json +11 -0
  185. package/test/fixtures/invalid-engines/package.json +12 -0
  186. package/test/fixtures/invalid-source-missing/package.json +5 -0
  187. package/test/fixtures/invalid-source-not-file/package.json +5 -0
  188. package/test/fixtures/invalid-source-not-file/src/index.js +1 -0
  189. package/test/fixtures/invalid-target-source-missing/package.json +9 -0
  190. package/test/fixtures/invalid-target-source-not-file/package.json +9 -0
  191. package/test/fixtures/invalid-target-source-not-file/src/index.js +1 -0
  192. package/test/fixtures/invalid-targets/package.json +19 -0
  193. package/test/fixtures/library-custom-scopehoist/package.json +9 -0
  194. package/test/fixtures/library-scopehoist/package.json +8 -0
  195. package/test/fixtures/local-plugin-config-pkg/.atlaspackrc +3 -0
  196. package/test/fixtures/local-plugin-config-pkg/node_modules/atlaspack-config-local/index.json +8 -0
  197. package/test/fixtures/local-plugin-config-pkg/node_modules/atlaspack-config-local/local-plugin.js +7 -0
  198. package/test/fixtures/local-plugin-config-pkg/node_modules/atlaspack-config-local/package.json +7 -0
  199. package/test/fixtures/main-format-mismatch/package.json +8 -0
  200. package/test/fixtures/main-global/package.json +8 -0
  201. package/test/fixtures/main-mjs/package.json +8 -0
  202. package/test/fixtures/module-a.js +1 -0
  203. package/test/fixtures/module-b.js +1 -0
  204. package/test/fixtures/plugins/local-plugin.js +7 -0
  205. package/test/fixtures/plugins/node_modules/atlaspack-transformer-bad-engines/index.js +7 -0
  206. package/test/fixtures/plugins/node_modules/atlaspack-transformer-bad-engines/package.json +7 -0
  207. package/test/fixtures/plugins/node_modules/atlaspack-transformer-no-engines/index.js +7 -0
  208. package/test/fixtures/plugins/node_modules/atlaspack-transformer-no-engines/package.json +4 -0
  209. package/test/fixtures/targets-default-distdir-none/package.json +5 -0
  210. package/test/fixtures/targets-default-distdir-one/package.json +8 -0
  211. package/test/fixtures/targets-default-distdir-two/package.json +18 -0
  212. package/test/requests/ConfigRequest.test.js +254 -0
  213. package/test/serializer.test.js +302 -0
  214. package/test/test-utils.js +80 -0
  215. package/test/utils.test.js +43 -0
@@ -0,0 +1,1697 @@
1
+ // @flow strict-local
2
+
3
+ import invariant, {AssertionError} from 'assert';
4
+ import path from 'path';
5
+
6
+ import type {Cache} from '@atlaspack/cache';
7
+ import {ContentGraph} from '@atlaspack/graph';
8
+ import type {
9
+ ContentGraphOpts,
10
+ ContentKey,
11
+ NodeId,
12
+ SerializedContentGraph,
13
+ } from '@atlaspack/graph';
14
+ import logger from '@atlaspack/logger';
15
+ import {hashString} from '@atlaspack/rust';
16
+ import type {Async, EnvMap} from '@atlaspack/types';
17
+ import {
18
+ type Deferred,
19
+ isGlobMatch,
20
+ isDirectoryInside,
21
+ makeDeferredWithPromise,
22
+ PromiseQueue,
23
+ } from '@atlaspack/utils';
24
+ import type {Options as WatcherOptions, Event} from '@parcel/watcher';
25
+ import type WorkerFarm from '@atlaspack/workers';
26
+ import nullthrows from 'nullthrows';
27
+
28
+ import {
29
+ ATLASPACK_VERSION,
30
+ VALID,
31
+ INITIAL_BUILD,
32
+ FILE_CREATE,
33
+ FILE_UPDATE,
34
+ FILE_DELETE,
35
+ ENV_CHANGE,
36
+ OPTION_CHANGE,
37
+ STARTUP,
38
+ ERROR,
39
+ } from './constants';
40
+ import type {AtlaspackV3} from './atlaspack-v3/AtlaspackV3';
41
+ import {
42
+ type ProjectPath,
43
+ fromProjectPathRelative,
44
+ toProjectPathUnsafe,
45
+ toProjectPath,
46
+ } from './projectPath';
47
+ import {report} from './ReporterRunner';
48
+ import {getConfigKeyContentHash} from './requests/ConfigRequest';
49
+ import type {AssetGraphRequestResult} from './requests/AssetGraphRequest';
50
+ import type {PackageRequestResult} from './requests/PackageRequest';
51
+ import type {ConfigRequestResult} from './requests/ConfigRequest';
52
+ import type {DevDepRequestResult} from './requests/DevDepRequest';
53
+ import type {WriteBundlesRequestResult} from './requests/WriteBundlesRequest';
54
+ import type {WriteBundleRequestResult} from './requests/WriteBundleRequest';
55
+ import type {TargetRequestResult} from './requests/TargetRequest';
56
+ import type {PathRequestResult} from './requests/PathRequest';
57
+ import type {AtlaspackConfigRequestResult} from './requests/AtlaspackConfigRequest';
58
+ import type {AtlaspackBuildRequestResult} from './requests/AtlaspackBuildRequest';
59
+ import type {EntryRequestResult} from './requests/EntryRequest';
60
+ import type {BundleGraphResult} from './requests/BundleGraphRequest';
61
+ import {deserialize, serialize} from './serializer';
62
+ import type {
63
+ AssetRequestResult,
64
+ AtlaspackOptions,
65
+ RequestInvalidation,
66
+ InternalFileCreateInvalidation,
67
+ InternalGlob,
68
+ } from './types';
69
+ import {BuildAbortError, assertSignalNotAborted, hashFromOption} from './utils';
70
+
71
+ export const requestGraphEdgeTypes = {
72
+ subrequest: 2,
73
+ invalidated_by_update: 3,
74
+ invalidated_by_delete: 4,
75
+ invalidated_by_create: 5,
76
+ invalidated_by_create_above: 6,
77
+ dirname: 7,
78
+ };
79
+
80
+ class FSBailoutError extends Error {
81
+ name: string = 'FSBailoutError';
82
+ }
83
+
84
+ export type RequestGraphEdgeType = $Values<typeof requestGraphEdgeTypes>;
85
+
86
+ type RequestGraphOpts = {|
87
+ ...ContentGraphOpts<RequestGraphNode, RequestGraphEdgeType>,
88
+ invalidNodeIds: Set<NodeId>,
89
+ incompleteNodeIds: Set<NodeId>,
90
+ globNodeIds: Set<NodeId>,
91
+ envNodeIds: Set<NodeId>,
92
+ optionNodeIds: Set<NodeId>,
93
+ unpredicatableNodeIds: Set<NodeId>,
94
+ invalidateOnBuildNodeIds: Set<NodeId>,
95
+ cachedRequestChunks: Set<number>,
96
+ configKeyNodes: Map<ProjectPath, Set<NodeId>>,
97
+ |};
98
+
99
+ type SerializedRequestGraph = {|
100
+ ...SerializedContentGraph<RequestGraphNode, RequestGraphEdgeType>,
101
+ invalidNodeIds: Set<NodeId>,
102
+ incompleteNodeIds: Set<NodeId>,
103
+ globNodeIds: Set<NodeId>,
104
+ envNodeIds: Set<NodeId>,
105
+ optionNodeIds: Set<NodeId>,
106
+ unpredicatableNodeIds: Set<NodeId>,
107
+ invalidateOnBuildNodeIds: Set<NodeId>,
108
+ cachedRequestChunks: Set<number>,
109
+ configKeyNodes: Map<ProjectPath, Set<NodeId>>,
110
+ |};
111
+
112
+ const FILE: 0 = 0;
113
+ const REQUEST: 1 = 1;
114
+ const FILE_NAME: 2 = 2;
115
+ const ENV: 3 = 3;
116
+ const OPTION: 4 = 4;
117
+ const GLOB: 5 = 5;
118
+ const CONFIG_KEY: 6 = 6;
119
+
120
+ type FileNode = {|id: ContentKey, +type: typeof FILE|};
121
+
122
+ type GlobNode = {|id: ContentKey, +type: typeof GLOB, value: InternalGlob|};
123
+
124
+ type FileNameNode = {|
125
+ id: ContentKey,
126
+ +type: typeof FILE_NAME,
127
+ |};
128
+
129
+ type EnvNode = {|
130
+ id: ContentKey,
131
+ +type: typeof ENV,
132
+ value: string | void,
133
+ |};
134
+
135
+ type OptionNode = {|
136
+ id: ContentKey,
137
+ +type: typeof OPTION,
138
+ hash: string,
139
+ |};
140
+
141
+ type ConfigKeyNode = {|
142
+ id: ContentKey,
143
+ +type: typeof CONFIG_KEY,
144
+ configKey: string,
145
+ contentHash: string,
146
+ |};
147
+
148
+ type Request<TInput, TResult> = {|
149
+ id: string,
150
+ +type: RequestType,
151
+ input: TInput,
152
+ run: ({|input: TInput, ...StaticRunOpts<TResult>|}) => Async<TResult>,
153
+ |};
154
+
155
+ export type RequestResult =
156
+ | AssetGraphRequestResult
157
+ | PackageRequestResult
158
+ | ConfigRequestResult
159
+ | DevDepRequestResult
160
+ | WriteBundlesRequestResult
161
+ | WriteBundleRequestResult
162
+ | TargetRequestResult
163
+ | PathRequestResult
164
+ | AtlaspackConfigRequestResult
165
+ | AtlaspackBuildRequestResult
166
+ | EntryRequestResult
167
+ | BundleGraphResult
168
+ | AssetRequestResult;
169
+
170
+ type InvalidateReason = number;
171
+ type RequestNode = {|
172
+ id: ContentKey,
173
+ +type: typeof REQUEST,
174
+ +requestType: RequestType,
175
+ invalidateReason: InvalidateReason,
176
+ result?: RequestResult,
177
+ resultCacheKey?: ?string,
178
+ hash?: string,
179
+ |};
180
+
181
+ export const requestTypes = {
182
+ atlaspack_build_request: 1,
183
+ bundle_graph_request: 2,
184
+ asset_graph_request: 3,
185
+ entry_request: 4,
186
+ target_request: 5,
187
+ atlaspack_config_request: 6,
188
+ path_request: 7,
189
+ dev_dep_request: 8,
190
+ asset_request: 9,
191
+ config_request: 10,
192
+ write_bundles_request: 11,
193
+ package_request: 12,
194
+ write_bundle_request: 13,
195
+ validation_request: 14,
196
+ };
197
+
198
+ type RequestType = $Values<typeof requestTypes>;
199
+ type RequestTypeName = $Keys<typeof requestTypes>;
200
+
201
+ type RequestGraphNode =
202
+ | RequestNode
203
+ | FileNode
204
+ | GlobNode
205
+ | FileNameNode
206
+ | EnvNode
207
+ | OptionNode
208
+ | ConfigKeyNode;
209
+
210
+ export type RunAPI<TResult: RequestResult> = {|
211
+ invalidateOnFileCreate: InternalFileCreateInvalidation => void,
212
+ invalidateOnFileDelete: ProjectPath => void,
213
+ invalidateOnFileUpdate: ProjectPath => void,
214
+ invalidateOnConfigKeyChange: (
215
+ filePath: ProjectPath,
216
+ configKey: string,
217
+ contentHash: string,
218
+ ) => void,
219
+ invalidateOnStartup: () => void,
220
+ invalidateOnBuild: () => void,
221
+ invalidateOnEnvChange: string => void,
222
+ invalidateOnOptionChange: string => void,
223
+ getInvalidations(): Array<RequestInvalidation>,
224
+ storeResult(result: TResult, cacheKey?: string): void,
225
+ getRequestResult<T: RequestResult>(contentKey: ContentKey): Async<?T>,
226
+ getPreviousResult<T: RequestResult>(ifMatch?: string): Async<?T>,
227
+ getSubRequests(): Array<RequestNode>,
228
+ getInvalidSubRequests(): Array<RequestNode>,
229
+ canSkipSubrequest(ContentKey): boolean,
230
+ runRequest: <TInput, TResult: RequestResult>(
231
+ subRequest: Request<TInput, TResult>,
232
+ opts?: RunRequestOpts,
233
+ ) => Promise<TResult>,
234
+ |};
235
+
236
+ type RunRequestOpts = {|
237
+ force: boolean,
238
+ |};
239
+
240
+ export type StaticRunOpts<TResult> = {|
241
+ api: RunAPI<TResult>,
242
+ farm: WorkerFarm,
243
+ invalidateReason: InvalidateReason,
244
+ options: AtlaspackOptions,
245
+ rustAtlaspack: ?AtlaspackV3,
246
+ |};
247
+
248
+ const nodeFromFilePath = (filePath: ProjectPath): RequestGraphNode => ({
249
+ id: fromProjectPathRelative(filePath),
250
+ type: FILE,
251
+ });
252
+ const nodeFromGlob = (glob: InternalGlob): RequestGraphNode => ({
253
+ id: fromProjectPathRelative(glob),
254
+ type: GLOB,
255
+ value: glob,
256
+ });
257
+ const nodeFromFileName = (fileName: string): RequestGraphNode => ({
258
+ id: 'file_name:' + fileName,
259
+ type: FILE_NAME,
260
+ });
261
+
262
+ const nodeFromRequest = (request: RequestNode): RequestGraphNode => ({
263
+ id: request.id,
264
+ type: REQUEST,
265
+ requestType: request.requestType,
266
+ invalidateReason: INITIAL_BUILD,
267
+ });
268
+
269
+ const nodeFromEnv = (env: string, value: string | void): RequestGraphNode => ({
270
+ id: 'env:' + env,
271
+ type: ENV,
272
+ value,
273
+ });
274
+
275
+ const nodeFromOption = (option: string, value: mixed): RequestGraphNode => ({
276
+ id: 'option:' + option,
277
+ type: OPTION,
278
+ hash: hashFromOption(value),
279
+ });
280
+
281
+ const nodeFromConfigKey = (
282
+ fileName: ProjectPath,
283
+ configKey: string,
284
+ contentHash: string,
285
+ ): RequestGraphNode => ({
286
+ id: `config_key:${fromProjectPathRelative(fileName)}:${configKey}`,
287
+ type: CONFIG_KEY,
288
+ configKey,
289
+ contentHash,
290
+ });
291
+
292
+ const keyFromEnvContentKey = (contentKey: ContentKey): string =>
293
+ contentKey.slice('env:'.length);
294
+
295
+ const keyFromOptionContentKey = (contentKey: ContentKey): string =>
296
+ contentKey.slice('option:'.length);
297
+
298
+ // This constant is chosen by local profiling the time to serialise n nodes and tuning until an average time of ~50 ms per blob.
299
+ // The goal is to free up the event loop periodically to allow interruption by the user.
300
+ const NODES_PER_BLOB = 2 ** 14;
301
+
302
+ export class RequestGraph extends ContentGraph<
303
+ RequestGraphNode,
304
+ RequestGraphEdgeType,
305
+ > {
306
+ invalidNodeIds: Set<NodeId> = new Set();
307
+ incompleteNodeIds: Set<NodeId> = new Set();
308
+ incompleteNodePromises: Map<NodeId, Promise<boolean>> = new Map();
309
+ globNodeIds: Set<NodeId> = new Set();
310
+ envNodeIds: Set<NodeId> = new Set();
311
+ optionNodeIds: Set<NodeId> = new Set();
312
+ // Unpredictable nodes are requests that cannot be predicted whether they should rerun based on
313
+ // filesystem changes alone. They should rerun on each startup of Atlaspack.
314
+ unpredicatableNodeIds: Set<NodeId> = new Set();
315
+ invalidateOnBuildNodeIds: Set<NodeId> = new Set();
316
+ cachedRequestChunks: Set<number> = new Set();
317
+ configKeyNodes: Map<ProjectPath, Set<NodeId>> = new Map();
318
+ nodesPerBlob: number = NODES_PER_BLOB;
319
+
320
+ // $FlowFixMe[prop-missing]
321
+ static deserialize(opts: RequestGraphOpts): RequestGraph {
322
+ // $FlowFixMe[prop-missing]
323
+ let deserialized = new RequestGraph(opts);
324
+ deserialized.invalidNodeIds = opts.invalidNodeIds;
325
+ deserialized.incompleteNodeIds = opts.incompleteNodeIds;
326
+ deserialized.globNodeIds = opts.globNodeIds;
327
+ deserialized.envNodeIds = opts.envNodeIds;
328
+ deserialized.optionNodeIds = opts.optionNodeIds;
329
+ deserialized.unpredicatableNodeIds = opts.unpredicatableNodeIds;
330
+ deserialized.invalidateOnBuildNodeIds = opts.invalidateOnBuildNodeIds;
331
+ deserialized.cachedRequestChunks = opts.cachedRequestChunks;
332
+ deserialized.configKeyNodes = opts.configKeyNodes;
333
+ return deserialized;
334
+ }
335
+
336
+ // $FlowFixMe[prop-missing]
337
+ serialize(): SerializedRequestGraph {
338
+ return {
339
+ ...super.serialize(),
340
+ invalidNodeIds: this.invalidNodeIds,
341
+ incompleteNodeIds: this.incompleteNodeIds,
342
+ globNodeIds: this.globNodeIds,
343
+ envNodeIds: this.envNodeIds,
344
+ optionNodeIds: this.optionNodeIds,
345
+ unpredicatableNodeIds: this.unpredicatableNodeIds,
346
+ invalidateOnBuildNodeIds: this.invalidateOnBuildNodeIds,
347
+ cachedRequestChunks: this.cachedRequestChunks,
348
+ configKeyNodes: this.configKeyNodes,
349
+ };
350
+ }
351
+
352
+ // addNode for RequestGraph should not override the value if added multiple times
353
+ addNode(node: RequestGraphNode): NodeId {
354
+ let nodeId = this._contentKeyToNodeId.get(node.id);
355
+ if (nodeId != null) {
356
+ return nodeId;
357
+ }
358
+
359
+ nodeId = super.addNodeByContentKey(node.id, node);
360
+ if (node.type === GLOB) {
361
+ this.globNodeIds.add(nodeId);
362
+ } else if (node.type === ENV) {
363
+ this.envNodeIds.add(nodeId);
364
+ } else if (node.type === OPTION) {
365
+ this.optionNodeIds.add(nodeId);
366
+ }
367
+
368
+ this.removeCachedRequestChunkForNode(nodeId);
369
+
370
+ return nodeId;
371
+ }
372
+
373
+ removeNode(nodeId: NodeId): void {
374
+ this.invalidNodeIds.delete(nodeId);
375
+ this.incompleteNodeIds.delete(nodeId);
376
+ this.incompleteNodePromises.delete(nodeId);
377
+ this.unpredicatableNodeIds.delete(nodeId);
378
+ this.invalidateOnBuildNodeIds.delete(nodeId);
379
+ let node = nullthrows(this.getNode(nodeId));
380
+ if (node.type === GLOB) {
381
+ this.globNodeIds.delete(nodeId);
382
+ } else if (node.type === ENV) {
383
+ this.envNodeIds.delete(nodeId);
384
+ } else if (node.type === OPTION) {
385
+ this.optionNodeIds.delete(nodeId);
386
+ } else if (node.type === CONFIG_KEY) {
387
+ for (let configKeyNodes of this.configKeyNodes.values()) {
388
+ configKeyNodes.delete(nodeId);
389
+ }
390
+ }
391
+ return super.removeNode(nodeId);
392
+ }
393
+
394
+ getRequestNode(nodeId: NodeId): RequestNode {
395
+ let node = nullthrows(this.getNode(nodeId));
396
+
397
+ if (node.type === REQUEST) {
398
+ return node;
399
+ }
400
+
401
+ throw new AssertionError({
402
+ message: `Expected a request node: ${
403
+ node.type
404
+ } (${typeof node.type}) does not equal ${REQUEST} (${typeof REQUEST}).`,
405
+ expected: REQUEST,
406
+ actual: node.type,
407
+ });
408
+ }
409
+
410
+ replaceSubrequests(
411
+ requestNodeId: NodeId,
412
+ subrequestContentKeys: Array<ContentKey>,
413
+ ) {
414
+ let subrequestNodeIds = [];
415
+ for (let key of subrequestContentKeys) {
416
+ if (this.hasContentKey(key)) {
417
+ subrequestNodeIds.push(this.getNodeIdByContentKey(key));
418
+ }
419
+ }
420
+
421
+ this.replaceNodeIdsConnectedTo(
422
+ requestNodeId,
423
+ subrequestNodeIds,
424
+ null,
425
+ requestGraphEdgeTypes.subrequest,
426
+ );
427
+ }
428
+
429
+ invalidateNode(nodeId: NodeId, reason: InvalidateReason) {
430
+ let node = nullthrows(this.getNode(nodeId));
431
+ invariant(node.type === REQUEST);
432
+ node.invalidateReason |= reason;
433
+ this.invalidNodeIds.add(nodeId);
434
+
435
+ let parentNodes = this.getNodeIdsConnectedTo(
436
+ nodeId,
437
+ requestGraphEdgeTypes.subrequest,
438
+ );
439
+ for (let parentNode of parentNodes) {
440
+ this.invalidateNode(parentNode, reason);
441
+ }
442
+
443
+ // If the node is invalidated, the cached request chunk on disk needs to be re-written
444
+ this.removeCachedRequestChunkForNode(nodeId);
445
+ }
446
+
447
+ invalidateUnpredictableNodes() {
448
+ for (let nodeId of this.unpredicatableNodeIds) {
449
+ let node = nullthrows(this.getNode(nodeId));
450
+ invariant(node.type !== FILE && node.type !== GLOB);
451
+ this.invalidateNode(nodeId, STARTUP);
452
+ }
453
+ }
454
+
455
+ invalidateOnBuildNodes() {
456
+ for (let nodeId of this.invalidateOnBuildNodeIds) {
457
+ let node = nullthrows(this.getNode(nodeId));
458
+ invariant(node.type !== FILE && node.type !== GLOB);
459
+ this.invalidateNode(nodeId, STARTUP);
460
+ }
461
+ }
462
+
463
+ invalidateEnvNodes(env: EnvMap) {
464
+ for (let nodeId of this.envNodeIds) {
465
+ let node = nullthrows(this.getNode(nodeId));
466
+ invariant(node.type === ENV);
467
+ if (env[keyFromEnvContentKey(node.id)] !== node.value) {
468
+ let parentNodes = this.getNodeIdsConnectedTo(
469
+ nodeId,
470
+ requestGraphEdgeTypes.invalidated_by_update,
471
+ );
472
+ for (let parentNode of parentNodes) {
473
+ this.invalidateNode(parentNode, ENV_CHANGE);
474
+ }
475
+ }
476
+ }
477
+ }
478
+
479
+ invalidateOptionNodes(options: AtlaspackOptions) {
480
+ for (let nodeId of this.optionNodeIds) {
481
+ let node = nullthrows(this.getNode(nodeId));
482
+ invariant(node.type === OPTION);
483
+ if (
484
+ hashFromOption(options[keyFromOptionContentKey(node.id)]) !== node.hash
485
+ ) {
486
+ let parentNodes = this.getNodeIdsConnectedTo(
487
+ nodeId,
488
+ requestGraphEdgeTypes.invalidated_by_update,
489
+ );
490
+ for (let parentNode of parentNodes) {
491
+ this.invalidateNode(parentNode, OPTION_CHANGE);
492
+ }
493
+ }
494
+ }
495
+ }
496
+
497
+ invalidateOnConfigKeyChange(
498
+ requestNodeId: NodeId,
499
+ filePath: ProjectPath,
500
+ configKey: string,
501
+ contentHash: string,
502
+ ) {
503
+ let configKeyNodeId = this.addNode(
504
+ nodeFromConfigKey(filePath, configKey, contentHash),
505
+ );
506
+ let nodes = this.configKeyNodes.get(filePath);
507
+
508
+ if (!nodes) {
509
+ nodes = new Set();
510
+ this.configKeyNodes.set(filePath, nodes);
511
+ }
512
+
513
+ nodes.add(configKeyNodeId);
514
+
515
+ if (
516
+ !this.hasEdge(
517
+ requestNodeId,
518
+ configKeyNodeId,
519
+ requestGraphEdgeTypes.invalidated_by_update,
520
+ )
521
+ ) {
522
+ this.addEdge(
523
+ requestNodeId,
524
+ configKeyNodeId,
525
+ // Store as an update edge, but file deletes are handled too
526
+ requestGraphEdgeTypes.invalidated_by_update,
527
+ );
528
+ }
529
+ }
530
+
531
+ invalidateOnFileUpdate(requestNodeId: NodeId, filePath: ProjectPath) {
532
+ let fileNodeId = this.addNode(nodeFromFilePath(filePath));
533
+
534
+ if (
535
+ !this.hasEdge(
536
+ requestNodeId,
537
+ fileNodeId,
538
+ requestGraphEdgeTypes.invalidated_by_update,
539
+ )
540
+ ) {
541
+ this.addEdge(
542
+ requestNodeId,
543
+ fileNodeId,
544
+ requestGraphEdgeTypes.invalidated_by_update,
545
+ );
546
+ }
547
+ }
548
+
549
+ invalidateOnFileDelete(requestNodeId: NodeId, filePath: ProjectPath) {
550
+ let fileNodeId = this.addNode(nodeFromFilePath(filePath));
551
+
552
+ if (
553
+ !this.hasEdge(
554
+ requestNodeId,
555
+ fileNodeId,
556
+ requestGraphEdgeTypes.invalidated_by_delete,
557
+ )
558
+ ) {
559
+ this.addEdge(
560
+ requestNodeId,
561
+ fileNodeId,
562
+ requestGraphEdgeTypes.invalidated_by_delete,
563
+ );
564
+ }
565
+ }
566
+
567
+ invalidateOnFileCreate(
568
+ requestNodeId: NodeId,
569
+ input: InternalFileCreateInvalidation,
570
+ ) {
571
+ let node;
572
+ if (input.glob != null) {
573
+ node = nodeFromGlob(input.glob);
574
+ } else if (input.fileName != null && input.aboveFilePath != null) {
575
+ let aboveFilePath = input.aboveFilePath;
576
+
577
+ // Create nodes and edges for each part of the filename pattern.
578
+ // For example, 'node_modules/foo' would create two nodes and one edge.
579
+ // This creates a sort of trie structure within the graph that can be
580
+ // quickly matched by following the edges. This is also memory efficient
581
+ // since common sub-paths (e.g. 'node_modules') are deduplicated.
582
+ let parts = input.fileName.split('/').reverse();
583
+ let lastNodeId;
584
+ for (let part of parts) {
585
+ let fileNameNode = nodeFromFileName(part);
586
+
587
+ let fileNameNodeId = this.addNode(fileNameNode);
588
+ if (
589
+ lastNodeId != null &&
590
+ !this.hasEdge(
591
+ lastNodeId,
592
+ fileNameNodeId,
593
+ requestGraphEdgeTypes.dirname,
594
+ )
595
+ ) {
596
+ this.addEdge(
597
+ lastNodeId,
598
+ fileNameNodeId,
599
+ requestGraphEdgeTypes.dirname,
600
+ );
601
+ }
602
+
603
+ lastNodeId = fileNameNodeId;
604
+ }
605
+
606
+ // The `aboveFilePath` condition asserts that requests are only invalidated
607
+ // if the file being created is "above" it in the filesystem (e.g. the file
608
+ // is created in a parent directory). There is likely to already be a node
609
+ // for this file in the graph (e.g. the source file) that we can reuse for this.
610
+ node = nodeFromFilePath(aboveFilePath);
611
+ let nodeId = this.addNode(node);
612
+
613
+ // Now create an edge from the `aboveFilePath` node to the first file_name node
614
+ // in the chain created above, and an edge from the last node in the chain back to
615
+ // the `aboveFilePath` node. When matching, we will start from the first node in
616
+ // the chain, and continue following it to parent directories until there is an
617
+ // edge pointing an `aboveFilePath` node that also points to the start of the chain.
618
+ // This indicates a complete match, and any requests attached to the `aboveFilePath`
619
+ // node will be invalidated.
620
+ let firstId = 'file_name:' + parts[0];
621
+ let firstNodeId = this.getNodeIdByContentKey(firstId);
622
+ if (
623
+ !this.hasEdge(
624
+ nodeId,
625
+ firstNodeId,
626
+ requestGraphEdgeTypes.invalidated_by_create_above,
627
+ )
628
+ ) {
629
+ this.addEdge(
630
+ nodeId,
631
+ firstNodeId,
632
+ requestGraphEdgeTypes.invalidated_by_create_above,
633
+ );
634
+ }
635
+
636
+ invariant(lastNodeId != null);
637
+ if (
638
+ !this.hasEdge(
639
+ lastNodeId,
640
+ nodeId,
641
+ requestGraphEdgeTypes.invalidated_by_create_above,
642
+ )
643
+ ) {
644
+ this.addEdge(
645
+ lastNodeId,
646
+ nodeId,
647
+ requestGraphEdgeTypes.invalidated_by_create_above,
648
+ );
649
+ }
650
+ } else if (input.filePath != null) {
651
+ node = nodeFromFilePath(input.filePath);
652
+ } else {
653
+ throw new Error('Invalid invalidation');
654
+ }
655
+
656
+ let nodeId = this.addNode(node);
657
+ if (
658
+ !this.hasEdge(
659
+ requestNodeId,
660
+ nodeId,
661
+ requestGraphEdgeTypes.invalidated_by_create,
662
+ )
663
+ ) {
664
+ this.addEdge(
665
+ requestNodeId,
666
+ nodeId,
667
+ requestGraphEdgeTypes.invalidated_by_create,
668
+ );
669
+ }
670
+ }
671
+
672
+ invalidateOnStartup(requestNodeId: NodeId) {
673
+ this.getRequestNode(requestNodeId);
674
+ this.unpredicatableNodeIds.add(requestNodeId);
675
+ }
676
+
677
+ invalidateOnBuild(requestNodeId: NodeId) {
678
+ this.getRequestNode(requestNodeId);
679
+ this.invalidateOnBuildNodeIds.add(requestNodeId);
680
+ }
681
+
682
+ invalidateOnEnvChange(
683
+ requestNodeId: NodeId,
684
+ env: string,
685
+ value: string | void,
686
+ ) {
687
+ let envNode = nodeFromEnv(env, value);
688
+ let envNodeId = this.addNode(envNode);
689
+
690
+ if (
691
+ !this.hasEdge(
692
+ requestNodeId,
693
+ envNodeId,
694
+ requestGraphEdgeTypes.invalidated_by_update,
695
+ )
696
+ ) {
697
+ this.addEdge(
698
+ requestNodeId,
699
+ envNodeId,
700
+ requestGraphEdgeTypes.invalidated_by_update,
701
+ );
702
+ }
703
+ }
704
+
705
+ invalidateOnOptionChange(
706
+ requestNodeId: NodeId,
707
+ option: string,
708
+ value: mixed,
709
+ ) {
710
+ let optionNode = nodeFromOption(option, value);
711
+ let optionNodeId = this.addNode(optionNode);
712
+
713
+ if (
714
+ !this.hasEdge(
715
+ requestNodeId,
716
+ optionNodeId,
717
+ requestGraphEdgeTypes.invalidated_by_update,
718
+ )
719
+ ) {
720
+ this.addEdge(
721
+ requestNodeId,
722
+ optionNodeId,
723
+ requestGraphEdgeTypes.invalidated_by_update,
724
+ );
725
+ }
726
+ }
727
+
728
+ clearInvalidations(nodeId: NodeId) {
729
+ this.unpredicatableNodeIds.delete(nodeId);
730
+ this.invalidateOnBuildNodeIds.delete(nodeId);
731
+ this.replaceNodeIdsConnectedTo(
732
+ nodeId,
733
+ [],
734
+ null,
735
+ requestGraphEdgeTypes.invalidated_by_update,
736
+ );
737
+ this.replaceNodeIdsConnectedTo(
738
+ nodeId,
739
+ [],
740
+ null,
741
+ requestGraphEdgeTypes.invalidated_by_delete,
742
+ );
743
+ this.replaceNodeIdsConnectedTo(
744
+ nodeId,
745
+ [],
746
+ null,
747
+ requestGraphEdgeTypes.invalidated_by_create,
748
+ );
749
+ }
750
+
751
+ getInvalidations(requestNodeId: NodeId): Array<RequestInvalidation> {
752
+ if (!this.hasNode(requestNodeId)) {
753
+ return [];
754
+ }
755
+
756
+ // For now just handling updates. Could add creates/deletes later if needed.
757
+ let invalidations = this.getNodeIdsConnectedFrom(
758
+ requestNodeId,
759
+ requestGraphEdgeTypes.invalidated_by_update,
760
+ );
761
+ return invalidations
762
+ .map(nodeId => {
763
+ let node = nullthrows(this.getNode(nodeId));
764
+ switch (node.type) {
765
+ case FILE:
766
+ return {type: 'file', filePath: toProjectPathUnsafe(node.id)};
767
+ case ENV:
768
+ return {type: 'env', key: keyFromEnvContentKey(node.id)};
769
+ case OPTION:
770
+ return {
771
+ type: 'option',
772
+ key: keyFromOptionContentKey(node.id),
773
+ };
774
+ }
775
+ })
776
+ .filter(Boolean);
777
+ }
778
+
779
+ getSubRequests(requestNodeId: NodeId): Array<RequestNode> {
780
+ if (!this.hasNode(requestNodeId)) {
781
+ return [];
782
+ }
783
+
784
+ let subRequests = this.getNodeIdsConnectedFrom(
785
+ requestNodeId,
786
+ requestGraphEdgeTypes.subrequest,
787
+ );
788
+
789
+ return subRequests.map(nodeId => {
790
+ let node = nullthrows(this.getNode(nodeId));
791
+ invariant(node.type === REQUEST);
792
+ return node;
793
+ });
794
+ }
795
+
796
+ getInvalidSubRequests(requestNodeId: NodeId): Array<RequestNode> {
797
+ if (!this.hasNode(requestNodeId)) {
798
+ return [];
799
+ }
800
+
801
+ let subRequests = this.getNodeIdsConnectedFrom(
802
+ requestNodeId,
803
+ requestGraphEdgeTypes.subrequest,
804
+ );
805
+
806
+ return subRequests
807
+ .filter(id => this.invalidNodeIds.has(id))
808
+ .map(nodeId => {
809
+ let node = nullthrows(this.getNode(nodeId));
810
+ invariant(node.type === REQUEST);
811
+ return node;
812
+ });
813
+ }
814
+
815
+ invalidateFileNameNode(
816
+ node: FileNameNode,
817
+ filePath: ProjectPath,
818
+ matchNodes: Array<FileNode>,
819
+ ) {
820
+ // If there is an edge between this file_name node and one of the original file nodes pointed to
821
+ // by the original file_name node, and the matched node is inside the current directory, invalidate
822
+ // all connected requests pointed to by the file node.
823
+ let dirname = path.dirname(fromProjectPathRelative(filePath));
824
+
825
+ let nodeId = this.getNodeIdByContentKey(node.id);
826
+ for (let matchNode of matchNodes) {
827
+ let matchNodeId = this.getNodeIdByContentKey(matchNode.id);
828
+ if (
829
+ this.hasEdge(
830
+ nodeId,
831
+ matchNodeId,
832
+ requestGraphEdgeTypes.invalidated_by_create_above,
833
+ ) &&
834
+ isDirectoryInside(
835
+ fromProjectPathRelative(toProjectPathUnsafe(matchNode.id)),
836
+ dirname,
837
+ )
838
+ ) {
839
+ let connectedNodes = this.getNodeIdsConnectedTo(
840
+ matchNodeId,
841
+ requestGraphEdgeTypes.invalidated_by_create,
842
+ );
843
+ for (let connectedNode of connectedNodes) {
844
+ this.invalidateNode(connectedNode, FILE_CREATE);
845
+ }
846
+ }
847
+ }
848
+
849
+ // Find the `file_name` node for the parent directory and
850
+ // recursively invalidate connected requests as described above.
851
+ let basename = path.basename(dirname);
852
+ let contentKey = 'file_name:' + basename;
853
+ if (this.hasContentKey(contentKey)) {
854
+ if (
855
+ this.hasEdge(
856
+ nodeId,
857
+ this.getNodeIdByContentKey(contentKey),
858
+ requestGraphEdgeTypes.dirname,
859
+ )
860
+ ) {
861
+ let parent = nullthrows(this.getNodeByContentKey(contentKey));
862
+ invariant(parent.type === FILE_NAME);
863
+ this.invalidateFileNameNode(
864
+ parent,
865
+ toProjectPathUnsafe(dirname),
866
+ matchNodes,
867
+ );
868
+ }
869
+ }
870
+ }
871
+
872
+ async respondToFSEvents(
873
+ events: Array<Event>,
874
+ options: AtlaspackOptions,
875
+ threshold: number,
876
+ ): Async<boolean> {
877
+ let didInvalidate = false;
878
+ let count = 0;
879
+ let predictedTime = 0;
880
+ let startTime = Date.now();
881
+
882
+ for (let {path: _path, type} of events) {
883
+ if (++count === 256) {
884
+ let duration = Date.now() - startTime;
885
+ predictedTime = duration * (events.length >> 8);
886
+ if (predictedTime > threshold) {
887
+ logger.warn({
888
+ origin: '@atlaspack/core',
889
+ message:
890
+ 'Building with clean cache. Cache invalidation took too long.',
891
+ meta: {
892
+ trackableEvent: 'cache_invalidation_timeout',
893
+ watcherEventCount: events.length,
894
+ predictedTime,
895
+ },
896
+ });
897
+ throw new FSBailoutError(
898
+ 'Responding to file system events exceeded threshold, start with empty cache.',
899
+ );
900
+ }
901
+ }
902
+
903
+ let _filePath = toProjectPath(options.projectRoot, _path);
904
+ let filePath = fromProjectPathRelative(_filePath);
905
+ let hasFileRequest = this.hasContentKey(filePath);
906
+
907
+ // If we see a 'create' event for the project root itself,
908
+ // this means the project root was moved and we need to
909
+ // re-run all requests.
910
+ if (type === 'create' && filePath === '') {
911
+ logger.verbose({
912
+ origin: '@atlaspack/core',
913
+ message:
914
+ 'Watcher reported project root create event. Invalidate all nodes.',
915
+ meta: {
916
+ trackableEvent: 'project_root_create',
917
+ },
918
+ });
919
+ for (let [id, node] of this.nodes.entries()) {
920
+ if (node?.type === REQUEST) {
921
+ this.invalidNodeIds.add(id);
922
+ }
923
+ }
924
+ return true;
925
+ }
926
+
927
+ // sometimes mac os reports update events as create events.
928
+ // if it was a create event, but the file already exists in the graph,
929
+ // then also invalidate nodes connected by invalidated_by_update edges.
930
+ if (hasFileRequest && (type === 'create' || type === 'update')) {
931
+ let nodeId = this.getNodeIdByContentKey(filePath);
932
+ let nodes = this.getNodeIdsConnectedTo(
933
+ nodeId,
934
+ requestGraphEdgeTypes.invalidated_by_update,
935
+ );
936
+
937
+ for (let connectedNode of nodes) {
938
+ didInvalidate = true;
939
+ this.invalidateNode(connectedNode, FILE_UPDATE);
940
+ }
941
+
942
+ if (type === 'create') {
943
+ let nodes = this.getNodeIdsConnectedTo(
944
+ nodeId,
945
+ requestGraphEdgeTypes.invalidated_by_create,
946
+ );
947
+ for (let connectedNode of nodes) {
948
+ didInvalidate = true;
949
+ this.invalidateNode(connectedNode, FILE_CREATE);
950
+ }
951
+ }
952
+ } else if (type === 'create') {
953
+ let basename = path.basename(filePath);
954
+ let fileNameNode = this.getNodeByContentKey('file_name:' + basename);
955
+ if (fileNameNode != null && fileNameNode.type === FILE_NAME) {
956
+ let fileNameNodeId = this.getNodeIdByContentKey(
957
+ 'file_name:' + basename,
958
+ );
959
+
960
+ // Find potential file nodes to be invalidated if this file name pattern matches
961
+ let above: Array<FileNode> = [];
962
+ for (const nodeId of this.getNodeIdsConnectedTo(
963
+ fileNameNodeId,
964
+ requestGraphEdgeTypes.invalidated_by_create_above,
965
+ )) {
966
+ let node = nullthrows(this.getNode(nodeId));
967
+ // these might also be `glob` nodes which get handled below, we only care about files here.
968
+ if (node.type === FILE) {
969
+ above.push(node);
970
+ }
971
+ }
972
+
973
+ if (above.length > 0) {
974
+ didInvalidate = true;
975
+ this.invalidateFileNameNode(fileNameNode, _filePath, above);
976
+ }
977
+ }
978
+
979
+ for (let globeNodeId of this.globNodeIds) {
980
+ let globNode = this.getNode(globeNodeId);
981
+ invariant(globNode && globNode.type === GLOB);
982
+
983
+ if (isGlobMatch(filePath, fromProjectPathRelative(globNode.value))) {
984
+ let connectedNodes = this.getNodeIdsConnectedTo(
985
+ globeNodeId,
986
+ requestGraphEdgeTypes.invalidated_by_create,
987
+ );
988
+ for (let connectedNode of connectedNodes) {
989
+ didInvalidate = true;
990
+ this.invalidateNode(connectedNode, FILE_CREATE);
991
+ }
992
+ }
993
+ }
994
+ } else if (hasFileRequest && type === 'delete') {
995
+ let nodeId = this.getNodeIdByContentKey(filePath);
996
+ for (let connectedNode of this.getNodeIdsConnectedTo(
997
+ nodeId,
998
+ requestGraphEdgeTypes.invalidated_by_delete,
999
+ )) {
1000
+ didInvalidate = true;
1001
+ this.invalidateNode(connectedNode, FILE_DELETE);
1002
+ }
1003
+
1004
+ // Delete the file node since it doesn't exist anymore.
1005
+ // This ensures that files that don't exist aren't sent
1006
+ // to requests as invalidations for future requests.
1007
+ this.removeNode(nodeId);
1008
+ }
1009
+
1010
+ let configKeyNodes = this.configKeyNodes.get(_filePath);
1011
+ if (configKeyNodes && (type === 'delete' || type === 'update')) {
1012
+ for (let nodeId of configKeyNodes) {
1013
+ let isInvalid = type === 'delete';
1014
+
1015
+ if (type === 'update') {
1016
+ let node = this.getNode(nodeId);
1017
+ invariant(node && node.type === CONFIG_KEY);
1018
+
1019
+ let contentHash = await getConfigKeyContentHash(
1020
+ _filePath,
1021
+ node.configKey,
1022
+ options,
1023
+ );
1024
+
1025
+ isInvalid = node.contentHash !== contentHash;
1026
+ }
1027
+
1028
+ if (isInvalid) {
1029
+ for (let connectedNode of this.getNodeIdsConnectedTo(
1030
+ nodeId,
1031
+ requestGraphEdgeTypes.invalidated_by_update,
1032
+ )) {
1033
+ this.invalidateNode(
1034
+ connectedNode,
1035
+ type === 'delete' ? FILE_DELETE : FILE_UPDATE,
1036
+ );
1037
+ }
1038
+ didInvalidate = true;
1039
+ this.removeNode(nodeId);
1040
+ }
1041
+ }
1042
+ }
1043
+ }
1044
+
1045
+ let duration = Date.now() - startTime;
1046
+ logger.verbose({
1047
+ origin: '@atlaspack/core',
1048
+ message: `RequestGraph.respondToFSEvents duration: ${duration}`,
1049
+ meta: {
1050
+ trackableEvent: 'fsevent_response_time',
1051
+ duration,
1052
+ predictedTime,
1053
+ },
1054
+ });
1055
+
1056
+ return didInvalidate && this.invalidNodeIds.size > 0;
1057
+ }
1058
+
1059
+ hasCachedRequestChunk(index: number): boolean {
1060
+ return this.cachedRequestChunks.has(index);
1061
+ }
1062
+
1063
+ setCachedRequestChunk(index: number): void {
1064
+ this.cachedRequestChunks.add(index);
1065
+ }
1066
+
1067
+ removeCachedRequestChunkForNode(nodeId: number): void {
1068
+ this.cachedRequestChunks.delete(Math.floor(nodeId / this.nodesPerBlob));
1069
+ }
1070
+ }
1071
+
1072
+ export default class RequestTracker {
1073
+ graph: RequestGraph;
1074
+ farm: WorkerFarm;
1075
+ options: AtlaspackOptions;
1076
+ rustAtlaspack: ?AtlaspackV3;
1077
+ signal: ?AbortSignal;
1078
+ stats: Map<RequestType, number> = new Map();
1079
+
1080
+ constructor({
1081
+ graph,
1082
+ farm,
1083
+ options,
1084
+ rustAtlaspack,
1085
+ }: {|
1086
+ graph?: RequestGraph,
1087
+ farm: WorkerFarm,
1088
+ options: AtlaspackOptions,
1089
+ rustAtlaspack?: AtlaspackV3,
1090
+ |}) {
1091
+ this.graph = graph || new RequestGraph();
1092
+ this.farm = farm;
1093
+ this.options = options;
1094
+ this.rustAtlaspack = rustAtlaspack;
1095
+ }
1096
+
1097
+ // TODO: refactor (abortcontroller should be created by RequestTracker)
1098
+ setSignal(signal?: AbortSignal) {
1099
+ this.signal = signal;
1100
+ }
1101
+
1102
+ startRequest(request: RequestNode): {|
1103
+ requestNodeId: NodeId,
1104
+ deferred: Deferred<boolean>,
1105
+ |} {
1106
+ let didPreviouslyExist = this.graph.hasContentKey(request.id);
1107
+ let requestNodeId;
1108
+ if (didPreviouslyExist) {
1109
+ requestNodeId = this.graph.getNodeIdByContentKey(request.id);
1110
+ // Clear existing invalidations for the request so that the new
1111
+ // invalidations created during the request replace the existing ones.
1112
+ this.graph.clearInvalidations(requestNodeId);
1113
+ } else {
1114
+ requestNodeId = this.graph.addNode(nodeFromRequest(request));
1115
+ }
1116
+
1117
+ this.graph.incompleteNodeIds.add(requestNodeId);
1118
+ this.graph.invalidNodeIds.delete(requestNodeId);
1119
+
1120
+ let {promise, deferred} = makeDeferredWithPromise();
1121
+ this.graph.incompleteNodePromises.set(requestNodeId, promise);
1122
+
1123
+ return {requestNodeId, deferred};
1124
+ }
1125
+
1126
+ // If a cache key is provided, the result will be removed from the node and stored in a separate cache entry
1127
+ storeResult(nodeId: NodeId, result: RequestResult, cacheKey: ?string) {
1128
+ let node = this.graph.getNode(nodeId);
1129
+ if (node && node.type === REQUEST) {
1130
+ node.result = result;
1131
+ node.resultCacheKey = cacheKey;
1132
+ }
1133
+ }
1134
+
1135
+ hasValidResult(nodeId: NodeId): boolean {
1136
+ return (
1137
+ this.graph.hasNode(nodeId) &&
1138
+ !this.graph.invalidNodeIds.has(nodeId) &&
1139
+ !this.graph.incompleteNodeIds.has(nodeId)
1140
+ );
1141
+ }
1142
+
1143
+ async getRequestResult<T: RequestResult>(
1144
+ contentKey: ContentKey,
1145
+ ifMatch?: string,
1146
+ ): Promise<?T> {
1147
+ let node = nullthrows(this.graph.getNodeByContentKey(contentKey));
1148
+ invariant(node.type === REQUEST);
1149
+
1150
+ if (ifMatch != null && node.resultCacheKey !== ifMatch) {
1151
+ return null;
1152
+ }
1153
+
1154
+ if (node.result != undefined) {
1155
+ // $FlowFixMe
1156
+ let result: T = (node.result: any);
1157
+ return result;
1158
+ } else if (node.resultCacheKey != null && ifMatch == null) {
1159
+ let key = node.resultCacheKey;
1160
+ invariant(this.options.cache.hasLargeBlob(key));
1161
+ let cachedResult: T = deserialize(
1162
+ await this.options.cache.getLargeBlob(key),
1163
+ );
1164
+ node.result = cachedResult;
1165
+ return cachedResult;
1166
+ }
1167
+ }
1168
+
1169
+ completeRequest(nodeId: NodeId) {
1170
+ this.graph.invalidNodeIds.delete(nodeId);
1171
+ this.graph.incompleteNodeIds.delete(nodeId);
1172
+ this.graph.incompleteNodePromises.delete(nodeId);
1173
+ let node = this.graph.getNode(nodeId);
1174
+ if (node && node.type === REQUEST) {
1175
+ node.invalidateReason = VALID;
1176
+ }
1177
+ this.graph.removeCachedRequestChunkForNode(nodeId);
1178
+ }
1179
+
1180
+ rejectRequest(nodeId: NodeId) {
1181
+ this.graph.incompleteNodeIds.delete(nodeId);
1182
+ this.graph.incompleteNodePromises.delete(nodeId);
1183
+
1184
+ let node = this.graph.getNode(nodeId);
1185
+ if (node?.type === REQUEST) {
1186
+ this.graph.invalidateNode(nodeId, ERROR);
1187
+ }
1188
+ }
1189
+
1190
+ respondToFSEvents(events: Array<Event>, threshold: number): Async<boolean> {
1191
+ return this.graph.respondToFSEvents(events, this.options, threshold);
1192
+ }
1193
+
1194
+ hasInvalidRequests(): boolean {
1195
+ return this.graph.invalidNodeIds.size > 0;
1196
+ }
1197
+
1198
+ getInvalidRequests(): Array<RequestNode> {
1199
+ let invalidRequests = [];
1200
+ for (let id of this.graph.invalidNodeIds) {
1201
+ let node = nullthrows(this.graph.getNode(id));
1202
+ invariant(node.type === REQUEST);
1203
+ invalidRequests.push(node);
1204
+ }
1205
+ return invalidRequests;
1206
+ }
1207
+
1208
+ replaceSubrequests(
1209
+ requestNodeId: NodeId,
1210
+ subrequestContextKeys: Array<ContentKey>,
1211
+ ) {
1212
+ this.graph.replaceSubrequests(requestNodeId, subrequestContextKeys);
1213
+ }
1214
+
1215
+ async runRequest<TInput, TResult: RequestResult>(
1216
+ request: Request<TInput, TResult>,
1217
+ opts?: ?RunRequestOpts,
1218
+ ): Promise<TResult> {
1219
+ let hasKey = this.graph.hasContentKey(request.id);
1220
+ let requestId = hasKey
1221
+ ? this.graph.getNodeIdByContentKey(request.id)
1222
+ : undefined;
1223
+ let hasValidResult = requestId != null && this.hasValidResult(requestId);
1224
+
1225
+ if (!opts?.force && hasValidResult) {
1226
+ // $FlowFixMe[incompatible-type]
1227
+ return this.getRequestResult<TResult>(request.id);
1228
+ }
1229
+
1230
+ if (requestId != null) {
1231
+ let incompletePromise = this.graph.incompleteNodePromises.get(requestId);
1232
+ if (incompletePromise != null) {
1233
+ // There is a another instance of this request already running, wait for its completion and reuse its result
1234
+ try {
1235
+ if (await incompletePromise) {
1236
+ // $FlowFixMe[incompatible-type]
1237
+ return this.getRequestResult<TResult>(request.id);
1238
+ }
1239
+ } catch (e) {
1240
+ // Rerun this request
1241
+ }
1242
+ }
1243
+ }
1244
+
1245
+ let previousInvalidations =
1246
+ requestId != null ? this.graph.getInvalidations(requestId) : [];
1247
+ let {requestNodeId, deferred} = this.startRequest({
1248
+ id: request.id,
1249
+ type: REQUEST,
1250
+ requestType: request.type,
1251
+ invalidateReason: INITIAL_BUILD,
1252
+ });
1253
+
1254
+ let {api, subRequestContentKeys} = this.createAPI(
1255
+ requestNodeId,
1256
+ previousInvalidations,
1257
+ );
1258
+
1259
+ try {
1260
+ let node = this.graph.getRequestNode(requestNodeId);
1261
+
1262
+ this.stats.set(request.type, (this.stats.get(request.type) ?? 0) + 1);
1263
+
1264
+ let result = await request.run({
1265
+ input: request.input,
1266
+ api,
1267
+ farm: this.farm,
1268
+ invalidateReason: node.invalidateReason,
1269
+ options: this.options,
1270
+ rustAtlaspack: this.rustAtlaspack,
1271
+ });
1272
+
1273
+ assertSignalNotAborted(this.signal);
1274
+ this.completeRequest(requestNodeId);
1275
+
1276
+ deferred.resolve(true);
1277
+ return result;
1278
+ } catch (err) {
1279
+ if (
1280
+ !(err instanceof BuildAbortError) &&
1281
+ request.type === requestTypes.dev_dep_request
1282
+ ) {
1283
+ logger.verbose({
1284
+ origin: '@atlaspack/core',
1285
+ message: `Failed DevDepRequest`,
1286
+ meta: {
1287
+ trackableEvent: 'failed_dev_dep_request',
1288
+ hasKey,
1289
+ hasValidResult,
1290
+ },
1291
+ });
1292
+ }
1293
+
1294
+ this.rejectRequest(requestNodeId);
1295
+ deferred.resolve(false);
1296
+ throw err;
1297
+ } finally {
1298
+ this.graph.replaceSubrequests(requestNodeId, [...subRequestContentKeys]);
1299
+ }
1300
+ }
1301
+
1302
+ flushStats(): {[requestType: string]: number} {
1303
+ let requestTypeEntries = {};
1304
+
1305
+ for (let key of (Object.keys(requestTypes): RequestTypeName[])) {
1306
+ requestTypeEntries[requestTypes[key]] = key;
1307
+ }
1308
+
1309
+ let formattedStats = {};
1310
+
1311
+ for (let [requestType, count] of this.stats.entries()) {
1312
+ let requestTypeName = requestTypeEntries[requestType];
1313
+ formattedStats[requestTypeName] = count;
1314
+ }
1315
+
1316
+ this.stats = new Map();
1317
+
1318
+ return formattedStats;
1319
+ }
1320
+
1321
+ createAPI<TResult: RequestResult>(
1322
+ requestId: NodeId,
1323
+ previousInvalidations: Array<RequestInvalidation>,
1324
+ ): {|api: RunAPI<TResult>, subRequestContentKeys: Set<ContentKey>|} {
1325
+ let subRequestContentKeys = new Set<ContentKey>();
1326
+ let api: RunAPI<TResult> = {
1327
+ invalidateOnFileCreate: input =>
1328
+ this.graph.invalidateOnFileCreate(requestId, input),
1329
+ invalidateOnConfigKeyChange: (filePath, configKey, contentHash) =>
1330
+ this.graph.invalidateOnConfigKeyChange(
1331
+ requestId,
1332
+ filePath,
1333
+ configKey,
1334
+ contentHash,
1335
+ ),
1336
+ invalidateOnFileDelete: filePath =>
1337
+ this.graph.invalidateOnFileDelete(requestId, filePath),
1338
+ invalidateOnFileUpdate: filePath =>
1339
+ this.graph.invalidateOnFileUpdate(requestId, filePath),
1340
+ invalidateOnStartup: () => this.graph.invalidateOnStartup(requestId),
1341
+ invalidateOnBuild: () => this.graph.invalidateOnBuild(requestId),
1342
+ invalidateOnEnvChange: env =>
1343
+ this.graph.invalidateOnEnvChange(requestId, env, this.options.env[env]),
1344
+ invalidateOnOptionChange: option =>
1345
+ this.graph.invalidateOnOptionChange(
1346
+ requestId,
1347
+ option,
1348
+ this.options[option],
1349
+ ),
1350
+ getInvalidations: () => previousInvalidations,
1351
+ storeResult: (result, cacheKey) => {
1352
+ this.storeResult(requestId, result, cacheKey);
1353
+ },
1354
+ getSubRequests: () => this.graph.getSubRequests(requestId),
1355
+ getInvalidSubRequests: () => this.graph.getInvalidSubRequests(requestId),
1356
+ getPreviousResult: <T: RequestResult>(ifMatch?: string): Async<?T> => {
1357
+ let contentKey = nullthrows(this.graph.getNode(requestId)?.id);
1358
+ return this.getRequestResult<T>(contentKey, ifMatch);
1359
+ },
1360
+ getRequestResult: <T: RequestResult>(id): Async<?T> =>
1361
+ this.getRequestResult<T>(id),
1362
+ canSkipSubrequest: contentKey => {
1363
+ if (
1364
+ this.graph.hasContentKey(contentKey) &&
1365
+ this.hasValidResult(this.graph.getNodeIdByContentKey(contentKey))
1366
+ ) {
1367
+ subRequestContentKeys.add(contentKey);
1368
+ return true;
1369
+ }
1370
+
1371
+ return false;
1372
+ },
1373
+ runRequest: <TInput, TResult: RequestResult>(
1374
+ subRequest: Request<TInput, TResult>,
1375
+ opts?: RunRequestOpts,
1376
+ ): Promise<TResult> => {
1377
+ subRequestContentKeys.add(subRequest.id);
1378
+ return this.runRequest<TInput, TResult>(subRequest, opts);
1379
+ },
1380
+ };
1381
+
1382
+ return {api, subRequestContentKeys};
1383
+ }
1384
+
1385
+ async writeToCache(signal?: AbortSignal) {
1386
+ let cacheKey = getCacheKey(this.options);
1387
+ let requestGraphKey = `requestGraph-${cacheKey}`;
1388
+ let snapshotKey = `snapshot-${cacheKey}`;
1389
+
1390
+ if (this.options.shouldDisableCache) {
1391
+ return;
1392
+ }
1393
+
1394
+ let serialisedGraph = this.graph.serialize();
1395
+
1396
+ // Delete an existing request graph cache, to prevent invalid states
1397
+ await this.options.cache.deleteLargeBlob(requestGraphKey);
1398
+
1399
+ let total = 0;
1400
+ const serialiseAndSet = async (
1401
+ key: string,
1402
+ // $FlowFixMe serialise input is any type
1403
+ contents: any,
1404
+ ): Promise<void> => {
1405
+ if (signal?.aborted) {
1406
+ throw new Error('Serialization was aborted');
1407
+ }
1408
+
1409
+ await this.options.cache.setLargeBlob(
1410
+ key,
1411
+ serialize(contents),
1412
+ signal
1413
+ ? {
1414
+ signal: signal,
1415
+ }
1416
+ : undefined,
1417
+ );
1418
+
1419
+ total += 1;
1420
+
1421
+ report({
1422
+ type: 'cache',
1423
+ phase: 'write',
1424
+ total,
1425
+ size: this.graph.nodes.length,
1426
+ });
1427
+ };
1428
+
1429
+ let queue = new PromiseQueue({
1430
+ maxConcurrent: 32,
1431
+ });
1432
+
1433
+ report({
1434
+ type: 'cache',
1435
+ phase: 'start',
1436
+ total,
1437
+ size: this.graph.nodes.length,
1438
+ });
1439
+
1440
+ // Preallocating a sparse array is faster than pushing when N is high enough
1441
+ let cacheableNodes = new Array(serialisedGraph.nodes.length);
1442
+ for (let i = 0; i < serialisedGraph.nodes.length; i += 1) {
1443
+ let node = serialisedGraph.nodes[i];
1444
+
1445
+ let resultCacheKey = node?.resultCacheKey;
1446
+ if (
1447
+ node?.type === REQUEST &&
1448
+ resultCacheKey != null &&
1449
+ node?.result != null
1450
+ ) {
1451
+ queue
1452
+ .add(() => serialiseAndSet(resultCacheKey, node.result))
1453
+ .catch(() => {
1454
+ // Handle promise rejection
1455
+ });
1456
+
1457
+ // eslint-disable-next-line no-unused-vars
1458
+ let {result: _, ...newNode} = node;
1459
+ cacheableNodes[i] = newNode;
1460
+ } else {
1461
+ cacheableNodes[i] = node;
1462
+ }
1463
+ }
1464
+
1465
+ let nodeCountsPerBlob = [];
1466
+
1467
+ for (
1468
+ let i = 0;
1469
+ i * this.graph.nodesPerBlob < cacheableNodes.length;
1470
+ i += 1
1471
+ ) {
1472
+ let nodesStartIndex = i * this.graph.nodesPerBlob;
1473
+ let nodesEndIndex = Math.min(
1474
+ (i + 1) * this.graph.nodesPerBlob,
1475
+ cacheableNodes.length,
1476
+ );
1477
+
1478
+ nodeCountsPerBlob.push(nodesEndIndex - nodesStartIndex);
1479
+
1480
+ if (!this.graph.hasCachedRequestChunk(i)) {
1481
+ // We assume the request graph nodes are immutable and won't change
1482
+ let nodesToCache = cacheableNodes.slice(nodesStartIndex, nodesEndIndex);
1483
+
1484
+ queue
1485
+ .add(() =>
1486
+ serialiseAndSet(
1487
+ getRequestGraphNodeKey(i, cacheKey),
1488
+ nodesToCache,
1489
+ ).then(() => {
1490
+ // Succeeded in writing to disk, save that we have completed this chunk
1491
+ this.graph.setCachedRequestChunk(i);
1492
+ }),
1493
+ )
1494
+ .catch(() => {
1495
+ // Handle promise rejection
1496
+ });
1497
+ }
1498
+ }
1499
+
1500
+ try {
1501
+ await queue.run();
1502
+
1503
+ // Set the request graph after the queue is flushed to avoid writing an invalid state
1504
+ await serialiseAndSet(requestGraphKey, {
1505
+ ...serialisedGraph,
1506
+ nodeCountsPerBlob,
1507
+ nodes: undefined,
1508
+ });
1509
+
1510
+ let opts = getWatcherOptions(this.options);
1511
+ let snapshotPath = path.join(this.options.cacheDir, snapshotKey + '.txt');
1512
+
1513
+ await this.options.inputFS.writeSnapshot(
1514
+ this.options.watchDir,
1515
+ snapshotPath,
1516
+ opts,
1517
+ );
1518
+ } catch (err) {
1519
+ // If we have aborted, ignore the error and continue
1520
+ if (!signal?.aborted) throw err;
1521
+ }
1522
+
1523
+ report({type: 'cache', phase: 'end', total, size: this.graph.nodes.length});
1524
+ }
1525
+
1526
+ static async init({
1527
+ farm,
1528
+ options,
1529
+ rustAtlaspack,
1530
+ }: {|
1531
+ farm: WorkerFarm,
1532
+ options: AtlaspackOptions,
1533
+ rustAtlaspack?: AtlaspackV3,
1534
+ |}): Async<RequestTracker> {
1535
+ let graph = await loadRequestGraph(options);
1536
+ return new RequestTracker({farm, graph, options, rustAtlaspack});
1537
+ }
1538
+ }
1539
+
1540
+ export function getWatcherOptions({
1541
+ watchIgnore = [],
1542
+ cacheDir,
1543
+ watchDir,
1544
+ watchBackend,
1545
+ }: AtlaspackOptions): WatcherOptions {
1546
+ const vcsDirs = ['.git', '.hg'];
1547
+ const uniqueDirs = [...new Set([...watchIgnore, ...vcsDirs, cacheDir])];
1548
+ const ignore = uniqueDirs.map(dir => path.resolve(watchDir, dir));
1549
+
1550
+ return {ignore, backend: watchBackend};
1551
+ }
1552
+
1553
+ function getCacheKey(options) {
1554
+ return hashString(
1555
+ `${ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${options.mode}:${
1556
+ options.shouldBuildLazily ? 'lazy' : 'eager'
1557
+ }:${options.watchBackend ?? ''}`,
1558
+ );
1559
+ }
1560
+
1561
+ function getRequestGraphNodeKey(index: number, cacheKey: string) {
1562
+ return `requestGraph-nodes-${index}-${cacheKey}`;
1563
+ }
1564
+
1565
+ export async function readAndDeserializeRequestGraph(
1566
+ cache: Cache,
1567
+ requestGraphKey: string,
1568
+ cacheKey: string,
1569
+ ): Async<{|requestGraph: RequestGraph, bufferLength: number|}> {
1570
+ let bufferLength = 0;
1571
+ const getAndDeserialize = async (key: string) => {
1572
+ let buffer = await cache.getLargeBlob(key);
1573
+ bufferLength += Buffer.byteLength(buffer);
1574
+ return deserialize(buffer);
1575
+ };
1576
+
1577
+ let serializedRequestGraph = await getAndDeserialize(requestGraphKey);
1578
+
1579
+ let nodePromises = serializedRequestGraph.nodeCountsPerBlob.map(
1580
+ async (nodesCount, i) => {
1581
+ let nodes = await getAndDeserialize(getRequestGraphNodeKey(i, cacheKey));
1582
+ invariant.equal(
1583
+ nodes.length,
1584
+ nodesCount,
1585
+ 'RequestTracker node chunk: invalid node count',
1586
+ );
1587
+ return nodes;
1588
+ },
1589
+ );
1590
+
1591
+ return {
1592
+ requestGraph: RequestGraph.deserialize({
1593
+ ...serializedRequestGraph,
1594
+ nodes: (await Promise.all(nodePromises)).flat(),
1595
+ }),
1596
+ // This is used inside atlaspack query for `.inspectCache`
1597
+ bufferLength,
1598
+ };
1599
+ }
1600
+
1601
+ async function loadRequestGraph(options): Async<RequestGraph> {
1602
+ if (options.shouldDisableCache) {
1603
+ return new RequestGraph();
1604
+ }
1605
+
1606
+ let cacheKey = getCacheKey(options);
1607
+ let requestGraphKey = `requestGraph-${cacheKey}`;
1608
+ let timeout;
1609
+ const snapshotKey = `snapshot-${cacheKey}`;
1610
+ const snapshotPath = path.join(options.cacheDir, snapshotKey + '.txt');
1611
+ if (await options.cache.hasLargeBlob(requestGraphKey)) {
1612
+ try {
1613
+ let {requestGraph} = await readAndDeserializeRequestGraph(
1614
+ options.cache,
1615
+ requestGraphKey,
1616
+ cacheKey,
1617
+ );
1618
+
1619
+ let opts = getWatcherOptions(options);
1620
+
1621
+ timeout = setTimeout(() => {
1622
+ logger.warn({
1623
+ origin: '@atlaspack/core',
1624
+ message: `Retrieving file system events since last build...\nThis can take upto a minute after branch changes or npm/yarn installs.`,
1625
+ });
1626
+ }, 5000);
1627
+ let startTime = Date.now();
1628
+ let events = await options.inputFS.getEventsSince(
1629
+ options.watchDir,
1630
+ snapshotPath,
1631
+ opts,
1632
+ );
1633
+ clearTimeout(timeout);
1634
+
1635
+ logger.verbose({
1636
+ origin: '@atlaspack/core',
1637
+ message: `File system event count: ${events.length}`,
1638
+ meta: {
1639
+ trackableEvent: 'watcher_events_count',
1640
+ watcherEventCount: events.length,
1641
+ duration: Date.now() - startTime,
1642
+ },
1643
+ });
1644
+
1645
+ requestGraph.invalidateUnpredictableNodes();
1646
+ requestGraph.invalidateOnBuildNodes();
1647
+ requestGraph.invalidateEnvNodes(options.env);
1648
+ requestGraph.invalidateOptionNodes(options);
1649
+
1650
+ await requestGraph.respondToFSEvents(
1651
+ options.unstableFileInvalidations || events,
1652
+ options,
1653
+ 10000,
1654
+ );
1655
+ return requestGraph;
1656
+ } catch (e) {
1657
+ // Prevent logging fs events took too long warning
1658
+ clearTimeout(timeout);
1659
+ logErrorOnBailout(options, snapshotPath, e);
1660
+ // This error means respondToFSEvents timed out handling the invalidation events
1661
+ // In this case we'll return a fresh RequestGraph
1662
+ return new RequestGraph();
1663
+ }
1664
+ }
1665
+
1666
+ return new RequestGraph();
1667
+ }
1668
+ function logErrorOnBailout(
1669
+ options: AtlaspackOptions,
1670
+ snapshotPath: string,
1671
+ e: Error,
1672
+ ): void {
1673
+ if (e.message && e.message.includes('invalid clockspec')) {
1674
+ const snapshotContents = options.inputFS.readFileSync(
1675
+ snapshotPath,
1676
+ 'utf-8',
1677
+ );
1678
+ logger.warn({
1679
+ origin: '@atlaspack/core',
1680
+ message: `Error reading clockspec from snapshot, building with clean cache.`,
1681
+ meta: {
1682
+ snapshotContents: snapshotContents,
1683
+ trackableEvent: 'invalid_clockspec_error',
1684
+ },
1685
+ });
1686
+ } else if (!(e instanceof FSBailoutError)) {
1687
+ logger.warn({
1688
+ origin: '@atlaspack/core',
1689
+ message: `Unexpected error loading cache from disk, building with clean cache.`,
1690
+ meta: {
1691
+ errorMessage: e.message,
1692
+ errorStack: e.stack,
1693
+ trackableEvent: 'cache_load_error',
1694
+ },
1695
+ });
1696
+ }
1697
+ }