bob.js

"use strict";
import async from 'async';
import config from './config.js';
import jazz from 'jazz';
import p from 'path';
import runner from './runner.js';
import task from './task.js';
import util from 'util';

class Bob {

  /**
   * Constructor for initialising Bob.
   *
   * @param {Object} opts: optional
   * - appDir: directory where the userland application calls Bob from
   * - bobDir: directory where Bob's installation is located
   * - appName: name of the userland application (retrieved from its package.json)
   * - appVersion: version value of the userland application (retrieved from its package.json)
   * - bobMode: either `human` or `robot`, when you are a robot - you have to declare yourself :-]
   * - quiet: when true, only display task name and command, but without the command output
   */
  constructor(opts) {
    this.opts = opts;
  }

  /**
   * Bob is goin' to work!
   * Execute specified tasks
   *
   * @param {Array} taskNames: an array of task names
   * @param {Function} cb: standard cb(err, result) callback
   */
  build(taskNames, cb) {
    const EXEC_OPTS = {
      cwd: this.opts.appDir,
      quiet: this.opts.quiet,
      maxBuffer: this.opts.maxBufferInKb * 1024
    };
    const self = this;

    this._init(taskNames, (err, settings) => {
      if (err) { cb(err); } else {
        self._commands(taskNames, settings, (err, commands) => {
          if (err) { cb(err); } else {
            runner.execSeries(commands, EXEC_OPTS, cb);
          }
        });
      }
    });
  }

  /**
   * Load Bob task files and application config file, and install optional dependencies, all in parallel.
   *
   * @param {Array} taskNames: an array of task names
   * @param {Function} cb: standard cb(err, result) callback
   */
  _init(taskNames, cb) {
    const self = this;

    function loadTask(cb) {
      task.load(taskNames, p.join(self.opts.bobDir, 'conf', 'tasks'), cb);
    }
    function loadConfig(cb) {
      config.load(taskNames, self.opts.appDir, cb);
    }
    async.parallel({ bobTasks: loadTask, appConfig: loadConfig }, cb);
  }

  /**
   * Prepare commands to execute.
   *
   * @param {Array} taskNames: an array of task names
   * @param {Object} settings: tasks and application configs
   * @param {Function} cb: standard cb(err, result) callback
   */
  _commands(taskNames, settings, cb) {
    const TPL_PARAMS = {
      app: this.opts.appDir,
      bob: this.opts.bobDir,
      name: this.opts.appName,
      tmp: this.opts.tmpDir,
      version: this.opts.appVersion
    };
    const bobTasks = settings.bobTasks,
      bobMode = this.opts.bobMode,
      self = this;
    const commands = [];

    taskNames.forEach((taskName) => {

      self._taskTypeNames(taskName, settings).forEach((taskTypeName) => {
        const taskType = bobTasks[taskName].types[taskTypeName];
        Object.keys(taskType).forEach((key) => {
          if (typeof taskType[key] === 'object') {
            taskType[key] = taskType[key][bobMode];
          }
        });
        taskType.preOpts = taskType.preOpts || taskType.opts;
        // strip undefined and null from command elements
        const elems = [taskType.bin, taskType.preOpts, taskType.args, taskType.postOpts].filter(elem => elem);
        commands.push({
          format: util.format(elems.join(' ')),
          meta: {
            task: taskName,
            type: taskTypeName
          }
        });
      });
    });

    const jobs = [];
    commands.forEach((command) => {
      jobs.push((cb) => {
        jazz.compile(command.format).process(TPL_PARAMS, (result) => {
          command.exec = result;
          delete command.format;
          cb(null, command);
        });
      });
    });
    async.parallel(jobs, cb);
  }

  _taskTypeNames(taskName, settings) {
    // NOTE: allow multiple types in application config
    let taskTypeNames =
      (settings.appConfig[taskName] && settings.appConfig[taskName].type) ?
      settings.appConfig[taskName].type :
      settings.bobTasks[taskName].default;
    if (!Array.isArray(taskTypeNames)) {
      taskTypeNames = [taskTypeNames];
    }
    return taskTypeNames;
  }
}

export {
  Bob as default
};