import { readFile , stat } from 'fs' ; import { resolve } from 'path' ; import { coerce } from 'semver' ; import { promisify } from 'util' ; import { snakeCase } from 'lodash' ; import { isConfigPath , PackageInfo } from '../../config' ; import { Logger } from '../../logging' ; import { PluginManifest } from '../types' ; import { PluginDiscoveryError } from './plugin_discovery_error' ; import { isCamelCase } from './is_camel_case' ; const fsReadFileAsync = promisify ( readFile ) ; const fsStatAsync = promisify ( stat ) ; /**
 * Name of the JSON manifest file that should be located in the plugin directory.
 */ const MANIFEST_FILE_NAME = 'opensearch_dashboards.json' ; /**
 * The special "opensearchDashboards" version can be used by the plugins to be always compatible.
 */ const ALWAYS_COMPATIBLE_VERSION = 'opensearchDashboards' ; /**
 * Names of the known manifest fields.
 */ const KNOWN_MANIFEST_FIELDS = ( ( ) => { const manifestFields = { id : true , opensearchDashboardsVersion : true , version : true , configPath : true , requiredPlugins : true , optionalPlugins : true , ui : true , server : true , extraPublicDirs : true , requiredBundles : true , } ; return new Set ( Object . keys ( manifestFields ) ) ; } ) ( ) ; param pluginPath param packageInfo internal export async function parseManifest ( pluginPath , packageInfo , log ) { const manifestPath = resolve ( pluginPath , MANIFEST_FILE_NAME ) ; let manifestContent ; try { manifestContent = await fsReadFileAsync ( manifestPath ) ; } catch ( err ) { throw PluginDiscoveryError . missingManifest ( manifestPath , err ) ; } let manifest ; try { manifest = JSON . parse ( manifestContent . toString ( ) ) ; } catch ( err ) { throw PluginDiscoveryError . invalidManifest ( manifestPath , err ) ; } if ( ! manifest || typeof manifest !== 'object' ) { throw PluginDiscoveryError . invalidManifest ( manifestPath , new Error ( 'Plugin manifest must contain a JSON encoded object.' ) ) ; } if ( ! manifest . id || typeof manifest . id !== 'string' ) { throw PluginDiscoveryError . invalidManifest ( manifestPath , new Error ( 'Plugin manifest must contain an "id" property.' ) ) ; } if ( manifest . id . includes ( '.' ) ) { throw PluginDiscoveryError . invalidManifest ( manifestPath , new Error ( 'Plugin "id" must not include `.` characters.' ) ) ; } if ( ! packageInfo . dist && ! isCamelCase ( manifest . id ) ) { log . warn ( `Expect plugin "id" in camelCase, but found: ${ manifest . id }` ) ; } if ( ! manifest . version || typeof manifest . version !== 'string' ) { throw PluginDiscoveryError . invalidManifest ( manifestPath , new Error ( `Plugin manifest for "${ manifest . id }" must contain a "version" property.` ) ) ; } if ( manifest . configPath !== undefined && ! isConfigPath ( manifest . configPath ) ) { throw PluginDiscoveryError . invalidManifest ( manifestPath , new Error ( `The "configPath" in plugin manifest for "${ manifest . id }" should either be a string or an array of strings.` ) ) ; } if ( manifest . extraPublicDirs && ( ! Array . isArray ( manifest . extraPublicDirs ) || ! manifest . extraPublicDirs . every ( ( dir ) => typeof dir === 'string' ) ) ) { throw PluginDiscoveryError . invalidManifest ( manifestPath , new Error ( `The "extraPublicDirs" in plugin manifest for "${ manifest . id }" should be an array of strings.` ) ) ; } const expectedOpenSearchDashboardsVersion = typeof manifest . opensearchDashboardsVersion === 'string' && manifest . opensearchDashboardsVersion ? manifest . opensearchDashboardsVersion : manifest . version ; if ( ! isVersionCompatible ( expectedOpenSearchDashboardsVersion , packageInfo . version ) ) { throw PluginDiscoveryError . incompatibleVersion ( manifestPath , new Error ( `Plugin "${ manifest . id }" is only compatible with OpenSearch Dashboards version "${ expectedOpenSearchDashboardsVersion }", but used OpenSearch Dashboards version is "${ packageInfo . version }".` ) ) ; } const includesServerPlugin = typeof manifest . server === 'boolean' ? manifest . server : false ; const includesUiPlugin = typeof manifest . ui === 'boolean' ? manifest . ui : false ; if ( ! includesServerPlugin && ! includesUiPlugin ) { throw PluginDiscoveryError . invalidManifest ( manifestPath , new Error ( `Both "server" and "ui" are missing or set to "false" in plugin manifest for "${ manifest . id }", but at least one of these must be set to "true".` ) ) ; } const unknownManifestKeys = Object . keys ( manifest ) . filter ( ( key ) => ! KNOWN_MANIFEST_FIELDS . has ( key ) ) ; if ( unknownManifestKeys . length > 0 ) { throw PluginDiscoveryError . invalidManifest ( manifestPath , new Error ( `Manifest for plugin "${ manifest . id }" contains the following unrecognized properties: ${ unknownManifestKeys }.` ) ) ; } return { id : manifest . id , version : manifest . version , opensearchDashboardsVersion : expectedOpenSearchDashboardsVersion , configPath : manifest . configPath || snakeCase ( manifest . id ) , requiredPlugins : Array . isArray ( manifest . requiredPlugins ) ? manifest . requiredPlugins : [ ] , optionalPlugins : Array . isArray ( manifest . optionalPlugins ) ? manifest . optionalPlugins : [ ] , requiredBundles : Array . isArray ( manifest . requiredBundles ) ? manifest . requiredBundles : [ ] , ui : includesUiPlugin , server : includesServerPlugin , extraPublicDirs : manifest . extraPublicDirs , } ; } param pluginPath internal export async function isNewPlatformPlugin ( pluginPath ) { try { return ( await fsStatAsync ( resolve ( pluginPath , MANIFEST_FILE_NAME ) ) ) . isFile ( ) ; } catch ( err ) { return false ; } } param expectedOpenSearchDashboardsVersion param actualOpenSearchDashboardsVersion function isVersionCompatible ( expectedOpenSearchDashboardsVersion , actualOpenSearchDashboardsVersion ) { if ( expectedOpenSearchDashboardsVersion === ALWAYS_COMPATIBLE_VERSION ) { return true ; } const coercedActualOpenSearchDashboardsVersion = coerce ( actualOpenSearchDashboardsVersion ) ; if ( coercedActualOpenSearchDashboardsVersion == null ) { return false ; } const coercedExpectedOpenSearchDashboardsVersion = coerce ( expectedOpenSearchDashboardsVersion ) ; if ( coercedExpectedOpenSearchDashboardsVersion == null ) { return false ; } return ( coercedActualOpenSearchDashboardsVersion . compare ( coercedExpectedOpenSearchDashboardsVersion ) === 0 ) ; }