export async function main()

in packages/angular_devkit/benchmark/src/main.ts [28:225]


export async function main({
  args,
  stdout = process.stdout,
  stderr = process.stderr,
}: MainOptions): Promise<0 | 1> {
  // Show usage of the CLI tool, and exit the process.
  function usage(logger: logging.Logger) {
    logger.info(tags.stripIndent`
    benchmark [options] -- [command to benchmark]

    Collects process stats from running the command.

    Options:
        --help                    Show this message.
        --verbose                 Show more information while running.
        --exit-code               Expected exit code for the command. Default is 0.
        --iterations              Number of iterations to run the benchmark over. Default is 5.
        --retries                 Number of times to retry when process fails. Default is 5.
        --cwd                     Current working directory to run the process in.
        --output-file             File to output benchmark log to.
        --overwrite-output-file   If the output file should be overwritten rather than appended to.
        --prefix                  Logging prefix.
        --watch-matcher           Text to match in stdout to mark an iteration complete.
        --watch-timeout           The maximum time in 'ms' to wait for the text specified in the matcher to be matched. Default is 10000.
        --watch-script            Script to run before each watch iteration.

    Example:
        benchmark --iterations=3 -- node my-script.js
  `);
  }

  interface BenchmarkCliArgv {
    help: boolean;
    verbose: boolean;
    'overwrite-output-file': boolean;
    'exit-code': number;
    iterations: number;
    retries: number;
    'output-file': string | null;
    cwd: string;
    prefix: string;
    'watch-timeout': number;
    'watch-matcher'?: string;
    'watch-script'?: string;
    '--': string[] | null;
  }

  // Parse the command line.
  const argv = minimist(args, {
    boolean: ['help', 'verbose', 'overwrite-output-file'],
    string: ['watch-matcher', 'watch-script'],
    default: {
      'exit-code': 0,
      'iterations': 5,
      'retries': 5,
      'output-file': null,
      'cwd': process.cwd(),
      'prefix': '[benchmark]',
      'watch-timeout': 10000,
    },
    '--': true,
  }) as {} as BenchmarkCliArgv;

  // Create the DevKit Logger used through the CLI.
  const logger = new logging.TransformLogger('benchmark-prefix-logger', (stream) =>
    stream.pipe(
      map((entry) => {
        if (argv['prefix']) {
          entry.message = `${argv['prefix']} ${entry.message}`;
        }

        return entry;
      }),
    ),
  );

  // Create a separate instance to prevent unintended global changes to the color configuration
  // Create function is not defined in the typings. See: https://github.com/doowb/ansi-colors/pull/44
  const colors = (ansiColors as typeof ansiColors & { create: () => typeof ansiColors }).create();

  // Log to console.
  logger.pipe(filter((entry) => entry.level != 'debug' || argv['verbose'])).subscribe((entry) => {
    let color: (s: string) => string = (x) => colors.dim.white(x);
    let output = stdout;
    switch (entry.level) {
      case 'info':
        color = (s) => s;
        break;
      case 'warn':
        color = colors.yellow;
        output = stderr;
        break;
      case 'error':
        color = colors.red;
        output = stderr;
        break;
      case 'fatal':
        color = (x: string) => colors.bold.red(x);
        output = stderr;
        break;
    }

    output.write(color(entry.message) + '\n');
  });

  // Print help.
  if (argv['help']) {
    usage(logger);

    return 0;
  }

  const commandArgv = argv['--'];

  const {
    'watch-timeout': watchTimeout,
    'watch-matcher': watchMatcher,
    'watch-script': watchScript,
    'exit-code': exitCode,
    'output-file': outFile,
    iterations,
    retries,
  } = argv;

  // Exit early if we can't find the command to benchmark.
  if (watchMatcher && !watchScript) {
    logger.fatal(`Cannot use --watch-matcher without specifying --watch-script.`);

    return 1;
  }

  if (!watchMatcher && watchScript) {
    logger.fatal(`Cannot use --watch-script without specifying --watch-matcher.`);

    return 1;
  }

  // Exit early if we can't find the command to benchmark.
  if (!commandArgv || !Array.isArray(argv['--']) || (argv['--'] as Array<string>).length < 1) {
    logger.fatal(`Missing command, see benchmark --help for help.`);

    return 1;
  }

  // Setup file logging.
  if (outFile !== null) {
    if (argv['overwrite-output-file']) {
      writeFileSync(outFile, '');
    }
    logger
      .pipe(filter((entry) => entry.level != 'debug' || argv['verbose']))
      .subscribe((entry) => appendFileSync(outFile, `${entry.message}\n`));
  }

  // Run benchmark on given command, capturing stats and reporting them.
  const cmd = commandArgv[0];
  const cmdArgs = commandArgv.slice(1);
  const command = new Command(cmd, cmdArgs, argv['cwd'], exitCode);
  const captures = [defaultStatsCapture];
  const reporters = [defaultReporter(logger)];

  logger.info(`Benchmarking process over ${iterations} iterations, with up to ${retries} retries.`);
  logger.info(`  ${command.toString()}`);

  try {
    let res$;
    if (watchMatcher && watchScript) {
      res$ = runBenchmarkWatch({
        command,
        captures,
        reporters,
        iterations,
        retries,
        logger,
        watchCommand: new Command('node', [watchScript]),
        watchMatcher,
        watchTimeout,
      });
    } else {
      res$ = runBenchmark({ command, captures, reporters, iterations, retries, logger });
    }

    const res = await res$.pipe(toArray()).toPromise();
    if (res.length === 0) {
      return 1;
    }
  } catch (error) {
    if (error.message) {
      logger.fatal(error.message);
    } else {
      logger.fatal(error);
    }

    return 1;
  }

  return 0;
}