UNPKG

@teambit/workspace

Version:
219 lines (215 loc) • 9.85 kB
"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