Custom ZSH Prompt Node Script

Here is the script I use to define a custom ZSH prompt

import childProcess from 'child_process';

const BRAND = '🧙‍';

/**
 * SETUP
 *   Add the following to ~/.zshrc file
 *     setopt PROMPT_SUBST
 *     PROMPT='$(node {SCRIPT_PATH} $?)'
 * 
 * LEGEND
 *   The color of time indicates the status of the last command
 *   '+' before the git branch indicates that you are ahead of origin
 *   '-' before the git branch indicates that you are behind origin
 *   '±' before the git branch indicates that you are ahead AND behind origin
 *   '?' after the git branch indicates that there are unstaged changes
 *   '+' after the git branch indicates that there are only staged changes
 *   The color of the git branch is inferred from the symbols and does not add any additional context
 */

async function exec(cmd) {
  return await new Promise((resolve) => {
    childProcess.exec(cmd, (error, stdout) => {
      resolve({
        error: !!error,
        output: stdout.replace(/\n$/, ''),
      });
    });
  });
}

async function getGitState() {
  const { error: gitError } = await exec('git rev-parse --git-dir');

  if (gitError) return null;

  const { output: branch } = await exec('git rev-parse --abbrev-ref HEAD');
  const { output: aheadLog } = await exec(`git log origin/${branch}..HEAD`)
  const { output: behindLog } = await exec(`git log HEAD..origin/${branch}`)
  const { output: status } = await exec('git status --porcelain');

  return {
    branch,
    ahead: !!aheadLog.match(/^commit /m),
    behind: !!behindLog.match(/^commit /m),
    untracked: !!status.match(/^\?\? /m),
    unstaged: !!status.match(/^([ MARC][MD]) /m),
    staged: !!status.match(/^(D[ M]|[MARC][ MD]) /m),
    unmerged: !!status.match(/^(A[AU]|D[DU]|U[ADU]) /m),
  };
}

const color = (color, str) => `%F{${color}}${str}%f`;
const green = (str) => color('green', str);
const red = (str) => color('red', str);
const yellow = (str) => color('yellow', str);
const cyan = (str) => color('cyan', str);
const magenta = (str) => color('magenta', str);

const cmdStatus = parseInt(process.argv[2] || '0');

const prompt = [];
const time = new Date().toLocaleTimeString();
const gitState = await getGitState();

prompt.push('\n');

if (BRAND) {
  prompt.push(`${BRAND} `);
}

prompt.push(cmdStatus === 0 ? green(time) : red(time));
prompt.push(' ∙ ');
prompt.push(cyan(process.cwd()));

if (gitState) {
  const gitSegment = [];

  gitSegment.push('(');

  if (gitState.ahead && gitState.behind) {
    gitSegment.push('±');
  } else if (gitState.ahead) {
    gitSegment.push('+');
  } else if (gitState.behind) {
    gitSegment.push('-');
  }

  if (gitState.behind || gitState.untracked || gitState.unstaged || gitState. unmerged) {
    gitSegment.push(red(gitState.branch));
  } else if (gitState.ahead || gitState.staged) {
    gitSegment.push(yellow(gitState.branch));
  } else {
    gitSegment.push(green(gitState.branch));
  }


  if (gitState.untracked || gitState.unstaged || gitState. unmerged) {
    gitSegment.push('?');
  } else if (gitState.staged) {
    gitSegment.push('+');
  }

  gitSegment.push(')');

  prompt.push(' ∙ ');
  prompt.push(gitSegment.join(''));
}

prompt.push('\n');
prompt.push(magenta('» '));

console.log(prompt.join(''));