|
|
|
"use strict";
|
|
|
|
|
|
|
|
Object.defineProperty(exports, "__esModule", {
|
|
|
|
value: true
|
|
|
|
});
|
|
|
|
exports.getWatcherOptions = getWatcherOptions;
|
|
|
|
exports.default = exports.RequestGraph = exports.requestGraphEdgeTypes = void 0;
|
|
|
|
|
|
|
|
function _assert() {
|
|
|
|
const data = _interopRequireDefault(require("assert"));
|
|
|
|
|
|
|
|
_assert = function () {
|
|
|
|
return data;
|
|
|
|
};
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _nullthrows() {
|
|
|
|
const data = _interopRequireDefault(require("nullthrows"));
|
|
|
|
|
|
|
|
_nullthrows = function () {
|
|
|
|
return data;
|
|
|
|
};
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _path() {
|
|
|
|
const data = _interopRequireDefault(require("path"));
|
|
|
|
|
|
|
|
_path = function () {
|
|
|
|
return data;
|
|
|
|
};
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _utils() {
|
|
|
|
const data = require("@parcel/utils");
|
|
|
|
|
|
|
|
_utils = function () {
|
|
|
|
return data;
|
|
|
|
};
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _hash() {
|
|
|
|
const data = require("@parcel/hash");
|
|
|
|
|
|
|
|
_hash = function () {
|
|
|
|
return data;
|
|
|
|
};
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _graph() {
|
|
|
|
const data = require("@parcel/graph");
|
|
|
|
|
|
|
|
_graph = function () {
|
|
|
|
return data;
|
|
|
|
};
|
|
|
|
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
|
|
|
var _utils2 = require("./utils");
|
|
|
|
|
|
|
|
var _projectPath = require("./projectPath");
|
|
|
|
|
|
|
|
var _constants = require("./constants");
|
|
|
|
|
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
|
|
|
|
|
|
const requestGraphEdgeTypes = {
|
|
|
|
subrequest: 2,
|
|
|
|
invalidated_by_update: 3,
|
|
|
|
invalidated_by_delete: 4,
|
|
|
|
invalidated_by_create: 5,
|
|
|
|
invalidated_by_create_above: 6,
|
|
|
|
dirname: 7
|
|
|
|
};
|
|
|
|
exports.requestGraphEdgeTypes = requestGraphEdgeTypes;
|
|
|
|
|
|
|
|
const nodeFromFilePath = filePath => ({
|
|
|
|
id: (0, _projectPath.fromProjectPathRelative)(filePath),
|
|
|
|
type: 'file',
|
|
|
|
value: {
|
|
|
|
filePath
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const nodeFromGlob = glob => ({
|
|
|
|
id: (0, _projectPath.fromProjectPathRelative)(glob),
|
|
|
|
type: 'glob',
|
|
|
|
value: glob
|
|
|
|
});
|
|
|
|
|
|
|
|
const nodeFromFileName = fileName => ({
|
|
|
|
id: 'file_name:' + fileName,
|
|
|
|
type: 'file_name',
|
|
|
|
value: fileName
|
|
|
|
});
|
|
|
|
|
|
|
|
const nodeFromRequest = request => ({
|
|
|
|
id: request.id,
|
|
|
|
type: 'request',
|
|
|
|
value: request,
|
|
|
|
invalidateReason: _constants.INITIAL_BUILD
|
|
|
|
});
|
|
|
|
|
|
|
|
const nodeFromEnv = (env, value) => ({
|
|
|
|
id: 'env:' + env,
|
|
|
|
type: 'env',
|
|
|
|
value: {
|
|
|
|
key: env,
|
|
|
|
value
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const nodeFromOption = (option, value) => ({
|
|
|
|
id: 'option:' + option,
|
|
|
|
type: 'option',
|
|
|
|
value: {
|
|
|
|
key: option,
|
|
|
|
hash: (0, _utils2.hashFromOption)(value)
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
class RequestGraph extends _graph().ContentGraph {
|
|
|
|
invalidNodeIds = new Set();
|
|
|
|
incompleteNodeIds = new Set();
|
|
|
|
incompleteNodePromises = new Map();
|
|
|
|
globNodeIds = new Set();
|
|
|
|
envNodeIds = new Set();
|
|
|
|
optionNodeIds = new Set(); // Unpredictable nodes are requests that cannot be predicted whether they should rerun based on
|
|
|
|
// filesystem changes alone. They should rerun on each startup of Parcel.
|
|
|
|
|
|
|
|
unpredicatableNodeIds = new Set(); // $FlowFixMe[prop-missing]
|
|
|
|
|
|
|
|
static deserialize(opts) {
|
|
|
|
// $FlowFixMe[prop-missing]
|
|
|
|
let deserialized = new RequestGraph(opts);
|
|
|
|
deserialized.invalidNodeIds = opts.invalidNodeIds;
|
|
|
|
deserialized.incompleteNodeIds = opts.incompleteNodeIds;
|
|
|
|
deserialized.globNodeIds = opts.globNodeIds;
|
|
|
|
deserialized.envNodeIds = opts.envNodeIds;
|
|
|
|
deserialized.optionNodeIds = opts.optionNodeIds;
|
|
|
|
deserialized.unpredicatableNodeIds = opts.unpredicatableNodeIds;
|
|
|
|
return deserialized;
|
|
|
|
} // $FlowFixMe[prop-missing]
|
|
|
|
|
|
|
|
|
|
|
|
serialize() {
|
|
|
|
return { ...super.serialize(),
|
|
|
|
invalidNodeIds: this.invalidNodeIds,
|
|
|
|
incompleteNodeIds: this.incompleteNodeIds,
|
|
|
|
globNodeIds: this.globNodeIds,
|
|
|
|
envNodeIds: this.envNodeIds,
|
|
|
|
optionNodeIds: this.optionNodeIds,
|
|
|
|
unpredicatableNodeIds: this.unpredicatableNodeIds
|
|
|
|
};
|
|
|
|
} // addNode for RequestGraph should not override the value if added multiple times
|
|
|
|
|
|
|
|
|
|
|
|
addNode(node) {
|
|
|
|
let nodeId = this._contentKeyToNodeId.get(node.id);
|
|
|
|
|
|
|
|
if (nodeId != null) {
|
|
|
|
return nodeId;
|
|
|
|
}
|
|
|
|
|
|
|
|
nodeId = super.addNodeByContentKey(node.id, node);
|
|
|
|
|
|
|
|
if (node.type === 'glob') {
|
|
|
|
this.globNodeIds.add(nodeId);
|
|
|
|
} else if (node.type === 'env') {
|
|
|
|
this.envNodeIds.add(nodeId);
|
|
|
|
} else if (node.type === 'option') {
|
|
|
|
this.optionNodeIds.add(nodeId);
|
|
|
|
}
|
|
|
|
|
|
|
|
return nodeId;
|
|
|
|
}
|
|
|
|
|
|
|
|
removeNode(nodeId) {
|
|
|
|
this.invalidNodeIds.delete(nodeId);
|
|
|
|
this.incompleteNodeIds.delete(nodeId);
|
|
|
|
this.incompleteNodePromises.delete(nodeId);
|
|
|
|
this.unpredicatableNodeIds.delete(nodeId);
|
|
|
|
let node = (0, _nullthrows().default)(this.getNode(nodeId));
|
|
|
|
|
|
|
|
if (node.type === 'glob') {
|
|
|
|
this.globNodeIds.delete(nodeId);
|
|
|
|
} else if (node.type === 'env') {
|
|
|
|
this.envNodeIds.delete(nodeId);
|
|
|
|
} else if (node.type === 'option') {
|
|
|
|
this.optionNodeIds.delete(nodeId);
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.removeNode(nodeId);
|
|
|
|
}
|
|
|
|
|
|
|
|
getRequestNode(nodeId) {
|
|
|
|
let node = (0, _nullthrows().default)(this.getNode(nodeId));
|
|
|
|
(0, _assert().default)(node.type === 'request');
|
|
|
|
return node;
|
|
|
|
}
|
|
|
|
|
|
|
|
replaceSubrequests(requestNodeId, subrequestContentKeys) {
|
|
|
|
let subrequestNodeIds = [];
|
|
|
|
|
|
|
|
for (let key of subrequestContentKeys) {
|
|
|
|
if (this.hasContentKey(key)) {
|
|
|
|
subrequestNodeIds.push(this.getNodeIdByContentKey(key));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.replaceNodeIdsConnectedTo(requestNodeId, subrequestNodeIds, null, requestGraphEdgeTypes.subrequest);
|
|
|
|
}
|
|
|
|
|
|
|
|
invalidateNode(nodeId, reason) {
|
|
|
|
let node = (0, _nullthrows().default)(this.getNode(nodeId));
|
|
|
|
(0, _assert().default)(node.type === 'request');
|
|
|
|
node.invalidateReason |= reason;
|
|
|
|
this.invalidNodeIds.add(nodeId);
|
|
|
|
let parentNodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.subrequest);
|
|
|
|
|
|
|
|
for (let parentNode of parentNodes) {
|
|
|
|
this.invalidateNode(parentNode, reason);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
invalidateUnpredictableNodes() {
|
|
|
|
for (let nodeId of this.unpredicatableNodeIds) {
|
|
|
|
let node = (0, _nullthrows().default)(this.getNode(nodeId));
|
|
|
|
(0, _assert().default)(node.type !== 'file' && node.type !== 'glob');
|
|
|
|
this.invalidateNode(nodeId, _constants.STARTUP);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
invalidateEnvNodes(env) {
|
|
|
|
for (let nodeId of this.envNodeIds) {
|
|
|
|
let node = (0, _nullthrows().default)(this.getNode(nodeId));
|
|
|
|
(0, _assert().default)(node.type === 'env');
|
|
|
|
|
|
|
|
if (env[node.value.key] !== node.value.value) {
|
|
|
|
let parentNodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_update);
|
|
|
|
|
|
|
|
for (let parentNode of parentNodes) {
|
|
|
|
this.invalidateNode(parentNode, _constants.ENV_CHANGE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
invalidateOptionNodes(options) {
|
|
|
|
for (let nodeId of this.optionNodeIds) {
|
|
|
|
let node = (0, _nullthrows().default)(this.getNode(nodeId));
|
|
|
|
(0, _assert().default)(node.type === 'option');
|
|
|
|
|
|
|
|
if ((0, _utils2.hashFromOption)(options[node.value.key]) !== node.value.hash) {
|
|
|
|
let parentNodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_update);
|
|
|
|
|
|
|
|
for (let parentNode of parentNodes) {
|
|
|
|
this.invalidateNode(parentNode, _constants.OPTION_CHANGE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
invalidateOnFileUpdate(requestNodeId, filePath) {
|
|
|
|
let fileNodeId = this.addNode(nodeFromFilePath(filePath));
|
|
|
|
|
|
|
|
if (!this.hasEdge(requestNodeId, fileNodeId, requestGraphEdgeTypes.invalidated_by_update)) {
|
|
|
|
this.addEdge(requestNodeId, fileNodeId, requestGraphEdgeTypes.invalidated_by_update);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
invalidateOnFileDelete(requestNodeId, filePath) {
|
|
|
|
let fileNodeId = this.addNode(nodeFromFilePath(filePath));
|
|
|
|
|
|
|
|
if (!this.hasEdge(requestNodeId, fileNodeId, requestGraphEdgeTypes.invalidated_by_delete)) {
|
|
|
|
this.addEdge(requestNodeId, fileNodeId, requestGraphEdgeTypes.invalidated_by_delete);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
invalidateOnFileCreate(requestNodeId, input) {
|
|
|
|
let node;
|
|
|
|
|
|
|
|
if (input.glob != null) {
|
|
|
|
node = nodeFromGlob(input.glob);
|
|
|
|
} else if (input.fileName != null && input.aboveFilePath != null) {
|
|
|
|
let aboveFilePath = input.aboveFilePath; // Create nodes and edges for each part of the filename pattern.
|
|
|
|
// For example, 'node_modules/foo' would create two nodes and one edge.
|
|
|
|
// This creates a sort of trie structure within the graph that can be
|
|
|
|
// quickly matched by following the edges. This is also memory efficient
|
|
|
|
// since common sub-paths (e.g. 'node_modules') are deduplicated.
|
|
|
|
|
|
|
|
let parts = input.fileName.split('/').reverse();
|
|
|
|
let lastNodeId;
|
|
|
|
|
|
|
|
for (let part of parts) {
|
|
|
|
let fileNameNode = nodeFromFileName(part);
|
|
|
|
let fileNameNodeId = this.addNode(fileNameNode);
|
|
|
|
|
|
|
|
if (lastNodeId != null && !this.hasEdge(lastNodeId, fileNameNodeId, requestGraphEdgeTypes.dirname)) {
|
|
|
|
this.addEdge(lastNodeId, fileNameNodeId, requestGraphEdgeTypes.dirname);
|
|
|
|
}
|
|
|
|
|
|
|
|
lastNodeId = fileNameNodeId;
|
|
|
|
} // The `aboveFilePath` condition asserts that requests are only invalidated
|
|
|
|
// if the file being created is "above" it in the filesystem (e.g. the file
|
|
|
|
// is created in a parent directory). There is likely to already be a node
|
|
|
|
// for this file in the graph (e.g. the source file) that we can reuse for this.
|
|
|
|
|
|
|
|
|
|
|
|
node = nodeFromFilePath(aboveFilePath);
|
|
|
|
let nodeId = this.addNode(node); // Now create an edge from the `aboveFilePath` node to the first file_name node
|
|
|
|
// in the chain created above, and an edge from the last node in the chain back to
|
|
|
|
// the `aboveFilePath` node. When matching, we will start from the first node in
|
|
|
|
// the chain, and continue following it to parent directories until there is an
|
|
|
|
// edge pointing an `aboveFilePath` node that also points to the start of the chain.
|
|
|
|
// This indicates a complete match, and any requests attached to the `aboveFilePath`
|
|
|
|
// node will be invalidated.
|
|
|
|
|
|
|
|
let firstId = 'file_name:' + parts[0];
|
|
|
|
let firstNodeId = this.getNodeIdByContentKey(firstId);
|
|
|
|
|
|
|
|
if (!this.hasEdge(nodeId, firstNodeId, requestGraphEdgeTypes.invalidated_by_create_above)) {
|
|
|
|
this.addEdge(nodeId, firstNodeId, requestGraphEdgeTypes.invalidated_by_create_above);
|
|
|
|
}
|
|
|
|
|
|
|
|
(0, _assert().default)(lastNodeId != null);
|
|
|
|
|
|
|
|
if (!this.hasEdge(lastNodeId, nodeId, requestGraphEdgeTypes.invalidated_by_create_above)) {
|
|
|
|
this.addEdge(lastNodeId, nodeId, requestGraphEdgeTypes.invalidated_by_create_above);
|
|
|
|
}
|
|
|
|
} else if (input.filePath != null) {
|
|
|
|
node = nodeFromFilePath(input.filePath);
|
|
|
|
} else {
|
|
|
|
throw new Error('Invalid invalidation');
|
|
|
|
}
|
|
|
|
|
|
|
|
let nodeId = this.addNode(node);
|
|
|
|
|
|
|
|
if (!this.hasEdge(requestNodeId, nodeId, requestGraphEdgeTypes.invalidated_by_create)) {
|
|
|
|
this.addEdge(requestNodeId, nodeId, requestGraphEdgeTypes.invalidated_by_create);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
invalidateOnStartup(requestNodeId) {
|
|
|
|
this.getRequestNode(requestNodeId);
|
|
|
|
this.unpredicatableNodeIds.add(requestNodeId);
|
|
|
|
}
|
|
|
|
|
|
|
|
invalidateOnEnvChange(requestNodeId, env, value) {
|
|
|
|
let envNode = nodeFromEnv(env, value);
|
|
|
|
let envNodeId = this.addNode(envNode);
|
|
|
|
|
|
|
|
if (!this.hasEdge(requestNodeId, envNodeId, requestGraphEdgeTypes.invalidated_by_update)) {
|
|
|
|
this.addEdge(requestNodeId, envNodeId, requestGraphEdgeTypes.invalidated_by_update);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
invalidateOnOptionChange(requestNodeId, option, value) {
|
|
|
|
let optionNode = nodeFromOption(option, value);
|
|
|
|
let optionNodeId = this.addNode(optionNode);
|
|
|
|
|
|
|
|
if (!this.hasEdge(requestNodeId, optionNodeId, requestGraphEdgeTypes.invalidated_by_update)) {
|
|
|
|
this.addEdge(requestNodeId, optionNodeId, requestGraphEdgeTypes.invalidated_by_update);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
clearInvalidations(nodeId) {
|
|
|
|
this.unpredicatableNodeIds.delete(nodeId);
|
|
|
|
this.replaceNodeIdsConnectedTo(nodeId, [], null, requestGraphEdgeTypes.invalidated_by_update);
|
|
|
|
this.replaceNodeIdsConnectedTo(nodeId, [], null, requestGraphEdgeTypes.invalidated_by_delete);
|
|
|
|
this.replaceNodeIdsConnectedTo(nodeId, [], null, requestGraphEdgeTypes.invalidated_by_create);
|
|
|
|
}
|
|
|
|
|
|
|
|
getInvalidations(requestNodeId) {
|
|
|
|
if (!this.hasNode(requestNodeId)) {
|
|
|
|
return [];
|
|
|
|
} // For now just handling updates. Could add creates/deletes later if needed.
|
|
|
|
|
|
|
|
|
|
|
|
let invalidations = this.getNodeIdsConnectedFrom(requestNodeId, requestGraphEdgeTypes.invalidated_by_update);
|
|
|
|
return invalidations.map(nodeId => {
|
|
|
|
let node = (0, _nullthrows().default)(this.getNode(nodeId));
|
|
|
|
|
|
|
|
switch (node.type) {
|
|
|
|
case 'file':
|
|
|
|
return {
|
|
|
|
type: 'file',
|
|
|
|
filePath: node.value.filePath
|
|
|
|
};
|
|
|
|
|
|
|
|
case 'env':
|
|
|
|
return {
|
|
|
|
type: 'env',
|
|
|
|
key: node.value.key
|
|
|
|
};
|
|
|
|
|
|
|
|
case 'option':
|
|
|
|
return {
|
|
|
|
type: 'option',
|
|
|
|
key: node.value.key
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}).filter(Boolean);
|
|
|
|
}
|
|
|
|
|
|
|
|
getSubRequests(requestNodeId) {
|
|
|
|
if (!this.hasNode(requestNodeId)) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
let subRequests = this.getNodeIdsConnectedFrom(requestNodeId, requestGraphEdgeTypes.subrequest);
|
|
|
|
return subRequests.map(nodeId => {
|
|
|
|
let node = (0, _nullthrows().default)(this.getNode(nodeId));
|
|
|
|
(0, _assert().default)(node.type === 'request');
|
|
|
|
return node.value;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
invalidateFileNameNode(node, filePath, matchNodes) {
|
|
|
|
// If there is an edge between this file_name node and one of the original file nodes pointed to
|
|
|
|
// by the original file_name node, and the matched node is inside the current directory, invalidate
|
|
|
|
// all connected requests pointed to by the file node.
|
|
|
|
let dirname = _path().default.dirname((0, _projectPath.fromProjectPathRelative)(filePath));
|
|
|
|
|
|
|
|
let nodeId = this.getNodeIdByContentKey(node.id);
|
|
|
|
|
|
|
|
for (let matchNode of matchNodes) {
|
|
|
|
let matchNodeId = this.getNodeIdByContentKey(matchNode.id);
|
|
|
|
|
|
|
|
if (this.hasEdge(nodeId, matchNodeId, requestGraphEdgeTypes.invalidated_by_create_above) && (0, _utils().isDirectoryInside)(_path().default.dirname((0, _projectPath.fromProjectPathRelative)(matchNode.value.filePath)), dirname)) {
|
|
|
|
let connectedNodes = this.getNodeIdsConnectedTo(matchNodeId, requestGraphEdgeTypes.invalidated_by_create);
|
|
|
|
|
|
|
|
for (let connectedNode of connectedNodes) {
|
|
|
|
this.invalidateNode(connectedNode, _constants.FILE_CREATE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // Find the `file_name` node for the parent directory and
|
|
|
|
// recursively invalidate connected requests as described above.
|
|
|
|
|
|
|
|
|
|
|
|
let basename = _path().default.basename(dirname);
|
|
|
|
|
|
|
|
let contentKey = 'file_name:' + basename;
|
|
|
|
|
|
|
|
if (this.hasContentKey(contentKey)) {
|
|
|
|
if (this.hasEdge(nodeId, this.getNodeIdByContentKey(contentKey), requestGraphEdgeTypes.dirname)) {
|
|
|
|
let parent = (0, _nullthrows().default)(this.getNodeByContentKey(contentKey));
|
|
|
|
(0, _assert().default)(parent.type === 'file_name');
|
|
|
|
this.invalidateFileNameNode(parent, (0, _projectPath.toProjectPathUnsafe)(dirname), matchNodes);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
respondToFSEvents(events) {
|
|
|
|
let didInvalidate = false;
|
|
|
|
|
|
|
|
for (let {
|
|
|
|
path: _filePath,
|
|
|
|
type
|
|
|
|
} of events) {
|
|
|
|
let filePath = (0, _projectPath.fromProjectPathRelative)(_filePath);
|
|
|
|
let hasFileRequest = this.hasContentKey(filePath); // If we see a 'create' event for the project root itself,
|
|
|
|
// this means the project root was moved and we need to
|
|
|
|
// re-run all requests.
|
|
|
|
|
|
|
|
if (type === 'create' && filePath === '') {
|
|
|
|
for (let [id, node] of this.nodes) {
|
|
|
|
if (node.type === 'request') {
|
|
|
|
this.invalidNodeIds.add(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
} // sometimes mac os reports update events as create events.
|
|
|
|
// if it was a create event, but the file already exists in the graph,
|
|
|
|
// then also invalidate nodes connected by invalidated_by_update edges.
|
|
|
|
|
|
|
|
|
|
|
|
if (hasFileRequest && (type === 'create' || type === 'update')) {
|
|
|
|
let nodeId = this.getNodeIdByContentKey(filePath);
|
|
|
|
let nodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_update);
|
|
|
|
|
|
|
|
for (let connectedNode of nodes) {
|
|
|
|
didInvalidate = true;
|
|
|
|
this.invalidateNode(connectedNode, _constants.FILE_UPDATE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (type === 'create') {
|
|
|
|
let nodes = this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_create);
|
|
|
|
|
|
|
|
for (let connectedNode of nodes) {
|
|
|
|
didInvalidate = true;
|
|
|
|
this.invalidateNode(connectedNode, _constants.FILE_CREATE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (type === 'create') {
|
|
|
|
let basename = _path().default.basename(filePath);
|
|
|
|
|
|
|
|
let fileNameNode = this.getNodeByContentKey('file_name:' + basename);
|
|
|
|
|
|
|
|
if (fileNameNode != null && (fileNameNode === null || fileNameNode === void 0 ? void 0 : fileNameNode.type) === 'file_name') {
|
|
|
|
let fileNameNodeId = this.getNodeIdByContentKey('file_name:' + basename); // Find potential file nodes to be invalidated if this file name pattern matches
|
|
|
|
|
|
|
|
let above = this.getNodeIdsConnectedTo(fileNameNodeId, requestGraphEdgeTypes.invalidated_by_create_above).map(nodeId => {
|
|
|
|
let node = (0, _nullthrows().default)(this.getNode(nodeId));
|
|
|
|
(0, _assert().default)(node.type === 'file');
|
|
|
|
return node;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (above.length > 0) {
|
|
|
|
didInvalidate = true;
|
|
|
|
this.invalidateFileNameNode(fileNameNode, _filePath, above);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let globeNodeId of this.globNodeIds) {
|
|
|
|
let globNode = this.getNode(globeNodeId);
|
|
|
|
(0, _assert().default)(globNode && globNode.type === 'glob');
|
|
|
|
|
|
|
|
if ((0, _utils().isGlobMatch)(filePath, (0, _projectPath.fromProjectPathRelative)(globNode.value))) {
|
|
|
|
let connectedNodes = this.getNodeIdsConnectedTo(globeNodeId, requestGraphEdgeTypes.invalidated_by_create);
|
|
|
|
|
|
|
|
for (let connectedNode of connectedNodes) {
|
|
|
|
didInvalidate = true;
|
|
|
|
this.invalidateNode(connectedNode, _constants.FILE_CREATE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (hasFileRequest && type === 'delete') {
|
|
|
|
let nodeId = this.getNodeIdByContentKey(filePath);
|
|
|
|
|
|
|
|
for (let connectedNode of this.getNodeIdsConnectedTo(nodeId, requestGraphEdgeTypes.invalidated_by_delete)) {
|
|
|
|
didInvalidate = true;
|
|
|
|
this.invalidateNode(connectedNode, _constants.FILE_DELETE);
|
|
|
|
} // Delete the file node since it doesn't exist anymore.
|
|
|
|
// This ensures that files that don't exist aren't sent
|
|
|
|
// to requests as invalidations for future requests.
|
|
|
|
|
|
|
|
|
|
|
|
this.removeNode(nodeId);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return didInvalidate && this.invalidNodeIds.size > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.RequestGraph = RequestGraph;
|
|
|
|
|
|
|
|
class RequestTracker {
|
|
|
|
constructor({
|
|
|
|
graph,
|
|
|
|
farm,
|
|
|
|
options
|
|
|
|
}) {
|
|
|
|
this.graph = graph || new RequestGraph();
|
|
|
|
this.farm = farm;
|
|
|
|
this.options = options;
|
|
|
|
} // TODO: refactor (abortcontroller should be created by RequestTracker)
|
|
|
|
|
|
|
|
|
|
|
|
setSignal(signal) {
|
|
|
|
this.signal = signal;
|
|
|
|
}
|
|
|
|
|
|
|
|
startRequest(request) {
|
|
|
|
let didPreviouslyExist = this.graph.hasContentKey(request.id);
|
|
|
|
let requestNodeId;
|
|
|
|
|
|
|
|
if (didPreviouslyExist) {
|
|
|
|
requestNodeId = this.graph.getNodeIdByContentKey(request.id); // Clear existing invalidations for the request so that the new
|
|
|
|
// invalidations created during the request replace the existing ones.
|
|
|
|
|
|
|
|
this.graph.clearInvalidations(requestNodeId);
|
|
|
|
} else {
|
|
|
|
requestNodeId = this.graph.addNode(nodeFromRequest(request));
|
|
|
|
}
|
|
|
|
|
|
|
|
this.graph.incompleteNodeIds.add(requestNodeId);
|
|
|
|
this.graph.invalidNodeIds.delete(requestNodeId);
|
|
|
|
let {
|
|
|
|
promise,
|
|
|
|
deferred
|
|
|
|
} = (0, _utils().makeDeferredWithPromise)();
|
|
|
|
this.graph.incompleteNodePromises.set(requestNodeId, promise);
|
|
|
|
return {
|
|
|
|
requestNodeId,
|
|
|
|
deferred
|
|
|
|
};
|
|
|
|
} // If a cache key is provided, the result will be removed from the node and stored in a separate cache entry
|
|
|
|
|
|
|
|
|
|
|
|
storeResult(nodeId, result, cacheKey) {
|
|
|
|
let node = this.graph.getNode(nodeId);
|
|
|
|
|
|
|
|
if (node && node.type === 'request') {
|
|
|
|
node.value.result = result;
|
|
|
|
node.value.resultCacheKey = cacheKey;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hasValidResult(nodeId) {
|
|
|
|
return this.graph.hasNode(nodeId) && !this.graph.invalidNodeIds.has(nodeId) && !this.graph.incompleteNodeIds.has(nodeId);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getRequestResult(contentKey, ifMatch) {
|
|
|
|
let node = (0, _nullthrows().default)(this.graph.getNodeByContentKey(contentKey));
|
|
|
|
(0, _assert().default)(node.type === 'request');
|
|
|
|
|
|
|
|
if (ifMatch != null && node.value.resultCacheKey !== ifMatch) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (node.value.result != undefined) {
|
|
|
|
// $FlowFixMe
|
|
|
|
let result = node.value.result;
|
|
|
|
return result;
|
|
|
|
} else if (node.value.resultCacheKey != null && ifMatch == null) {
|
|
|
|
let cachedResult = (0, _nullthrows().default)(await this.options.cache.get(node.value.resultCacheKey) // $FlowFixMe
|
|
|
|
);
|
|
|
|
node.value.result = cachedResult;
|
|
|
|
return cachedResult;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
completeRequest(nodeId) {
|
|
|
|
this.graph.invalidNodeIds.delete(nodeId);
|
|
|
|
this.graph.incompleteNodeIds.delete(nodeId);
|
|
|
|
this.graph.incompleteNodePromises.delete(nodeId);
|
|
|
|
let node = this.graph.getNode(nodeId);
|
|
|
|
|
|
|
|
if ((node === null || node === void 0 ? void 0 : node.type) === 'request') {
|
|
|
|
node.invalidateReason = _constants.VALID;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rejectRequest(nodeId) {
|
|
|
|
this.graph.incompleteNodeIds.delete(nodeId);
|
|
|
|
this.graph.incompleteNodePromises.delete(nodeId);
|
|
|
|
let node = this.graph.getNode(nodeId);
|
|
|
|
|
|
|
|
if ((node === null || node === void 0 ? void 0 : node.type) === 'request') {
|
|
|
|
this.graph.invalidateNode(nodeId, _constants.ERROR);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
respondToFSEvents(events) {
|
|
|
|
return this.graph.respondToFSEvents(events);
|
|
|
|
}
|
|
|
|
|
|
|
|
hasInvalidRequests() {
|
|
|
|
return this.graph.invalidNodeIds.size > 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
getInvalidRequests() {
|
|
|
|
let invalidRequests = [];
|
|
|
|
|
|
|
|
for (let id of this.graph.invalidNodeIds) {
|
|
|
|
let node = (0, _nullthrows().default)(this.graph.getNode(id));
|
|
|
|
(0, _assert().default)(node.type === 'request');
|
|
|
|
invalidRequests.push(node.value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return invalidRequests;
|
|
|
|
}
|
|
|
|
|
|
|
|
replaceSubrequests(requestNodeId, subrequestContextKeys) {
|
|
|
|
this.graph.replaceSubrequests(requestNodeId, subrequestContextKeys);
|
|
|
|
}
|
|
|
|
|
|
|
|
async runRequest(request, opts) {
|
|
|
|
let requestId = this.graph.hasContentKey(request.id) ? this.graph.getNodeIdByContentKey(request.id) : undefined;
|
|
|
|
let hasValidResult = requestId != null && this.hasValidResult(requestId);
|
|
|
|
|
|
|
|
if (!(opts !== null && opts !== void 0 && opts.force) && hasValidResult) {
|
|
|
|
// $FlowFixMe[incompatible-type]
|
|
|
|
return this.getRequestResult(request.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (requestId != null) {
|
|
|
|
let incompletePromise = this.graph.incompleteNodePromises.get(requestId);
|
|
|
|
|
|
|
|
if (incompletePromise != null) {
|
|
|
|
// There is a another instance of this request already running, wait for its completion and reuse its result
|
|
|
|
try {
|
|
|
|
if (await incompletePromise) {
|
|
|
|
// $FlowFixMe[incompatible-type]
|
|
|
|
return this.getRequestResult(request.id);
|
|
|
|
}
|
|
|
|
} catch (e) {// Rerun this request
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let previousInvalidations = requestId != null ? this.graph.getInvalidations(requestId) : [];
|
|
|
|
let {
|
|
|
|
requestNodeId,
|
|
|
|
deferred
|
|
|
|
} = this.startRequest({
|
|
|
|
id: request.id,
|
|
|
|
type: request.type
|
|
|
|
});
|
|
|
|
let {
|
|
|
|
api,
|
|
|
|
subRequestContentKeys
|
|
|
|
} = this.createAPI(requestNodeId, previousInvalidations);
|
|
|
|
|
|
|
|
try {
|
|
|
|
let node = this.graph.getRequestNode(requestNodeId);
|
|
|
|
let result = await request.run({
|
|
|
|
input: request.input,
|
|
|
|
api,
|
|
|
|
farm: this.farm,
|
|
|
|
options: this.options,
|
|
|
|
invalidateReason: node.invalidateReason
|
|
|
|
});
|
|
|
|
(0, _utils2.assertSignalNotAborted)(this.signal);
|
|
|
|
this.completeRequest(requestNodeId);
|
|
|
|
deferred.resolve(true);
|
|
|
|
return result;
|
|
|
|
} catch (err) {
|
|
|
|
this.rejectRequest(requestNodeId);
|
|
|
|
deferred.resolve(false);
|
|
|
|
throw err;
|
|
|
|
} finally {
|
|
|
|
this.graph.replaceSubrequests(requestNodeId, [...subRequestContentKeys]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
createAPI(requestId, previousInvalidations) {
|
|
|
|
let subRequestContentKeys = new Set();
|
|
|
|
return {
|
|
|
|
api: {
|
|
|
|
invalidateOnFileCreate: input => this.graph.invalidateOnFileCreate(requestId, input),
|
|
|
|
invalidateOnFileDelete: filePath => this.graph.invalidateOnFileDelete(requestId, filePath),
|
|
|
|
invalidateOnFileUpdate: filePath => this.graph.invalidateOnFileUpdate(requestId, filePath),
|
|
|
|
invalidateOnStartup: () => this.graph.invalidateOnStartup(requestId),
|
|
|
|
invalidateOnEnvChange: env => this.graph.invalidateOnEnvChange(requestId, env, this.options.env[env]),
|
|
|
|
invalidateOnOptionChange: option => this.graph.invalidateOnOptionChange(requestId, option, this.options[option]),
|
|
|
|
getInvalidations: () => previousInvalidations,
|
|
|
|
storeResult: (result, cacheKey) => {
|
|
|
|
this.storeResult(requestId, result, cacheKey);
|
|
|
|
},
|
|
|
|
getSubRequests: () => this.graph.getSubRequests(requestId),
|
|
|
|
getPreviousResult: ifMatch => {
|
|
|
|
var _this$graph$getNode;
|
|
|
|
|
|
|
|
let contentKey = (0, _nullthrows().default)((_this$graph$getNode = this.graph.getNode(requestId)) === null || _this$graph$getNode === void 0 ? void 0 : _this$graph$getNode.id);
|
|
|
|
return this.getRequestResult(contentKey, ifMatch);
|
|
|
|
},
|
|
|
|
getRequestResult: id => this.getRequestResult(id),
|
|
|
|
canSkipSubrequest: contentKey => {
|
|
|
|
if (this.graph.hasContentKey(contentKey) && this.hasValidResult(this.graph.getNodeIdByContentKey(contentKey))) {
|
|
|
|
subRequestContentKeys.add(contentKey);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
runRequest: (subRequest, opts) => {
|
|
|
|
subRequestContentKeys.add(subRequest.id);
|
|
|
|
return this.runRequest(subRequest, opts);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
subRequestContentKeys
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
async writeToCache() {
|
|
|
|
let cacheKey = getCacheKey(this.options);
|
|
|
|
let requestGraphKey = (0, _hash().hashString)(`${cacheKey}:requestGraph`);
|
|
|
|
let snapshotKey = (0, _hash().hashString)(`${cacheKey}:snapshot`);
|
|
|
|
|
|
|
|
if (this.options.shouldDisableCache) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let promises = [];
|
|
|
|
|
|
|
|
for (let [, node] of this.graph.nodes) {
|
|
|
|
if (node.type !== 'request') {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let resultCacheKey = node.value.resultCacheKey;
|
|
|
|
|
|
|
|
if (resultCacheKey != null && node.value.result != null) {
|
|
|
|
promises.push(this.options.cache.set(resultCacheKey, node.value.result));
|
|
|
|
delete node.value.result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
promises.push(this.options.cache.set(requestGraphKey, this.graph));
|
|
|
|
let opts = getWatcherOptions(this.options);
|
|
|
|
|
|
|
|
let snapshotPath = _path().default.join(this.options.cacheDir, snapshotKey + '.txt');
|
|
|
|
|
|
|
|
promises.push(this.options.inputFS.writeSnapshot(this.options.projectRoot, snapshotPath, opts));
|
|
|
|
await Promise.all(promises);
|
|
|
|
}
|
|
|
|
|
|
|
|
static async init({
|
|
|
|
farm,
|
|
|
|
options
|
|
|
|
}) {
|
|
|
|
let graph = await loadRequestGraph(options);
|
|
|
|
return new RequestTracker({
|
|
|
|
farm,
|
|
|
|
options,
|
|
|
|
graph
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.default = RequestTracker;
|
|
|
|
|
|
|
|
function getWatcherOptions(options) {
|
|
|
|
let vcsDirs = ['.git', '.hg'].map(dir => _path().default.join(options.projectRoot, dir));
|
|
|
|
let ignore = [options.cacheDir, ...vcsDirs];
|
|
|
|
return {
|
|
|
|
ignore
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function getCacheKey(options) {
|
|
|
|
return `${_constants.PARCEL_VERSION}:${JSON.stringify(options.entries)}:${options.mode}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function loadRequestGraph(options) {
|
|
|
|
if (options.shouldDisableCache) {
|
|
|
|
return new RequestGraph();
|
|
|
|
}
|
|
|
|
|
|
|
|
let cacheKey = getCacheKey(options);
|
|
|
|
let requestGraphKey = (0, _hash().hashString)(`${cacheKey}:requestGraph`);
|
|
|
|
let requestGraph = await options.cache.get(requestGraphKey);
|
|
|
|
|
|
|
|
if (requestGraph) {
|
|
|
|
let opts = getWatcherOptions(options);
|
|
|
|
let snapshotKey = (0, _hash().hashString)(`${cacheKey}:snapshot`);
|
|
|
|
|
|
|
|
let snapshotPath = _path().default.join(options.cacheDir, snapshotKey + '.txt');
|
|
|
|
|
|
|
|
let events = await options.inputFS.getEventsSince(options.projectRoot, snapshotPath, opts);
|
|
|
|
requestGraph.invalidateUnpredictableNodes();
|
|
|
|
requestGraph.invalidateEnvNodes(options.env);
|
|
|
|
requestGraph.invalidateOptionNodes(options);
|
|
|
|
requestGraph.respondToFSEvents(events.map(e => ({
|
|
|
|
type: e.type,
|
|
|
|
path: (0, _projectPath.toProjectPath)(options.projectRoot, e.path)
|
|
|
|
})));
|
|
|
|
return requestGraph;
|
|
|
|
}
|
|
|
|
|
|
|
|
return new RequestGraph();
|
|
|
|
}
|