import { readdir , stat } from 'fs' ; import { resolve } from 'path' ; import { bindNodeCallback , from , merge , Observable } from 'rxjs' ; import { catchError , filter , map , mergeMap , shareReplay } from 'rxjs/operators' ; import { CoreContext } from '../../core_context' ; import { Logger } from '../../logging' ; import { PluginWrapper } from '../plugin' ; import { createPluginInitializerContext , InstanceInfo } from '../plugin_context' ; import { PluginsConfig } from '../plugins_config' ; import { PluginDiscoveryError } from './plugin_discovery_error' ; import { parseManifest } from './plugin_manifest_parser' ; const fsReadDir$ = bindNodeCallback < string , string [ ] > ( readdir ) ; const fsStat$ = bindNodeCallback ( stat ) ; const maxScanDepth = 5 ; interface PluginSearchPathEntry { dir : string ; depth : number ; } param config param coreContext internal export function discover ( config , coreContext , instanceInfo ) { const log = coreContext . logger . get ( 'plugins-discovery' ) ; log . debug ( 'Discovering plugins...' ) ; if ( config . additionalPluginPaths . length && coreContext . env . mode . dev ) { log . warn ( `Explicit plugin paths [${ config . additionalPluginPaths }] should only be used in development. Relative imports may not work properly in production.` ) ; } const discoveryResults$ = merge ( from ( config . additionalPluginPaths ) , processPluginSearchPaths$ ( config . pluginSearchPaths , log ) ) . pipe ( mergeMap ( ( pluginPathOrError ) => { return typeof pluginPathOrError === 'string' ? createPlugin$ ( pluginPathOrError , log , coreContext , instanceInfo ) : [ pluginPathOrError ] ; } ) , shareReplay ( ) ) ; return { plugin$ : discoveryResults$ . pipe ( filter ( ( entry ) : entry is PluginWrapper => entry instanceof PluginWrapper ) ) , error$ : discoveryResults$ . pipe ( filter ( ( entry ) : entry is PluginDiscoveryError => ! ( entry instanceof PluginWrapper ) ) ) , } ; } param pluginDirs param log function processPluginSearchPaths$ ( pluginDirs , log ) { function recursiveScanFolder ( ent ) { return from ( [ ent ] ) . pipe ( mergeMap ( ( entry ) => { return findManifestInFolder ( entry . dir , ( ) => { if ( entry . depth > maxScanDepth ) { return [ ] ; } return mapSubdirectories ( entry . dir , ( subDir ) => recursiveScanFolder ( { dir : subDir , depth : entry . depth + 1 } ) ) ; } ) ; } ) ) ; } return from ( pluginDirs . map ( ( dir ) => ( { dir , depth : 0 } ) ) ) . pipe ( mergeMap ( ( entry ) => { log . debug ( `Scanning "${ entry . dir }" for plugin sub-directories...` ) ; return fsReadDir$ ( entry . dir ) . pipe ( mergeMap ( ( ) => recursiveScanFolder ( entry ) ) , catchError ( ( err ) => [ PluginDiscoveryError . invalidSearchPath ( entry . dir , err ) ] ) ) ; } ) ) ; } param dir param notFound function findManifestInFolder ( dir , notFound ) { return fsStat$ ( resolve ( dir , 'opensearch_dashboards.json' ) ) . pipe ( mergeMap ( ( stats ) => { if ( stats . isFile ( ) ) { return [ dir ] ; } return [ ] ; } ) , catchError ( ( manifestStatError ) => { if ( manifestStatError . code !== 'ENOENT' ) { return [ PluginDiscoveryError . invalidPluginPath ( dir , manifestStatError ) ] ; } return notFound ( ) ; } ) ) ; } param dir param mapFunc function mapSubdirectories ( dir , mapFunc ) { return fsReadDir$ ( dir ) . pipe ( mergeMap ( ( subDirs ) => subDirs . map ( ( subDir ) => resolve ( dir , subDir ) ) ) , mergeMap ( ( subDir ) => fsStat$ ( subDir ) . pipe ( mergeMap ( ( pathStat ) => ( pathStat . isDirectory ( ) ? mapFunc ( subDir ) : [ ] ) ) , catchError ( ( subDirStatError ) => [ PluginDiscoveryError . invalidPluginPath ( subDir , subDirStatError ) , ] ) ) ) ) ; } param path param log param coreContext function createPlugin$ ( path , log , coreContext , instanceInfo ) { return from ( parseManifest ( path , coreContext . env . packageInfo , log ) ) . pipe ( map ( ( manifest ) => { log . debug ( `Successfully discovered plugin "${ manifest . id }" at "${ path }"` ) ; const opaqueId = Symbol ( manifest . id ) ; return new PluginWrapper ( { path , manifest , opaqueId , initializerContext : createPluginInitializerContext ( coreContext , opaqueId , manifest , instanceInfo ) , } ) ; } ) , catchError ( ( err ) => [ err ] ) ) ; }