@teambit/workspace
Version:
219 lines (215 loc) • 9.85 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.GraphFromFsBuilder = void 0;
function _pMapSeries() {
const data = _interopRequireDefault(require("p-map-series"));
_pMapSeries = function () {
return data;
};
return data;
}
function _graph() {
const data = require("@teambit/graph.cleargraph");
_graph = function () {
return data;
};
return data;
}
function _lodash() {
const data = require("lodash");
_lodash = function () {
return data;
};
return data;
}
function _componentId() {
const data = require("@teambit/component-id");
_componentId = function () {
return data;
};
return data;
}
function _legacy() {
const data = require("@teambit/legacy.scope");
_legacy = function () {
return data;
};
return data;
}
function _scope() {
const data = require("@teambit/scope");
_scope = function () {
return data;
};
return data;
}
function _lodash2() {
const data = _interopRequireDefault(require("lodash.compact"));
_lodash2 = function () {
return data;
};
return data;
}
function _bitError() {
const data = require("@teambit/bit-error");
_bitError = function () {
return data;
};
return data;
}
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
class GraphFromFsBuilder {
constructor(workspace, logger, dependencyResolver, ignoreIds = [], shouldLoadItsDeps, shouldThrowOnMissingDep = true) {
this.workspace = workspace;
this.logger = logger;
this.dependencyResolver = dependencyResolver;
this.ignoreIds = ignoreIds;
this.shouldLoadItsDeps = shouldLoadItsDeps;
this.shouldThrowOnMissingDep = shouldThrowOnMissingDep;
_defineProperty(this, "graph", new (_graph().Graph)());
_defineProperty(this, "completed", []);
_defineProperty(this, "depth", 1);
_defineProperty(this, "consumer", void 0);
_defineProperty(this, "importedIds", []);
_defineProperty(this, "currentLane", void 0);
this.consumer = this.workspace.consumer;
}
/**
* create a graph with all dependencies and flattened dependencies of the given components.
* the nodes are components and the edges has a label of the dependency type.
*
* the way how it is done is iterations by depths. each depth we gather all the dependencies of
* that depths, make sure all objects exist and then check their dependencies for the next depth.
* once there is no dependency left, we're on the last depth level and the graph is ready.
*
* for example, imagine the following graph:
* A1 -> A2 -> A3
* B1 -> B2 -> B3
* C1 -> C2 -> C3
*
* where the buildGraph is given [A1, B1, C1].
* first, it saves all these components as nodes in the graph. then, it finds the dependencies of
* the next level, in this case they're [A2, B2, C2]. it runs `importMany` in case some objects
* are missing. then, it loads them all (some from FS, some from the model) and sets the edges
* between the component and the dependencies.
* once done, it finds all their dependencies, which are [A3, B3, C3] and repeat the process
* above. since there are no more dependencies, the graph is completed.
* in this case, the total depth levels are 3.
*
* even with a huge project, there are not many depth levels. by iterating through depth levels
* we keep performance sane as the importMany doesn't run multiple time and therefore the round
* trips to the remotes are minimal.
*
* normally, one importMany of the seeders is enough as importMany knows to fetch all flattened.
* however, since this buildGraph is performed on the workspace, a dependency may be new or
* modified and as such, we don't know its flattened yet.
*/
async buildGraph(ids) {
this.logger.debug(`GraphFromFsBuilder, buildGraph with ${ids.length} seeders`);
const start = Date.now();
const components = await this.loadManyComponents(ids);
this.currentLane = await this.workspace.consumer.getCurrentLaneObject();
await this.processManyComponents(components);
this.logger.debug(`GraphFromFsBuilder, buildGraph with ${ids.length} seeders completed (${(Date.now() - start) / 1000} sec)`);
return this.graph;
}
async getAllDepsUnfiltered(component) {
const deps = await this.dependencyResolver.getComponentDependencies(component);
const depsIds = deps.map(dep => dep.componentId);
return depsIds.filter(depId => !this.ignoreIds.includes(depId.toString()));
}
async getAllDepsFiltered(component) {
const depsWithoutIgnore = await this.getAllDepsUnfiltered(component);
const shouldLoadFunc = this.shouldLoadItsDeps;
if (!shouldLoadFunc) return depsWithoutIgnore;
const deps = await (0, _pMapSeries().default)(depsWithoutIgnore, async depId => {
const shouldLoad = await shouldLoadFunc(depId);
if (!shouldLoad) this.ignoreIds.push(depId.toString());
return shouldLoad ? depId : null;
});
return (0, _lodash2().default)(deps);
}
async processManyComponents(components) {
this.logger.debug(`GraphFromFsBuilder.processManyComponents depth ${this.depth}, ${components.length} components`);
this.depth += 1;
await this.importObjects(components);
const allDependencies = await (0, _pMapSeries().default)(components, component => this.processOneComponent(component));
const allDependenciesFlattened = (0, _lodash().flatten)(allDependencies);
if (allDependenciesFlattened.length) await this.processManyComponents(allDependenciesFlattened);
}
/**
* only for components from the workspace that can be modified to add/remove dependencies, we need to make sure that
* all their dependencies are imported.
* remember that `importMany` fetches all flattened dependencies. once a component from scope is imported, we know
* that all its flattened dependencies are there. no need to call importMany again for them.
*/
async importObjects(components) {
const workspaceIds = this.workspace.listIds();
const compOnWorkspaceOnly = components.filter(comp => workspaceIds.find(id => id.isEqual(comp.id)));
const allDeps = (await Promise.all(compOnWorkspaceOnly.map(c => this.getAllDepsUnfiltered(c)))).flat();
const allDepsNotImported = allDeps.filter(d => !this.importedIds.includes(d.toString()));
const exportedDeps = allDepsNotImported.map(id => id).filter(dep => this.workspace.isExported(dep));
const scopeComponentsImporter = this.consumer.scope.scopeImporter;
await scopeComponentsImporter.importMany({
ids: _componentId().ComponentIdList.uniqFromArray(exportedDeps),
preferDependencyGraph: false,
throwForDependencyNotFound: this.shouldThrowOnMissingDep,
throwForSeederNotFound: this.shouldThrowOnMissingDep,
reFetchUnBuiltVersion: false,
lane: this.currentLane,
reason: 'for building a graph from the workspace'
});
allDepsNotImported.map(id => this.importedIds.push(id.toString()));
}
async processOneComponent(component) {
const idStr = component.id.toString();
if (this.completed.includes(idStr)) return [];
const allIds = await this.getAllDepsFiltered(component);
const allDependenciesComps = await this.loadManyComponents(allIds, idStr);
const deps = await this.dependencyResolver.getComponentDependencies(component);
deps.forEach(dep => {
const depId = dep.componentId;
if (this.ignoreIds.includes(depId.toString())) return;
if (!this.graph.hasNode(depId.toString())) {
if (this.shouldThrowOnMissingDep) {
throw new Error(`buildOneComponent: missing node of ${depId.toString()}`);
}
this.logger.warn(`ignoring missing ${depId.toString()}`);
return;
}
this.graph.setEdge(new (_graph().Edge)(idStr, depId.toString(), dep.lifecycle));
});
this.completed.push(idStr);
return allDependenciesComps;
}
async loadManyComponents(componentsIds, dependenciesOf) {
const components = await (0, _pMapSeries().default)(componentsIds, async comp => {
const idStr = comp.toString();
const fromGraph = this.graph.node(idStr)?.attr;
if (fromGraph) return fromGraph;
try {
const component = await this.workspace.get(comp);
this.graph.setNode(new (_graph().Node)(idStr, component));
return component;
} catch (err) {
if (err instanceof _legacy().ComponentNotFound || err instanceof _scope().ComponentNotFound || err instanceof _legacy().ScopeNotFound) {
if (dependenciesOf && !this.shouldThrowOnMissingDep) {
this.logger.warn(`component ${idStr}, dependency of ${dependenciesOf} was not found. continuing without it`);
return null;
}
throw new (_bitError().BitError)(`error: component "${idStr}" was not found.\nthis component is a dependency of "${dependenciesOf || '<none>'}" and is needed as part of the graph generation`);
}
if (dependenciesOf) this.logger.error(`failed loading dependencies of ${dependenciesOf}`);
throw err;
}
});
return (0, _lodash2().default)(components);
}
}
exports.GraphFromFsBuilder = GraphFromFsBuilder;
//# sourceMappingURL=build-graph-from-fs.js.map