mirror of
https://github.com/alibaba/lowcode-engine.git
synced 2025-12-12 03:01:16 +00:00
207 lines
6.8 KiB
TypeScript
207 lines
6.8 KiB
TypeScript
import { engineConfig } from '@alilc/lowcode-editor-core';
|
|
import { getLogger } from '@alilc/lowcode-utils';
|
|
import {
|
|
ILowCodePlugin,
|
|
ILowCodePluginConfig,
|
|
ILowCodePluginManager,
|
|
ILowCodePluginContext,
|
|
ILowCodeRegisterOptions,
|
|
IPluginContextOptions,
|
|
PreferenceValueType,
|
|
ILowCodePluginConfigMeta,
|
|
PluginPreference,
|
|
ILowCodePluginPreferenceDeclaration,
|
|
isLowCodeRegisterOptions,
|
|
ILowCodePluginContextApiAssembler,
|
|
} from './plugin-types';
|
|
import { filterValidOptions } from './plugin-utils';
|
|
import { LowCodePlugin } from './plugin';
|
|
// eslint-disable-next-line import/no-named-as-default
|
|
import LowCodePluginContext from './plugin-context';
|
|
import { invariant } from '../utils';
|
|
import sequencify from './sequencify';
|
|
import semverSatisfies from 'semver/functions/satisfies';
|
|
|
|
const logger = getLogger({ level: 'warn', bizName: 'designer:pluginManager' });
|
|
|
|
export class LowCodePluginManager implements ILowCodePluginManager {
|
|
private plugins: ILowCodePlugin[] = [];
|
|
|
|
private pluginsMap: Map<string, ILowCodePlugin> = new Map();
|
|
|
|
private pluginPreference?: PluginPreference = new Map();
|
|
|
|
contextApiAssembler: ILowCodePluginContextApiAssembler;
|
|
|
|
constructor(contextApiAssembler: ILowCodePluginContextApiAssembler) {
|
|
this.contextApiAssembler = contextApiAssembler;
|
|
}
|
|
|
|
private _getLowCodePluginContext(options: IPluginContextOptions) {
|
|
return new LowCodePluginContext(this, options, this.contextApiAssembler);
|
|
}
|
|
|
|
isEngineVersionMatched(versionExp: string): boolean {
|
|
const engineVersion = engineConfig.get('ENGINE_VERSION');
|
|
// ref: https://github.com/npm/node-semver#functions
|
|
// 1.0.1-beta should match '^1.0.0'
|
|
return semverSatisfies(engineVersion, versionExp, { includePrerelease: true });
|
|
}
|
|
|
|
/**
|
|
* register a plugin
|
|
* @param pluginConfigCreator - a creator function which returns the plugin config
|
|
* @param options - the plugin options
|
|
* @param registerOptions - the plugin register options
|
|
*/
|
|
async register(
|
|
pluginConfigCreator: (ctx: ILowCodePluginContext, options: any) => ILowCodePluginConfig,
|
|
options?: any,
|
|
registerOptions?: ILowCodeRegisterOptions,
|
|
): Promise<void> {
|
|
// registerOptions maybe in the second place
|
|
if (isLowCodeRegisterOptions(options)) {
|
|
registerOptions = options;
|
|
options = {};
|
|
}
|
|
let { pluginName, meta = {} } = pluginConfigCreator as any;
|
|
const { preferenceDeclaration, engines } = meta as ILowCodePluginConfigMeta;
|
|
const ctx = this._getLowCodePluginContext({ pluginName });
|
|
const customFilterValidOptions = engineConfig.get('customPluginFilterOptions', filterValidOptions);
|
|
const config = pluginConfigCreator(ctx, customFilterValidOptions(options, preferenceDeclaration!));
|
|
// compat the legacy way to declare pluginName
|
|
// @ts-ignore
|
|
pluginName = pluginName || config.name;
|
|
invariant(
|
|
pluginName,
|
|
'pluginConfigCreator.pluginName required',
|
|
config,
|
|
);
|
|
|
|
ctx.setPreference(pluginName, (preferenceDeclaration as ILowCodePluginPreferenceDeclaration));
|
|
|
|
const allowOverride = registerOptions?.override === true;
|
|
|
|
if (this.pluginsMap.has(pluginName)) {
|
|
if (!allowOverride) {
|
|
throw new Error(`Plugin with name ${pluginName} exists`);
|
|
} else {
|
|
// clear existing plugin
|
|
const originalPlugin = this.pluginsMap.get(pluginName);
|
|
logger.log(
|
|
'plugin override, originalPlugin with name ',
|
|
pluginName,
|
|
' will be destroyed, config:',
|
|
originalPlugin?.config,
|
|
);
|
|
originalPlugin?.destroy();
|
|
this.pluginsMap.delete(pluginName);
|
|
}
|
|
}
|
|
|
|
const engineVersionExp = engines && engines.lowcodeEngine;
|
|
if (engineVersionExp && !this.isEngineVersionMatched(engineVersionExp)) {
|
|
throw new Error(`plugin ${pluginName} skipped, engine check failed, current engine version is ${engineConfig.get('ENGINE_VERSION')}, meta.engines.lowcodeEngine is ${engineVersionExp}`);
|
|
}
|
|
|
|
const plugin = new LowCodePlugin(pluginName, this, config, meta);
|
|
// support initialization of those plugins which registered after normal initialization by plugin-manager
|
|
if (registerOptions?.autoInit) {
|
|
await plugin.init();
|
|
}
|
|
this.plugins.push(plugin);
|
|
this.pluginsMap.set(pluginName, plugin);
|
|
logger.log(`plugin registered with pluginName: ${pluginName}, config: ${config}, meta: ${meta}`);
|
|
}
|
|
|
|
get(pluginName: string): ILowCodePlugin | undefined {
|
|
return this.pluginsMap.get(pluginName);
|
|
}
|
|
|
|
getAll(): ILowCodePlugin[] {
|
|
return this.plugins;
|
|
}
|
|
|
|
has(pluginName: string): boolean {
|
|
return this.pluginsMap.has(pluginName);
|
|
}
|
|
|
|
async delete(pluginName: string): Promise<boolean> {
|
|
const idx = this.plugins.findIndex((plugin) => plugin.name === pluginName);
|
|
if (idx === -1) return false;
|
|
const plugin = this.plugins[idx];
|
|
await plugin.destroy();
|
|
|
|
this.plugins.splice(idx, 1);
|
|
return this.pluginsMap.delete(pluginName);
|
|
}
|
|
|
|
async init(pluginPreference?: PluginPreference) {
|
|
const pluginNames: string[] = [];
|
|
const pluginObj: { [name: string]: ILowCodePlugin } = {};
|
|
this.pluginPreference = pluginPreference;
|
|
this.plugins.forEach((plugin) => {
|
|
pluginNames.push(plugin.name);
|
|
pluginObj[plugin.name] = plugin;
|
|
});
|
|
const { missingTasks, sequence } = sequencify(pluginObj, pluginNames);
|
|
invariant(!missingTasks.length, 'plugin dependency missing', missingTasks);
|
|
logger.log('load plugin sequence:', sequence);
|
|
|
|
for (const pluginName of sequence) {
|
|
try {
|
|
await this.pluginsMap.get(pluginName)!.init();
|
|
} catch (e) /* istanbul ignore next */ {
|
|
logger.error(
|
|
`Failed to init plugin:${pluginName}, it maybe affect those plugins which depend on this.`,
|
|
);
|
|
logger.error(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
async destroy() {
|
|
for (const plugin of this.plugins) {
|
|
await plugin.destroy();
|
|
}
|
|
}
|
|
|
|
get size() {
|
|
return this.pluginsMap.size;
|
|
}
|
|
|
|
getPluginPreference(pluginName: string): Record<string, PreferenceValueType> | null | undefined {
|
|
if (!this.pluginPreference) {
|
|
return null;
|
|
}
|
|
return this.pluginPreference.get(pluginName);
|
|
}
|
|
|
|
toProxy() {
|
|
return new Proxy(this, {
|
|
get(target, prop, receiver) {
|
|
if (target.pluginsMap.has(prop as string)) {
|
|
// 禁用态的插件,直接返回 undefined
|
|
if (target.pluginsMap.get(prop as string)!.disabled) {
|
|
return undefined;
|
|
}
|
|
return target.pluginsMap.get(prop as string)?.toProxy();
|
|
}
|
|
return Reflect.get(target, prop, receiver);
|
|
},
|
|
});
|
|
}
|
|
|
|
/* istanbul ignore next */
|
|
setDisabled(pluginName: string, flag = true) {
|
|
logger.warn(`plugin:${pluginName} has been set disable:${flag}`);
|
|
this.pluginsMap.get(pluginName)?.setDisabled(flag);
|
|
}
|
|
|
|
async dispose() {
|
|
await this.destroy();
|
|
this.plugins = [];
|
|
this.pluginsMap.clear();
|
|
}
|
|
}
|