UNPKG

@teambit/harmony

Version:
166 lines (133 loc) 5.17 kB
import { Graph } from 'cleargraph'; import { fromExtension, fromExtensions } from './from-extension'; import { ExtensionManifest } from '../extension'; import { Extension } from '../extension'; import { RuntimeDefinition, Runtimes } from '../runtimes'; import { RequireFn } from '../harmony'; export type DependencyGraphOptions = { getName?: (manifest: any) => string; }; function getName(manifest: any) { return Reflect.getMetadata('harmony:name', manifest) || manifest.id || manifest.name; } export type Edge = { type: string, runtime?: string }; export default class DependencyGraph extends Graph<Extension, Edge> { private cache = new Map<string, Extension>(); getRuntimeDependencies(aspect: Extension, runtime: RuntimeDefinition, options: DependencyGraphOptions = {}): Extension[] { const dependencies = this.successors(aspect.name); const runtimeDeps = this.successors(aspect.name, (edge) => { if (!edge.runtime) return false; return edge.runtime === runtime.name; }); const runtimeManifest = aspect.getRuntime(runtime); if (!runtimeManifest) return Array.from(dependencies.values()); if (runtimeDeps && runtimeDeps.size) return this.sortDeps(runtimeManifest.dependencies, Array.from(runtimeDeps.values()), options); return this.sortDeps(runtimeManifest.dependencies, Array.from(dependencies.values()), options); } private sortDeps(originalDependencies: any[], targetDependencies: any[], options: DependencyGraphOptions = {}) { const _originalDependencies = options.getName ? originalDependencies?.map((aspect) => { if (!options.getName) return aspect; aspect.id = options.getName(aspect); return aspect; }) || [] : originalDependencies; return targetDependencies.sort((a, b) => { return _originalDependencies.findIndex(item => item.id === a.id) - _originalDependencies.findIndex(item => item.id === b.id); }); } byExecutionOrder() { return this.toposort(true); } private async enrichRuntimeExtension(id: string, aspect: Extension, runtime: RuntimeDefinition, runtimes: Runtimes, requireFn: RequireFn, options: DependencyGraphOptions = {}) { await requireFn(aspect, runtime); const runtimeManifest = aspect.getRuntime(runtime) if (!runtimeManifest) return; const deps = runtimeManifest.dependencies; if (!deps) return; const promises = deps.map(async (dep: any) => { const depId = options.getName ? options.getName(dep): dep.id; if (!this.hasNode(depId)) { this.add(dep); if (dep.declareRuntime) { runtimes.add(dep.declareRuntime); } const node = this.get(depId); if (!node) return; await requireFn(node, runtime); await this.enrichRuntimeExtension(depId, this.get(depId), runtime, runtimes, requireFn); } this.setEdge(id, depId, { runtime: runtime.name, type: 'runtime-dependency' }); }); return Promise.all(promises); } async enrichRuntime(runtime: RuntimeDefinition, runtimes: Runtimes, requireFn: RequireFn, options: DependencyGraphOptions = {}) { const promises = Array.from(this.nodes.entries()).map(async ([id, aspect]) => { return this.enrichRuntimeExtension(id, aspect, runtime, runtimes, requireFn, options); }); return Promise.all(promises); } add(manifest: ExtensionManifest) { const { vertices, edges } = fromExtension(manifest); this.setNodes(vertices); this.setEdges(edges); return this; } load(extensions: ExtensionManifest[]) { const newExtensions = extensions.filter((extension) => { if (!extension.id) return false; return !this.get(extension.id); }); const { vertices, edges } = fromExtensions(newExtensions); // Only set new vertices this.setNodes(vertices, false); // false because we don't want to override already-loaded extensions this.setEdges(edges); return this; } // :TODO refactor this asap getExtension(manifest: ExtensionManifest) { const id = getName(manifest); const cachedVertex = this.cache.get(id); if (cachedVertex) return cachedVertex; const res = this.node(id); if (res) { this.cache.set(res.name, res); return res; } return null; } get extensions(): ExtensionManifest[] { return Array.from(this.nodes.values()); } get aspects() { return this.extensions; } get(id: string): any { const cachedVertex = this.cache.get(id); if (cachedVertex) return cachedVertex; const res = this.node(id); if (res) { this.cache.set(res.name, res); return res; } return null; } /** * build Harmony from a single extension. */ static fromRoot(extension: ExtensionManifest) { const { vertices, edges } = fromExtension(extension); return new DependencyGraph(vertices, edges); } /** * build Harmony from set of extensions */ static from(extensions: ExtensionManifest[], options: DependencyGraphOptions = {}) { const { vertices, edges } = fromExtensions(extensions, options); return new DependencyGraph(vertices, edges); } }