diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.ts index 86937ca4028ea34..43773fbdbbb6997 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.ts @@ -12,7 +12,7 @@ import { coerce } from 'semver'; import { promisify } from 'util'; import { snakeCase } from 'lodash'; import { isConfigPath, PackageInfo } from '../../config'; -import { PluginManifest } from '../types'; +import { PluginManifest, PluginType } from '../types'; import { PluginDiscoveryError } from './plugin_discovery_error'; import { isCamelCase } from './is_camel_case'; @@ -179,8 +179,8 @@ export async function parseManifest( ); } - const type = manifest.type ?? 'standard'; - if (type !== 'preboot' && type !== 'standard') { + const type = manifest.type ?? PluginType.standard; + if (type !== PluginType.preboot && type !== PluginType.standard) { throw PluginDiscoveryError.invalidManifest( manifestPath, new Error( diff --git a/src/core/server/plugins/plugin.ts b/src/core/server/plugins/plugin.ts index d3a711bf96471c6..df3fa3c6215ec4d 100644 --- a/src/core/server/plugins/plugin.ts +++ b/src/core/server/plugins/plugin.ts @@ -15,13 +15,14 @@ import { isConfigSchema } from '@kbn/config-schema'; import { Logger } from '../logging'; import { - Plugin, AsyncPlugin, + Plugin, + PluginConfigDescriptor, + PluginInitializer, PluginInitializerContext, PluginManifest, - PluginInitializer, PluginOpaqueId, - PluginConfigDescriptor, + PluginType, PrebootPlugin, } from './types'; import { CorePreboot, CoreSetup, CoreStart } from '..'; @@ -39,25 +40,6 @@ export interface CommonPluginWrapper { readonly includesUiPlugin: PluginManifest['ui']; } -export interface PrebootPluginWrapper - extends CommonPluginWrapper { - setup(setupContext: CorePreboot, plugins: TPluginsSetup): TSetup; - stop(): Promise; -} - -export interface StandardPluginWrapper< - TSetup = unknown, - TStart = unknown, - TPluginsSetup extends object = object, - TPluginsStart extends object = object -> extends CommonPluginWrapper { - readonly startDependencies: Promise<[CoreStart, TPluginsStart, TStart]>; - - setup(setupContext: CoreSetup, plugins: TPluginsSetup): TSetup | Promise; - start(startContext: CoreStart, plugins: TPluginsStart): TStart | Promise; - stop(): Promise; -} - /** * Lightweight wrapper around discovered plugin that is responsible for instantiating * plugin and dispatching proper context and dependencies into plugin's lifecycle hooks. @@ -127,11 +109,11 @@ export class PluginWrapper< ): TSetup | Promise { this.instance = this.createPluginInstance(); - if (this.isStandardPluginInstance(this.instance)) { - return this.instance.setup(setupContext as CoreSetup, plugins); + if (this.isPrebootPluginInstance(this.instance)) { + return this.instance.setup(setupContext as CorePreboot, plugins); } - return this.instance.setup(setupContext as CorePreboot, plugins); + return this.instance.setup(setupContext as CoreSetup, plugins); } /** @@ -146,8 +128,8 @@ export class PluginWrapper< throw new Error(`Plugin "${this.name}" can't be started since it isn't set up.`); } - if (!this.isStandardPluginInstance(this.instance)) { - throw new Error(`Plugin "${this.name}" is not a standard plugin and cannot be started.`); + if (this.isPrebootPluginInstance(this.instance)) { + throw new Error(`Plugin "${this.name}" is a preboot plugin and cannot be started.`); } const startContract = this.instance.start(startContext, plugins); @@ -197,25 +179,6 @@ export class PluginWrapper< return configDescriptor; } - /** - * Indicates whether plugin is a standard plugin (manifest `type` property is equal to `standard`). - */ - public isStandardPlugin(): this is StandardPluginWrapper< - TSetup, - TStart, - TPluginsSetup, - TPluginsStart - > { - return this.isStandardPluginInstance(this.instance); - } - - /** - * Indicates whether plugin is a preboot plugin (manifest `type` property is equal to `preboot`). - */ - public isPrebootPlugin(): this is PrebootPluginWrapper { - return this.isPrebootPluginInstance(this.instance); - } - private createPluginInstance() { this.log.debug('Initializing plugin'); @@ -248,17 +211,9 @@ export class PluginWrapper< return instance; } - private isStandardPluginInstance( - instance: PluginWrapper['instance'] - ): instance is - | Plugin - | AsyncPlugin { - return this.manifest.type === 'standard'; - } - private isPrebootPluginInstance( instance: PluginWrapper['instance'] ): instance is PrebootPlugin { - return this.manifest.type === 'preboot'; + return this.manifest.type === PluginType.preboot; } } diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 1f94e18de968104..449f095a086d4de 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -9,7 +9,7 @@ import { shareReplay } from 'rxjs/operators'; import type { RequestHandlerContext } from 'src/core/server'; import { CoreContext } from '../core_context'; -import { PluginWrapper, PrebootPluginWrapper, StandardPluginWrapper } from './plugin'; +import { PluginWrapper } from './plugin'; import { PluginsServicePrebootSetupDeps, PluginsServiceSetupDeps, @@ -94,7 +94,7 @@ export function createPluginInitializerContext( export function createPluginPrebootSetupContext( coreContext: CoreContext, deps: PluginsServicePrebootSetupDeps, - plugin: PrebootPluginWrapper + plugin: PluginWrapper ): CorePreboot { return { elasticsearch: { @@ -128,7 +128,7 @@ export function createPluginPrebootSetupContext( export function createPluginSetupContext( coreContext: CoreContext, deps: PluginsServiceSetupDeps, - plugin: StandardPluginWrapper + plugin: PluginWrapper ): CoreSetup { const router = deps.http.createRouter('', plugin.opaqueId); diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index e7f14a432fee407..30d860158d810b9 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -8,8 +8,8 @@ import Path from 'path'; import { Observable } from 'rxjs'; -import { filter, first, map, concatMap, tap, toArray } from 'rxjs/operators'; -import { pick, getFlattenedObject } from '@kbn/std'; +import { concatMap, filter, first, map, tap, toArray } from 'rxjs/operators'; +import { getFlattenedObject, pick } from '@kbn/std'; import { CoreService } from '../../types'; import { CoreContext } from '../core_context'; @@ -18,10 +18,10 @@ import { discover, PluginDiscoveryError, PluginDiscoveryErrorType } from './disc import { PluginWrapper } from './plugin'; import { DiscoveredPlugin, - PluginConfigDescriptor, - PluginName, InternalPluginInfo, + PluginConfigDescriptor, PluginDependencies, + PluginName, PluginType, } from './types'; import { PluginsConfig, PluginsConfigType } from './plugins_config'; @@ -115,26 +115,27 @@ export class PluginsService implements CoreService plugin.path), + pluginTree: this.pluginsSystem.getPluginDependencies(PluginType.preboot), + pluginPaths: this.pluginsSystem.getPlugins(PluginType.preboot).map((plugin) => plugin.path), uiPlugins: { internal: this.uiPluginInternalInfo, - public: uiPlugins.preboot, - browserConfigs: this.generateUiPluginsConfigs(uiPlugins.preboot), + public: prebootUIPlugins, + browserConfigs: this.generateUiPluginsConfigs(prebootUIPlugins), }, }, standard: { - pluginTree: pluginTree.standard, - pluginPaths: plugins.standard.map((plugin) => plugin.path), + pluginTree: this.pluginsSystem.getPluginDependencies(PluginType.standard), + pluginPaths: this.pluginsSystem + .getPlugins(PluginType.standard) + .map((plugin) => plugin.path), uiPlugins: { internal: this.uiPluginInternalInfo, - public: uiPlugins.standard, - browserConfigs: this.generateUiPluginsConfigs(uiPlugins.standard), + public: standardUIPlugins, + browserConfigs: this.generateUiPluginsConfigs(standardUIPlugins), }, }, }; @@ -153,7 +154,7 @@ export class PluginsService implements CoreService(); if (config.initialize) { - contracts = await this.pluginsSystem.setupStandardPlugins(deps); + contracts = await this.pluginsSystem.setupPlugins(PluginType.standard, deps); this.registerPluginStaticDirs(deps); } else { this.log.info('Plugin initialization disabled.'); @@ -178,15 +179,15 @@ export class PluginsService implements CoreService { test('can be setup even without plugins', async () => { for (const pluginsSetup of [ - await pluginsSystem.setupPrebootPlugins(prebootDeps), - await pluginsSystem.setupStandardPlugins(setupDeps), + await pluginsSystem.setupPlugins(PluginType.preboot, prebootDeps), + await pluginsSystem.setupPlugins(PluginType.standard, setupDeps), ]) { expect(pluginsSetup).toBeInstanceOf(Map); expect(pluginsSetup.size).toBe(0); @@ -93,8 +94,8 @@ test('can be setup even without plugins', async () => { }); test('getPlugins returns the list of plugins', () => { - const pluginA = createPlugin('plugin-a', { type: 'preboot' }); - const pluginB = createPlugin('plugin-b', { type: 'preboot' }); + const pluginA = createPlugin('plugin-a', { type: PluginType.preboot }); + const pluginB = createPlugin('plugin-b', { type: PluginType.preboot }); const pluginC = createPlugin('plugin-c'); const pluginD = createPlugin('plugin-d'); @@ -102,14 +103,12 @@ test('getPlugins returns the list of plugins', () => { pluginsSystem.addPlugin(plugin); } - expect(pluginsSystem.getPlugins()).toEqual({ - preboot: [pluginA, pluginB], - standard: [pluginC, pluginD], - }); + expect(pluginsSystem.getPlugins(PluginType.preboot)).toEqual([pluginA, pluginB]); + expect(pluginsSystem.getPlugins(PluginType.standard)).toEqual([pluginC, pluginD]); }); test('getPluginDependencies returns dependency tree of symbols', () => { - for (const type of ['standard', 'preboot'] as PluginType[]) { + for (const type of [PluginType.preboot, PluginType.standard]) { pluginsSystem.addPlugin( createPlugin(`plugin-a-${type}`, { type, required: [`no-dep-${type}`] }) ); @@ -123,143 +122,187 @@ test('getPluginDependencies returns dependency tree of symbols', () => { pluginsSystem.addPlugin(createPlugin(`no-dep-${type}`, { type })); } - expect(pluginsSystem.getPluginDependencies()).toMatchInlineSnapshot(` + expect(pluginsSystem.getPluginDependencies(PluginType.preboot)).toMatchInlineSnapshot(` Object { - "preboot": Object { - "asNames": Map { - "plugin-a-preboot" => Array [ - "no-dep-preboot", - ], - "plugin-b-preboot" => Array [ - "plugin-a-preboot", - "no-dep-preboot", - ], - "no-dep-preboot" => Array [], - }, - "asOpaqueIds": Map { - Symbol(plugin-a-preboot) => Array [ - Symbol(no-dep-preboot), - ], - Symbol(plugin-b-preboot) => Array [ - Symbol(plugin-a-preboot), - Symbol(no-dep-preboot), - ], - Symbol(no-dep-preboot) => Array [], - }, + "asNames": Map { + "plugin-a-preboot" => Array [ + "no-dep-preboot", + ], + "plugin-b-preboot" => Array [ + "plugin-a-preboot", + "no-dep-preboot", + ], + "no-dep-preboot" => Array [], }, - "standard": Object { - "asNames": Map { - "plugin-a-standard" => Array [ - "no-dep-standard", - ], - "plugin-b-standard" => Array [ - "plugin-a-standard", - "no-dep-standard", - ], - "no-dep-standard" => Array [], - }, - "asOpaqueIds": Map { - Symbol(plugin-a-standard) => Array [ - Symbol(no-dep-standard), - ], - Symbol(plugin-b-standard) => Array [ - Symbol(plugin-a-standard), - Symbol(no-dep-standard), - ], - Symbol(no-dep-standard) => Array [], - }, + "asOpaqueIds": Map { + Symbol(plugin-a-preboot) => Array [ + Symbol(no-dep-preboot), + ], + Symbol(plugin-b-preboot) => Array [ + Symbol(plugin-a-preboot), + Symbol(no-dep-preboot), + ], + Symbol(no-dep-preboot) => Array [], + }, + } + `); + expect(pluginsSystem.getPluginDependencies(PluginType.standard)).toMatchInlineSnapshot(` + Object { + "asNames": Map { + "plugin-a-standard" => Array [ + "no-dep-standard", + ], + "plugin-b-standard" => Array [ + "plugin-a-standard", + "no-dep-standard", + ], + "no-dep-standard" => Array [], + }, + "asOpaqueIds": Map { + Symbol(plugin-a-standard) => Array [ + Symbol(no-dep-standard), + ], + Symbol(plugin-b-standard) => Array [ + Symbol(plugin-a-standard), + Symbol(no-dep-standard), + ], + Symbol(no-dep-standard) => Array [], }, } `); }); -test('`setupPrebootPlugins` throws plugin has missing required dependency', async () => { - pluginsSystem.addPlugin(createPlugin('some-id', { type: 'preboot', required: ['missing-dep'] })); +test('`setupPlugins` throws plugin has missing required dependency', async () => { + pluginsSystem.addPlugin( + createPlugin('some-id-preboot', { type: PluginType.preboot, required: ['missing-dep'] }) + ); + await expect( + pluginsSystem.setupPlugins(PluginType.preboot, prebootDeps) + ).rejects.toMatchInlineSnapshot( + `[Error: Topological ordering of plugins did not complete, these plugins have cyclic or missing dependencies: ["some-id-preboot"]]` + ); - await expect(pluginsSystem.setupPrebootPlugins(prebootDeps)).rejects.toMatchInlineSnapshot( + pluginsSystem.addPlugin(createPlugin('some-id', { required: ['missing-dep'] })); + await expect( + pluginsSystem.setupPlugins(PluginType.standard, setupDeps) + ).rejects.toMatchInlineSnapshot( `[Error: Topological ordering of plugins did not complete, these plugins have cyclic or missing dependencies: ["some-id"]]` ); }); -test('`setupStandardPlugins` throws plugin has missing required dependency', async () => { - pluginsSystem.addPlugin(createPlugin('some-id', { required: ['missing-dep'] })); +test('`setupPlugins` throws plugin has incompatible required dependency', async () => { + pluginsSystem.addPlugin( + createPlugin('some-id-preboot', { + type: PluginType.preboot, + required: ['incompatible-standard'], + }) + ); + pluginsSystem.addPlugin(createPlugin('incompatible-standard')); + await expect( + pluginsSystem.setupPlugins(PluginType.preboot, prebootDeps) + ).rejects.toMatchInlineSnapshot( + `[Error: Topological ordering of plugins did not complete, these plugins have cyclic or missing dependencies: ["some-id-preboot"]]` + ); - await expect(pluginsSystem.setupStandardPlugins(setupDeps)).rejects.toMatchInlineSnapshot( + pluginsSystem.addPlugin(createPlugin('some-id', { required: ['incompatible-preboot'] })); + pluginsSystem.addPlugin(createPlugin('incompatible-preboot', { type: PluginType.preboot })); + await expect( + pluginsSystem.setupPlugins(PluginType.standard, setupDeps) + ).rejects.toMatchInlineSnapshot( `[Error: Topological ordering of plugins did not complete, these plugins have cyclic or missing dependencies: ["some-id"]]` ); }); -test('`setupPrebootPlugins` throws if plugins have circular required dependency', async () => { - pluginsSystem.addPlugin(createPlugin('no-dep', { type: 'preboot' })); +test('`setupPlugins` throws if plugins have circular required dependency', async () => { + pluginsSystem.addPlugin(createPlugin('no-dep-preboot', { type: PluginType.preboot })); pluginsSystem.addPlugin( - createPlugin('depends-on-1', { type: 'preboot', required: ['depends-on-2'] }) + createPlugin('depends-on-1-preboot', { + type: PluginType.preboot, + required: ['depends-on-2-preboot'], + }) ); pluginsSystem.addPlugin( - createPlugin('depends-on-2', { type: 'preboot', required: ['depends-on-1'] }) + createPlugin('depends-on-2-preboot', { + type: PluginType.preboot, + required: ['depends-on-1-preboot'], + }) ); - await expect(pluginsSystem.setupPrebootPlugins(prebootDeps)).rejects.toMatchInlineSnapshot( - `[Error: Topological ordering of plugins did not complete, these plugins have cyclic or missing dependencies: ["depends-on-1","depends-on-2"]]` + await expect( + pluginsSystem.setupPlugins(PluginType.preboot, prebootDeps) + ).rejects.toMatchInlineSnapshot( + `[Error: Topological ordering of plugins did not complete, these plugins have cyclic or missing dependencies: ["depends-on-1-preboot","depends-on-2-preboot"]]` ); -}); -test('`setupStandardPlugins` throws if plugins have circular required dependency', async () => { pluginsSystem.addPlugin(createPlugin('no-dep')); pluginsSystem.addPlugin(createPlugin('depends-on-1', { required: ['depends-on-2'] })); pluginsSystem.addPlugin(createPlugin('depends-on-2', { required: ['depends-on-1'] })); - await expect(pluginsSystem.setupStandardPlugins(setupDeps)).rejects.toMatchInlineSnapshot( + await expect( + pluginsSystem.setupPlugins(PluginType.standard, setupDeps) + ).rejects.toMatchInlineSnapshot( `[Error: Topological ordering of plugins did not complete, these plugins have cyclic or missing dependencies: ["depends-on-1","depends-on-2"]]` ); }); -test('`setupPrebootPlugins` throws if plugins have circular optional dependency', async () => { - pluginsSystem.addPlugin(createPlugin('no-dep', { type: 'preboot' })); +test('`setupPlugins` throws if plugins have circular optional dependency', async () => { + pluginsSystem.addPlugin(createPlugin('no-dep-preboot', { type: PluginType.preboot })); pluginsSystem.addPlugin( - createPlugin('depends-on-1', { type: 'preboot', optional: ['depends-on-2'] }) + createPlugin('depends-on-1-preboot', { + type: PluginType.preboot, + optional: ['depends-on-2-preboot'], + }) ); pluginsSystem.addPlugin( - createPlugin('depends-on-2', { type: 'preboot', optional: ['depends-on-1'] }) + createPlugin('depends-on-2-preboot', { + type: PluginType.preboot, + optional: ['depends-on-1-preboot'], + }) ); - await expect(pluginsSystem.setupPrebootPlugins(prebootDeps)).rejects.toMatchInlineSnapshot( - `[Error: Topological ordering of plugins did not complete, these plugins have cyclic or missing dependencies: ["depends-on-1","depends-on-2"]]` + await expect( + pluginsSystem.setupPlugins(PluginType.preboot, prebootDeps) + ).rejects.toMatchInlineSnapshot( + `[Error: Topological ordering of plugins did not complete, these plugins have cyclic or missing dependencies: ["depends-on-1-preboot","depends-on-2-preboot"]]` ); -}); -test('`setupStandardPlugins` throws if plugins have circular optional dependency', async () => { pluginsSystem.addPlugin(createPlugin('no-dep')); pluginsSystem.addPlugin(createPlugin('depends-on-1', { optional: ['depends-on-2'] })); pluginsSystem.addPlugin(createPlugin('depends-on-2', { optional: ['depends-on-1'] })); - await expect(pluginsSystem.setupStandardPlugins(setupDeps)).rejects.toMatchInlineSnapshot( + await expect( + pluginsSystem.setupPlugins(PluginType.standard, setupDeps) + ).rejects.toMatchInlineSnapshot( `[Error: Topological ordering of plugins did not complete, these plugins have cyclic or missing dependencies: ["depends-on-1","depends-on-2"]]` ); }); -test('`setupPrebootPlugins` ignores missing optional dependency', async () => { - const plugin = createPlugin('some-id', { type: 'preboot', optional: ['missing-dep'] }); - jest.spyOn(plugin, 'setup').mockResolvedValue('test'); +test('`setupPlugins` ignores missing optional dependency', async () => { + const prebootPlugin = createPlugin('some-id-preboot', { + type: PluginType.preboot, + optional: ['missing-dep'], + }); + jest.spyOn(prebootPlugin, 'setup').mockResolvedValue('test'); - pluginsSystem.addPlugin(plugin); + pluginsSystem.addPlugin(prebootPlugin); - expect([...(await pluginsSystem.setupPrebootPlugins(prebootDeps))]).toMatchInlineSnapshot(` + expect([...(await pluginsSystem.setupPlugins(PluginType.preboot, prebootDeps))]) + .toMatchInlineSnapshot(` Array [ Array [ - "some-id", + "some-id-preboot", "test", ], ] `); -}); -test('`setupStandardPlugins` ignores missing optional dependency', async () => { const plugin = createPlugin('some-id', { optional: ['missing-dep'] }); jest.spyOn(plugin, 'setup').mockResolvedValue('test'); pluginsSystem.addPlugin(plugin); - expect([...(await pluginsSystem.setupStandardPlugins(setupDeps))]).toMatchInlineSnapshot(` + expect([...(await pluginsSystem.setupPlugins(PluginType.standard, setupDeps))]) + .toMatchInlineSnapshot(` Array [ Array [ "some-id", @@ -269,6 +312,84 @@ test('`setupStandardPlugins` ignores missing optional dependency', async () => { `); }); +test('correctly orders preboot plugins and returns exposed values for "setup"', async () => { + const plugins = new Map([ + [ + createPlugin('order-4', { type: PluginType.preboot, required: ['order-2'] }), + { 'order-2': 'added-as-2' }, + ], + [createPlugin('order-0', { type: PluginType.preboot }), {}], + [ + createPlugin('order-2', { + type: PluginType.preboot, + required: ['order-1'], + optional: ['order-0'], + }), + { 'order-1': 'added-as-3', 'order-0': 'added-as-1' }, + ], + [ + createPlugin('order-1', { type: PluginType.preboot, required: ['order-0'] }), + { 'order-0': 'added-as-1' }, + ], + [ + createPlugin('order-3', { + type: PluginType.preboot, + required: ['order-2'], + optional: ['missing-dep'], + }), + { 'order-2': 'added-as-2' }, + ], + ] as Array<[PluginWrapper, Record]>); + + const setupContextMap = new Map(); + + [...plugins.keys()].forEach((plugin, index) => { + jest.spyOn(plugin, 'setup').mockResolvedValue(`added-as-${index}`); + setupContextMap.set(plugin.name, `setup-for-${plugin.name}`); + pluginsSystem.addPlugin(plugin); + }); + + mockCreatePluginPrebootSetupContext.mockImplementation((context, deps, plugin) => + setupContextMap.get(plugin.name) + ); + + expect([...(await pluginsSystem.setupPlugins(PluginType.preboot, prebootDeps))]) + .toMatchInlineSnapshot(` + Array [ + Array [ + "order-0", + "added-as-1", + ], + Array [ + "order-1", + "added-as-3", + ], + Array [ + "order-2", + "added-as-2", + ], + Array [ + "order-3", + "added-as-4", + ], + Array [ + "order-4", + "added-as-0", + ], + ] + `); + + for (const [plugin, deps] of plugins) { + expect(mockCreatePluginPrebootSetupContext).toHaveBeenCalledWith( + coreContext, + prebootDeps, + plugin + ); + expect(plugin.setup).toHaveBeenCalledTimes(1); + expect(plugin.setup).toHaveBeenCalledWith(setupContextMap.get(plugin.name), deps); + } +}); + test('correctly orders plugins and returns exposed values for "setup" and "start"', async () => { interface Contracts { setup: Record; @@ -333,7 +454,8 @@ test('correctly orders plugins and returns exposed values for "setup" and "start startContextMap.get(plugin.name) ); - expect([...(await pluginsSystem.setupStandardPlugins(setupDeps))]).toMatchInlineSnapshot(` + expect([...(await pluginsSystem.setupPlugins(PluginType.standard, setupDeps))]) + .toMatchInlineSnapshot(` Array [ Array [ "order-0", @@ -364,7 +486,8 @@ test('correctly orders plugins and returns exposed values for "setup" and "start expect(plugin.setup).toHaveBeenCalledWith(setupContextMap.get(plugin.name), deps.setup); } - expect([...(await pluginsSystem.startStandardPlugins(startDeps))]).toMatchInlineSnapshot(` + expect([...(await pluginsSystem.startPlugins(PluginType.standard, startDeps))]) + .toMatchInlineSnapshot(` Array [ Array [ "order-0", @@ -396,6 +519,54 @@ test('correctly orders plugins and returns exposed values for "setup" and "start } }); +test('`setupPlugins` only setups preboot plugins that have server side', async () => { + const firstPluginToRun = createPlugin('order-0', { type: PluginType.preboot }); + const secondPluginNotToRun = createPlugin('order-not-run', { + type: PluginType.preboot, + server: false, + }); + const thirdPluginToRun = createPlugin('order-1', { type: PluginType.preboot }); + + [firstPluginToRun, secondPluginNotToRun, thirdPluginToRun].forEach((plugin, index) => { + jest.spyOn(plugin, 'setup').mockResolvedValue(`added-as-${index}`); + + pluginsSystem.addPlugin(plugin); + }); + + expect([...(await pluginsSystem.setupPlugins(PluginType.preboot, prebootDeps))]) + .toMatchInlineSnapshot(` + Array [ + Array [ + "order-1", + "added-as-2", + ], + Array [ + "order-0", + "added-as-0", + ], + ] + `); + + expect(mockCreatePluginPrebootSetupContext).toHaveBeenCalledWith( + coreContext, + prebootDeps, + firstPluginToRun + ); + expect(mockCreatePluginPrebootSetupContext).not.toHaveBeenCalledWith( + coreContext, + secondPluginNotToRun + ); + expect(mockCreatePluginPrebootSetupContext).toHaveBeenCalledWith( + coreContext, + prebootDeps, + thirdPluginToRun + ); + + expect(firstPluginToRun.setup).toHaveBeenCalledTimes(1); + expect(secondPluginNotToRun.setup).not.toHaveBeenCalled(); + expect(thirdPluginToRun.setup).toHaveBeenCalledTimes(1); +}); + test('`setupPlugins` only setups plugins that have server side', async () => { const firstPluginToRun = createPlugin('order-0'); const secondPluginNotToRun = createPlugin('order-not-run', { server: false }); @@ -407,7 +578,8 @@ test('`setupPlugins` only setups plugins that have server side', async () => { pluginsSystem.addPlugin(plugin); }); - expect([...(await pluginsSystem.setupStandardPlugins(setupDeps))]).toMatchInlineSnapshot(` + expect([...(await pluginsSystem.setupPlugins(PluginType.standard, setupDeps))]) + .toMatchInlineSnapshot(` Array [ Array [ "order-1", @@ -438,35 +610,64 @@ test('`setupPlugins` only setups plugins that have server side', async () => { }); test('`uiPlugins` returns empty Map before plugins are added', async () => { - expect(pluginsSystem.uiPlugins().standard).toMatchInlineSnapshot(`Map {}`); + expect(pluginsSystem.uiPlugins(PluginType.preboot)).toMatchInlineSnapshot(`Map {}`); + expect(pluginsSystem.uiPlugins(PluginType.standard)).toMatchInlineSnapshot(`Map {}`); }); test('`uiPlugins` returns ordered Maps of all plugin manifests', async () => { - const plugins = new Map([ - [createPlugin('order-4', { required: ['order-2'] }), { 'order-2': 'added-as-2' }], - [createPlugin('order-0'), {}], - [ - createPlugin('order-2', { required: ['order-1'], optional: ['order-0'] }), - { 'order-1': 'added-as-3', 'order-0': 'added-as-1' }, - ], - [createPlugin('order-1', { required: ['order-0'] }), { 'order-0': 'added-as-1' }], - [ - createPlugin('order-3', { required: ['order-2'], optional: ['missing-dep'] }), - { 'order-2': 'added-as-2' }, - ], - ] as Array<[PluginWrapper, Record]>); + const plugins = new Map( + [PluginType.preboot, PluginType.standard].flatMap( + (type) => + [ + [ + createPlugin(`order-4-${type}`, { type, required: [`order-2-${type}`] }), + { 'order-2': 'added-as-2' }, + ], + [createPlugin(`order-0-${type}`, { type }), {}], + [ + createPlugin(`order-2-${type}`, { + type, + required: [`order-1-${type}`], + optional: [`order-0-${type}`], + }), + { 'order-1': 'added-as-3', 'order-0': 'added-as-1' }, + ], + [ + createPlugin(`order-1-${type}`, { type, required: [`order-0-${type}`] }), + { 'order-0': 'added-as-1' }, + ], + [ + createPlugin(`order-3-${type}`, { + type, + required: [`order-2-${type}`], + optional: ['missing-dep'], + }), + { 'order-2': 'added-as-2' }, + ], + ] as Array<[PluginWrapper, Record]> + ) + ); [...plugins.keys()].forEach((plugin) => { pluginsSystem.addPlugin(plugin); }); - expect([...pluginsSystem.uiPlugins().standard.keys()]).toMatchInlineSnapshot(` + expect([...pluginsSystem.uiPlugins(PluginType.preboot).keys()]).toMatchInlineSnapshot(` + Array [ + "order-0-preboot", + "order-1-preboot", + "order-2-preboot", + "order-3-preboot", + "order-4-preboot", + ] + `); + expect([...pluginsSystem.uiPlugins(PluginType.standard).keys()]).toMatchInlineSnapshot(` Array [ - "order-0", - "order-1", - "order-2", - "order-3", - "order-4", + "order-0-standard", + "order-1-standard", + "order-2-standard", + "order-3-standard", + "order-4-standard", ] `); }); @@ -489,14 +690,14 @@ test('`uiPlugins` returns only ui plugin dependencies', async () => { pluginsSystem.addPlugin(plugin); }); - const plugin = pluginsSystem.uiPlugins().standard.get('ui-plugin')!; + const plugin = pluginsSystem.uiPlugins(PluginType.standard).get('ui-plugin')!; expect(plugin.requiredPlugins).toEqual(['req-ui']); expect(plugin.optionalPlugins).toEqual(['opt-ui']); }); test('can start without plugins', async () => { - await pluginsSystem.setupStandardPlugins(setupDeps); - const pluginsStart = await pluginsSystem.startStandardPlugins(startDeps); + await pluginsSystem.setupPlugins(PluginType.standard, setupDeps); + const pluginsStart = await pluginsSystem.startPlugins(PluginType.standard, startDeps); expect(pluginsStart).toBeInstanceOf(Map); expect(pluginsStart.size).toBe(0); @@ -513,8 +714,8 @@ test('`startPlugins` only starts plugins that were setup', async () => { pluginsSystem.addPlugin(plugin); }); - await pluginsSystem.setupStandardPlugins(setupDeps); - const result = await pluginsSystem.startStandardPlugins(startDeps); + await pluginsSystem.setupPlugins(PluginType.standard, setupDeps); + const result = await pluginsSystem.startPlugins(PluginType.standard, startDeps); expect([...result]).toMatchInlineSnapshot(` Array [ Array [ @@ -542,7 +743,7 @@ describe('setup', () => { pluginsSystem.addPlugin(plugin); mockCreatePluginSetupContext.mockImplementation(() => ({})); - const promise = pluginsSystem.setupStandardPlugins(setupDeps); + const promise = pluginsSystem.setupPlugins(PluginType.standard, setupDeps); jest.runAllTimers(); await expect(promise).rejects.toMatchInlineSnapshot( @@ -560,7 +761,7 @@ describe('setup', () => { jest.spyOn(plugin, 'start').mockResolvedValue(`started-as-${index}`); pluginsSystem.addPlugin(plugin); }); - await pluginsSystem.setupStandardPlugins(setupDeps); + await pluginsSystem.setupPlugins(PluginType.standard, setupDeps); const log = logger.get.mock.results[0].value as jest.Mocked; expect(log.info).toHaveBeenCalledWith(`Setting up [2] plugins: [order-1,order-0]`); }); @@ -582,8 +783,8 @@ describe('start', () => { mockCreatePluginSetupContext.mockImplementation(() => ({})); mockCreatePluginStartContext.mockImplementation(() => ({})); - await pluginsSystem.setupStandardPlugins(setupDeps); - const promise = pluginsSystem.startStandardPlugins(startDeps); + await pluginsSystem.setupPlugins(PluginType.standard, setupDeps); + const promise = pluginsSystem.startPlugins(PluginType.standard, startDeps); jest.runAllTimers(); await expect(promise).rejects.toMatchInlineSnapshot( @@ -601,8 +802,8 @@ describe('start', () => { jest.spyOn(plugin, 'start').mockResolvedValue(`started-as-${index}`); pluginsSystem.addPlugin(plugin); }); - await pluginsSystem.setupStandardPlugins(setupDeps); - await pluginsSystem.startStandardPlugins(startDeps); + await pluginsSystem.setupPlugins(PluginType.standard, setupDeps); + await pluginsSystem.startPlugins(PluginType.standard, startDeps); const log = logger.get.mock.results[0].value as jest.Mocked; expect(log.info).toHaveBeenCalledWith(`Starting [2] plugins: [order-1,order-0]`); }); @@ -644,8 +845,8 @@ describe('asynchronous plugins', () => { .mockReturnValue(asyncStart ? Promise.resolve('start-async') : 'start-sync'); pluginsSystem.addPlugin(asyncPlugin); - await pluginsSystem.setupStandardPlugins(setupDeps); - await pluginsSystem.startStandardPlugins(startDeps); + await pluginsSystem.setupPlugins(PluginType.standard, setupDeps); + await pluginsSystem.startPlugins(PluginType.standard, startDeps); }; it('logs a warning if a plugin returns a promise from its setup contract in dev mode', async () => { @@ -751,8 +952,8 @@ describe('stop', () => { mockCreatePluginSetupContext.mockImplementation(() => ({})); - await pluginsSystem.setupStandardPlugins(setupDeps); - const stopPromise = pluginsSystem.stopStandardPlugins(); + await pluginsSystem.setupPlugins(PluginType.standard, setupDeps); + const stopPromise = pluginsSystem.stopPlugins(PluginType.standard); jest.runAllTimers(); await stopPromise; diff --git a/src/core/server/plugins/plugins_system.ts b/src/core/server/plugins/plugins_system.ts index 74a239ff7ed58d0..d3bc5acd60b9de0 100644 --- a/src/core/server/plugins/plugins_system.ts +++ b/src/core/server/plugins/plugins_system.ts @@ -9,8 +9,8 @@ import { withTimeout, isPromise } from '@kbn/std'; import { CoreContext } from '../core_context'; import { Logger } from '../logging'; -import { PluginWrapper, PrebootPluginWrapper, StandardPluginWrapper } from './plugin'; -import { DiscoveredPlugin, PluginName, PluginType } from './types'; +import { PluginWrapper } from './plugin'; +import { DiscoveredPlugin, PluginDependencies, PluginName, PluginType } from './types'; import { createPluginPrebootSetupContext, createPluginSetupContext, @@ -21,13 +21,13 @@ import { PluginsServiceSetupDeps, PluginsServiceStartDeps, } from './plugins_service'; -import { PluginDependencies } from '.'; const Sec = 1000; /** @internal */ export class PluginsSystem { - private readonly plugins = new Map(); + private readonly prebootPlugins = new Map(); + private readonly standardPlugins = new Map(); private readonly log: Logger; // `satup`, the past-tense version of the noun `setup`. private readonly satupPrebootPlugins: PluginName[] = []; @@ -38,96 +38,80 @@ export class PluginsSystem { } public addPlugin(plugin: PluginWrapper) { - this.plugins.set(plugin.name, plugin); + if (plugin.manifest.type === PluginType.preboot) { + this.prebootPlugins.set(plugin.name, plugin); + } else { + this.standardPlugins.set(plugin.name, plugin); + } } - public getPlugins() { - return [...this.plugins.values()].reduce( - (groups, plugin) => { - const pluginsGroup = plugin.isPrebootPlugin() ? groups.preboot : groups.standard; - pluginsGroup.push(plugin); - return groups; - }, - { preboot: [], standard: [] } as { [key in PluginType]: PluginWrapper[] } - ); + public getPlugins(type: PluginType) { + return [...(type === PluginType.preboot ? this.prebootPlugins : this.standardPlugins).values()]; } /** * @returns a ReadonlyMap of each plugin and an Array of its available dependencies * @internal */ - public getPluginDependencies(): { [key in PluginType]: PluginDependencies } { - const dependenciesByType = { - preboot: { asNames: new Map(), asOpaqueIds: new Map() }, - standard: { asNames: new Map(), asOpaqueIds: new Map() }, - }; - - for (const plugin of this.plugins.values()) { + public getPluginDependencies(type: PluginType): PluginDependencies { + const plugins = type === PluginType.preboot ? this.prebootPlugins : this.standardPlugins; + const asNames = new Map(); + const asOpaqueIds = new Map(); + for (const plugin of plugins.values()) { const dependencies = [ ...new Set([ ...plugin.requiredPlugins, - ...plugin.optionalPlugins.filter((optPlugin) => this.plugins.has(optPlugin)), + ...plugin.optionalPlugins.filter((optPlugin) => plugins.has(optPlugin)), ]), ]; - const pluginsGroup = plugin.isPrebootPlugin() - ? dependenciesByType.preboot - : dependenciesByType.standard; - // SAME AS DEPS???? NAME === DEP - pluginsGroup.asNames.set( + asNames.set( plugin.name, - dependencies.map((pluginName) => this.plugins.get(pluginName)!.name) + dependencies.map((pluginName) => plugins.get(pluginName)!.name) ); - pluginsGroup.asOpaqueIds.set( + asOpaqueIds.set( plugin.opaqueId, - dependencies.map((pluginName) => this.plugins.get(pluginName)!.opaqueId) + dependencies.map((pluginName) => plugins.get(pluginName)!.opaqueId) ); } - return dependenciesByType; - } - - public async setupPrebootPlugins(deps: PluginsServicePrebootSetupDeps) { - const contracts = await this.setupPlugins( - (plugin) => plugin.isPrebootPlugin(), - (plugin: PrebootPluginWrapper, pluginDepContracts) => - plugin.setup( - createPluginPrebootSetupContext(this.coreContext, deps, plugin), - pluginDepContracts - ) - ); - - this.satupPrebootPlugins.push(...contracts.keys()); - - return contracts; + return { asNames, asOpaqueIds }; } - public async setupStandardPlugins(deps: PluginsServiceSetupDeps) { - const contracts = await this.setupPlugins( - (plugin) => plugin.isStandardPlugin(), - (plugin: StandardPluginWrapper, pluginDepContracts) => - plugin.setup(createPluginSetupContext(this.coreContext, deps, plugin), pluginDepContracts) - ); - - this.satupStandardPlugins.push(...contracts.keys()); + public async setupPlugins( + type: PluginType.preboot, + deps: PluginsServicePrebootSetupDeps + ): Promise>; + public async setupPlugins( + type: PluginType.standard, + deps: PluginsServiceSetupDeps + ): Promise>; + async setupPlugins( + type: PluginType, + deps: PluginsServicePrebootSetupDeps | PluginsServiceSetupDeps + ): Promise> { + const [plugins, satupPlugins] = + type === PluginType.preboot + ? [this.prebootPlugins, this.satupPrebootPlugins] + : [this.standardPlugins, this.satupStandardPlugins]; - return contracts; - } - - public async startStandardPlugins(deps: PluginsServiceStartDeps) { const contracts = new Map(); - if (this.satupStandardPlugins.length === 0) { + if (plugins.size === 0) { return contracts; } + const sortedPlugins = new Map( + [...this.getTopologicallySortedPluginNames(plugins)] + .map((pluginName) => [pluginName, plugins.get(pluginName)!] as [string, PluginWrapper]) + .filter(([, plugin]) => plugin.includesServerPlugin) + ); this.log.info( - `Starting [${this.satupStandardPlugins.length}] plugins: [${[...this.satupStandardPlugins]}]` + `Setting up [${sortedPlugins.size}] plugins: [${[...sortedPlugins.keys()].join(',')}]` ); - for (const pluginName of this.satupStandardPlugins) { - this.log.debug(`Starting plugin "${pluginName}"...`); - const plugin = this.plugins.get(pluginName)!; + for (const [pluginName, plugin] of sortedPlugins) { + this.log.debug(`Setting up plugin "${pluginName}"...`); const pluginDeps = new Set([...plugin.requiredPlugins, ...plugin.optionalPlugins]); const pluginDepContracts = Array.from(pluginDeps).reduce((depContracts, dependencyName) => { // Only set if present. Could be absent if plugin does not have server-side code or is a @@ -139,25 +123,37 @@ export class PluginsSystem { return depContracts; }, {} as Record); + let pluginSetupContext; + if (type === PluginType.preboot) { + pluginSetupContext = createPluginPrebootSetupContext( + this.coreContext, + deps as PluginsServicePrebootSetupDeps, + plugin + ); + } else { + pluginSetupContext = createPluginSetupContext( + this.coreContext, + deps as PluginsServiceSetupDeps, + plugin + ); + } + let contract: unknown; - const contractOrPromise = plugin.start( - createPluginStartContext(this.coreContext, deps, plugin), - pluginDepContracts - ); + const contractOrPromise = plugin.setup(pluginSetupContext, pluginDepContracts); if (isPromise(contractOrPromise)) { if (this.coreContext.env.mode.dev) { this.log.warn( - `Plugin ${pluginName} is using asynchronous start lifecycle. Asynchronous plugins support will be removed in a later version.` + `Plugin ${pluginName} is using asynchronous setup lifecycle. Asynchronous plugins support will be removed in a later version.` ); } - const contractMaybe = await withTimeout({ + const contractMaybe = await withTimeout({ promise: contractOrPromise, timeoutMs: 10 * Sec, }); if (contractMaybe.timedout) { throw new Error( - `Start lifecycle of "${pluginName}" plugin wasn't completed in 10sec. Consider disabling the plugin and re-start.` + `Setup lifecycle of "${pluginName}" plugin wasn't completed in 10sec. Consider disabling the plugin and re-start.` ); } else { contract = contractMaybe.value; @@ -167,83 +163,25 @@ export class PluginsSystem { } contracts.set(pluginName, contract); + satupPlugins.push(pluginName); } return contracts; } - public async stopPrebootPlugins() { - if (this.plugins.size === 0 || this.satupPrebootPlugins.length === 0) { - return; - } - - this.log.info(`Stopping preboot plugins.`); - - await this.stopPlugins(this.satupPrebootPlugins); - } - - public async stopStandardPlugins() { - if (this.plugins.size === 0 || this.satupStandardPlugins.length === 0) { - return; - } - - this.log.info(`Stopping standard plugins.`); - - await this.stopPlugins(this.satupStandardPlugins); - } - - /** - * Get a Map of all discovered UI plugins in topological order. - */ - public uiPlugins() { - const uiPluginNames = [...this.getTopologicallySortedPluginNames().keys()].filter( - (pluginName) => this.plugins.get(pluginName)!.includesUiPlugin - ); - - const publicPluginsByType = { - preboot: new Map(), - standard: new Map(), - }; - - for (const pluginName of uiPluginNames) { - const plugin = this.plugins.get(pluginName)!; - const pluginsGroup = plugin.isPrebootPlugin() - ? publicPluginsByType.preboot - : publicPluginsByType.standard; - - pluginsGroup.set(pluginName, { - id: pluginName, - type: plugin.manifest.type, - configPath: plugin.manifest.configPath, - requiredPlugins: plugin.manifest.requiredPlugins.filter((p) => uiPluginNames.includes(p)), - optionalPlugins: plugin.manifest.optionalPlugins.filter((p) => uiPluginNames.includes(p)), - requiredBundles: plugin.manifest.requiredBundles, - }); - } - - return publicPluginsByType; - } - - private async setupPlugins( - pluginFilter: (plugin: PluginWrapper) => boolean, - pluginInitializer: (plugin: PluginWrapper, depContracts: Record) => unknown - ) { + public async startPlugins(type: PluginType.standard, deps: PluginsServiceStartDeps) { const contracts = new Map(); - if (this.plugins.size === 0) { + if (this.satupStandardPlugins.length === 0) { return contracts; } - const sortedPlugins = new Map( - [...this.getTopologicallySortedPluginNames()] - .map((pluginName) => [pluginName, this.plugins.get(pluginName)!] as [string, PluginWrapper]) - .filter(([, plugin]) => plugin.includesServerPlugin && pluginFilter(plugin)) - ); this.log.info( - `Setting up [${sortedPlugins.size}] plugins: [${[...sortedPlugins.keys()].join(',')}]` + `Starting [${this.satupStandardPlugins.length}] plugins: [${[...this.satupStandardPlugins]}]` ); - for (const [pluginName, plugin] of sortedPlugins) { - this.log.debug(`Setting up plugin "${pluginName}"...`); + for (const pluginName of this.satupStandardPlugins) { + this.log.debug(`Starting plugin "${pluginName}"...`); + const plugin = this.standardPlugins.get(pluginName)!; const pluginDeps = new Set([...plugin.requiredPlugins, ...plugin.optionalPlugins]); const pluginDepContracts = Array.from(pluginDeps).reduce((depContracts, dependencyName) => { // Only set if present. Could be absent if plugin does not have server-side code or is a @@ -256,21 +194,24 @@ export class PluginsSystem { }, {} as Record); let contract: unknown; - const contractOrPromise = pluginInitializer(plugin, pluginDepContracts); + const contractOrPromise = plugin.start( + createPluginStartContext(this.coreContext, deps, plugin), + pluginDepContracts + ); if (isPromise(contractOrPromise)) { if (this.coreContext.env.mode.dev) { this.log.warn( - `Plugin ${pluginName} is using asynchronous setup lifecycle. Asynchronous plugins support will be removed in a later version.` + `Plugin ${pluginName} is using asynchronous start lifecycle. Asynchronous plugins support will be removed in a later version.` ); } - const contractMaybe = await withTimeout({ + const contractMaybe = await withTimeout({ promise: contractOrPromise, timeoutMs: 10 * Sec, }); if (contractMaybe.timedout) { throw new Error( - `Setup lifecycle of "${pluginName}" plugin wasn't completed in 10sec. Consider disabling the plugin and re-start.` + `Start lifecycle of "${pluginName}" plugin wasn't completed in 10sec. Consider disabling the plugin and re-start.` ); } else { contract = contractMaybe.value; @@ -285,15 +226,26 @@ export class PluginsSystem { return contracts; } - private async stopPlugins(pluginsToStop: PluginName[]) { + public async stopPlugins(type: PluginType) { + const [plugins, satupPlugins] = + type === PluginType.preboot + ? [this.prebootPlugins, this.satupPrebootPlugins] + : [this.standardPlugins, this.satupStandardPlugins]; + + if (plugins.size === 0 || satupPlugins.length === 0) { + return; + } + + this.log.info(`Stopping all "${type}" plugins.`); + // Stop plugins in the reverse order of when they were set up. - while (pluginsToStop.length > 0) { - const pluginName = pluginsToStop.pop()!; + while (satupPlugins.length > 0) { + const pluginName = satupPlugins.pop()!; this.log.debug(`Stopping plugin "${pluginName}"...`); const resultMaybe = await withTimeout({ - promise: this.plugins.get(pluginName)!.stop(), + promise: plugins.get(pluginName)!.stop(), timeoutMs: 30 * Sec, }); @@ -303,6 +255,38 @@ export class PluginsSystem { } } + /** + * Get a Map of all discovered UI plugins in topological order. + */ + public uiPlugins(type: PluginType) { + const plugins = type === PluginType.preboot ? this.prebootPlugins : this.standardPlugins; + const uiPluginNames = [...this.getTopologicallySortedPluginNames(plugins).keys()].filter( + (pluginName) => plugins.get(pluginName)!.includesUiPlugin + ); + const publicPlugins = new Map( + uiPluginNames.map((pluginName) => { + const plugin = plugins.get(pluginName)!; + return [ + pluginName, + { + id: pluginName, + type: plugin.manifest.type, + configPath: plugin.manifest.configPath, + requiredPlugins: plugin.manifest.requiredPlugins.filter((p) => + uiPluginNames.includes(p) + ), + optionalPlugins: plugin.manifest.optionalPlugins.filter((p) => + uiPluginNames.includes(p) + ), + requiredBundles: plugin.manifest.requiredBundles, + }, + ]; + }) + ); + + return publicPlugins; + } + /** * Gets topologically sorted plugin names that are registered with the plugin system. * Ordering is possible if and only if the plugins graph has no directed cycles, @@ -311,18 +295,18 @@ export class PluginsSystem { * * Uses Kahn's Algorithm to sort the graph. */ - private getTopologicallySortedPluginNames() { + private getTopologicallySortedPluginNames(plugins: Map) { // We clone plugins so we can remove handled nodes while we perform the // topological ordering. If the cloned graph is _not_ empty at the end, we // know we were not able to topologically order the graph. We exclude optional // dependencies that are not present in the plugins graph. const pluginsDependenciesGraph = new Map( - [...this.plugins.entries()].map(([pluginName, plugin]) => { + [...plugins.entries()].map(([pluginName, plugin]) => { return [ pluginName, new Set([ ...plugin.requiredPlugins, - ...plugin.optionalPlugins.filter((dependency) => this.plugins.has(dependency)), + ...plugin.optionalPlugins.filter((dependency) => plugins.has(dependency)), ]), ] as [PluginName, Set]; }) diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index 3258991c50975bc..0de93d9f26cf13d 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -117,7 +117,10 @@ export type PluginName = string; export type PluginOpaqueId = symbol; /** @public */ -export type PluginType = 'preboot' | 'standard'; +export enum PluginType { + preboot = 'preboot', + standard = 'standard', +} /** @internal */ export interface PluginDependencies {