From 0490f98e686496bc98244df48ff35588e43c80a9 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Tue, 28 Nov 2017 17:04:14 -0800 Subject: [PATCH 01/28] add support for argument aliases --- src/cli/cli.ts | 8 +-- src/cli/shared/configuration.ts | 121 ++++++++++++++++++++++---------- src/cli/verify-sandboxes.ts | 2 +- 3 files changed, 90 insertions(+), 41 deletions(-) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index cbb54877..694bb200 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -14,7 +14,7 @@ async function run() { const rawArgs = process.argv.slice(2); const config = new Configuration(rawArgs); - let configFile = path.resolve(config.configFilePath); + let configFile = path.resolve(config.switches.config.value); let playgroundConfig; try { playgroundConfig = require(configFile.replace(/.json$/, '')); @@ -26,15 +26,15 @@ async function run() { const sandboxesPath = await build(playgroundConfig.sourceRoot); config.port = playgroundConfig.angularCli.port ? playgroundConfig.angularCli.port : 4201; - if (config.runWatch) { + if (!config.flags.noWatch.active) { startWatch(playgroundConfig, () => build(playgroundConfig.sourceRoot)); } - if (config.runAngularCliServe && playgroundConfig.angularCli) { + if (!config.flags.noServe.active && playgroundConfig.angularCli) { runAngularCli(playgroundConfig.angularCli); } - if (config.runCheckErrors) { + if (config.flags.runCheckErrors.active) { verifySandboxes(config, sandboxesPath); } } diff --git a/src/cli/shared/configuration.ts b/src/cli/shared/configuration.ts index d05d64fc..838efc85 100644 --- a/src/cli/shared/configuration.ts +++ b/src/cli/shared/configuration.ts @@ -1,31 +1,54 @@ +interface Flags { + [key: string]: { + aliases: string[]; + active: boolean; + }; +} + +interface Switches { + [key: string]: { + aliases: string[]; + value: any; + }; +} /** * Configuration object used to parse and assign command line arguments */ export class Configuration { - private supportedFlags = { - noWatch: '--no-watch', - noServe: '--no-serve', - checkErrs: '--check-errors', - randomScenario: '--random-scenario' + flags: Flags = { + noWatch: { + aliases: ['--no-watch', '-W'], + active: false + }, + noServe: { + aliases: ['--no-serve', '-S'], + active: false + }, + runCheckErrors: { + aliases: ['--check-errors', '-E'], + active: false + }, + randomScenario: { + aliases: ['--random-scenario', '-R'], + active: false + } }; - private supportedArguments = { - config: '--config' + switches: Switches = { + config: { + aliases: ['--config', '-C'], + value: 'angular-playground.json' + } }; - runWatch: boolean; - runAngularCliServe: boolean; - runCheckErrors: boolean; - randomScenario: boolean; - configFilePath: string; port: number; timeoutAttempts = 20; chromeArguments = [ '--disable-gpu', '--no-sandbox' ]; constructor(rawArgv: string[]) { - const { flags, args } = this.getParsedArguments(rawArgv); - this.configureFlags(flags); - this.configureArguments(args); + const { argvFlags, argvSwitches } = this.getParsedArguments(rawArgv); + this.configureFlags(argvFlags); + this.configureArguments(argvSwitches); } get baseUrl(): string { @@ -33,23 +56,24 @@ export class Configuration { } // Boolean flags - private configureFlags(flags: string[]) { - this.runWatch = flags.indexOf(this.supportedFlags.noWatch) === -1; - this.runAngularCliServe = flags.indexOf(this.supportedFlags.noServe) === -1; - this.runCheckErrors = flags.indexOf(this.supportedFlags.checkErrs) !== -1; - this.randomScenario = flags.indexOf(this.supportedFlags.randomScenario) !== -1; + private configureFlags(argvFlags: string[]) { + Object.keys(this.flags).forEach(flag => { + const options = this.flags[flag]; + options.active = this.argInList(options.aliases, argvFlags); + }); } // Arguments that may have values attached - private configureArguments(args: string[]) { - const configIndex = args.indexOf(this.supportedArguments.config); - if (configIndex !== -1) { - this.configFilePath = this.getArgValue(configIndex, args); - } else if (args.length > 0) { - this.configFilePath = args[0]; - } else { - this.configFilePath = 'angular-playground.json'; - } + private configureArguments(argvSwitches: string[]) { + Object.keys(this.switches).forEach(switchName => { + const switchIndex = this.argInListWithIndex(this.switches[switchName].aliases, argvSwitches); + if (switchIndex !== -1) { + this.switches[switchName].value = this.getArgValue(switchIndex, argvSwitches); + } else if (switchName === 'config' && argvSwitches.length > 0) { + // Support default argument for config path + this.switches[switchName].value = argvSwitches[0]; + } + }); } /** @@ -57,17 +81,21 @@ export class Configuration { * @param supportedFlags - Accepted command line flags * @param args - Process arguments */ - private getParsedArguments(args: string[]): { flags: string[], args: string[] } { - const flags: string[] = []; + private getParsedArguments(argv: string[]): { argvFlags: string[], argvSwitches: string[] } { + const argvFlags: string[] = []; - args = args.reduce((accr, value) => { - Object.keys(this.supportedFlags) - .map(key => this.supportedFlags[key]) - .indexOf(value) > -1 ? flags.push(value) : accr.push(value); + const argvSwitches = argv.reduce((accr, value) => { + Object.keys(this.flags) + .map(key => this.flags[key].aliases) + .forEach(aliases => { + this.argInList([value], aliases) + ? argvFlags.push(value) + : accr.push(value); + }); return accr; }, []); - return { flags, args }; + return { argvFlags, argvSwitches }; } /** @@ -79,4 +107,25 @@ export class Configuration { private getArgValue(startingIndex: number, args: string[]): string { return args[startingIndex + 1]; } + + /** + * Returns whether or not the argument or its alias is present in the list of all arguments + * @param argumentWithAlias - List of arguments and aliases + * @param allArguments - Process argv + */ + private argInList(argumentWithAlias: string[], allArguments: string[]): boolean { + return this.argInListWithIndex(argumentWithAlias, allArguments) !== -1; + } + + /** + * Returns index of the argument or its alias in the list of all arguments. -1 if not found. + * @param argumentWithAlias - List of arguments and aliases + * @param allArguments - Process argv + */ + private argInListWithIndex(argumentWithAlias: string[], allArguments: string[]): number { + const argumentOrAliasIndex = allArguments.findIndex(argv => { + return argumentWithAlias.indexOf(argv) !== -1; + }); + return argumentOrAliasIndex; + } } diff --git a/src/cli/verify-sandboxes.ts b/src/cli/verify-sandboxes.ts index 399a7f5d..74e7a191 100644 --- a/src/cli/verify-sandboxes.ts +++ b/src/cli/verify-sandboxes.ts @@ -36,7 +36,7 @@ async function main (configuration: Configuration, sandboxesPath: string) { args: configuration.chromeArguments }); - const scenarios = getSandboxMetadata(configuration.baseUrl, configuration.randomScenario, sandboxesPath); + const scenarios = getSandboxMetadata(configuration.baseUrl, configuration.flags.randomScenario.active, sandboxesPath); console.log(`Retrieved ${scenarios.length} scenarios.\n`); for (let i = 0; i < scenarios.length; i++) { await openScenarioInNewPage(scenarios[i], configuration.timeoutAttempts); From 7cc21963382bc751ec909b5a04a495799c0697f7 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Tue, 28 Nov 2017 17:10:27 -0800 Subject: [PATCH 02/28] cleanup --- src/cli/shared/configuration.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/cli/shared/configuration.ts b/src/cli/shared/configuration.ts index 838efc85..22e27d2a 100644 --- a/src/cli/shared/configuration.ts +++ b/src/cli/shared/configuration.ts @@ -46,7 +46,7 @@ export class Configuration { chromeArguments = [ '--disable-gpu', '--no-sandbox' ]; constructor(rawArgv: string[]) { - const { argvFlags, argvSwitches } = this.getParsedArguments(rawArgv); + const { argvFlags, argvSwitches } = this.separateRawArguments(rawArgv); this.configureFlags(argvFlags); this.configureArguments(argvSwitches); } @@ -58,30 +58,31 @@ export class Configuration { // Boolean flags private configureFlags(argvFlags: string[]) { Object.keys(this.flags).forEach(flag => { - const options = this.flags[flag]; - options.active = this.argInList(options.aliases, argvFlags); + const currentFlag = this.flags[flag]; + currentFlag.active = this.argInList(currentFlag.aliases, argvFlags); }); } // Arguments that may have values attached private configureArguments(argvSwitches: string[]) { Object.keys(this.switches).forEach(switchName => { - const switchIndex = this.argInListWithIndex(this.switches[switchName].aliases, argvSwitches); + const currentSwitch = this.switches[switchName]; + const switchIndex = this.argInListWithIndex(currentSwitch.aliases, argvSwitches); + if (switchIndex !== -1) { - this.switches[switchName].value = this.getArgValue(switchIndex, argvSwitches); + currentSwitch.value = this.getArgValue(switchIndex, argvSwitches); } else if (switchName === 'config' && argvSwitches.length > 0) { // Support default argument for config path - this.switches[switchName].value = argvSwitches[0]; + currentSwitch.value = argvSwitches[0]; } }); } /** - * Separates accepted command line arguments from other ts-node arguments - * @param supportedFlags - Accepted command line flags - * @param args - Process arguments + * Separates flags and switches + * @param argv - Process arguments */ - private getParsedArguments(argv: string[]): { argvFlags: string[], argvSwitches: string[] } { + private separateRawArguments(argv: string[]): { argvFlags: string[], argvSwitches: string[] } { const argvFlags: string[] = []; const argvSwitches = argv.reduce((accr, value) => { From 945f181afc25ec6aa86daa1161fdec8b998f5a99 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Tue, 28 Nov 2017 17:20:46 -0800 Subject: [PATCH 03/28] migrate port and timeout attempts as cli params --- src/cli/cli.ts | 4 +++- src/cli/shared/configuration.ts | 13 ++++++++++--- src/cli/verify-sandboxes.ts | 6 +++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 694bb200..252524af 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -24,7 +24,9 @@ async function run() { } const sandboxesPath = await build(playgroundConfig.sourceRoot); - config.port = playgroundConfig.angularCli.port ? playgroundConfig.angularCli.port : 4201; + if (playgroundConfig.angularCli.port) { + config.switches.port.value = playgroundConfig.angularCli.port; + } if (!config.flags.noWatch.active) { startWatch(playgroundConfig, () => build(playgroundConfig.sourceRoot)); diff --git a/src/cli/shared/configuration.ts b/src/cli/shared/configuration.ts index 22e27d2a..b309027b 100644 --- a/src/cli/shared/configuration.ts +++ b/src/cli/shared/configuration.ts @@ -11,6 +11,7 @@ interface Switches { value: any; }; } + /** * Configuration object used to parse and assign command line arguments */ @@ -38,11 +39,17 @@ export class Configuration { config: { aliases: ['--config', '-C'], value: 'angular-playground.json' + }, + port: { + aliases: ['--port', '-P'], + value: 4201 + }, + timeoutAttempts: { + aliases: ['--timeout-attempts', '-TA'], + value: 90 } }; - port: number; - timeoutAttempts = 20; chromeArguments = [ '--disable-gpu', '--no-sandbox' ]; constructor(rawArgv: string[]) { @@ -52,7 +59,7 @@ export class Configuration { } get baseUrl(): string { - return `http://localhost:${this.port}`; + return `http://localhost:${this.switches.port.value}`; } // Boolean flags diff --git a/src/cli/verify-sandboxes.ts b/src/cli/verify-sandboxes.ts index 74e7a191..5af227e9 100644 --- a/src/cli/verify-sandboxes.ts +++ b/src/cli/verify-sandboxes.ts @@ -29,7 +29,7 @@ export async function verifySandboxes(configuration: Configuration, sandboxesPat ///////////////////////////////// async function main (configuration: Configuration, sandboxesPath: string) { - let timeoutAttempts = configuration.timeoutAttempts; + const timeoutAttempts = configuration.switches.timeoutAttempts.value; browser = await puppeteer.launch({ headless: true, handleSIGINT: false, @@ -39,7 +39,7 @@ async function main (configuration: Configuration, sandboxesPath: string) { const scenarios = getSandboxMetadata(configuration.baseUrl, configuration.flags.randomScenario.active, sandboxesPath); console.log(`Retrieved ${scenarios.length} scenarios.\n`); for (let i = 0; i < scenarios.length; i++) { - await openScenarioInNewPage(scenarios[i], configuration.timeoutAttempts); + await openScenarioInNewPage(scenarios[i], timeoutAttempts); } browser.close(); @@ -73,7 +73,7 @@ async function openScenarioInNewPage(scenario: ScenarioSummary, timeoutAttempts: await page.goto(scenario.url); } catch (e) { await page.close(); - await delay(5000); + await delay(1000); console.log(`Attempting to connect. (Attempts Remaining: ${timeoutAttempts})`); await openScenarioInNewPage(scenario, timeoutAttempts - 1); } From f40c53aab0085015afcb2cd8850590757b7c9868 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Wed, 29 Nov 2017 11:40:07 -0800 Subject: [PATCH 04/28] add method to locate first available port --- package-lock.json | 12 +++---- package.json | 1 + src/cli/cli.ts | 10 +++++- src/cli/run-angular-cli.ts | 16 +++++---- src/cli/shared/configuration.ts | 28 +++++++++++++--- src/cli/shared/find-port.ts | 59 +++++++++++++++++++++++++++++++++ 6 files changed, 106 insertions(+), 20 deletions(-) create mode 100644 src/cli/shared/find-port.ts diff --git a/package-lock.json b/package-lock.json index 2ff8ea78..a305ed14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -186,10 +186,9 @@ "dev": true }, "async": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.5.0.tgz", - "integrity": "sha512-e+lJAJeNWuPCNyxZKOBdaJGyLGHugXVQtrAwtuAe2vhxTYxFTKE73p8JuTmdH0qdQZtDvI4dhJwjZc5zsfIsYw==", - "dev": true, + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", "requires": { "lodash": "4.17.4" }, @@ -197,8 +196,7 @@ "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", - "dev": true + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" } } }, @@ -998,7 +996,7 @@ "integrity": "sha1-oUXyFveaDabJxrF7zkfhmQGM2Dg=", "dev": true, "requires": { - "async": "2.5.0", + "async": "2.6.0", "clone": "1.0.2", "es6-templates": "0.2.3", "extend": "3.0.1", diff --git a/package.json b/package.json index a795bfed..6df3bee3 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "zone.js": ">=0.8.14" }, "dependencies": { + "async": "^2.6.0", "node-watch": "^0.4.1" }, "devDependencies": { diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 252524af..f8000fe4 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -5,6 +5,7 @@ import { startWatch } from './start-watch'; import { runAngularCli } from './run-angular-cli'; import { Configuration } from './shared/configuration'; import { verifySandboxes } from './verify-sandboxes'; +import { findFirstFreePort } from './shared/find-port'; (async () => { await run(); @@ -23,11 +24,18 @@ async function run() { process.exit(1); } + // Parity between command line arguments and configuration file + config.overrideWithFile(playgroundConfig); + const sandboxesPath = await build(playgroundConfig.sourceRoot); if (playgroundConfig.angularCli.port) { config.switches.port.value = playgroundConfig.angularCli.port; } + if (config.flags.checkErrors.active) { + // get port dynamically + } + if (!config.flags.noWatch.active) { startWatch(playgroundConfig, () => build(playgroundConfig.sourceRoot)); } @@ -36,7 +44,7 @@ async function run() { runAngularCli(playgroundConfig.angularCli); } - if (config.flags.runCheckErrors.active) { + if (config.flags.checkErrors.active) { verifySandboxes(config, sandboxesPath); } } diff --git a/src/cli/run-angular-cli.ts b/src/cli/run-angular-cli.ts index 34113a47..fcb850d1 100644 --- a/src/cli/run-angular-cli.ts +++ b/src/cli/run-angular-cli.ts @@ -4,13 +4,15 @@ const childProcess = require('child_process'); export const runAngularCli = (angularCliConfig) => { let port = angularCliConfig.port ? angularCliConfig.port : 4201; - let cliName = '@angular/cli'; - try { - fs.accessSync(path.resolve('node_modules/@angular/cli/bin/ng')); - } catch (e) { - cliName = 'angular-cl'; - } - let cliPath = `node_modules/${cliName}/bin/ng`; + // TODO: Add config option + const cliPath = 'node_modules/@angular/cli/bin/ng'; + // let cliName = '@angular/cli'; + // try { + // fs.accessSync(path.resolve('node_modules/@angular/cli/bin/ng')); + // } catch (e) { + // cliName = 'angular-cl'; + // } + // let cliPath = `node_modules/${cliName}/bin/ng`; let args = [cliPath, 'serve', '-no-progress']; args.push('--port'); args.push(port.toString()); diff --git a/src/cli/shared/configuration.ts b/src/cli/shared/configuration.ts index b309027b..481e2921 100644 --- a/src/cli/shared/configuration.ts +++ b/src/cli/shared/configuration.ts @@ -25,7 +25,7 @@ export class Configuration { aliases: ['--no-serve', '-S'], active: false }, - runCheckErrors: { + checkErrors: { aliases: ['--check-errors', '-E'], active: false }, @@ -40,16 +40,17 @@ export class Configuration { aliases: ['--config', '-C'], value: 'angular-playground.json' }, - port: { - aliases: ['--port', '-P'], - value: 4201 - }, + // port: { + // aliases: ['--port', '-P'], + // value: 4201 + // }, timeoutAttempts: { aliases: ['--timeout-attempts', '-TA'], value: 90 } }; + // Used to tailor the version of headless chromium ran by puppeteer chromeArguments = [ '--disable-gpu', '--no-sandbox' ]; constructor(rawArgv: string[]) { @@ -62,6 +63,23 @@ export class Configuration { return `http://localhost:${this.switches.port.value}`; } + /** + * Override flags and switches with angular playground configuration JSON file + * @param playgroundConfig + */ + overrideWithFile(playgroundConfig: any) { + Object.keys(playgroundConfig).forEach(key => { + const settingValue = playgroundConfig[key]; + if (this.flags.hasOwnProperty(key)) { + this.flags[key].active = settingValue; + } + + if (this.switches.hasOwnProperty(key)) { + this.switches[key].value = settingValue; + } + }); + } + // Boolean flags private configureFlags(argvFlags: string[]) { Object.keys(this.flags).forEach(flag => { diff --git a/src/cli/shared/find-port.ts b/src/cli/shared/find-port.ts new file mode 100644 index 00000000..b64d1582 --- /dev/null +++ b/src/cli/shared/find-port.ts @@ -0,0 +1,59 @@ +import * as net from 'net'; +// Legacy import +const detect = require('async/detect'); + +/** + * Function that detects the first port not in use in a given range + * e.g. + * findFirstFreePort('127.0.0.1', 8000, 8030, (port) => { + * console.log(port) + * }); + * + * @param host - Host to check ports + * @param start - Starting point for range + * @param end - Ending point for range + * @param callback - Callback on result + */ +export function findFirstFreePort(host: string, start: number, end: number, callback: Function) { + const ports = []; + for (let i = start; i < end; i++) { + ports.push(i); + } + + const probe = (port: number, cb: Function) => { + let calledOnce = false; + let connected = false; + + const server = net.createServer().listen(port, host); + const timeoutRef = setTimeout(() => { + calledOnce = true; + cb(port, false); + }, 2000); + + // Active timeout won't require node event loop to remain active + timeoutRef.unref(); + + server.on('listening', () => { + clearTimeout(timeoutRef); + if (server) server.close(); + if (!calledOnce) { + calledOnce = true; + cb(port, true); + } + }); + + server.on('error', (err: any) => { + clearTimeout(timeoutRef); + let result = true; + if (err.code === 'EADDRINUSE' || err.code === 'EACCES') { + result = false; + } + if (!calledOnce) { + calledOnce = true; + cb(port, result); + } + }); + }; + + detect(ports, probe, callback); +} From 9dc10668abadbe7aa48eb2e1ab3602d94089319f Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Wed, 29 Nov 2017 15:49:56 -0800 Subject: [PATCH 05/28] refactor flags --- src/cli/cli.ts | 26 +++-- src/cli/shared/configuration.ts | 191 +++++++++++--------------------- src/cli/verify-sandboxes.ts | 12 +- 3 files changed, 88 insertions(+), 141 deletions(-) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index f8000fe4..72afc844 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -13,10 +13,12 @@ import { findFirstFreePort } from './shared/find-port'; async function run() { const rawArgs = process.argv.slice(2); + console.log(rawArgs); const config = new Configuration(rawArgs); + let sandboxPort, playgroundConfig; - let configFile = path.resolve(config.switches.config.value); - let playgroundConfig; + // let configFile = path.resolve(config.switches.config.value); + let configFile = path.resolve(config.flags.config.value); try { playgroundConfig = require(configFile.replace(/.json$/, '')); } catch (e) { @@ -25,26 +27,28 @@ async function run() { } // Parity between command line arguments and configuration file - config.overrideWithFile(playgroundConfig); + config.applyConfigurationFile(playgroundConfig); + console.log(config); const sandboxesPath = await build(playgroundConfig.sourceRoot); - if (playgroundConfig.angularCli.port) { - config.switches.port.value = playgroundConfig.angularCli.port; - } - if (config.flags.checkErrors.active) { + if (config.flags.checkErrors.value) { // get port dynamically + findFirstFreePort('127.0.0.1', 7000, 9000, port => { + sandboxPort = port; + playgroundConfig.angularCli.port = port; + }); } - if (!config.flags.noWatch.active) { + if (!config.flags.noWatch.value) { startWatch(playgroundConfig, () => build(playgroundConfig.sourceRoot)); } - if (!config.flags.noServe.active && playgroundConfig.angularCli) { + if (!config.flags.noServe.value && playgroundConfig.angularCli) { runAngularCli(playgroundConfig.angularCli); } - if (config.flags.checkErrors.active) { - verifySandboxes(config, sandboxesPath); + if (config.flags.checkErrors.value) { + verifySandboxes(config, sandboxesPath, sandboxPort); } } diff --git a/src/cli/shared/configuration.ts b/src/cli/shared/configuration.ts index 481e2921..77cdcf08 100644 --- a/src/cli/shared/configuration.ts +++ b/src/cli/shared/configuration.ts @@ -1,15 +1,12 @@ -interface Flags { - [key: string]: { - aliases: string[]; - active: boolean; - }; +class Flag { + constructor( + public aliases: string[], + public value: any, + public required = false + ) {} } - -interface Switches { - [key: string]: { - aliases: string[]; - value: any; - }; +interface Flags { + [key: string]: Flag; } /** @@ -17,141 +14,85 @@ interface Switches { */ export class Configuration { flags: Flags = { - noWatch: { - aliases: ['--no-watch', '-W'], - active: false - }, - noServe: { - aliases: ['--no-serve', '-S'], - active: false - }, - checkErrors: { - aliases: ['--check-errors', '-E'], - active: false - }, - randomScenario: { - aliases: ['--random-scenario', '-R'], - active: false - } - }; - - switches: Switches = { - config: { - aliases: ['--config', '-C'], - value: 'angular-playground.json' - }, - // port: { - // aliases: ['--port', '-P'], - // value: 4201 - // }, - timeoutAttempts: { - aliases: ['--timeout-attempts', '-TA'], - value: 90 - } + noWatch: new Flag(['--no-watch'], false), + noServe: new Flag(['--no-serve'], false), + checkErrors: new Flag(['--check-errors'], false), + randomScenario: new Flag(['--random-scenario'], false), + sourceRoot: new Flag(['--src', '-S'], null, true), + config: new Flag(['--config', '-C'], 'angular-playground.json'), + timeout: new Flag(['--timeout'], 90), + ngCliApp: new Flag(['--ng-cli-app'], 'playground'), + ngCliEnv: new Flag(['--ng-cli-env'], 'environments/environment.ts'), + ngCliPort: new Flag(['--ng-cli-port'], 4201), }; // Used to tailor the version of headless chromium ran by puppeteer chromeArguments = [ '--disable-gpu', '--no-sandbox' ]; constructor(rawArgv: string[]) { - const { argvFlags, argvSwitches } = this.separateRawArguments(rawArgv); - this.configureFlags(argvFlags); - this.configureArguments(argvSwitches); - } - - get baseUrl(): string { - return `http://localhost:${this.switches.port.value}`; + // Apply command line arguments + rawArgv.forEach((argv, i) => { + const matchingFlagName = this.findMatchingFlagName(argv); + + if (matchingFlagName) { + const matchingFlag = this.flags[matchingFlagName]; + if (typeof matchingFlag.value === 'boolean') { + matchingFlag.value = true; + } else { + // Value follows flag + matchingFlag.value = rawArgv[i + 1]; + } + } + }); } /** * Override flags and switches with angular playground configuration JSON file * @param playgroundConfig */ - overrideWithFile(playgroundConfig: any) { + applyConfigurationFile(playgroundConfig: any) { Object.keys(playgroundConfig).forEach(key => { - const settingValue = playgroundConfig[key]; - if (this.flags.hasOwnProperty(key)) { - this.flags[key].active = settingValue; - } - - if (this.switches.hasOwnProperty(key)) { - this.switches[key].value = settingValue; - } - }); - } - - // Boolean flags - private configureFlags(argvFlags: string[]) { - Object.keys(this.flags).forEach(flag => { - const currentFlag = this.flags[flag]; - currentFlag.active = this.argInList(currentFlag.aliases, argvFlags); - }); - } - - // Arguments that may have values attached - private configureArguments(argvSwitches: string[]) { - Object.keys(this.switches).forEach(switchName => { - const currentSwitch = this.switches[switchName]; - const switchIndex = this.argInListWithIndex(currentSwitch.aliases, argvSwitches); - - if (switchIndex !== -1) { - currentSwitch.value = this.getArgValue(switchIndex, argvSwitches); - } else if (switchName === 'config' && argvSwitches.length > 0) { - // Support default argument for config path - currentSwitch.value = argvSwitches[0]; + if (key !== 'angularCli') { + if (this.flags.hasOwnProperty(key)) { + this.flags[key].value = playgroundConfig[key]; + } + } else { + // Nested flag + Object.keys(playgroundConfig.angularCli).forEach(cliKey => { + this.setAngularCliFlag(cliKey, playgroundConfig[key][cliKey]); + }); } }); - } - - /** - * Separates flags and switches - * @param argv - Process arguments - */ - private separateRawArguments(argv: string[]): { argvFlags: string[], argvSwitches: string[] } { - const argvFlags: string[] = []; - - const argvSwitches = argv.reduce((accr, value) => { - Object.keys(this.flags) - .map(key => this.flags[key].aliases) - .forEach(aliases => { - this.argInList([value], aliases) - ? argvFlags.push(value) - : accr.push(value); - }); - return accr; - }, []); - return { argvFlags, argvSwitches }; + const missingFlag = this.getAnyUnfulfilledFlag(); + if (missingFlag !== undefined) { + throw new Error(`CLI flag ${missingFlag.aliases[0]} needs a value.`); + } } - /** - * Gets the value of an argument from list of args (next consecutive argument) - * e.g. --config ./src/ - * @param startingIndex - Index of argument - * @param args - list of args - */ - private getArgValue(startingIndex: number, args: string[]): string { - return args[startingIndex + 1]; + private setAngularCliFlag(key: string, value: any) { + switch (key) { + case 'appName': + this.flags.ngCliApp.value = value; + break; + case 'port': + this.flags.ngCliPort.value = value; + break; + case 'environment': + this.flags.ngCliEnv.value = value; + } } - /** - * Returns whether or not the argument or its alias is present in the list of all arguments - * @param argumentWithAlias - List of arguments and aliases - * @param allArguments - Process argv - */ - private argInList(argumentWithAlias: string[], allArguments: string[]): boolean { - return this.argInListWithIndex(argumentWithAlias, allArguments) !== -1; + private findMatchingFlagName(alias: string): string { + const matchingIndex = Object.keys(this.flags) + .map(key => this.flags[key].aliases) + .findIndex(aliases => aliases.indexOf(alias) !== -1); + return matchingIndex > -1 ? Object.keys(this.flags)[matchingIndex] : undefined; } - /** - * Returns index of the argument or its alias in the list of all arguments. -1 if not found. - * @param argumentWithAlias - List of arguments and aliases - * @param allArguments - Process argv - */ - private argInListWithIndex(argumentWithAlias: string[], allArguments: string[]): number { - const argumentOrAliasIndex = allArguments.findIndex(argv => { - return argumentWithAlias.indexOf(argv) !== -1; - }); - return argumentOrAliasIndex; + private getAnyUnfulfilledFlag(): Flag { + return Object.keys(this.flags) + .map(key => this.flags[key]) + .find(flag => flag.required && flag.value === null); } } diff --git a/src/cli/verify-sandboxes.ts b/src/cli/verify-sandboxes.ts index 5af227e9..2c00ad5e 100644 --- a/src/cli/verify-sandboxes.ts +++ b/src/cli/verify-sandboxes.ts @@ -16,27 +16,29 @@ interface ScenarioSummary { let browser: any; let currentScenario = ''; const reporter = new ErrorReporter(); +let hostUrl = ''; // Ensure Chromium instances are destroyed on err process.on('unhandledRejection', () => { if (browser) browser.close(); }); -export async function verifySandboxes(configuration: Configuration, sandboxesPath: string) { - await main(configuration, sandboxesPath); +export async function verifySandboxes(configuration: Configuration, sandboxesPath: string, port: number) { + hostUrl = `localhost:${port}`; + await main(configuration, sandboxesPath, port); } ///////////////////////////////// -async function main (configuration: Configuration, sandboxesPath: string) { - const timeoutAttempts = configuration.switches.timeoutAttempts.value; +async function main (configuration: Configuration, sandboxesPath: string, port: number) { + const timeoutAttempts = configuration.flags.timeout.value; browser = await puppeteer.launch({ headless: true, handleSIGINT: false, args: configuration.chromeArguments }); - const scenarios = getSandboxMetadata(configuration.baseUrl, configuration.flags.randomScenario.active, sandboxesPath); + const scenarios = getSandboxMetadata(hostUrl, configuration.flags.randomScenario.value, sandboxesPath); console.log(`Retrieved ${scenarios.length} scenarios.\n`); for (let i = 0; i < scenarios.length; i++) { await openScenarioInNewPage(scenarios[i], timeoutAttempts); From 4915488e31bbaa1965853e634feb9a94665bd9b4 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Wed, 29 Nov 2017 15:58:06 -0800 Subject: [PATCH 06/28] run angular CLi now uses config flags --- src/cli/cli.ts | 2 +- src/cli/run-angular-cli.ts | 26 +++++++++----------------- src/cli/shared/configuration.ts | 8 +++++++- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 72afc844..0a15eb85 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -45,7 +45,7 @@ async function run() { } if (!config.flags.noServe.value && playgroundConfig.angularCli) { - runAngularCli(playgroundConfig.angularCli); + runAngularCli(config, playgroundConfig.angularCli); } if (config.flags.checkErrors.value) { diff --git a/src/cli/run-angular-cli.ts b/src/cli/run-angular-cli.ts index fcb850d1..f6a2580a 100644 --- a/src/cli/run-angular-cli.ts +++ b/src/cli/run-angular-cli.ts @@ -1,26 +1,18 @@ +import { Configuration } from './shared/configuration'; + const fs = require('fs'); const path = require('path'); const childProcess = require('child_process'); -export const runAngularCli = (angularCliConfig) => { - let port = angularCliConfig.port ? angularCliConfig.port : 4201; - // TODO: Add config option - const cliPath = 'node_modules/@angular/cli/bin/ng'; - // let cliName = '@angular/cli'; - // try { - // fs.accessSync(path.resolve('node_modules/@angular/cli/bin/ng')); - // } catch (e) { - // cliName = 'angular-cl'; - // } - // let cliPath = `node_modules/${cliName}/bin/ng`; - let args = [cliPath, 'serve', '-no-progress']; +export const runAngularCli = (config: Configuration, angularCliConfig: any) => { + let args = [config.flags.ngCliCmdPath.value, 'serve', '-no-progress']; args.push('--port'); - args.push(port.toString()); - if (angularCliConfig.appName) { - args.push(`-a=${angularCliConfig.appName}`); + args.push(config.flags.ngCliPort.value.toString()); + if (config.flags.ngCliApp.value) { + args.push(`-a=${config.flags.ngCliApp.value}`); } - if (angularCliConfig.environment) { - args.push(`-e=${angularCliConfig.environment}`); + if (config.flags.ngCliEnv.value) { + args.push(`-e=${config.flags.ngCliEnv.value}`); } if (angularCliConfig.args) { args = args.concat(angularCliConfig.args); diff --git a/src/cli/shared/configuration.ts b/src/cli/shared/configuration.ts index 77cdcf08..29fef1d3 100644 --- a/src/cli/shared/configuration.ts +++ b/src/cli/shared/configuration.ts @@ -22,8 +22,9 @@ export class Configuration { config: new Flag(['--config', '-C'], 'angular-playground.json'), timeout: new Flag(['--timeout'], 90), ngCliApp: new Flag(['--ng-cli-app'], 'playground'), - ngCliEnv: new Flag(['--ng-cli-env'], 'environments/environment.ts'), + ngCliEnv: new Flag(['--ng-cli-env'], null), ngCliPort: new Flag(['--ng-cli-port'], 4201), + ngCliCmdPath: new Flag(['--ng-cli-cmd'], 'node_modules/@angular/cli/bin/ng') }; // Used to tailor the version of headless chromium ran by puppeteer @@ -70,6 +71,7 @@ export class Configuration { } } + // TODO: Refactor to use "Flag directories" for future directory support and maintenance private setAngularCliFlag(key: string, value: any) { switch (key) { case 'appName': @@ -80,6 +82,10 @@ export class Configuration { break; case 'environment': this.flags.ngCliEnv.value = value; + break; + case 'cmdPath': + this.flags.ngCliCmdPath.value = value; + break; } } From 299709ec2b00e5fd39a7e11be257c77793ae3e94 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Wed, 29 Nov 2017 16:00:14 -0800 Subject: [PATCH 07/28] watch and build now don't need config dep --- src/cli/cli.ts | 2 +- src/cli/start-watch.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 0a15eb85..1e5ac760 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -41,7 +41,7 @@ async function run() { } if (!config.flags.noWatch.value) { - startWatch(playgroundConfig, () => build(playgroundConfig.sourceRoot)); + startWatch(config.flags.sourceRoot.value, () => build(config.flags.sourceRoot.value)); } if (!config.flags.noServe.value && playgroundConfig.angularCli) { diff --git a/src/cli/start-watch.ts b/src/cli/start-watch.ts index 94b16a88..c4088f94 100644 --- a/src/cli/start-watch.ts +++ b/src/cli/start-watch.ts @@ -1,7 +1,7 @@ const path = require('path'); const watch = require('node-watch'); -export const startWatch = (config, cb) => { +export const startWatch = (sourceRoot, cb) => { let filter = (fn) => { return (filename) => { if (!/node_modules/.test(filename) && /\.sandbox.ts$/.test(filename)) { @@ -9,5 +9,5 @@ export const startWatch = (config, cb) => { } }; }; - watch([path.resolve(config.sourceRoot)], filter(cb)); + watch([path.resolve(sourceRoot)], filter(cb)); }; From dae462c082ff90965640d30f72a61c3cda685407 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Wed, 29 Nov 2017 16:09:33 -0800 Subject: [PATCH 08/28] improve logging for timeout changes --- src/cli/verify-sandboxes.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cli/verify-sandboxes.ts b/src/cli/verify-sandboxes.ts index 2c00ad5e..dcf49eea 100644 --- a/src/cli/verify-sandboxes.ts +++ b/src/cli/verify-sandboxes.ts @@ -41,6 +41,7 @@ async function main (configuration: Configuration, sandboxesPath: string, port: const scenarios = getSandboxMetadata(hostUrl, configuration.flags.randomScenario.value, sandboxesPath); console.log(`Retrieved ${scenarios.length} scenarios.\n`); for (let i = 0; i < scenarios.length; i++) { + console.log(`Checking: ${scenarios[i].name}: ${scenarios[i].description}`); await openScenarioInNewPage(scenarios[i], timeoutAttempts); } @@ -71,12 +72,10 @@ async function openScenarioInNewPage(scenario: ScenarioSummary, timeoutAttempts: currentScenario = scenario.name; try { - console.log(`Checking: ${currentScenario}: ${scenario.description}`); await page.goto(scenario.url); } catch (e) { await page.close(); await delay(1000); - console.log(`Attempting to connect. (Attempts Remaining: ${timeoutAttempts})`); await openScenarioInNewPage(scenario, timeoutAttempts - 1); } } From e7bc6f71275ff733e5cb727385bf80299b3fc9b2 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Wed, 29 Nov 2017 16:38:21 -0800 Subject: [PATCH 09/28] make find-port into an async method --- src/cli/cli.ts | 7 +++---- src/cli/shared/find-port.ts | 8 ++++++-- src/cli/verify-sandboxes.ts | 5 +++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 1e5ac760..474e14bc 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -34,10 +34,9 @@ async function run() { if (config.flags.checkErrors.value) { // get port dynamically - findFirstFreePort('127.0.0.1', 7000, 9000, port => { - sandboxPort = port; - playgroundConfig.angularCli.port = port; - }); + const port = await findFirstFreePort('127.0.0.1', 7000, 9000); + sandboxPort = port; + config.flags.ngCliPort.value = port; } if (!config.flags.noWatch.value) { diff --git a/src/cli/shared/find-port.ts b/src/cli/shared/find-port.ts index b64d1582..36cb914e 100644 --- a/src/cli/shared/find-port.ts +++ b/src/cli/shared/find-port.ts @@ -14,7 +14,7 @@ const detect = require('async/detect'); * @param end - Ending point for range * @param callback - Callback on result */ -export function findFirstFreePort(host: string, start: number, end: number, callback: Function) { +export async function findFirstFreePort(host: string, start: number, end: number) { const ports = []; for (let i = start; i < end; i++) { ports.push(i); @@ -55,5 +55,9 @@ export function findFirstFreePort(host: string, start: number, end: number, call }); }; - detect(ports, probe, callback); + return new Promise(resolve => { + detect(ports, probe, (port: number) => { + resolve(port); + }); + }); } diff --git a/src/cli/verify-sandboxes.ts b/src/cli/verify-sandboxes.ts index dcf49eea..674cb0ee 100644 --- a/src/cli/verify-sandboxes.ts +++ b/src/cli/verify-sandboxes.ts @@ -72,10 +72,15 @@ async function openScenarioInNewPage(scenario: ScenarioSummary, timeoutAttempts: currentScenario = scenario.name; try { + console.log('attempting to hit page'); + console.log(scenario.url); await page.goto(scenario.url); } catch (e) { + console.log('closing page') await page.close(); + console.log('beginning delay') await delay(1000); + console.log('running again') await openScenarioInNewPage(scenario, timeoutAttempts - 1); } } From 39bec874a165327e0ee8e507c89bfc4d1ae82758 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Wed, 29 Nov 2017 16:48:02 -0800 Subject: [PATCH 10/28] fix base URL --- src/cli/verify-sandboxes.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/cli/verify-sandboxes.ts b/src/cli/verify-sandboxes.ts index 674cb0ee..f566349a 100644 --- a/src/cli/verify-sandboxes.ts +++ b/src/cli/verify-sandboxes.ts @@ -24,7 +24,7 @@ process.on('unhandledRejection', () => { }); export async function verifySandboxes(configuration: Configuration, sandboxesPath: string, port: number) { - hostUrl = `localhost:${port}`; + hostUrl = `http://localhost:${port}`; await main(configuration, sandboxesPath, port); } @@ -72,15 +72,10 @@ async function openScenarioInNewPage(scenario: ScenarioSummary, timeoutAttempts: currentScenario = scenario.name; try { - console.log('attempting to hit page'); - console.log(scenario.url); await page.goto(scenario.url); } catch (e) { - console.log('closing page') await page.close(); - console.log('beginning delay') await delay(1000); - console.log('running again') await openScenarioInNewPage(scenario, timeoutAttempts - 1); } } From 0547d36f328e5b9e6aa831888487268e6a029d78 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Wed, 29 Nov 2017 16:51:28 -0800 Subject: [PATCH 11/28] move ts-node and puppeteer to cli deps --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 6df3bee3..0f0c677c 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,9 @@ }, "dependencies": { "async": "^2.6.0", - "node-watch": "^0.4.1" + "node-watch": "^0.4.1", + "ts-node": "^3.3.0", + "puppeteer": "^0.13.0" }, "devDependencies": { "@angular/common": "^5.0.0", @@ -64,9 +66,7 @@ "glob": "^7.1.2", "gulp": "^3.9.1", "gulp-inline-ng2-template": "^4.0.0", - "puppeteer": "^0.13.0", "rxjs": "^5.5.2", - "ts-node": "^3.3.0", "tslint": "5.3.2", "typescript": "2.4.2", "zone.js": "0.8.4" From 1fb2561fc97e695d39abd3be1f77838dac61c92c Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Wed, 29 Nov 2017 16:57:32 -0800 Subject: [PATCH 12/28] remove console logs --- src/cli/cli.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 474e14bc..b9849b10 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -13,7 +13,6 @@ import { findFirstFreePort } from './shared/find-port'; async function run() { const rawArgs = process.argv.slice(2); - console.log(rawArgs); const config = new Configuration(rawArgs); let sandboxPort, playgroundConfig; @@ -28,8 +27,6 @@ async function run() { // Parity between command line arguments and configuration file config.applyConfigurationFile(playgroundConfig); - console.log(config); - const sandboxesPath = await build(playgroundConfig.sourceRoot); if (config.flags.checkErrors.value) { From 55efeb68bc23a94b2772b8216358d93cad55c324 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Wed, 29 Nov 2017 17:25:04 -0800 Subject: [PATCH 13/28] allow for nested flags --- src/cli/cli.ts | 3 +-- src/cli/run-angular-cli.ts | 13 ++++----- src/cli/shared/configuration.ts | 48 ++++++++++++--------------------- 3 files changed, 25 insertions(+), 39 deletions(-) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index b9849b10..514184e3 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -16,7 +16,6 @@ async function run() { const config = new Configuration(rawArgs); let sandboxPort, playgroundConfig; - // let configFile = path.resolve(config.switches.config.value); let configFile = path.resolve(config.flags.config.value); try { playgroundConfig = require(configFile.replace(/.json$/, '')); @@ -33,7 +32,7 @@ async function run() { // get port dynamically const port = await findFirstFreePort('127.0.0.1', 7000, 9000); sandboxPort = port; - config.flags.ngCliPort.value = port; + config.flags.angularCli.port.value = port; } if (!config.flags.noWatch.value) { diff --git a/src/cli/run-angular-cli.ts b/src/cli/run-angular-cli.ts index f6a2580a..1387d27d 100644 --- a/src/cli/run-angular-cli.ts +++ b/src/cli/run-angular-cli.ts @@ -5,14 +5,15 @@ const path = require('path'); const childProcess = require('child_process'); export const runAngularCli = (config: Configuration, angularCliConfig: any) => { - let args = [config.flags.ngCliCmdPath.value, 'serve', '-no-progress']; + const cliConfig = config.flags.angularCli; + let args = [cliConfig.cmdPath.value, 'serve', '-no-progress']; args.push('--port'); - args.push(config.flags.ngCliPort.value.toString()); - if (config.flags.ngCliApp.value) { - args.push(`-a=${config.flags.ngCliApp.value}`); + args.push(cliConfig.port.value.toString()); + if (cliConfig.appName.value) { + args.push(`-a=${cliConfig.appName.value}`); } - if (config.flags.ngCliEnv.value) { - args.push(`-e=${config.flags.ngCliEnv.value}`); + if (cliConfig.environment.value) { + args.push(`-e=${cliConfig.environment.value}`); } if (angularCliConfig.args) { args = args.concat(angularCliConfig.args); diff --git a/src/cli/shared/configuration.ts b/src/cli/shared/configuration.ts index 29fef1d3..6f1dd89f 100644 --- a/src/cli/shared/configuration.ts +++ b/src/cli/shared/configuration.ts @@ -5,15 +5,12 @@ class Flag { public required = false ) {} } -interface Flags { - [key: string]: Flag; -} /** * Configuration object used to parse and assign command line arguments */ export class Configuration { - flags: Flags = { + flags: any = { noWatch: new Flag(['--no-watch'], false), noServe: new Flag(['--no-serve'], false), checkErrors: new Flag(['--check-errors'], false), @@ -21,10 +18,12 @@ export class Configuration { sourceRoot: new Flag(['--src', '-S'], null, true), config: new Flag(['--config', '-C'], 'angular-playground.json'), timeout: new Flag(['--timeout'], 90), - ngCliApp: new Flag(['--ng-cli-app'], 'playground'), - ngCliEnv: new Flag(['--ng-cli-env'], null), - ngCliPort: new Flag(['--ng-cli-port'], 4201), - ngCliCmdPath: new Flag(['--ng-cli-cmd'], 'node_modules/@angular/cli/bin/ng') + angularCli: { + appName: new Flag(['--ng-cli-app'], 'playground'), + environment: new Flag(['--ng-cli-env'], null), + port: new Flag(['--ng-cli-port'], 4201), + cmdPath: new Flag(['--ng-cli-cmd'], 'node_modules/@angular/cli/bin/ng') + } }; // Used to tailor the version of headless chromium ran by puppeteer @@ -53,15 +52,16 @@ export class Configuration { */ applyConfigurationFile(playgroundConfig: any) { Object.keys(playgroundConfig).forEach(key => { - if (key !== 'angularCli') { + if (this.instanceOfFlagGroup(this.flags[key])) { + Object.keys(playgroundConfig[key]).forEach(nestedKey => { + if (this.flags[key].hasOwnProperty(nestedKey)) { + this.flags[key][nestedKey].value = playgroundConfig[key][nestedKey]; + } + }); + } else { if (this.flags.hasOwnProperty(key)) { this.flags[key].value = playgroundConfig[key]; } - } else { - // Nested flag - Object.keys(playgroundConfig.angularCli).forEach(cliKey => { - this.setAngularCliFlag(cliKey, playgroundConfig[key][cliKey]); - }); } }); @@ -71,29 +71,15 @@ export class Configuration { } } - // TODO: Refactor to use "Flag directories" for future directory support and maintenance - private setAngularCliFlag(key: string, value: any) { - switch (key) { - case 'appName': - this.flags.ngCliApp.value = value; - break; - case 'port': - this.flags.ngCliPort.value = value; - break; - case 'environment': - this.flags.ngCliEnv.value = value; - break; - case 'cmdPath': - this.flags.ngCliCmdPath.value = value; - break; - } + private instanceOfFlagGroup(item: any) { + return !item.hasOwnProperty('value'); } private findMatchingFlagName(alias: string): string { const matchingIndex = Object.keys(this.flags) .map(key => this.flags[key].aliases) .findIndex(aliases => aliases.indexOf(alias) !== -1); - return matchingIndex > -1 ? Object.keys(this.flags)[matchingIndex] : undefined; + return matchingIndex !== -1 ? Object.keys(this.flags)[matchingIndex] : undefined; } private getAnyUnfulfilledFlag(): Flag { From 63bd10ce22090c29c7e1388093fda5e05b10c097 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Thu, 30 Nov 2017 08:28:52 -0800 Subject: [PATCH 14/28] fix applying configuration for nested flags --- src/cli/shared/configuration.ts | 63 +++++++++++++++++++-------------- src/cli/verify-sandboxes.ts | 2 +- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/cli/shared/configuration.ts b/src/cli/shared/configuration.ts index 6f1dd89f..395feefc 100644 --- a/src/cli/shared/configuration.ts +++ b/src/cli/shared/configuration.ts @@ -32,16 +32,13 @@ export class Configuration { constructor(rawArgv: string[]) { // Apply command line arguments rawArgv.forEach((argv, i) => { - const matchingFlagName = this.findMatchingFlagName(argv); + const matchingFlag = this.findMatchingFlag(argv); + if (!matchingFlag) return; - if (matchingFlagName) { - const matchingFlag = this.flags[matchingFlagName]; - if (typeof matchingFlag.value === 'boolean') { - matchingFlag.value = true; - } else { - // Value follows flag - matchingFlag.value = rawArgv[i + 1]; - } + if (typeof matchingFlag.value === 'boolean') { + matchingFlag.value = true; + } else { + matchingFlag.value = rawArgv[i + 1]; } }); } @@ -64,27 +61,41 @@ export class Configuration { } } }); - - const missingFlag = this.getAnyUnfulfilledFlag(); - if (missingFlag !== undefined) { - throw new Error(`CLI flag ${missingFlag.aliases[0]} needs a value.`); - } } - private instanceOfFlagGroup(item: any) { - return !item.hasOwnProperty('value'); - } + /** + * Iterates through flags and flag-groups to find the flag with a matching alias + * @param alias - alias from argv, e.g. --config + */ + private findMatchingFlag(alias: string): Flag { + let result: Flag; + + Object.keys(this.flags).forEach(key => { + const currentFlag = this.flags[key]; + + if (this.instanceOfFlagGroup(currentFlag)) { + Object.keys(currentFlag).forEach(nestedKey => { + const currentSubFlag = currentFlag[nestedKey]; - private findMatchingFlagName(alias: string): string { - const matchingIndex = Object.keys(this.flags) - .map(key => this.flags[key].aliases) - .findIndex(aliases => aliases.indexOf(alias) !== -1); - return matchingIndex !== -1 ? Object.keys(this.flags)[matchingIndex] : undefined; + if (currentSubFlag.aliases.includes(alias)) { + result = currentSubFlag; + } + }); + } else { + if (currentFlag.aliases.includes(alias)) { + result = currentFlag; + } + } + }); + + return result; } - private getAnyUnfulfilledFlag(): Flag { - return Object.keys(this.flags) - .map(key => this.flags[key]) - .find(flag => flag.required && flag.value === null); + /** + * Determines if provided item is a Flag or Flag-group + * @param item - Flag or Flag-group + */ + private instanceOfFlagGroup(item: any) { + return !item.hasOwnProperty('value'); } } diff --git a/src/cli/verify-sandboxes.ts b/src/cli/verify-sandboxes.ts index f566349a..36c5f86f 100644 --- a/src/cli/verify-sandboxes.ts +++ b/src/cli/verify-sandboxes.ts @@ -64,7 +64,7 @@ async function main (configuration: Configuration, sandboxesPath: string, port: async function openScenarioInNewPage(scenario: ScenarioSummary, timeoutAttempts: number) { if (timeoutAttempts === 0) { await browser.close(); - process.exit(1); + throw new Error('Unable to connect to Playground.'); } const page = await browser.newPage(); From 878ed91b8185d0f2e45d64f4417c3e85afabf1de Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Thu, 30 Nov 2017 09:05:54 -0800 Subject: [PATCH 15/28] add recursive findFlag --- src/cli/cli.ts | 1 + src/cli/shared/configuration.ts | 33 ++++++++++----------------------- 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 514184e3..36142ed8 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -27,6 +27,7 @@ async function run() { // Parity between command line arguments and configuration file config.applyConfigurationFile(playgroundConfig); const sandboxesPath = await build(playgroundConfig.sourceRoot); + console.log(config) if (config.flags.checkErrors.value) { // get port dynamically diff --git a/src/cli/shared/configuration.ts b/src/cli/shared/configuration.ts index 395feefc..7a9c35a8 100644 --- a/src/cli/shared/configuration.ts +++ b/src/cli/shared/configuration.ts @@ -32,7 +32,7 @@ export class Configuration { constructor(rawArgv: string[]) { // Apply command line arguments rawArgv.forEach((argv, i) => { - const matchingFlag = this.findMatchingFlag(argv); + const matchingFlag = this.findFlag(argv, this.flags); if (!matchingFlag) return; if (typeof matchingFlag.value === 'boolean') { @@ -63,32 +63,19 @@ export class Configuration { }); } - /** - * Iterates through flags and flag-groups to find the flag with a matching alias - * @param alias - alias from argv, e.g. --config - */ - private findMatchingFlag(alias: string): Flag { - let result: Flag; - - Object.keys(this.flags).forEach(key => { - const currentFlag = this.flags[key]; + private findFlag(alias: string, flagGroup: any): Flag { + for (const key in flagGroup) { + if (!flagGroup.hasOwnProperty(key)) continue; + const currentFlag = flagGroup[key]; if (this.instanceOfFlagGroup(currentFlag)) { - Object.keys(currentFlag).forEach(nestedKey => { - const currentSubFlag = currentFlag[nestedKey]; - - if (currentSubFlag.aliases.includes(alias)) { - result = currentSubFlag; - } - }); - } else { - if (currentFlag.aliases.includes(alias)) { - result = currentFlag; - } + return this.findFlag(alias, currentFlag); + } else if (currentFlag.aliases.includes(alias)) { + return currentFlag; } - }); + } - return result; + return undefined; } /** From 0a78f9abb77df29f2d38e3ad70f8c91841a36ac6 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Thu, 30 Nov 2017 09:06:15 -0800 Subject: [PATCH 16/28] get rid of unnecessary config log --- src/cli/cli.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 36142ed8..514184e3 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -27,7 +27,6 @@ async function run() { // Parity between command line arguments and configuration file config.applyConfigurationFile(playgroundConfig); const sandboxesPath = await build(playgroundConfig.sourceRoot); - console.log(config) if (config.flags.checkErrors.value) { // get port dynamically From b2b4a3a3529a12159742962cf40a48e320885506 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Thu, 30 Nov 2017 09:18:54 -0800 Subject: [PATCH 17/28] refactor find implementation --- src/cli/shared/configuration.ts | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/cli/shared/configuration.ts b/src/cli/shared/configuration.ts index 7a9c35a8..ac4a448a 100644 --- a/src/cli/shared/configuration.ts +++ b/src/cli/shared/configuration.ts @@ -63,19 +63,32 @@ export class Configuration { }); } + /** + * Return a flag that contains the provided alias or undefined if none found + * @param alias - Alias provided by argv. e.g. --config + * @param flagGroup - Grouping of flags to check + */ private findFlag(alias: string, flagGroup: any): Flag { - for (const key in flagGroup) { - if (!flagGroup.hasOwnProperty(key)) continue; - const currentFlag = flagGroup[key]; + return this.getValues(flagGroup).find(flag => { + if (this.instanceOfFlagGroup(flag)) { + return this.findFlag(alias, flag); + } + + return flag.aliases.includes(alias); + }); + } + + // Shim for Object.values() + private getValues(obj: any): any[] { + const vals = []; - if (this.instanceOfFlagGroup(currentFlag)) { - return this.findFlag(alias, currentFlag); - } else if (currentFlag.aliases.includes(alias)) { - return currentFlag; + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + vals.push(obj[key]); } } - return undefined; + return vals; } /** From 1fa17d642d37c355fb3f4b6a858d44a815a63bfa Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Thu, 30 Nov 2017 09:37:14 -0800 Subject: [PATCH 18/28] add default sourceRoot arg --- src/cli/shared/configuration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/shared/configuration.ts b/src/cli/shared/configuration.ts index ac4a448a..e15e33aa 100644 --- a/src/cli/shared/configuration.ts +++ b/src/cli/shared/configuration.ts @@ -15,7 +15,7 @@ export class Configuration { noServe: new Flag(['--no-serve'], false), checkErrors: new Flag(['--check-errors'], false), randomScenario: new Flag(['--random-scenario'], false), - sourceRoot: new Flag(['--src', '-S'], null, true), + sourceRoot: new Flag(['--src', '-S'], './src', true), config: new Flag(['--config', '-C'], 'angular-playground.json'), timeout: new Flag(['--timeout'], 90), angularCli: { From aca0c292f884cb3abea70f4180fbe22856664e81 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Thu, 30 Nov 2017 09:53:15 -0800 Subject: [PATCH 19/28] refactor applyConfig to use recursion --- src/cli/shared/configuration.ts | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/cli/shared/configuration.ts b/src/cli/shared/configuration.ts index e15e33aa..fdf35911 100644 --- a/src/cli/shared/configuration.ts +++ b/src/cli/shared/configuration.ts @@ -48,19 +48,20 @@ export class Configuration { * @param playgroundConfig */ applyConfigurationFile(playgroundConfig: any) { - Object.keys(playgroundConfig).forEach(key => { - if (this.instanceOfFlagGroup(this.flags[key])) { - Object.keys(playgroundConfig[key]).forEach(nestedKey => { - if (this.flags[key].hasOwnProperty(nestedKey)) { - this.flags[key][nestedKey].value = playgroundConfig[key][nestedKey]; - } - }); - } else { - if (this.flags.hasOwnProperty(key)) { - this.flags[key].value = playgroundConfig[key]; + const applyToFlags = (config: any, flagGroup: any) => { + Object.keys(config).forEach(key => { + if (!flagGroup.hasOwnProperty(key)) return; + const flag = flagGroup[key]; + + if (this.instanceOfFlagGroup(flag)) { + applyToFlags(config[key], flag); } - } - }); + + flag.value = config[key]; + }); + }; + + applyToFlags(playgroundConfig, this.flags); } /** From 315820fecc7a4d197786001408e8284bd493b0e5 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Fri, 1 Dec 2017 11:25:19 -0800 Subject: [PATCH 20/28] add better error reporting on check errors --- src/cli/shared/error-reporter.ts | 13 ++++++++----- src/cli/verify-sandboxes.ts | 13 ++++++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/cli/shared/error-reporter.ts b/src/cli/shared/error-reporter.ts index 3b350b8c..77479aed 100644 --- a/src/cli/shared/error-reporter.ts +++ b/src/cli/shared/error-reporter.ts @@ -3,7 +3,7 @@ export enum ReportType { } export class ErrorReporter { - private _errors: { error: any, scenario: string }[] = []; + private _errors: { descriptions: any, scenario: string }[] = []; constructor(public type = ReportType.Log) {} @@ -11,15 +11,18 @@ export class ErrorReporter { return this._errors; } - addError(error: any, scenario: string) { - this._errors.push({ error, scenario }); + addError(descriptions: any, scenario: string) { + this._errors.push({ descriptions, scenario }); } compileReport() { switch (this.type) { case ReportType.Log: - console.log('Found errors in the following scenarios:'); - this._errors.forEach(e => console.log(e.scenario)); + console.error(`\x1b[31mERROR Found\x1b[0m in the following scenarios:`); + this._errors.forEach(e => { + console.log(e.scenario); + console.log(e.descriptions); + }); } } diff --git a/src/cli/verify-sandboxes.ts b/src/cli/verify-sandboxes.ts index 36c5f86f..fec485f2 100644 --- a/src/cli/verify-sandboxes.ts +++ b/src/cli/verify-sandboxes.ts @@ -5,6 +5,8 @@ import { ErrorReporter, ReportType } from './shared/error-reporter'; import { Configuration } from './shared/configuration'; // ts-node required for runtime typescript compilation of sandboxes.ts require('ts-node/register'); +// Legacy import +const asyncMap = require('async/map'); interface ScenarioSummary { @@ -131,10 +133,15 @@ function loadSandboxMenuItems(path: string): any[] { * Callback when Chromium page encounters a console error * @param msg - Error message */ -function onConsoleErr(msg: any) { +async function onConsoleErr(msg: any) { if (msg.type === 'error') { - console.error(`ERROR Found in ${currentScenario}`); - reporter.addError(msg, currentScenario); + console.error(`\x1b[31mERROR Found\x1b[0m in ${currentScenario}`); + const descriptions = msg.args + .map(a => a._remoteObject) + .filter(o => o.type === 'object') + .map(o => o.description); + descriptions.map(d => console.error(d)); + reporter.addError(descriptions, currentScenario); } } From 60f7caefe57bc20077dbea29ff568ed252713d64 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Fri, 1 Dec 2017 11:28:13 -0800 Subject: [PATCH 21/28] abstract hex colors --- src/cli/shared/error-reporter.ts | 6 +++++- src/cli/verify-sandboxes.ts | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/cli/shared/error-reporter.ts b/src/cli/shared/error-reporter.ts index 77479aed..9e9b8c63 100644 --- a/src/cli/shared/error-reporter.ts +++ b/src/cli/shared/error-reporter.ts @@ -11,6 +11,10 @@ export class ErrorReporter { return this._errors; } + redWrap(msg: string): string { + return `\x1b[31m${msg}\x1b[0m`; + } + addError(descriptions: any, scenario: string) { this._errors.push({ descriptions, scenario }); } @@ -18,7 +22,7 @@ export class ErrorReporter { compileReport() { switch (this.type) { case ReportType.Log: - console.error(`\x1b[31mERROR Found\x1b[0m in the following scenarios:`); + console.error(`${this.redWrap('ERROR Found')} in the following scenarios:`); this._errors.forEach(e => { console.log(e.scenario); console.log(e.descriptions); diff --git a/src/cli/verify-sandboxes.ts b/src/cli/verify-sandboxes.ts index fec485f2..a5130a2e 100644 --- a/src/cli/verify-sandboxes.ts +++ b/src/cli/verify-sandboxes.ts @@ -135,7 +135,7 @@ function loadSandboxMenuItems(path: string): any[] { */ async function onConsoleErr(msg: any) { if (msg.type === 'error') { - console.error(`\x1b[31mERROR Found\x1b[0m in ${currentScenario}`); + console.error(`${reporter.redWrap('ERROR Found')} in ${currentScenario}`); const descriptions = msg.args .map(a => a._remoteObject) .filter(o => o.type === 'object') From 7e0f5a8c85abb01aa1c9522b79fec2a7f3f269ca Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Fri, 1 Dec 2017 12:34:48 -0800 Subject: [PATCH 22/28] add bamboo report output to test runner --- src/cli/shared/bamboo-reporter.ts | 35 +++++++++++++++++++++++++++++++ src/cli/shared/configuration.ts | 1 + src/cli/shared/error-reporter.ts | 25 +++++++++++++++++++--- src/cli/verify-sandboxes.ts | 7 ++++--- 4 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 src/cli/shared/bamboo-reporter.ts diff --git a/src/cli/shared/bamboo-reporter.ts b/src/cli/shared/bamboo-reporter.ts new file mode 100644 index 00000000..6ce906d5 --- /dev/null +++ b/src/cli/shared/bamboo-reporter.ts @@ -0,0 +1,35 @@ +export class BambooStats { + suites = 1; + passes: number; + pending = 0; + duration = 0; + time = 0; + + constructor( + public tests: number, + public failures: number, + public start = 0, + public end = 0 + ) { + this.passes = this.tests - this.failures; + } + +} + +export class BambooResults { + constructor( + public stats: BambooStats, + public failures: any[], + public passes: any[], + public skips: any[] + ) {} + + getJson() { + return JSON.stringify({ + stats: this.stats, + failures: this.failures, + passes: this.passes, + skips: this.skips + }, null, 2); + } +} diff --git a/src/cli/shared/configuration.ts b/src/cli/shared/configuration.ts index fdf35911..a187f823 100644 --- a/src/cli/shared/configuration.ts +++ b/src/cli/shared/configuration.ts @@ -18,6 +18,7 @@ export class Configuration { sourceRoot: new Flag(['--src', '-S'], './src', true), config: new Flag(['--config', '-C'], 'angular-playground.json'), timeout: new Flag(['--timeout'], 90), + reportPath: new Flag(['--report', '-R'], './sandbox.report.json'), angularCli: { appName: new Flag(['--ng-cli-app'], 'playground'), environment: new Flag(['--ng-cli-env'], null), diff --git a/src/cli/shared/error-reporter.ts b/src/cli/shared/error-reporter.ts index 9e9b8c63..73369130 100644 --- a/src/cli/shared/error-reporter.ts +++ b/src/cli/shared/error-reporter.ts @@ -1,11 +1,20 @@ +import * as fs from 'fs'; +import { BambooResults, BambooStats } from './bamboo-reporter'; +import { ScenarioSummary } from '../verify-sandboxes'; + export enum ReportType { - Log + Log, + Bamboo } export class ErrorReporter { private _errors: { descriptions: any, scenario: string }[] = []; - constructor(public type = ReportType.Log) {} + constructor( + public scenarios: ScenarioSummary[], + public type = ReportType.Log, + public filename: string + ) {} get errors() { return this._errors; @@ -22,11 +31,21 @@ export class ErrorReporter { compileReport() { switch (this.type) { case ReportType.Log: - console.error(`${this.redWrap('ERROR Found')} in the following scenarios:`); + console.error(`${this.redWrap('ERROR:')} in the following scenarios`); this._errors.forEach(e => { console.log(e.scenario); console.log(e.descriptions); }); + break; + case ReportType.Bamboo: + const stats = new BambooStats(this.scenarios.length, this.errors.length); + const result = new BambooResults( + stats, + this.errors, + this.scenarios.map(s => `${s.name}: ${s.description}`), + []); + fs.writeFileSync(this.filename, result.getJson()); + break; } } diff --git a/src/cli/verify-sandboxes.ts b/src/cli/verify-sandboxes.ts index a5130a2e..aaa7e70e 100644 --- a/src/cli/verify-sandboxes.ts +++ b/src/cli/verify-sandboxes.ts @@ -9,7 +9,7 @@ require('ts-node/register'); const asyncMap = require('async/map'); -interface ScenarioSummary { +export interface ScenarioSummary { url: string; name: string; description: string; @@ -17,7 +17,7 @@ interface ScenarioSummary { let browser: any; let currentScenario = ''; -const reporter = new ErrorReporter(); +let reporter: ErrorReporter; let hostUrl = ''; // Ensure Chromium instances are destroyed on err @@ -41,6 +41,7 @@ async function main (configuration: Configuration, sandboxesPath: string, port: }); const scenarios = getSandboxMetadata(hostUrl, configuration.flags.randomScenario.value, sandboxesPath); + reporter = new ErrorReporter(scenarios, ReportType.Bamboo, configuration.flags.reportPath.value); console.log(`Retrieved ${scenarios.length} scenarios.\n`); for (let i = 0; i < scenarios.length; i++) { console.log(`Checking: ${scenarios[i].name}: ${scenarios[i].description}`); @@ -135,7 +136,7 @@ function loadSandboxMenuItems(path: string): any[] { */ async function onConsoleErr(msg: any) { if (msg.type === 'error') { - console.error(`${reporter.redWrap('ERROR Found')} in ${currentScenario}`); + console.error(`${reporter.redWrap('ERROR:')} in ${currentScenario}`); const descriptions = msg.args .map(a => a._remoteObject) .filter(o => o.type === 'object') From 1f2970c53dfd03eb71afdbbbba0a961417351a6c Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Fri, 1 Dec 2017 12:41:17 -0800 Subject: [PATCH 23/28] change report type to string const for easy configuration --- src/cli/shared/configuration.ts | 5 ++++- src/cli/shared/error-reporter.ts | 16 ++++++++-------- src/cli/verify-sandboxes.ts | 4 ++-- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/cli/shared/configuration.ts b/src/cli/shared/configuration.ts index a187f823..b8c36a1e 100644 --- a/src/cli/shared/configuration.ts +++ b/src/cli/shared/configuration.ts @@ -1,3 +1,5 @@ +import { REPORT_TYPE } from './error-reporter'; + class Flag { constructor( public aliases: string[], @@ -18,7 +20,8 @@ export class Configuration { sourceRoot: new Flag(['--src', '-S'], './src', true), config: new Flag(['--config', '-C'], 'angular-playground.json'), timeout: new Flag(['--timeout'], 90), - reportPath: new Flag(['--report', '-R'], './sandbox.report.json'), + reportPath: new Flag(['--report-path', '-R'], './sandbox.report.json'), + reportType: new Flag(['--report-type'], REPORT_TYPE.LOG), angularCli: { appName: new Flag(['--ng-cli-app'], 'playground'), environment: new Flag(['--ng-cli-env'], null), diff --git a/src/cli/shared/error-reporter.ts b/src/cli/shared/error-reporter.ts index 73369130..b457acfb 100644 --- a/src/cli/shared/error-reporter.ts +++ b/src/cli/shared/error-reporter.ts @@ -2,18 +2,18 @@ import * as fs from 'fs'; import { BambooResults, BambooStats } from './bamboo-reporter'; import { ScenarioSummary } from '../verify-sandboxes'; -export enum ReportType { - Log, - Bamboo -} +export const REPORT_TYPE = { + LOG: 'log', + BAMBOO: 'bamboo' +}; export class ErrorReporter { private _errors: { descriptions: any, scenario: string }[] = []; constructor( public scenarios: ScenarioSummary[], - public type = ReportType.Log, - public filename: string + public filename: string, + public type: string ) {} get errors() { @@ -30,14 +30,14 @@ export class ErrorReporter { compileReport() { switch (this.type) { - case ReportType.Log: + case REPORT_TYPE.LOG: console.error(`${this.redWrap('ERROR:')} in the following scenarios`); this._errors.forEach(e => { console.log(e.scenario); console.log(e.descriptions); }); break; - case ReportType.Bamboo: + case REPORT_TYPE.BAMBOO: const stats = new BambooStats(this.scenarios.length, this.errors.length); const result = new BambooResults( stats, diff --git a/src/cli/verify-sandboxes.ts b/src/cli/verify-sandboxes.ts index aaa7e70e..7aeb10c9 100644 --- a/src/cli/verify-sandboxes.ts +++ b/src/cli/verify-sandboxes.ts @@ -1,7 +1,7 @@ import * as puppeteer from 'puppeteer'; import * as process from 'process'; import * as path from 'path'; -import { ErrorReporter, ReportType } from './shared/error-reporter'; +import { ErrorReporter, REPORT_TYPE } from './shared/error-reporter'; import { Configuration } from './shared/configuration'; // ts-node required for runtime typescript compilation of sandboxes.ts require('ts-node/register'); @@ -41,7 +41,7 @@ async function main (configuration: Configuration, sandboxesPath: string, port: }); const scenarios = getSandboxMetadata(hostUrl, configuration.flags.randomScenario.value, sandboxesPath); - reporter = new ErrorReporter(scenarios, ReportType.Bamboo, configuration.flags.reportPath.value); + reporter = new ErrorReporter(scenarios, configuration.flags.reportPath.value, configuration.flags.reportType.value); console.log(`Retrieved ${scenarios.length} scenarios.\n`); for (let i = 0; i < scenarios.length; i++) { console.log(`Checking: ${scenarios[i].name}: ${scenarios[i].description}`); From 0702740e69541f7bc47bfc801eb8cc21f07fbcd2 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Fri, 1 Dec 2017 14:38:11 -0800 Subject: [PATCH 24/28] rename reporter to json reporter, tweak json output --- src/cli/reporters/json-reporter.ts | 43 ++++++++++++++++++++++++++++++ src/cli/shared/bamboo-reporter.ts | 35 ------------------------ src/cli/shared/error-reporter.ts | 16 +++++------ 3 files changed, 49 insertions(+), 45 deletions(-) create mode 100644 src/cli/reporters/json-reporter.ts delete mode 100644 src/cli/shared/bamboo-reporter.ts diff --git a/src/cli/reporters/json-reporter.ts b/src/cli/reporters/json-reporter.ts new file mode 100644 index 00000000..25b98e6c --- /dev/null +++ b/src/cli/reporters/json-reporter.ts @@ -0,0 +1,43 @@ +class JSONStats { + suites = 1; + passes: number; + pending = 0; + duration = 0; + time = 0; + + constructor( + public tests: number, + public failures: number, + public start = 0, + public end = 0 + ) { + this.passes = this.tests - this.failures; + } + +} + +export class JSONReporter { + constructor ( + public errors: any[], + public scenarioNames: string[] + ) {} + + getJson() { + return JSON.stringify({ + stats: new JSONStats(this.scenarioNames.length, this.errors.length), + failures: this.errors.map(failure => { + if (!failure) return; + return { + title: failure.scenario, + err: { + message: failure.descriptions[0] + } + }; + }), + passes: this.scenarioNames.map(pass => { + return { title: pass }; + }), + skips: [] + }, null, 2); + } +} diff --git a/src/cli/shared/bamboo-reporter.ts b/src/cli/shared/bamboo-reporter.ts deleted file mode 100644 index 6ce906d5..00000000 --- a/src/cli/shared/bamboo-reporter.ts +++ /dev/null @@ -1,35 +0,0 @@ -export class BambooStats { - suites = 1; - passes: number; - pending = 0; - duration = 0; - time = 0; - - constructor( - public tests: number, - public failures: number, - public start = 0, - public end = 0 - ) { - this.passes = this.tests - this.failures; - } - -} - -export class BambooResults { - constructor( - public stats: BambooStats, - public failures: any[], - public passes: any[], - public skips: any[] - ) {} - - getJson() { - return JSON.stringify({ - stats: this.stats, - failures: this.failures, - passes: this.passes, - skips: this.skips - }, null, 2); - } -} diff --git a/src/cli/shared/error-reporter.ts b/src/cli/shared/error-reporter.ts index b457acfb..752bc4c9 100644 --- a/src/cli/shared/error-reporter.ts +++ b/src/cli/shared/error-reporter.ts @@ -1,10 +1,10 @@ import * as fs from 'fs'; -import { BambooResults, BambooStats } from './bamboo-reporter'; +import { JSONReporter } from '../reporters/json-reporter'; import { ScenarioSummary } from '../verify-sandboxes'; export const REPORT_TYPE = { LOG: 'log', - BAMBOO: 'bamboo' + JSON: 'json' }; export class ErrorReporter { @@ -37,14 +37,10 @@ export class ErrorReporter { console.log(e.descriptions); }); break; - case REPORT_TYPE.BAMBOO: - const stats = new BambooStats(this.scenarios.length, this.errors.length); - const result = new BambooResults( - stats, - this.errors, - this.scenarios.map(s => `${s.name}: ${s.description}`), - []); - fs.writeFileSync(this.filename, result.getJson()); + case REPORT_TYPE.JSON: + const scenarioNames = this.scenarios.map(s => `${s.name}: ${s.description}`); + const results = new JSONReporter(this.errors, scenarioNames); + fs.writeFileSync(this.filename, results.getJson()); break; } } From bfbeebe93f399ffbbbd0ce2d55efd8b7cf8f254e Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Fri, 1 Dec 2017 15:36:25 -0800 Subject: [PATCH 25/28] update changelog --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f263e7c8..c45bd178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,36 @@ + + +# 3.0.0 (2017-12-01) + +### Features +* **cli:** Added a boat-load of new CLI configuration options. Every option available + in the `angular-playground.json` file is now also available as a CLI argument + (except @angular/cli arguments). [Read more about it in our docs](http://angularplayground.it/docs/api/configuration). +* **new docs:** Speaking of docs, check out our newly-designed + [docs page](http://angularplayground.it/docs/getting-started/introduction). +* **new error checking utility:** A new CLI option has been introduced that will run and visit + all sandbox scenarios in headless chrome, surfacing any errors that appear in the + console. [Never forget to mock a dependency again!](http://angularplayground.it/docs/how-to/run-the-test-suite) +* **report formats for builds:** Used in conjunction with the checking utility, you can now + generate a JSON report that your build system can read for error reporting. Read all + about it [here](http://angularplayground.it/docs/api/reporter-formats). +* **command bar shows all components as default:** Got the Playground running but don't know where + to begin? We'll help you out by showing all of your available scenarios. + +### Breaking Changes +* **no default configuration argument**: The CLI no longer supports a default configuration file argument. + **Note:** `angular-playground` with no arguments will still default to using the + `angular-playground.json` file as expected. + +The following: +``` +angular-playground my-configuration-file.json +``` +is now: +``` +angular-playground --config my-configuration-file.json +``` + # 2.3.0 (2017-11-13) From 94c39a9cfc16705b1a8bab17b595a3a34990c5ed Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Fri, 1 Dec 2017 15:42:41 -0800 Subject: [PATCH 26/28] tidy up presentation --- CHANGELOG.md | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c45bd178..97027f59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,14 +22,26 @@ **Note:** `angular-playground` with no arguments will still default to using the `angular-playground.json` file as expected. -The following: -``` -angular-playground my-configuration-file.json -``` -is now: -``` -angular-playground --config my-configuration-file.json -``` + Before: + ``` + angular-playground my-configuration-file.json + ``` + After: + ``` + angular-playground --config my-configuration-file.json + ``` + +* **new cli argument style**: CLI arguments now match typical npm style: `--argument` for full name, `-A` for abbreviation. + + Before: + ``` + -no-watch -no-serve + ``` + + After: + ``` + --no-watch --no-serve + ``` # 2.3.0 (2017-11-13) From c55a483aff5674e655ec83883074b38b5af2d609 Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Fri, 1 Dec 2017 16:15:07 -0800 Subject: [PATCH 27/28] add commits --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97027f59..e106b3aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,21 +6,27 @@ * **cli:** Added a boat-load of new CLI configuration options. Every option available in the `angular-playground.json` file is now also available as a CLI argument (except @angular/cli arguments). [Read more about it in our docs](http://angularplayground.it/docs/api/configuration). + ([9dc1066](https://github.com/SoCreate/angular-playground/pull/68/commits/9dc1066)) * **new docs:** Speaking of docs, check out our newly-designed [docs page](http://angularplayground.it/docs/getting-started/introduction). * **new error checking utility:** A new CLI option has been introduced that will run and visit all sandbox scenarios in headless chrome, surfacing any errors that appear in the console. [Never forget to mock a dependency again!](http://angularplayground.it/docs/how-to/run-the-test-suite) + ([6074586](https://github.com/SoCreate/angular-playground/commit/6074586)) * **report formats for builds:** Used in conjunction with the checking utility, you can now generate a JSON report that your build system can read for error reporting. Read all about it [here](http://angularplayground.it/docs/api/reporter-formats). + ([7e0f5a8](https://github.com/SoCreate/angular-playground/pull/68/commits/7e0f5a8)) + * **command bar shows all components as default:** Got the Playground running but don't know where to begin? We'll help you out by showing all of your available scenarios. + ([51680fd](https://github.com/SoCreate/angular-playground/commit/51680fd) ### Breaking Changes * **no default configuration argument**: The CLI no longer supports a default configuration file argument. **Note:** `angular-playground` with no arguments will still default to using the `angular-playground.json` file as expected. + ([9dc1066](https://github.com/SoCreate/angular-playground/pull/68/commits/9dc1066)) Before: ``` From e32e97edf6ffb676276cce9eff7897a851fe64fd Mon Sep 17 00:00:00 2001 From: mgmarlow Date: Fri, 1 Dec 2017 16:41:48 -0800 Subject: [PATCH 28/28] fix urls on commit listings --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e106b3aa..5a4e8b26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ * **cli:** Added a boat-load of new CLI configuration options. Every option available in the `angular-playground.json` file is now also available as a CLI argument (except @angular/cli arguments). [Read more about it in our docs](http://angularplayground.it/docs/api/configuration). - ([9dc1066](https://github.com/SoCreate/angular-playground/pull/68/commits/9dc1066)) + ([9dc1066](https://github.com/SoCreate/angular-playground/commit/9dc1066)) * **new docs:** Speaking of docs, check out our newly-designed [docs page](http://angularplayground.it/docs/getting-started/introduction). * **new error checking utility:** A new CLI option has been introduced that will run and visit @@ -16,7 +16,7 @@ * **report formats for builds:** Used in conjunction with the checking utility, you can now generate a JSON report that your build system can read for error reporting. Read all about it [here](http://angularplayground.it/docs/api/reporter-formats). - ([7e0f5a8](https://github.com/SoCreate/angular-playground/pull/68/commits/7e0f5a8)) + ([7e0f5a8](https://github.com/SoCreate/angular-playground/commit/7e0f5a8)) * **command bar shows all components as default:** Got the Playground running but don't know where to begin? We'll help you out by showing all of your available scenarios. @@ -26,7 +26,7 @@ * **no default configuration argument**: The CLI no longer supports a default configuration file argument. **Note:** `angular-playground` with no arguments will still default to using the `angular-playground.json` file as expected. - ([9dc1066](https://github.com/SoCreate/angular-playground/pull/68/commits/9dc1066)) + ([9dc1066](https://github.com/SoCreate/angular-playground/commit/9dc1066)) Before: ```