import { DynamicModule, Inject, Module, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; import { ModuleRef } from '@nestjs/core'; import { getConfig } from '../config/config-helpers'; import { ConfigModule } from '../config/config.module'; import { ConfigService } from '../config/config.service'; import { Logger } from '../config/logger/vendure-logger'; import { getPluginModules, getWorkerControllers, hasLifecycleMethod, isDynamicModule, } from './plugin-metadata'; import { PluginLifecycleMethods } from './vendure-plugin'; export enum PluginProcessContext { Main, Worker, } const PLUGIN_PROCESS_CONTEXT = 'PLUGIN_PROCESS_CONTEXT'; /** * This module collects and re-exports all providers defined in plugins so that they can be used in other * modules and in responsible for executing any lifecycle methods defined by the plugins. */ @Module({ imports: [ConfigModule], }) export class PluginModule implements OnModuleInit, OnModuleDestroy { static forRoot(): DynamicModule { return { module: PluginModule, providers: [{ provide: PLUGIN_PROCESS_CONTEXT, useValue: PluginProcessContext.Main }], imports: [...getConfig().plugins], }; } static forWorker(): DynamicModule { return { module: PluginModule, providers: [{ provide: PLUGIN_PROCESS_CONTEXT, useValue: PluginProcessContext.Worker }], imports: [...pluginsWithWorkerControllers()], }; } constructor( @Inject(PLUGIN_PROCESS_CONTEXT) private processContext: PluginProcessContext, private moduleRef: ModuleRef, private configService: ConfigService, ) {} async onModuleInit() { if (this.processContext === PluginProcessContext.Main) { await this.runPluginLifecycleMethods('onVendureBootstrap', instance => { const pluginName = instance.constructor.name || '(anonymous plugin)'; Logger.verbose(`Bootstrapped plugin ${pluginName}`); }); } if (this.processContext === PluginProcessContext.Worker) { await this.runPluginLifecycleMethods('onVendureWorkerBootstrap'); } } async onModuleDestroy() { if (this.processContext === PluginProcessContext.Main) { await this.runPluginLifecycleMethods('onVendureClose'); } if (this.processContext === PluginProcessContext.Worker) { await this.runPluginLifecycleMethods('onVendureWorkerClose'); } } private async runPluginLifecycleMethods( lifecycleMethod: keyof PluginLifecycleMethods, afterRun?: (instance: any) => void, ) { for (const plugin of getPluginModules(this.configService.plugins)) { let instance: any; try { instance = this.moduleRef.get(plugin, { strict: false }); } catch (e) { Logger.error(`Could not find ${plugin.name}`, undefined, e.stack); } if (instance) { if (hasLifecycleMethod(instance, lifecycleMethod)) { await instance[lifecycleMethod](); } if (typeof afterRun === 'function') { afterRun(instance); } } } } } function pluginsWithWorkerControllers(): DynamicModule[] { return getConfig().plugins.map(plugin => { const controllers = getWorkerControllers(plugin); if (isDynamicModule(plugin)) { return { ...plugin, controllers, }; } else { return { module: plugin, controllers, }; } }); }