Source: bin/git-branch-is.js

#!/usr/bin/env node
/**
 * @copyright Copyright 2016 Kevin Locke <kevin@kevinlocke.name>
 * @license MIT
 */

'use strict';

const { Command } = require('commander');

const gitBranchIs = require('..');
const packageJson = require('../package.json');

function collect(arg, args) {
  args.push(arg);
  return args;
}

/** Result from command entry points.
 *
 * @typedef {{
 *   code: (?number|undefined),
 *   stdout: (?string|undefined),
 *   stderr: (?string|undefined)
 * }} CommandResult
 * @property {?number=} code Exit code for the command.
 * @property {?string=} stdout Content to write to stdout.
 * @property {?string=} stderr Content to write to stderr.
 */

/**
 * Entry point for this command.
 *
 * @param {!Array<string>} args Command-line arguments.
 * @param {?function(Error, ?CommandResult=)=} callback Callback for the
 * command result or error.  Required if <code>global.Promise</code> is not
 * defined.
 * @returns {Promise|undefined} If <code>callback</code> is not given and
 * <code>global.Promise</code> is defined, a <code>Promise</code> which will
 * resolve on completion.
 */
function gitBranchIsCmd(args, callback) {
  if (!callback && typeof Promise === 'function') {
    return new Promise((resolve, reject) => {
      gitBranchIsCmd(args, (err, result) => {
        if (err) { reject(err); } else { resolve(result); }
      });
    });
  }

  if (typeof callback !== 'function') {
    throw new TypeError('callback must be a function');
  }

  // TODO:  Proxy console.{error,log} and process.exit so we can return result
  const command = new Command()
    .arguments('<branch_name>')
    .option('-C <path>', 'run as if started in <path>')
    .option(
      '--git-arg <arg>', 'additional argument to git (can be repeated)',
      collect, [],
    )
    .option('--git-dir <dir>', 'set the path to the repository')
    .option('--git-path <path>', 'set the path to the git binary')
    .option('-i, --ignore-case', 'compare/match branch_name case-insensitively')
    .option('-I, --invert-match', 'inverts/negates comparison')
    // Note:  Commander.js only supports one long option per option call
    // https://github.com/tj/commander.js/issues/430
    .option('--not', 'inverts/negates comparison (same as --invert-match)')
    .option('-q, --quiet', 'suppress warning message if branch differs')
    .option('-r, --regex', 'match branch_name as a regular expression')
    .option('-v, --verbose', 'print a message if the branch matches')
    .version(packageJson.version)
    .parse(args);

  if (command.args.length !== 1) {
    callback(new Error(`Exactly one argument is required.\n${
      command.helpInformation()}`));
    return undefined;
  }

  // -C option is cmd in options Object
  command.cwd = command.C;

  // pluralize --git-arg to cover multiple uses
  command.gitArgs = command.gitArg;

  // treat --not as an alias for --invert-match
  command.invertMatch = command.invertMatch || command.not;

  const expectedBranch = command.args[0];

  let expectedBranchRegExp;
  if (command.regex) {
    try {
      expectedBranchRegExp = new RegExp(
        expectedBranch,
        command.ignoreCase ? 'i' : undefined,
      );
    } catch (errRegExp) {
      // Benefit of avoiding unnecessary API changes outweighs style concerns
      // eslint-disable-next-line unicorn/no-null
      callback(null, {
        code: 2,
        stderr: `Error: Invalid RegExp "${expectedBranch}": ${
          errRegExp}\n`,
      });
      return undefined;
    }
  }

  gitBranchIs.getBranch(command, (err, currentBranch) => {
    if (err) {
      callback(err);
      return;
    }

    let errMsg, isMatch;
    if (expectedBranchRegExp) {
      isMatch = expectedBranchRegExp.test(currentBranch);
      if (command.invertMatch) {
        isMatch = !isMatch;
      }

      if (!isMatch && !command.quiet) {
        errMsg = command.invertMatch
          ? `Current branch "${currentBranch}" matches "${expectedBranch}".\n`
          : `Current branch "${currentBranch}" does not match "${
            expectedBranch}".\n`;
      }
    } else {
      isMatch = currentBranch === expectedBranch
        || (command.ignoreCase
            && currentBranch.toUpperCase() === expectedBranch.toUpperCase());
      if (command.invertMatch) {
        isMatch = !isMatch;
      }

      if (!isMatch && !command.quiet) {
        errMsg = command.invertMatch
          ? `Current branch is "${currentBranch}".\n`
          : `Current branch is "${currentBranch}", not "${expectedBranch}".\n`;
      }
    }

    // Benefit of avoiding unnecessary API changes outweighs style concerns
    // eslint-disable-next-line unicorn/no-null
    callback(null, {
      code: isMatch ? 0 : 1,
      stderr: errMsg && `Error: ${errMsg}`,
      stdout: isMatch && command.verbose
        ? `Current branch is "${currentBranch}".\n`
        : null, // eslint-disable-line unicorn/no-null
    });
  });
  return undefined;
}

module.exports = gitBranchIsCmd;

if (require.main === module) {
  // This file was invoked directly.
  /* eslint-disable no-process-exit */
  gitBranchIsCmd(process.argv, (err, result) => {
    const errOrResult = err || result;
    if (errOrResult.stdout) { process.stdout.write(errOrResult.stdout); }
    if (errOrResult.stderr) { process.stderr.write(errOrResult.stderr); }
    if (err) { process.stderr.write(`${err.name}: ${err.message}\n`); }

    const code = typeof errOrResult.code === 'number' ? errOrResult.code
      : err ? 1 : 0;
    process.exit(code);
  });
}