in src/core/server/plugins/discovery/plugin_manifest_parser.ts [88:213]
export async function parseManifest(
pluginPath: string,
packageInfo: PackageInfo,
log: Logger
): Promise<PluginManifest> {
const manifestPath = resolve(pluginPath, MANIFEST_FILE_NAME);
let manifestContent;
try {
manifestContent = await fsReadFileAsync(manifestPath);
} catch (err) {
throw PluginDiscoveryError.missingManifest(manifestPath, err);
}
let manifest: Partial<PluginManifest>;
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.')
);
}
// Plugin id can be used as a config path or as a logger context and having dots
// in there may lead to various issues, so we forbid that.
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,
};
}