From 44f5901f896bc823ef33942dca3fd62b47975fca Mon Sep 17 00:00:00 2001 From: David Blackman Date: Wed, 8 Jul 2020 23:22:44 -0400 Subject: [PATCH 1/5] whole lots of features, start of html output skeleton --- lib/api.ts | 4 +- lib/apiEnv.ts | 16 +- lib/compare-formatters/compare-formatter.ts | 7 + lib/compare-types.ts | 9 ++ lib/compare.ts | 170 ++++++++++++++++---- lib/config.ts | 9 +- lib/logger.ts | 2 +- lib/run-query.ts | 17 +- package-lock.json | 8 + package.json | 2 +- tsconfig.json | 4 + 11 files changed, 199 insertions(+), 49 deletions(-) create mode 100644 lib/compare-formatters/compare-formatter.ts create mode 100644 lib/compare-types.ts create mode 100644 tsconfig.json diff --git a/lib/api.ts b/lib/api.ts index dc2e41a..66cf361 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -7,7 +7,7 @@ import { argvToApiEnv, getApiEnvCommandLineOptions, ApiEnv, fixApiEnvKey, } from './apiEnv'; import runQuery from './run-query'; -import config from './config'; +import { config } from './config'; const makeUsageString = (toolName: string) => `This tool has a lot of options, here are some examples: @@ -61,7 +61,7 @@ let apiEnv: ApiEnv; let params: Record = {}; if (argv._.length === 1 && argv._[0].startsWith('http')) { - // The user just specified a radar url, they probably just want to run it with the + // The user specified a full url, they probably just want to run it with the // right api key. So infer all the params from the passed url const url = new URL(argv._[0]); apiEnv = { diff --git a/lib/apiEnv.ts b/lib/apiEnv.ts index 97996a5..4348812 100644 --- a/lib/apiEnv.ts +++ b/lib/apiEnv.ts @@ -127,7 +127,21 @@ export function argvToApiEnv(argv: any): ApiEnv { apiEnv.protocol = url.protocol.replace(':', ''); } - fixApiEnvKey(apiEnv); + apiEnv.protocol = apiEnv.protocol || 'http'; + + if (config.authStyle) { + fixApiEnvKey(apiEnv); + } return apiEnv as ApiEnv; } + +/** + * @param apiEnv + */ +export function apiEnvToApiSh(apiEnv: ApiEnv): string { + if (apiEnv.keyEnv) { + return `./api.sh --keyEnv ${apiEnv.keyEnv}`; + } + return './api.sh'; +} diff --git a/lib/compare-formatters/compare-formatter.ts b/lib/compare-formatters/compare-formatter.ts new file mode 100644 index 0000000..6c04e1f --- /dev/null +++ b/lib/compare-formatters/compare-formatter.ts @@ -0,0 +1,7 @@ +import { Change } from '../compare-types'; + +export interface CompareFormatter { + logChange(change: Change); + queryRan(numQueriesRun: number); + finished(); +} diff --git a/lib/compare-types.ts b/lib/compare-types.ts new file mode 100644 index 0000000..6269b8e --- /dev/null +++ b/lib/compare-types.ts @@ -0,0 +1,9 @@ +import { AxiosResponse } from 'axios'; +import * as queryString from 'query-string'; + +export type Change = { + params: queryString.ParsedQuery; + delta: unknown; + oldResponse: AxiosResponse; + newResponse: AxiosResponse; +}; diff --git a/lib/compare.ts b/lib/compare.ts index 1bf097a..4895ee1 100644 --- a/lib/compare.ts +++ b/lib/compare.ts @@ -4,26 +4,31 @@ import * as fs from 'fs'; import * as _ from 'lodash'; import * as queryString from 'query-string'; import * as Bluebird from 'bluebird'; -import c from 'ansi-colors'; -import parseCsvSync from 'csv-parse/lib/sync'; -import jsondiffpatch from 'jsondiffpatch'; +import * as chalk from 'chalk'; +import * as parseCsvSync from 'csv-parse/lib/sync'; +import * as jsondiffpatch from 'jsondiffpatch'; import { getApiEnvCommandLineOptions, ApiEnv, argvToApiEnv } from './apiEnv'; import runQuery from './run-query'; -import { globalCommandLineOptions } from './cli-utils'; +import { globalCommandLineOptions, failedExit } from './cli-utils'; +import { Change } from './compare-types'; const OLD_KEY = 'old'; const NEW_KEY = 'new'; +type OutputMode = 'html' | 'text'; + type ParsedArgs = { input_params?: string; input_csv?: string; input_queries?: string; endpoint: string; - extra_params: string, + extra_params: string; method: string; - ignored_fields: string[], - concurrency: number, - unchanged: boolean, + ignored_fields: string[]; + concurrency: number; + unchanged: boolean; + key_map: string[]; + output_mode: OutputMode; }; /** @@ -88,6 +93,30 @@ function parseArgv() { 'field names to ignore when diffing responses. geometry latitude longitude are common for geocode compare runs', }); + yargs.option('concurrency', { + type: 'number', + default: 10, + description: 'concurrency of api queries per host to run', + }); + + yargs.option('unchanged', { + type: 'boolean', + default: false, + description: 'whether or not to print all queries, even unchanged ones', + }); + + yargs.option('key_map', { + type: 'array', + default: [], + description: + 'a mapping of csv columns to parameter names in the format csv_header1=param1 csv_header2=param2, if all numbers, are assumed to be csv column numbers', + }); + + yargs.option('output_mode', { + choices: ['html', 'text'], + description: 'what kind of output to generate', + }); + yargs.group(['input_params', 'endpoint', 'input_queries', 'input_csv'], 'Query options:'); yargs.group(oldParams, 'Options for "old" server to compare:'); yargs.group(newParams, 'Options for "new" server to compare:'); @@ -116,7 +145,7 @@ function generateQueries(argv: ParsedArgs) { const hasInputFile = argv.input_params || argv.input_csv; if ((argv.endpoint && !hasInputFile) || (!argv.endpoint && hasInputFile)) { console.error( - c.red( + chalk.red( 'Must specify both --endpoint and (--input_params or --input_csv) , perhaps you wanted --input_queries?', ), ); @@ -136,15 +165,41 @@ function generateQueries(argv: ParsedArgs) { return _.flatMap(argv.input_csv, (input_csv_file: string) => { const fileLines = fs.readFileSync(input_csv_file).toString(); + const keyMap: Record = {}; + + argv.key_map.forEach((str: string) => { + const parts = str.split('='); + if (parts.length !== 2) { + failedExit(`invalid keymap ${str}, must be of form csv_column_name=param_name`); + } + const [csvHeader, paramName] = parts; + keyMap[csvHeader] = paramName; + }); + const hasNumericKeyMap = _.every(_.keys(keyMap), (k) => /^\d+$/.test(k)); + const records = parseCsvSync(fileLines, { - columns: true, + columns: !hasNumericKeyMap, skip_empty_lines: true, }); return records.map((record) => { - // eslint-disable-next-line no-param-reassign - delete record['']; - return `${endpoint}?${queryString.stringify(record)}`; + const modifiedRecord = record; + + delete modifiedRecord['']; + + _.forEach(keyMap, (paramName, csvHeader) => { + if (!_.includes(_.keys(modifiedRecord), csvHeader)) { + failedExit( + `CSV input is missing specified header ${csvHeader}, sample row: ${JSON.stringify( + record, + )}`, + ); + } + modifiedRecord[paramName] = modifiedRecord[csvHeader]; + delete modifiedRecord[csvHeader]; + }); + + return `${endpoint}?${queryString.stringify(modifiedRecord)}`; }); }); } @@ -175,10 +230,11 @@ async function compareQuery({ oldApiEnv: ApiEnv; newApiEnv: ApiEnv; query: string; - argv: ParsedArgs -}) { + argv: ParsedArgs; +}): Promise { const [endpoint, paramsString] = query.split('?'); const params = queryString.parse(`${paramsString}&${argv.extra_params}`); + delete params.undefined; const oldResponse = await runQuery(oldApiEnv, { endpoint, params, @@ -198,21 +254,69 @@ async function compareQuery({ const delta = differ.diff(oldResponse.data, newResponse.data); - const outputLines = `${JSON.stringify(params)} - ./api.sh --keyEnv ${oldApiEnv.keyEnv} ${oldResponse.url} - ./api.sh api --keyEnv ${newApiEnv.keyEnv} ${newResponse.url}`; + if (!delta && !argv.unchanged) { + return undefined; + } + + return { + params, + delta, + oldResponse, + newResponse, + }; +} + +const changes: Change[] = []; +/** + * @param change + */ +function outputChangeHtml(change: Change) { + changes.push(change); +} - if (!delta) { - if (argv.unchanged) { - console.log(c.cyan(`Unchanged: ${outputLines}`)); +/** + * @param oldApiEnv + * @param newApiEnv + + * @param change + */ +function outputChangeText(oldApiEnv: ApiEnv, newApiEnv: ApiEnv, change: Change) { + const apiEnvToApiSh = (apiEnv: ApiEnv): string => { + if (apiEnv.keyEnv) { + return `./api.sh --keyEnv ${apiEnv.keyEnv}`; } - return { didChange: false }; + return './api.sh'; + }; + const outputLines = `${JSON.stringify(change.params)} + ${apiEnvToApiSh(oldApiEnv)} ${change.oldResponse.request.res.responseUrl} + ${apiEnvToApiSh(newApiEnv)} ${change.newResponse.request.res.responseUrl}`; + + if (!change.delta) { + console.log(chalk.cyan(`Unchanged: ${outputLines}`)); + } else { + console.log(chalk.yellow(`Changed: ${outputLines}`)); } - console.log(c.yellow(`Changed: ${outputLines}`)); - (jsondiffpatch.console as any).log(delta); + (jsondiffpatch.console as any).log(change.delta); +} - return { didChange: true, specificChange: undefined }; +/** + * @param output_mode + * @param oldApiEnv + * @param newApiEnv + * @param change + */ +function outputChange( + output_mode: OutputMode, + oldApiEnv: ApiEnv, + newApiEnv: ApiEnv, + change: Change, +) { + if (output_mode === 'html') { + outputChangeHtml(change); + } else { + outputChangeText(oldApiEnv, newApiEnv, change); + } } /** @@ -231,7 +335,7 @@ async function compareQueries({ oldApiEnv: ApiEnv; newApiEnv: ApiEnv; queries: string[]; - argv: ParsedArgs + argv: ParsedArgs; }) { let numQueriesRun = 0; let numQueriesChanged = 0; @@ -243,12 +347,16 @@ async function compareQueries({ console.log(`IN PROGRESS. ${numQueriesRun}/${queries.length} run`); } numQueriesRun += 1; - const { didChange } = await compareQuery({ - oldApiEnv, newApiEnv, query, argv, + const change = await compareQuery({ + oldApiEnv, + newApiEnv, + query, + argv, }); - if (didChange) { + if (change) { numQueriesChanged += 1; } + outputChange(argv.output_mode, oldApiEnv, newApiEnv, change); }, { concurrency: argv.concurrency }, ); @@ -263,7 +371,9 @@ const newApiEnv = argvToApiEnv(argv[NEW_KEY]); const queries = generateQueries(argv); if (!queries || queries.length === 0) { - console.error(c.red('No queries found')); + failedExit( + 'No queries found, did you specify one of: --input_params, --input_csv, --input_queries?', + ); } compareQueries({ diff --git a/lib/config.ts b/lib/config.ts index 9e8e545..e406f66 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -1,7 +1,8 @@ import * as fs from 'fs'; -import hjson from 'hjson'; +import * as hjson from 'hjson'; -const CONFIG_FILE = 'config.hjson'; +const noConfigFile = process.env.CONFIG_FILE === ''; +const CONFIG_FILE = process.env.CONFIG_FILE || 'config.hjson'; export type ConfigHostEntry = { host: string; @@ -18,8 +19,8 @@ export type Config = { hosts: Record; }; -if (!fs.existsSync(CONFIG_FILE)) { +if (!noConfigFile && !fs.existsSync(CONFIG_FILE)) { throw new Error(`${CONFIG_FILE} missing`); } -const config = hjson.parse(fs.readFileSync(CONFIG_FILE).toString()) as Config; +const config = hjson.parse(noConfigFile ? '' : fs.readFileSync(CONFIG_FILE).toString()) as Config; export default config; diff --git a/lib/logger.ts b/lib/logger.ts index 8566885..5633efc 100644 --- a/lib/logger.ts +++ b/lib/logger.ts @@ -1,4 +1,4 @@ -import winston from 'winston'; +import * as winston from 'winston'; const logger = winston.createLogger({ level: 'info', diff --git a/lib/run-query.ts b/lib/run-query.ts index 089c087..f581d4b 100644 --- a/lib/run-query.ts +++ b/lib/run-query.ts @@ -1,8 +1,8 @@ +/* eslint-disable no-console */ /* eslint-disable no-param-reassign */ -import axios from 'axios'; +import axios, { Method, AxiosResponse } from 'axios'; import { ApiEnv } from './apiEnv'; import config from './config'; -import logger from './logger'; /** * @param apiEnv @@ -10,7 +10,6 @@ import logger from './logger'; * @param root0.params * @param root0.method * @param root0.endpoint - * @param root0.verbose */ export default async function runQuery( apiEnv: ApiEnv, @@ -18,14 +17,12 @@ export default async function runQuery( params, endpoint, method, - verbose, }: { - params: Record; + params: Record; method: string; endpoint: string; - verbose?: boolean; }, -) { +): Promise { // v1/xxxx ... maybe someone was lazy and didn't start with an opening slash if (endpoint[0] !== '/' && !endpoint.startsWith('http:')) { endpoint = `/${endpoint}`; @@ -48,9 +45,9 @@ export default async function runQuery( url = `${apiEnv.protocol}://${url}`; } - logger.info(`Fetching ${url}`); + // logger.info(`Fetching ${url}`); - const headers: any = { + const headers: Record = { 'User-Agent': 'radar-compare-tool/unknown', }; @@ -65,7 +62,7 @@ export default async function runQuery( headers, params: method === 'GET' ? params : undefined, data: method === 'POST' ? params : undefined, - method: method.toLowerCase() as any, + method: method.toLowerCase() as Method, }); return response; } catch (error) { diff --git a/package-lock.json b/package-lock.json index 1d3bd2f..eedeebf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,6 +62,14 @@ "kuler": "^2.0.0" } }, + "@types/chalk": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/chalk/-/chalk-2.2.0.tgz", + "integrity": "sha512-1zzPV9FDe1I/WHhRkf9SNgqtRJWZqrBWgu7JGveuHmmyR9CnAPCie2N/x+iHrgnpYBIcCJWHBoMRv2TRWktsvw==", + "requires": { + "chalk": "*" + } + }, "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", diff --git a/package.json b/package.json index 775a0f4..a71be27 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,10 @@ }, "homepage": "https://github.com/radarlabs/compare#readme", "dependencies": { + "@types/chalk": "^2.2.0", "@types/lodash": "^4.14.157", "@types/node": "^14.0.13", "@types/yargs": "^15.0.5", - "ansi-colors": "^4.1.1", "async-csv": "^2.1.3", "axios": "^0.19.2", "bluebird": "^3.7.2", diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..408c3c0 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,4 @@ +{ + "compilerOptions": { + } +} \ No newline at end of file From eb2275e823baa9358221b8d9a8deb2454e02c0d4 Mon Sep 17 00:00:00 2001 From: David Blackman Date: Thu, 9 Jul 2020 11:46:03 -0400 Subject: [PATCH 2/5] json mode --- .gitignore | 3 +- compare.sh | 2 +- lib/api.ts | 4 +- lib/apiEnv.ts | 10 - lib/compare-formatters/compare-formatter.ts | 7 - lib/compare-types.ts | 9 - lib/compare.ts | 384 -------------------- lib/compare/TODO.md | 9 + lib/compare/argv.ts | 131 +++++++ lib/compare/change.ts | 26 ++ lib/compare/compare.ts | 212 +++++++++++ lib/compare/formatters/compare-formatter.ts | 35 ++ lib/compare/formatters/console-formatter.ts | 46 +++ lib/compare/formatters/get-formatter.ts | 24 ++ lib/compare/formatters/json-formatter.ts | 30 ++ 15 files changed, 518 insertions(+), 414 deletions(-) delete mode 100644 lib/compare-formatters/compare-formatter.ts delete mode 100644 lib/compare-types.ts delete mode 100644 lib/compare.ts create mode 100644 lib/compare/TODO.md create mode 100644 lib/compare/argv.ts create mode 100644 lib/compare/change.ts create mode 100644 lib/compare/compare.ts create mode 100644 lib/compare/formatters/compare-formatter.ts create mode 100644 lib/compare/formatters/console-formatter.ts create mode 100644 lib/compare/formatters/get-formatter.ts create mode 100644 lib/compare/formatters/json-formatter.ts diff --git a/.gitignore b/.gitignore index eacee98..cc5cac6 100644 --- a/.gitignore +++ b/.gitignore @@ -110,4 +110,5 @@ dist *.log *.csv *.tsv -config.hjson \ No newline at end of file +config.hjson +*.json diff --git a/compare.sh b/compare.sh index 48c674c..454082d 100755 --- a/compare.sh +++ b/compare.sh @@ -1,3 +1,3 @@ #!/bin/sh -./node_modules/.bin/ts-node lib/compare.ts $@ +./node_modules/.bin/ts-node lib/compare/compare.ts $@ diff --git a/lib/api.ts b/lib/api.ts index 66cf361..4976d9b 100644 --- a/lib/api.ts +++ b/lib/api.ts @@ -7,7 +7,7 @@ import { argvToApiEnv, getApiEnvCommandLineOptions, ApiEnv, fixApiEnvKey, } from './apiEnv'; import runQuery from './run-query'; -import { config } from './config'; +import config from './config'; const makeUsageString = (toolName: string) => `This tool has a lot of options, here are some examples: @@ -97,5 +97,5 @@ if (argv._.length === 1 && argv._[0].startsWith('http')) { } runQuery(apiEnv, { - params, method: argv.method, endpoint, verbose: true, + params, method: argv.method, endpoint, }).then(({ data }) => console.dir(data, { depth: null, colors: chalk.level > 0 })); diff --git a/lib/apiEnv.ts b/lib/apiEnv.ts index 4348812..d0ae230 100644 --- a/lib/apiEnv.ts +++ b/lib/apiEnv.ts @@ -135,13 +135,3 @@ export function argvToApiEnv(argv: any): ApiEnv { return apiEnv as ApiEnv; } - -/** - * @param apiEnv - */ -export function apiEnvToApiSh(apiEnv: ApiEnv): string { - if (apiEnv.keyEnv) { - return `./api.sh --keyEnv ${apiEnv.keyEnv}`; - } - return './api.sh'; -} diff --git a/lib/compare-formatters/compare-formatter.ts b/lib/compare-formatters/compare-formatter.ts deleted file mode 100644 index 6c04e1f..0000000 --- a/lib/compare-formatters/compare-formatter.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Change } from '../compare-types'; - -export interface CompareFormatter { - logChange(change: Change); - queryRan(numQueriesRun: number); - finished(); -} diff --git a/lib/compare-types.ts b/lib/compare-types.ts deleted file mode 100644 index 6269b8e..0000000 --- a/lib/compare-types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { AxiosResponse } from 'axios'; -import * as queryString from 'query-string'; - -export type Change = { - params: queryString.ParsedQuery; - delta: unknown; - oldResponse: AxiosResponse; - newResponse: AxiosResponse; -}; diff --git a/lib/compare.ts b/lib/compare.ts deleted file mode 100644 index 4895ee1..0000000 --- a/lib/compare.ts +++ /dev/null @@ -1,384 +0,0 @@ -/* eslint-disable no-console */ -/* eslint-disable camelcase */ -import * as fs from 'fs'; -import * as _ from 'lodash'; -import * as queryString from 'query-string'; -import * as Bluebird from 'bluebird'; -import * as chalk from 'chalk'; -import * as parseCsvSync from 'csv-parse/lib/sync'; -import * as jsondiffpatch from 'jsondiffpatch'; -import { getApiEnvCommandLineOptions, ApiEnv, argvToApiEnv } from './apiEnv'; -import runQuery from './run-query'; -import { globalCommandLineOptions, failedExit } from './cli-utils'; -import { Change } from './compare-types'; - -const OLD_KEY = 'old'; -const NEW_KEY = 'new'; - -type OutputMode = 'html' | 'text'; - -type ParsedArgs = { - input_params?: string; - input_csv?: string; - input_queries?: string; - endpoint: string; - extra_params: string; - method: string; - ignored_fields: string[]; - concurrency: number; - unchanged: boolean; - key_map: string[]; - output_mode: OutputMode; -}; - -/** - * - */ -function parseArgv() { - // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require - const yargs = require('yargs').strict(); - - _.forEach(globalCommandLineOptions, (val, key) => { - yargs.option(key, val); - }); - - yargs.option(OLD_KEY); - yargs.hide(OLD_KEY); - yargs.option(NEW_KEY); - yargs.hide(NEW_KEY); - - const oldParams = []; - const newParams = []; - _.forEach(getApiEnvCommandLineOptions(), (val, key) => { - yargs.option(`${OLD_KEY}.${key}`, { - ...val, - alias: val.alias ? `${OLD_KEY}.${val.alias}` : null, - }); - oldParams.push(`${OLD_KEY}.${key}`); - yargs.option(`${NEW_KEY}.${key}`, { - ...val, - alias: val.alias ? `${NEW_KEY}.${val.alias}` : null, - }); - newParams.push(`${NEW_KEY}.${key}`); - }); - - yargs.option('input_params', { - type: 'array', - description: 'A file containing url encoded query params, requires --endpoint', - }); - - yargs.option('extra_params', { - type: 'string', - description: - 'Extra static parameters that will be added to each query, maybe something like limit=2 to make diffs less noisy', - }); - - yargs.option('input_csv', { - type: 'array', - description: 'A file containingquery params in a csv, first line is , requires --endpoint', - }); - - yargs.option('endpoint', { - description: 'Endpoint to query using query param strings from --input_params', - }); - - yargs.option('input_queries', { - description: 'A file containing endpoints + queries, one per line', - }); - - yargs.option('ignored_fields', { - type: 'array', - default: [], - description: - 'field names to ignore when diffing responses. geometry latitude longitude are common for geocode compare runs', - }); - - yargs.option('concurrency', { - type: 'number', - default: 10, - description: 'concurrency of api queries per host to run', - }); - - yargs.option('unchanged', { - type: 'boolean', - default: false, - description: 'whether or not to print all queries, even unchanged ones', - }); - - yargs.option('key_map', { - type: 'array', - default: [], - description: - 'a mapping of csv columns to parameter names in the format csv_header1=param1 csv_header2=param2, if all numbers, are assumed to be csv column numbers', - }); - - yargs.option('output_mode', { - choices: ['html', 'text'], - description: 'what kind of output to generate', - }); - - yargs.group(['input_params', 'endpoint', 'input_queries', 'input_csv'], 'Query options:'); - yargs.group(oldParams, 'Options for "old" server to compare:'); - yargs.group(newParams, 'Options for "new" server to compare:'); - yargs.implies('input_csv', 'endpoint'); - yargs.implies('input_params', 'endpoint'); - - yargs.usage(`This tool takes in a set of queries to compare against two radar api servers. -It has a bunch of options, here are some examples: - -./run.sh compare --old.prod --new.local --endpoint /search/autocomplete --input_params input.txt - Compares /search/autocomplete on prod vs local, using query string in input.txt - -./run.sh compare --old.prod --new.local --new.key_env=staging --endpoint /search/autocomplete --input_params input.txt - Same, but looks for a staging key in the env var STAGING_TEST_RADAR_API_KEY in the env or in - -There are other ways to configure old and new, look in the help for more. These options are the same as to ./run.sh api, just with new & old prepended - `); - - return yargs.argv; -} - -/** - * @param argv - */ -function generateQueries(argv: ParsedArgs) { - const hasInputFile = argv.input_params || argv.input_csv; - if ((argv.endpoint && !hasInputFile) || (!argv.endpoint && hasInputFile)) { - console.error( - chalk.red( - 'Must specify both --endpoint and (--input_params or --input_csv) , perhaps you wanted --input_queries?', - ), - ); - } - - if (argv.endpoint && argv.input_params) { - const { endpoint } = argv; - return _.flatMap(argv.input_params, (input_param_file: string) => fs - .readFileSync(input_param_file) - .toString() - .split('\n') - .filter((line) => !!line) - .map((line) => `${endpoint}?${line}`)); - } - if (argv.endpoint && argv.input_csv) { - const { endpoint } = argv; - return _.flatMap(argv.input_csv, (input_csv_file: string) => { - const fileLines = fs.readFileSync(input_csv_file).toString(); - - const keyMap: Record = {}; - - argv.key_map.forEach((str: string) => { - const parts = str.split('='); - if (parts.length !== 2) { - failedExit(`invalid keymap ${str}, must be of form csv_column_name=param_name`); - } - const [csvHeader, paramName] = parts; - keyMap[csvHeader] = paramName; - }); - const hasNumericKeyMap = _.every(_.keys(keyMap), (k) => /^\d+$/.test(k)); - - const records = parseCsvSync(fileLines, { - columns: !hasNumericKeyMap, - skip_empty_lines: true, - }); - - return records.map((record) => { - const modifiedRecord = record; - - delete modifiedRecord['']; - - _.forEach(keyMap, (paramName, csvHeader) => { - if (!_.includes(_.keys(modifiedRecord), csvHeader)) { - failedExit( - `CSV input is missing specified header ${csvHeader}, sample row: ${JSON.stringify( - record, - )}`, - ); - } - modifiedRecord[paramName] = modifiedRecord[csvHeader]; - delete modifiedRecord[csvHeader]; - }); - - return `${endpoint}?${queryString.stringify(modifiedRecord)}`; - }); - }); - } - if (argv.input_queries) { - return _.flatMap(argv.input_queries, (input_queries_file: string) => fs - .readFileSync(input_queries_file) - .toString() - .split('\n') - .filter((line) => !!line)); - } - - return []; -} - -/** - * @param root0 - * @param root0.oldApiEnv - * @param root0.newApiEnv - * @param root0.query - * @param root0.argv - */ -async function compareQuery({ - oldApiEnv, - newApiEnv, - query, - argv, -}: { - oldApiEnv: ApiEnv; - newApiEnv: ApiEnv; - query: string; - argv: ParsedArgs; -}): Promise { - const [endpoint, paramsString] = query.split('?'); - const params = queryString.parse(`${paramsString}&${argv.extra_params}`); - delete params.undefined; - const oldResponse = await runQuery(oldApiEnv, { - endpoint, - params, - method: argv.method, - }); - const newResponse = await runQuery(newApiEnv, { - endpoint, - params, - method: argv.method, - }); - - const differ = jsondiffpatch.create({ - propertyFilter(name, _context) { - return !['buildInfo', 'debug', ...(argv.ignored_fields || [])].includes(name); - }, - }); - - const delta = differ.diff(oldResponse.data, newResponse.data); - - if (!delta && !argv.unchanged) { - return undefined; - } - - return { - params, - delta, - oldResponse, - newResponse, - }; -} - -const changes: Change[] = []; -/** - * @param change - */ -function outputChangeHtml(change: Change) { - changes.push(change); -} - -/** - * @param oldApiEnv - * @param newApiEnv - - * @param change - */ -function outputChangeText(oldApiEnv: ApiEnv, newApiEnv: ApiEnv, change: Change) { - const apiEnvToApiSh = (apiEnv: ApiEnv): string => { - if (apiEnv.keyEnv) { - return `./api.sh --keyEnv ${apiEnv.keyEnv}`; - } - return './api.sh'; - }; - const outputLines = `${JSON.stringify(change.params)} - ${apiEnvToApiSh(oldApiEnv)} ${change.oldResponse.request.res.responseUrl} - ${apiEnvToApiSh(newApiEnv)} ${change.newResponse.request.res.responseUrl}`; - - if (!change.delta) { - console.log(chalk.cyan(`Unchanged: ${outputLines}`)); - } else { - console.log(chalk.yellow(`Changed: ${outputLines}`)); - } - - (jsondiffpatch.console as any).log(change.delta); -} - -/** - * @param output_mode - * @param oldApiEnv - * @param newApiEnv - * @param change - */ -function outputChange( - output_mode: OutputMode, - oldApiEnv: ApiEnv, - newApiEnv: ApiEnv, - change: Change, -) { - if (output_mode === 'html') { - outputChangeHtml(change); - } else { - outputChangeText(oldApiEnv, newApiEnv, change); - } -} - -/** - * @param root0 - * @param root0.oldApiEnv - * @param root0.newApiEnv - * @param root0.queries - * @param root0.argv - */ -async function compareQueries({ - oldApiEnv, - newApiEnv, - queries, - argv, -}: { - oldApiEnv: ApiEnv; - newApiEnv: ApiEnv; - queries: string[]; - argv: ParsedArgs; -}) { - let numQueriesRun = 0; - let numQueriesChanged = 0; - - await Bluebird.map( - queries, - async (query: string) => { - if (numQueriesRun % 10 === 0) { - console.log(`IN PROGRESS. ${numQueriesRun}/${queries.length} run`); - } - numQueriesRun += 1; - const change = await compareQuery({ - oldApiEnv, - newApiEnv, - query, - argv, - }); - if (change) { - numQueriesChanged += 1; - } - outputChange(argv.output_mode, oldApiEnv, newApiEnv, change); - }, - { concurrency: argv.concurrency }, - ); - console.log(`DONE. ${numQueriesChanged}/${numQueriesRun} changed`); -} - -const argv = parseArgv() as ParsedArgs; - -const oldApiEnv = argvToApiEnv(argv[OLD_KEY]); -const newApiEnv = argvToApiEnv(argv[NEW_KEY]); - -const queries = generateQueries(argv); - -if (!queries || queries.length === 0) { - failedExit( - 'No queries found, did you specify one of: --input_params, --input_csv, --input_queries?', - ); -} - -compareQueries({ - oldApiEnv, - newApiEnv, - queries, - argv, -}); diff --git a/lib/compare/TODO.md b/lib/compare/TODO.md new file mode 100644 index 0000000..74ee5fb --- /dev/null +++ b/lib/compare/TODO.md @@ -0,0 +1,9 @@ +- html viewer mode +- comparison voting in html viewer mode +- check that api is still working +- think one last time about the config file +- write a better README + + +- save gold set mode +- compare to golden set \ No newline at end of file diff --git a/lib/compare/argv.ts b/lib/compare/argv.ts new file mode 100644 index 0000000..8dd5736 --- /dev/null +++ b/lib/compare/argv.ts @@ -0,0 +1,131 @@ +/* eslint-disable camelcase */ +import * as _ from 'lodash'; +import { getApiEnvCommandLineOptions } from '../apiEnv'; +import { globalCommandLineOptions } from '../cli-utils'; + +export type OutputMode = 'html' | 'text' | 'json'; + +export type ParsedArgs = { + input_params?: string; + input_csv?: string; + input_queries?: string; + endpoint: string; + extra_params: string; + method: string; + ignored_fields: string[]; + concurrency: number; + unchanged: boolean; + key_map: string[]; + output_mode: OutputMode; + output_file: string; +}; + +export const OLD_KEY = 'old'; +export const NEW_KEY = 'new'; + +/** + * + */ +export function parseArgv() { + // eslint-disable-next-line @typescript-eslint/no-var-requires, global-require + const yargs = require('yargs').strict(); + + _.forEach(globalCommandLineOptions, (val, key) => { + yargs.option(key, val); + }); + + yargs.option(OLD_KEY); + yargs.hide(OLD_KEY); + yargs.option(NEW_KEY); + yargs.hide(NEW_KEY); + + const oldParams = []; + const newParams = []; + _.forEach(getApiEnvCommandLineOptions(), (val, key) => { + yargs.option(`${OLD_KEY}.${key}`, { + ...val, + alias: val.alias ? `${OLD_KEY}.${val.alias}` : null, + }); + oldParams.push(`${OLD_KEY}.${key}`); + yargs.option(`${NEW_KEY}.${key}`, { + ...val, + alias: val.alias ? `${NEW_KEY}.${val.alias}` : null, + }); + newParams.push(`${NEW_KEY}.${key}`); + }); + + yargs.option('input_params', { + type: 'array', + description: 'A file containing url encoded query params, requires --endpoint', + }); + + yargs.option('extra_params', { + type: 'string', + description: + 'Extra static parameters that will be added to each query, maybe something like limit=2 to make diffs less noisy', + }); + + yargs.option('input_csv', { + type: 'array', + description: 'A file containingquery params in a csv, first line is , requires --endpoint', + }); + + yargs.option('endpoint', { + description: 'Endpoint to query using query param strings from --input_params', + }); + + yargs.option('input_queries', { + description: 'A file containing endpoints + queries, one per line', + }); + + yargs.option('ignored_fields', { + type: 'array', + default: [], + description: + 'field names to ignore when diffing responses. geometry latitude longitude are common for geocode compare runs', + }); + + yargs.option('concurrency', { + type: 'number', + default: 10, + description: 'concurrency of api queries per host to run', + }); + + yargs.option('unchanged', { + type: 'boolean', + default: false, + description: 'whether or not to print all queries, even unchanged ones', + }); + + yargs.option('key_map', { + type: 'array', + default: [], + description: + 'a mapping of csv columns to parameter names in the format csv_header1=param1 csv_header2=param2, if all numbers, are assumed to be csv column numbers', + }); + + yargs.option('output_mode', { + choices: ['html', 'text', 'json'], + description: 'what kind of output to generate', + }); + + yargs.group(['input_params', 'endpoint', 'input_queries', 'input_csv'], 'Query options:'); + yargs.group(oldParams, 'Options for "old" server to compare:'); + yargs.group(newParams, 'Options for "new" server to compare:'); + yargs.implies('input_csv', 'endpoint'); + yargs.implies('input_params', 'endpoint'); + + yargs.usage(`This tool takes in a set of queries to compare against two radar api servers. +It has a bunch of options, here are some examples: + +./run.sh compare --old.prod --new.local --endpoint /search/autocomplete --input_params input.txt + Compares /search/autocomplete on prod vs local, using query string in input.txt + +./run.sh compare --old.prod --new.local --new.key_env=staging --endpoint /search/autocomplete --input_params input.txt + Same, but looks for a staging key in the env var STAGING_TEST_RADAR_API_KEY in the env or in + +There are other ways to configure old and new, look in the help for more. These options are the same as to ./run.sh api, just with new & old prepended + `); + + return yargs.argv; +} diff --git a/lib/compare/change.ts b/lib/compare/change.ts new file mode 100644 index 0000000..f7c5059 --- /dev/null +++ b/lib/compare/change.ts @@ -0,0 +1,26 @@ +/* eslint-disable camelcase */ +import { AxiosResponse } from 'axios'; +import * as queryString from 'query-string'; + +export type Change = { + params: queryString.ParsedQuery; + delta: unknown; + oldResponse: AxiosResponse; + newResponse: AxiosResponse; +}; + +type OutputMode = 'html' | 'text'; + +export type ParsedArgs = { + input_params?: string; + input_csv?: string; + input_queries?: string; + endpoint: string; + extra_params: string; + method: string; + ignored_fields: string[]; + concurrency: number; + unchanged: boolean; + key_map: string[]; + output_mode: OutputMode; +}; diff --git a/lib/compare/compare.ts b/lib/compare/compare.ts new file mode 100644 index 0000000..afb70c1 --- /dev/null +++ b/lib/compare/compare.ts @@ -0,0 +1,212 @@ +/* eslint-disable camelcase */ +import * as fs from 'fs'; +import * as _ from 'lodash'; +import * as queryString from 'query-string'; +import * as Bluebird from 'bluebird'; +import * as chalk from 'chalk'; +import * as parseCsvSync from 'csv-parse/lib/sync'; +import * as jsondiffpatch from 'jsondiffpatch'; +import { ApiEnv, argvToApiEnv } from '../apiEnv'; +import runQuery from '../run-query'; +import { failedExit } from '../cli-utils'; +import { Change, ParsedArgs } from './change'; +import { CompareFormatter } from './formatters/compare-formatter'; +import { parseArgv, OLD_KEY, NEW_KEY } from './argv'; +import getFormatter from './formatters/get-formatter'; + +/** + * @param argv + */ +function generateQueries(argv: ParsedArgs) { + const hasInputFile = argv.input_params || argv.input_csv; + if ((argv.endpoint && !hasInputFile) || (!argv.endpoint && hasInputFile)) { + console.error( + chalk.red( + 'Must specify both --endpoint and (--input_params or --input_csv) , perhaps you wanted --input_queries?', + ), + ); + } + + if (argv.endpoint && argv.input_params) { + const { endpoint } = argv; + return _.flatMap(argv.input_params, (input_param_file: string) => fs + .readFileSync(input_param_file) + .toString() + .split('\n') + .filter((line) => !!line) + .map((line) => `${endpoint}?${line}`)); + } + if (argv.endpoint && argv.input_csv) { + const { endpoint } = argv; + return _.flatMap(argv.input_csv, (input_csv_file: string) => { + const fileLines = fs.readFileSync(input_csv_file).toString(); + + const keyMap: Record = {}; + + argv.key_map.forEach((str: string) => { + const parts = str.split('='); + if (parts.length !== 2) { + failedExit(`invalid keymap ${str}, must be of form csv_column_name=param_name`); + } + const [csvHeader, paramName] = parts; + keyMap[csvHeader] = paramName; + }); + const hasNumericKeyMap = _.every(_.keys(keyMap), (k) => /^\d+$/.test(k)); + + const records = parseCsvSync(fileLines, { + columns: !hasNumericKeyMap, + skip_empty_lines: true, + }); + + return records.map((record) => { + const modifiedRecord = record; + + delete modifiedRecord['']; + + _.forEach(keyMap, (paramName, csvHeader) => { + if (!_.includes(_.keys(modifiedRecord), csvHeader)) { + failedExit( + `CSV input is missing specified header ${csvHeader}, sample row: ${JSON.stringify( + record, + )}`, + ); + } + modifiedRecord[paramName] = modifiedRecord[csvHeader]; + delete modifiedRecord[csvHeader]; + }); + + return `${endpoint}?${queryString.stringify(modifiedRecord)}`; + }); + }); + } + if (argv.input_queries) { + return _.flatMap(argv.input_queries, (input_queries_file: string) => fs + .readFileSync(input_queries_file) + .toString() + .split('\n') + .filter((line) => !!line)); + } + + return []; +} + +/** + * @param root0 + * @param root0.oldApiEnv + * @param root0.newApiEnv + * @param root0.query + * @param root0.argv + */ +async function compareQuery({ + oldApiEnv, + newApiEnv, + query, + argv, +}: { + oldApiEnv: ApiEnv; + newApiEnv: ApiEnv; + query: string; + argv: ParsedArgs; +}): Promise { + const [endpoint, paramsString] = query.split('?'); + const params = queryString.parse(`${paramsString}&${argv.extra_params}`); + delete params.undefined; + const oldResponse = await runQuery(oldApiEnv, { + endpoint, + params, + method: argv.method, + }); + const newResponse = await runQuery(newApiEnv, { + endpoint, + params, + method: argv.method, + }); + + const differ = jsondiffpatch.create({ + propertyFilter(name, _context) { + return !['buildInfo', 'debug', ...(argv.ignored_fields || [])].includes(name); + }, + }); + + const delta = differ.diff(oldResponse.data, newResponse.data); + + if (!delta && !argv.unchanged) { + return undefined; + } + + return { + params, + delta, + oldResponse, + newResponse, + }; +} + +/** + * @param root0 + * @param root0.oldApiEnv + * @param root0.newApiEnv + * @param root0.queries + * @param root0.argv + * @param root0.formatter + */ +async function compareQueries({ + oldApiEnv, + newApiEnv, + queries, + argv, + formatter, +}: { + oldApiEnv: ApiEnv; + newApiEnv: ApiEnv; + queries: string[]; + argv: ParsedArgs; + formatter: CompareFormatter +}) { + let numQueriesRun = 0; + + await Bluebird.map( + queries, + async (query: string) => { + formatter.queryRan(); + + numQueriesRun += 1; + const change = await compareQuery({ + oldApiEnv, + newApiEnv, + query, + argv, + }); + if (change) { + formatter.logChange(change); + } + }, + { concurrency: argv.concurrency }, + ); + formatter.finished(); +} + +const argv = parseArgv() as ParsedArgs; + +const oldApiEnv = argvToApiEnv(argv[OLD_KEY]); +const newApiEnv = argvToApiEnv(argv[NEW_KEY]); + +const queries = generateQueries(argv); + +if (!queries || queries.length === 0) { + failedExit( + 'No queries found, did you specify one of: --input_params, --input_csv, --input_queries?', + ); +} + +const formatter = getFormatter(argv.output_mode, { + oldApiEnv, newApiEnv, argv, totalQueries: queries.length, +}); + +compareQueries({ + oldApiEnv, + newApiEnv, + queries, + argv, + formatter, +}); diff --git a/lib/compare/formatters/compare-formatter.ts b/lib/compare/formatters/compare-formatter.ts new file mode 100644 index 0000000..a317874 --- /dev/null +++ b/lib/compare/formatters/compare-formatter.ts @@ -0,0 +1,35 @@ +import { Change, ParsedArgs } from '../change'; +import { ApiEnv } from '../../apiEnv'; + +export type FormatterArgv = Pick + +export type FormatterConstructorParams = { + oldApiEnv: ApiEnv; + newApiEnv: ApiEnv; + argv: FormatterArgv; + totalQueries: number; +} + +export abstract class CompareFormatter { + totalQueries: number; + + abstract logChange(change: Change): void; + + abstract queryRan(): void; + + abstract finished(): void; + + oldApiEnv: ApiEnv; + + newApiEnv: ApiEnv; + + constructor({ + oldApiEnv, + newApiEnv, + totalQueries, + }: FormatterConstructorParams) { + this.oldApiEnv = oldApiEnv; + this.newApiEnv = newApiEnv; + this.totalQueries = totalQueries; + } +} diff --git a/lib/compare/formatters/console-formatter.ts b/lib/compare/formatters/console-formatter.ts new file mode 100644 index 0000000..ae523df --- /dev/null +++ b/lib/compare/formatters/console-formatter.ts @@ -0,0 +1,46 @@ +/* eslint-disable no-console */ +import * as chalk from 'chalk'; +import * as jsondiffpatch from 'jsondiffpatch'; + +import { Change } from '../change'; +import { CompareFormatter } from './compare-formatter'; +import { ApiEnv } from '../../apiEnv'; + +export default class ConsoleFormatter extends CompareFormatter { + numQueriesRun = 0; + + numQueriesChanged = 0; + + logChange(change: Change): void { + this.numQueriesChanged += 1; + + const apiEnvToApiSh = (apiEnv: ApiEnv): string => { + if (apiEnv.keyEnv) { + return `./api.sh --keyEnv ${apiEnv.keyEnv}`; + } + return './api.sh'; + }; + const outputLines = `${JSON.stringify(change.params)} + ${apiEnvToApiSh(this.oldApiEnv)} ${change.oldResponse.request.res.responseUrl} + ${apiEnvToApiSh(this.newApiEnv)} ${change.newResponse.request.res.responseUrl}`; + + if (!change.delta) { + console.log(chalk.cyan(`Unchanged: ${outputLines}`)); + } else { + console.log(chalk.yellow(`Changed: ${outputLines}`)); + } + + (jsondiffpatch.console as any).log(change.delta); + } + + queryRan(): void { + this.numQueriesRun += 1; + if (this.numQueriesRun % 10 === 0) { + console.log(`IN PROGRESS. ${this.numQueriesRun}/${this.totalQueries} run`); + } + } + + finished(): void { + console.log(`DONE. ${this.numQueriesChanged}/${this.numQueriesRun} changed`); + } +} diff --git a/lib/compare/formatters/get-formatter.ts b/lib/compare/formatters/get-formatter.ts new file mode 100644 index 0000000..acb6dc7 --- /dev/null +++ b/lib/compare/formatters/get-formatter.ts @@ -0,0 +1,24 @@ +import { CompareFormatter, FormatterConstructorParams } from './compare-formatter'; +import { failedExit } from '../../cli-utils'; +import ConsoleFormatter from './console-formatter'; +import JsonFormatter from './json-formatter'; + +/** + * @param outputMode + * @param params + */ +export default function getFormatter( + outputMode: string, + params: FormatterConstructorParams, +): CompareFormatter { + switch (outputMode) { + case 'html': + throw failedExit('HTML Not Implemented'); + case 'text': + return new ConsoleFormatter(params); + case 'json': + return new JsonFormatter(params); + default: + throw failedExit(`Unknown output_mode: ${outputMode}`); + } +} diff --git a/lib/compare/formatters/json-formatter.ts b/lib/compare/formatters/json-formatter.ts new file mode 100644 index 0000000..720d0f4 --- /dev/null +++ b/lib/compare/formatters/json-formatter.ts @@ -0,0 +1,30 @@ +/* eslint-disable no-console */ +import { Change } from '../change'; +import { CompareFormatter } from './compare-formatter'; + +type JsonChange = Pick + +export default class JsonFormatter extends CompareFormatter { + numQueriesRun = 0; + + numQueriesChanged = 0; + + changes: JsonChange[] = [] + + logChange(change: Change): void { + this.numQueriesChanged += 1; + this.changes.push({ delta: change.delta, params: change.params }); + } + + queryRan(): void { + this.numQueriesRun += 1; + if (this.numQueriesRun % 10 === 0) { + console.error(`IN PROGRESS. ${this.numQueriesRun}/${this.totalQueries} run`); + } + } + + finished(): void { + console.log(JSON.stringify({ changes: this.changes })); + } +} From bbee5d970ee82e350ebb81cd61733fd726ba049d Mon Sep 17 00:00:00 2001 From: David Blackman Date: Thu, 9 Jul 2020 14:56:10 -0400 Subject: [PATCH 3/5] most of the way to an html mode --- lib/compare/TODO.md | 9 +- lib/compare/compare.html | 303 +++++++++++++++++++++++ lib/compare/formatters/json-formatter.ts | 22 +- 3 files changed, 328 insertions(+), 6 deletions(-) create mode 100644 lib/compare/compare.html diff --git a/lib/compare/TODO.md b/lib/compare/TODO.md index 74ee5fb..6b175df 100644 --- a/lib/compare/TODO.md +++ b/lib/compare/TODO.md @@ -1,5 +1,12 @@ - html viewer mode -- comparison voting in html viewer mode +--- make voting work +--- add in some info about the run +--- pretty it up a bit +--- actually add html output mode + +- overall add performance characteristics + + - check that api is still working - think one last time about the config file - write a better README diff --git a/lib/compare/compare.html b/lib/compare/compare.html new file mode 100644 index 0000000..35763e7 --- /dev/null +++ b/lib/compare/compare.html @@ -0,0 +1,303 @@ + + + + Compare + + + + + + ` + + + + +
+ + + + + + diff --git a/lib/compare/formatters/json-formatter.ts b/lib/compare/formatters/json-formatter.ts index 720d0f4..b23c7ff 100644 --- a/lib/compare/formatters/json-formatter.ts +++ b/lib/compare/formatters/json-formatter.ts @@ -2,19 +2,25 @@ import { Change } from '../change'; import { CompareFormatter } from './compare-formatter'; -type JsonChange = Pick +type JsonChange = { old: unknown; new: unknown, oldUrl: string, newUrl: string } & Pick; export default class JsonFormatter extends CompareFormatter { numQueriesRun = 0; numQueriesChanged = 0; - changes: JsonChange[] = [] + changes: JsonChange[] = []; logChange(change: Change): void { this.numQueriesChanged += 1; - this.changes.push({ delta: change.delta, params: change.params }); + this.changes.push({ + delta: change.delta, + params: change.params, + old: change.oldResponse.data, + new: change.newResponse.data, + oldUrl: change.oldResponse.request.res.responseUrl, + newUrl: change.newResponse.request.res.responseUrl, + }); } queryRan(): void { @@ -25,6 +31,12 @@ export default class JsonFormatter extends CompareFormatter { } finished(): void { - console.log(JSON.stringify({ changes: this.changes })); + console.log( + JSON.stringify({ + changes: this.changes, + oldApiEnv: this.oldApiEnv, + newApiEnv: this.newApiEnv, + }), + ); } } From b06047b49020d41668d7beaa93472a67d36beb25 Mon Sep 17 00:00:00 2001 From: David Blackman Date: Thu, 9 Jul 2020 16:43:44 -0400 Subject: [PATCH 4/5] html mode 95% there --- lib/compare/TODO.md | 4 +- lib/compare/argv.ts | 1 + lib/compare/compare.html | 303 --- lib/compare/formatters/compare.html | 411 +++ lib/compare/formatters/get-formatter.ts | 11 +- lib/compare/formatters/html-formatter.ts | 14 + lib/compare/formatters/json-formatter.ts | 19 +- package-lock.json | 3152 +++++++++++++++++++++- package.json | 4 + 9 files changed, 3598 insertions(+), 321 deletions(-) delete mode 100644 lib/compare/compare.html create mode 100644 lib/compare/formatters/compare.html create mode 100644 lib/compare/formatters/html-formatter.ts diff --git a/lib/compare/TODO.md b/lib/compare/TODO.md index 6b175df..7390f94 100644 --- a/lib/compare/TODO.md +++ b/lib/compare/TODO.md @@ -1,8 +1,6 @@ - html viewer mode ---- make voting work --- add in some info about the run ---- pretty it up a bit ---- actually add html output mode +--- summary mode - overall add performance characteristics diff --git a/lib/compare/argv.ts b/lib/compare/argv.ts index 8dd5736..65943b7 100644 --- a/lib/compare/argv.ts +++ b/lib/compare/argv.ts @@ -106,6 +106,7 @@ export function parseArgv() { yargs.option('output_mode', { choices: ['html', 'text', 'json'], + default: 'text', description: 'what kind of output to generate', }); diff --git a/lib/compare/compare.html b/lib/compare/compare.html deleted file mode 100644 index 35763e7..0000000 --- a/lib/compare/compare.html +++ /dev/null @@ -1,303 +0,0 @@ - - - - Compare - - - - - - ` - - - - -
- - - - - - diff --git a/lib/compare/formatters/compare.html b/lib/compare/formatters/compare.html new file mode 100644 index 0000000..c29c19b --- /dev/null +++ b/lib/compare/formatters/compare.html @@ -0,0 +1,411 @@ + + + + Compare + + + + + + + + + +
+
+ + + + + + + + diff --git a/lib/compare/formatters/get-formatter.ts b/lib/compare/formatters/get-formatter.ts index acb6dc7..ff06292 100644 --- a/lib/compare/formatters/get-formatter.ts +++ b/lib/compare/formatters/get-formatter.ts @@ -2,18 +2,21 @@ import { CompareFormatter, FormatterConstructorParams } from './compare-formatte import { failedExit } from '../../cli-utils'; import ConsoleFormatter from './console-formatter'; import JsonFormatter from './json-formatter'; +import HtmlFormatter from './html-formatter'; +import { OutputMode } from '../argv'; /** - * @param outputMode - * @param params + * @param outputMode the output mode specified on the commandlin + * @param params data required to construct a compare formater + * @returns {CompareFormatter} the compare formatter */ export default function getFormatter( - outputMode: string, + outputMode: OutputMode, params: FormatterConstructorParams, ): CompareFormatter { switch (outputMode) { case 'html': - throw failedExit('HTML Not Implemented'); + return new HtmlFormatter(params); case 'text': return new ConsoleFormatter(params); case 'json': diff --git a/lib/compare/formatters/html-formatter.ts b/lib/compare/formatters/html-formatter.ts new file mode 100644 index 0000000..97dc073 --- /dev/null +++ b/lib/compare/formatters/html-formatter.ts @@ -0,0 +1,14 @@ +/* eslint-disable no-console */ +import * as fs from 'fs'; +import * as path from 'path'; +import JsonFormatter from './json-formatter'; + +export default class HtmlFormatter extends JsonFormatter { + finished(): void { + const filePath = path.join(__dirname, 'compare.html'); + const html = fs.readFileSync(filePath).toString(); + console.log( + html.replace('JSON_GO_HERE', JSON.stringify(this.finishedDict())), + ); + } +} diff --git a/lib/compare/formatters/json-formatter.ts b/lib/compare/formatters/json-formatter.ts index b23c7ff..bc1f556 100644 --- a/lib/compare/formatters/json-formatter.ts +++ b/lib/compare/formatters/json-formatter.ts @@ -1,8 +1,9 @@ /* eslint-disable no-console */ +import * as md5 from 'md5'; import { Change } from '../change'; import { CompareFormatter } from './compare-formatter'; -type JsonChange = { old: unknown; new: unknown, oldUrl: string, newUrl: string } & Pick; +type JsonChange = { id: string, old: unknown; new: unknown, oldUrl: string, newUrl: string } & Pick; export default class JsonFormatter extends CompareFormatter { numQueriesRun = 0; @@ -14,6 +15,7 @@ export default class JsonFormatter extends CompareFormatter { logChange(change: Change): void { this.numQueriesChanged += 1; this.changes.push({ + id: md5(JSON.stringify({ delta: change.delta, params: change.params })), delta: change.delta, params: change.params, old: change.oldResponse.data, @@ -30,13 +32,18 @@ export default class JsonFormatter extends CompareFormatter { } } + finishedDict(): any { + return { + command: process.argv.join(' '), + changes: this.changes, + oldApiEnv: this.oldApiEnv, + newApiEnv: this.newApiEnv, + }; + } + finished(): void { console.log( - JSON.stringify({ - changes: this.changes, - oldApiEnv: this.oldApiEnv, - newApiEnv: this.newApiEnv, - }), + JSON.stringify(this.finishedDict()), ); } } diff --git a/package-lock.json b/package-lock.json index eedeebf..b1ba11b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -98,11 +98,27 @@ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.157.tgz", "integrity": "sha512-Ft5BNFmv2pHDgxV5JDsndOWTRJ+56zte0ZpYLowp03tW+K+t8u8YMOzAnpuqPgzX6WO1XpDIUm7u04M8vdDiVQ==" }, + "@types/md5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/md5/-/md5-2.2.0.tgz", + "integrity": "sha512-JN8OVL/wiDlCWTPzplsgMPu0uE9Q6blwp68rYsfk2G8aokRUQ8XD9MEhZwihfAiQvoyE+m31m6i3GFXwYWomKQ==", + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "14.0.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.13.tgz", "integrity": "sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA==" }, + "@types/npm": { + "version": "2.0.31", + "resolved": "https://registry.npmjs.org/@types/npm/-/npm-2.0.31.tgz", + "integrity": "sha512-v4JpUx83wVGItleYsnYeZrM8NTLSnYDfTE/iGm4owy6zZPNFNmnsvvrxiYtG3cVHt/XutzTjUBQ9Bh8bnvEkCw==", + "requires": { + "@types/node": "*" + } + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -252,11 +268,6 @@ "uri-js": "^4.2.2" } }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==" - }, "ansi-escapes": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", @@ -517,6 +528,11 @@ } } }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", @@ -724,6 +740,11 @@ "which": "^2.0.1" } }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" + }, "csv": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/csv/-/csv-5.3.2.tgz", @@ -1674,6 +1695,11 @@ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "is-callable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", @@ -2187,6 +2213,16 @@ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" }, + "md5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", + "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", + "requires": { + "charenc": "~0.0.1", + "crypt": "~0.0.1", + "is-buffer": "~1.1.1" + } + }, "memory-pager": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", @@ -2294,6 +2330,3112 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, + "npm": { + "version": "6.14.6", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.14.6.tgz", + "integrity": "sha512-axnz6iHFK6WPE0js/+mRp+4IOwpHn5tJEw5KB6FiCU764zmffrhsYHbSHi2kKqNkRBt53XasXjngZfBD3FQzrQ==", + "requires": { + "JSONStream": "^1.3.5", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "aproba": "^2.0.0", + "archy": "~1.0.0", + "bin-links": "^1.1.7", + "bluebird": "^3.5.5", + "byte-size": "^5.0.1", + "cacache": "^12.0.3", + "call-limit": "^1.1.1", + "chownr": "^1.1.4", + "ci-info": "^2.0.0", + "cli-columns": "^3.1.2", + "cli-table3": "^0.5.1", + "cmd-shim": "^3.0.3", + "columnify": "~1.5.4", + "config-chain": "^1.1.12", + "debuglog": "*", + "detect-indent": "~5.0.0", + "detect-newline": "^2.1.0", + "dezalgo": "~1.0.3", + "editor": "~1.0.0", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "fs-vacuum": "~1.2.10", + "fs-write-stream-atomic": "~1.0.10", + "gentle-fs": "^2.3.0", + "glob": "^7.1.6", + "graceful-fs": "^4.2.4", + "has-unicode": "~2.0.1", + "hosted-git-info": "^2.8.8", + "iferr": "^1.0.2", + "imurmurhash": "*", + "infer-owner": "^1.0.4", + "inflight": "~1.0.6", + "inherits": "^2.0.4", + "ini": "^1.3.5", + "init-package-json": "^1.10.3", + "is-cidr": "^3.0.0", + "json-parse-better-errors": "^1.0.2", + "lazy-property": "~1.0.0", + "libcipm": "^4.0.7", + "libnpm": "^3.0.1", + "libnpmaccess": "^3.0.2", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", + "libnpx": "^10.2.2", + "lock-verify": "^2.1.0", + "lockfile": "^1.0.4", + "lodash._baseindexof": "*", + "lodash._baseuniq": "~4.6.0", + "lodash._bindcallback": "*", + "lodash._cacheindexof": "*", + "lodash._createcache": "*", + "lodash._getnative": "*", + "lodash.clonedeep": "~4.5.0", + "lodash.restparam": "*", + "lodash.union": "~4.6.0", + "lodash.uniq": "~4.5.0", + "lodash.without": "~4.4.0", + "lru-cache": "^5.1.1", + "meant": "~1.0.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.5", + "move-concurrently": "^1.0.1", + "node-gyp": "^5.1.0", + "nopt": "^4.0.3", + "normalize-package-data": "^2.5.0", + "npm-audit-report": "^1.3.2", + "npm-cache-filename": "~1.0.2", + "npm-install-checks": "^3.0.2", + "npm-lifecycle": "^3.1.4", + "npm-package-arg": "^6.1.1", + "npm-packlist": "^1.4.8", + "npm-pick-manifest": "^3.0.2", + "npm-profile": "^4.0.4", + "npm-registry-fetch": "^4.0.5", + "npm-user-validate": "~1.0.0", + "npmlog": "~4.1.2", + "once": "~1.4.0", + "opener": "^1.5.1", + "osenv": "^0.1.5", + "pacote": "^9.5.12", + "path-is-inside": "~1.0.2", + "promise-inflight": "~1.0.1", + "qrcode-terminal": "^0.12.0", + "query-string": "^6.8.2", + "qw": "~1.0.1", + "read": "~1.0.7", + "read-cmd-shim": "^1.0.5", + "read-installed": "~4.0.3", + "read-package-json": "^2.1.1", + "read-package-tree": "^5.3.1", + "readable-stream": "^3.6.0", + "readdir-scoped-modules": "^1.1.0", + "request": "^2.88.0", + "retry": "^0.12.0", + "rimraf": "^2.7.1", + "safe-buffer": "^5.1.2", + "semver": "^5.7.1", + "sha": "^3.0.0", + "slide": "~1.1.6", + "sorted-object": "~2.0.1", + "sorted-union-stream": "~2.1.3", + "ssri": "^6.0.1", + "stringify-package": "^1.0.1", + "tar": "^4.4.13", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "uid-number": "0.0.6", + "umask": "~1.1.0", + "unique-filename": "^1.1.1", + "unpipe": "~1.0.0", + "update-notifier": "^2.5.0", + "uuid": "^3.3.3", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^1.3.1", + "worker-farm": "^1.7.0", + "write-file-atomic": "^2.4.3" + }, + "dependencies": { + "JSONStream": { + "version": "1.3.5", + "bundled": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "agent-base": { + "version": "4.3.0", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "3.5.2", + "bundled": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "ajv": { + "version": "5.5.2", + "bundled": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ansi-align": { + "version": "2.0.0", + "bundled": true, + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true + }, + "ansistyles": { + "version": "0.1.3", + "bundled": true + }, + "aproba": { + "version": "2.0.0", + "bundled": true + }, + "archy": { + "version": "1.0.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "asap": { + "version": "2.0.6", + "bundled": true + }, + "asn1": { + "version": "0.2.4", + "bundled": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "bundled": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true + }, + "aws-sign2": { + "version": "0.7.0", + "bundled": true + }, + "aws4": { + "version": "1.8.0", + "bundled": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bin-links": { + "version": "1.1.7", + "bundled": true, + "requires": { + "bluebird": "^3.5.3", + "cmd-shim": "^3.0.0", + "gentle-fs": "^2.3.0", + "graceful-fs": "^4.1.15", + "npm-normalize-package-bin": "^1.0.0", + "write-file-atomic": "^2.3.0" + } + }, + "bluebird": { + "version": "3.5.5", + "bundled": true + }, + "boxen": { + "version": "1.3.0", + "bundled": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.0.0", + "bundled": true + }, + "builtins": { + "version": "1.0.3", + "bundled": true + }, + "byline": { + "version": "5.0.0", + "bundled": true + }, + "byte-size": { + "version": "5.0.1", + "bundled": true + }, + "cacache": { + "version": "12.0.3", + "bundled": true, + "requires": { + "bluebird": "^3.5.5", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.4", + "graceful-fs": "^4.1.15", + "infer-owner": "^1.0.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.3", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "call-limit": { + "version": "1.1.1", + "bundled": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true + }, + "capture-stack-trace": { + "version": "1.0.0", + "bundled": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chownr": { + "version": "1.1.4", + "bundled": true + }, + "ci-info": { + "version": "2.0.0", + "bundled": true + }, + "cidr-regex": { + "version": "2.0.10", + "bundled": true, + "requires": { + "ip-regex": "^2.1.0" + } + }, + "cli-boxes": { + "version": "1.0.0", + "bundled": true + }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "requires": { + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + } + }, + "cli-table3": { + "version": "0.5.1", + "bundled": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "clone": { + "version": "1.0.4", + "bundled": true + }, + "cmd-shim": { + "version": "3.0.3", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" + } + }, + "co": { + "version": "4.6.0", + "bundled": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "color-convert": { + "version": "1.9.1", + "bundled": true, + "requires": { + "color-name": "^1.1.1" + } + }, + "color-name": { + "version": "1.1.3", + "bundled": true + }, + "colors": { + "version": "1.3.3", + "bundled": true, + "optional": true + }, + "columnify": { + "version": "1.5.4", + "bundled": true, + "requires": { + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" + } + }, + "combined-stream": { + "version": "1.0.6", + "bundled": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "concat-stream": { + "version": "1.6.2", + "bundled": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "config-chain": { + "version": "1.1.12", + "bundled": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "configstore": { + "version": "3.1.2", + "bundled": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "copy-concurrently": { + "version": "1.0.5", + "bundled": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "iferr": { + "version": "0.1.5", + "bundled": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "create-error-class": { + "version": "3.0.2", + "bundled": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "bundled": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "bundled": true + } + } + }, + "crypto-random-string": { + "version": "1.0.0", + "bundled": true + }, + "cyclist": { + "version": "0.2.2", + "bundled": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true + }, + "defaults": { + "version": "1.0.3", + "bundled": true, + "requires": { + "clone": "^1.0.2" + } + }, + "define-properties": { + "version": "1.1.3", + "bundled": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "detect-indent": { + "version": "5.0.0", + "bundled": true + }, + "detect-newline": { + "version": "2.1.0", + "bundled": true + }, + "dezalgo": { + "version": "1.0.3", + "bundled": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "bundled": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotenv": { + "version": "5.0.1", + "bundled": true + }, + "duplexer3": { + "version": "0.1.4", + "bundled": true + }, + "duplexify": { + "version": "3.6.0", + "bundled": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "editor": { + "version": "1.0.0", + "bundled": true + }, + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "requires": { + "once": "^1.4.0" + } + }, + "env-paths": { + "version": "2.2.0", + "bundled": true + }, + "err-code": { + "version": "1.1.2", + "bundled": true + }, + "errno": { + "version": "0.1.7", + "bundled": true, + "requires": { + "prr": "~1.0.1" + } + }, + "es-abstract": { + "version": "1.12.0", + "bundled": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "bundled": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-promise": { + "version": "4.2.8", + "bundled": true + }, + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "bundled": true + } + } + }, + "extend": { + "version": "3.0.2", + "bundled": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "bundled": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true + }, + "figgy-pudding": { + "version": "3.5.1", + "bundled": true + }, + "find-npm-prefix": { + "version": "1.0.2", + "bundled": true + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true + }, + "form-data": { + "version": "2.3.2", + "bundled": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs-minipass": { + "version": "1.2.7", + "bundled": true, + "requires": { + "minipass": "^2.6.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "fs-vacuum": { + "version": "1.2.10", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "path-is-inside": "^1.0.1", + "rimraf": "^2.5.2" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "function-bind": { + "version": "1.1.1", + "bundled": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "genfun": { + "version": "5.0.0", + "bundled": true + }, + "gentle-fs": { + "version": "2.3.0", + "bundled": true, + "requires": { + "aproba": "^1.1.2", + "chownr": "^1.1.2", + "cmd-shim": "^3.0.3", + "fs-vacuum": "^1.2.10", + "graceful-fs": "^4.1.11", + "iferr": "^0.1.5", + "infer-owner": "^1.0.4", + "mkdirp": "^0.5.1", + "path-is-inside": "^1.0.2", + "read-cmd-shim": "^1.0.1", + "slide": "^1.1.6" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "iferr": { + "version": "0.1.5", + "bundled": true + } + } + }, + "get-caller-file": { + "version": "1.0.3", + "bundled": true + }, + "get-stream": { + "version": "4.1.0", + "bundled": true, + "requires": { + "pump": "^3.0.0" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global-dirs": { + "version": "0.1.1", + "bundled": true, + "requires": { + "ini": "^1.3.4" + } + }, + "got": { + "version": "6.7.1", + "bundled": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "bundled": true + } + } + }, + "graceful-fs": { + "version": "4.2.4", + "bundled": true + }, + "har-schema": { + "version": "2.0.0", + "bundled": true + }, + "har-validator": { + "version": "5.1.0", + "bundled": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "bundled": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true + }, + "has-symbols": { + "version": "1.0.0", + "bundled": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "hosted-git-info": { + "version": "2.8.8", + "bundled": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.4", + "bundled": true, + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "bundled": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "iferr": { + "version": "1.0.2", + "bundled": true + }, + "ignore-walk": { + "version": "3.0.3", + "bundled": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "import-lazy": { + "version": "2.1.0", + "bundled": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true + }, + "infer-owner": { + "version": "1.0.4", + "bundled": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "init-package-json": { + "version": "1.10.3", + "bundled": true, + "requires": { + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "bundled": true + }, + "ip": { + "version": "1.1.5", + "bundled": true + }, + "ip-regex": { + "version": "2.1.0", + "bundled": true + }, + "is-callable": { + "version": "1.1.4", + "bundled": true + }, + "is-ci": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ci-info": "^1.5.0" + }, + "dependencies": { + "ci-info": { + "version": "1.6.0", + "bundled": true + } + } + }, + "is-cidr": { + "version": "3.0.0", + "bundled": true, + "requires": { + "cidr-regex": "^2.0.10" + } + }, + "is-date-object": { + "version": "1.0.1", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "bundled": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "bundled": true + }, + "is-obj": { + "version": "1.0.1", + "bundled": true + }, + "is-path-inside": { + "version": "1.0.1", + "bundled": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-redirect": { + "version": "1.0.0", + "bundled": true + }, + "is-regex": { + "version": "1.0.4", + "bundled": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-retry-allowed": { + "version": "1.2.0", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "is-symbol": { + "version": "1.0.2", + "bundled": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "latest-version": { + "version": "3.1.0", + "bundled": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "lazy-property": { + "version": "1.0.0", + "bundled": true + }, + "lcid": { + "version": "2.0.0", + "bundled": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "libcipm": { + "version": "4.0.7", + "bundled": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "graceful-fs": "^4.1.11", + "ini": "^1.3.5", + "lock-verify": "^2.0.2", + "mkdirp": "^0.5.1", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "pacote": "^9.1.0", + "read-package-json": "^2.0.13", + "rimraf": "^2.6.2", + "worker-farm": "^1.6.0" + } + }, + "libnpm": { + "version": "3.0.1", + "bundled": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.3", + "find-npm-prefix": "^1.0.2", + "libnpmaccess": "^3.0.2", + "libnpmconfig": "^1.2.1", + "libnpmhook": "^5.0.3", + "libnpmorg": "^1.0.1", + "libnpmpublish": "^1.1.2", + "libnpmsearch": "^2.0.2", + "libnpmteam": "^1.0.2", + "lock-verify": "^2.0.2", + "npm-lifecycle": "^3.0.0", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "npm-profile": "^4.0.2", + "npm-registry-fetch": "^4.0.0", + "npmlog": "^4.1.2", + "pacote": "^9.5.3", + "read-package-json": "^2.0.13", + "stringify-package": "^1.0.0" + } + }, + "libnpmaccess": { + "version": "3.0.2", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "get-stream": "^4.0.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmconfig": { + "version": "1.2.1", + "bundled": true, + "requires": { + "figgy-pudding": "^3.5.1", + "find-up": "^3.0.0", + "ini": "^1.3.5" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "bundled": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "2.2.0", + "bundled": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "bundled": true + } + } + }, + "libnpmhook": { + "version": "5.0.3", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmorg": { + "version": "1.0.1", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmpublish": { + "version": "1.1.2", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "lodash.clonedeep": "^4.5.0", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-registry-fetch": "^4.0.0", + "semver": "^5.5.1", + "ssri": "^6.0.1" + } + }, + "libnpmsearch": { + "version": "2.0.2", + "bundled": true, + "requires": { + "figgy-pudding": "^3.5.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpmteam": { + "version": "1.0.2", + "bundled": true, + "requires": { + "aproba": "^2.0.0", + "figgy-pudding": "^3.4.1", + "get-stream": "^4.0.0", + "npm-registry-fetch": "^4.0.0" + } + }, + "libnpx": { + "version": "10.2.2", + "bundled": true, + "requires": { + "dotenv": "^5.0.1", + "npm-package-arg": "^6.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.0", + "update-notifier": "^2.3.0", + "which": "^1.3.0", + "y18n": "^4.0.0", + "yargs": "^11.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lock-verify": { + "version": "2.1.0", + "bundled": true, + "requires": { + "npm-package-arg": "^6.1.0", + "semver": "^5.4.1" + } + }, + "lockfile": { + "version": "1.0.4", + "bundled": true, + "requires": { + "signal-exit": "^3.0.2" + } + }, + "lodash._baseindexof": { + "version": "3.1.0", + "bundled": true + }, + "lodash._baseuniq": { + "version": "4.6.0", + "bundled": true, + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "bundled": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "bundled": true + }, + "lodash._createcache": { + "version": "3.1.2", + "bundled": true, + "requires": { + "lodash._getnative": "^3.0.0" + } + }, + "lodash._createset": { + "version": "4.0.3", + "bundled": true + }, + "lodash._getnative": { + "version": "3.9.1", + "bundled": true + }, + "lodash._root": { + "version": "3.0.1", + "bundled": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "bundled": true + }, + "lodash.restparam": { + "version": "3.6.1", + "bundled": true + }, + "lodash.union": { + "version": "4.6.0", + "bundled": true + }, + "lodash.uniq": { + "version": "4.5.0", + "bundled": true + }, + "lodash.without": { + "version": "4.4.0", + "bundled": true + }, + "lowercase-keys": { + "version": "1.0.1", + "bundled": true + }, + "lru-cache": { + "version": "5.1.1", + "bundled": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "requires": { + "pify": "^3.0.0" + } + }, + "make-fetch-happen": { + "version": "5.0.2", + "bundled": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^12.0.0", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.3", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "bundled": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "meant": { + "version": "1.0.1", + "bundled": true + }, + "mem": { + "version": "4.3.0", + "bundled": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "2.1.0", + "bundled": true + } + } + }, + "mime-db": { + "version": "1.35.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.19", + "bundled": true, + "requires": { + "mime-db": "~1.35.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minizlib": { + "version": "1.3.3", + "bundled": true, + "requires": { + "minipass": "^2.9.0" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "mississippi": { + "version": "3.0.0", + "bundled": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "bundled": true, + "requires": { + "minimist": "^1.2.5" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "bundled": true + } + } + }, + "move-concurrently": { + "version": "1.0.1", + "bundled": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + } + } + }, + "ms": { + "version": "2.1.1", + "bundled": true + }, + "mute-stream": { + "version": "0.0.7", + "bundled": true + }, + "nice-try": { + "version": "1.0.5", + "bundled": true + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-gyp": { + "version": "5.1.0", + "bundled": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.1.2", + "request": "^2.88.0", + "rimraf": "^2.6.3", + "semver": "^5.7.1", + "tar": "^4.4.12", + "which": "^1.3.1" + } + }, + "nopt": { + "version": "4.0.3", + "bundled": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "bundled": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "resolve": { + "version": "1.10.0", + "bundled": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "npm-audit-report": { + "version": "1.3.2", + "bundled": true, + "requires": { + "cli-table3": "^0.5.0", + "console-control-strings": "^1.1.0" + } + }, + "npm-bundled": { + "version": "1.1.1", + "bundled": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-cache-filename": { + "version": "1.0.2", + "bundled": true + }, + "npm-install-checks": { + "version": "3.0.2", + "bundled": true, + "requires": { + "semver": "^2.3.0 || 3.x || 4 || 5" + } + }, + "npm-lifecycle": { + "version": "3.1.4", + "bundled": true, + "requires": { + "byline": "^5.0.0", + "graceful-fs": "^4.1.15", + "node-gyp": "^5.0.2", + "resolve-from": "^4.0.0", + "slide": "^1.1.6", + "uid-number": "0.0.6", + "umask": "^1.1.0", + "which": "^1.3.1" + } + }, + "npm-logical-tree": { + "version": "1.2.1", + "bundled": true + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "bundled": true + }, + "npm-package-arg": { + "version": "6.1.1", + "bundled": true, + "requires": { + "hosted-git-info": "^2.7.1", + "osenv": "^0.1.5", + "semver": "^5.6.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "1.4.8", + "bundled": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "3.0.2", + "bundled": true, + "requires": { + "figgy-pudding": "^3.5.1", + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "npm-profile": { + "version": "4.0.4", + "bundled": true, + "requires": { + "aproba": "^1.1.2 || 2", + "figgy-pudding": "^3.4.1", + "npm-registry-fetch": "^4.0.0" + } + }, + "npm-registry-fetch": { + "version": "4.0.5", + "bundled": true, + "requires": { + "JSONStream": "^1.3.4", + "bluebird": "^3.5.1", + "figgy-pudding": "^3.4.1", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "npm-package-arg": "^6.1.0", + "safe-buffer": "^5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "bundled": true + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npm-user-validate": { + "version": "1.0.0", + "bundled": true + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "oauth-sign": { + "version": "0.9.0", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "object-keys": { + "version": "1.0.12", + "bundled": true + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "bundled": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.5.1", + "bundled": true + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-locale": { + "version": "3.1.0", + "bundled": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "bundled": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "1.0.0", + "bundled": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + } + } + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "bundled": true + }, + "p-finally": { + "version": "1.0.0", + "bundled": true + }, + "p-is-promise": { + "version": "2.1.0", + "bundled": true + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true + }, + "package-json": { + "version": "4.0.1", + "bundled": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pacote": { + "version": "9.5.12", + "bundled": true, + "requires": { + "bluebird": "^3.5.3", + "cacache": "^12.0.2", + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "get-stream": "^4.1.0", + "glob": "^7.1.3", + "infer-owner": "^1.0.4", + "lru-cache": "^5.1.1", + "make-fetch-happen": "^5.0.0", + "minimatch": "^3.0.4", + "minipass": "^2.3.5", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-normalize-package-bin": "^1.0.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^3.0.0", + "npm-registry-fetch": "^4.0.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.1", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.6.0", + "ssri": "^6.0.1", + "tar": "^4.4.10", + "unique-filename": "^1.1.1", + "which": "^1.3.1" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "path-is-inside": { + "version": "1.0.2", + "bundled": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true + }, + "path-parse": { + "version": "1.0.6", + "bundled": true + }, + "performance-now": { + "version": "2.1.0", + "bundled": true + }, + "pify": { + "version": "3.0.0", + "bundled": true + }, + "prepend-http": { + "version": "1.0.4", + "bundled": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "promzard": { + "version": "0.3.0", + "bundled": true, + "requires": { + "read": "1" + } + }, + "proto-list": { + "version": "1.2.4", + "bundled": true + }, + "protoduck": { + "version": "5.0.1", + "bundled": true, + "requires": { + "genfun": "^5.0.0" + } + }, + "prr": { + "version": "1.0.1", + "bundled": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true + }, + "psl": { + "version": "1.1.29", + "bundled": true + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "bundled": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "1.4.1", + "bundled": true + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true + }, + "qs": { + "version": "6.5.2", + "bundled": true + }, + "query-string": { + "version": "6.8.2", + "bundled": true, + "requires": { + "decode-uri-component": "^0.2.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + } + }, + "qw": { + "version": "1.0.1", + "bundled": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.5", + "bundled": true + } + } + }, + "read": { + "version": "1.0.7", + "bundled": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-cmd-shim": { + "version": "1.0.5", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "read-installed": { + "version": "4.0.3", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + } + }, + "read-package-json": { + "version": "2.1.1", + "bundled": true, + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0" + } + }, + "read-package-tree": { + "version": "5.3.1", + "bundled": true, + "requires": { + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "util-promisify": "^2.1.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "bundled": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.1.0", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "registry-auth-token": { + "version": "3.4.0", + "bundled": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "bundled": true, + "requires": { + "rc": "^1.0.1" + } + }, + "request": { + "version": "2.88.0", + "bundled": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true + }, + "retry": { + "version": "0.12.0", + "bundled": true + }, + "rimraf": { + "version": "2.7.1", + "bundled": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-queue": { + "version": "1.0.3", + "bundled": true, + "requires": { + "aproba": "^1.1.1" + }, + "dependencies": { + "aproba": { + "version": "1.2.0", + "bundled": true + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "semver": { + "version": "5.7.1", + "bundled": true + }, + "semver-diff": { + "version": "2.1.0", + "bundled": true, + "requires": { + "semver": "^5.0.3" + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "sha": { + "version": "3.0.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "slide": { + "version": "1.1.6", + "bundled": true + }, + "smart-buffer": { + "version": "4.1.0", + "bundled": true + }, + "socks": { + "version": "2.3.3", + "bundled": true, + "requires": { + "ip": "1.1.5", + "smart-buffer": "^4.1.0" + } + }, + "socks-proxy-agent": { + "version": "4.0.2", + "bundled": true, + "requires": { + "agent-base": "~4.2.1", + "socks": "~2.3.2" + }, + "dependencies": { + "agent-base": { + "version": "4.2.1", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + } + } + } + }, + "sorted-object": { + "version": "2.0.1", + "bundled": true + }, + "sorted-union-stream": { + "version": "2.1.3", + "bundled": true, + "requires": { + "from2": "^1.3.0", + "stream-iterate": "^1.1.0" + }, + "dependencies": { + "from2": { + "version": "1.3.0", + "bundled": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.10" + } + }, + "isarray": { + "version": "0.0.1", + "bundled": true + }, + "readable-stream": { + "version": "1.1.14", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + } + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "bundled": true + }, + "split-on-first": { + "version": "1.1.0", + "bundled": true + }, + "sshpk": { + "version": "1.14.2", + "bundled": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.1", + "bundled": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-iterate": { + "version": "1.2.0", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "stream-shift": { + "version": "1.0.0", + "bundled": true + }, + "strict-uri-encode": { + "version": "2.0.0", + "bundled": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.3.0", + "bundled": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.0", + "bundled": true + } + } + }, + "stringify-package": { + "version": "1.0.1", + "bundled": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tar": { + "version": "4.4.13", + "bundled": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "minipass": { + "version": "2.9.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + } + } + }, + "term-size": { + "version": "1.2.0", + "bundled": true, + "requires": { + "execa": "^0.7.0" + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true + }, + "through": { + "version": "2.3.8", + "bundled": true + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "timed-out": { + "version": "4.0.1", + "bundled": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true + }, + "tough-cookie": { + "version": "2.4.3", + "bundled": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "bundled": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true + }, + "umask": { + "version": "1.1.0", + "bundled": true + }, + "unique-filename": { + "version": "1.1.1", + "bundled": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.0", + "bundled": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "bundled": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "bundled": true + }, + "unzip-response": { + "version": "2.0.1", + "bundled": true + }, + "update-notifier": { + "version": "2.5.0", + "bundled": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "bundled": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "util-extend": { + "version": "1.0.3", + "bundled": true + }, + "util-promisify": { + "version": "2.1.0", + "bundled": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "uuid": { + "version": "3.3.3", + "bundled": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "bundled": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "which": { + "version": "1.3.1", + "bundled": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "requires": { + "string-width": "^1.0.2" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "widest-line": { + "version": "2.0.1", + "bundled": true, + "requires": { + "string-width": "^2.1.1" + } + }, + "worker-farm": { + "version": "1.7.0", + "bundled": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "write-file-atomic": { + "version": "2.4.3", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "bundled": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true + }, + "y18n": { + "version": "4.0.0", + "bundled": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true + }, + "yargs": { + "version": "11.1.1", + "bundled": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + }, + "dependencies": { + "y18n": { + "version": "3.2.1", + "bundled": true + } + } + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", diff --git a/package.json b/package.json index a71be27..d07d2e0 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,9 @@ "dependencies": { "@types/chalk": "^2.2.0", "@types/lodash": "^4.14.157", + "@types/md5": "^2.2.0", "@types/node": "^14.0.13", + "@types/npm": "^2.0.31", "@types/yargs": "^15.0.5", "async-csv": "^2.1.3", "axios": "^0.19.2", @@ -38,7 +40,9 @@ "jsondiffpatch": "^0.4.1", "lint-staged": "^10.2.11", "lodash": "^4.17.15", + "md5": "^2.2.1", "mongodb": "^3.5.3", + "npm": "^6.14.6", "query-string": "^6.13.1", "request": "^2.88.2", "request-promise": "^4.2.5", From 7fffcd5b281d21d0bb47887edc24b1aea70f79ab Mon Sep 17 00:00:00 2001 From: David Blackman Date: Thu, 9 Jul 2020 19:37:37 -0400 Subject: [PATCH 5/5] html output is pretty good now --- lib/compare/TODO.md | 8 -- lib/compare/change.ts | 2 +- lib/compare/compare.ts | 16 ++- lib/compare/formatters/compare-formatter.ts | 6 +- lib/compare/formatters/compare.html | 125 ++++++++++++++++++-- lib/compare/formatters/console-formatter.ts | 29 ++++- lib/compare/formatters/html-formatter.ts | 13 +- lib/compare/formatters/json-formatter.ts | 52 +++++++- lib/run-query.ts | 12 +- package-lock.json | 18 +++ package.json | 2 + 11 files changed, 245 insertions(+), 38 deletions(-) diff --git a/lib/compare/TODO.md b/lib/compare/TODO.md index 7390f94..7bfb9e1 100644 --- a/lib/compare/TODO.md +++ b/lib/compare/TODO.md @@ -1,14 +1,6 @@ -- html viewer mode ---- add in some info about the run ---- summary mode - -- overall add performance characteristics - - - check that api is still working - think one last time about the config file - write a better README - - save gold set mode - compare to golden set \ No newline at end of file diff --git a/lib/compare/change.ts b/lib/compare/change.ts index f7c5059..7f17f1a 100644 --- a/lib/compare/change.ts +++ b/lib/compare/change.ts @@ -4,7 +4,7 @@ import * as queryString from 'query-string'; export type Change = { params: queryString.ParsedQuery; - delta: unknown; + delta?: unknown; oldResponse: AxiosResponse; newResponse: AxiosResponse; }; diff --git a/lib/compare/compare.ts b/lib/compare/compare.ts index afb70c1..2e87080 100644 --- a/lib/compare/compare.ts +++ b/lib/compare/compare.ts @@ -107,7 +107,7 @@ async function compareQuery({ newApiEnv: ApiEnv; query: string; argv: ParsedArgs; -}): Promise { +}): Promise { const [endpoint, paramsString] = query.split('?'); const params = queryString.parse(`${paramsString}&${argv.extra_params}`); delete params.undefined; @@ -130,10 +130,6 @@ async function compareQuery({ const delta = differ.diff(oldResponse.data, newResponse.data); - if (!delta && !argv.unchanged) { - return undefined; - } - return { params, delta, @@ -163,27 +159,29 @@ async function compareQueries({ argv: ParsedArgs; formatter: CompareFormatter }) { - let numQueriesRun = 0; + const oldResponseTimes: number[] = []; + const newResponseTimes: number[] = []; await Bluebird.map( queries, async (query: string) => { formatter.queryRan(); - numQueriesRun += 1; const change = await compareQuery({ oldApiEnv, newApiEnv, query, argv, }); - if (change) { + if (change.delta || argv.unchanged) { formatter.logChange(change); } + oldResponseTimes.push((change.oldResponse as any).duration); + newResponseTimes.push((change.newResponse as any).duration); }, { concurrency: argv.concurrency }, ); - formatter.finished(); + formatter.finished({ oldResponseTimes, newResponseTimes }); } const argv = parseArgv() as ParsedArgs; diff --git a/lib/compare/formatters/compare-formatter.ts b/lib/compare/formatters/compare-formatter.ts index a317874..5b31927 100644 --- a/lib/compare/formatters/compare-formatter.ts +++ b/lib/compare/formatters/compare-formatter.ts @@ -17,12 +17,15 @@ export abstract class CompareFormatter { abstract queryRan(): void; - abstract finished(): void; + abstract finished({ oldResponseTimes, newResponseTimes }: + { oldResponseTimes: number[], newResponseTimes: number[] }): void; oldApiEnv: ApiEnv; newApiEnv: ApiEnv; + startDate: Date; + constructor({ oldApiEnv, newApiEnv, @@ -31,5 +34,6 @@ export abstract class CompareFormatter { this.oldApiEnv = oldApiEnv; this.newApiEnv = newApiEnv; this.totalQueries = totalQueries; + this.startDate = new Date(); } } diff --git a/lib/compare/formatters/compare.html b/lib/compare/formatters/compare.html index c29c19b..b454e2a 100644 --- a/lib/compare/formatters/compare.html +++ b/lib/compare/formatters/compare.html @@ -34,22 +34,22 @@ } .change[data-quality="unscored"], - .scoreboard .unscored { + div.unscored, tr.unscored { background: beige; } .change[data-quality="better"], - .scoreboard .better { + div.better, tr.better { background: lightgreen; } .change[data-quality="worse"], - .scoreboard .worse { + div.worse, tr.worse{ background: lightcoral; } .change[data-quality="neutral"], - .scoreboard .neutral { + div.neutral, tr.neutral { background: lightgrey; } @@ -269,8 +269,56 @@ +
+
+ + + +