import { createDecorator, CyclicDependencyError, Disposable, Graph } from '@alilc/lowcode-shared'; import { type Plugin, type PluginContext } from './plugin'; import { BoostsManager } from './boosts'; import { IPackageManagementService } from '../package'; import { ISchemaService } from '../schema'; import { ICodeRuntimeService } from '../code-runtime'; import { IRuntimeIntlService } from '../intl'; import { IRuntimeUtilService } from '../util'; export interface IExtensionHostService { readonly boostsManager: BoostsManager; registerPlugin(plugin: Plugin | Plugin[]): Promise; getPlugin(name: string): Plugin | undefined; } export const IExtensionHostService = createDecorator('pluginManagementService'); export class ExtensionHostService extends Disposable implements IExtensionHostService { boostsManager: BoostsManager; private _activePlugins = new Set(); private _pluginStore = new Map(); private _pluginDependencyGraph = new Graph((name) => name); private _pluginSetupContext: PluginContext; constructor( @IPackageManagementService packageManagementService: IPackageManagementService, @ISchemaService schemaService: ISchemaService, @ICodeRuntimeService codeRuntimeService: ICodeRuntimeService, @IRuntimeIntlService runtimeIntlService: IRuntimeIntlService, @IRuntimeUtilService runtimeUtilService: IRuntimeUtilService, ) { super(); this.boostsManager = new BoostsManager(codeRuntimeService, runtimeIntlService, runtimeUtilService); this._pluginSetupContext = { globalState: new Map(), boosts: this.boostsManager.toExpose(), schema: schemaService, packageManager: packageManagementService, }; } async registerPlugin(plugins: Plugin | Plugin[]) { const items = (Array.isArray(plugins) ? plugins : [plugins]).filter( (plugin) => !this._pluginStore.has(plugin.name), ); for (const item of items) { this._pluginStore.set(item.name, item); } await this._doRegisterPlugins(items); } private async _doRegisterPlugins(plugins: Plugin[]) { for (const plugin of plugins) { this._pluginDependencyGraph.lookupOrInsertNode(plugin.name); if (plugin.dependsOn) { for (const dependency of plugin.dependsOn) { this._pluginDependencyGraph.insertEdge(plugin.name, dependency); } } } while (true) { const roots = this._pluginDependencyGraph.roots(); if (roots.length === 0 || roots.every((node) => !this._pluginStore.has(node.data))) { if (this._pluginDependencyGraph.isEmpty()) { throw new CyclicDependencyError(this._pluginDependencyGraph); } break; } for (const { data } of roots) { const plugin = this._pluginStore.get(data); if (plugin) { await this._doSetupPlugin(plugin); this._pluginDependencyGraph.removeNode(plugin.name); } } } } private async _doSetupPlugin(plugin: Plugin) { if (this._activePlugins.has(plugin.name)) return; this._addDispose(await plugin.setup(this._pluginSetupContext)); this._activePlugins.add(plugin.name); } getPlugin(name: string): Plugin | undefined { return this._pluginStore.get(name); } }