AppLauncher.js

/* globals Wsh: false */

(function () {
  if (Wsh && Wsh.AppLauncher) return;

  /**
   * The WSH (Windows Script Host) CLI that launches apps according to the schema defined in a JSON file.
   *
   * @namespace AppLauncher
   * @memberof Wsh
   * @requires {@link https://github.com/tuckn/WshAppLauncher|tuckn/WshBasicPackage}
   */
  Wsh.AppLauncher = {};

  // Shorthands
  var util = Wsh.Util;
  var os = Wsh.OS;
  var child_process = Wsh.ChildProcess;
  var logger = Wsh.Logger;

  var objAdd = Object.assign;
  var insp = util.inspect;
  var obtain = util.obtainPropVal;
  var parseTmp = util.parseTemplateLiteral;
  var parseDate = util.parseDateLiteral;
  var hasContent = util.hasContent;
  var includes = util.includes;
  var isArray = util.isArray;
  var isSolidString = util.isSolidString;
  var isPlainObject = util.isPlainObject;
  var srrd = os.surroundCmdArg;

  var apL = Wsh.AppLauncher; // Shorthand

  /** @constant {string} */
  var MODULE_TITLE = 'WshAppLauncher/AppLauncher.js';

  var throwErrNonStr = function (functionName, errVal) {
    util.throwTypeError('string', MODULE_TITLE, functionName, errVal);
  };

  var throwErrNonObject = function (functionName, errVal) {
    util.throwTypeError('object', MODULE_TITLE, functionName, errVal);
  };

  var throwValErr = function (valName, functionName, errVal) {
    util.throwValueError(valName, MODULE_TITLE, functionName, errVal);
  };

  // apL.launchAppUsingLog {{{
  /**
   * Launches the app.
   *
   * @example
   * var apL = Wsh.AppLauncher; // Shorthand
   *
   * apL.launchAppUsingLog('C:\\Windows\\System32\\net.exe', ['use'], {
   *   runsAdmin: true,
   *   winStyle: 'nonActive',
   *   logger: 'info/console' // See https://github.com/tuckn/WshLogger
   * });
   * @function launchAppUsingLog
   * @memberof Wsh.AppLauncher
   * @param {string} app - The executable file path or the command of CMD.
   * @param {string[]} [args] - The Array of arguments.
   * @param {object} [options] - Optional parameters.
   * @param {boolean} [options.shell=false] - Wrap with CMD.EXE
   * @param {(number|string)} [options.winStyle='activeDef'] - See {@link https://tuckn.net/docs/WshUtil/Wsh.Constants.windowStyles.html|Wsh.Constants.windowStyles}.
   * @param {(boolean|undefined)} [options.runsAdmin] - true: as Admin, false: as User
   * @param {boolean} [options.isDryRun=false] - No execute, returns the string of command.
   * @param {(Logger|string|object)} [options.logger] - The Logger instance or create options. See {@link https://tuckn.net/docs/WshLogger/Wsh.Logger.html#.create|Wsh.Logger.create}.
   * @param {boolean} [options.transportsLog=true] - Outputs Wsh.Logger logs after connecting. See {@link https://tuckn.net/docs/WshLogger/Wsh.Logger.html#.this.transport|Wsh.Logger.transport}.
   * @returns {void}
   */
  apL.launchAppUsingLog = function (app, args, options) {
    var FN = 'apL.launchAppUsingLog';

    var loggerObj = obtain(options, 'logger', {});
    var lggr = logger.create(loggerObj);
    lggr.info('Start the function ' + FN);

    if (!isSolidString(app)) throwErrNonStr(FN, app);

    // Arguments
    var argsStr = os.joinCmdArgs(args);
    // Command
    var command = srrd(app) + ' ' + argsStr;
    // Options
    var shell = obtain(options, 'shell', false);
    var winStyle = obtain(options, 'winStyle', 'activeDef');
    var runsAdmin = obtain(options, 'runsAdmin');
    var isDryRun = obtain(options, 'isDryRun', false);

    var op = objAdd(
      // Default option values
      {
        shell: shell,
        winStyle: winStyle,
        runsAdmin: runsAdmin,
        isDryRun: isDryRun
      },
      // Specified option values
      options
    );

    lggr.info('command: ' + command);
    lggr.info('shell: ' + shell);
    lggr.info('winStyle: ' + winStyle);
    lggr.info('runsAdmin: ' + runsAdmin);
    lggr.info('isDryRun: ' + isDryRun);

    var rtn = child_process.exec(command, op);
    if (isDryRun) lggr.info(rtn);

    lggr.info('Finished the function ' + FN);
    var transportsLog = obtain(options, 'transportsLog', true);
    if (transportsLog) lggr.transport();

    return;
  }; // }}}

  // apL.launchAppsUsingSchema {{{
  /**
   * @typedef {object} typeSchemaAppLauncher
   * @property {string} [description]
   * @property {object} [components]
   * @property {...typeSchemaAppLauncherTask} tasks
   */

  /**
   * @typedef {object} typeSchemaAppLauncherTask
   * @property {string} [description] - The task description.
   * @property {boolean} [available=true] - If specifying false, Skips the task.
   * @property {string} app - The executable file path or the command of CMD.
   * @property {string[]} [args] - The Array of arguments.
   * @property {boolean} [shell=false] - Wrap with CMD.EXE
   * @property {(number|string)} [winStyle='activeDef'] - See {@link https://tuckn.net/docs/WshUtil/Wsh.Constants.windowStyles.html|Wsh.Constants.windowStyles}.
   * @property {(boolean|undefined)} [runsAdmin] - true: as Admin, false: as User
   */

  /**
   * Backs up the directories.
   *
   * @example
   * var apL = Wsh.AppLauncher; // Shorthand
   * var schema = {
   *   description: 'Example Schema WshAppLauncher',
   *   components: {
   *     binDir: 'D:\\MyApps',
   *     etcDir: 'D:\\MyConfs',
   *     wmLeftPath: null,
   *     wmRightPath: null
   *   },
   *   tasks: {
   *     'main:Claunch': {
   *       app: '${binDir}\\CLaunch\\ClAdmin.exe'
   *     },
   *     'main:AutoHotkey': {
   *       app: '${binDir}\\AutoHotkey\\AutoHotkeyU64.exe',
   *       args: ['${etcDir}\\MyHotKey.ahk'],
   *       runsAdmin: true
   *     },
   *     'main:FreeCommander': {
   *       app: '${binDir}\\FreeCommander\\FreeCommander.exe',
   *       args: ['/N', '/ini=${etcDir}\\FreeCommander.ini'],
   *       winStyle: 'nonActiveMin'
   *     },
   *     'main:mkTmpDir': {
   *       app: 'mkdir',
   *       args: ['R:\\tmp_#{yyyyMMdd}'],
   *       shell: true
   *     },
   *     'dev:cmdAdmin': {
   *       available: false,
   *       app: 'C:\\Windows\\System32\\cmd.exe',
   *       runsAdmin: true
   *     },
   *     'dev:Vim': {
   *       app: '${binDir}\\Vim\\gvim.exe',
   *       args: ['-N', '-u', '${etcDir}\\_vimrc', '-U', '${etcDir}\\_gvimrc']
   *     },
   *     'app:WinMerge': {
   *       app: '${binDir}\\WinMerge\\WinMergePortable.exe',
   *       args: ['${wmLeftPath}', '${wmRightPath}'],
   *       winStyle: 'activeMax'
   *     }
   *   }
   * };
   *
   * apL.launchAppsUsingSchema(schema, 'main:*', {
   *   logger: 'info/console',
   * });
   * // Only process appLog:current. appLog:lastMonth is not processed because available is false.
   * @function launchAppsUsingSchema
   * @memberof Wsh.AppLauncher
   * @param {typeSchemaAppLauncher} schema
   * @param {string} [taskName] - The task name to launch.
   * @param {object} [options] - Optional parameters.
   * @param {object} [options.overwrites] - Ex. { anyVal1: 'myP@ss', anyVal2: 'p_w_d' }
   * @param {(string|Object)} [options.logger] - See options of {@link Wsh.Logger.create}
   * @param {boolean} [options.isDryRun=false] - No execute, returns the string of command.
   * @returns {void}
   */
  apL.launchAppsUsingSchema = function (schema, taskName, options) {
    var FN = 'apL.launchAppsUsingSchema';
    if (!isPlainObject(schema)) throwErrNonObject(FN, schema);
    if (!isSolidString(taskName)) throwErrNonStr(FN, taskName);

    var loggerObj = obtain(options, 'logger', {});
    var lggr = logger.create(loggerObj);
    lggr.info('Start function ' + FN);
    lggr.info('taskName: "' + taskName + '"');

    var tasks = schema.tasks; // Shorthand
    var taskNames = Object.keys(tasks);
    var regNameMatcher;
    if (includes(taskName, '*')) {
      regNameMatcher = new RegExp(taskName.replace(/\*/g, '.*'));
    } else {
      regNameMatcher = new RegExp(taskName);
    }
    var filteredNames = taskNames.filter(function (taskName) {
      return regNameMatcher.test(taskName);
    });
    lggr.info('matched tasks: ' + filteredNames.length);

    var vals = schema.components; // Shorthand

    // Set option values in keys storing null.
    if (hasContent(options.overwrites)) {
      Object.keys(vals).forEach(function (key) {
        if (vals[key] !== null) return;

        Object.keys(options.overwrites).some(function (writeKey) {
          if (key === writeKey) {
            vals[key] = options.overwrites[writeKey];
            return true;
          }
        });
      });
    }

    var isDryRun = obtain(options, 'isDryRun', false);
    if (isDryRun) lggr.info('dry-run [' + FN + ']:');

    filteredNames.forEach(function (taskName) {
      lggr.info('Start the task: ' + taskName);

      if (tasks[taskName].available === false) {
        lggr.info('available: false => Skip this task');
        return;
      }

      var app = parseDate(parseTmp(tasks[taskName].app || '', vals));

      var args = [];
      if (isArray(tasks[taskName].args)) {
        args = tasks[taskName].args.map(function (arg) {
          return parseDate(parseTmp(arg || '', vals));
        });
      }

      var shell = obtain(tasks[taskName], 'shell', false);
      var winStyle = parseTmp(tasks[taskName].winStyle || '', vals);
      var runsAdmin = obtain(tasks[taskName], 'runsAdmin');

      try {
        apL.launchAppUsingLog(
          app,
          args,
          objAdd(
            {
              transportsLog: false,
              throws: false
            },
            options,
            // Option values on the Schema JSON
            {
              shell: shell,
              winStyle: winStyle,
              runsAdmin: runsAdmin,
              logger: lggr
            }
          )
        );
      } catch (e) {
        lggr.error(insp(e)); // It does not stop with an error.
      }
    });

    lggr.info('Finished function ' + FN);
    var transportsLog = obtain(options, 'transportsLog', true);
    if (transportsLog) lggr.transport();

    return;
  }; // }}}
})();

// vim:set foldmethod=marker commentstring=//%s :