current commit
260810.7 · feat: report GraphQL error details
import { spawn, spawnSync } from 'node:child_process';
import { fileURLToPath } from 'node:url';

import {
  isDebugVerbosity,
  debuglog
} from './verbosity.js';

export const IGNORE = '__ignore__';

export function reportError(error, write = console.error) {
  write(error?.stack ?? error);
  if (error?.data !== undefined) {
    write(JSON.stringify(error.data, null, 2));
  }
}

function runAsyncBase(cmd, args, {
  ignoreFailure = true,
  spawnArgs,
  input,
  captureStderr = false,
  captureStdout = false
} = {}) {
  if (cmd instanceof URL) {
    cmd = fileURLToPath(cmd);
  }
  let stdio = 'inherit';
  if (captureStderr || captureStdout || input != null) {
    stdio = [
      input == null ? 'inherit' : 'pipe',
      captureStdout ? 'pipe' : 'inherit',
      captureStderr ? 'pipe' : 'inherit'
    ];
  }
  return new Promise((resolve, reject) => {
    const opt = Object.assign({
      cwd: process.cwd(),
      stdio
    }, spawnArgs);
    if (isDebugVerbosity()) {
      debuglog('[Spawn]', `${cmd} ${(args || []).join(' ')}`, opt);
    }
    const child = spawn(cmd, args, opt);
    let stderr;
    if (!captureStdout && captureStderr) {
      stderr = '';
      child.stderr.setEncoding('utf8');
      child.stderr.on('data', (chunk) => { stderr += chunk; });
    }
    let stdout;
    if (captureStdout) {
      stdout = '';
      child.stdout.setEncoding('utf8');
      child.stdout.on('data', (chunk) => { stdout += chunk; });
    }
    child.on('error', reject);
    child.on('close', (code) => {
      if (code !== 0) {
        if (ignoreFailure) {
          return reject(new Error(IGNORE));
        }
        const err = new Error(`${cmd} ${args} failed: ${code}`);
        err.code = code;
        err.messageOnly = true;
        return reject(err);
      }
      if (captureStdout === 'lines') {
        stdout = stdout.split(/\r?\n/g);
        if (stdout[stdout.length - 1] === '') stdout.pop();
      }
      return resolve(stdout ?? stderr);
    });
    if (input != null) child.stdin.end(input);
  });
}

export function forceRunAsync(cmd, args, options) {
  return runAsyncBase(cmd, args, options).catch((error) => {
    if (error.message !== IGNORE) {
      if (!error.messageOnly) {
        reportError(error);
      }
      throw error;
    }
  });
}

export function runPromise(promise) {
  return promise.catch((error) => {
    if (error.message !== IGNORE) {
      reportError(error);
    }
    exit();
  });
}

export function runAsync(cmd, args, options) {
  return runPromise(runAsyncBase(cmd, args, options));
}

export function runSync(cmd, args, options) {
  if (cmd instanceof URL) {
    cmd = fileURLToPath(cmd);
  }
  const opt = Object.assign({
    cwd: process.cwd()
  }, options);
  if (isDebugVerbosity()) {
    debuglog('[SpawnSync]', `${cmd} ${(args || []).join(' ')}`, opt);
  }
  const child = spawnSync(cmd, args, opt);
  if (child.error) {
    throw child.error;
  } else if (child.status !== 0) {
    throw new Error(`${cmd} ${args} failed with stderr: ` +
                    child.stderr.toString());
  } else {
    return child.stdout.toString();
  }
}

export function exit() {
  process.exit(1);
}