Home Manual Reference Source

src/services/building/buildNodeRunner.js

const path = require('path');
const { provider } = require('jimple');
/**
 * This service provides a simple interface for running targets on a development environment using
 * `Nodemon`. The actual service that does the _'running'_ is `buildNodeRunnerProcess`, but this
 * one takes care of reading and processing a target settings before telling the other service
 * to start Nodemon.
 */
class BuildNodeRunner {
  /**
   * Class constructor.
   * @param {BuildNodeRunnerProcess}       buildNodeRunnerProcess To actually run a target process.
   * @param {ProjectConfigurationSettings} projectConfiguration   To read the targets and their
   *                                                              templates.
   * @param {Targets}                      targets                To get the information of the
   *                                                              included targets.
   * @param {Utils}                        utils                  To normalize executable
   *                                                              extensions.
   */
  constructor(buildNodeRunnerProcess, projectConfiguration, targets, utils) {
    /**
     * A local reference for the `buildNodeRunnerProcess` service.
     * @type {BuildNodeRunnerProcess}
     */
    this.buildNodeRunnerProcess = buildNodeRunnerProcess;
    /**
     * A local reference for the `targets` service.
     * @type {Targets}
     */
    this.targets = targets;
    /**
     * A local reference for the `utils` service.
     * @type {Utils}
     */
    this.utils = utils;
    // Check the project settings and enable the `nodemon` legacy watch mode.
    if (projectConfiguration.others.nodemon.legacyWatch) {
      this.buildNodeRunnerProcess.enableLegacyWatch();
    }
  }
  /**
   * Run a target with Nodemon.
   * @param {Target}  target               The target information.
   * @param {boolean} [forceInspect=false] Whether or not to _"force enable"_ the Node inspector,
   *                                       even if the target has disabled it.
   * @return {Nodemon}
   * @throws {Error} If the target type is `browser`.
   * @throws {Error} If the target needs to be bundled.
   */
  runTarget(target, forceInspect = false) {
    if (target.is.browser) {
      throw new Error(`${target.name} is a browser target and can't be executed`);
    } else if (target.bundle) {
      throw new Error(`${target.name} needs to be bundled in order to run`);
    }

    const inspectOptions = Object.assign({}, target.inspect, {
      enabled: (forceInspect || target.inspect.enabled),
    });

    return target.transpile ?
      this._runWithTranspilation(target, inspectOptions) :
      this._run(target, inspectOptions);
  }
  /**
   * Runs a target that requires transpilation. It executes the file from the distribution
   * directory while it watches the source directory.
   * @param {Target}                target         The target information.
   * @param {NodeInspectorSettings} inspectOptions The options for the Node inspector.
   * @return {Nodemon}
   * @throws {Error} If one of the included targets requires bundling.
   * @access protected
   * @ignore
   */
  _runWithTranspilation(target, inspectOptions) {
    const { paths: { source, build }, includeTargets } = target;
    const executable = path.join(build, target.entry.development);
    const watch = [build];
    const copyPaths = [];
    const transpilationPaths = [{
      from: source,
      to: build,
    }];

    includeTargets.forEach((name) => {
      const subTarget = this.targets.getTarget(name);
      if (subTarget.bundle) {
        const errorMessage = `The target ${name} requires bundling so it can't be ` +
          `included by ${target.name}`;
        throw new Error(errorMessage);
      } else {
        watch.push(subTarget.paths.build);
        const pathSettings = {
          from: subTarget.paths.source,
          to: subTarget.paths.build,
        };
        if (subTarget.transpile) {
          transpilationPaths.push(pathSettings);
        } else {
          copyPaths.push(pathSettings);
        }
      }
    });

    this.buildNodeRunnerProcess.run(
      this.utils.ensureExtension(executable),
      watch,
      inspectOptions,
      transpilationPaths,
      copyPaths,
      {},
      ['*.test.js'],
      () => this.targets.loadTargetDotEnvFile(target, 'development')
    );
  }
  /**
   * Runs a target that doesn't require transpilation. It executes and watches the source directory.
   * @param {Target}                target         The target information.
   * @param {NodeInspectorSettings} inspectOptions The options for the Node inspector.
   * @return {Nodemon}
   * @throws {Error} If one of the included targets requires bundling.
   * @throws {Error} If one of the included targets requires transpiling.
   * @access protected
   * @ignore
   */
  _run(target, inspectOptions) {
    const { paths: { source }, includeTargets } = target;
    const executable = path.join(source, target.entry.development);
    const watch = [source];

    includeTargets.forEach((name) => {
      const subTarget = this.targets.getTarget(name);
      if (subTarget.bundle) {
        const errorMessage = `The target ${name} requires bundling so it can't be ` +
          `included by ${target.name}`;
        throw new Error(errorMessage);
      } else if (subTarget.transpile) {
        const errorMessage = `The target ${name} requires transpilation so it can't be ` +
          `included by ${target.name}`;
        throw new Error(errorMessage);
      } else {
        watch.push(subTarget.paths.source);
      }
    });

    this.buildNodeRunnerProcess.run(
      executable,
      watch,
      inspectOptions,
      [],
      [],
      {},
      ['*.test.js'],
      () => this.targets.loadTargetDotEnvFile(target, 'development')
    );
  }
}
/**
 * The service provider that once registered on the app container will set an instance of
 * `BuildNodeRunner` as the `buildNodeRunner` service.
 * @example
 * // Register it on the container
 * container.register(buildNodeRunner);
 * // Getting access to the service instance
 * const buildNodeRunner = container.get('buildNodeRunner');
 * @type {Provider}
 */
const buildNodeRunner = provider((app) => {
  app.set('buildNodeRunner', () => new BuildNodeRunner(
    app.get('buildNodeRunnerProcess'),
    app.get('projectConfiguration').getConfig(),
    app.get('targets'),
    app.get('utils')
  ));
});

module.exports = {
  BuildNodeRunner,
  buildNodeRunner,
};