0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2024-12-16 21:56:25 -05:00
verdaccio/packages/loaders/src/plugin-async-loader.ts
Juan Picado 631abe1ac1
refactor logger module (#3592)
* refactor logger module

* Update index.ts
2023-02-05 21:00:32 +01:00

150 lines
5.5 KiB
TypeScript

import buildDebug from 'debug';
import fs from 'fs';
import { dirname, isAbsolute, join, resolve } from 'path';
import { pluginUtils } from '@verdaccio/core';
import { logger } from '@verdaccio/logger';
import { Config, Logger } from '@verdaccio/types';
import { PluginType, isES6, isValid, tryLoad } from './utils';
const debug = buildDebug('verdaccio:plugin:loader:async');
const { lstat } = fs.promises ? fs.promises : require('fs/promises');
async function isDirectory(pathFolder: string) {
const stat = await lstat(pathFolder);
return stat.isDirectory();
}
export type Params = { config: Config; logger: Logger };
// type Plugins<T> =
// | pluginUtils.Auth<T>
// | pluginUtils.Storage<T>
// | pluginUtils.ExpressMiddleware<T, unknown, unknown>;
/**
* The plugin loader find recursively plugins, if one plugin fails is ignored and report the error to the logger.
*
* The loader follows the order:
* - If the at the `config.yaml` file the `plugins: ./plugins` is defined
* - If is absolute will use the provided path
* - If is relative, will use the base path of the config file. eg: /root/config.yaml the plugins folder should be
* hosted at /root/plugins
* - The next step is find at the node_modules or global based on the `require` native algorithm.
* - If the package is scoped eg: @scope/foo, try to load the package `@scope/foo`
* - If the package is not scoped, will use the default prefix: verdaccio-foo.
* - If a custom prefix is provided, the verdaccio- is replaced by the config.server.pluginPrefix.
*
* The `sanityCheck` is the validation for the required methods to load the plugin, if the validation fails the plugin won't be loaded.
* The `params` is an object that contains the global configuration and the logger.
*
* @param {*} pluginConfigs the custom plugin section
* @param {*} params a set of params to initialize the plugin
* @param {*} sanityCheck callback that check the shape that should fulfill the plugin
* @param {*} prefix by default is verdaccio but can be override with config.server.pluginPrefix
* @return {Array} list of plugins
*/
export async function asyncLoadPlugin<T extends pluginUtils.Plugin<T>>(
pluginConfigs: any = {},
params: Params,
sanityCheck: (plugin: PluginType<T>) => boolean,
prefix: string = 'verdaccio'
): Promise<PluginType<T>[]> {
const pluginsIds = Object.keys(pluginConfigs);
const { config } = params;
let plugins: PluginType<T>[] = [];
for (let pluginId of pluginsIds) {
debug('plugin %s', pluginId);
if (typeof config.plugins === 'string') {
let pluginsPath = config.plugins;
debug('plugin path %s', pluginsPath);
if (!isAbsolute(pluginsPath)) {
if (typeof config.config_path === 'string' && !config.configPath) {
logger.error(
'configPath is missing and the legacy config.config_path is not available for loading plugins'
);
}
if (!config.configPath) {
logger.error('config path property is required for loading plugins');
continue;
}
pluginsPath = resolve(join(dirname(config.configPath), pluginsPath));
}
logger.debug({ path: pluginsPath }, 'plugins folder defined, loading plugins from @{path} ');
// throws if is nto a directory
try {
await isDirectory(pluginsPath);
const pluginDir = pluginsPath;
const externalFilePlugin = resolve(pluginDir, `${prefix}-${pluginId}`);
let plugin = tryLoad<T>(externalFilePlugin, (a: any, b: any) => {
logger.error(a, b);
});
if (plugin && isValid(plugin)) {
plugin = executePlugin(plugin, pluginConfigs[pluginId], params);
if (!sanityCheck(plugin)) {
logger.error(
{ content: externalFilePlugin },
"@{content} doesn't look like a valid plugin"
);
continue;
}
plugins.push(plugin);
continue;
}
} catch (err: any) {
logger.warn(
{ err: err.message, pluginsPath, pluginId },
'@{err} on loading plugins at @{pluginsPath} for @{pluginId}'
);
}
}
if (typeof pluginId === 'string') {
const isScoped: boolean = pluginId.startsWith('@') && pluginId.includes('/');
debug('is scoped plugin %s', isScoped);
const pluginName = isScoped ? pluginId : `${prefix}-${pluginId}`;
debug('plugin pkg name %s', pluginName);
let plugin = tryLoad<T>(pluginName, (a: any, b: any) => {
logger.error(a, b);
});
if (plugin && isValid(plugin)) {
plugin = executePlugin(plugin, pluginConfigs[pluginId], params);
if (!sanityCheck(plugin)) {
logger.error({ content: pluginName }, "@{content} doesn't look like a valid plugin");
continue;
}
plugins.push(plugin);
continue;
} else {
logger.error(
{ pluginName },
'package not found, try to install @{pluginName} with a package manager'
);
continue;
}
}
}
debug('plugin found %s', plugins.length);
return plugins;
}
export function executePlugin<T>(
plugin: PluginType<T>,
pluginConfig: unknown,
params: Params
): PluginType<T> {
if (isES6(plugin)) {
debug('plugin is ES6');
// @ts-expect-error no relevant for the code
// eslint-disable-next-line new-cap
return new plugin.default(pluginConfig, params) as Plugin;
} else {
debug('plugin is commonJS');
// @ts-expect-error improve this type
return plugin(pluginConfig, params) as PluginType<T>;
}
}