import { join } from 'path' ; import typeDetect from 'type-detect' ; import { Subject } from 'rxjs' ; import { first } from 'rxjs/operators' ; import { isPromise } from '@kbn/std' ; import { isConfigSchema } from '@kbn/config-schema' ; import { Logger } from '../logging' ; import { AsyncPlugin , Plugin , PluginConfigDescriptor , PluginInitializer , PluginInitializerContext , PluginManifest , PluginOpaqueId , PluginType , PrebootPlugin , } from './types' ; import { CorePreboot , CoreSetup , CoreStart } from '..' ; const OSS_PATH_REGEX = /[\/|\\]src[\/|\\]plugins[\/|\\]/ ; const XPACK_PATH_REGEX = /[\/|\\]x-pack[\/|\\]plugins[\/|\\]/ ; internal export class PluginWrapper < TSetup = unknown , TStart = unknown , TPluginsSetup extends object = object , TPluginsStart extends object = object > { public readonly path : string ; public readonly source : 'oss' | 'x-pack' | 'external' ; public readonly manifest : PluginManifest ; public readonly opaqueId : PluginOpaqueId ; public readonly name : PluginManifest [ 'id' ] ; public readonly configPath : PluginManifest [ 'configPath' ] ; public readonly requiredPlugins : PluginManifest [ 'requiredPlugins' ] ; public readonly optionalPlugins : PluginManifest [ 'optionalPlugins' ] ; public readonly requiredBundles : PluginManifest [ 'requiredBundles' ] ; public readonly includesServerPlugin : PluginManifest [ 'server' ] ; public readonly includesUiPlugin : PluginManifest [ 'ui' ] ; private readonly log : Logger ; private readonly initializerContext : PluginInitializerContext ; private instance ? : Plugin < TSetup , TStart , TPluginsSetup , TPluginsStart > | PrebootPlugin < TSetup , TPluginsSetup > | AsyncPlugin < TSetup , TStart , TPluginsSetup , TPluginsStart > ; private readonly startDependencies$ = new Subject < [ CoreStart , TPluginsStart , TStart ] > ( ) ; public readonly startDependencies = this . startDependencies$ . pipe ( first ( ) ) . toPromise ( ) ; constructor ( public readonly params ) { this . path = params . path ; this . source = getPluginSource ( params . path ) ; this . manifest = params . manifest ; this . opaqueId = params . opaqueId ; this . initializerContext = params . initializerContext ; this . log = params . initializerContext . logger . get ( ) ; this . name = params . manifest . id ; this . configPath = params . manifest . configPath ; this . requiredPlugins = params . manifest . requiredPlugins ; this . optionalPlugins = params . manifest . optionalPlugins ; this . requiredBundles = params . manifest . requiredBundles ; this . includesServerPlugin = params . manifest . server ; this . includesUiPlugin = params . manifest . ui ; } param setupContext param plugins public setup ( setupContext , plugins ) { this . instance = this . createPluginInstance ( ) ; if ( this . isPrebootPluginInstance ( this . instance ) ) { return this . instance . setup ( setupContext as CorePreboot , plugins ) ; } return this . instance . setup ( setupContext as CoreSetup , plugins ) ; } param startContext param plugins public start ( startContext , plugins ) { if ( this . instance === undefined ) { throw new Error ( `Plugin "${ this . name }" can't be started since it isn't set up.` ) ; } 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 ) ; if ( isPromise ( startContract ) ) { return startContract . then ( ( resolvedContract ) => { this . startDependencies$ . next ( [ startContext , plugins , resolvedContract ] ) ; return resolvedContract ; } ) ; } else { this . startDependencies$ . next ( [ startContext , plugins , startContract ] ) ; return startContract ; } } /**
     * Calls optional `stop` function exposed by the plugin initializer.
     */ public async stop ( ) { if ( this . instance === undefined ) { throw new Error ( `Plugin "${ this . name }" can't be stopped since it isn't set up.` ) ; } if ( typeof this . instance . stop === 'function' ) { await this . instance . stop ( ) ; } this . instance = undefined ; } public getConfigDescriptor ( ) { if ( ! this . manifest . server ) { return null ; } const pluginPathServer = join ( this . path , 'server' ) ; const pluginDefinition = require ( pluginPathServer ) ; if ( ! ( 'config' in pluginDefinition ) ) { this . log . debug ( `"${ pluginPathServer }" does not export "config".` ) ; return null ; } const configDescriptor = pluginDefinition . config ; if ( ! isConfigSchema ( configDescriptor . schema ) ) { throw new Error ( 'Configuration schema expected to be an instance of Type' ) ; } return configDescriptor ; } private createPluginInstance ( ) { this . log . debug ( 'Initializing plugin' ) ; const pluginDefinition = require ( join ( this . path , 'server' ) ) ; if ( ! ( 'plugin' in pluginDefinition ) ) { throw new Error ( `Plugin "${ this . name }" does not export "plugin" definition (${ this . path }).` ) ; } const { plugin : initializer } = pluginDefinition as { plugin : PluginInitializer < TSetup , TStart , TPluginsSetup , TPluginsStart > ; } ; if ( ! initializer || typeof initializer !== 'function' ) { throw new Error ( `Definition of plugin "${ this . name }" should be a function (${ this . path }).` ) ; } const instance = initializer ( this . initializerContext ) ; if ( ! instance || typeof instance !== 'object' ) { throw new Error ( `Initializer for plugin "${ this . manifest . id }" is expected to return plugin instance, but returned "${ typeDetect ( instance ) }".` ) ; } if ( typeof instance . setup !== 'function' ) { throw new Error ( `Instance of plugin "${ this . name }" does not define "setup" function.` ) ; } return instance ; } private isPrebootPluginInstance ( instance ) { return this . manifest . type === PluginType . preboot ; } } function getPluginSource ( path ) { if ( OSS_PATH_REGEX . test ( path ) ) { return 'oss' ; } else if ( XPACK_PATH_REGEX . test ( path ) ) { return 'x-pack' ; } return 'external' ; }