mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-18 20:02:40 +08:00
233 lines
6.8 KiB
JavaScript
233 lines
6.8 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import { logging, tags } from '@angular-devkit/core';
|
|
import { ProcessOutput } from '@angular-devkit/core/node';
|
|
import * as ansiColors from 'ansi-colors';
|
|
import { appendFileSync, writeFileSync } from 'fs';
|
|
import * as minimist from 'minimist';
|
|
import { filter, map, toArray } from 'rxjs/operators';
|
|
import { Command } from '../src/command';
|
|
import { defaultReporter } from '../src/default-reporter';
|
|
import { defaultStatsCapture } from '../src/default-stats-capture';
|
|
import { runBenchmark } from '../src/run-benchmark';
|
|
import { runBenchmarkWatch } from './run-benchmark-watch';
|
|
|
|
|
|
export interface MainOptions {
|
|
args: string[];
|
|
stdout?: ProcessOutput;
|
|
stderr?: ProcessOutput;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (require.main === module) {
|
|
const args = process.argv.slice(2);
|
|
main({ args })
|
|
.then(exitCode => process.exitCode = exitCode)
|
|
.catch(e => { throw (e); });
|
|
}
|