@atlaspack/core 2.24.0 → 2.24.2-dev-ts-project-refs-d30e9754f.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/LICENSE +201 -0
- package/dist/AssetGraph.js +591 -0
- package/dist/Atlaspack.js +656 -0
- package/dist/AtlaspackConfig.js +324 -0
- package/dist/AtlaspackConfig.schema.js +108 -0
- package/dist/BundleGraph.js +1628 -0
- package/dist/CommittedAsset.js +142 -0
- package/dist/Dependency.js +125 -0
- package/dist/Environment.js +132 -0
- package/dist/EnvironmentManager.js +108 -0
- package/dist/IdentifierRegistry.js +38 -0
- package/dist/InternalConfig.js +37 -0
- package/dist/PackagerRunner.js +531 -0
- package/dist/ReporterRunner.js +151 -0
- package/dist/RequestTracker.js +1368 -0
- package/dist/SymbolPropagation.js +620 -0
- package/dist/TargetDescriptor.schema.js +143 -0
- package/dist/Transformation.js +487 -0
- package/dist/UncommittedAsset.js +315 -0
- package/dist/Validation.js +196 -0
- package/dist/applyRuntimes.js +305 -0
- package/dist/assetUtils.js +168 -0
- package/dist/atlaspack-v3/AtlaspackV3.js +70 -0
- package/dist/atlaspack-v3/NapiWorkerPool.js +57 -0
- package/dist/atlaspack-v3/fs.js +52 -0
- package/dist/atlaspack-v3/index.js +25 -0
- package/dist/atlaspack-v3/jsCallable.js +16 -0
- package/dist/atlaspack-v3/worker/compat/asset-symbols.js +190 -0
- package/dist/atlaspack-v3/worker/compat/bitflags.js +94 -0
- package/dist/atlaspack-v3/worker/compat/dependency.js +43 -0
- package/dist/atlaspack-v3/worker/compat/environment.js +57 -0
- package/dist/atlaspack-v3/worker/compat/index.js +25 -0
- package/dist/atlaspack-v3/worker/compat/mutable-asset.js +152 -0
- package/dist/atlaspack-v3/worker/compat/plugin-config.js +76 -0
- package/dist/atlaspack-v3/worker/compat/plugin-logger.js +26 -0
- package/dist/atlaspack-v3/worker/compat/plugin-options.js +122 -0
- package/dist/atlaspack-v3/worker/compat/plugin-tracer.js +10 -0
- package/dist/atlaspack-v3/worker/compat/target.js +14 -0
- package/dist/atlaspack-v3/worker/worker.js +292 -0
- package/dist/constants.js +17 -0
- package/dist/dumpGraphToGraphViz.js +281 -0
- package/dist/index.js +62 -0
- package/dist/loadAtlaspackPlugin.js +128 -0
- package/dist/loadDotEnv.js +41 -0
- package/dist/projectPath.js +83 -0
- package/dist/public/Asset.js +279 -0
- package/dist/public/Bundle.js +224 -0
- package/dist/public/BundleGraph.js +359 -0
- package/dist/public/BundleGroup.js +53 -0
- package/dist/public/Config.js +286 -0
- package/dist/public/Dependency.js +138 -0
- package/dist/public/Environment.js +278 -0
- package/dist/public/MutableBundleGraph.js +277 -0
- package/dist/public/PluginOptions.js +80 -0
- package/dist/public/Symbols.js +248 -0
- package/dist/public/Target.js +69 -0
- package/dist/registerCoreWithSerializer.js +38 -0
- package/dist/requests/AssetGraphRequest.js +429 -0
- package/dist/requests/AssetGraphRequestRust.js +246 -0
- package/dist/requests/AssetRequest.js +130 -0
- package/dist/requests/AtlaspackBuildRequest.js +60 -0
- package/dist/requests/AtlaspackConfigRequest.js +490 -0
- package/dist/requests/BundleGraphRequest.js +441 -0
- package/dist/requests/ConfigRequest.js +222 -0
- package/dist/requests/DevDepRequest.js +204 -0
- package/dist/requests/EntryRequest.js +314 -0
- package/dist/requests/PackageRequest.js +65 -0
- package/dist/requests/PathRequest.js +349 -0
- package/dist/requests/TargetRequest.js +1310 -0
- package/dist/requests/ValidationRequest.js +49 -0
- package/dist/requests/WriteBundleRequest.js +254 -0
- package/dist/requests/WriteBundlesRequest.js +165 -0
- package/dist/requests/asset-graph-diff.js +126 -0
- package/dist/requests/asset-graph-dot.js +131 -0
- package/dist/resolveOptions.js +268 -0
- package/dist/rustWorkerThreadDylibHack.js +19 -0
- package/dist/serializerCore.browser.js +43 -0
- package/dist/summarizeRequest.js +39 -0
- package/dist/types.js +31 -0
- package/dist/utils.js +172 -0
- package/dist/worker.js +130 -0
- package/lib/AssetGraph.js +1 -0
- package/lib/atlaspack-v3/AtlaspackV3.js +7 -3
- package/lib/requests/AssetGraphRequestRust.js +1 -1
- package/lib/types/atlaspack-v3/AtlaspackV3.d.ts +1 -1
- package/package.json +22 -22
- package/src/AssetGraph.ts +1 -0
- package/src/atlaspack-v3/AtlaspackV3.ts +12 -3
- package/src/requests/AssetGraphRequestRust.ts +1 -1
- package/tsconfig.json +55 -2
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,1368 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.RequestGraph = exports.requestTypes = exports.requestGraphEdgeTypes = void 0;
|
|
40
|
+
exports.getWatcherOptions = getWatcherOptions;
|
|
41
|
+
exports.readAndDeserializeRequestGraph = readAndDeserializeRequestGraph;
|
|
42
|
+
exports.invalidateRequestGraph = invalidateRequestGraph;
|
|
43
|
+
exports.invalidateRequestGraphFSEvents = invalidateRequestGraphFSEvents;
|
|
44
|
+
exports.runInvalidation = runInvalidation;
|
|
45
|
+
exports.cleanUpOrphans = cleanUpOrphans;
|
|
46
|
+
exports.getBiggestFSEventsInvalidations = getBiggestFSEventsInvalidations;
|
|
47
|
+
const assert_1 = __importStar(require("assert"));
|
|
48
|
+
const path_1 = __importDefault(require("path"));
|
|
49
|
+
const build_cache_1 = require("@atlaspack/build-cache");
|
|
50
|
+
const cache_1 = require("@atlaspack/cache");
|
|
51
|
+
const feature_flags_1 = require("@atlaspack/feature-flags");
|
|
52
|
+
const graph_1 = require("@atlaspack/graph");
|
|
53
|
+
const logger_1 = __importStar(require("@atlaspack/logger"));
|
|
54
|
+
const rust_1 = require("@atlaspack/rust");
|
|
55
|
+
const utils_1 = require("@atlaspack/utils");
|
|
56
|
+
const nullthrows_1 = __importDefault(require("nullthrows"));
|
|
57
|
+
const constants_1 = require("./constants");
|
|
58
|
+
const projectPath_1 = require("./projectPath");
|
|
59
|
+
const ReporterRunner_1 = require("./ReporterRunner");
|
|
60
|
+
const ConfigRequest_1 = require("./requests/ConfigRequest");
|
|
61
|
+
const utils_2 = require("./utils");
|
|
62
|
+
const perf_hooks_1 = require("perf_hooks");
|
|
63
|
+
const EnvironmentManager_1 = require("./EnvironmentManager");
|
|
64
|
+
exports.requestGraphEdgeTypes = {
|
|
65
|
+
subrequest: 2,
|
|
66
|
+
invalidated_by_update: 3,
|
|
67
|
+
invalidated_by_delete: 4,
|
|
68
|
+
invalidated_by_create: 5,
|
|
69
|
+
invalidated_by_create_above: 6,
|
|
70
|
+
dirname: 7,
|
|
71
|
+
};
|
|
72
|
+
class FSBailoutError extends Error {
|
|
73
|
+
constructor() {
|
|
74
|
+
super(...arguments);
|
|
75
|
+
this.name = 'FSBailoutError';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const FILE = 0;
|
|
79
|
+
const REQUEST = 1;
|
|
80
|
+
const FILE_NAME = 2;
|
|
81
|
+
const ENV = 3;
|
|
82
|
+
const OPTION = 4;
|
|
83
|
+
const GLOB = 5;
|
|
84
|
+
const CONFIG_KEY = 6;
|
|
85
|
+
exports.requestTypes = {
|
|
86
|
+
atlaspack_build_request: 1,
|
|
87
|
+
bundle_graph_request: 2,
|
|
88
|
+
asset_graph_request: 3,
|
|
89
|
+
entry_request: 4,
|
|
90
|
+
target_request: 5,
|
|
91
|
+
atlaspack_config_request: 6,
|
|
92
|
+
path_request: 7,
|
|
93
|
+
dev_dep_request: 8,
|
|
94
|
+
asset_request: 9,
|
|
95
|
+
config_request: 10,
|
|
96
|
+
write_bundles_request: 11,
|
|
97
|
+
package_request: 12,
|
|
98
|
+
write_bundle_request: 13,
|
|
99
|
+
validation_request: 14,
|
|
100
|
+
};
|
|
101
|
+
const nodeFromFilePath = (filePath) => ({
|
|
102
|
+
id: (0, projectPath_1.fromProjectPathRelative)(filePath),
|
|
103
|
+
type: FILE,
|
|
104
|
+
});
|
|
105
|
+
const nodeFromGlob = (glob) => ({
|
|
106
|
+
id: (0, projectPath_1.fromProjectPathRelative)(glob),
|
|
107
|
+
type: GLOB,
|
|
108
|
+
value: glob,
|
|
109
|
+
});
|
|
110
|
+
const nodeFromFileName = (fileName) => ({
|
|
111
|
+
id: 'file_name:' + fileName,
|
|
112
|
+
type: FILE_NAME,
|
|
113
|
+
});
|
|
114
|
+
const nodeFromRequest = (request) => ({
|
|
115
|
+
id: request.id,
|
|
116
|
+
type: REQUEST,
|
|
117
|
+
requestType: request.requestType,
|
|
118
|
+
invalidateReason: constants_1.INITIAL_BUILD,
|
|
119
|
+
});
|
|
120
|
+
const nodeFromEnv = (env, value) => ({
|
|
121
|
+
id: 'env:' + env,
|
|
122
|
+
type: ENV,
|
|
123
|
+
value,
|
|
124
|
+
});
|
|
125
|
+
const nodeFromOption = (option, value) => ({
|
|
126
|
+
id: 'option:' + option,
|
|
127
|
+
type: OPTION,
|
|
128
|
+
hash: (0, utils_2.hashFromOption)(value),
|
|
129
|
+
});
|
|
130
|
+
const nodeFromConfigKey = (fileName, configKey, contentHash) => ({
|
|
131
|
+
id: `config_key:${(0, projectPath_1.fromProjectPathRelative)(fileName)}:${JSON.stringify(configKey)}`,
|
|
132
|
+
type: CONFIG_KEY,
|
|
133
|
+
configKey,
|
|
134
|
+
contentHash,
|
|
135
|
+
});
|
|
136
|
+
const keyFromEnvContentKey = (contentKey) => contentKey.slice('env:'.length);
|
|
137
|
+
const keyFromOptionContentKey = (contentKey) => contentKey.slice('option:'.length);
|
|
138
|
+
// This constant is chosen by local profiling the time to serialise n nodes and tuning until an average time of ~50 ms per blob.
|
|
139
|
+
// The goal is to free up the event loop periodically to allow interruption by the user.
|
|
140
|
+
const NODES_PER_BLOB = 2 ** 14;
|
|
141
|
+
// @ts-expect-error TS2417
|
|
142
|
+
class RequestGraph extends graph_1.ContentGraph {
|
|
143
|
+
constructor() {
|
|
144
|
+
super(...arguments);
|
|
145
|
+
this.invalidNodeIds = new Set();
|
|
146
|
+
this.incompleteNodeIds = new Set();
|
|
147
|
+
this.incompleteNodePromises = new Map();
|
|
148
|
+
this.globNodeIds = new Set();
|
|
149
|
+
this.envNodeIds = new Set();
|
|
150
|
+
this.optionNodeIds = new Set();
|
|
151
|
+
// Unpredictable nodes are requests that cannot be predicted whether they should rerun based on
|
|
152
|
+
// filesystem changes alone. They should rerun on each startup of Atlaspack.
|
|
153
|
+
this.unpredicatableNodeIds = new Set();
|
|
154
|
+
this.invalidateOnBuildNodeIds = new Set();
|
|
155
|
+
this.cachedRequestChunks = new Set();
|
|
156
|
+
this.configKeyNodes = new Map();
|
|
157
|
+
this.nodesPerBlob = NODES_PER_BLOB;
|
|
158
|
+
}
|
|
159
|
+
static deserialize(opts) {
|
|
160
|
+
let deserialized = new RequestGraph(opts);
|
|
161
|
+
deserialized.invalidNodeIds = opts.invalidNodeIds;
|
|
162
|
+
deserialized.incompleteNodeIds = opts.incompleteNodeIds;
|
|
163
|
+
deserialized.globNodeIds = opts.globNodeIds;
|
|
164
|
+
deserialized.envNodeIds = opts.envNodeIds;
|
|
165
|
+
deserialized.optionNodeIds = opts.optionNodeIds;
|
|
166
|
+
deserialized.unpredicatableNodeIds = opts.unpredicatableNodeIds;
|
|
167
|
+
deserialized.invalidateOnBuildNodeIds = opts.invalidateOnBuildNodeIds;
|
|
168
|
+
deserialized.cachedRequestChunks = opts.cachedRequestChunks;
|
|
169
|
+
deserialized.configKeyNodes = opts.configKeyNodes;
|
|
170
|
+
return deserialized;
|
|
171
|
+
}
|
|
172
|
+
serialize() {
|
|
173
|
+
return {
|
|
174
|
+
...super.serialize(),
|
|
175
|
+
invalidNodeIds: this.invalidNodeIds,
|
|
176
|
+
incompleteNodeIds: this.incompleteNodeIds,
|
|
177
|
+
globNodeIds: this.globNodeIds,
|
|
178
|
+
envNodeIds: this.envNodeIds,
|
|
179
|
+
optionNodeIds: this.optionNodeIds,
|
|
180
|
+
unpredicatableNodeIds: this.unpredicatableNodeIds,
|
|
181
|
+
invalidateOnBuildNodeIds: this.invalidateOnBuildNodeIds,
|
|
182
|
+
cachedRequestChunks: this.cachedRequestChunks,
|
|
183
|
+
configKeyNodes: this.configKeyNodes,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// addNode for RequestGraph should not override the value if added multiple times
|
|
187
|
+
addNode(node) {
|
|
188
|
+
let nodeId = this._contentKeyToNodeId.get(node.id);
|
|
189
|
+
if (nodeId != null) {
|
|
190
|
+
return nodeId;
|
|
191
|
+
}
|
|
192
|
+
nodeId = super.addNodeByContentKey(node.id, node);
|
|
193
|
+
if (node.type === GLOB) {
|
|
194
|
+
this.globNodeIds.add(nodeId);
|
|
195
|
+
}
|
|
196
|
+
else if (node.type === ENV) {
|
|
197
|
+
this.envNodeIds.add(nodeId);
|
|
198
|
+
}
|
|
199
|
+
else if (node.type === OPTION) {
|
|
200
|
+
this.optionNodeIds.add(nodeId);
|
|
201
|
+
}
|
|
202
|
+
this.removeCachedRequestChunkForNode(nodeId);
|
|
203
|
+
return nodeId;
|
|
204
|
+
}
|
|
205
|
+
removeNode(nodeId, removeOrphans = true) {
|
|
206
|
+
this.invalidNodeIds.delete(nodeId);
|
|
207
|
+
this.incompleteNodeIds.delete(nodeId);
|
|
208
|
+
this.incompleteNodePromises.delete(nodeId);
|
|
209
|
+
this.unpredicatableNodeIds.delete(nodeId);
|
|
210
|
+
this.invalidateOnBuildNodeIds.delete(nodeId);
|
|
211
|
+
let node = (0, nullthrows_1.default)(this.getNode(nodeId));
|
|
212
|
+
if (node.type === GLOB) {
|
|
213
|
+
this.globNodeIds.delete(nodeId);
|
|
214
|
+
}
|
|
215
|
+
else if (node.type === ENV) {
|
|
216
|
+
this.envNodeIds.delete(nodeId);
|
|
217
|
+
}
|
|
218
|
+
else if (node.type === OPTION) {
|
|
219
|
+
this.optionNodeIds.delete(nodeId);
|
|
220
|
+
}
|
|
221
|
+
else if (node.type === CONFIG_KEY) {
|
|
222
|
+
for (let configKeyNodes of this.configKeyNodes.values()) {
|
|
223
|
+
configKeyNodes.delete(nodeId);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return super.removeNode(nodeId, removeOrphans);
|
|
227
|
+
}
|
|
228
|
+
getRequestNode(nodeId) {
|
|
229
|
+
let node = (0, nullthrows_1.default)(this.getNode(nodeId));
|
|
230
|
+
if (node.type === REQUEST) {
|
|
231
|
+
return node;
|
|
232
|
+
}
|
|
233
|
+
throw new assert_1.AssertionError({
|
|
234
|
+
message: `Expected a request node: ${node.type} (${typeof node.type}) does not equal ${REQUEST} (${typeof REQUEST}).`,
|
|
235
|
+
expected: REQUEST,
|
|
236
|
+
actual: node.type,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
replaceSubrequests(requestNodeId, subrequestContentKeys) {
|
|
240
|
+
let subrequestNodeIds = [];
|
|
241
|
+
for (let key of subrequestContentKeys) {
|
|
242
|
+
if (this.hasContentKey(key)) {
|
|
243
|
+
subrequestNodeIds.push(this.getNodeIdByContentKey(key));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
this.replaceNodeIdsConnectedTo(requestNodeId, subrequestNodeIds, null, exports.requestGraphEdgeTypes.subrequest);
|
|
247
|
+
}
|
|
248
|
+
invalidateNode(nodeId, reason) {
|
|
249
|
+
let node = (0, nullthrows_1.default)(this.getNode(nodeId));
|
|
250
|
+
(0, assert_1.default)(node.type === REQUEST);
|
|
251
|
+
node.invalidateReason |= reason;
|
|
252
|
+
this.invalidNodeIds.add(nodeId);
|
|
253
|
+
let parentNodes = this.getNodeIdsConnectedTo(nodeId, exports.requestGraphEdgeTypes.subrequest);
|
|
254
|
+
for (let parentNode of parentNodes) {
|
|
255
|
+
this.invalidateNode(parentNode, reason);
|
|
256
|
+
}
|
|
257
|
+
// If the node is invalidated, the cached request chunk on disk needs to be re-written
|
|
258
|
+
this.removeCachedRequestChunkForNode(nodeId);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Nodes that are invalidated on start-up, such as JavaScript babel configuration files which are
|
|
262
|
+
* imported when the build kicks-off and might doing arbitrary work such as reading from the file
|
|
263
|
+
* system.
|
|
264
|
+
*/
|
|
265
|
+
invalidateUnpredictableNodes() {
|
|
266
|
+
for (let nodeId of this.unpredicatableNodeIds) {
|
|
267
|
+
let node = (0, nullthrows_1.default)(this.getNode(nodeId));
|
|
268
|
+
(0, assert_1.default)(node.type !== FILE && node.type !== GLOB);
|
|
269
|
+
this.invalidateNode(nodeId, constants_1.STARTUP);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Effectively uncacheable nodes.
|
|
274
|
+
*/
|
|
275
|
+
invalidateOnBuildNodes() {
|
|
276
|
+
for (let nodeId of this.invalidateOnBuildNodeIds) {
|
|
277
|
+
let node = (0, nullthrows_1.default)(this.getNode(nodeId));
|
|
278
|
+
(0, assert_1.default)(node.type !== FILE && node.type !== GLOB);
|
|
279
|
+
this.invalidateNode(nodeId, constants_1.STARTUP);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Nodes invalidated by environment changes, corresponds to `env: ...` inputs.
|
|
284
|
+
*/
|
|
285
|
+
invalidateEnvNodes(env) {
|
|
286
|
+
const invalidatedKeys = [];
|
|
287
|
+
for (let nodeId of this.envNodeIds) {
|
|
288
|
+
let node = (0, nullthrows_1.default)(this.getNode(nodeId));
|
|
289
|
+
(0, assert_1.default)(node.type === ENV);
|
|
290
|
+
const key = keyFromEnvContentKey(node.id);
|
|
291
|
+
if (env[key] !== node.value) {
|
|
292
|
+
invalidatedKeys.push(key);
|
|
293
|
+
let parentNodes = this.getNodeIdsConnectedTo(nodeId, exports.requestGraphEdgeTypes.invalidated_by_update);
|
|
294
|
+
for (let parentNode of parentNodes) {
|
|
295
|
+
this.invalidateNode(parentNode, constants_1.ENV_CHANGE);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return invalidatedKeys;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Nodes invalidated by option changes.
|
|
303
|
+
*/
|
|
304
|
+
invalidateOptionNodes(options) {
|
|
305
|
+
const invalidatedKeys = [];
|
|
306
|
+
for (let nodeId of this.optionNodeIds) {
|
|
307
|
+
let node = (0, nullthrows_1.default)(this.getNode(nodeId));
|
|
308
|
+
(0, assert_1.default)(node.type === OPTION);
|
|
309
|
+
const key = keyFromOptionContentKey(node.id);
|
|
310
|
+
// @ts-expect-error TS7053
|
|
311
|
+
if ((0, utils_2.hashFromOption)(options[key]) !== node.hash) {
|
|
312
|
+
invalidatedKeys.push(key);
|
|
313
|
+
let parentNodes = this.getNodeIdsConnectedTo(nodeId, exports.requestGraphEdgeTypes.invalidated_by_update);
|
|
314
|
+
for (let parentNode of parentNodes) {
|
|
315
|
+
this.invalidateNode(parentNode, constants_1.OPTION_CHANGE);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return invalidatedKeys;
|
|
320
|
+
}
|
|
321
|
+
invalidateOnConfigKeyChange(requestNodeId, filePath, configKey, contentHash) {
|
|
322
|
+
let configKeyNodeId = this.addNode(nodeFromConfigKey(filePath, configKey, contentHash));
|
|
323
|
+
let nodes = this.configKeyNodes.get(filePath);
|
|
324
|
+
if (!nodes) {
|
|
325
|
+
nodes = new Set();
|
|
326
|
+
this.configKeyNodes.set(filePath, nodes);
|
|
327
|
+
}
|
|
328
|
+
nodes.add(configKeyNodeId);
|
|
329
|
+
if (!this.hasEdge(requestNodeId, configKeyNodeId, exports.requestGraphEdgeTypes.invalidated_by_update)) {
|
|
330
|
+
this.addEdge(requestNodeId, configKeyNodeId,
|
|
331
|
+
// Store as an update edge, but file deletes are handled too
|
|
332
|
+
exports.requestGraphEdgeTypes.invalidated_by_update);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
invalidateOnFileUpdate(requestNodeId, filePath) {
|
|
336
|
+
let fileNodeId = this.addNode(nodeFromFilePath(filePath));
|
|
337
|
+
if (!this.hasEdge(requestNodeId, fileNodeId, exports.requestGraphEdgeTypes.invalidated_by_update)) {
|
|
338
|
+
this.addEdge(requestNodeId, fileNodeId, exports.requestGraphEdgeTypes.invalidated_by_update);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
invalidateOnFileDelete(requestNodeId, filePath) {
|
|
342
|
+
let fileNodeId = this.addNode(nodeFromFilePath(filePath));
|
|
343
|
+
if (!this.hasEdge(requestNodeId, fileNodeId, exports.requestGraphEdgeTypes.invalidated_by_delete)) {
|
|
344
|
+
this.addEdge(requestNodeId, fileNodeId, exports.requestGraphEdgeTypes.invalidated_by_delete);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
invalidateOnFileCreate(requestNodeId, input) {
|
|
348
|
+
let node;
|
|
349
|
+
// @ts-expect-error TS2339
|
|
350
|
+
if (input.glob != null) {
|
|
351
|
+
// @ts-expect-error TS2339
|
|
352
|
+
node = nodeFromGlob(input.glob);
|
|
353
|
+
// @ts-expect-error TS2339
|
|
354
|
+
}
|
|
355
|
+
else if (input.fileName != null && input.aboveFilePath != null) {
|
|
356
|
+
// @ts-expect-error TS2339
|
|
357
|
+
let aboveFilePath = input.aboveFilePath;
|
|
358
|
+
// Create nodes and edges for each part of the filename pattern.
|
|
359
|
+
// For example, 'node_modules/foo' would create two nodes and one edge.
|
|
360
|
+
// This creates a sort of trie structure within the graph that can be
|
|
361
|
+
// quickly matched by following the edges. This is also memory efficient
|
|
362
|
+
// since common sub-paths (e.g. 'node_modules') are deduplicated.
|
|
363
|
+
// @ts-expect-error TS2339
|
|
364
|
+
let parts = input.fileName.split('/').reverse();
|
|
365
|
+
let lastNodeId;
|
|
366
|
+
for (let part of parts) {
|
|
367
|
+
let fileNameNode = nodeFromFileName(part);
|
|
368
|
+
let fileNameNodeId = this.addNode(fileNameNode);
|
|
369
|
+
if (lastNodeId != null &&
|
|
370
|
+
!this.hasEdge(lastNodeId, fileNameNodeId, exports.requestGraphEdgeTypes.dirname)) {
|
|
371
|
+
this.addEdge(lastNodeId, fileNameNodeId, exports.requestGraphEdgeTypes.dirname);
|
|
372
|
+
}
|
|
373
|
+
lastNodeId = fileNameNodeId;
|
|
374
|
+
}
|
|
375
|
+
// The `aboveFilePath` condition asserts that requests are only invalidated
|
|
376
|
+
// if the file being created is "above" it in the filesystem (e.g. the file
|
|
377
|
+
// is created in a parent directory). There is likely to already be a node
|
|
378
|
+
// for this file in the graph (e.g. the source file) that we can reuse for this.
|
|
379
|
+
node = nodeFromFilePath(aboveFilePath);
|
|
380
|
+
let nodeId = this.addNode(node);
|
|
381
|
+
// Now create an edge from the `aboveFilePath` node to the first file_name node
|
|
382
|
+
// in the chain created above, and an edge from the last node in the chain back to
|
|
383
|
+
// the `aboveFilePath` node. When matching, we will start from the first node in
|
|
384
|
+
// the chain, and continue following it to parent directories until there is an
|
|
385
|
+
// edge pointing an `aboveFilePath` node that also points to the start of the chain.
|
|
386
|
+
// This indicates a complete match, and any requests attached to the `aboveFilePath`
|
|
387
|
+
// node will be invalidated.
|
|
388
|
+
let firstId = 'file_name:' + parts[0];
|
|
389
|
+
let firstNodeId = this.getNodeIdByContentKey(firstId);
|
|
390
|
+
if (!this.hasEdge(nodeId, firstNodeId, exports.requestGraphEdgeTypes.invalidated_by_create_above)) {
|
|
391
|
+
this.addEdge(nodeId, firstNodeId, exports.requestGraphEdgeTypes.invalidated_by_create_above);
|
|
392
|
+
}
|
|
393
|
+
(0, assert_1.default)(lastNodeId != null);
|
|
394
|
+
if (!this.hasEdge(lastNodeId, nodeId, exports.requestGraphEdgeTypes.invalidated_by_create_above)) {
|
|
395
|
+
this.addEdge(lastNodeId, nodeId, exports.requestGraphEdgeTypes.invalidated_by_create_above);
|
|
396
|
+
}
|
|
397
|
+
// @ts-expect-error TS2339
|
|
398
|
+
}
|
|
399
|
+
else if (input.filePath != null) {
|
|
400
|
+
// @ts-expect-error TS2339
|
|
401
|
+
node = nodeFromFilePath(input.filePath);
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
throw new Error('Invalid invalidation');
|
|
405
|
+
}
|
|
406
|
+
let nodeId = this.addNode(node);
|
|
407
|
+
if (!this.hasEdge(requestNodeId, nodeId, exports.requestGraphEdgeTypes.invalidated_by_create)) {
|
|
408
|
+
this.addEdge(requestNodeId, nodeId, exports.requestGraphEdgeTypes.invalidated_by_create);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
invalidateOnStartup(requestNodeId) {
|
|
412
|
+
this.getRequestNode(requestNodeId);
|
|
413
|
+
this.unpredicatableNodeIds.add(requestNodeId);
|
|
414
|
+
}
|
|
415
|
+
invalidateOnBuild(requestNodeId) {
|
|
416
|
+
this.getRequestNode(requestNodeId);
|
|
417
|
+
this.invalidateOnBuildNodeIds.add(requestNodeId);
|
|
418
|
+
}
|
|
419
|
+
invalidateOnEnvChange(requestNodeId, env, value) {
|
|
420
|
+
const envNode = nodeFromEnv(env, value);
|
|
421
|
+
const envNodeId = this.addNode(envNode);
|
|
422
|
+
if (!this.hasEdge(requestNodeId, envNodeId, exports.requestGraphEdgeTypes.invalidated_by_update)) {
|
|
423
|
+
this.addEdge(requestNodeId, envNodeId, exports.requestGraphEdgeTypes.invalidated_by_update);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
invalidateOnOptionChange(requestNodeId, option, value) {
|
|
427
|
+
let optionNode = nodeFromOption(option, value);
|
|
428
|
+
let optionNodeId = this.addNode(optionNode);
|
|
429
|
+
if (!this.hasEdge(requestNodeId, optionNodeId, exports.requestGraphEdgeTypes.invalidated_by_update)) {
|
|
430
|
+
this.addEdge(requestNodeId, optionNodeId, exports.requestGraphEdgeTypes.invalidated_by_update);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
clearInvalidations(nodeId) {
|
|
434
|
+
this.unpredicatableNodeIds.delete(nodeId);
|
|
435
|
+
this.invalidateOnBuildNodeIds.delete(nodeId);
|
|
436
|
+
this.replaceNodeIdsConnectedTo(nodeId, [], null, exports.requestGraphEdgeTypes.invalidated_by_update);
|
|
437
|
+
this.replaceNodeIdsConnectedTo(nodeId, [], null, exports.requestGraphEdgeTypes.invalidated_by_delete);
|
|
438
|
+
this.replaceNodeIdsConnectedTo(nodeId, [], null, exports.requestGraphEdgeTypes.invalidated_by_create);
|
|
439
|
+
}
|
|
440
|
+
getInvalidations(requestNodeId) {
|
|
441
|
+
if (!this.hasNode(requestNodeId)) {
|
|
442
|
+
return [];
|
|
443
|
+
}
|
|
444
|
+
// For now just handling updates. Could add creates/deletes later if needed.
|
|
445
|
+
let invalidations = this.getNodeIdsConnectedFrom(requestNodeId, exports.requestGraphEdgeTypes.invalidated_by_update);
|
|
446
|
+
// @ts-expect-error TS2322
|
|
447
|
+
return invalidations
|
|
448
|
+
.map((nodeId) => {
|
|
449
|
+
let node = (0, nullthrows_1.default)(this.getNode(nodeId));
|
|
450
|
+
switch (node.type) {
|
|
451
|
+
case FILE:
|
|
452
|
+
return { type: 'file', filePath: (0, projectPath_1.toProjectPathUnsafe)(node.id) };
|
|
453
|
+
case ENV:
|
|
454
|
+
return { type: 'env', key: keyFromEnvContentKey(node.id) };
|
|
455
|
+
case OPTION:
|
|
456
|
+
return {
|
|
457
|
+
type: 'option',
|
|
458
|
+
key: keyFromOptionContentKey(node.id),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
})
|
|
462
|
+
.filter(Boolean);
|
|
463
|
+
}
|
|
464
|
+
getSubRequests(requestNodeId) {
|
|
465
|
+
if (!this.hasNode(requestNodeId)) {
|
|
466
|
+
return [];
|
|
467
|
+
}
|
|
468
|
+
let subRequests = this.getNodeIdsConnectedFrom(requestNodeId, exports.requestGraphEdgeTypes.subrequest);
|
|
469
|
+
return subRequests.map((nodeId) => {
|
|
470
|
+
let node = (0, nullthrows_1.default)(this.getNode(nodeId));
|
|
471
|
+
(0, assert_1.default)(node.type === REQUEST);
|
|
472
|
+
return node;
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
getInvalidSubRequests(requestNodeId) {
|
|
476
|
+
if (!this.hasNode(requestNodeId)) {
|
|
477
|
+
return [];
|
|
478
|
+
}
|
|
479
|
+
let subRequests = this.getNodeIdsConnectedFrom(requestNodeId, exports.requestGraphEdgeTypes.subrequest);
|
|
480
|
+
return subRequests
|
|
481
|
+
.filter((id) => this.invalidNodeIds.has(id))
|
|
482
|
+
.map((nodeId) => {
|
|
483
|
+
let node = (0, nullthrows_1.default)(this.getNode(nodeId));
|
|
484
|
+
(0, assert_1.default)(node.type === REQUEST);
|
|
485
|
+
return node;
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
invalidateFileNameNode(node, filePath, matchNodes, invalidateNode) {
|
|
489
|
+
// If there is an edge between this file_name node and one of the original file nodes pointed to
|
|
490
|
+
// by the original file_name node, and the matched node is inside the current directory, invalidate
|
|
491
|
+
// all connected requests pointed to by the file node.
|
|
492
|
+
let nodeId = this.getNodeIdByContentKey(node.id);
|
|
493
|
+
let dirname = path_1.default.dirname((0, projectPath_1.fromProjectPathRelative)(filePath));
|
|
494
|
+
if ((0, feature_flags_1.getFeatureFlag)('fixQuadraticCacheInvalidation')) {
|
|
495
|
+
while (dirname !== '/') {
|
|
496
|
+
if (!this.hasContentKey(dirname))
|
|
497
|
+
break;
|
|
498
|
+
const matchNodeId = this.getNodeIdByContentKey(dirname);
|
|
499
|
+
if (!this.hasEdge(nodeId, matchNodeId, exports.requestGraphEdgeTypes.invalidated_by_create_above))
|
|
500
|
+
break;
|
|
501
|
+
const connectedNodes = this.getNodeIdsConnectedTo(matchNodeId, exports.requestGraphEdgeTypes.invalidated_by_create);
|
|
502
|
+
for (let connectedNode of connectedNodes) {
|
|
503
|
+
invalidateNode(connectedNode, constants_1.FILE_CREATE);
|
|
504
|
+
}
|
|
505
|
+
dirname = path_1.default.dirname(dirname);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
for (let matchNode of matchNodes) {
|
|
510
|
+
let matchNodeId = this.getNodeIdByContentKey(matchNode.id);
|
|
511
|
+
if (this.hasEdge(nodeId, matchNodeId, exports.requestGraphEdgeTypes.invalidated_by_create_above) &&
|
|
512
|
+
(0, utils_1.isDirectoryInside)((0, projectPath_1.fromProjectPathRelative)((0, projectPath_1.toProjectPathUnsafe)(matchNode.id)), dirname)) {
|
|
513
|
+
let connectedNodes = this.getNodeIdsConnectedTo(matchNodeId, exports.requestGraphEdgeTypes.invalidated_by_create);
|
|
514
|
+
for (let connectedNode of connectedNodes) {
|
|
515
|
+
this.invalidateNode(connectedNode, constants_1.FILE_CREATE);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
// Find the `file_name` node for the parent directory and
|
|
521
|
+
// recursively invalidate connected requests as described above.
|
|
522
|
+
let basename = path_1.default.basename(dirname);
|
|
523
|
+
let contentKey = 'file_name:' + basename;
|
|
524
|
+
if (this.hasContentKey(contentKey)) {
|
|
525
|
+
if (this.hasEdge(nodeId, this.getNodeIdByContentKey(contentKey), exports.requestGraphEdgeTypes.dirname)) {
|
|
526
|
+
let parent = (0, nullthrows_1.default)(this.getNodeByContentKey(contentKey));
|
|
527
|
+
(0, assert_1.default)(parent.type === FILE_NAME);
|
|
528
|
+
this.invalidateFileNameNode(parent, (0, projectPath_1.toProjectPathUnsafe)(dirname), matchNodes, invalidateNode);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async respondToFSEvents(events, options, threshold,
|
|
533
|
+
/**
|
|
534
|
+
* True if this is the start-up (loading phase) invalidation.
|
|
535
|
+
*/
|
|
536
|
+
isInitialBuild = false) {
|
|
537
|
+
let didInvalidate = false;
|
|
538
|
+
let count = 0;
|
|
539
|
+
let predictedTime = 0;
|
|
540
|
+
let startTime = Date.now();
|
|
541
|
+
const enableOptimization = (0, feature_flags_1.getFeatureFlag)('fixQuadraticCacheInvalidation');
|
|
542
|
+
const removeOrphans = !enableOptimization;
|
|
543
|
+
const invalidatedNodes = new Set();
|
|
544
|
+
const invalidateNode = (nodeId, reason) => {
|
|
545
|
+
if (enableOptimization && invalidatedNodes.has(nodeId)) {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
invalidatedNodes.add(nodeId);
|
|
549
|
+
this.invalidateNode(nodeId, reason);
|
|
550
|
+
};
|
|
551
|
+
const aboveCache = new Map();
|
|
552
|
+
const getAbove = (fileNameNodeId) => {
|
|
553
|
+
const cachedResult = aboveCache.get(fileNameNodeId);
|
|
554
|
+
if (enableOptimization && cachedResult) {
|
|
555
|
+
return cachedResult;
|
|
556
|
+
}
|
|
557
|
+
let above = [];
|
|
558
|
+
const children = this.getNodeIdsConnectedTo(fileNameNodeId, exports.requestGraphEdgeTypes.invalidated_by_create_above);
|
|
559
|
+
for (const nodeId of children) {
|
|
560
|
+
let node = (0, nullthrows_1.default)(this.getNode(nodeId));
|
|
561
|
+
if (node.type === FILE) {
|
|
562
|
+
above.push(node);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
aboveCache.set(fileNameNodeId, above);
|
|
566
|
+
return above;
|
|
567
|
+
};
|
|
568
|
+
const invalidationsByPath = new Map();
|
|
569
|
+
for (let { path: _path, type } of events) {
|
|
570
|
+
const invalidationsBefore = this.getInvalidNodeCount();
|
|
571
|
+
if (!enableOptimization &&
|
|
572
|
+
process.env.ATLASPACK_DISABLE_CACHE_TIMEOUT !== 'true' &&
|
|
573
|
+
++count === 256) {
|
|
574
|
+
let duration = Date.now() - startTime;
|
|
575
|
+
predictedTime = duration * (events.length >> 8);
|
|
576
|
+
if (predictedTime > threshold) {
|
|
577
|
+
logger_1.default.warn({
|
|
578
|
+
origin: '@atlaspack/core',
|
|
579
|
+
message: 'Building with clean cache. Cache invalidation took too long.',
|
|
580
|
+
meta: {
|
|
581
|
+
trackableEvent: 'cache_invalidation_timeout',
|
|
582
|
+
watcherEventCount: events.length,
|
|
583
|
+
predictedTime,
|
|
584
|
+
},
|
|
585
|
+
});
|
|
586
|
+
throw new FSBailoutError('Responding to file system events exceeded threshold, start with empty cache.');
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
let _filePath = (0, projectPath_1.toProjectPath)(options.projectRoot, _path);
|
|
590
|
+
let filePath = (0, projectPath_1.fromProjectPathRelative)(_filePath);
|
|
591
|
+
let hasFileRequest = this.hasContentKey(filePath);
|
|
592
|
+
// If we see a 'create' event for the project root itself,
|
|
593
|
+
// this means the project root was moved and we need to
|
|
594
|
+
// re-run all requests.
|
|
595
|
+
if (type === 'create' && filePath === '') {
|
|
596
|
+
logger_1.default.verbose({
|
|
597
|
+
origin: '@atlaspack/core',
|
|
598
|
+
message: 'Watcher reported project root create event. Invalidate all nodes.',
|
|
599
|
+
meta: {
|
|
600
|
+
trackableEvent: 'project_root_create',
|
|
601
|
+
},
|
|
602
|
+
});
|
|
603
|
+
for (let [id, node] of this.nodes.entries()) {
|
|
604
|
+
if (node?.type === REQUEST) {
|
|
605
|
+
this.invalidNodeIds.add(id);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
return {
|
|
609
|
+
didInvalidate: true,
|
|
610
|
+
invalidationsByPath: new Map(),
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
// sometimes mac os reports update events as create events.
|
|
614
|
+
// if it was a create event, but the file already exists in the graph,
|
|
615
|
+
// then also invalidate nodes connected by invalidated_by_update edges.
|
|
616
|
+
if (hasFileRequest && (type === 'create' || type === 'update')) {
|
|
617
|
+
let nodeId = this.getNodeIdByContentKey(filePath);
|
|
618
|
+
let nodes = this.getNodeIdsConnectedTo(nodeId, exports.requestGraphEdgeTypes.invalidated_by_update);
|
|
619
|
+
for (let connectedNode of nodes) {
|
|
620
|
+
didInvalidate = true;
|
|
621
|
+
invalidateNode(connectedNode, constants_1.FILE_UPDATE);
|
|
622
|
+
}
|
|
623
|
+
if (type === 'create') {
|
|
624
|
+
let nodes = this.getNodeIdsConnectedTo(nodeId, exports.requestGraphEdgeTypes.invalidated_by_create);
|
|
625
|
+
for (let connectedNode of nodes) {
|
|
626
|
+
didInvalidate = true;
|
|
627
|
+
invalidateNode(connectedNode, constants_1.FILE_CREATE);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
else if (type === 'create') {
|
|
632
|
+
let basename = path_1.default.basename(filePath);
|
|
633
|
+
let fileNameNode = this.getNodeByContentKey('file_name:' + basename);
|
|
634
|
+
if (fileNameNode != null && fileNameNode.type === FILE_NAME) {
|
|
635
|
+
let fileNameNodeId = this.getNodeIdByContentKey('file_name:' + basename);
|
|
636
|
+
// Find potential file nodes to be invalidated if this file name pattern matches
|
|
637
|
+
let above = getAbove(fileNameNodeId);
|
|
638
|
+
if (above.length > 0) {
|
|
639
|
+
didInvalidate = true;
|
|
640
|
+
this.invalidateFileNameNode(fileNameNode, _filePath, above, invalidateNode);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
for (let globeNodeId of this.globNodeIds) {
|
|
644
|
+
let globNode = this.getNode(globeNodeId);
|
|
645
|
+
(0, assert_1.default)(globNode && globNode.type === GLOB);
|
|
646
|
+
if ((0, utils_1.isGlobMatch)(filePath, (0, projectPath_1.fromProjectPathRelative)(globNode.value))) {
|
|
647
|
+
let connectedNodes = this.getNodeIdsConnectedTo(globeNodeId, exports.requestGraphEdgeTypes.invalidated_by_create);
|
|
648
|
+
for (let connectedNode of connectedNodes) {
|
|
649
|
+
didInvalidate = true;
|
|
650
|
+
invalidateNode(connectedNode, constants_1.FILE_CREATE);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
else if (hasFileRequest && type === 'delete') {
|
|
656
|
+
let nodeId = this.getNodeIdByContentKey(filePath);
|
|
657
|
+
for (let connectedNode of this.getNodeIdsConnectedTo(nodeId, exports.requestGraphEdgeTypes.invalidated_by_delete)) {
|
|
658
|
+
didInvalidate = true;
|
|
659
|
+
invalidateNode(connectedNode, constants_1.FILE_DELETE);
|
|
660
|
+
}
|
|
661
|
+
// Delete the file node since it doesn't exist anymore.
|
|
662
|
+
// This ensures that files that don't exist aren't sent
|
|
663
|
+
// to requests as invalidations for future requests.
|
|
664
|
+
this.removeNode(nodeId, removeOrphans);
|
|
665
|
+
}
|
|
666
|
+
let configKeyNodes = this.configKeyNodes.get(_filePath);
|
|
667
|
+
// With granular invalidations we will always run this block,
|
|
668
|
+
// so even if we get a create event (for whatever reason), we will still
|
|
669
|
+
// try to limit invalidations from config key changes through hashing.
|
|
670
|
+
//
|
|
671
|
+
// Currently create events can invalidate a large number of nodes due to
|
|
672
|
+
// "create above" invalidations.
|
|
673
|
+
if (configKeyNodes) {
|
|
674
|
+
for (let nodeId of configKeyNodes) {
|
|
675
|
+
let isInvalid = type === 'delete';
|
|
676
|
+
if (type !== 'delete') {
|
|
677
|
+
let node = this.getNode(nodeId);
|
|
678
|
+
(0, assert_1.default)(node && node.type === CONFIG_KEY);
|
|
679
|
+
let contentHash = await (0, ConfigRequest_1.getConfigKeyContentHash)(_filePath, node.configKey, options);
|
|
680
|
+
isInvalid = node.contentHash !== contentHash;
|
|
681
|
+
}
|
|
682
|
+
if (isInvalid) {
|
|
683
|
+
for (let connectedNode of this.getNodeIdsConnectedTo(nodeId, exports.requestGraphEdgeTypes.invalidated_by_update)) {
|
|
684
|
+
invalidateNode(connectedNode, type === 'delete' ? constants_1.FILE_DELETE : constants_1.FILE_UPDATE);
|
|
685
|
+
}
|
|
686
|
+
didInvalidate = true;
|
|
687
|
+
this.removeNode(nodeId, removeOrphans);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
const invalidationsAfter = this.getInvalidNodeCount();
|
|
692
|
+
const invalidationsForEvent = invalidationsAfter - invalidationsBefore;
|
|
693
|
+
invalidationsByPath.set(_path, (invalidationsByPath.get(_path) ?? 0) + invalidationsForEvent);
|
|
694
|
+
}
|
|
695
|
+
if ((0, feature_flags_1.getFeatureFlag)('fixQuadraticCacheInvalidation')) {
|
|
696
|
+
cleanUpOrphans(this);
|
|
697
|
+
}
|
|
698
|
+
let duration = Date.now() - startTime;
|
|
699
|
+
logger_1.default.verbose({
|
|
700
|
+
origin: '@atlaspack/core',
|
|
701
|
+
message: `RequestGraph.respondToFSEvents duration: ${duration}`,
|
|
702
|
+
meta: {
|
|
703
|
+
trackableEvent: 'fsevent_response_time',
|
|
704
|
+
duration,
|
|
705
|
+
predictedTime,
|
|
706
|
+
isInitialBuild,
|
|
707
|
+
numberOfEvents: events.length,
|
|
708
|
+
numberOfInvalidatedNodes: invalidatedNodes.size,
|
|
709
|
+
},
|
|
710
|
+
});
|
|
711
|
+
return {
|
|
712
|
+
didInvalidate,
|
|
713
|
+
invalidationsByPath,
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
hasCachedRequestChunk(index) {
|
|
717
|
+
return this.cachedRequestChunks.has(index);
|
|
718
|
+
}
|
|
719
|
+
setCachedRequestChunk(index) {
|
|
720
|
+
this.cachedRequestChunks.add(index);
|
|
721
|
+
}
|
|
722
|
+
removeCachedRequestChunkForNode(nodeId) {
|
|
723
|
+
this.cachedRequestChunks.delete(Math.floor(nodeId / this.nodesPerBlob));
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Returns the number of invalidated nodes in the graph.
|
|
727
|
+
*/
|
|
728
|
+
getInvalidNodeCount() {
|
|
729
|
+
return this.invalidNodeIds.size;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
exports.RequestGraph = RequestGraph;
|
|
733
|
+
class RequestTracker {
|
|
734
|
+
constructor({ graph, farm, options, rustAtlaspack, }) {
|
|
735
|
+
this.stats = new Map();
|
|
736
|
+
this.graph = graph || new RequestGraph();
|
|
737
|
+
this.farm = farm;
|
|
738
|
+
this.options = options;
|
|
739
|
+
this.rustAtlaspack = rustAtlaspack;
|
|
740
|
+
}
|
|
741
|
+
startRequest(request) {
|
|
742
|
+
let didPreviouslyExist = this.graph.hasContentKey(request.id);
|
|
743
|
+
let requestNodeId;
|
|
744
|
+
if (didPreviouslyExist) {
|
|
745
|
+
requestNodeId = this.graph.getNodeIdByContentKey(request.id);
|
|
746
|
+
// Clear existing invalidations for the request so that the new
|
|
747
|
+
// invalidations created during the request replace the existing ones.
|
|
748
|
+
this.graph.clearInvalidations(requestNodeId);
|
|
749
|
+
}
|
|
750
|
+
else {
|
|
751
|
+
requestNodeId = this.graph.addNode(nodeFromRequest(request));
|
|
752
|
+
}
|
|
753
|
+
this.graph.incompleteNodeIds.add(requestNodeId);
|
|
754
|
+
this.graph.invalidNodeIds.delete(requestNodeId);
|
|
755
|
+
let { promise, deferred } = (0, utils_1.makeDeferredWithPromise)();
|
|
756
|
+
// @ts-expect-error TS2345
|
|
757
|
+
this.graph.incompleteNodePromises.set(requestNodeId, promise);
|
|
758
|
+
return { requestNodeId, deferred };
|
|
759
|
+
}
|
|
760
|
+
// If a cache key is provided, the result will be removed from the node and stored in a separate cache entry
|
|
761
|
+
storeResult(nodeId, result, cacheKey) {
|
|
762
|
+
let node = this.graph.getNode(nodeId);
|
|
763
|
+
if (node && node.type === REQUEST) {
|
|
764
|
+
node.result = result;
|
|
765
|
+
node.resultCacheKey = cacheKey;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
hasValidResult(nodeId) {
|
|
769
|
+
return (this.graph.hasNode(nodeId) &&
|
|
770
|
+
!this.graph.invalidNodeIds.has(nodeId) &&
|
|
771
|
+
!this.graph.incompleteNodeIds.has(nodeId));
|
|
772
|
+
}
|
|
773
|
+
async getRequestResult(contentKey, ifMatch) {
|
|
774
|
+
let node = (0, nullthrows_1.default)(this.graph.getNodeByContentKey(contentKey));
|
|
775
|
+
(0, assert_1.default)(node.type === REQUEST);
|
|
776
|
+
if (ifMatch != null && node.resultCacheKey !== ifMatch) {
|
|
777
|
+
return null;
|
|
778
|
+
}
|
|
779
|
+
if (node.result != undefined) {
|
|
780
|
+
let result = node.result;
|
|
781
|
+
return result;
|
|
782
|
+
}
|
|
783
|
+
else if (node.resultCacheKey != null && ifMatch == null) {
|
|
784
|
+
let key = node.resultCacheKey;
|
|
785
|
+
if (!(0, feature_flags_1.getFeatureFlag)('cachePerformanceImprovements')) {
|
|
786
|
+
(0, assert_1.default)(this.options.cache.hasLargeBlob(key));
|
|
787
|
+
}
|
|
788
|
+
let cachedResult = (0, feature_flags_1.getFeatureFlag)('cachePerformanceImprovements')
|
|
789
|
+
? (0, nullthrows_1.default)(await this.options.cache.get(key))
|
|
790
|
+
: (0, build_cache_1.deserialize)(await this.options.cache.getLargeBlob(key));
|
|
791
|
+
node.result = cachedResult;
|
|
792
|
+
return cachedResult;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
completeRequest(nodeId) {
|
|
796
|
+
this.graph.invalidNodeIds.delete(nodeId);
|
|
797
|
+
this.graph.incompleteNodeIds.delete(nodeId);
|
|
798
|
+
this.graph.incompleteNodePromises.delete(nodeId);
|
|
799
|
+
let node = this.graph.getNode(nodeId);
|
|
800
|
+
if (node && node.type === REQUEST) {
|
|
801
|
+
node.invalidateReason = constants_1.VALID;
|
|
802
|
+
}
|
|
803
|
+
this.graph.removeCachedRequestChunkForNode(nodeId);
|
|
804
|
+
}
|
|
805
|
+
rejectRequest(nodeId) {
|
|
806
|
+
this.graph.incompleteNodeIds.delete(nodeId);
|
|
807
|
+
this.graph.incompleteNodePromises.delete(nodeId);
|
|
808
|
+
let node = this.graph.getNode(nodeId);
|
|
809
|
+
if (node?.type === REQUEST) {
|
|
810
|
+
this.graph.invalidateNode(nodeId, constants_1.ERROR);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
respondToFSEvents(events, threshold) {
|
|
814
|
+
return this.graph.respondToFSEvents(events, this.options, threshold);
|
|
815
|
+
}
|
|
816
|
+
hasInvalidRequests() {
|
|
817
|
+
return this.graph.invalidNodeIds.size > 0;
|
|
818
|
+
}
|
|
819
|
+
getInvalidRequests() {
|
|
820
|
+
let invalidRequests = [];
|
|
821
|
+
for (let id of this.graph.invalidNodeIds) {
|
|
822
|
+
let node = (0, nullthrows_1.default)(this.graph.getNode(id));
|
|
823
|
+
(0, assert_1.default)(node.type === REQUEST);
|
|
824
|
+
invalidRequests.push(node);
|
|
825
|
+
}
|
|
826
|
+
return invalidRequests;
|
|
827
|
+
}
|
|
828
|
+
replaceSubrequests(requestNodeId, subrequestContextKeys) {
|
|
829
|
+
this.graph.replaceSubrequests(requestNodeId, subrequestContextKeys);
|
|
830
|
+
}
|
|
831
|
+
async runRequest(request, opts) {
|
|
832
|
+
let hasKey = this.graph.hasContentKey(request.id);
|
|
833
|
+
let requestId = hasKey
|
|
834
|
+
? this.graph.getNodeIdByContentKey(request.id)
|
|
835
|
+
: undefined;
|
|
836
|
+
let hasValidResult = requestId != null && this.hasValidResult(requestId);
|
|
837
|
+
if (!opts?.force && hasValidResult) {
|
|
838
|
+
// @ts-expect-error TS2322
|
|
839
|
+
return this.getRequestResult(request.id);
|
|
840
|
+
}
|
|
841
|
+
if (requestId != null) {
|
|
842
|
+
let incompletePromise = this.graph.incompleteNodePromises.get(requestId);
|
|
843
|
+
if (incompletePromise != null) {
|
|
844
|
+
// There is a another instance of this request already running, wait for its completion and reuse its result
|
|
845
|
+
try {
|
|
846
|
+
if (await incompletePromise) {
|
|
847
|
+
// @ts-expect-error TS2322
|
|
848
|
+
return this.getRequestResult(request.id);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
catch (e) {
|
|
852
|
+
// Rerun this request
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
let previousInvalidations = requestId != null ? this.graph.getInvalidations(requestId) : [];
|
|
857
|
+
let { requestNodeId, deferred } = this.startRequest({
|
|
858
|
+
id: request.id,
|
|
859
|
+
type: REQUEST,
|
|
860
|
+
requestType: request.type,
|
|
861
|
+
invalidateReason: constants_1.INITIAL_BUILD,
|
|
862
|
+
});
|
|
863
|
+
let { api, subRequestContentKeys } = this.createAPI(requestNodeId, previousInvalidations);
|
|
864
|
+
try {
|
|
865
|
+
let node = this.graph.getRequestNode(requestNodeId);
|
|
866
|
+
this.stats.set(request.type, (this.stats.get(request.type) ?? 0) + 1);
|
|
867
|
+
let result = await request.run({
|
|
868
|
+
input: request.input,
|
|
869
|
+
api,
|
|
870
|
+
farm: this.farm,
|
|
871
|
+
invalidateReason: node.invalidateReason,
|
|
872
|
+
options: this.options,
|
|
873
|
+
rustAtlaspack: this.rustAtlaspack,
|
|
874
|
+
});
|
|
875
|
+
this.completeRequest(requestNodeId);
|
|
876
|
+
deferred.resolve(true);
|
|
877
|
+
return result;
|
|
878
|
+
}
|
|
879
|
+
catch (err) {
|
|
880
|
+
if (!(err instanceof utils_2.BuildAbortError) &&
|
|
881
|
+
request.type === exports.requestTypes.dev_dep_request) {
|
|
882
|
+
logger_1.default.verbose({
|
|
883
|
+
origin: '@atlaspack/core',
|
|
884
|
+
message: `Failed DevDepRequest`,
|
|
885
|
+
meta: {
|
|
886
|
+
trackableEvent: 'failed_dev_dep_request',
|
|
887
|
+
hasKey,
|
|
888
|
+
hasValidResult,
|
|
889
|
+
},
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
this.rejectRequest(requestNodeId);
|
|
893
|
+
deferred.resolve(false);
|
|
894
|
+
throw err;
|
|
895
|
+
}
|
|
896
|
+
finally {
|
|
897
|
+
this.graph.replaceSubrequests(requestNodeId, [...subRequestContentKeys]);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
flushStats() {
|
|
901
|
+
let requestTypeEntries = {};
|
|
902
|
+
for (let key of Object.keys(exports.requestTypes)) {
|
|
903
|
+
requestTypeEntries[exports.requestTypes[key]] = key;
|
|
904
|
+
}
|
|
905
|
+
let formattedStats = {};
|
|
906
|
+
for (let [requestType, count] of this.stats.entries()) {
|
|
907
|
+
let requestTypeName = requestTypeEntries[requestType];
|
|
908
|
+
formattedStats[requestTypeName] = count;
|
|
909
|
+
}
|
|
910
|
+
this.stats = new Map();
|
|
911
|
+
return formattedStats;
|
|
912
|
+
}
|
|
913
|
+
createAPI(requestId, previousInvalidations) {
|
|
914
|
+
let subRequestContentKeys = new Set();
|
|
915
|
+
let api = {
|
|
916
|
+
invalidateOnFileCreate: (input) => this.graph.invalidateOnFileCreate(requestId, input),
|
|
917
|
+
invalidateOnConfigKeyChange: (filePath, configKey, contentHash) => this.graph.invalidateOnConfigKeyChange(requestId, filePath, configKey, contentHash),
|
|
918
|
+
invalidateOnFileDelete: (filePath) => this.graph.invalidateOnFileDelete(requestId, filePath),
|
|
919
|
+
invalidateOnFileUpdate: (filePath) => this.graph.invalidateOnFileUpdate(requestId, filePath),
|
|
920
|
+
invalidateOnStartup: () => this.graph.invalidateOnStartup(requestId),
|
|
921
|
+
invalidateOnBuild: () => this.graph.invalidateOnBuild(requestId),
|
|
922
|
+
invalidateOnEnvChange: (env) => this.graph.invalidateOnEnvChange(requestId, env, this.options.env[env]),
|
|
923
|
+
invalidateOnOptionChange: (option) => this.graph.invalidateOnOptionChange(requestId, option,
|
|
924
|
+
// @ts-expect-error TS7053
|
|
925
|
+
this.options[option]),
|
|
926
|
+
getInvalidations: () => previousInvalidations,
|
|
927
|
+
storeResult: (result, cacheKey) => {
|
|
928
|
+
this.storeResult(requestId, result, cacheKey);
|
|
929
|
+
},
|
|
930
|
+
getSubRequests: () => this.graph.getSubRequests(requestId),
|
|
931
|
+
getInvalidSubRequests: () => this.graph.getInvalidSubRequests(requestId),
|
|
932
|
+
getPreviousResult: (ifMatch) => {
|
|
933
|
+
let contentKey = (0, nullthrows_1.default)(this.graph.getNode(requestId)?.id);
|
|
934
|
+
return this.getRequestResult(contentKey, ifMatch);
|
|
935
|
+
},
|
|
936
|
+
getRequestResult: (
|
|
937
|
+
// @ts-expect-error TS7006
|
|
938
|
+
id) => this.getRequestResult(id),
|
|
939
|
+
canSkipSubrequest: (contentKey) => {
|
|
940
|
+
if (this.graph.hasContentKey(contentKey) &&
|
|
941
|
+
this.hasValidResult(this.graph.getNodeIdByContentKey(contentKey))) {
|
|
942
|
+
subRequestContentKeys.add(contentKey);
|
|
943
|
+
return true;
|
|
944
|
+
}
|
|
945
|
+
return false;
|
|
946
|
+
},
|
|
947
|
+
runRequest: (subRequest, opts) => {
|
|
948
|
+
subRequestContentKeys.add(subRequest.id);
|
|
949
|
+
return this.runRequest(subRequest, opts);
|
|
950
|
+
},
|
|
951
|
+
};
|
|
952
|
+
return { api, subRequestContentKeys };
|
|
953
|
+
}
|
|
954
|
+
async writeToCache(signal) {
|
|
955
|
+
const options = this.options;
|
|
956
|
+
async function runCacheImprovements(newPath, oldPath) {
|
|
957
|
+
if ((0, feature_flags_1.getFeatureFlag)('cachePerformanceImprovements')) {
|
|
958
|
+
(0, assert_1.default)(options.cache instanceof cache_1.LMDBLiteCache);
|
|
959
|
+
const result = await newPath(options.cache);
|
|
960
|
+
return result;
|
|
961
|
+
}
|
|
962
|
+
else {
|
|
963
|
+
const result = await oldPath();
|
|
964
|
+
return result;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
let cacheKey = getCacheKey(this.options);
|
|
968
|
+
let requestGraphKey = (0, feature_flags_1.getFeatureFlag)('cachePerformanceImprovements')
|
|
969
|
+
? `${cacheKey}/RequestGraph`
|
|
970
|
+
: `requestGraph-${cacheKey}`;
|
|
971
|
+
let snapshotKey = (0, feature_flags_1.getFeatureFlag)('cachePerformanceImprovements')
|
|
972
|
+
? `${cacheKey}/snapshot`
|
|
973
|
+
: `snapshot-${cacheKey}`;
|
|
974
|
+
if (this.options.shouldDisableCache) {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
let total = 0;
|
|
978
|
+
await runCacheImprovements(async (cache) => {
|
|
979
|
+
await cache.getNativeRef().startWriteTransaction();
|
|
980
|
+
}, () => Promise.resolve());
|
|
981
|
+
try {
|
|
982
|
+
(0, ReporterRunner_1.report)({
|
|
983
|
+
type: 'cache',
|
|
984
|
+
phase: 'start',
|
|
985
|
+
total,
|
|
986
|
+
size: this.graph.nodes.length,
|
|
987
|
+
});
|
|
988
|
+
if ((0, feature_flags_1.getFeatureFlag)('environmentDeduplication')) {
|
|
989
|
+
await (0, EnvironmentManager_1.writeEnvironmentsToCache)(options.cache);
|
|
990
|
+
}
|
|
991
|
+
let serialisedGraph = this.graph.serialize();
|
|
992
|
+
// Delete an existing request graph cache, to prevent invalid states
|
|
993
|
+
await this.options.cache.deleteLargeBlob(requestGraphKey);
|
|
994
|
+
const serialiseAndSet = async (key, contents) => {
|
|
995
|
+
if (signal?.aborted) {
|
|
996
|
+
throw new Error('Serialization was aborted');
|
|
997
|
+
}
|
|
998
|
+
await runCacheImprovements((cache) => {
|
|
999
|
+
(0, logger_1.instrument)(`RequestTracker::writeToCache::cache.put(${key})`, () => {
|
|
1000
|
+
cache.getNativeRef().putNoConfirm(key, (0, build_cache_1.serialize)(contents));
|
|
1001
|
+
});
|
|
1002
|
+
return Promise.resolve();
|
|
1003
|
+
}, async () => {
|
|
1004
|
+
await this.options.cache.setLargeBlob(key, (0, build_cache_1.serialize)(contents), signal
|
|
1005
|
+
? {
|
|
1006
|
+
signal: signal,
|
|
1007
|
+
}
|
|
1008
|
+
: undefined);
|
|
1009
|
+
});
|
|
1010
|
+
total += 1;
|
|
1011
|
+
(0, ReporterRunner_1.report)({
|
|
1012
|
+
type: 'cache',
|
|
1013
|
+
phase: 'write',
|
|
1014
|
+
total,
|
|
1015
|
+
size: this.graph.nodes.length,
|
|
1016
|
+
});
|
|
1017
|
+
};
|
|
1018
|
+
let queue = new utils_1.PromiseQueue({
|
|
1019
|
+
maxConcurrent: 32,
|
|
1020
|
+
});
|
|
1021
|
+
// Preallocating a sparse array is faster than pushing when N is high enough
|
|
1022
|
+
let cacheableNodes = new Array(serialisedGraph.nodes.length);
|
|
1023
|
+
for (let i = 0; i < serialisedGraph.nodes.length; i += 1) {
|
|
1024
|
+
let node = serialisedGraph.nodes[i];
|
|
1025
|
+
// @ts-expect-error TS2339
|
|
1026
|
+
let resultCacheKey = node?.resultCacheKey;
|
|
1027
|
+
if (node?.type === REQUEST &&
|
|
1028
|
+
resultCacheKey != null &&
|
|
1029
|
+
node?.result != null) {
|
|
1030
|
+
queue.add(() => serialiseAndSet(resultCacheKey, node.result));
|
|
1031
|
+
// eslint-disable-next-line no-unused-vars
|
|
1032
|
+
let { result: _, ...newNode } = node;
|
|
1033
|
+
cacheableNodes[i] = newNode;
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
cacheableNodes[i] = node;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
let nodeCountsPerBlob = [];
|
|
1040
|
+
for (let i = 0; i * this.graph.nodesPerBlob < cacheableNodes.length; i += 1) {
|
|
1041
|
+
let nodesStartIndex = i * this.graph.nodesPerBlob;
|
|
1042
|
+
let nodesEndIndex = Math.min((i + 1) * this.graph.nodesPerBlob, cacheableNodes.length);
|
|
1043
|
+
nodeCountsPerBlob.push(nodesEndIndex - nodesStartIndex);
|
|
1044
|
+
if (!this.graph.hasCachedRequestChunk(i)) {
|
|
1045
|
+
// We assume the request graph nodes are immutable and won't change
|
|
1046
|
+
let nodesToCache = cacheableNodes.slice(nodesStartIndex, nodesEndIndex);
|
|
1047
|
+
queue.add(() => serialiseAndSet(getRequestGraphNodeKey(i, cacheKey), nodesToCache).then(() => {
|
|
1048
|
+
// Succeeded in writing to disk, save that we have completed this chunk
|
|
1049
|
+
this.graph.setCachedRequestChunk(i);
|
|
1050
|
+
}));
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
await queue.run();
|
|
1054
|
+
// Set the request graph after the queue is flushed to avoid writing an invalid state
|
|
1055
|
+
await serialiseAndSet(requestGraphKey, {
|
|
1056
|
+
...serialisedGraph,
|
|
1057
|
+
nodeCountsPerBlob,
|
|
1058
|
+
nodes: undefined,
|
|
1059
|
+
});
|
|
1060
|
+
await runCacheImprovements(() => serialiseAndSet(`${cacheKey}/cache_metadata`, {
|
|
1061
|
+
version: constants_1.ATLASPACK_VERSION,
|
|
1062
|
+
entries: this.options.entries,
|
|
1063
|
+
mode: this.options.mode,
|
|
1064
|
+
shouldBuildLazily: this.options.shouldBuildLazily,
|
|
1065
|
+
watchBackend: this.options.watchBackend,
|
|
1066
|
+
}), () => Promise.resolve());
|
|
1067
|
+
let opts = getWatcherOptions(this.options);
|
|
1068
|
+
let snapshotPath = path_1.default.join(this.options.cacheDir, snapshotKey + '.txt');
|
|
1069
|
+
await this.options.outputFS.writeSnapshot(this.options.watchDir, snapshotPath, opts);
|
|
1070
|
+
}
|
|
1071
|
+
catch (err) {
|
|
1072
|
+
// If we have aborted, ignore the error and continue
|
|
1073
|
+
if (!signal?.aborted)
|
|
1074
|
+
throw err;
|
|
1075
|
+
}
|
|
1076
|
+
finally {
|
|
1077
|
+
await runCacheImprovements(async (cache) => {
|
|
1078
|
+
await cache.getNativeRef().commitWriteTransaction();
|
|
1079
|
+
}, () => Promise.resolve());
|
|
1080
|
+
}
|
|
1081
|
+
(0, ReporterRunner_1.report)({ type: 'cache', phase: 'end', total, size: this.graph.nodes.length });
|
|
1082
|
+
}
|
|
1083
|
+
static async init({ farm, options, rustAtlaspack, }) {
|
|
1084
|
+
let graph = await loadRequestGraph(options);
|
|
1085
|
+
return new RequestTracker({ farm, graph, options, rustAtlaspack });
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
exports.default = RequestTracker;
|
|
1089
|
+
function getWatcherOptions({ watchIgnore = [], cacheDir, watchDir, watchBackend, }) {
|
|
1090
|
+
const vcsDirs = ['.git', '.hg'];
|
|
1091
|
+
const uniqueDirs = [...new Set([...watchIgnore, ...vcsDirs, cacheDir])];
|
|
1092
|
+
const ignore = uniqueDirs.map((dir) => path_1.default.resolve(watchDir, dir));
|
|
1093
|
+
return { ignore, backend: watchBackend };
|
|
1094
|
+
}
|
|
1095
|
+
function getCacheKey(options) {
|
|
1096
|
+
if ((0, feature_flags_1.getFeatureFlag)('cachePerformanceImprovements')) {
|
|
1097
|
+
const hash = (0, rust_1.hashString)(`${constants_1.ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${options.mode}:${options.shouldBuildLazily ? 'lazy' : 'eager'}:${options.watchBackend ?? ''}`);
|
|
1098
|
+
return `RequestTracker/${constants_1.ATLASPACK_VERSION}/${hash}`;
|
|
1099
|
+
}
|
|
1100
|
+
return (0, rust_1.hashString)(`${constants_1.ATLASPACK_VERSION}:${JSON.stringify(options.entries)}:${options.mode}:${options.shouldBuildLazily ? 'lazy' : 'eager'}:${options.watchBackend ?? ''}`);
|
|
1101
|
+
}
|
|
1102
|
+
function getRequestGraphNodeKey(index, cacheKey) {
|
|
1103
|
+
if ((0, feature_flags_1.getFeatureFlag)('cachePerformanceImprovements')) {
|
|
1104
|
+
return `${cacheKey}/RequestGraph/nodes/${index}`;
|
|
1105
|
+
}
|
|
1106
|
+
return `requestGraph-nodes-${index}-${cacheKey}`;
|
|
1107
|
+
}
|
|
1108
|
+
async function readAndDeserializeRequestGraph(cache, requestGraphKey, cacheKey) {
|
|
1109
|
+
let bufferLength = 0;
|
|
1110
|
+
const getAndDeserialize = async (key) => {
|
|
1111
|
+
if ((0, feature_flags_1.getFeatureFlag)('cachePerformanceImprovements')) {
|
|
1112
|
+
const buffer = await cache.getBlob(key);
|
|
1113
|
+
bufferLength += Buffer.byteLength(buffer);
|
|
1114
|
+
return (0, build_cache_1.deserialize)(buffer);
|
|
1115
|
+
}
|
|
1116
|
+
else {
|
|
1117
|
+
const buffer = await cache.getLargeBlob(key);
|
|
1118
|
+
bufferLength += Buffer.byteLength(buffer);
|
|
1119
|
+
return (0, build_cache_1.deserialize)(buffer);
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
let serializedRequestGraph = await getAndDeserialize(requestGraphKey);
|
|
1123
|
+
let nodePromises = serializedRequestGraph.nodeCountsPerBlob.map(
|
|
1124
|
+
// @ts-expect-error TS7006
|
|
1125
|
+
async (nodesCount, i) => {
|
|
1126
|
+
let nodes = await getAndDeserialize(getRequestGraphNodeKey(i, cacheKey));
|
|
1127
|
+
assert_1.default.equal(nodes.length, nodesCount, 'RequestTracker node chunk: invalid node count');
|
|
1128
|
+
return nodes;
|
|
1129
|
+
});
|
|
1130
|
+
return {
|
|
1131
|
+
requestGraph: RequestGraph.deserialize({
|
|
1132
|
+
...serializedRequestGraph,
|
|
1133
|
+
nodes: (await Promise.all(nodePromises)).flat(),
|
|
1134
|
+
}),
|
|
1135
|
+
// This is used inside atlaspack query for `.inspectCache`
|
|
1136
|
+
bufferLength,
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
async function loadRequestGraph(options) {
|
|
1140
|
+
if (options.shouldDisableCache) {
|
|
1141
|
+
return new RequestGraph();
|
|
1142
|
+
}
|
|
1143
|
+
let cacheKey = getCacheKey(options);
|
|
1144
|
+
let requestGraphKey = (0, feature_flags_1.getFeatureFlag)('cachePerformanceImprovements')
|
|
1145
|
+
? `${cacheKey}/RequestGraph`
|
|
1146
|
+
: `requestGraph-${cacheKey}`;
|
|
1147
|
+
let timeout;
|
|
1148
|
+
const snapshotKey = (0, feature_flags_1.getFeatureFlag)('cachePerformanceImprovements')
|
|
1149
|
+
? `${cacheKey}/snapshot`
|
|
1150
|
+
: `snapshot-${cacheKey}`;
|
|
1151
|
+
const snapshotPath = path_1.default.join(options.cacheDir, snapshotKey + '.txt');
|
|
1152
|
+
const commonMeta = {
|
|
1153
|
+
cacheKey,
|
|
1154
|
+
snapshotKey,
|
|
1155
|
+
cacheKeyOptions: {
|
|
1156
|
+
version: constants_1.ATLASPACK_VERSION,
|
|
1157
|
+
entries: options.entries,
|
|
1158
|
+
mode: options.mode,
|
|
1159
|
+
shouldBuildLazily: options.shouldBuildLazily,
|
|
1160
|
+
watchBackend: options.watchBackend,
|
|
1161
|
+
},
|
|
1162
|
+
};
|
|
1163
|
+
logger_1.default.verbose({
|
|
1164
|
+
origin: '@atlaspack/core',
|
|
1165
|
+
message: 'Loading request graph',
|
|
1166
|
+
meta: {
|
|
1167
|
+
...commonMeta,
|
|
1168
|
+
},
|
|
1169
|
+
});
|
|
1170
|
+
if ((0, feature_flags_1.getFeatureFlag)('environmentDeduplication')) {
|
|
1171
|
+
await (0, EnvironmentManager_1.loadEnvironmentsFromCache)(options.cache);
|
|
1172
|
+
}
|
|
1173
|
+
const hasRequestGraphInCache = (0, feature_flags_1.getFeatureFlag)('cachePerformanceImprovements')
|
|
1174
|
+
? await options.cache.has(requestGraphKey)
|
|
1175
|
+
: await options.cache.hasLargeBlob(requestGraphKey);
|
|
1176
|
+
if (hasRequestGraphInCache) {
|
|
1177
|
+
try {
|
|
1178
|
+
let { requestGraph } = await readAndDeserializeRequestGraph(options.cache, requestGraphKey, cacheKey);
|
|
1179
|
+
let opts = getWatcherOptions(options);
|
|
1180
|
+
timeout = setTimeout(() => {
|
|
1181
|
+
logger_1.default.warn({
|
|
1182
|
+
origin: '@atlaspack/core',
|
|
1183
|
+
message: `Retrieving file system events since last build...\nThis can take upto a minute after branch changes or npm/yarn installs.`,
|
|
1184
|
+
});
|
|
1185
|
+
}, 5000);
|
|
1186
|
+
let startTime = Date.now();
|
|
1187
|
+
let events = process.env.ATLASPACK_BYPASS_CACHE_INVALIDATION === 'true'
|
|
1188
|
+
? []
|
|
1189
|
+
: await options.inputFS.getEventsSince(options.watchDir, snapshotPath, opts);
|
|
1190
|
+
clearTimeout(timeout);
|
|
1191
|
+
logger_1.default.verbose({
|
|
1192
|
+
origin: '@atlaspack/core',
|
|
1193
|
+
message: `File system event count: ${events.length}`,
|
|
1194
|
+
meta: {
|
|
1195
|
+
...commonMeta,
|
|
1196
|
+
trackableEvent: 'watcher_events_count',
|
|
1197
|
+
watcherEventCount: events.length,
|
|
1198
|
+
duration: Date.now() - startTime,
|
|
1199
|
+
},
|
|
1200
|
+
});
|
|
1201
|
+
if ((0, feature_flags_1.getFeatureFlag)('verboseRequestInvalidationStats')) {
|
|
1202
|
+
const invalidationStats = await invalidateRequestGraph(requestGraph, options, events);
|
|
1203
|
+
logger_1.default.verbose({
|
|
1204
|
+
origin: '@atlaspack/core',
|
|
1205
|
+
message: 'Request track loaded from cache',
|
|
1206
|
+
meta: {
|
|
1207
|
+
...commonMeta,
|
|
1208
|
+
trackableEvent: 'request_tracker_cache_key_hit',
|
|
1209
|
+
invalidationStats,
|
|
1210
|
+
},
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1213
|
+
else {
|
|
1214
|
+
requestGraph.invalidateUnpredictableNodes();
|
|
1215
|
+
requestGraph.invalidateOnBuildNodes();
|
|
1216
|
+
requestGraph.invalidateEnvNodes(options.env);
|
|
1217
|
+
requestGraph.invalidateOptionNodes(options);
|
|
1218
|
+
}
|
|
1219
|
+
return requestGraph;
|
|
1220
|
+
}
|
|
1221
|
+
catch (e) {
|
|
1222
|
+
// Prevent logging fs events took too long warning
|
|
1223
|
+
clearTimeout(timeout);
|
|
1224
|
+
logErrorOnBailout(options, snapshotPath, e);
|
|
1225
|
+
// This error means respondToFSEvents timed out handling the invalidation events
|
|
1226
|
+
// In this case we'll return a fresh RequestGraph
|
|
1227
|
+
return new RequestGraph();
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
logger_1.default.verbose({
|
|
1231
|
+
origin: '@atlaspack/core',
|
|
1232
|
+
message: 'Cache entry for request tracker was not found, initializing a clean cache.',
|
|
1233
|
+
meta: {
|
|
1234
|
+
...commonMeta,
|
|
1235
|
+
trackableEvent: 'request_tracker_cache_key_miss',
|
|
1236
|
+
},
|
|
1237
|
+
});
|
|
1238
|
+
return new RequestGraph();
|
|
1239
|
+
}
|
|
1240
|
+
/**
|
|
1241
|
+
* Respond to unpredictable, build, environment changes, option changes and file-system events
|
|
1242
|
+
* invalidating RequestGraph nodes.
|
|
1243
|
+
*
|
|
1244
|
+
* Returns the count of nodes invalidated by each invalidation type.
|
|
1245
|
+
*/
|
|
1246
|
+
async function invalidateRequestGraph(requestGraph, options, events) {
|
|
1247
|
+
const invalidationFns = [
|
|
1248
|
+
{
|
|
1249
|
+
key: 'unpredictable',
|
|
1250
|
+
// @ts-expect-error TS2322
|
|
1251
|
+
fn: () => requestGraph.invalidateUnpredictableNodes(),
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
key: 'onBuild',
|
|
1255
|
+
// @ts-expect-error TS2322
|
|
1256
|
+
fn: () => requestGraph.invalidateOnBuildNodes(),
|
|
1257
|
+
},
|
|
1258
|
+
{
|
|
1259
|
+
key: 'env',
|
|
1260
|
+
fn: () => requestGraph.invalidateEnvNodes(options.env),
|
|
1261
|
+
},
|
|
1262
|
+
{
|
|
1263
|
+
key: 'option',
|
|
1264
|
+
fn: () => requestGraph.invalidateOptionNodes(options),
|
|
1265
|
+
},
|
|
1266
|
+
{
|
|
1267
|
+
key: 'fsEvents',
|
|
1268
|
+
fn: () => invalidateRequestGraphFSEvents(requestGraph, options, events),
|
|
1269
|
+
},
|
|
1270
|
+
];
|
|
1271
|
+
const invalidations = [];
|
|
1272
|
+
for (const invalidation of invalidationFns) {
|
|
1273
|
+
invalidations.push(await runInvalidation(requestGraph, invalidation));
|
|
1274
|
+
}
|
|
1275
|
+
const invalidatedCount = invalidations.reduce((acc, invalidation) => acc + invalidation.count, 0);
|
|
1276
|
+
const requestCount = requestGraph.nodes.reduce((acc, node) => acc + (node?.type === REQUEST ? 1 : 0), 0);
|
|
1277
|
+
const nodeCount = requestGraph.nodes.length;
|
|
1278
|
+
const nodeInvalidationRatio = invalidatedCount / nodeCount;
|
|
1279
|
+
const requestInvalidationRatio = invalidatedCount / requestCount;
|
|
1280
|
+
return {
|
|
1281
|
+
invalidations,
|
|
1282
|
+
nodeCount,
|
|
1283
|
+
requestCount,
|
|
1284
|
+
invalidatedCount,
|
|
1285
|
+
nodeInvalidationRatio,
|
|
1286
|
+
requestInvalidationRatio,
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Invalidate the request graph based on file-system events.
|
|
1291
|
+
*
|
|
1292
|
+
* Returns statistics about the invalidations.
|
|
1293
|
+
*/
|
|
1294
|
+
async function invalidateRequestGraphFSEvents(requestGraph, options, events) {
|
|
1295
|
+
const { invalidationsByPath } = await requestGraph.respondToFSEvents(options.unstableFileInvalidations || events, options, 10000, true);
|
|
1296
|
+
const biggestInvalidations = getBiggestFSEventsInvalidations(invalidationsByPath);
|
|
1297
|
+
return {
|
|
1298
|
+
biggestInvalidations,
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Runs an invalidation function and reports metrics.
|
|
1303
|
+
*/
|
|
1304
|
+
async function runInvalidation(requestGraph, invalidationFn) {
|
|
1305
|
+
const start = perf_hooks_1.performance.now();
|
|
1306
|
+
const startInvalidationCount = requestGraph.getInvalidNodeCount();
|
|
1307
|
+
const result = await invalidationFn.fn();
|
|
1308
|
+
const count = requestGraph.getInvalidNodeCount() - startInvalidationCount;
|
|
1309
|
+
const duration = perf_hooks_1.performance.now() - start;
|
|
1310
|
+
return {
|
|
1311
|
+
key: invalidationFn.key,
|
|
1312
|
+
count,
|
|
1313
|
+
detail: result ?? null,
|
|
1314
|
+
duration,
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
function logErrorOnBailout(options, snapshotPath, e) {
|
|
1318
|
+
if (e.message && e.message.includes('invalid clockspec')) {
|
|
1319
|
+
const snapshotContents = options.inputFS.readFileSync(snapshotPath, 'utf-8');
|
|
1320
|
+
logger_1.default.warn({
|
|
1321
|
+
origin: '@atlaspack/core',
|
|
1322
|
+
message: `Error reading clockspec from snapshot, building with clean cache.`,
|
|
1323
|
+
meta: {
|
|
1324
|
+
snapshotContents: snapshotContents,
|
|
1325
|
+
trackableEvent: 'invalid_clockspec_error',
|
|
1326
|
+
},
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
else {
|
|
1330
|
+
logger_1.default.warn({
|
|
1331
|
+
origin: '@atlaspack/core',
|
|
1332
|
+
message: `Unexpected error loading cache from disk, building with clean cache.`,
|
|
1333
|
+
meta: {
|
|
1334
|
+
errorMessage: e.message,
|
|
1335
|
+
errorStack: e.stack,
|
|
1336
|
+
trackableEvent: 'cache_load_error',
|
|
1337
|
+
},
|
|
1338
|
+
});
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
function cleanUpOrphans(graph) {
|
|
1342
|
+
if (graph.rootNodeId == null) {
|
|
1343
|
+
return [];
|
|
1344
|
+
}
|
|
1345
|
+
const reachableNodes = new Set();
|
|
1346
|
+
graph.traverse((nodeId) => {
|
|
1347
|
+
reachableNodes.add(nodeId);
|
|
1348
|
+
});
|
|
1349
|
+
const removedNodeIds = [];
|
|
1350
|
+
graph.nodes.forEach((_node, nodeId) => {
|
|
1351
|
+
if (!reachableNodes.has(nodeId)) {
|
|
1352
|
+
removedNodeIds.push(nodeId);
|
|
1353
|
+
graph.removeNode(nodeId);
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
return removedNodeIds;
|
|
1357
|
+
}
|
|
1358
|
+
/**
|
|
1359
|
+
* Returns paths that invalidated the most nodes
|
|
1360
|
+
*/
|
|
1361
|
+
function getBiggestFSEventsInvalidations(invalidationsByPath, limit = 10) {
|
|
1362
|
+
const invalidations = [];
|
|
1363
|
+
for (const [path, count] of invalidationsByPath) {
|
|
1364
|
+
invalidations.push({ path, count });
|
|
1365
|
+
}
|
|
1366
|
+
invalidations.sort((a, b) => b.count - a.count);
|
|
1367
|
+
return invalidations.slice(0, limit);
|
|
1368
|
+
}
|