Home Manual Reference Source

src/services/common/plugins.js

const path = require('path');
const { provider } = require('jimple');
/**
 * This service is in charge of looking for, loading and registering plugins for the app.
 */
class Plugins {
  /**
   * Class constructor.
   * @param {string}    prefix      The prefix the dependencies need to have in order to be
   *                                identified as plugins.
   * @param {Projext}   app         To send to the plugis so they can register their services.
   * @param {Logger}    appLogger   To indicate if a plugin couldn't be loaded.
   * @param {Object}    packageInfo To read the dependencies list.
   * @param {PathUtils} pathUtils   To build the dependencies paths.
   */
  constructor(prefix, app, appLogger, packageInfo, pathUtils) {
    /**
     * The prefix the dependencies need to have in order to be identified as plugins.
     * @type {string}
     */
    this.prefix = prefix;
    /**
     * The local reference to the main app.
     * @type {Projext}
     */
    this.app = app;
    /**
     * A local reference for the `appLogger` service.
     * @type {Logger}
     */
    this.appLogger = appLogger;
    /**
     * The implementation `package.json`
     * @type {Object}
     */
    this.packageInfo = packageInfo;
    /**
     * A local reference for the `pathUtils` service.
     * @type {PathUtils}
     */
    this.pathUtils = pathUtils;
    /**
     * After the plugins are loaded, this property will have a list with the plugins names.
     * @type {Array}
     * @access protected
     * @ignore
     */
    this._loadedPlugins = [];
  }
  /**
   * Search for plugins on the `package.json` and loads them.
   * @param  {boolean} [dependencies=true]    Whether or not to look for plugins on the
   *                                          `dependencies`.
   * @param  {boolean} [devDependencies=true] Whether or not to look for plugins on the
   *                                          `devDependencies`.
   */
  load(dependencies = true, devDependencies = true) {
    const packages = [];
    if (dependencies && this.packageInfo.dependencies) {
      packages.push(...Object.keys(this.packageInfo.dependencies));
    }

    if (devDependencies && this.packageInfo.devDependencies) {
      packages.push(...Object.keys(this.packageInfo.devDependencies));
    }

    packages
    .filter((name) => name.startsWith(this.prefix))
    .forEach((name) => this._loadPlugin(name));
  }
  /**
   * Loads a plugin from a file relative to the project root directory.
   * @param {string} filePath The path to the file.
   */
  loadFromFile(filePath) {
    this._loadPluginFile(
      filePath,
      path.basename(filePath),
      this.pathUtils.join(filePath)
    );
  }
  /**
   * Gets the names of the loaded plugins.
   * @return {string}
   */
  getLoadedPlugins() {
    return this._loadedPlugins;
  }
  /**
   * Checks whether a plugin was loaded or not.
   * @param {string} name The plugin's name.
   * @return {boolean}
   */
  loaded(name) {
    return this.getLoadedPlugins().includes(name);
  }
  /**
   * Loads a plugin by its package name.
   * @param {string} packageName The name of the plugin.
   * @ignore
   * @access protected
   */
  _loadPlugin(packageName) {
    this._loadPluginFile(
      packageName,
      packageName.substr(this.prefix.length),
      this.pathUtils.join('node_modules', packageName)
    );
  }
  /**
   * Loads a plugin form an specific file.
   * @param {string} reference A name for the plugin to show in case the plugin can't be loaded.
   *                           In the case of a plugin from the `node_modules`, it should be the
   *                           package name; on any other case, it should be the file path.
   * @param {string} name      The name the service will use to save it on the list of loaded
   *                           plugins.
   * @param {string} filepath  The path to the file to `require`.
   * @throws {Error} If the plugin can't be loaded or registered.
   * @ignore
   * @access protected
   */
  _loadPluginFile(reference, name, filepath) {
    try {
      // eslint-disable-next-line global-require,import/no-dynamic-require
      const plugin = require(filepath);
      if (plugin.plugin && typeof plugin.plugin === 'function') {
        plugin.plugin(this.app);
      } else {
        plugin(this.app);
      }
    } catch (error) {
      this.appLogger.error(`The plugin ${reference} couldn't be loaded`);
      throw error;
    }

    this._loadedPlugins.push(name);
  }
}
/**
 * Generate a `Provider` with an already defined prefix for the plugins.
 * @example
 * // Generate the provider
 * const provider = plugins('my-plugin-');
 * // Register it on the container
 * container.register(provider);
 * // Getting access to the service instance
 * const plugins = container.get('plugins');
 * @param {string} prefix The prefix the dependencies need to have in order to
 *                        be identified as plugins.
 * @return {Provider}
 */
const plugins = (prefix) => provider((app) => {
  app.set('plugins', () => new Plugins(
    prefix,
    app,
    app.get('appLogger'),
    app.get('packageInfo'),
    app.get('pathUtils')
  ));
});

module.exports = {
  Plugins,
  plugins,
};