├── cli
├── .gitignore
├── commands
│ ├── commandsexports.js
│ ├── initalize.js
│ ├── configure.js
│ └── start.js
├── index.js
├── questions
│ └── questions.js
├── package.json
└── util
│ └── QueryInspector.js
├── .gitignore
├── chaos-qoala-agent
├── .gitignore
├── tests
│ ├── jest.config.js
│ └── index.test.js
├── .eslintrc.js
├── package.json
└── index.js
├── chaos-qoala-integration-tests
├── .gitignore
├── tests
│ ├── jest.config.js
│ ├── gql-schema
│ │ └── schema.gql
│ ├── globalSetup.js
│ ├── globalTeardown.js
│ ├── query-inspector.test.js
│ ├── platforms
│ │ ├── express-standalone.js
│ │ ├── express-graphql-app.js
│ │ └── apollo-server-express-app.js
│ ├── express.test.js
│ └── apollo.test.js
├── chaos-qoala-config.js
├── .eslintrc.js
├── steady-state-server-stub
│ ├── readme.txt
│ ├── server.js
│ └── results.js
└── package.json
├── client
├── scss
│ ├── _graphs.scss
│ ├── application.scss
│ ├── _fileupload.scss
│ ├── _navbar.scss
│ ├── _thisisus.scss
│ ├── _instructions.scss
│ └── _main.scss
├── src
│ ├── components
│ │ ├── Upload.jsx
│ │ ├── DownloadButton.jsx
│ │ ├── NavBar.jsx
│ │ ├── Home.jsx
│ │ ├── App.jsx
│ │ ├── SteadyStateChart.jsx
│ │ ├── ChaosFlagChart.jsx
│ │ ├── Main.jsx
│ │ ├── Graphs.jsx
│ │ ├── Instructions.jsx
│ │ ├── FileUpload.jsx
│ │ └── ThisIsUs.jsx
│ ├── App.test.js
│ ├── index.js
│ ├── index.css
│ └── App.css
├── index.html
├── .gitignore
├── public
│ ├── manifest.json
│ └── index.html
├── webpack.config.js
├── package.json
├── server
│ └── server.js
└── README.md
├── aws
├── common-aws.js
├── package.json
├── delete-aws.js
├── .env_EXAMPLE
├── lambda.js
├── deploy-aws.js
├── cloud-formation-s3.yml
├── chaos-website-deployer.js
├── cloud-formation-lambda-api.yml
└── package-lock.json
├── LICENSE
└── README.md
/cli/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 |
--------------------------------------------------------------------------------
/chaos-qoala-agent/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/chaos-qoala-agent/tests/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |
3 | };
4 |
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .vscode
3 |
--------------------------------------------------------------------------------
/client/scss/_graphs.scss:
--------------------------------------------------------------------------------
1 | h2 {
2 | text-align: center;
3 | color: rgb(132, 0, 255);
4 | }
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/tests/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | globalSetup: './globalSetup.js',
3 | globalTeardown: './globalTeardown.js',
4 | };
5 |
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/tests/gql-schema/schema.gql:
--------------------------------------------------------------------------------
1 | type Query{
2 | dontKnockMeOut: String
3 | knockMeOut: String
4 | }
5 |
6 | schema {
7 | query: Query
8 | }
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/tests/globalSetup.js:
--------------------------------------------------------------------------------
1 | const globalSetupFunc = () => {
2 | // add any global startup tasks
3 | };
4 |
5 | module.exports = globalSetupFunc;
6 |
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/tests/globalTeardown.js:
--------------------------------------------------------------------------------
1 | const globalTeardownFunc = () => {
2 | // add any global cleanup tasks
3 | };
4 |
5 | module.exports = globalTeardownFunc;
6 |
--------------------------------------------------------------------------------
/cli/commands/commandsexports.js:
--------------------------------------------------------------------------------
1 | // This exports the command files in one file to make things a touch easier
2 |
3 | module.exports = {
4 | ...require('./initalize'),
5 | ...require('./configure'),
6 | ...require('./start'),
7 | };
8 |
--------------------------------------------------------------------------------
/client/src/components/Upload.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FileUpload from './FileUpload.jsx';
3 |
4 | function Upload() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
12 |
13 | export default Upload;
14 |
--------------------------------------------------------------------------------
/client/src/components/DownloadButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | function DownloadButton() {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default DownloadButton
12 |
--------------------------------------------------------------------------------
/client/scss/application.scss:
--------------------------------------------------------------------------------
1 | @import '_navbar.scss';
2 | @import '_main.scss';
3 | @import '_instructions.scss';
4 | @import '_thisisus.scss';
5 | @import '_fileupload.scss';
6 | @import '_graphs.scss';
7 | @import url('https://fonts.googleapis.com/css?family=Manjari&display=swap');
8 |
--------------------------------------------------------------------------------
/client/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import App from './components/App.jsx';
4 |
5 | // // uncomment so that webpack can bundle styles
6 | import styles from '../scss/application.scss';
7 |
8 | render(
9 | ,
10 | document.getElementById('root')
11 | );
12 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ChasQoaLa: Chaos Engineering Meets GraphQL
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/chaos-qoala-config.js:
--------------------------------------------------------------------------------
1 | const config = {
2 | socketPort: 'http://localhost:1025',
3 | graphQLPort: 'http://localhost:3000',
4 | blastRadius: 1.0,
5 | delay: 3000,
6 | missingData: true,
7 | affectedQueries: { knockMeOut: true, dontKnockMeOut: false },
8 | runTime: '60',
9 | ensueChaos: true,
10 | };
11 |
12 | module.exports = config;
13 |
--------------------------------------------------------------------------------
/chaos-qoala-agent/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | commonjs: true,
5 | es6: true,
6 | jest: true,
7 | },
8 | extends: [
9 | 'airbnb-base',
10 | ],
11 | globals: {
12 | Atomics: 'readonly',
13 | SharedArrayBuffer: 'readonly',
14 | },
15 | parserOptions: {
16 | ecmaVersion: 2018,
17 | },
18 | rules: {
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | commonjs: true,
5 | es6: true,
6 | jest: true,
7 | },
8 | extends: [
9 | 'airbnb-base',
10 | ],
11 | globals: {
12 | Atomics: 'readonly',
13 | SharedArrayBuffer: 'readonly',
14 | },
15 | parserOptions: {
16 | ecmaVersion: 2018,
17 | },
18 | rules: {
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # aws
4 | .creds.js
5 |
6 | # dependencies
7 | /node_modules
8 | /.pnp
9 | .pnp.js
10 |
11 | # testing
12 | /coverage
13 |
14 | # production
15 | /build
16 |
17 | # misc
18 | .DS_Store
19 | .env.local
20 | .env.development.local
21 | .env.test.local
22 | .env.production.local
23 |
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
--------------------------------------------------------------------------------
/client/scss/_fileupload.scss:
--------------------------------------------------------------------------------
1 | .cq-file-upload {
2 | margin: 100px;
3 | border-style: solid;
4 | border-color: rgb(187, 20, 151);
5 | border-width: 10px;
6 | text-align: center;
7 | text-transform: capitalize;
8 | color: rgb(187, 20, 151);
9 | font-family: Verdana, Geneva, Tahoma, sans-serif ;
10 | font-size: 15px;
11 | font-weight: bold;
12 | background-color: rgb(0, 0, 0);
13 | }
14 |
15 | .cq-file-upload:hover {
16 | border-color: black;
17 | background-color: rgb(187, 20, 151);
18 | color: black;
19 | }
--------------------------------------------------------------------------------
/aws/common-aws.js:
--------------------------------------------------------------------------------
1 | const DOMAIN_NAME = 'lovechaos.net';
2 | module.exports = {
3 | DOMAIN_NAME : DOMAIN_NAME,
4 | S3_STACK_NAME : DOMAIN_NAME.replace('.', '') + '-www',
5 | API_STACK_NAME : DOMAIN_NAME.replace('.', '') + '-api',
6 | LAMBDA_S3_BUCKET : DOMAIN_NAME.replace('.', '') + '-lambda',
7 | API_END_POINT : 'api.' + DOMAIN_NAME,
8 | REGION_HOSTED_ZONE_ID : process.env.REGION_HOSTED_ZONE_ID,
9 | CUSTOM_DOMAIN_HOSTED_ZONE_ID : process.env.CUSTOM_DOMAIN_HOSTED_ZONE_ID,
10 | REGIONAL_CERTIFICATE_ARN : process.env.REGIONAL_CERTIFICATE_ARN
11 | };
12 |
--------------------------------------------------------------------------------
/chaos-qoala-agent/tests/index.test.js:
--------------------------------------------------------------------------------
1 | beforeAll(() => {
2 | // eslint-disable-next-line no-console
3 | console.log('add global setup code here');
4 | });
5 |
6 |
7 | // test altering basic query response
8 | describe('Chaos 🐨 Agent Unit Tests', () => {
9 | // note the async so we can await fetch
10 | test('add unit tests here', () => {
11 | expect('the more tests the merrier').toBe('the more tests the merrier');
12 | });
13 | });
14 |
15 |
16 | afterAll(() => {
17 | // eslint-disable-next-line no-console
18 | console.log('add global clean up code here');
19 | });
20 |
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/cli/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const program = require('commander');
4 |
5 | const { configure, start } = require('./commands/commandsexports');
6 |
7 | program
8 | // defines a new command and how the command will be executed as well as what text the user will see
9 | .command('configure')
10 | .description('Initiate a config file')
11 | .action(configure);
12 |
13 | program
14 | .command('start')
15 | .description('Send the information to the ChaosQoala agent')
16 | .action(start);
17 |
18 | program
19 | .parse(process.argv);
20 |
21 |
22 | if (!process.argv.slice(2).length) {
23 | program.outputHelp();
24 | }
25 |
--------------------------------------------------------------------------------
/aws/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aws",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "deploy-aws": "node deploy-aws.js",
9 | "delete-aws": "node delete-aws.js"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "dependencies": {
15 | "adm-zip": "^0.4.13",
16 | "aws-sdk": "^2.524.0",
17 | "dotenv": "^8.1.0"
18 | },
19 | "devDependencies": {
20 | "eslint": "^6.3.0",
21 | "eslint-config-airbnb-base": "^14.0.0",
22 | "eslint-plugin-import": "^2.18.2"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/client/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 40vmin;
8 | pointer-events: none;
9 | }
10 |
11 | .App-header {
12 | background-color: #282c34;
13 | min-height: 100vh;
14 | display: flex;
15 | flex-direction: column;
16 | align-items: center;
17 | justify-content: center;
18 | font-size: calc(10px + 2vmin);
19 | color: white;
20 | }
21 |
22 | .App-link {
23 | color: #61dafb;
24 | }
25 |
26 | @keyframes App-logo-spin {
27 | from {
28 | transform: rotate(0deg);
29 | }
30 | to {
31 | transform: rotate(360deg);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 | React App
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/aws/delete-aws.js:
--------------------------------------------------------------------------------
1 | const chaosDeployer = require('./chaos-website-deployer');
2 | const awsVars = require('./common-aws');
3 |
4 | (async () => {
5 | try {
6 | await chaosDeployer.init();
7 |
8 | const s3Exists = await chaosDeployer.stackExists(awsVars.S3_STACK_NAME);
9 | const apiExists = await chaosDeployer.stackExists(awsVars.API_STACK_NAME);
10 |
11 | if (s3Exists) {
12 | await chaosDeployer.deleteS3Objects(awsVars.DOMAIN_NAME);
13 | await chaosDeployer.deleteS3Objects(awsVars.LAMBDA_S3_BUCKET);
14 | await chaosDeployer.deleteStack(awsVars.S3_STACK_NAME);
15 | }
16 |
17 | if (apiExists) await chaosDeployer.deleteStack(awsVars.API_STACK_NAME);
18 | } catch (err) {
19 | console.log(err);
20 | }
21 | })();
22 |
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/tests/query-inspector.test.js:
--------------------------------------------------------------------------------
1 | const express = require('./platforms/express-graphql-app');
2 |
3 | const platform = express;
4 | const QueryInspector = require('../../cli/util/QueryInspector');
5 |
6 | beforeAll(async () => {
7 | await platform.start();
8 | });
9 |
10 | describe('Schema Inspector', () => {
11 | test('basic test', async () => {
12 | const url = `http://localhost:${platform.port}/graphql`;
13 | const inspector = new QueryInspector(url);
14 | await inspector.hydrateQueryMap();
15 | const queryList = inspector.getQueryMap();
16 | expect(Object.keys(queryList).join()).toBe('dontKnockMeOut,knockMeOut');
17 | });
18 | });
19 |
20 | // stop platform servers at end of all tests
21 | afterAll(async (done) => {
22 | await platform.stop();
23 | done();
24 | });
25 |
--------------------------------------------------------------------------------
/aws/.env_EXAMPLE:
--------------------------------------------------------------------------------
1 | # your .aws/certificates file for the node sdk must include this entry with the square brackets this [chaos] instead of [default].
2 | # also make sure you have created a user with the AdministratorAccess managed policy role and use the keys for that user in the
3 | # .aws/certificates file
4 | AWS_PROFILE=chaos
5 |
6 | # AWW region you are deploying to
7 | AWS_REGION=us-west-1
8 |
9 | # lookup the hosted zone id for the region you are deploying in from the following site
10 | # https://gist.github.com/nicolasdao/558050d892f74d63b515ebaf0485457c
11 | REGION_HOSTED_ZONE_ID=Z2MUQ32089INYE
12 |
13 | # look in route 53 for your personal domain details (chaosqoala.com)
14 | CUSTOM_DOMAIN_HOSTED_ZONE_ID=Z3..........
15 |
16 | # the certificate ARN you generact from ACM for your personal domain
17 | REGIONAL_CERTIFICATE_ARN=arn:aws:acm:us-west-1:4..........
--------------------------------------------------------------------------------
/client/src/components/NavBar.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import Upload from "./Upload.jsx";
3 | import { NavLink } from "react-router-dom";
4 |
5 | // import DownloadButton from './DownloadButton.jsx';
6 |
7 | function NavBar() {
8 | return (
9 |
10 |
11 |
14 |
15 |
27 |
28 | );
29 | }
30 |
31 | export default NavBar;
32 |
--------------------------------------------------------------------------------
/chaos-qoala-agent/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chaos-qoala-agent",
3 | "version": "0.0.1",
4 | "description": "Chaos QoaLa Agent - for use on a GraphQL server in conjunction with Chaos QoaLa Controller client app",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "jest --config ./tests/jest.config.js"
8 | },
9 | "author": "tasselwobegong.com",
10 | "license": "MIT",
11 | "dependencies": {
12 | "express": "^4.17.1",
13 | "socket.io": "^2.2.0",
14 | "zlib": "^1.0.5"
15 | },
16 | "devDependencies": {
17 | "eslint": "^6.2.1",
18 | "eslint-config-airbnb": "^18.0.1",
19 | "eslint-config-airbnb-base": "^14.0.0",
20 | "eslint-plugin-import": "^2.18.2",
21 | "eslint-plugin-jest": "^22.15.2",
22 | "eslint-plugin-jsx-a11y": "^6.2.3",
23 | "eslint-plugin-react": "^7.14.3",
24 | "eslint-plugin-react-hooks": "^1.7.0",
25 | "jest": "^24.9.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/client/src/components/Home.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { BrowserRouter as Router, Route } from 'react-router-dom';
3 | import NavBar from './NavBar.jsx';
4 | import Upload from './Upload.jsx';
5 | import Main from './Main.jsx';
6 | import Instructions from './Instructions.jsx';
7 | import ThisIsUs from './ThisIsUs.jsx';
8 |
9 | class Home extends Component {
10 | constructor() {
11 | super();
12 | this.state = {};
13 | }
14 |
15 | render() {
16 | return (
17 | //
18 |
19 |
20 |
21 |
22 |
23 | {/*
24 |
25 |
26 |
27 | */}
28 |
29 | //
30 | );
31 | }
32 | }
33 |
34 | export default Home;
35 |
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/steady-state-server-stub/readme.txt:
--------------------------------------------------------------------------------
1 | #starting the fake steady state data service
2 | cd chaos-qoala-integration-tests
3 | npm run steadystate
4 |
5 | #default port is 7766
6 |
7 | #start the test run (example below assumes your machine has an IP of 192.168.0.85):
8 |
9 | PUT http://192.168.0.85:7766/steadystate/start
10 |
11 | #get the results (if there was no prior call to /steadystate/start then error code of 400 will be returned)
12 |
13 | base url is /steadystate/start which then takes 4 url args
14 | 1. baseline - baseline (integer) figure for steady state
15 | 2. lower variance - % as integer
16 | 3. upper variance - % as an integer
17 | 4. results per second - as an integer
18 |
19 | so to get results based on a baseline of 100 with a maximum 50% variance in the lower range
20 | and a 10% variance in the upper range, and 10 results per test run second you would use the following:
21 |
22 | GET http://192.168.0.85:7766/steadystate/stop/100/50/10/10/
--------------------------------------------------------------------------------
/client/src/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, Fragment } from 'react';
2 | import { BrowserRouter as Router, Route } from 'react-router-dom';
3 | import Home from './Home.jsx';
4 | // import NavBar from './NavBar.jsx';
5 | import Upload from './Upload.jsx';
6 | import NavBar from './NavBar.jsx';
7 | // import Main from './Main.jsx';
8 | // import Instructions from './Instructions.jsx';
9 | // import ThisIsUs from './ThisIsUs.jsx';
10 | class App extends Component {
11 | constructor() {
12 | super();
13 | this.state = {};
14 | }
15 |
16 | render() {
17 | return (
18 |
19 |
20 |
21 |
22 | {/* */}
23 |
24 | {/*
25 |
26 | */}
27 |
28 |
29 | );
30 | }
31 | }
32 |
33 | export default App;
--------------------------------------------------------------------------------
/client/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const express = require('express');
3 |
4 | module.exports = {
5 | entry: {
6 | src: './src/index.js'
7 | },
8 | output: {
9 | path: path.resolve(__dirname, 'build'),
10 | filename: 'bundle.js'
11 | },
12 | mode: process.env.NODE_ENV,
13 | module: {
14 | rules: [
15 | {
16 | test: /\.jsx?/,
17 | exclude: /node_modules/,
18 | loader: 'babel-loader',
19 | query: {
20 | presets: ['@babel/env', '@babel/react']
21 | }
22 | },
23 | {
24 | test: /\.s?css/,
25 | use: [
26 | 'style-loader', 'css-loader', 'sass-loader'
27 | ]
28 | }
29 | ]
30 | },
31 | devServer: {
32 | publicPath: '/build',
33 | proxy: {
34 | '/api': 'http://localhost:3000'
35 | }
36 | },
37 |
38 | }
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/client/src/components/SteadyStateChart.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Line } from 'react-chartjs-2';
3 |
4 | class SteadyStateChart extends Component {
5 | constructor(props){
6 | super(props);
7 | }
8 |
9 | // static defaultProps = {
10 | // displayTitle: true,
11 | // displayLegend: true,
12 | // legendPosition: 'right',
13 | // location: 'City'
14 | // }
15 |
16 | render() {
17 | return (
18 |
19 |
33 |
34 | );
35 | }
36 |
37 | }
38 |
39 | export default SteadyStateChart;
--------------------------------------------------------------------------------
/cli/commands/initalize.js:
--------------------------------------------------------------------------------
1 | const { writeFileSync } = require('fs');
2 | const { join } = require('path');
3 |
4 | // reads the path to the package.json file
5 | const packageJSON = join(__dirname, '..', 'package.json');
6 | const configFile = require(packageJSON);
7 |
8 | /* Saves the users' questions in the package.json file */
9 | function initalize(state) {
10 | configFile.chaosQoala = {
11 | ...state,
12 | };
13 |
14 | writeFileSync(
15 | packageJSON,
16 | JSON.stringify(configFile, null, 2),
17 | );
18 | }
19 |
20 | /* This function reads the configuration file */
21 |
22 | function read() {
23 | const { configQuestions = {} } = configFile;
24 | const { questions = [], state = {} } = configQuestions;
25 | return {
26 | questions,
27 | state,
28 | };
29 | }
30 |
31 | /* This function saves the updated config file */
32 | function save(state) {
33 | const { configQuestions = {} } = configFile;
34 | initalize({ ...configQuestions, state: { ...configQuestions.state, ...state } });
35 | }
36 |
37 | module.exports = {
38 | initalize,
39 | read,
40 | save,
41 | };
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 OSLabs Beta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/client/src/components/ChaosFlagChart.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Bar } from 'react-chartjs-2';
3 |
4 | class ChaosFlagChart extends Component {
5 | constructor(props){
6 | super(props);
7 | }
8 |
9 | // static defaultProps = {
10 | // displayTitle: true,
11 | // displayLegend: true,
12 | // legendPosition: 'right',
13 | // location: 'City'
14 | // }
15 |
16 | render() {
17 | return (
18 |
19 |
38 |
39 | );
40 | }
41 |
42 | }
43 |
44 | export default ChaosFlagChart;
--------------------------------------------------------------------------------
/client/src/components/Main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NavLink } from "react-router-dom";
3 |
4 | function Main() {
5 | return (
6 |
7 |
8 |
12 | ChaosQoaLa
13 |
14 |
Bringing Chaos Engineering to GraphQL
15 |
16 |
17 | Star Us on GitHub!{" "}
18 |
19 |
26 |
27 |
28 |
33 |
34 | );
35 | }
36 |
37 | export default Main;
38 |
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "testing",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "npm link chaos-qoala-agent && jest --config ./tests/jest.config.js --runInBand",
8 | "steadystate": "nodemon ./steady-state-server-stub/server.js"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "devDependencies": {
14 | "eslint": "^6.2.1",
15 | "eslint-config-airbnb": "^18.0.1",
16 | "eslint-config-airbnb-base": "^14.0.0",
17 | "eslint-plugin-import": "^2.18.2",
18 | "eslint-plugin-jest": "^22.15.2",
19 | "eslint-plugin-jsx-a11y": "^6.2.3",
20 | "eslint-plugin-react": "^7.14.3",
21 | "eslint-plugin-react-hooks": "^1.7.0",
22 | "jest": "^24.9.0",
23 | "nodemon": "^1.19.1"
24 | },
25 | "dependencies": {
26 | "apollo-server-express": "^2.8.2",
27 | "chaosqoala-agent": "^1.0.0",
28 | "express": "^4.17.1",
29 | "express-graphql": "^0.9.0",
30 | "graphql": "^14.4.2",
31 | "node-fetch": "^2.6.0",
32 | "path": "^0.12.7",
33 | "random-js": "^2.1.0",
34 | "socket.io-client": "^2.2.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/aws/lambda.js:
--------------------------------------------------------------------------------
1 | exports.handler = function(event, context) {
2 | const body = JSON.parse(event.body);
3 | const data = JSON.parse(body);
4 |
5 | const { chaosResults } = data;
6 | const { agentData } = data;
7 |
8 | const chaosTimes = chaosResults.map(function(element) {
9 | return element["timeOfResult"];
10 | });
11 |
12 | const chaosData = chaosResults.map(function(element) {
13 | return element["result"];
14 | });
15 |
16 | const agentTimes = agentData.map(function(element) {
17 | return element["timeOfResponse"];
18 | });
19 |
20 | const agentFlags = agentData.map(function(element) {
21 | return element["chaosResponse"];
22 | });
23 |
24 | const responseCode = 200;
25 | const response = {
26 | statusCode: responseCode,
27 | headers: {
28 | "Access-Control-Allow-Origin": "*",
29 | "Content-Type" : "application/json",
30 | "Access-Control-Allow-Origin" : "*",
31 | "Allow" : "OPTIONS, POST",
32 | "Access-Control-Allow-Methods" : "OPTIONS, POST",
33 | "Access-Control-Allow-Headers" : "*"
34 | },
35 | body: JSON.stringify({ chaosTimes, chaosData, agentTimes, agentFlags })
36 | };
37 | context.succeed(response);
38 | };
--------------------------------------------------------------------------------
/cli/questions/questions.js:
--------------------------------------------------------------------------------
1 | module.exports = [{
2 | type: 'input',
3 | name: 'socketPort',
4 | message: 'Please enter the URI of the Socket.io port over which Chaos will be sent and received',
5 | }, {
6 | type: 'input',
7 | name: 'graphQLPort',
8 | message: 'Please enter the URI of GraphQL service',
9 | },
10 | {
11 | type: 'number',
12 | name: 'blastRadius',
13 | message: 'Please enter your desired blast radius',
14 | default: Math.random(),
15 | }, {
16 | type: 'number',
17 | name: 'delay',
18 | message: 'Please enter the amount of time you would like your data to be dealyed (in milliseconds)',
19 | }, {
20 | type: 'number',
21 | name: 'runTime',
22 | message: 'Please enter how long you would like the ChaosQoala to run (in minutes). **NOTE: The default runtime is 1 hour',
23 | default: 60,
24 | }, {
25 | type: 'input',
26 | name: 'steadyStateStartURL',
27 | message: 'Please enter the steady state start URL',
28 | }, {
29 | type: 'input',
30 | name: 'steadyStateStartHTTPVerb',
31 | message: 'Please enter the steady state start HTTP verb',
32 | }, {
33 | type: 'input',
34 | name: 'steadyStateStopURL',
35 | message: 'Please enter the steady state stop URL',
36 | }, {
37 | type: 'input',
38 | name: 'steadyStateStopHTTPVerb',
39 | message: 'Please enter the steady state stop HTTP verb',
40 | },
41 | ];
42 |
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/tests/platforms/express-standalone.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | const express = require('express');
3 | const expressGraphQL = require('express-graphql');
4 | const path = require('path');
5 | const fs = require('fs');
6 | const { buildSchema } = require('graphql');
7 |
8 | // Create an express server and a GraphQL endpoint
9 | const app = express();
10 |
11 | // 🐨 🐨 🐨 🐨 🐨 🐨 🐨 🐨 🐨
12 | // eslint-disable-next-line import/no-unresolved
13 | // eslint-disable-next-line import/no-extraneous-dependencies
14 | const { chaos, chaosSocketServer } = require('chaos-qoala-agent');
15 |
16 | app.use(chaos);
17 | this.chaosSocketServer = chaosSocketServer;
18 | // 🐨 🐨 🐨 🐨 🐨 🐨 🐨 🐨 🐨
19 |
20 | // get gql test schema definition
21 | const pathToSchema = path.resolve(__dirname, '../gql-schema/schema.gql');
22 | const schema = buildSchema(fs.readFileSync(pathToSchema, 'utf-8'));
23 |
24 | // Root resolver
25 | const root = {
26 | knockMeOut: () => 'I should be knocked out by Chaos QoaLa',
27 | dontKnockMeOut: () => 'I should NOT be knocked out by Chaos QoaLa',
28 | };
29 |
30 | app.use('/graphql', expressGraphQL({
31 | schema,
32 | rootValue: root,
33 | graphiql: true,
34 | }));
35 |
36 | app.listen(3000, () => {
37 | // eslint-disable-next-line no-console
38 | console.log('🚆 Express GraphQL Server ready at localhost:3000/graphql');
39 | });
40 |
--------------------------------------------------------------------------------
/cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chaosqoala",
3 | "version": "1.0.0",
4 | "description": "Chaos Engineering meets GraphQL",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "bin": {
12 | "chaosqoala": "./index.js"
13 | },
14 | "dependencies": {
15 | "chalk": "^2.4.2",
16 | "commander": "^3.0.0",
17 | "express": "^4.17.1",
18 | "figlet": "^1.2.3",
19 | "fs": "0.0.1-security",
20 | "http": "0.0.0",
21 | "inquirer": "^7.0.0",
22 | "node-fetch": "^2.6.0",
23 | "node-sass": "^4.12.0",
24 | "prompt": "^1.0.0",
25 | "save": "^2.4.0",
26 | "socket.io": "^2.2.0",
27 | "socket.io-client": "^2.2.0"
28 | },
29 | "chaosQoala": {
30 | "state": {
31 | "socketPort": "http://localhost:1025",
32 | "graphQLPort": "http://localhost:3000/graphql",
33 | "blastRadius": "0.5",
34 | "delay": "3000",
35 | "runTime": "60",
36 | "steadyStateStartURL": "http://localhost:7766/steadystate/start",
37 | "steadyStateStartHTTPVerb": "PUT",
38 | "steadyStateStopURL": "http://localhost:7766/steadystate/stop/100/50/20/10",
39 | "steadyStateStopHTTPVerb": "GET",
40 | "affectedQueries": {
41 | "dontKnockMeOut": false,
42 | "knockMeOut": true
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/cli/util/QueryInspector.js:
--------------------------------------------------------------------------------
1 | const fetch = require('node-fetch');
2 |
3 | class QueryInspector {
4 | constructor(uri) {
5 | this.uri = uri;
6 | this.queryMap = {};
7 | }
8 |
9 | // returns the count of querys found in the schema
10 | async hydrateQueryMap() {
11 | const queryToGetAllQueryNames = '{__schema{queryType{fields{name}}}}';
12 | const query = { query: queryToGetAllQueryNames, variables: null };
13 |
14 | const response = await fetch(this.uri, {
15 | method: 'POST',
16 | headers: {
17 | 'Content-Type': 'application/json',
18 | },
19 | body: JSON.stringify(query),
20 | });
21 | const responseJson = await response.json();
22 |
23 | // eslint-disable-next-line no-underscore-dangle
24 | responseJson.data.__schema.queryType.fields.forEach((element) => {
25 | // add query as key in an object - the boolean represents
26 | // if the data returned from the query will potentially be
27 | // affected due to user preferences in the chaos config
28 | // for the moment we don't have access to those preferences
29 | // so just initialize to false
30 | this.queryMap[element.name] = false;
31 | });
32 |
33 | return Object.keys(this.queryMap).length;
34 | }
35 |
36 | // returns the list of available queries once the hydrate function has been called
37 | getQueryMap() {
38 | return this.queryMap;
39 | }
40 | }
41 |
42 | module.exports = QueryInspector;
43 |
--------------------------------------------------------------------------------
/client/src/components/Graphs.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import SteadyStateChart from './SteadyStateChart.jsx';
3 | import ChaosFlagChart from './ChaosFlagChart.jsx';
4 |
5 | class Graphs extends Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | steadyStateChartData: {},
10 | chaosFlagChartData: {}
11 | };
12 | }
13 |
14 |
15 | render() {
16 | const steadyStateChartConfig = {
17 | labels: this.props.data.chaosTimes,
18 | datasets: [
19 | {
20 | label: "Latency",
21 | data: this.props.data.chaosData,
22 | fill: false,
23 | borderColor: "red",
24 | backgroundColor: "blue"
25 | }
26 | ]
27 | };
28 | const chaosFlagChartConfig = {
29 | labels: this.props.data.agentTimes,
30 | datasets: [
31 | {
32 | label: "Chaos Instances",
33 | data: this.props.data.agentFlags,
34 | backgroundColor: "red",
35 | fill: false
36 | }
37 | ]
38 | };
39 | return (
40 |
41 |
ChaosQoaLa Experiment Results
42 |
46 |
50 |
51 | );
52 | }
53 | }
54 |
55 | export default Graphs;
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/steady-state-server-stub/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const getResults = require('./results');
3 |
4 | const PORT = 7766;
5 | const app = express();
6 |
7 | let startTimeGlobal;
8 |
9 | app.put('/steadystate/start/', (req, res) => {
10 | startTimeGlobal = new Date();
11 | res.sendStatus(200);
12 | });
13 |
14 | app.get('/steadystate/stop/:baseline/:lowerVariance/:upperVariance/:resultsPerSecond/', (req, res) => {
15 | const stopTime = new Date(Date.now());
16 | // if no start time then return client error 400
17 | if (!startTimeGlobal) return res.sendStatus(400);
18 | // send back results array based on user query params
19 | const baseline = parseInt(req.params.baseline, 10);
20 | const lowerVariance = parseInt(req.params.lowerVariance, 10);
21 | const upperVariance = parseInt(req.params.upperVariance, 10);
22 | const resultsPerSecond = parseInt(req.params.resultsPerSecond, 10);
23 | res.status(200);
24 | res.json(getResults(
25 | baseline,
26 | lowerVariance,
27 | upperVariance,
28 | startTimeGlobal,
29 | stopTime,
30 | resultsPerSecond,
31 | ));
32 | // reset start time so that we can do a clean start/stop cycle
33 | // on the next iteration of testing
34 | startTimeGlobal = undefined;
35 | return undefined;
36 | });
37 |
38 | app.listen(PORT, () => {
39 | // eslint-disable-next-line no-console
40 | console.log(`📉 steady state service ready at localhost:${PORT}/steadystate/`);
41 | });
42 |
43 | module.exports = app;
44 |
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/steady-state-server-stub/results.js:
--------------------------------------------------------------------------------
1 | const { MersenneTwister19937, integer } = require('random-js');
2 |
3 | const getResults = (
4 | baseline,
5 | lowerVariance,
6 | upperVariance,
7 | startDate,
8 | endDate,
9 | resultsPerSecond,
10 | ) => {
11 | // results container
12 | const results = [];
13 |
14 | // calculate min/max range via varience against baseline
15 | const lowerDelta = Math.floor(baseline * (lowerVariance / 100));
16 | const upperDelta = Math.floor(baseline * (upperVariance / 100));
17 | const min = baseline - lowerDelta;
18 | const max = baseline + upperDelta;
19 |
20 | // create a Mersenne Twister-19937 that is auto-seeded based on time and other random values
21 | const engine = MersenneTwister19937.autoSeed();
22 | // create a distribution that will consistently produce integers within min/max
23 | const distribution = integer(min, max);
24 | // ms interval bewteen results
25 | const msBetweenResults = Math.floor(1000 / resultsPerSecond);
26 |
27 | // fake time that will increment during results production
28 | let resultMs = startDate.getTime();
29 | const endMs = endDate.getTime();
30 | // while the end time hasnt been reached add a new fake result
31 | while ((resultMs + msBetweenResults) <= endMs) {
32 | // new result time
33 | resultMs += msBetweenResults;
34 | results.push({ timeOfResult: (new Date(resultMs)).toJSON(), result: distribution(engine) });
35 | }
36 | return results;
37 | };
38 |
39 | module.exports = getResults;
40 |
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/tests/platforms/express-graphql-app.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | const express = require('express');
3 | const expressGraphQL = require('express-graphql');
4 | const path = require('path');
5 | const fs = require('fs');
6 | const { buildSchema } = require('graphql');
7 |
8 | module.exports = {
9 | gqlserver: undefined,
10 |
11 | port: 3000,
12 |
13 | chaosSocketServer: undefined,
14 |
15 | start() {
16 | // Create an express server and a GraphQL endpoint
17 | const app = express();
18 |
19 | // 🐨 🐨 🐨 🐨 🐨 🐨 🐨 🐨 🐨
20 | const { chaos, chaosSocketServer } = require('chaosqoala-agent');
21 | app.use(chaos);
22 | this.chaosSocketServer = chaosSocketServer;
23 | // 🐨 🐨 🐨 🐨 🐨 🐨 🐨 🐨 🐨
24 |
25 | // get gql test schema definition
26 | const pathToSchema = path.resolve(__dirname, '../gql-schema/schema.gql');
27 | const schema = buildSchema(fs.readFileSync(pathToSchema, 'utf-8'));
28 |
29 | // Root resolver
30 | const root = {
31 | knockMeOut: () => 'I should be knocked out by Chaos QoaLa',
32 | dontKnockMeOut: () => 'I should NOT be knocked out by Chaos QoaLa',
33 | };
34 |
35 | app.use('/graphql', expressGraphQL({
36 | schema,
37 | rootValue: root,
38 | graphiql: true,
39 | }));
40 |
41 | this.gqlserver = app.listen(this.port, () => {
42 | // eslint-disable-next-line no-console
43 | console.log(`🚆 Express GraphQL Server ready at localhost:${this.port}/graphql`);
44 | });
45 | },
46 |
47 | stop() {
48 | // we need to close the chaos socket server for a clean exit
49 | this.chaosSocketServer.close();
50 | // then close the gql server
51 | this.gqlserver.close();
52 | },
53 | };
54 |
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/tests/platforms/apollo-server-express-app.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable global-require */
2 | const express = require('express');
3 | const { ApolloServer } = require('apollo-server-express');
4 | const path = require('path');
5 | const fs = require('fs');
6 |
7 | module.exports = {
8 | gqlserver: undefined,
9 |
10 | port: 4000,
11 |
12 | chaosSocketServer: undefined,
13 |
14 | start() {
15 | // get gql test schema definition
16 | const pathToSchema = path.resolve(__dirname, '../gql-schema/schema.gql');
17 | const typeDefs = fs.readFileSync(pathToSchema, 'utf-8');
18 |
19 | // Provide resolver functions for your schema fields
20 | const resolvers = {
21 | Query: {
22 | knockMeOut: () => 'I should be knocked out by Chaos QoaLa',
23 | dontKnockMeOut: () => 'I should NOT be knocked out by Chaos QoaLa',
24 | },
25 | };
26 |
27 | const apollo = new ApolloServer({ typeDefs, resolvers });
28 | const app = express();
29 |
30 | // 🐨 🐨 🐨 🐨 🐨 🐨 🐨 🐨 🐨
31 | const { chaos, chaosSocketServer } = require('chaosqoala-agent');
32 | app.use(chaos);
33 | this.chaosSocketServer = chaosSocketServer;
34 | // 🐨 🐨 🐨 🐨 🐨 🐨 🐨 🐨 🐨
35 |
36 | apollo.applyMiddleware({ app }); // app is from an existing express app
37 |
38 | this.gqlserver = app.listen({ port: this.port }, () => {
39 | // eslint-disable-next-line no-console
40 | console.log(`🚀 Apollo Server ready at http://localhost:${this.port}${apollo.graphqlPath}`);
41 | });
42 | },
43 |
44 | stop() {
45 | // we need to close the chaos socket server for a clean exit
46 | this.chaosSocketServer.close();
47 | // then close the gql server
48 | this.gqlserver.close();
49 | },
50 | };
51 |
--------------------------------------------------------------------------------
/cli/commands/configure.js:
--------------------------------------------------------------------------------
1 | const { prompt } = require('inquirer');
2 | const { magentaBright } = require('chalk');
3 | const { textSync } = require('figlet');
4 | const QueryInspector = require('../util/QueryInspector');
5 |
6 | // Requiring in the configure file from the same file
7 | const initalize = require('./initalize');
8 | // importing the read and save functions from the initalize.js file
9 | const { read, save } = initalize;
10 |
11 | // declaring the questions and state objects
12 | let { questions } = read();
13 |
14 | /* Declaring a variable and initialziing it to null.
15 | This will variable will be set to the results of
16 | the user answering the questions and it will be
17 | passed to the send function to connect to the socket */
18 |
19 | questions = require('../questions/questions')
20 | .map(({ message, name }) => ({
21 | message,
22 | // This will be the returned property on the package.JSON object
23 | name,
24 | }));
25 |
26 | function configure() {
27 | /* The below uses figlet and chalk to print ChaosQoala in
28 | the terminal when the user starts ChaosQoala */
29 | console.log(
30 | magentaBright(
31 | textSync('ChaosQoaLa', { horizontalLayout: 'full' }),
32 | ),
33 | );
34 | prompt(questions)
35 | .then(async (result) => {
36 | const { graphQLPort } = result;
37 | const inspector = new QueryInspector(graphQLPort);
38 | await inspector.hydrateQueryMap();
39 | const affectedQueries = inspector.getQueryMap();
40 | save({ ...result, affectedQueries });
41 | console.log('Your config has been saved to package.json - to enable data knockout on a schema query set it\'s flag to true in the affectedQueries section of package.json');
42 | });
43 | }
44 |
45 | module.exports = {
46 | configure,
47 | };
48 |
--------------------------------------------------------------------------------
/client/src/components/Instructions.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { NavLink } from "react-router-dom";
3 |
4 | function Instructions() {
5 | return (
6 |
7 |
8 |
9 |
Planning Phase
10 |
Add steady state data feed and make baseline data available
11 |
Install and configure the agent on your GraphQL servers
12 |
Install the CLI on test users’ machines
13 |
14 |
15 |

16 |
17 |
18 |
19 |
20 |
Execution Phase
21 |
Run CLI to create a test setup
22 |
Start & stop the experiment
23 |
24 |
25 |

26 |
27 |
28 |
29 |
30 |
Upload & Analyze Results
31 |
Compare steady state data during "chaos" against baseline
32 |
33 |
34 |

38 |
39 |
40 |
41 |
42 |
43 |
46 |
47 | );
48 | }
49 |
50 | export default Instructions;
51 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactcharts",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "aws-sdk": "^2.522.0",
7 | "axios": "^0.19.0",
8 | "babel": "^6.23.0",
9 | "bluebird": "^3.5.5",
10 | "chart.js": "^2.8.0",
11 | "express": "^4.17.1",
12 | "file-type": "^12.3.0",
13 | "multiparty": "^4.2.1",
14 | "node-sass": "^4.12.0",
15 | "nodemon": "^1.19.2",
16 | "path": "^0.12.7",
17 | "react": "^16.9.0",
18 | "react-chartjs-2": "^2.7.6",
19 | "react-dom": "^16.9.0",
20 | "react-dropzone": "^10.1.8",
21 | "react-router": "^5.0.1",
22 | "react-router-dom": "^5.0.1",
23 | "react-scripts": "3.1.1",
24 | "webpack-dev-server": "^3.8.0"
25 | },
26 | "scripts": {
27 | "start": "nodemon server/server.js",
28 | "build": "NODE_ENV=production webpack",
29 | "dev": "NODE_ENV=development nodemon server/server.js & webpack-dev-server --open",
30 | "test": "react-scripts test",
31 | "eject": "react-scripts eject"
32 | },
33 | "eslintConfig": {
34 | "extends": "react-app"
35 | },
36 | "browserslist": {
37 | "production": [
38 | ">0.2%",
39 | "not dead",
40 | "not op_mini all"
41 | ],
42 | "development": [
43 | "last 1 chrome version",
44 | "last 1 firefox version",
45 | "last 1 safari version"
46 | ]
47 | },
48 | "devDependencies": {
49 | "@babel/core": "^7.6.0",
50 | "@babel/preset-env": "^7.6.0",
51 | "babel-loader": "^8.0.6",
52 | "eslint": "^6.3.0",
53 | "eslint-config-airbnb": "^18.0.1",
54 | "eslint-plugin-import": "^2.18.2",
55 | "eslint-plugin-jsx-a11y": "^6.2.3",
56 | "eslint-plugin-react": "^7.14.3",
57 | "eslint-plugin-react-hooks": "^1.7.0",
58 | "style-loader": "^1.0.0",
59 | "webpack": "^4.39.3",
60 | "webpack-cli": "^3.3.8"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/aws/deploy-aws.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const chaosDeployer = require('./chaos-website-deployer');
3 | const AdmZip = require('adm-zip');
4 |
5 | const awsVars = require('./common-aws');
6 |
7 | (async () => {
8 | try {
9 | await chaosDeployer.init();
10 |
11 | const s3CreationParameters = [
12 | {
13 | ParameterKey: 'chaosDomain',
14 | ParameterValue: awsVars.DOMAIN_NAME,
15 | },
16 | {
17 | ParameterKey: 'chaosLambdaS3Bucket',
18 | ParameterValue: awsVars.LAMBDA_S3_BUCKET,
19 | }];
20 |
21 | await chaosDeployer.createStack(awsVars.S3_STACK_NAME, './cloud-formation-s3.yml', s3CreationParameters);
22 |
23 | const indexContents = fs.readFileSync('../client/index.html', 'utf8');
24 | const bundleContents = fs.readFileSync('../client/build/bundle.js', 'utf8');
25 | await chaosDeployer.createObject(awsVars.DOMAIN_NAME, 'index.html', indexContents, {ContentType: "text/html", ACL: "public-read"});
26 | await chaosDeployer.createObject(awsVars.DOMAIN_NAME, 'build/bundle.js', bundleContents, {ContentType: "text/javascript", ACL: "public-read"});
27 |
28 | const zip = new AdmZip();
29 | zip.addLocalFile('./lambda.js');
30 | const lambdaZip = zip.toBuffer();
31 | await chaosDeployer.createObject(awsVars.LAMBDA_S3_BUCKET, 'lambda.zip', lambdaZip);
32 |
33 | const apiCreationParameters = [
34 | { ParameterKey: 'chaosLambdaS3Bucket', ParameterValue: awsVars.LAMBDA_S3_BUCKET,},
35 | { ParameterKey: 'regionalCertificateArn', ParameterValue: awsVars.REGIONAL_CERTIFICATE_ARN,},
36 | { ParameterKey: 'customHostedZoneId', ParameterValue: awsVars.CUSTOM_DOMAIN_HOSTED_ZONE_ID,},
37 | { ParameterKey: 'regionHostedZoneId', ParameterValue: awsVars.REGION_HOSTED_ZONE_ID,},
38 | { ParameterKey: 'chaosDomainApi', ParameterValue: awsVars.API_END_POINT,},
39 | ];
40 | await chaosDeployer.createStack(awsVars.API_STACK_NAME, './cloud-formation-lambda-api.yml', apiCreationParameters);
41 |
42 | } catch (err) {
43 | console.log(err);
44 | }
45 | })();
46 |
--------------------------------------------------------------------------------
/client/src/components/FileUpload.jsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useState } from 'react';
2 | import { useDropzone } from 'react-dropzone';
3 | import Graphs from './Graphs.jsx';
4 |
5 | function FileUpload() {
6 | const [data, setData] = useState();
7 | const onDrop = useCallback((acceptedFiles) => {
8 | const reader = new FileReader();
9 |
10 | reader.onabort = () => console.log('file reading was aborted');
11 | reader.onerror = () => console.log('file reading has failed');
12 | reader.onload = () => {
13 | // Do whatever you want with the file contents
14 | const binaryStr = reader.result;
15 | // aws lambda end point
16 | fetch('https://api.chaosqoala.io/transform/logs/', {
17 | method: 'POST',
18 | mode: 'cors',
19 | cache: 'no-cache',
20 | headers: {
21 | 'Content-Type': 'application/json',
22 | },
23 | body: JSON.stringify(binaryStr),
24 | })
25 | .then((response) => response.json())
26 | .then((json) => setData(json));
27 | };
28 |
29 | acceptedFiles.forEach((file) => reader.readAsBinaryString(file));
30 | }, []);
31 |
32 | // call back that is invoked when file is rejected
33 | const onDropRejected = useCallback((rejectedFile) => {
34 | console.log('Error uploading file - probably because it is not JSON');
35 | });
36 |
37 | // accept a single json file
38 | const accept = 'application/json';
39 | const multiple = false;
40 |
41 | const { getRootProps, getInputProps, isDragActive } = useDropzone({
42 | onDrop, onDropRejected, accept, multiple,
43 | });
44 | return (
45 |
46 |
47 |
48 | {
49 | isDragActive
50 | ?
Drop the files here ...
51 | :
Drag and drop some files here, or click to select files
52 | }
53 |
54 |
55 | {!data ? '' : }
56 |
57 |
58 | );
59 | }
60 |
61 | export default FileUpload;
62 |
--------------------------------------------------------------------------------
/client/server/server.js:
--------------------------------------------------------------------------------
1 | // const express = require("express");
2 | // const app = express();
3 | // const AWS = require("aws-sdk");
4 | // const fs = require("fs");
5 | // const fileType = require("file-type");
6 | // const bluebird = require("bluebird");
7 | // const multiparty = require("multiparty");
8 |
9 | // // const { AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY } = require("../creds");
10 |
11 | // // configure the keys for accessing AWS
12 | // AWS.config.update({
13 | // accessKeyId: AWS_ACCESS_KEY_ID,
14 | // secretAccessKey: AWS_SECRET_ACCESS_KEY
15 | // });
16 |
17 | // // configure AWS to work with promises
18 | // AWS.config.setPromisesDependency(bluebird);
19 |
20 | // // create S3 instance
21 | // const s3 = new AWS.S3();
22 |
23 | // // abstracts function to upload a file returning a promise
24 | // const uploadFile = (buffer, name, type) => {
25 | // const params = {
26 | // ACL: "public-read",
27 | // Body: buffer,
28 | // Bucket: process.env.S3_BUCKET,
29 | // ContentType: type.mime,
30 | // Key: `${name}.${type.ext}`
31 | // };
32 | // return s3.upload(params).promise();
33 | // };
34 |
35 | // // Define POST route
36 | // app.post("/test-upload", (request, response) => {
37 | // const form = new multiparty.Form();
38 | // form.parse(request, async (error, fields, files) => {
39 | // if (error) throw new Error(error);
40 | // try {
41 | // const path = files.file[0].path;
42 | // const buffer = fs.readFileSync(path);
43 | // const type = fileType(buffer);
44 | // const timestamp = Date.now().toString();
45 | // const fileName = `bucketFolder/${timestamp}-lg`;
46 | // const data = await uploadFile(buffer, fileName, type);
47 | // return response.status(200).send(data);
48 | // } catch (error) {
49 | // return response.status(400).send(error);
50 | // }
51 | // });
52 | // });
53 |
54 | // app.listen(PORT || 9000);
55 | // console.log("Server up and running...");
56 |
57 | const express = require('express');
58 | const app = express();
59 | const path = require('path');
60 | const fs = require('fs');
61 |
62 | // serve index.html on the route '/'
63 | app.get('/', (req, res) => {
64 | res.sendFile(path.join(__dirname, '../index.html'));
65 | });
66 |
67 |
68 | app.listen(3000); //listens on port 3000 -> http://localhost:3000/
69 |
70 |
71 |
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/tests/express.test.js:
--------------------------------------------------------------------------------
1 | const fetch = require('node-fetch');
2 | const io = require('socket.io-client');
3 | // express with chaos agent embedded
4 | const express = require('./platforms/express-graphql-app');
5 | // import chaos config file - TODO: remove once controler has command line mode
6 | const config = require('../chaos-qoala-config');
7 | // get express config
8 | const platform = express;
9 |
10 | let agentDataReceived = false;
11 |
12 | // increase default jest timeout as our tests intentionally add latency
13 | jest.setTimeout(30000);
14 |
15 | // start platform server before testing
16 | beforeAll((done) => {
17 | platform.start();
18 | const socket = io.connect('http://localhost:1025');
19 | // define event handler for sucessfull connection
20 | socket.on('connect', async () => {
21 | socket.emit('eucalyptus', config, () => {
22 | done();
23 | });
24 | });
25 | // listen for incoming agent data sent during a chaos experiment
26 | socket.on('eucalyptus', (agentData) => {
27 | agentDataReceived = true;
28 | });
29 | });
30 |
31 | // test altering basic query response
32 | describe('Chaos 🐨 Proof of Concept', () => {
33 | // note the async so we can await fetch
34 | test('change response data', async () => {
35 | // construct url
36 | const url = `http://localhost:${platform.port}/graphql`;
37 |
38 | // gql query
39 | const query = {
40 | query: '{ dontKnockMeOut, knockMeOut }',
41 | variables: null,
42 | };
43 |
44 | // start time
45 | const startTimeInMS = performance.now();
46 |
47 | // note we await the fetch to stop jest moving on
48 | const response = await fetch(url, {
49 | method: 'POST',
50 | headers: {
51 | 'Content-Type': 'application/json',
52 | },
53 | body: JSON.stringify(query),
54 | });
55 |
56 | // expect the correct injection via chaos middleware
57 | const json = await response.json();
58 | expect(json.data.dontKnockMeOut).toBe('I should NOT be knocked out by Chaos QoaLa');
59 | expect(json.data.knockMeOut).toBe(undefined);
60 |
61 | // check time is within 3 seconds of the configured delay
62 | const THREE_SECONDS_IN_MS = 3000;
63 | const timeTakenInMS = performance.now() - startTimeInMS;
64 | expect(timeTakenInMS).toBeGreaterThan(config.delay);
65 | expect(timeTakenInMS).toBeLessThan(config.delay + THREE_SECONDS_IN_MS);
66 | expect(agentDataReceived).toBe(true);
67 | });
68 | });
69 |
70 | // stop platform servers at end of all tests
71 | afterAll(() => {
72 | platform.stop();
73 | });
74 |
--------------------------------------------------------------------------------
/chaos-qoala-integration-tests/tests/apollo.test.js:
--------------------------------------------------------------------------------
1 | const fetch = require('node-fetch');
2 | const io = require('socket.io-client');
3 | // apollp with chaos agent embedded
4 | const apollo = require('./platforms/apollo-server-express-app');
5 | // import chaos config file - TODO: remove once controler has command line mode
6 | const config = require('../chaos-qoala-config');
7 | // get apollo config
8 | const platform = apollo;
9 |
10 | let agentDataReceived = false;
11 |
12 | // increase default jest timeout as our tests intentionally add latency
13 | jest.setTimeout(30000);
14 |
15 | // start platform server before testing
16 | beforeAll((done) => {
17 | platform.start();
18 | const socket = io.connect('http://localhost:1025');
19 | // define event handler for sucessfull connection
20 | socket.on('connect', () => {
21 | socket.emit('eucalyptus', config, () => {
22 | done();
23 | });
24 | });
25 | // testing some code here (to see if agent data is received from index.js--->tests passing sucessfully)
26 | socket.on('eucalyptus', (agentData) => {
27 | agentDataReceived = true;
28 | });
29 | });
30 |
31 | // test altering basic query response
32 | describe('Chaos 🐨 Proof of Concept', () => {
33 | // note the async so we can await fetch
34 | test('change response data', async () => {
35 | // construct url
36 | const url = `http://localhost:${platform.port}/graphql`;
37 |
38 | // gql query
39 | const query = {
40 | query: '{ dontKnockMeOut, knockMeOut }',
41 | variables: null,
42 | };
43 |
44 | // start time
45 | const startTimeInMS = performance.now();
46 |
47 | // note we await the fetch to stop jest moving on
48 | const response = await fetch(url, {
49 | method: 'POST',
50 | headers: {
51 | 'Content-Type': 'application/json',
52 | },
53 | body: JSON.stringify(query),
54 | });
55 |
56 | // expect the correct injection via chaos middleware
57 | const json = await response.json();
58 | expect(json.data.dontKnockMeOut).toBe('I should NOT be knocked out by Chaos QoaLa');
59 | expect(json.data.knockMeOut).toBe(undefined);
60 |
61 | // check time is within 3 seconds of the configured delay
62 | const THREE_SECONDS_IN_MS = 3000;
63 | const timeTakenInMS = performance.now() - startTimeInMS;
64 | expect(timeTakenInMS).toBeGreaterThan(config.delay);
65 | expect(timeTakenInMS).toBeLessThan(config.delay + THREE_SECONDS_IN_MS);
66 | expect(agentDataReceived).toBe(true);
67 | });
68 | });
69 |
70 | // stop platform server at end of all tests
71 | afterAll(() => {
72 | platform.stop();
73 | });
74 |
--------------------------------------------------------------------------------
/cli/commands/start.js:
--------------------------------------------------------------------------------
1 | const { readFileSync, appendFileSync } = require('fs');
2 | const { join } = require('path');
3 | const io = require('socket.io-client');
4 | const fetch = require('node-fetch');
5 |
6 | const ALL_AGENT_DATA = [];
7 |
8 | const packageJSON = join(__dirname, '..', 'package.json');
9 |
10 | /* The below function reads the package.json file and grabs the state
11 | and opens a socket.io connection to send the configure information to the agent */
12 | async function start() {
13 | // console.log(state);
14 | const myPackageJSON = readFileSync(packageJSON, 'utf8');
15 | const parsedMyPackageJSON = JSON.parse(myPackageJSON);
16 | const { state } = parsedMyPackageJSON.chaosQoala;
17 | // Establishes a ensueChaos property on the state that will be set to
18 | state.ensueChaos = true;
19 | // creates result file with current time stamp. Appends file with chaos config from state
20 | const fileTimestamp = new Date().toJSON();
21 |
22 | // Retrieves the user's socket information from the package.json
23 | const { socketPort } = state;
24 | const socket = io.connect(socketPort);
25 | socket.on('connect', () => {
26 | socket.emit('eucalyptus', state, () => {
27 | console.log('GraphQL server received config');
28 | });
29 | });
30 |
31 | socket.on('eucalyptus', (agentData) => {
32 | ALL_AGENT_DATA.push(agentData);
33 | });
34 |
35 | const {
36 | steadyStateStartURL, steadyStateStartHTTPVerb, steadyStateStopURL, steadyStateStopHTTPVerb,
37 | } = state;
38 |
39 | await fetch(steadyStateStartURL, {
40 | method: steadyStateStartHTTPVerb,
41 | })
42 | .then(() => { console.log('steady state monitoring service started successfully'); })
43 | .catch((err) => { console.log(`steady steady monitoring service failed to start: ${err}`); });
44 |
45 | const keypress = async () => {
46 | process.stdin.setRawMode(true);
47 | return new Promise(resolve => process.stdin.once('data', () => {
48 | process.stdin.setRawMode(false);
49 | resolve();
50 | }));
51 | };
52 |
53 | (async () => {
54 | console.log('Press any key to stop chaos');
55 | await keypress();
56 | await fetch(steadyStateStopURL, { method: steadyStateStopHTTPVerb })
57 | .then(response => response.json())
58 | .then((chaosResults) => {
59 | appendFileSync(
60 | `${fileTimestamp}_results.json`,
61 | JSON.stringify({ chaosConfig: state, chaosResults, agentData: ALL_AGENT_DATA }, null, 2),
62 | );
63 | });
64 | // ensueChaos flag changed to false when any key pressed
65 | state.ensueChaos = false;
66 | socket.emit('eucalyptus', state, () => {
67 | console.log('chaos ended');
68 | });
69 | })().then(process.exit);
70 | }
71 |
72 |
73 | module.exports = {
74 | start,
75 | };
76 |
--------------------------------------------------------------------------------
/client/src/components/ThisIsUs.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 |
3 | function ThisIsUs() {
4 | return (
5 |
6 |
7 |
Meet The Team
8 |
9 |
10 |
11 |
Jacob Ory
12 |
13 |
21 |
22 |
23 |
Nicolas Venegas Parker
24 |
25 |
33 |
34 |
35 |
Samantha Wessel
36 |
37 |
45 |
46 |
47 |
Simon Maharai
48 |
49 |
57 |
58 |
59 |
60 |
Want to contribute?
61 |
ChaosQoala is an open source project
62 |
63 |
66 |
© 2019 ChaosQoala/OSLabs. All rights reserved
67 |
68 | );
69 | }
70 |
71 | export default ThisIsUs;
72 |
--------------------------------------------------------------------------------
/client/scss/_navbar.scss:
--------------------------------------------------------------------------------
1 | @mixin for-phone-only {
2 | @media (max-width: 599px) {
3 | @content;
4 | }
5 | }
6 |
7 | @mixin for-tablet-portrait-up {
8 | @media (min-width: 600px) and (max-width: 924px) {
9 | @content;
10 | }
11 | }
12 | @mixin for-tablet-landscape-up {
13 | @media (min-width: 925px) {
14 | @content;
15 | }
16 | }
17 | @mixin for-desktop-up {
18 | @media (min-width: 1200px) {
19 | @content;
20 | }
21 | }
22 | @mixin for-big-desktop-up {
23 | @media (min-width: 1800px) {
24 | @content;
25 | }
26 | }
27 |
28 | #navbar {
29 | background-color: #171c1fe8;
30 | position: fixed;
31 | top: 0;
32 | left: 0;
33 | overflow-x: hidden;
34 | min-width: 100vw;
35 | display: flex;
36 | margin-left: auto;
37 | margin-right: auto;
38 | grid-template-columns: 1fr 15rem 15rem;
39 | justify-content: space-between;
40 | height: 3rem;
41 | align-items: center;
42 | padding: 0 10px;
43 | color: rgba(117, 117, 117, 1);
44 | box-shadow: 5px 10px 20px -20px white;
45 | font-family: "Manjari";
46 | }
47 |
48 | #navBarQL {
49 | height: 2.5rem;
50 | margin-left: 1.3rem;
51 | @include for-phone-only {
52 | height: 2rem;
53 | }
54 | @include for-tablet-portrait-up {
55 | height: 2.3rem;
56 | }
57 | }
58 |
59 | #navbarupload {
60 | box-shadow: inset 0px 1px 0px 0px #ffffff;
61 | background: white;
62 | border: 1px solid #dcdcdc;
63 | border-radius: 5px;
64 | display: inline-block;
65 | color: #666666;
66 | font-weight: bold;
67 | padding: 0.2rem 0.8rem;
68 | text-decoration: none;
69 | @include for-phone-only {
70 | height: 1rem;
71 | border-radius: 3px;
72 | margin-right: 0.5em;
73 | font-size: 0.9em;
74 | padding: 0.1em;
75 | }
76 |
77 | @include for-tablet-portrait-up {
78 | height: 1rem;
79 | border-radius: 3px;
80 | margin-right: 0.5em;
81 | font-size: 0.9em;
82 | padding: 0.1em;
83 | }
84 |
85 | @include for-tablet-landscape-up {
86 | margin-right: 0.5rem;
87 | }
88 | }
89 | #navbarupload:hover {
90 | color: #e31e98;
91 | }
92 |
93 | #navbardownload {
94 | box-shadow: inset 0px 1px 0px 0px #ffffff;
95 | background: white;
96 | border: 1px solid #dcdcdc;
97 | border-radius: 5px;
98 | display: inline-block;
99 | color: #666666;
100 | font-weight: bold;
101 | padding: 0.2rem 0.8rem;
102 | text-decoration: none;
103 | @include for-phone-only {
104 | height: 1rem;
105 | border-radius: 3px;
106 | margin-right: 0.5em;
107 | font-size: 0.9em;
108 | padding: 0.1em;
109 | }
110 | @include for-tablet-portrait-up {
111 | height: 1rem;
112 | border-radius: 3px;
113 | margin-right: 0.5em;
114 | font-size: 0.9em;
115 | padding: 0.1em;
116 | }
117 | }
118 | #navbardownload:hover {
119 | color: #e31e98;
120 | }
121 |
122 | #rightnav {
123 | margin-right: 1.3rem;
124 | }
125 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/aws/cloud-formation-s3.yml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: 2010-09-09
2 | Parameters:
3 | chaosLambdaS3Bucket:
4 | Description: Name of the S3 lambda bucket
5 | Type: String
6 | chaosDomain:
7 | Description: Name of the domain to host on i.e example.com
8 | Type: String
9 | Mappings:
10 | RegionMap:
11 | us-east-1:
12 | S3hostedzoneID: Z3AQBSTGFYJSTF
13 | websiteendpoint: s3-website-us-east-1.amazonaws.com
14 | us-west-1:
15 | S3hostedzoneID: Z2F56UZL2M1ACD
16 | websiteendpoint: s3-website-us-west-1.amazonaws.com
17 | us-west-2:
18 | S3hostedzoneID: Z3BJ6K6RIION7M
19 | websiteendpoint: s3-website-us-west-2.amazonaws.com
20 | eu-west-1:
21 | S3hostedzoneID: Z1BKCTXD74EZPE
22 | websiteendpoint: s3-website-eu-west-1.amazonaws.com
23 | ap-southeast-1:
24 | S3hostedzoneID: Z3O0J2DXBE1FTB
25 | websiteendpoint: s3-website-ap-southeast-1.amazonaws.com
26 | ap-southeast-2:
27 | S3hostedzoneID: Z1WCIGYICN2BYD
28 | websiteendpoint: s3-website-ap-southeast-2.amazonaws.com
29 | ap-northeast-1:
30 | S3hostedzoneID: Z2M4EHUR26P7ZW
31 | websiteendpoint: s3-website-ap-northeast-1.amazonaws.com
32 | sa-east-1:
33 | S3hostedzoneID: Z31GFT0UA1I2HV
34 | websiteendpoint: s3-website-sa-east-1.amazonaws.com
35 | us-east-2:
36 | S3hostedzoneID: Z2O1EMRO9K5GLX
37 | websiteendpoint: s3-website.us-east-2.amazonaws.com
38 | Resources:
39 | S3LambdaBucket:
40 | Type: AWS::S3::Bucket
41 | Properties:
42 | BucketName:
43 | # refer to chaos-website-deployer code to work out how this param is passed
44 | Ref: chaosLambdaS3Bucket
45 | PublicAccessBlockConfiguration:
46 | BlockPublicAcls: true
47 | BlockPublicPolicy: true
48 | IgnorePublicAcls: true
49 | RestrictPublicBuckets: true
50 | RootBucket:
51 | Type: AWS::S3::Bucket
52 | Properties:
53 | BucketName:
54 | Ref: chaosDomain
55 | AccessControl: PublicRead
56 | WebsiteConfiguration:
57 | IndexDocument: index.html
58 | ErrorDocument: 404.html
59 | WWWBucket:
60 | Type: AWS::S3::Bucket
61 | Properties:
62 | BucketName: !Sub
63 | - www.${Domain}
64 | - Domain: !Ref chaosDomain
65 | AccessControl: BucketOwnerFullControl
66 | WebsiteConfiguration:
67 | RedirectAllRequestsTo:
68 | HostName: !Ref RootBucket
69 | myDNS:
70 | Type: AWS::Route53::RecordSetGroup
71 | Properties:
72 | HostedZoneName: !Sub
73 | - ${Domain}.
74 | - Domain: !Ref chaosDomain
75 | Comment: Zone apex alias.
76 | RecordSets:
77 | -
78 | Name: !Sub
79 | - www.${Domain}
80 | - Domain: !Ref chaosDomain
81 | Type: A
82 | AliasTarget:
83 | HostedZoneId: !FindInMap [ RegionMap, !Ref 'AWS::Region', S3hostedzoneID]
84 | DNSName: !FindInMap [ RegionMap, !Ref 'AWS::Region', websiteendpoint]
85 | -
86 | Name: !Ref chaosDomain
87 | Type: A
88 | AliasTarget:
89 | HostedZoneId: !FindInMap [ RegionMap, !Ref 'AWS::Region', S3hostedzoneID]
90 | DNSName: !FindInMap [ RegionMap, !Ref 'AWS::Region', websiteendpoint]
91 |
92 | Outputs:
93 | WebsiteURL:
94 | Value: !GetAtt RootBucket.WebsiteURL
95 | Description: URL for website hosted on S3
--------------------------------------------------------------------------------
/client/scss/_thisisus.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | }
5 |
6 | @mixin for-phone-only {
7 | @media (max-width: 599px) {
8 | @content;
9 | }
10 | }
11 |
12 | @mixin for-tablet-portrait-up {
13 | @media (min-width: 600px) and (max-width: 924px) {
14 | @content;
15 | }
16 | }
17 | @mixin for-tablet-landscape-up {
18 | @media (min-width: 925px) {
19 | @content;
20 | }
21 | }
22 | @mixin for-desktop-up {
23 | @media (min-width: 1200px) {
24 | @content;
25 | }
26 | }
27 | @mixin for-big-desktop-up {
28 | @media (min-width: 1800px) {
29 | @content;
30 | }
31 | }
32 |
33 | #thisisus {
34 | background: linear-gradient(to top left, #e31e98 -34%, #827c82 47%);
35 | display: flex;
36 | flex-direction: column;
37 | min-height: 50vh;
38 | align-items: center;
39 | justify-content: space-evenly;
40 | font-family: "Manjari";
41 |
42 | #meettheteam {
43 | color: #f8f8ff;
44 | display: flex;
45 | margin-bottom: 2rem;
46 | }
47 |
48 | #bios {
49 | display: flex;
50 | width: 75%;
51 | align-items: center;
52 | justify-content: space-between;
53 | @include for-phone-only {
54 | width: 100%;
55 | flex-direction: column;
56 | flex-wrap: wrap;
57 | flex: 0 0 50%;
58 | // border: 1px solid green;
59 | }
60 | @include for-tablet-portrait-up {
61 | width: 100%;
62 | flex-direction: column;
63 | flex-wrap: wrap;
64 | flex: 0 0 50%;
65 | }
66 | }
67 |
68 | .whoami {
69 | display: flex;
70 | flex-direction: column;
71 | align-items: center;
72 | width: -webkit-fill-available;
73 | @include for-phone-only {
74 | width: 25%;
75 | display: flex;
76 | justify-content: center;
77 | align-items: center;
78 | flex-basis: 25%;
79 | margin-bottom: 2rem;
80 | // border: 1px solid salmon;
81 | }
82 | @include for-tablet-portrait-up {
83 | width: 25%;
84 | display: flex;
85 | justify-content: center;
86 | align-items: center;
87 | flex-basis: 25%;
88 | margin-bottom: 2rem;
89 | // justify-content: space-evenly;
90 | }
91 | }
92 |
93 | h6 {
94 | color: #f8f8ff;
95 | margin: auto 0;
96 | font-size: 1rem;
97 | // border: 1px solid purple;
98 | text-align: center;
99 | }
100 |
101 | hr {
102 | display: block;
103 | color: #f8f8ff;
104 | width: 8rem;
105 | }
106 | .outsidelinks {
107 | display: flex;
108 | width: 5rem;
109 | justify-content: space-between;
110 | }
111 | }
112 |
113 | #opensource {
114 | display: flex;
115 | flex-direction: column;
116 | flex-wrap: wrap;
117 | justify-content: center;
118 | align-items: center;
119 | margin-top: 3rem;
120 |
121 | p {
122 | font-size: 2rem;
123 | font-weight: 900;
124 | color: #f8f8ff;
125 | margin-bottom: 0.5rem;
126 | }
127 |
128 | h6 {
129 | color: #f8f8ff;
130 | }
131 | }
132 |
133 | #finalgitlink {
134 | display: inline-block;
135 | font-weight: 400;
136 | color: #f8f8ff;
137 | text-align: center;
138 | vertical-align: middle;
139 | background-color: transparent;
140 | border: 1px solid #f8f8ff;
141 | font-size: 1rem;
142 | line-height: 1.5;
143 | width: 7em;
144 | border-radius: 0.25rem;
145 | margin-top: 2rem;
146 | margin-bottom: 2rem;
147 |
148 | @include for-phone-only {
149 | display: inline-block;
150 | font-weight: 400;
151 | color: #f8f8ff;
152 | text-align: center;
153 | vertical-align: middle;
154 | background-color: transparent;
155 | border: 1px solid #f8f8ff;
156 | padding: 0.375rem 0.75rem;
157 | font-size: 1rem;
158 | line-height: 1.5;
159 | border-radius: 0.25rem;
160 | margin-top: 2rem;
161 | margin-bottom: 2rem;
162 | }
163 | }
164 |
165 | #finalgitlink:hover {
166 | color: black;
167 | background-color: white;
168 | }
169 |
--------------------------------------------------------------------------------
/client/scss/_instructions.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | }
6 |
7 | @mixin for-phone-to-tablet {
8 | @media (max-width: 924px) {
9 | @content;
10 | }
11 | }
12 |
13 | @mixin for-tablet-landscape-up {
14 | @media (min-width: 925px) {
15 | @content;
16 | }
17 | }
18 | @mixin for-desktop-up {
19 | @media (min-width: 1200px) {
20 | @content;
21 | }
22 | }
23 | @mixin for-big-desktop-up {
24 | @media (min-width: 1800px) {
25 | @content;
26 | }
27 | }
28 |
29 | #instructions {
30 | margin-top: 0;
31 | margin-left: 3rem;
32 | display: flex;
33 | flex-direction: column;
34 | justify-content: center;
35 | align-items: center;
36 | font-family: "Manjari";
37 |
38 | #steponeparent {
39 | display: flex;
40 | min-width: 100%;
41 | justify-content: space-between;
42 | margin-top: 4rem;
43 | margin-bottom: 5rem;
44 |
45 | @include for-phone-to-tablet {
46 | display: block;
47 | float: none;
48 | width: 100%;
49 |
50 | img {
51 | width: 100%;
52 | }
53 | }
54 | }
55 |
56 | #stepone {
57 | align-self: flex-start;
58 | }
59 |
60 | #planningphase {
61 | margin-right: 4rem;
62 | }
63 |
64 | #steptwoparent {
65 | display: flex;
66 | flex-direction: row-reverse;
67 | min-width: 100%;
68 | justify-content: space-between;
69 | margin-bottom: 5rem;
70 | @include for-phone-to-tablet {
71 | display: block;
72 | float: none;
73 | width: 100%;
74 | img {
75 | width: 90%;
76 | }
77 | }
78 | }
79 |
80 | #steptwo {
81 | align-self: flex-start;
82 | margin-right: 8rem;
83 | }
84 |
85 | #stepthreeparent {
86 | display: flex;
87 | min-width: 100%;
88 | justify-content: space-around;
89 | // margin-left: 7rem;
90 | margin-bottom: 5rem;
91 | // margin-right: 10rem;
92 | @include for-phone-to-tablet {
93 | display: block;
94 | float: none;
95 | width: 100%;
96 | img {
97 | width: 90%;
98 | height: auto;
99 | }
100 | }
101 | }
102 |
103 | #stepthree {
104 | align-self: flex-start;
105 | padding-top: 5rem;
106 | }
107 |
108 | #download {
109 | display: inline-block;
110 | font-weight: 400;
111 | color: black;
112 | text-align: center;
113 | vertical-align: middle;
114 | background-color: transparent;
115 | border: 1px solid black;
116 | padding: 0.5rem 1rem;
117 | font-size: 1rem;
118 | line-height: 1.5;
119 | width: auto;
120 | border-radius: 0.25rem;
121 | margin-top: 3rem;
122 | margin-bottom: 1rem;
123 |
124 | @include for-phone-to-tablet {
125 | display: inline-block;
126 | font-weight: 400;
127 | color: black;
128 | text-align: center;
129 | vertical-align: middle;
130 | background-color: transparent;
131 | border: 1px solid black;
132 | padding: 0.375rem 0.75rem;
133 | font-size: 1rem;
134 | line-height: 1.5;
135 | border-radius: 0.25rem;
136 | }
137 | }
138 |
139 | #download:hover {
140 | color: #e31e98;
141 | background-color: black;
142 | }
143 |
144 | #uploadlink {
145 | display: inline-block;
146 | font-weight: 400;
147 | color: fuchsia;
148 | text-align: center;
149 | vertical-align: middle;
150 | background-color: transparent;
151 | border: 1px solid black;
152 | padding: 0.5rem 1rem;
153 | font-size: 1rem;
154 | line-height: 1.5;
155 | width: auto;
156 | border-radius: 0.25rem;
157 | margin-top: 1rem;
158 | margin-bottom: 3rem;
159 |
160 | @include for-phone-to-tablet {
161 | display: inline-block;
162 | font-weight: 400;
163 | color: black;
164 | text-align: center;
165 | vertical-align: middle;
166 | background-color: transparent;
167 | border: 1px solid black;
168 | padding: 0.375rem 0.75rem;
169 | font-size: 1rem;
170 | line-height: 1.5;
171 | border-radius: 0.25rem;
172 | }
173 |
174 | a {
175 | text-decoration: none;
176 | color: black;
177 | }
178 | }
179 |
180 | #uploadlink:hover {
181 | background-color: grey;
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/aws/chaos-website-deployer.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const AWS = require('aws-sdk');
3 | require('dotenv').config();
4 |
5 | module.exports = {
6 | init: () => new Promise((resolve, reject) => {
7 | AWS.config.getCredentials((err) => {
8 | if (err) {
9 | reject(err.stack);
10 | } else {
11 | console.log('Access key:', AWS.config.credentials.accessKeyId);
12 | console.log('Secret access key:', AWS.config.credentials.secretAccessKey);
13 | console.log('Region', process.env.AWS_REGION);
14 | resolve();
15 | }
16 | });
17 | }),
18 | stackExists: (name) => new Promise((resolve, reject) => {
19 | const cloudformation = new AWS.CloudFormation({ apiVersion: '2010-05-15' });
20 | const currentStacksQueryParams = { StackStatusFilter: ['CREATE_COMPLETE', 'CREATE_IN_PROGRESS', 'DELETE_FAILED', 'ROLLBACK_COMPLETE'] };
21 | cloudformation.listStacks(currentStacksQueryParams, (err, data) => {
22 | if (err) {
23 | reject(err.stack); // an error occurred
24 | } else {
25 | for (let i = 0; i < data.StackSummaries.length; i += 1) {
26 | if (data.StackSummaries[i].StackName === name) {
27 | resolve(true);
28 | }
29 | }
30 | resolve(false);
31 | }
32 | });
33 | }),
34 | createStack: (name, templatePath, templateParams = []) => new Promise((resolve, reject) => {
35 | const template = fs.readFileSync(templatePath, 'utf8');
36 | const cloudformation = new AWS.CloudFormation({ apiVersion: '2010-05-15' });
37 |
38 | const createParams = {
39 | StackName: name,
40 | TemplateBody: template,
41 | Capabilities: ['CAPABILITY_IAM'],
42 | Parameters: templateParams,
43 | };
44 |
45 | cloudformation.createStack(createParams, (err) => {
46 | if (err) {
47 | reject(new Error(`FAILED: to create stack "${name}"\n${err.stack}`));
48 | } else {
49 | console.log(`--waiting-- while creation of stack ${name} completes`);
50 | cloudformation.waitFor('stackCreateComplete', { StackName: createParams.StackName }, (err, data) => {
51 | if (err) {
52 | reject(new Error(`--FAILED-- to create stack "${name}"\n${err.stack}`));
53 | } else {
54 | console.log(`--success-- created stack "${name}"`);
55 | resolve();
56 | }
57 | });
58 | }
59 | });
60 | }),
61 | createObject: (bucketName, objectName, objectData, options = {}) => new Promise((resolve, reject) => {
62 | const s3 = new AWS.S3();
63 | const createObjParams = {
64 | Body: objectData,
65 | Bucket: bucketName,
66 | Key: objectName,
67 | ...options
68 | };
69 | s3.putObject(createObjParams, (err, data) => {
70 | if (err) {
71 | reject(new Error(`--FAILED-- to create object "${objectName}"\n${err.stack}`));
72 | } else {
73 | console.log(`--success-- created object "${objectName}"`);
74 | resolve();
75 | }
76 | });
77 | }),
78 | deleteStack: (name) => new Promise((resolve, reject) => {
79 | const cloudformation = new AWS.CloudFormation({ apiVersion: '2010-05-15' });
80 | const deleteParams = {
81 | StackName: name,
82 | };
83 | cloudformation.deleteStack(deleteParams, (err) => {
84 | if (err) {
85 | reject(new Error(`--FAILED-- to delete stack "${name}"\n${err.stack}`));
86 | } else {
87 | console.log(`--waiting-- while deletion of stack ${name} completes`);
88 | cloudformation.waitFor('stackDeleteComplete', deleteParams, (err, data) => {
89 | if (err) {
90 | reject(new Error(`--FAILED-- to delete stack "${name}"\n${err.stack}`));
91 | } else {
92 | console.log(`--success-- deleted stack "${name}"`);
93 | resolve();
94 | }
95 | });
96 | }
97 | });
98 | }),
99 | deleteS3Objects: (bucketName) => new Promise((resolve, reject) => {
100 | const s3 = new AWS.S3();
101 |
102 | s3.listBuckets((err, data) => {
103 | if (err) {
104 | reject(new Error(`--FAILED-- to check if bucket "${bucketName}" exists\n${err.stack}`));
105 | } else {
106 | const namesOfBucketsThatExist = data.Buckets.map((element) => element.Name === bucketName);
107 | if (!namesOfBucketsThatExist.includes(bucketName)) resolve();
108 | const listObjectsParams = {
109 | Bucket: bucketName,
110 | };
111 |
112 | s3.listObjectsV2(listObjectsParams, (err, data) => {
113 | if (err) {
114 | reject(new Error(`--FAILED-- to get object keys for bucket "${bucketName}"\n${err.stack}`));
115 | } else {
116 | const s3BucketKeys = data.Contents.map((element) => ({ Key: element.Key }));
117 | const deleteObjectsParams = {
118 | Bucket: bucketName,
119 | Delete: {
120 | Objects: s3BucketKeys,
121 | Quiet: true,
122 | },
123 | };
124 |
125 | s3.deleteObjects(deleteObjectsParams, (err) => {
126 | if (err) {
127 | reject(new Error(`--FAILED-- to delete objects for bucket "${bucketName}"\n${err.stack}`));
128 | } else {
129 | console.log(`--success-- deleted objects for bucket "${bucketName}"`);
130 | resolve();
131 | }
132 | });
133 | }
134 | });
135 | }
136 | });
137 | }),
138 | };
139 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # **Chaos QoaLa**
2 | ChaosQoaLa is a chaos engineering tool for injecting failure into JavaScript backed GraphQL end points. ChaosQoaLa can be used to add latency, and/or knock out specific data sections of a GraphQL server's responses. The extent of the chaos effect can be controlled via a "blast radius" configuration parameter so only a specified portion of responses are affected. There are three fundamental components. The first component is a piece of named middleware you will integrate into your GraphQL server implementation. This "**Agent**" sits in the server and will cause chaos on demand when it is sent instructions to do so. The "**Controller**" is a CLI tool which is installed on a test user's personal machine/VM and can be used to configure, run, and stop a chaos experiment. The "**Site**" (www.chaosqoala.io) can be used to upload results files
3 | generated by the Controller for visualization.
4 |
5 | ## **Supported GraphQL Servers**
6 |
7 | - Express GraphQL
8 | - Apollo Server Express
9 |
10 | ## **Installation**
11 |
12 | **Agent**
13 |
14 |
15 | # npm install chaosqoaloa-agent
16 | Once installed in your server's codebase the agent can be integrated in 3 lines of code (between the koalas).
17 |
18 | > Express Graph QL example:
19 |
20 | 
21 |
22 | > Apollo Server Express example:
23 |
24 | 
25 |
26 |
27 |
28 | **Controller**
29 |
30 |
31 | Install on each test users' machine.
32 |
33 | # npm install chaosqoaloa
34 |
35 |
36 | ## **Steady State Integration**
37 | A key metric in chaos engineering experiments is 'steady state'. Steady state is a measure of how well a system is performing. Different systems will be measured by different metrics, for example Netflix uses 'number of plays' to evaluate how well their systems or service is performing. ChaosQoaLa requires access to an endpoint that can be called when the chaos experiment starts, and again when the experiment ends. The end point must be stateful and respond to the stop invocation with steady state data points for the duration of the test run. The results are expected to be in a JSON array in the format:
38 |
39 | [ { "timeOfResult": "2019-09-10T04:34:22.290Z", "result": 97 },
40 | { "timeOfResult": "2019-09-10T04:34:22.390Z", "result": 79 },
41 | {.... ]
42 |
43 | ## **Using ChaosQoaLa**
44 |
45 | **Configuring a test run**
46 |
47 |
48 | To configure a test run enter the following command on a user's machine with the Controller installed
49 |
50 | # chaosqoala configure
51 | The CLI tool will display a series of questions:
52 |
53 | Please enter the URI of the Socket.io port over which Chaos will be sent and received
54 | By default the Agent listens on port 1025 of the GraphQL server in which it has been installed. Enter the fully qualified path of port 1025 on the GraphQL server, for example https://gql.example.com:1025
55 |
56 | Please enter the URI of GraphQL service
57 | Enter the fully qualified path of the GraphQL server, for example https://gql.example.com
58 |
59 | Please enter your desired blast radius
60 | Enter a number between 0.0 and 1.0 to represent the % of responses that will be affected by chaos, for example a figure of 0.33 will result in 33% of responses from the server being subjected to failure injection.
61 |
62 | Please enter the amount of time you would like your data to be delayed (in milliseconds)
63 | Enter the number of milliseconds of latency to introduce to responses chosen for failure injection, use a value of zero in order to switch off latency injection.
64 |
65 | Please enter how long you would like the ChaosQoala to run (in minutes).
66 | Enter the duration of the test run in minutes. Test runs can be terminated at any point by pressing any key in the Controller CLI during test execution.
67 |
68 | Please enter the steady state start URL
69 |
70 | Enter the fully qualified URI for the steady state service you have implemented, this URI will be invoked when the chaos experiment is started. For example: https://api.example.com/steadystate/start
71 |
72 | Please enter the steady state start HTTP verb
73 | Enter the http verb used to invoke the steady state start route, GET/POST/PUT etc.
74 |
75 | Please enter the steady state stop URL
76 | Enter the fully qualified URI for the steady state service you have implemented, this URI will invoked when the chaos experiment is stopped. For example: https://api.example.com/steadystate/stop
77 |
78 | Please enter the steady state stop HTTP verb
79 | Enter the http verb used to invoke the steady state stop route, eg GET/POST/PUT etc.
80 |
81 | **Configuring query knockout**
82 |
83 |
84 | Once configure has been ran the Controller will write the configuration to its package.json. The Controller also inspects the GraphQL end point entered and extracts a list of available queries. To knock out data for a GraphQL query during an experiment just toggle the booleans in the affectedQueries object of the package.json:
85 |
86 | "chaosConfig": {
87 | . . .
88 | "affectedQueries": {
89 | "dontKnockMeOut": false,
90 | "knockMeOut": true
91 | },
92 | },
93 | **Running an experiment**
94 |
95 |
96 | Once you are happy with the configuration the experiment can be started with the start command
97 |
98 | # chaosqoala start
99 | When the experiment ends a results file will be generated on the machine the Controller is running on - this file (*timestamp*_results.json) can be visualized on the ChaosQoaLa site via the upload link.
100 | ## **License**
101 | This project is licensed under the MIT License.
102 |
103 | ## **Authors**
104 | - Jacob Ory: github.com/jakeory
105 | - Simon Maharai: github.com/Simon-IM
106 | - Nicolas Venegas Parker: github.com/nicvhub
107 | - Samantha Wessel: github.com/sw8wm2013
108 | ## **Acknowledgements**
109 | Huge thanks to Natalie Klein [linkedin.com/in/nataliesklein](https://www.linkedin.com/in/nataliesklein) for all the input, advice, and mentorship
110 |
--------------------------------------------------------------------------------
/aws/cloud-formation-lambda-api.yml:
--------------------------------------------------------------------------------
1 | AWSTemplateFormatVersion: "2010-09-09"
2 | Description: "Lambda function to support ChaosQoala Website Data analysis and translation to Chart js data structure"
3 |
4 | Parameters:
5 | chaosLambdaS3Bucket:
6 | Description: The name of the bucket to store the lambda function code during deployment
7 | Type: String
8 | regionalCertificateArn:
9 | Description: The certificate generated from ACM for you custom hosting domain (example.com)
10 | Type: String
11 | customHostedZoneId:
12 | Description: The hosted zone id for you custom hosting domain (example.com)
13 | Type: String
14 | regionHostedZoneId:
15 | Description: The AWS region hosted zone id
16 | Type: String
17 | chaosDomainApi:
18 | Description: Name of the lambda end point i.e api.example.com
19 | Type: String
20 |
21 | Resources:
22 | customDomainForAPI:
23 | Type: AWS::ApiGateway::DomainName
24 | Properties:
25 | DomainName: !Ref chaosDomainApi
26 | EndpointConfiguration:
27 | Types:
28 | - REGIONAL
29 | RegionalCertificateArn:
30 | Ref: regionalCertificateArn
31 |
32 | apiGateway:
33 | Type: "AWS::ApiGateway::RestApi"
34 | Properties:
35 | Name: "chaos-qoala-api"
36 | Description: "Chaos Qoala API"
37 | EndpointConfiguration:
38 | Types:
39 | - REGIONAL
40 |
41 | apiGatewayRootMethod:
42 | Type: "AWS::ApiGateway::Method"
43 | Properties:
44 | AuthorizationType: "NONE"
45 | HttpMethod: "POST"
46 | Integration:
47 | IntegrationHttpMethod: "POST"
48 | Type: "AWS_PROXY"
49 | Uri: !Sub
50 | - "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${lambdaArn}/invocations"
51 | - lambdaArn: !GetAtt "lambdaFunction.Arn"
52 | ResourceId: !GetAtt "apiGateway.RootResourceId"
53 | RestApiId: !Ref "apiGateway"
54 |
55 | OptionsMethod:
56 | Type: AWS::ApiGateway::Method
57 | Properties:
58 | AuthorizationType: NONE
59 | RestApiId: !Ref "apiGateway"
60 | ResourceId: !GetAtt "apiGateway.RootResourceId"
61 | HttpMethod: OPTIONS
62 | Integration:
63 | IntegrationResponses:
64 | - StatusCode: 200
65 | ResponseParameters:
66 | method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
67 | method.response.header.Access-Control-Allow-Methods: "'POST,OPTIONS'"
68 | method.response.header.Access-Control-Allow-Origin: "'*'"
69 | ResponseTemplates:
70 | application/json: ''
71 | PassthroughBehavior: WHEN_NO_MATCH
72 | RequestTemplates:
73 | application/json: '{"statusCode": 200}'
74 | Type: MOCK
75 | MethodResponses:
76 | - StatusCode: 200
77 | ResponseModels:
78 | application/json: 'Empty'
79 | ResponseParameters:
80 | method.response.header.Access-Control-Allow-Headers: true
81 | method.response.header.Access-Control-Allow-Methods: true
82 | method.response.header.Access-Control-Allow-Origin: true
83 |
84 | apiGatewayDeployment:
85 | Type: "AWS::ApiGateway::Deployment"
86 | DependsOn:
87 | - "apiGatewayRootMethod"
88 | Properties:
89 | RestApiId: !Ref "apiGateway"
90 | StageName: logs
91 |
92 | mappingCustomDomainToChaosAPI:
93 | Type: AWS::ApiGateway::BasePathMapping
94 | DependsOn:
95 | - "customDomainForAPI"
96 | - "apiGateway"
97 | Properties:
98 | BasePath: "transform"
99 | DomainName: !Ref chaosDomainApi
100 | RestApiId: !Ref "apiGateway"
101 |
102 | route53RecordSet:
103 | Type: AWS::Route53::RecordSet
104 | DependsOn:
105 | - "mappingCustomDomainToChaosAPI"
106 | Properties:
107 | AliasTarget:
108 | DNSName: !GetAtt "customDomainForAPI.RegionalDomainName"
109 | EvaluateTargetHealth: false
110 | HostedZoneId:
111 | Ref: regionHostedZoneId
112 | Name: !Ref chaosDomainApi
113 | HostedZoneId:
114 | Ref: customHostedZoneId
115 | Type: "A"
116 |
117 | lambdaFunction:
118 | Type: "AWS::Lambda::Function"
119 | Properties:
120 | Description: "function that turns a json chaos results log into data consumable by chart js"
121 | Code:
122 | S3Bucket: !Ref "chaosLambdaS3Bucket"
123 | S3Key: "lambda.zip"
124 | FunctionName: "chaos-qoala-data-analysis-and-transform"
125 | Handler: "lambda.handler"
126 | MemorySize: 128
127 | Role: !GetAtt "lambdaIAMRole.Arn"
128 | Runtime: "nodejs8.10"
129 | Timeout: 300
130 |
131 | lambdaApiGatewayInvoke:
132 | Type: "AWS::Lambda::Permission"
133 | Properties:
134 | Action: "lambda:InvokeFunction"
135 | FunctionName: !GetAtt "lambdaFunction.Arn"
136 | Principal: "apigateway.amazonaws.com"
137 | SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${apiGateway}/*/POST/"
138 |
139 | lambdaIAMRole:
140 | Type: "AWS::IAM::Role"
141 | Properties:
142 | AssumeRolePolicyDocument:
143 | Version: "2012-10-17"
144 | Statement:
145 | - Action:
146 | - "sts:AssumeRole"
147 | Effect: "Allow"
148 | Principal:
149 | Service:
150 | - "lambda.amazonaws.com"
151 | Policies:
152 | - PolicyDocument:
153 | Version: "2012-10-17"
154 | Statement:
155 | - Action:
156 | - "logs:CreateLogGroup"
157 | - "logs:CreateLogStream"
158 | - "logs:PutLogEvents"
159 | Effect: "Allow"
160 | Resource:
161 | - !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/chaos-qoala-data-analysis-and-transform:*"
162 | PolicyName: "lambda"
163 |
164 | lambdaLogGroup:
165 | Type: "AWS::Logs::LogGroup"
166 | Properties:
167 | LogGroupName: !Sub "/aws/lambda/chaos-qoala-data-analysis-and-transform"
168 | RetentionInDays: 90
169 |
170 | Outputs:
171 | apiGatewayInvokeURL:
172 | Value: !Sub "https://${apiGateway}.execute-api.${AWS::Region}.amazonaws.com/logs"
173 |
174 | lambdaArn:
175 | Value: !GetAtt "lambdaFunction.Arn"
--------------------------------------------------------------------------------
/client/scss/_main.scss:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | @mixin for-phone-only {
8 | @media (max-width: 599px) {
9 | @content;
10 | }
11 | }
12 |
13 | @mixin for-tablet-portrait-up {
14 | @media (min-width: 600px) and (max-width: 924px) {
15 | @content;
16 | }
17 | }
18 | @mixin for-tablet-landscape-up {
19 | @media (min-width: 925px) {
20 | @content;
21 | }
22 | }
23 | @mixin for-desktop-up {
24 | @media (min-width: 1200px) {
25 | @content;
26 | }
27 | }
28 | @mixin for-big-desktop-up {
29 | @media (min-width: 1800px) {
30 | @content;
31 | }
32 | }
33 |
34 | #main {
35 | //background: linear-gradient(to bottom right, #e31e98 -15%, #827c82 68%);
36 | min-width: 100vw;
37 | left: 0;
38 | background: linear-gradient(to top left, #e31e98 -34%, #827c82 47%);
39 | display: flex;
40 | flex-flow: column nowrap;
41 | justify-content: center;
42 | align-content: center;
43 | min-height: 75vh;
44 |
45 | @include for-phone-only {
46 | display: flex;
47 | }
48 |
49 | @include for-tablet-portrait-up {
50 | display: flex;
51 | }
52 |
53 | @include for-phone-only {
54 | display: flex;
55 | }
56 |
57 | #mainpglogo {
58 | width: 9rem;
59 | height: auto;
60 | align-self: center;
61 | position: relative;
62 | }
63 |
64 | #mainqoala {
65 | width: 5rem;
66 | height: auto;
67 | align-self: center;
68 | padding-right: 1.5rem;
69 | vertical-align: sub;
70 | @include for-phone-only {
71 | margin-top: 2rem;
72 | margin: auto 0;
73 | width: 3.5rem;
74 | height: auto;
75 | }
76 | @include for-tablet-portrait-up {
77 | margin-top: 2rem;
78 | margin: auto 0;
79 | width: 3.5rem;
80 | height: auto;
81 | }
82 | }
83 |
84 | #missionstatement {
85 | align-self: center;
86 | font-weight: 800;
87 | font-family: "Manjari";
88 | font-size: 5rem;
89 | color: rgba(243, 239, 239, 0.89);
90 | font-size: 1.2em;
91 | @include for-phone-only {
92 | font-size: 0.7em;
93 | margin-top: 0.1rem;
94 | }
95 | @include for-tablet-portrait-up {
96 | font-size: 0.7em;
97 | margin-top: 0.1rem;
98 | }
99 | }
100 |
101 | #title {
102 | align-self: center;
103 | font-weight: 800;
104 | font-family: "Manjari";
105 | font-size: 5rem;
106 | color: rgba(243, 239, 239, 0.89);
107 | margin-top: 1rem;
108 | margin-bottom: 0.1rem;
109 | height: 6rem;
110 | @include for-phone-only {
111 | font-size: 1.8em;
112 | margin-top: 5rem;
113 | margin-bottom: 0.2rem;
114 | }
115 | @include for-tablet-portrait-up {
116 | font-size: 1.8em;
117 | margin-top: 5rem;
118 | margin-bottom: 0.2rem;
119 | }
120 | }
121 |
122 | p {
123 | align-self: center;
124 | font-weight: 800;
125 | font-family: "Manjari";
126 | font-size: 5rem;
127 | color: rgba(243, 239, 239, 0.89);
128 | @include for-phone-only {
129 | font-size: 1.99em;
130 | margin-top: 5rem;
131 | }
132 | @include for-tablet-portrait-up {
133 | font-size: 1.99em;
134 | margin-top: 5rem;
135 | }
136 | }
137 |
138 | span {
139 | font-size: 6rem;
140 | font-weight: 900;
141 | align-self: center;
142 | @include for-phone-only {
143 | font-size: 1.5em;
144 | margin: auto 0;
145 | }
146 |
147 | @include for-tablet-portrait-up {
148 | font-size: 1.5em;
149 | margin: auto 0;
150 | }
151 | }
152 | #starp {
153 | font-size: 1rem;
154 | display: inline-block;
155 | font-weight: 400;
156 | align-self: center;
157 | margin-top: 5rem;
158 | @include for-phone-only {
159 | font-size: 1em;
160 | margin-top: 5rem;
161 | }
162 |
163 | @include for-tablet-portrait-up {
164 | font-size: 1em;
165 | margin-top: 5rem;
166 | }
167 | }
168 |
169 | #starp {
170 | font-size: 1.5rem;
171 | display: inline-block;
172 | font-weight: 400;
173 | align-self: center;
174 | @include for-phone-only {
175 | align-content: center;
176 | font-size: 1.3em;
177 | margin-top: 5rem;
178 | }
179 |
180 | @include for-tablet-portrait-up {
181 | align-content: center;
182 | font-size: 1.3em;
183 | margin-top: 5rem;
184 | }
185 |
186 | a {
187 | align-self: center;
188 | text-decoration: none;
189 | }
190 |
191 | #gitbutton {
192 | padding: 0.2rem 0.4rem;
193 | line-height: 0.1rem;
194 | border-radius: 5px;
195 | vertical-align: top;
196 | @include for-phone-only {
197 | font-weight: 400;
198 | text-align: center;
199 | vertical-align: middle;
200 | user-select: none;
201 | padding: 0.1rem 0.1rem;
202 | font-size: 0.3rem;
203 | line-height: 1.3;
204 | border-radius: 0.25rem;
205 | }
206 |
207 | @include for-tablet-portrait-up {
208 | font-weight: 400;
209 | text-align: center;
210 | vertical-align: middle;
211 | user-select: none;
212 | padding: 0.1rem 0.1rem;
213 | font-size: 0.3rem;
214 | line-height: 1.3;
215 | border-radius: 0.25rem;
216 | }
217 | }
218 | }
219 |
220 | #downloadbutton {
221 | display: flex;
222 | justify-content: center;
223 | text-decoration: none;
224 | }
225 |
226 | .star {
227 | width: 1em;
228 | height: auto;
229 | margin-right: 0.3em;
230 | align-self: center;
231 | }
232 |
233 | #npmbutton {
234 | display: inline-block;
235 | font-weight: 400;
236 | color: #f8f8ff;
237 | text-align: center;
238 | vertical-align: middle;
239 | background-color: transparent;
240 | border: 1px solid #f8f8ff;
241 | padding: 0.5rem 1rem;
242 | font-size: 1rem;
243 | line-height: 1.5;
244 | width: auto;
245 | border-radius: 0.25rem;
246 |
247 | @include for-phone-only {
248 | display: inline-block;
249 | font-weight: 400;
250 | color: #f8f8ff;
251 | text-align: center;
252 | vertical-align: middle;
253 | background-color: transparent;
254 | border: 1px solid #f8f8ff;
255 | padding: 0.375rem 0.75rem;
256 | font-size: 1rem;
257 | line-height: 1.5;
258 | border-radius: 0.25rem;
259 | }
260 | @include for-tablet-portrait-up {
261 | display: inline-block;
262 | font-weight: 400;
263 | color: #f8f8ff;
264 | text-align: center;
265 | vertical-align: middle;
266 | background-color: transparent;
267 | border: 1px solid #f8f8ff;
268 | padding: 0.375rem 0.75rem;
269 | font-size: 1rem;
270 | line-height: 1.5;
271 | border-radius: 0.25rem;
272 | }
273 | }
274 |
275 | #npmbutton:hover {
276 | color: black;
277 | background-color: white;
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/chaos-qoala-agent/index.js:
--------------------------------------------------------------------------------
1 | // some of the code below was taken from this handy npm module
2 | // https://github.com/zuojiang/modify-response-middleware/tree/master/src
3 |
4 |
5 | // GLOBAL SETUP OF WEB SOCKET CHANNEL, AND MODULE SCOPED VARIABLES
6 | // ---------------------------------------------------------------
7 |
8 | const zlib = require('zlib');
9 | const app = require('express')();
10 | const chaosSocketServer = require('http').Server(app);
11 | const io = require('socket.io')(chaosSocketServer);
12 |
13 | // intialize module flags - these values will be dynamically
14 | // changed via configuration info sent over socket.io
15 |
16 | // master on/off switch
17 | let ENSUE_CHAOS = false;
18 | // length of any latency injection
19 | let DELAY_RESPONSE_MS = 0;
20 | // map of queries and if they will be targets for data knockout
21 | let QUERY_TARGETS = {};
22 | // the blast radius of the current experiemnt
23 | let BLAST_RADIUS = 0;
24 | // this is calculated per message - but scoped at module level for ease of implementation
25 | let CHAOS_CHANCE = 0;
26 | // web socket address of config and data streaming
27 | // use a port number above 1024 for linux compatibility
28 | const CHAOS_PORT = 1025;
29 |
30 | // start the socket channel on port 1025
31 | // configuration information will be sent to this channel and
32 | // live streaming results data will be sent back to the controller
33 | // process during chaos experiments.
34 | chaosSocketServer.listen(CHAOS_PORT);
35 |
36 | io.on('connection', (socket) => {
37 | // all chaos data is sent and received on the topic 'eucalyptus'
38 | // if data arrives on this port then it is a config message from the controller
39 | socket.on('eucalyptus', (config, acknowledge) => {
40 | // extract and assign configuration vals
41 | const {
42 | ensueChaos,
43 | delay,
44 | blastRadius,
45 | affectedQueries,
46 | } = config;
47 |
48 | ENSUE_CHAOS = ensueChaos;
49 | BLAST_RADIUS = blastRadius;
50 | DELAY_RESPONSE_MS = delay;
51 | QUERY_TARGETS = affectedQueries;
52 |
53 | // invoke callback sent by the controller to acknowledge receipt of config
54 | acknowledge();
55 | });
56 | });
57 |
58 | // HELPER FUNCTIONS
59 | // ----------------
60 |
61 | // encoding and decoding functions for servers implementing compression
62 | // of response data, in such cases we need to de-compress before altering
63 | // any content during chaos experiments then re-compress
64 |
65 | function encoding(res, content) {
66 | switch (res.getHeader('content-encoding')) {
67 | case 'gzip':
68 | return zlib.gzipSync(content);
69 | case 'deflate':
70 | return zlib.deflateSync(content);
71 | default: return content;
72 | }
73 | }
74 | function decoding(res, content) {
75 | switch (res.getHeader('content-encoding')) {
76 | case 'gzip':
77 | return zlib.gunzipSync(content);
78 | case 'deflate':
79 | return zlib.inflateSync(content);
80 | default: return content;
81 | }
82 | }
83 |
84 | // FUNCTION THAT CAN BE USED TO CREATE A PIECE OF MIDDLEWARE
85 | // THAT RE-WRITES THE EXPRESS RESPONSE API FUNCTIONS SO THAT
86 | // RATHER THAN SENDING DATA BACK TO THE CLIENT THE DATA IS PASSED
87 | // TO THE 'modify' CALL BACK WHICH MAY MODIFY THE RESPONSE
88 | // BEFORE IT IS SENT BACK AS NORMAL TO THE CLIENT
89 | // -------------------------------------------------------------
90 |
91 | const modifyRes = function responseHijackingMiddlewareCreator(modify) {
92 | // note the signature of the returned function - just like a regular named middleware
93 | return (req, res, next) => {
94 | // preserve then original res.end function
95 | const expressAPIEnd = res.end;
96 | // create a buffer and a helper function to write to it
97 | const list = [];
98 | res.write = (chunk) => {
99 | list.push(chunk);
100 | };
101 |
102 | // IMPORTANT re-write express res.end so instead of sending data it just
103 | // exposes response data to the callback provided, then repackages the
104 | // potentially altered response and sends it back to the client
105 | // via the original express API res.end implementation.
106 | res.end = async function newExpressResEndFunction(chunk) {
107 | // data will be exposed to the callback function via this variable
108 | let content;
109 | // if data being supplied in this call to res.end then add to the buffer
110 | if (chunk) list.push(chunk);
111 | if (Buffer.isBuffer(list[0])) content = Buffer.concat(list);
112 | else content = list.join('');
113 | // decode data if required
114 | let decodedContent = decoding(res, content);
115 |
116 | // this is the IMPORTANT line where the code calls the callback responsible
117 | // for altering the content data
118 | decodedContent = await modify(decodedContent, req, res);
119 |
120 | // re encode the data if required
121 | if (Buffer.isBuffer(decodedContent) || typeof decodedContent === 'string') {
122 | content = encoding(res, decodedContent);
123 | // reset the content length to an adjusted value
124 | if (!res.headersSent) {
125 | res.setHeader('content-length', content.length);
126 | }
127 | }
128 | // IMPORTANT invoke original express res.send function
129 | expressAPIEnd.call(res, content);
130 | };
131 | if (next) next();
132 | };
133 | };
134 |
135 | // eslint-disable-next-line no-unused-vars
136 | const chaos = modifyRes(async (content, req, res) => {
137 | // create a promisi-fied timeout function to inflict latency
138 | function addLatency(ms) {
139 | return new Promise((resolve) => setTimeout(resolve, ms));
140 | }
141 | try {
142 | // attempt to parse the data in to json
143 | const data = JSON.parse(content.toString());
144 | // if currently running a chaos experiment
145 | if (ENSUE_CHAOS === true) {
146 | // to determine if this response will be affected - 'roll the dice'
147 | // by getting a manual number and comparing against the blast radius
148 | // the bigger the blast radius value is, the greater the chances the number
149 | // will be less than it and a chaos effect will be seen
150 | CHAOS_CHANCE = Math.random();
151 | const affectMessageWithChaos = (CHAOS_CHANCE < BLAST_RADIUS);
152 |
153 | // construct a logging data packet for this response
154 | const agentData = {
155 | timeOfResponse: new Date().toJSON(),
156 | chaosResponse: affectMessageWithChaos,
157 | };
158 |
159 | // send the logging data packet back to the controller
160 | io.emit('eucalyptus', agentData);
161 |
162 | // if we are affecting this message
163 | if (affectMessageWithChaos) {
164 | // add latency
165 | await addLatency(DELAY_RESPONSE_MS);
166 | // knock out a section of data based on configuation against queries
167 | const dataSectionsOfResults = Object.keys(data.data);
168 | dataSectionsOfResults.forEach((name) => {
169 | // if a data section is on the knock out list remove the data from the response
170 | if (QUERY_TARGETS[name]) delete data.data[name];
171 | });
172 | }
173 | }
174 | // return altered data
175 | return Buffer.from(JSON.stringify(data));
176 | } catch (err) {
177 | // if data not parsable as json it will error and be caught in this section
178 | // so just pass back unaffected content as this response is probably unrelated
179 | // to GraphQL
180 | return content;
181 | }
182 | });
183 |
184 | module.exports = { chaos, chaosSocketServer };
185 |
--------------------------------------------------------------------------------
/aws/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aws",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@babel/code-frame": {
8 | "version": "7.5.5",
9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
10 | "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
11 | "dev": true,
12 | "requires": {
13 | "@babel/highlight": "^7.0.0"
14 | }
15 | },
16 | "@babel/highlight": {
17 | "version": "7.5.0",
18 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
19 | "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
20 | "dev": true,
21 | "requires": {
22 | "chalk": "^2.0.0",
23 | "esutils": "^2.0.2",
24 | "js-tokens": "^4.0.0"
25 | }
26 | },
27 | "acorn": {
28 | "version": "7.0.0",
29 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
30 | "integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
31 | "dev": true
32 | },
33 | "acorn-jsx": {
34 | "version": "5.0.2",
35 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.2.tgz",
36 | "integrity": "sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==",
37 | "dev": true
38 | },
39 | "adm-zip": {
40 | "version": "0.4.13",
41 | "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.13.tgz",
42 | "integrity": "sha512-fERNJX8sOXfel6qCBCMPvZLzENBEhZTzKqg6vrOW5pvoEaQuJhRU4ndTAh6lHOxn1I6jnz2NHra56ZODM751uw=="
43 | },
44 | "ajv": {
45 | "version": "6.10.2",
46 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
47 | "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
48 | "dev": true,
49 | "requires": {
50 | "fast-deep-equal": "^2.0.1",
51 | "fast-json-stable-stringify": "^2.0.0",
52 | "json-schema-traverse": "^0.4.1",
53 | "uri-js": "^4.2.2"
54 | }
55 | },
56 | "ansi-escapes": {
57 | "version": "3.2.0",
58 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
59 | "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
60 | "dev": true
61 | },
62 | "ansi-regex": {
63 | "version": "3.0.0",
64 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
65 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
66 | "dev": true
67 | },
68 | "ansi-styles": {
69 | "version": "3.2.1",
70 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
71 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
72 | "dev": true,
73 | "requires": {
74 | "color-convert": "^1.9.0"
75 | }
76 | },
77 | "argparse": {
78 | "version": "1.0.10",
79 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
80 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
81 | "dev": true,
82 | "requires": {
83 | "sprintf-js": "~1.0.2"
84 | }
85 | },
86 | "array-includes": {
87 | "version": "3.0.3",
88 | "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz",
89 | "integrity": "sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0=",
90 | "dev": true,
91 | "requires": {
92 | "define-properties": "^1.1.2",
93 | "es-abstract": "^1.7.0"
94 | }
95 | },
96 | "astral-regex": {
97 | "version": "1.0.0",
98 | "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
99 | "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
100 | "dev": true
101 | },
102 | "aws-sdk": {
103 | "version": "2.524.0",
104 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.524.0.tgz",
105 | "integrity": "sha512-nCnKEExs1OqwNG6sIrRy+Lj8Zt2M6BTa6YlEMQ1gIpdwznaAN9XyTnm+MDc3iYsSl58MOBwiKpY8d6CONuxuDw==",
106 | "requires": {
107 | "buffer": "4.9.1",
108 | "events": "1.1.1",
109 | "ieee754": "1.1.8",
110 | "jmespath": "0.15.0",
111 | "querystring": "0.2.0",
112 | "sax": "1.2.1",
113 | "url": "0.10.3",
114 | "uuid": "3.3.2",
115 | "xml2js": "0.4.19"
116 | }
117 | },
118 | "balanced-match": {
119 | "version": "1.0.0",
120 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
121 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
122 | "dev": true
123 | },
124 | "base64-js": {
125 | "version": "1.3.1",
126 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
127 | "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
128 | },
129 | "brace-expansion": {
130 | "version": "1.1.11",
131 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
132 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
133 | "dev": true,
134 | "requires": {
135 | "balanced-match": "^1.0.0",
136 | "concat-map": "0.0.1"
137 | }
138 | },
139 | "buffer": {
140 | "version": "4.9.1",
141 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
142 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
143 | "requires": {
144 | "base64-js": "^1.0.2",
145 | "ieee754": "^1.1.4",
146 | "isarray": "^1.0.0"
147 | }
148 | },
149 | "callsites": {
150 | "version": "3.1.0",
151 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
152 | "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
153 | "dev": true
154 | },
155 | "chalk": {
156 | "version": "2.4.2",
157 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
158 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
159 | "dev": true,
160 | "requires": {
161 | "ansi-styles": "^3.2.1",
162 | "escape-string-regexp": "^1.0.5",
163 | "supports-color": "^5.3.0"
164 | }
165 | },
166 | "chardet": {
167 | "version": "0.7.0",
168 | "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
169 | "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
170 | "dev": true
171 | },
172 | "cli-cursor": {
173 | "version": "2.1.0",
174 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
175 | "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
176 | "dev": true,
177 | "requires": {
178 | "restore-cursor": "^2.0.0"
179 | }
180 | },
181 | "cli-width": {
182 | "version": "2.2.0",
183 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
184 | "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
185 | "dev": true
186 | },
187 | "color-convert": {
188 | "version": "1.9.3",
189 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
190 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
191 | "dev": true,
192 | "requires": {
193 | "color-name": "1.1.3"
194 | }
195 | },
196 | "color-name": {
197 | "version": "1.1.3",
198 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
199 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
200 | "dev": true
201 | },
202 | "concat-map": {
203 | "version": "0.0.1",
204 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
205 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
206 | "dev": true
207 | },
208 | "confusing-browser-globals": {
209 | "version": "1.0.8",
210 | "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.8.tgz",
211 | "integrity": "sha512-lI7asCibVJ6Qd3FGU7mu4sfG4try4LX3+GVS+Gv8UlrEf2AeW57piecapnog2UHZSbcX/P/1UDWVaTsblowlZg==",
212 | "dev": true
213 | },
214 | "contains-path": {
215 | "version": "0.1.0",
216 | "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz",
217 | "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=",
218 | "dev": true
219 | },
220 | "cross-spawn": {
221 | "version": "6.0.5",
222 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
223 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
224 | "dev": true,
225 | "requires": {
226 | "nice-try": "^1.0.4",
227 | "path-key": "^2.0.1",
228 | "semver": "^5.5.0",
229 | "shebang-command": "^1.2.0",
230 | "which": "^1.2.9"
231 | },
232 | "dependencies": {
233 | "semver": {
234 | "version": "5.7.1",
235 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
236 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
237 | "dev": true
238 | }
239 | }
240 | },
241 | "debug": {
242 | "version": "4.1.1",
243 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
244 | "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
245 | "dev": true,
246 | "requires": {
247 | "ms": "^2.1.1"
248 | }
249 | },
250 | "deep-is": {
251 | "version": "0.1.3",
252 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
253 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
254 | "dev": true
255 | },
256 | "define-properties": {
257 | "version": "1.1.3",
258 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
259 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
260 | "dev": true,
261 | "requires": {
262 | "object-keys": "^1.0.12"
263 | }
264 | },
265 | "doctrine": {
266 | "version": "3.0.0",
267 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
268 | "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
269 | "dev": true,
270 | "requires": {
271 | "esutils": "^2.0.2"
272 | }
273 | },
274 | "dotenv": {
275 | "version": "8.1.0",
276 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.1.0.tgz",
277 | "integrity": "sha512-GUE3gqcDCaMltj2++g6bRQ5rBJWtkWTmqmD0fo1RnnMuUqHNCt2oTPeDnS9n6fKYvlhn7AeBkb38lymBtWBQdA=="
278 | },
279 | "emoji-regex": {
280 | "version": "7.0.3",
281 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
282 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
283 | "dev": true
284 | },
285 | "error-ex": {
286 | "version": "1.3.2",
287 | "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
288 | "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
289 | "dev": true,
290 | "requires": {
291 | "is-arrayish": "^0.2.1"
292 | }
293 | },
294 | "es-abstract": {
295 | "version": "1.14.2",
296 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.14.2.tgz",
297 | "integrity": "sha512-DgoQmbpFNOofkjJtKwr87Ma5EW4Dc8fWhD0R+ndq7Oc456ivUfGOOP6oAZTTKl5/CcNMP+EN+e3/iUzgE0veZg==",
298 | "dev": true,
299 | "requires": {
300 | "es-to-primitive": "^1.2.0",
301 | "function-bind": "^1.1.1",
302 | "has": "^1.0.3",
303 | "has-symbols": "^1.0.0",
304 | "is-callable": "^1.1.4",
305 | "is-regex": "^1.0.4",
306 | "object-inspect": "^1.6.0",
307 | "object-keys": "^1.1.1",
308 | "string.prototype.trimleft": "^2.0.0",
309 | "string.prototype.trimright": "^2.0.0"
310 | }
311 | },
312 | "es-to-primitive": {
313 | "version": "1.2.0",
314 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
315 | "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
316 | "dev": true,
317 | "requires": {
318 | "is-callable": "^1.1.4",
319 | "is-date-object": "^1.0.1",
320 | "is-symbol": "^1.0.2"
321 | }
322 | },
323 | "escape-string-regexp": {
324 | "version": "1.0.5",
325 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
326 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
327 | "dev": true
328 | },
329 | "eslint": {
330 | "version": "6.3.0",
331 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.3.0.tgz",
332 | "integrity": "sha512-ZvZTKaqDue+N8Y9g0kp6UPZtS4FSY3qARxBs7p4f0H0iof381XHduqVerFWtK8DPtKmemqbqCFENWSQgPR/Gow==",
333 | "dev": true,
334 | "requires": {
335 | "@babel/code-frame": "^7.0.0",
336 | "ajv": "^6.10.0",
337 | "chalk": "^2.1.0",
338 | "cross-spawn": "^6.0.5",
339 | "debug": "^4.0.1",
340 | "doctrine": "^3.0.0",
341 | "eslint-scope": "^5.0.0",
342 | "eslint-utils": "^1.4.2",
343 | "eslint-visitor-keys": "^1.1.0",
344 | "espree": "^6.1.1",
345 | "esquery": "^1.0.1",
346 | "esutils": "^2.0.2",
347 | "file-entry-cache": "^5.0.1",
348 | "functional-red-black-tree": "^1.0.1",
349 | "glob-parent": "^5.0.0",
350 | "globals": "^11.7.0",
351 | "ignore": "^4.0.6",
352 | "import-fresh": "^3.0.0",
353 | "imurmurhash": "^0.1.4",
354 | "inquirer": "^6.4.1",
355 | "is-glob": "^4.0.0",
356 | "js-yaml": "^3.13.1",
357 | "json-stable-stringify-without-jsonify": "^1.0.1",
358 | "levn": "^0.3.0",
359 | "lodash": "^4.17.14",
360 | "minimatch": "^3.0.4",
361 | "mkdirp": "^0.5.1",
362 | "natural-compare": "^1.4.0",
363 | "optionator": "^0.8.2",
364 | "progress": "^2.0.0",
365 | "regexpp": "^2.0.1",
366 | "semver": "^6.1.2",
367 | "strip-ansi": "^5.2.0",
368 | "strip-json-comments": "^3.0.1",
369 | "table": "^5.2.3",
370 | "text-table": "^0.2.0",
371 | "v8-compile-cache": "^2.0.3"
372 | }
373 | },
374 | "eslint-config-airbnb-base": {
375 | "version": "14.0.0",
376 | "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.0.0.tgz",
377 | "integrity": "sha512-2IDHobw97upExLmsebhtfoD3NAKhV4H0CJWP3Uprd/uk+cHuWYOczPVxQ8PxLFUAw7o3Th1RAU8u1DoUpr+cMA==",
378 | "dev": true,
379 | "requires": {
380 | "confusing-browser-globals": "^1.0.7",
381 | "object.assign": "^4.1.0",
382 | "object.entries": "^1.1.0"
383 | }
384 | },
385 | "eslint-import-resolver-node": {
386 | "version": "0.3.2",
387 | "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz",
388 | "integrity": "sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q==",
389 | "dev": true,
390 | "requires": {
391 | "debug": "^2.6.9",
392 | "resolve": "^1.5.0"
393 | },
394 | "dependencies": {
395 | "debug": {
396 | "version": "2.6.9",
397 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
398 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
399 | "dev": true,
400 | "requires": {
401 | "ms": "2.0.0"
402 | }
403 | },
404 | "ms": {
405 | "version": "2.0.0",
406 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
407 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
408 | "dev": true
409 | }
410 | }
411 | },
412 | "eslint-module-utils": {
413 | "version": "2.4.1",
414 | "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz",
415 | "integrity": "sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw==",
416 | "dev": true,
417 | "requires": {
418 | "debug": "^2.6.8",
419 | "pkg-dir": "^2.0.0"
420 | },
421 | "dependencies": {
422 | "debug": {
423 | "version": "2.6.9",
424 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
425 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
426 | "dev": true,
427 | "requires": {
428 | "ms": "2.0.0"
429 | }
430 | },
431 | "ms": {
432 | "version": "2.0.0",
433 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
434 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
435 | "dev": true
436 | }
437 | }
438 | },
439 | "eslint-plugin-import": {
440 | "version": "2.18.2",
441 | "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz",
442 | "integrity": "sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ==",
443 | "dev": true,
444 | "requires": {
445 | "array-includes": "^3.0.3",
446 | "contains-path": "^0.1.0",
447 | "debug": "^2.6.9",
448 | "doctrine": "1.5.0",
449 | "eslint-import-resolver-node": "^0.3.2",
450 | "eslint-module-utils": "^2.4.0",
451 | "has": "^1.0.3",
452 | "minimatch": "^3.0.4",
453 | "object.values": "^1.1.0",
454 | "read-pkg-up": "^2.0.0",
455 | "resolve": "^1.11.0"
456 | },
457 | "dependencies": {
458 | "debug": {
459 | "version": "2.6.9",
460 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
461 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
462 | "dev": true,
463 | "requires": {
464 | "ms": "2.0.0"
465 | }
466 | },
467 | "doctrine": {
468 | "version": "1.5.0",
469 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz",
470 | "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=",
471 | "dev": true,
472 | "requires": {
473 | "esutils": "^2.0.2",
474 | "isarray": "^1.0.0"
475 | }
476 | },
477 | "ms": {
478 | "version": "2.0.0",
479 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
480 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
481 | "dev": true
482 | }
483 | }
484 | },
485 | "eslint-scope": {
486 | "version": "5.0.0",
487 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz",
488 | "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==",
489 | "dev": true,
490 | "requires": {
491 | "esrecurse": "^4.1.0",
492 | "estraverse": "^4.1.1"
493 | }
494 | },
495 | "eslint-utils": {
496 | "version": "1.4.2",
497 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.2.tgz",
498 | "integrity": "sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==",
499 | "dev": true,
500 | "requires": {
501 | "eslint-visitor-keys": "^1.0.0"
502 | }
503 | },
504 | "eslint-visitor-keys": {
505 | "version": "1.1.0",
506 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
507 | "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
508 | "dev": true
509 | },
510 | "espree": {
511 | "version": "6.1.1",
512 | "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.1.tgz",
513 | "integrity": "sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==",
514 | "dev": true,
515 | "requires": {
516 | "acorn": "^7.0.0",
517 | "acorn-jsx": "^5.0.2",
518 | "eslint-visitor-keys": "^1.1.0"
519 | }
520 | },
521 | "esprima": {
522 | "version": "4.0.1",
523 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
524 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
525 | "dev": true
526 | },
527 | "esquery": {
528 | "version": "1.0.1",
529 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
530 | "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
531 | "dev": true,
532 | "requires": {
533 | "estraverse": "^4.0.0"
534 | }
535 | },
536 | "esrecurse": {
537 | "version": "4.2.1",
538 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
539 | "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
540 | "dev": true,
541 | "requires": {
542 | "estraverse": "^4.1.0"
543 | }
544 | },
545 | "estraverse": {
546 | "version": "4.3.0",
547 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
548 | "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
549 | "dev": true
550 | },
551 | "esutils": {
552 | "version": "2.0.3",
553 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
554 | "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
555 | "dev": true
556 | },
557 | "events": {
558 | "version": "1.1.1",
559 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
560 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
561 | },
562 | "external-editor": {
563 | "version": "3.1.0",
564 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
565 | "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
566 | "dev": true,
567 | "requires": {
568 | "chardet": "^0.7.0",
569 | "iconv-lite": "^0.4.24",
570 | "tmp": "^0.0.33"
571 | }
572 | },
573 | "fast-deep-equal": {
574 | "version": "2.0.1",
575 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
576 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
577 | "dev": true
578 | },
579 | "fast-json-stable-stringify": {
580 | "version": "2.0.0",
581 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
582 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
583 | "dev": true
584 | },
585 | "fast-levenshtein": {
586 | "version": "2.0.6",
587 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
588 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
589 | "dev": true
590 | },
591 | "figures": {
592 | "version": "2.0.0",
593 | "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
594 | "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
595 | "dev": true,
596 | "requires": {
597 | "escape-string-regexp": "^1.0.5"
598 | }
599 | },
600 | "file-entry-cache": {
601 | "version": "5.0.1",
602 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
603 | "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
604 | "dev": true,
605 | "requires": {
606 | "flat-cache": "^2.0.1"
607 | }
608 | },
609 | "find-up": {
610 | "version": "2.1.0",
611 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
612 | "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
613 | "dev": true,
614 | "requires": {
615 | "locate-path": "^2.0.0"
616 | }
617 | },
618 | "flat-cache": {
619 | "version": "2.0.1",
620 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
621 | "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
622 | "dev": true,
623 | "requires": {
624 | "flatted": "^2.0.0",
625 | "rimraf": "2.6.3",
626 | "write": "1.0.3"
627 | }
628 | },
629 | "flatted": {
630 | "version": "2.0.1",
631 | "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
632 | "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
633 | "dev": true
634 | },
635 | "fs.realpath": {
636 | "version": "1.0.0",
637 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
638 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
639 | "dev": true
640 | },
641 | "function-bind": {
642 | "version": "1.1.1",
643 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
644 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
645 | "dev": true
646 | },
647 | "functional-red-black-tree": {
648 | "version": "1.0.1",
649 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
650 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
651 | "dev": true
652 | },
653 | "glob": {
654 | "version": "7.1.4",
655 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
656 | "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
657 | "dev": true,
658 | "requires": {
659 | "fs.realpath": "^1.0.0",
660 | "inflight": "^1.0.4",
661 | "inherits": "2",
662 | "minimatch": "^3.0.4",
663 | "once": "^1.3.0",
664 | "path-is-absolute": "^1.0.0"
665 | }
666 | },
667 | "glob-parent": {
668 | "version": "5.0.0",
669 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.0.0.tgz",
670 | "integrity": "sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==",
671 | "dev": true,
672 | "requires": {
673 | "is-glob": "^4.0.1"
674 | }
675 | },
676 | "globals": {
677 | "version": "11.12.0",
678 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
679 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
680 | "dev": true
681 | },
682 | "graceful-fs": {
683 | "version": "4.2.2",
684 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
685 | "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==",
686 | "dev": true
687 | },
688 | "has": {
689 | "version": "1.0.3",
690 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
691 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
692 | "dev": true,
693 | "requires": {
694 | "function-bind": "^1.1.1"
695 | }
696 | },
697 | "has-flag": {
698 | "version": "3.0.0",
699 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
700 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
701 | "dev": true
702 | },
703 | "has-symbols": {
704 | "version": "1.0.0",
705 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
706 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
707 | "dev": true
708 | },
709 | "hosted-git-info": {
710 | "version": "2.8.4",
711 | "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz",
712 | "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==",
713 | "dev": true
714 | },
715 | "iconv-lite": {
716 | "version": "0.4.24",
717 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
718 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
719 | "dev": true,
720 | "requires": {
721 | "safer-buffer": ">= 2.1.2 < 3"
722 | }
723 | },
724 | "ieee754": {
725 | "version": "1.1.8",
726 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz",
727 | "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q="
728 | },
729 | "ignore": {
730 | "version": "4.0.6",
731 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
732 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
733 | "dev": true
734 | },
735 | "import-fresh": {
736 | "version": "3.1.0",
737 | "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz",
738 | "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==",
739 | "dev": true,
740 | "requires": {
741 | "parent-module": "^1.0.0",
742 | "resolve-from": "^4.0.0"
743 | }
744 | },
745 | "imurmurhash": {
746 | "version": "0.1.4",
747 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
748 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
749 | "dev": true
750 | },
751 | "inflight": {
752 | "version": "1.0.6",
753 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
754 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
755 | "dev": true,
756 | "requires": {
757 | "once": "^1.3.0",
758 | "wrappy": "1"
759 | }
760 | },
761 | "inherits": {
762 | "version": "2.0.4",
763 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
764 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
765 | "dev": true
766 | },
767 | "inquirer": {
768 | "version": "6.5.2",
769 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
770 | "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
771 | "dev": true,
772 | "requires": {
773 | "ansi-escapes": "^3.2.0",
774 | "chalk": "^2.4.2",
775 | "cli-cursor": "^2.1.0",
776 | "cli-width": "^2.0.0",
777 | "external-editor": "^3.0.3",
778 | "figures": "^2.0.0",
779 | "lodash": "^4.17.12",
780 | "mute-stream": "0.0.7",
781 | "run-async": "^2.2.0",
782 | "rxjs": "^6.4.0",
783 | "string-width": "^2.1.0",
784 | "strip-ansi": "^5.1.0",
785 | "through": "^2.3.6"
786 | }
787 | },
788 | "is-arrayish": {
789 | "version": "0.2.1",
790 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
791 | "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
792 | "dev": true
793 | },
794 | "is-callable": {
795 | "version": "1.1.4",
796 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
797 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
798 | "dev": true
799 | },
800 | "is-date-object": {
801 | "version": "1.0.1",
802 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
803 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
804 | "dev": true
805 | },
806 | "is-extglob": {
807 | "version": "2.1.1",
808 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
809 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
810 | "dev": true
811 | },
812 | "is-fullwidth-code-point": {
813 | "version": "2.0.0",
814 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
815 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
816 | "dev": true
817 | },
818 | "is-glob": {
819 | "version": "4.0.1",
820 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
821 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
822 | "dev": true,
823 | "requires": {
824 | "is-extglob": "^2.1.1"
825 | }
826 | },
827 | "is-promise": {
828 | "version": "2.1.0",
829 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
830 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
831 | "dev": true
832 | },
833 | "is-regex": {
834 | "version": "1.0.4",
835 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
836 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
837 | "dev": true,
838 | "requires": {
839 | "has": "^1.0.1"
840 | }
841 | },
842 | "is-symbol": {
843 | "version": "1.0.2",
844 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
845 | "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
846 | "dev": true,
847 | "requires": {
848 | "has-symbols": "^1.0.0"
849 | }
850 | },
851 | "isarray": {
852 | "version": "1.0.0",
853 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
854 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
855 | },
856 | "isexe": {
857 | "version": "2.0.0",
858 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
859 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
860 | "dev": true
861 | },
862 | "jmespath": {
863 | "version": "0.15.0",
864 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
865 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
866 | },
867 | "js-tokens": {
868 | "version": "4.0.0",
869 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
870 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
871 | "dev": true
872 | },
873 | "js-yaml": {
874 | "version": "3.13.1",
875 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
876 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
877 | "dev": true,
878 | "requires": {
879 | "argparse": "^1.0.7",
880 | "esprima": "^4.0.0"
881 | }
882 | },
883 | "json-schema-traverse": {
884 | "version": "0.4.1",
885 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
886 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
887 | "dev": true
888 | },
889 | "json-stable-stringify-without-jsonify": {
890 | "version": "1.0.1",
891 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
892 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
893 | "dev": true
894 | },
895 | "levn": {
896 | "version": "0.3.0",
897 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
898 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
899 | "dev": true,
900 | "requires": {
901 | "prelude-ls": "~1.1.2",
902 | "type-check": "~0.3.2"
903 | }
904 | },
905 | "load-json-file": {
906 | "version": "2.0.0",
907 | "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
908 | "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
909 | "dev": true,
910 | "requires": {
911 | "graceful-fs": "^4.1.2",
912 | "parse-json": "^2.2.0",
913 | "pify": "^2.0.0",
914 | "strip-bom": "^3.0.0"
915 | }
916 | },
917 | "locate-path": {
918 | "version": "2.0.0",
919 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
920 | "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
921 | "dev": true,
922 | "requires": {
923 | "p-locate": "^2.0.0",
924 | "path-exists": "^3.0.0"
925 | }
926 | },
927 | "lodash": {
928 | "version": "4.17.15",
929 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
930 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
931 | "dev": true
932 | },
933 | "mimic-fn": {
934 | "version": "1.2.0",
935 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
936 | "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
937 | "dev": true
938 | },
939 | "minimatch": {
940 | "version": "3.0.4",
941 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
942 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
943 | "dev": true,
944 | "requires": {
945 | "brace-expansion": "^1.1.7"
946 | }
947 | },
948 | "minimist": {
949 | "version": "0.0.8",
950 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
951 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
952 | "dev": true
953 | },
954 | "mkdirp": {
955 | "version": "0.5.1",
956 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
957 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
958 | "dev": true,
959 | "requires": {
960 | "minimist": "0.0.8"
961 | }
962 | },
963 | "ms": {
964 | "version": "2.1.2",
965 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
966 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
967 | "dev": true
968 | },
969 | "mute-stream": {
970 | "version": "0.0.7",
971 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
972 | "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
973 | "dev": true
974 | },
975 | "natural-compare": {
976 | "version": "1.4.0",
977 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
978 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
979 | "dev": true
980 | },
981 | "nice-try": {
982 | "version": "1.0.5",
983 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
984 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
985 | "dev": true
986 | },
987 | "normalize-package-data": {
988 | "version": "2.5.0",
989 | "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
990 | "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
991 | "dev": true,
992 | "requires": {
993 | "hosted-git-info": "^2.1.4",
994 | "resolve": "^1.10.0",
995 | "semver": "2 || 3 || 4 || 5",
996 | "validate-npm-package-license": "^3.0.1"
997 | },
998 | "dependencies": {
999 | "semver": {
1000 | "version": "5.7.1",
1001 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
1002 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
1003 | "dev": true
1004 | }
1005 | }
1006 | },
1007 | "object-inspect": {
1008 | "version": "1.6.0",
1009 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz",
1010 | "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==",
1011 | "dev": true
1012 | },
1013 | "object-keys": {
1014 | "version": "1.1.1",
1015 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
1016 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
1017 | "dev": true
1018 | },
1019 | "object.assign": {
1020 | "version": "4.1.0",
1021 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
1022 | "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
1023 | "dev": true,
1024 | "requires": {
1025 | "define-properties": "^1.1.2",
1026 | "function-bind": "^1.1.1",
1027 | "has-symbols": "^1.0.0",
1028 | "object-keys": "^1.0.11"
1029 | }
1030 | },
1031 | "object.entries": {
1032 | "version": "1.1.0",
1033 | "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.0.tgz",
1034 | "integrity": "sha512-l+H6EQ8qzGRxbkHOd5I/aHRhHDKoQXQ8g0BYt4uSweQU1/J6dZUOyWh9a2Vky35YCKjzmgxOzta2hH6kf9HuXA==",
1035 | "dev": true,
1036 | "requires": {
1037 | "define-properties": "^1.1.3",
1038 | "es-abstract": "^1.12.0",
1039 | "function-bind": "^1.1.1",
1040 | "has": "^1.0.3"
1041 | }
1042 | },
1043 | "object.values": {
1044 | "version": "1.1.0",
1045 | "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.0.tgz",
1046 | "integrity": "sha512-8mf0nKLAoFX6VlNVdhGj31SVYpaNFtUnuoOXWyFEstsWRgU837AK+JYM0iAxwkSzGRbwn8cbFmgbyxj1j4VbXg==",
1047 | "dev": true,
1048 | "requires": {
1049 | "define-properties": "^1.1.3",
1050 | "es-abstract": "^1.12.0",
1051 | "function-bind": "^1.1.1",
1052 | "has": "^1.0.3"
1053 | }
1054 | },
1055 | "once": {
1056 | "version": "1.4.0",
1057 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
1058 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
1059 | "dev": true,
1060 | "requires": {
1061 | "wrappy": "1"
1062 | }
1063 | },
1064 | "onetime": {
1065 | "version": "2.0.1",
1066 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
1067 | "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
1068 | "dev": true,
1069 | "requires": {
1070 | "mimic-fn": "^1.0.0"
1071 | }
1072 | },
1073 | "optionator": {
1074 | "version": "0.8.2",
1075 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
1076 | "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
1077 | "dev": true,
1078 | "requires": {
1079 | "deep-is": "~0.1.3",
1080 | "fast-levenshtein": "~2.0.4",
1081 | "levn": "~0.3.0",
1082 | "prelude-ls": "~1.1.2",
1083 | "type-check": "~0.3.2",
1084 | "wordwrap": "~1.0.0"
1085 | }
1086 | },
1087 | "os-tmpdir": {
1088 | "version": "1.0.2",
1089 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
1090 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
1091 | "dev": true
1092 | },
1093 | "p-limit": {
1094 | "version": "1.3.0",
1095 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
1096 | "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
1097 | "dev": true,
1098 | "requires": {
1099 | "p-try": "^1.0.0"
1100 | }
1101 | },
1102 | "p-locate": {
1103 | "version": "2.0.0",
1104 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
1105 | "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
1106 | "dev": true,
1107 | "requires": {
1108 | "p-limit": "^1.1.0"
1109 | }
1110 | },
1111 | "p-try": {
1112 | "version": "1.0.0",
1113 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
1114 | "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
1115 | "dev": true
1116 | },
1117 | "parent-module": {
1118 | "version": "1.0.1",
1119 | "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
1120 | "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
1121 | "dev": true,
1122 | "requires": {
1123 | "callsites": "^3.0.0"
1124 | }
1125 | },
1126 | "parse-json": {
1127 | "version": "2.2.0",
1128 | "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
1129 | "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
1130 | "dev": true,
1131 | "requires": {
1132 | "error-ex": "^1.2.0"
1133 | }
1134 | },
1135 | "path-exists": {
1136 | "version": "3.0.0",
1137 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
1138 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
1139 | "dev": true
1140 | },
1141 | "path-is-absolute": {
1142 | "version": "1.0.1",
1143 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
1144 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
1145 | "dev": true
1146 | },
1147 | "path-key": {
1148 | "version": "2.0.1",
1149 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
1150 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
1151 | "dev": true
1152 | },
1153 | "path-parse": {
1154 | "version": "1.0.6",
1155 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
1156 | "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
1157 | "dev": true
1158 | },
1159 | "path-type": {
1160 | "version": "2.0.0",
1161 | "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
1162 | "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
1163 | "dev": true,
1164 | "requires": {
1165 | "pify": "^2.0.0"
1166 | }
1167 | },
1168 | "pify": {
1169 | "version": "2.3.0",
1170 | "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
1171 | "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
1172 | "dev": true
1173 | },
1174 | "pkg-dir": {
1175 | "version": "2.0.0",
1176 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
1177 | "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
1178 | "dev": true,
1179 | "requires": {
1180 | "find-up": "^2.1.0"
1181 | }
1182 | },
1183 | "prelude-ls": {
1184 | "version": "1.1.2",
1185 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
1186 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
1187 | "dev": true
1188 | },
1189 | "progress": {
1190 | "version": "2.0.3",
1191 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
1192 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
1193 | "dev": true
1194 | },
1195 | "punycode": {
1196 | "version": "1.3.2",
1197 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
1198 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
1199 | },
1200 | "querystring": {
1201 | "version": "0.2.0",
1202 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
1203 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
1204 | },
1205 | "read-pkg": {
1206 | "version": "2.0.0",
1207 | "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
1208 | "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
1209 | "dev": true,
1210 | "requires": {
1211 | "load-json-file": "^2.0.0",
1212 | "normalize-package-data": "^2.3.2",
1213 | "path-type": "^2.0.0"
1214 | }
1215 | },
1216 | "read-pkg-up": {
1217 | "version": "2.0.0",
1218 | "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
1219 | "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
1220 | "dev": true,
1221 | "requires": {
1222 | "find-up": "^2.0.0",
1223 | "read-pkg": "^2.0.0"
1224 | }
1225 | },
1226 | "regexpp": {
1227 | "version": "2.0.1",
1228 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
1229 | "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
1230 | "dev": true
1231 | },
1232 | "resolve": {
1233 | "version": "1.12.0",
1234 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
1235 | "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
1236 | "dev": true,
1237 | "requires": {
1238 | "path-parse": "^1.0.6"
1239 | }
1240 | },
1241 | "resolve-from": {
1242 | "version": "4.0.0",
1243 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
1244 | "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
1245 | "dev": true
1246 | },
1247 | "restore-cursor": {
1248 | "version": "2.0.0",
1249 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
1250 | "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
1251 | "dev": true,
1252 | "requires": {
1253 | "onetime": "^2.0.0",
1254 | "signal-exit": "^3.0.2"
1255 | }
1256 | },
1257 | "rimraf": {
1258 | "version": "2.6.3",
1259 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
1260 | "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
1261 | "dev": true,
1262 | "requires": {
1263 | "glob": "^7.1.3"
1264 | }
1265 | },
1266 | "run-async": {
1267 | "version": "2.3.0",
1268 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
1269 | "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
1270 | "dev": true,
1271 | "requires": {
1272 | "is-promise": "^2.1.0"
1273 | }
1274 | },
1275 | "rxjs": {
1276 | "version": "6.5.3",
1277 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz",
1278 | "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==",
1279 | "dev": true,
1280 | "requires": {
1281 | "tslib": "^1.9.0"
1282 | }
1283 | },
1284 | "safer-buffer": {
1285 | "version": "2.1.2",
1286 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1287 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1288 | "dev": true
1289 | },
1290 | "sax": {
1291 | "version": "1.2.1",
1292 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
1293 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
1294 | },
1295 | "semver": {
1296 | "version": "6.3.0",
1297 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
1298 | "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
1299 | "dev": true
1300 | },
1301 | "shebang-command": {
1302 | "version": "1.2.0",
1303 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
1304 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
1305 | "dev": true,
1306 | "requires": {
1307 | "shebang-regex": "^1.0.0"
1308 | }
1309 | },
1310 | "shebang-regex": {
1311 | "version": "1.0.0",
1312 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
1313 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
1314 | "dev": true
1315 | },
1316 | "signal-exit": {
1317 | "version": "3.0.2",
1318 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
1319 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
1320 | "dev": true
1321 | },
1322 | "slice-ansi": {
1323 | "version": "2.1.0",
1324 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
1325 | "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
1326 | "dev": true,
1327 | "requires": {
1328 | "ansi-styles": "^3.2.0",
1329 | "astral-regex": "^1.0.0",
1330 | "is-fullwidth-code-point": "^2.0.0"
1331 | }
1332 | },
1333 | "spdx-correct": {
1334 | "version": "3.1.0",
1335 | "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
1336 | "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
1337 | "dev": true,
1338 | "requires": {
1339 | "spdx-expression-parse": "^3.0.0",
1340 | "spdx-license-ids": "^3.0.0"
1341 | }
1342 | },
1343 | "spdx-exceptions": {
1344 | "version": "2.2.0",
1345 | "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
1346 | "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
1347 | "dev": true
1348 | },
1349 | "spdx-expression-parse": {
1350 | "version": "3.0.0",
1351 | "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
1352 | "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
1353 | "dev": true,
1354 | "requires": {
1355 | "spdx-exceptions": "^2.1.0",
1356 | "spdx-license-ids": "^3.0.0"
1357 | }
1358 | },
1359 | "spdx-license-ids": {
1360 | "version": "3.0.5",
1361 | "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
1362 | "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
1363 | "dev": true
1364 | },
1365 | "sprintf-js": {
1366 | "version": "1.0.3",
1367 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
1368 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
1369 | "dev": true
1370 | },
1371 | "string-width": {
1372 | "version": "2.1.1",
1373 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
1374 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
1375 | "dev": true,
1376 | "requires": {
1377 | "is-fullwidth-code-point": "^2.0.0",
1378 | "strip-ansi": "^4.0.0"
1379 | },
1380 | "dependencies": {
1381 | "strip-ansi": {
1382 | "version": "4.0.0",
1383 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
1384 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
1385 | "dev": true,
1386 | "requires": {
1387 | "ansi-regex": "^3.0.0"
1388 | }
1389 | }
1390 | }
1391 | },
1392 | "string.prototype.trimleft": {
1393 | "version": "2.1.0",
1394 | "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz",
1395 | "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==",
1396 | "dev": true,
1397 | "requires": {
1398 | "define-properties": "^1.1.3",
1399 | "function-bind": "^1.1.1"
1400 | }
1401 | },
1402 | "string.prototype.trimright": {
1403 | "version": "2.1.0",
1404 | "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz",
1405 | "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==",
1406 | "dev": true,
1407 | "requires": {
1408 | "define-properties": "^1.1.3",
1409 | "function-bind": "^1.1.1"
1410 | }
1411 | },
1412 | "strip-ansi": {
1413 | "version": "5.2.0",
1414 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
1415 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
1416 | "dev": true,
1417 | "requires": {
1418 | "ansi-regex": "^4.1.0"
1419 | },
1420 | "dependencies": {
1421 | "ansi-regex": {
1422 | "version": "4.1.0",
1423 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
1424 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
1425 | "dev": true
1426 | }
1427 | }
1428 | },
1429 | "strip-bom": {
1430 | "version": "3.0.0",
1431 | "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
1432 | "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
1433 | "dev": true
1434 | },
1435 | "strip-json-comments": {
1436 | "version": "3.0.1",
1437 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz",
1438 | "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==",
1439 | "dev": true
1440 | },
1441 | "supports-color": {
1442 | "version": "5.5.0",
1443 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1444 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1445 | "dev": true,
1446 | "requires": {
1447 | "has-flag": "^3.0.0"
1448 | }
1449 | },
1450 | "table": {
1451 | "version": "5.4.6",
1452 | "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
1453 | "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
1454 | "dev": true,
1455 | "requires": {
1456 | "ajv": "^6.10.2",
1457 | "lodash": "^4.17.14",
1458 | "slice-ansi": "^2.1.0",
1459 | "string-width": "^3.0.0"
1460 | },
1461 | "dependencies": {
1462 | "string-width": {
1463 | "version": "3.1.0",
1464 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
1465 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
1466 | "dev": true,
1467 | "requires": {
1468 | "emoji-regex": "^7.0.1",
1469 | "is-fullwidth-code-point": "^2.0.0",
1470 | "strip-ansi": "^5.1.0"
1471 | }
1472 | }
1473 | }
1474 | },
1475 | "text-table": {
1476 | "version": "0.2.0",
1477 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
1478 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
1479 | "dev": true
1480 | },
1481 | "through": {
1482 | "version": "2.3.8",
1483 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
1484 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
1485 | "dev": true
1486 | },
1487 | "tmp": {
1488 | "version": "0.0.33",
1489 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
1490 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
1491 | "dev": true,
1492 | "requires": {
1493 | "os-tmpdir": "~1.0.2"
1494 | }
1495 | },
1496 | "tslib": {
1497 | "version": "1.10.0",
1498 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
1499 | "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
1500 | "dev": true
1501 | },
1502 | "type-check": {
1503 | "version": "0.3.2",
1504 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
1505 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
1506 | "dev": true,
1507 | "requires": {
1508 | "prelude-ls": "~1.1.2"
1509 | }
1510 | },
1511 | "uri-js": {
1512 | "version": "4.2.2",
1513 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
1514 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
1515 | "dev": true,
1516 | "requires": {
1517 | "punycode": "^2.1.0"
1518 | },
1519 | "dependencies": {
1520 | "punycode": {
1521 | "version": "2.1.1",
1522 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
1523 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
1524 | "dev": true
1525 | }
1526 | }
1527 | },
1528 | "url": {
1529 | "version": "0.10.3",
1530 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
1531 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=",
1532 | "requires": {
1533 | "punycode": "1.3.2",
1534 | "querystring": "0.2.0"
1535 | }
1536 | },
1537 | "uuid": {
1538 | "version": "3.3.2",
1539 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
1540 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
1541 | },
1542 | "v8-compile-cache": {
1543 | "version": "2.1.0",
1544 | "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz",
1545 | "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==",
1546 | "dev": true
1547 | },
1548 | "validate-npm-package-license": {
1549 | "version": "3.0.4",
1550 | "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
1551 | "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
1552 | "dev": true,
1553 | "requires": {
1554 | "spdx-correct": "^3.0.0",
1555 | "spdx-expression-parse": "^3.0.0"
1556 | }
1557 | },
1558 | "which": {
1559 | "version": "1.3.1",
1560 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
1561 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
1562 | "dev": true,
1563 | "requires": {
1564 | "isexe": "^2.0.0"
1565 | }
1566 | },
1567 | "wordwrap": {
1568 | "version": "1.0.0",
1569 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
1570 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
1571 | "dev": true
1572 | },
1573 | "wrappy": {
1574 | "version": "1.0.2",
1575 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1576 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
1577 | "dev": true
1578 | },
1579 | "write": {
1580 | "version": "1.0.3",
1581 | "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
1582 | "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
1583 | "dev": true,
1584 | "requires": {
1585 | "mkdirp": "^0.5.1"
1586 | }
1587 | },
1588 | "xml2js": {
1589 | "version": "0.4.19",
1590 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
1591 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
1592 | "requires": {
1593 | "sax": ">=0.6.0",
1594 | "xmlbuilder": "~9.0.1"
1595 | }
1596 | },
1597 | "xmlbuilder": {
1598 | "version": "9.0.7",
1599 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
1600 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
1601 | }
1602 | }
1603 | }
1604 |
--------------------------------------------------------------------------------