diff --git a/.gitignore b/.gitignore index 6dda408d30db0..f48cdeb4f0a8c 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,5 @@ coverage/ # CDK Context & Staging files cdk.context.json .cdk.staging/ +cdk.out/ + diff --git a/packages/@aws-cdk/applet-js/README.md b/packages/@aws-cdk/applet-js/README.md index d583b3dbc1f08..c71d87e54ea60 100644 --- a/packages/@aws-cdk/applet-js/README.md +++ b/packages/@aws-cdk/applet-js/README.md @@ -1,2 +1,5 @@ ## AWS CDK applet host program for Javascript + +CDK applets have been deprecated in favor of [decdk](https://github.com/awslabs/aws-cdk/tree/master/packages/decdk). + This module is part of the [AWS Cloud Development Kit](https://github.com/awslabs/aws-cdk) project. diff --git a/packages/@aws-cdk/applet-js/bin/cdk-applet-js.ts b/packages/@aws-cdk/applet-js/bin/cdk-applet-js.ts index f75a94a1338f3..f94028c403f17 100644 --- a/packages/@aws-cdk/applet-js/bin/cdk-applet-js.ts +++ b/packages/@aws-cdk/applet-js/bin/cdk-applet-js.ts @@ -1,104 +1,3 @@ #!/usr/bin/env node -import 'source-map-support/register'; - -import cdk = require('@aws-cdk/cdk'); -import child_process = require('child_process'); -import fs = require('fs-extra'); -import os = require('os'); -import path = require('path'); -import YAML = require('yaml'); - -import { isStackConstructor, parseApplet } from '../lib/applet-helpers'; - -main().catch(e => { - // tslint:disable-next-line:no-console - console.error(e); - process.exit(1); -}); - -async function main() { - const progname = path.basename(process.argv[1]); - - const appletFile = process.argv[2]; - if (!appletFile) { - throw new Error(`Usage: ${progname} `); - } - - // read applet(s) properties from the provided file - const fileContents = YAML.parse(await fs.readFile(appletFile, { encoding: 'utf-8' })); - if (typeof fileContents !== 'object') { - throw new Error(`${appletFile}: should contain a YAML object`); - } - const appletMap = fileContents.applets; - if (!appletMap) { - throw new Error(`${appletFile}: must have an 'applets' key`); - } - - const searchDir = path.dirname(appletFile); - const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'cdkapplet')); - try { - const app = new cdk.App(); - - for (const [name, definition] of Object.entries(appletMap)) { - await constructStack(app, searchDir, tempDir, name, definition); - } - app.run(); - } finally { - await fs.remove(tempDir); - } -} - -/** - * Construct a stack from the given props - * @param props Const - */ -async function constructStack(app: cdk.App, searchDir: string, tempDir: string, stackName: string, spec: any) { - // the 'applet' attribute tells us how to load the applet. in the javascript case - // it will be in the format : where is technically passed to "require" - // and is expected to be exported from the module. - const appletSpec: string | undefined = spec.type; - if (!appletSpec) { - throw new Error(`Applet ${stackName} missing "type" attribute`); - } - - const applet = parseApplet(appletSpec); - - const props = spec.properties || {}; - - if (applet.npmPackage) { - // tslint:disable-next-line:no-console - console.error(`Installing NPM package ${applet.npmPackage}`); - // Magic marker to download this package directly off of NPM - // We're going to run NPM as a shell (since programmatic usage is not stable - // by their own admission) and we're installing into a temporary directory. - // (Installing into a permanent directory is useless since NPM doesn't do - // any real caching anyway). - child_process.execFileSync('npm', ['install', '--prefix', tempDir, '--global', applet.npmPackage], { - stdio: 'inherit' - }); - searchDir = path.join(tempDir, 'lib'); - } - - // we need to resolve the module name relatively to where the applet file is - // and not relative to this module or cwd. - const modulePath = require.resolve(applet.moduleName, { paths: [ searchDir ] }); - - // load the module - const pkg = require(modulePath); - - // find the applet class within the package - // tslint:disable-next-line:variable-name - const appletConstructor = pkg[applet.className]; - if (!appletConstructor) { - throw new Error(`Cannot find applet class "${applet.className}" in module "${applet.moduleName}"`); - } - - if (isStackConstructor(appletConstructor)) { - // add the applet stack into the app. - new appletConstructor(app, stackName, props); - } else { - // Make a stack THEN add it in - const stack = new cdk.Stack(app, stackName, props); - new appletConstructor(stack, 'Default', props); - } -} +// tslint:disable-next-line:no-console +console.error('applets are no longer supported. see: https://github.com/awslabs/aws-cdk/tree/master/packages/decdk'); diff --git a/packages/@aws-cdk/applet-js/lib/applet-helpers.ts b/packages/@aws-cdk/applet-js/lib/applet-helpers.ts deleted file mode 100644 index 2bc519d12ad06..0000000000000 --- a/packages/@aws-cdk/applet-js/lib/applet-helpers.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Determine whether this constructorFunction is going to create an object that inherits from Stack - * - * We do structural typing. - */ -export function isStackConstructor(constructorFn: any) { - // Test for a public method that Stack has - return constructorFn.prototype.findResource !== undefined; -} - -/** - * Extract module name from a NPM package specification - */ -export function extractModuleName(packageSpec: string) { - const m = /^((?:@[a-zA-Z-]+\/)?[a-zA-Z-]+)/i.exec(packageSpec); - if (!m) { throw new Error(`Could not find package name in ${packageSpec}`); } - return m[1]; -} - -export function parseApplet(applet: string): AppletSpec { - const m = /^(npm:\/\/)?([a-z0-9_@./-]+)(:[a-z_0-9]+)?$/i.exec(applet); - if (!m) { - throw new Error(`"applet" value is "${applet}" but it must be in the form "[npm://][:]". - If is not specified, "Applet" is the default`); - } - - if (m[1] === 'npm://') { - return { - npmPackage: m[2], - moduleName: extractModuleName(m[2]), - className: className(m[3]), - }; - } else { - return { - moduleName: m[2], - className: className(m[3]), - }; - } - - function className(s: string | undefined) { - if (s) { - return s.substr(1); - } - return 'Applet'; - } -} - -export interface AppletSpec { - npmPackage?: string; - moduleName: string; - className: string; -} \ No newline at end of file diff --git a/packages/@aws-cdk/applet-js/package.json b/packages/@aws-cdk/applet-js/package.json index 2c337276cb23b..7f5abe38296f8 100644 --- a/packages/@aws-cdk/applet-js/package.json +++ b/packages/@aws-cdk/applet-js/package.json @@ -1,6 +1,7 @@ { "name": "@aws-cdk/applet-js", "version": "0.32.0", + "deprecated": "Applets have been deprecated in favor of 'decdk'", "description": "Javascript CDK applet host program", "main": "bin/cdk-applet-js.js", "types": "bin/cdk-applet-js.d.ts", @@ -25,16 +26,9 @@ "license": "Apache-2.0", "devDependencies": { "@types/fs-extra": "^5.0.5", - "@types/yaml": "^1.0.2", "cdk-build-tools": "^0.32.0", "pkglint": "^0.32.0" }, - "dependencies": { - "@aws-cdk/cdk": "^0.32.0", - "fs-extra": "^7.0.1", - "source-map-support": "^0.5.12", - "yaml": "^1.5.0" - }, "repository": { "url": "https://github.com/awslabs/aws-cdk.git", "type": "git", diff --git a/packages/@aws-cdk/applet-js/test/expected1.json b/packages/@aws-cdk/applet-js/test/expected1.json deleted file mode 100644 index 5f49964fdcf1e..0000000000000 --- a/packages/@aws-cdk/applet-js/test/expected1.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "TestApplet", - "template": { - "Parameters": { - "P1": { - "Type": "String", - "Default": "hello" - }, - "P2": { - "Type": "Number", - "Default": 123 - } - } - }, - "metadata": { - "/TestApplet/P1": [ - { - "type": "aws:cdk:logicalId", - "data": "P1", - "trace": [ - "**REDACTED**" - ] - } - ], - "/TestApplet/P2": [ - { - "type": "aws:cdk:logicalId", - "data": "P2", - "trace": [ - "**REDACTED**" - ] - } - ] - } -} diff --git a/packages/@aws-cdk/applet-js/test/expected2.json b/packages/@aws-cdk/applet-js/test/expected2.json deleted file mode 100644 index 3ef09cc69797f..0000000000000 --- a/packages/@aws-cdk/applet-js/test/expected2.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "TestApplet", - "template": { - "Parameters": { - "P1": { - "Type": "String", - "Default": "hello" - }, - "P2": { - "Type": "Number", - "Default": 123 - }, - "P3": { - "Type": "StringList", - "Default": "hello,this,is,awesome,12345" - } - } - }, - "metadata": { - "/TestApplet/P1": [ - { - "type": "aws:cdk:logicalId", - "data": "P1", - "trace": [ - "**REDACTED**" - ] - } - ], - "/TestApplet/P2": [ - { - "type": "aws:cdk:logicalId", - "data": "P2", - "trace": [ - "**REDACTED**" - ] - } - ], - "/TestApplet/P3": [ - { - "type": "aws:cdk:logicalId", - "data": "P3", - "trace": [ - "**REDACTED**" - ] - } - ] - } -} diff --git a/packages/@aws-cdk/applet-js/test/expected3.json b/packages/@aws-cdk/applet-js/test/expected3.json deleted file mode 100644 index 495c9527bb181..0000000000000 --- a/packages/@aws-cdk/applet-js/test/expected3.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "Applet", - "template": { - "Description": "this should be reflected in the template description" - }, - "metadata": {} -} diff --git a/packages/@aws-cdk/applet-js/test/manual-test-npm.yaml b/packages/@aws-cdk/applet-js/test/manual-test-npm.yaml deleted file mode 100644 index 6bc517200cd34..0000000000000 --- a/packages/@aws-cdk/applet-js/test/manual-test-npm.yaml +++ /dev/null @@ -1,3 +0,0 @@ -applets: - Hello: - type: npm://@aws-cdk/aws-ecs:Hello diff --git a/packages/@aws-cdk/applet-js/test/negative-test4.yaml b/packages/@aws-cdk/applet-js/test/negative-test4.yaml deleted file mode 100755 index 25a9c1f24031b..0000000000000 --- a/packages/@aws-cdk/applet-js/test/negative-test4.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# malformed applet specification -applets: - Applet: - type: invalid:number:of:components:in:applet:name diff --git a/packages/@aws-cdk/applet-js/test/negative-test5.yaml b/packages/@aws-cdk/applet-js/test/negative-test5.yaml deleted file mode 100755 index 233462e65d968..0000000000000 --- a/packages/@aws-cdk/applet-js/test/negative-test5.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# applet module not found -applets: - Applet: - type: notfound diff --git a/packages/@aws-cdk/applet-js/test/negative-test6.yaml b/packages/@aws-cdk/applet-js/test/negative-test6.yaml deleted file mode 100755 index 6989798953d29..0000000000000 --- a/packages/@aws-cdk/applet-js/test/negative-test6.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# applet class not found -applets: - Applet: - type: ./test-applet:ClassNotFound diff --git a/packages/@aws-cdk/applet-js/test/negative-test7.yaml b/packages/@aws-cdk/applet-js/test/negative-test7.yaml deleted file mode 100755 index 65710d6706fcb..0000000000000 --- a/packages/@aws-cdk/applet-js/test/negative-test7.yaml +++ /dev/null @@ -1,4 +0,0 @@ -# no module -applets: - Applet: - type: :TestApplet diff --git a/packages/@aws-cdk/applet-js/test/test-applet.ts b/packages/@aws-cdk/applet-js/test/test-applet.ts deleted file mode 100644 index 1254b5a766420..0000000000000 --- a/packages/@aws-cdk/applet-js/test/test-applet.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { App, CfnParameter, Construct, Stack, StackProps } from '@aws-cdk/cdk'; - -export interface TestAppletProps extends StackProps { - prop1: string - prop2: number - prop3?: string[] -} - -export class TestApplet extends Stack { - constructor(scope: App, id: string, props: TestAppletProps) { - super(scope, id, props); - - new CfnParameter(this, 'P1', { default: this.node.required(props, 'prop1'), type: 'String' }); - new CfnParameter(this, 'P2', { default: this.node.required(props, 'prop2'), type: 'Number' }); - - if (props.prop3) { - new CfnParameter(this, 'P3', { default: props.prop3.join(','), type: 'StringList' }); - } - } -} - -export interface AppletProps extends StackProps { - desc?: string -} - -export class Applet extends Stack { - constructor(scope: App, id: string, props: AppletProps) { - super(scope, id); - - this.templateOptions.description = props.desc; - } -} - -interface NoStackAppletProps { - argument: string; -} - -export class NoStackApplet extends Construct { - constructor(scope: Construct, id: string, props: NoStackAppletProps) { - super(scope, id); - - new CfnParameter(this, 'P1', { default: props.argument, type: 'String' }); - } -} diff --git a/packages/@aws-cdk/applet-js/test/test-multistack-expected.json b/packages/@aws-cdk/applet-js/test/test-multistack-expected.json deleted file mode 100644 index d3cfb592f5396..0000000000000 --- a/packages/@aws-cdk/applet-js/test/test-multistack-expected.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "Stack2", - "template": { - "Parameters": { - "P1": { - "Type": "String", - "Default": "stack2" - }, - "P2": { - "Type": "Number", - "Default": 456 - } - } - }, - "metadata": { - "/Stack2/P1": [ - { - "type": "aws:cdk:logicalId", - "data": "P1", - "trace": [ - "**REDACTED**" - ] - } - ], - "/Stack2/P2": [ - { - "type": "aws:cdk:logicalId", - "data": "P2", - "trace": [ - "**REDACTED**" - ] - } - ] - } -} diff --git a/packages/@aws-cdk/applet-js/test/test-multistack.yaml b/packages/@aws-cdk/applet-js/test/test-multistack.yaml deleted file mode 100644 index 41259ead87f34..0000000000000 --- a/packages/@aws-cdk/applet-js/test/test-multistack.yaml +++ /dev/null @@ -1,11 +0,0 @@ -applets: - Stack1: - type: ./test-applet:TestApplet - properties: - prop1: stack1 - prop2: 123 - Stack2: - type: ./test-applet:TestApplet - properties: - prop1: stack2 - prop2: 456 diff --git a/packages/@aws-cdk/applet-js/test/test-nonstack-expected.json b/packages/@aws-cdk/applet-js/test/test-nonstack-expected.json deleted file mode 100644 index 3abdc38dc8187..0000000000000 --- a/packages/@aws-cdk/applet-js/test/test-nonstack-expected.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "NoStackApplet", - "template": { - "Parameters": { - "P1": { - "Type": "String", - "Default": "this should be reflected in the template description" - } - } - }, - "metadata": { - "/NoStackApplet/Default/P1": [ - { - "type": "aws:cdk:logicalId", - "data": "P1", - "trace": [ - "**REDACTED**" - ] - } - ] - } -} diff --git a/packages/@aws-cdk/applet-js/test/test-nonstack.yaml b/packages/@aws-cdk/applet-js/test/test-nonstack.yaml deleted file mode 100644 index 7f2e960cf0391..0000000000000 --- a/packages/@aws-cdk/applet-js/test/test-nonstack.yaml +++ /dev/null @@ -1,6 +0,0 @@ -applets: - NoStackApplet: - type: ./test-applet:NoStackApplet - properties: - argument: this should be reflected in the template description - diff --git a/packages/@aws-cdk/applet-js/test/test.applets.ts b/packages/@aws-cdk/applet-js/test/test.applets.ts deleted file mode 100644 index 1e0cd80f67fc8..0000000000000 --- a/packages/@aws-cdk/applet-js/test/test.applets.ts +++ /dev/null @@ -1,117 +0,0 @@ -import child_process = require('child_process'); -import fs = require('fs'); -import { Test } from 'nodeunit'; -import os = require('os'); -import path = require('path'); - -export = { - 'basic test 1'(test: Test) { - expectMatch(test, 'test/expected1.json', getStack('TestApplet', synthesizeApplet('test/test1.yaml'))); - test.done(); - }, - - 'basic test 2'(test: Test) { - expectMatch(test, 'test/expected2.json', getStack('TestApplet', synthesizeApplet('test/test2.yaml'))); - test.done(); - }, - - 'can use shebang'(test: Test) { - fs.chmodSync('test/test3.yaml', 0o755); - expectMatch(test, 'test/expected3.json', getStack('Applet', synthesizeApplet('test/test3.yaml', true))); - test.done(); - }, - - 'test non stack construct'(test: Test) { - expectMatch(test, 'test/test-nonstack-expected.json', getStack('NoStackApplet', synthesizeApplet('test/test-nonstack.yaml'))); - test.done(); - }, - - 'test multiple stacks'(test: Test) { - expectMatch(test, 'test/test-multistack-expected.json', getStack('Stack2', synthesizeApplet('test/test-multistack.yaml'))); - test.done(); - }, - - 'expect failure 4'(test: Test) { - test.throws(() => { - synthesizeApplet('test/negative-test4.yaml'); - }, /but it must be in the form/); - test.done(); - }, - - 'expect failure 5'(test: Test) { - test.throws(() => { - synthesizeApplet('test/negative-test5.yaml'); - }, /Cannot find module/); - test.done(); - }, - - 'expect failure 6'(test: Test) { - test.throws(() => { - synthesizeApplet('test/negative-test6.yaml'); - }, /Cannot find applet class/); - test.done(); - }, - - 'expect failure 7'(test: Test) { - test.throws(() => { - synthesizeApplet('test/negative-test7.yaml'); - }, /but it must be in the form/); - test.done(); - }, -}; - -function expectMatch(test: Test, expectedFile: string, stack: any) { - try { - const expected = JSON.parse(fs.readFileSync(expectedFile, { encoding: 'utf-8' })); - test.deepEqual(stack, expected); - } catch (e) { - if (e.code === 'ENOENT') { - // tslint:disable-next-line:no-console - console.log(JSON.stringify(stack, undefined, 2)); - throw new Error(`Make a file ${expectedFile} with the previous contents`); - } - } -} - -function synthesizeApplet(yamlFile: string, direct = false) { - // Can't depend on aws-cdk here, so we're reimplementing cx-api. - // tslint:disable-next-line:no-console - console.log('Writing to ', os.tmpdir()); - - const command = direct ? yamlFile : 'cdk-applet-js'; - const args = direct ? [] : [yamlFile]; - const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'cdk-applet-tests')); - - child_process.execFileSync(command, args, { - env: { - ...process.env, - CDK_OUTDIR: outdir, - PATH: 'bin:' + process.env.PATH - } - }); - - return JSON.parse(fs.readFileSync(path.join(outdir, 'cdk.out'), { encoding: 'utf-8' })); -} - -function getStack(stackName: string, allStacks: any) { - for (const stack of allStacks.stacks) { - if (stack.name === stackName) { - return stripStackMetadata(stack); - } - } - - // tslint:disable-next-line:no-console - console.log(allStacks); - throw new Error('Could not find stack: ' + stackName); -} - -function stripStackMetadata(stack: any) { - for (const key of Object.keys(stack.metadata)) { - if (!stack.metadata[key]) { continue; } - for (const entry of (stack.metadata[key] as any[])) { - if (entry.trace) { entry.trace = ['**REDACTED**']; } - } - } - delete stack.environment; - return stack; -} \ No newline at end of file diff --git a/packages/@aws-cdk/applet-js/test/test.helpers.ts b/packages/@aws-cdk/applet-js/test/test.helpers.ts deleted file mode 100644 index 31d03ce711c46..0000000000000 --- a/packages/@aws-cdk/applet-js/test/test.helpers.ts +++ /dev/null @@ -1,56 +0,0 @@ -import cdk = require('@aws-cdk/cdk'); -import { Test } from 'nodeunit'; -import { extractModuleName, isStackConstructor, parseApplet } from '../lib/applet-helpers'; - -export = { - 'test that refactoring Stack didn\'t break Stack detection'(test: Test) { - test.equals(true, isStackConstructor(cdk.Stack)); - test.done(); - }, - - 'test package name extraction'(test: Test) { - test.equals('my-package', extractModuleName('my-package')); - test.equals('my-package', extractModuleName('my-package@1.0')); - test.equals('@scope/my-package', extractModuleName('@scope/my-package')); - test.equals('@scope/my-package', extractModuleName('@scope/my-package@1.0')); - test.done(); - }, - - 'test applet name extraction'(test: Test) { - test.deepEqual(parseApplet('applet'), { - moduleName: 'applet', - className: 'Applet' - }); - - test.deepEqual(parseApplet('applet:Class'), { - moduleName: 'applet', - className: 'Class' - }); - - test.deepEqual(parseApplet('npm://applet:Class'), { - npmPackage: 'applet', - moduleName: 'applet', - className: 'Class' - }); - - test.deepEqual(parseApplet('npm://applet@1.0:Class'), { - npmPackage: 'applet@1.0', - moduleName: 'applet', - className: 'Class' - }); - - test.deepEqual(parseApplet('npm://applet@1.0'), { - npmPackage: 'applet@1.0', - moduleName: 'applet', - className: 'Applet' - }); - - test.deepEqual(parseApplet('npm://@scope/applet@1.0'), { - npmPackage: '@scope/applet@1.0', - moduleName: '@scope/applet', - className: 'Applet' - }); - - test.done(); - } -}; \ No newline at end of file diff --git a/packages/@aws-cdk/applet-js/test/test1.yaml b/packages/@aws-cdk/applet-js/test/test1.yaml deleted file mode 100644 index 2f4e0a2e08173..0000000000000 --- a/packages/@aws-cdk/applet-js/test/test1.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# applet is loaded from the local ./test-applet.js file -applets: - TestApplet: - type: ./test-applet:TestApplet - properties: - prop1: hello - prop2: 123 diff --git a/packages/@aws-cdk/applet-js/test/test2.yaml b/packages/@aws-cdk/applet-js/test/test2.yaml deleted file mode 100644 index 5478b5db6cb26..0000000000000 --- a/packages/@aws-cdk/applet-js/test/test2.yaml +++ /dev/null @@ -1,13 +0,0 @@ -# applet is loaded from the local ./test-applet.js file -applets: - TestApplet: - type: ./test-applet:TestApplet - properties: - prop1: hello - prop2: 123 - prop3: - - hello - - this - - is - - awesome - - 12345 diff --git a/packages/@aws-cdk/applet-js/test/test3.yaml b/packages/@aws-cdk/applet-js/test/test3.yaml deleted file mode 100755 index c5923c94b4559..0000000000000 --- a/packages/@aws-cdk/applet-js/test/test3.yaml +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env cdk-applet-js -applets: - Applet: - type: ./test-applet - properties: - desc: this should be reflected in the template description diff --git a/packages/@aws-cdk/assert/jest.ts b/packages/@aws-cdk/assert/jest.ts index 0aaafbd3478b9..a785c0058cb14 100644 --- a/packages/@aws-cdk/assert/jest.ts +++ b/packages/@aws-cdk/assert/jest.ts @@ -1,5 +1,5 @@ import { Stack } from "@aws-cdk/cdk"; -import { SynthesizedStack } from "@aws-cdk/cx-api"; +import cxapi = require("@aws-cdk/cx-api"); import { HaveResourceAssertion, ResourcePart } from "./lib/assertions/have-resource"; import { MatchStyle, matchTemplate } from "./lib/assertions/match-template"; import { expect as ourExpect } from './lib/expect'; @@ -23,7 +23,7 @@ declare global { expect.extend({ toMatchTemplate( - actual: SynthesizedStack | Stack, + actual: cxapi.CloudFormationStackArtifact | Stack, template: any, matchStyle?: MatchStyle) { @@ -44,7 +44,7 @@ expect.extend({ }, toHaveResource( - actual: SynthesizedStack | Stack, + actual: cxapi.CloudFormationStackArtifact | Stack, resourceType: string, properties?: any, comparison?: ResourcePart) { @@ -53,7 +53,7 @@ expect.extend({ return assertHaveResource(assertion, actual); }, toHaveResourceLike( - actual: SynthesizedStack | Stack, + actual: cxapi.CloudFormationStackArtifact | Stack, resourceType: string, properties?: any, comparison?: ResourcePart) { @@ -63,7 +63,7 @@ expect.extend({ } }); -function assertHaveResource(assertion: HaveResourceAssertion, actual: SynthesizedStack | Stack) { +function assertHaveResource(assertion: HaveResourceAssertion, actual: cxapi.CloudFormationStackArtifact | Stack) { const inspector = ourExpect(actual); const pass = assertion.assertUsing(inspector); if (pass) { diff --git a/packages/@aws-cdk/assert/lib/expect.ts b/packages/@aws-cdk/assert/lib/expect.ts index 9fca81ff8e2f3..fea0be7a87995 100644 --- a/packages/@aws-cdk/assert/lib/expect.ts +++ b/packages/@aws-cdk/assert/lib/expect.ts @@ -1,44 +1,10 @@ import cdk = require('@aws-cdk/cdk'); -import { ConstructNode, ConstructOrder } from '@aws-cdk/cdk'; import api = require('@aws-cdk/cx-api'); import { StackInspector } from './inspector'; import { SynthUtils } from './synth-utils'; -export function expect(stack: api.SynthesizedStack | cdk.Stack, skipValidation = false): StackInspector { - // Can't use 'instanceof' here, that breaks if we have multiple copies - // of this library. - let sstack: api.SynthesizedStack; - - if (cdk.Stack.isStack(stack)) { - const session = SynthUtils.synthesize(stack, { - skipValidation - }); - - sstack = { - name: stack.name, - template: SynthUtils.templateForStackName(session, stack.name), - metadata: collectStackMetadata(stack.node), - environment: { - name: 'test', - account: 'test', - region: 'test' - } - }; - } else { - sstack = stack; - } - - return new StackInspector(sstack); -} - -function collectStackMetadata(root: ConstructNode): api.StackMetadata { - const result: api.StackMetadata = {}; - for (const construct of root.findAll(ConstructOrder.PreOrder)) { - const path = `/${root.id}/${construct.node.path}`; - for (const entry of construct.node.metadata) { - result[path] = result[path] || []; - result[path].push(root.resolve(entry)); - } - } - return result; +export function expect(stack: api.CloudFormationStackArtifact | cdk.Stack, skipValidation = false): StackInspector { + // if this is already a synthesized stack, then just inspect it. + const artifact = stack instanceof api.CloudFormationStackArtifact ? stack : SynthUtils.synthesize(stack, { skipValidation }); + return new StackInspector(artifact); } diff --git a/packages/@aws-cdk/assert/lib/inspector.ts b/packages/@aws-cdk/assert/lib/inspector.ts index d5e3315bc80ed..ee446ab95b28a 100644 --- a/packages/@aws-cdk/assert/lib/inspector.ts +++ b/packages/@aws-cdk/assert/lib/inspector.ts @@ -27,7 +27,7 @@ export abstract class Inspector { } export class StackInspector extends Inspector { - constructor(public readonly stack: api.SynthesizedStack) { + constructor(public readonly stack: api.CloudFormationStackArtifact) { super(); } @@ -46,7 +46,7 @@ export class StackInspector extends Inspector { } export class StackPathInspector extends Inspector { - constructor(public readonly stack: api.SynthesizedStack, public readonly path: string) { + constructor(public readonly stack: api.CloudFormationStackArtifact, public readonly path: string) { super(); } diff --git a/packages/@aws-cdk/assert/lib/synth-utils.ts b/packages/@aws-cdk/assert/lib/synth-utils.ts index 5d6b689d255fa..f8575848ce53a 100644 --- a/packages/@aws-cdk/assert/lib/synth-utils.ts +++ b/packages/@aws-cdk/assert/lib/synth-utils.ts @@ -1,22 +1,29 @@ -import { ISynthesisSession, Stack, SynthesisOptions, Synthesizer } from '@aws-cdk/cdk'; +import { Stack, SynthesisOptions, Synthesizer } from '@aws-cdk/cdk'; +import cxapi = require('@aws-cdk/cx-api'); export class SynthUtils { - public static toCloudFormation(stack: Stack, options: SynthesisOptions = { }): any { - const session = this.synthesize(stack, options); - return this.templateForStackName(session, stack.name); - } - - public static templateForStackName(session: ISynthesisSession, stackName: string): any { - return session.store.readJson(session.getArtifact(stackName).properties!.templateFile); + /** + * Synthesizes the stack and returns a `CloudFormationStackArtifact` which can be inspected. + */ + public static synthesize(stack: Stack, options: SynthesisOptions = { }): cxapi.CloudFormationStackArtifact { + // always synthesize against the root (be it an App or whatever) so all artifacts will be included + const synth = new Synthesizer(); + const assembly = synth.synthesize(stack.node.root, options); + return assembly.getStack(stack.name); } - public static synthesize(stack: Stack, options: SynthesisOptions): ISynthesisSession { - const synth = new Synthesizer(); - return synth.synthesize(stack, options); + /** + * Synthesizes the stack and returns the resulting CloudFormation template. + */ + public static toCloudFormation(stack: Stack, options: SynthesisOptions = { }): any { + return this.synthesize(stack, options).template; } + /** + * @returns Returns a subset of the synthesized CloudFormation template (only specific resource types). + */ public static subset(stack: Stack, options: SubsetOptions): any { - const template = SynthUtils.toCloudFormation(stack); + const template = SynthUtils.synthesize(stack).template; if (template.Resources) { for (const [key, resource] of Object.entries(template.Resources)) { if (options.resourceTypes && !options.resourceTypes.includes((resource as any).Type)) { diff --git a/packages/@aws-cdk/assert/test/test.assertions.ts b/packages/@aws-cdk/assert/test/test.assertions.ts index c59a96114e6e0..755cf07b912fe 100644 --- a/packages/@aws-cdk/assert/test/test.assertions.ts +++ b/packages/@aws-cdk/assert/test/test.assertions.ts @@ -18,8 +18,7 @@ passingExample('expect non-synthesized stack at to have ' const resourceType = 'Test::Resource'; const stack = new cdk.Stack(); new TestResource(stack, 'TestResource', { type: resourceType }); - // '//' because the stack has no name, which leads to an empty path entry here. - expect(stack).at('//TestResource').to(haveType(resourceType)); + expect(stack).at('/TestResource').to(haveType(resourceType)); }); passingExample('expect at *not* to have ', () => { const resourceType = 'Test::Resource'; @@ -205,11 +204,13 @@ function failingExample(title: string, cb: (test: Test) => void) { }; } -function synthesizedStack(fn: (stack: cdk.Stack) => void): cx.SynthesizedStack { +function synthesizedStack(fn: (stack: cdk.Stack) => void): cx.CloudFormationStackArtifact { const app = new cdk.App(); const stack = new cdk.Stack(app, 'TestStack'); fn(stack); - return app.synthesizeStack(stack.name); + + const assembly = app.run(); + return assembly.getStack(stack.name); } interface TestResourceProps extends cdk.CfnResourceProps { diff --git a/packages/@aws-cdk/assert/test/test.have-resource.ts b/packages/@aws-cdk/assert/test/test.have-resource.ts index 888a8a3147b3d..75153eb780ed0 100644 --- a/packages/@aws-cdk/assert/test/test.have-resource.ts +++ b/packages/@aws-cdk/assert/test/test.have-resource.ts @@ -1,4 +1,7 @@ +import cxapi = require('@aws-cdk/cx-api'); +import { writeFileSync } from 'fs'; import { Test } from 'nodeunit'; +import { join } from 'path'; import { expect, haveResource } from '../lib/index'; export = { @@ -89,15 +92,16 @@ export = { }, }; -function mkStack(template: any) { - return { - name: 'test', - template, - metadata: {}, - environment: { - name: 'test', - account: 'test', - region: 'test' +function mkStack(template: any): cxapi.CloudFormationStackArtifact { + const assembly = new cxapi.CloudAssemblyBuilder(); + assembly.addArtifact('test', { + type: cxapi.ArtifactType.AwsCloudFormationStack, + environment: cxapi.EnvironmentUtils.format('123456789', 'us-west-2'), + properties: { + templateFile: 'template.json' } - }; + }); + + writeFileSync(join(assembly.outdir, 'template.json'), JSON.stringify(template)); + return assembly.build().getStack('test'); } diff --git a/packages/@aws-cdk/assert/test/test.synth-utils.ts b/packages/@aws-cdk/assert/test/test.synth-utils.ts new file mode 100644 index 0000000000000..473ac58c5d32c --- /dev/null +++ b/packages/@aws-cdk/assert/test/test.synth-utils.ts @@ -0,0 +1,18 @@ +import { App, Stack } from '@aws-cdk/cdk'; +import { Test } from 'nodeunit'; +import { SynthUtils } from '../lib'; + +export = { + 'SynthUtils.synthesize() is always executed against the root of the tree'(test: Test) { + // GIVEN + const root = new App(); + const stack1 = new Stack(root, 'stack1'); + const stack2 = new Stack(root, 'stack2'); + stack2.addDependency(stack1); + + // THEN + // this would have failed if we didn't synthesize at the root because 'stack1' would not be emitted + SynthUtils.synthesize(stack2); + test.done(); + } +}; \ No newline at end of file diff --git a/packages/@aws-cdk/assets-docker/test/test.image-asset.ts b/packages/@aws-cdk/assets-docker/test/test.image-asset.ts index 869b596bf1421..e77a9fc1083c9 100644 --- a/packages/@aws-cdk/assets-docker/test/test.image-asset.ts +++ b/packages/@aws-cdk/assets-docker/test/test.image-asset.ts @@ -1,10 +1,8 @@ import { expect, haveResource, SynthUtils } from '@aws-cdk/assert'; import iam = require('@aws-cdk/aws-iam'); import cdk = require('@aws-cdk/cdk'); -import cxapi = require('@aws-cdk/cx-api'); import fs = require('fs'); import { Test } from 'nodeunit'; -import os = require('os'); import path = require('path'); import { DockerImageAsset } from '../lib'; @@ -21,7 +19,7 @@ export = { }); // THEN - const template = SynthUtils.toCloudFormation(stack); + const template = SynthUtils.synthesize(stack).template; test.deepEqual(template.Parameters.ImageImageName5E684353, { Type: 'String', @@ -167,27 +165,17 @@ export = { }, 'docker directory is staged if asset staging is enabled'(test: Test) { - const workdir = mkdtempSync(); - process.chdir(workdir); - - const app = new cdk.App({ - context: { [cxapi.ASSET_STAGING_DIR_CONTEXT]: '.stage-me' } - }); - + const app = new cdk.App(); const stack = new cdk.Stack(app, 'stack'); new DockerImageAsset(stack, 'MyAsset', { directory: path.join(__dirname, 'demo-image') }); - app.run(); + const session = app.run(); - test.ok(fs.existsSync('.stage-me/1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439c/Dockerfile')); - test.ok(fs.existsSync('.stage-me/1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439c/index.py')); + test.ok(fs.existsSync(path.join(session.directory, 'asset.1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439c/Dockerfile'))); + test.ok(fs.existsSync(path.join(session.directory, 'asset.1a17a141505ac69144931fe263d130f4612251caa4bbbdaf68a44ed0f405439c/index.py'))); test.done(); } }; - -function mkdtempSync() { - return fs.mkdtempSync(path.join(os.tmpdir(), 'test.assets')); -} diff --git a/packages/@aws-cdk/assets/lib/staging.ts b/packages/@aws-cdk/assets/lib/staging.ts index 05e465acb4227..173fe73374fa7 100644 --- a/packages/@aws-cdk/assets/lib/staging.ts +++ b/packages/@aws-cdk/assets/lib/staging.ts @@ -1,4 +1,4 @@ -import { Construct, Token } from '@aws-cdk/cdk'; +import { Construct } from '@aws-cdk/cdk'; import cxapi = require('@aws-cdk/cx-api'); import fs = require('fs'); import path = require('path'); @@ -48,13 +48,7 @@ export class Staging extends Construct { private readonly copyOptions: CopyOptions; - /** - * The asset path after "prepare" is called. - * - * If staging is disabled, this will just be the original path. - * If staging is enabled it will be the staged path. - */ - private _preparedAssetPath?: string; + private readonly relativePath?: string; constructor(scope: Construct, id: string, props: StagingProps) { super(scope, id); @@ -62,23 +56,22 @@ export class Staging extends Construct { this.sourcePath = props.sourcePath; this.copyOptions = props; this.sourceHash = fingerprint(this.sourcePath, props); - this.stagedPath = new Token(() => this._preparedAssetPath).toString(); - } - protected prepare() { - const stagingDir = this.node.getContext(cxapi.ASSET_STAGING_DIR_CONTEXT); - if (!stagingDir) { - this._preparedAssetPath = this.sourcePath; - return; + const stagingDisabled = this.node.getContext(cxapi.DISABLE_ASSET_STAGING_CONTEXT); + if (stagingDisabled) { + this.stagedPath = this.sourcePath; + } else { + this.relativePath = `asset.` + this.sourceHash + path.extname(this.sourcePath); + this.stagedPath = this.relativePath; // always relative to outdir } + } - if (!fs.existsSync(stagingDir)) { - fs.mkdirSync(stagingDir); + protected synthesize(session: cxapi.CloudAssemblyBuilder) { + if (!this.relativePath) { + return; } - const targetPath = path.join(stagingDir, this.sourceHash + path.extname(this.sourcePath)); - - this._preparedAssetPath = targetPath; + const targetPath = path.join(session.outdir, this.relativePath); // asset already staged if (fs.existsSync(targetPath)) { diff --git a/packages/@aws-cdk/assets/test/test.asset.ts b/packages/@aws-cdk/assets/test/test.asset.ts index bb92463bd7454..53a3a42020d5c 100644 --- a/packages/@aws-cdk/assets/test/test.asset.ts +++ b/packages/@aws-cdk/assets/test/test.asset.ts @@ -13,7 +13,12 @@ const SAMPLE_ASSET_DIR = path.join(__dirname, 'sample-asset-directory'); export = { 'simple use case'(test: Test) { - const stack = new cdk.Stack(); + const app = new cdk.App({ + context: { + [cxapi.DISABLE_ASSET_STAGING_CONTEXT]: 'true' + } + }); + const stack = new cdk.Stack(app, 'MyStack'); const asset = new ZipDirectoryAsset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR }); @@ -24,11 +29,11 @@ export = { test.ok(entry, 'found metadata entry'); // verify that now the template contains parameters for this asset - const template = SynthUtils.toCloudFormation(stack); + const session = app.run(); test.deepEqual(stack.node.resolve(entry!.data), { path: SAMPLE_ASSET_DIR, - id: 'MyAsset', + id: 'MyStackMyAssetBDDF29E3', packaging: 'zip', sourceHash: '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', s3BucketParameter: 'MyAssetS3Bucket68C9B344', @@ -36,6 +41,7 @@ export = { artifactHashParameter: 'MyAssetArtifactHashF518BDDE', }); + const template = JSON.parse(fs.readFileSync(path.join(session.directory, 'MyStack.template.json'), 'utf-8')); test.equal(template.Parameters.MyAssetS3Bucket68C9B344.Type, 'String'); test.equal(template.Parameters.MyAssetS3VersionKey68E1A45D.Type, 'String'); @@ -51,10 +57,9 @@ export = { path: dirPath }); - const synth = app.synthesizeStack(stack.name); - + const synth = app.run().getStack(stack.name); test.deepEqual(synth.metadata['/my-stack/MyAsset'][0].data, { - path: dirPath, + path: 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', id: "mystackMyAssetD6B1B593", packaging: "zip", sourceHash: '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', @@ -74,10 +79,10 @@ export = { test.ok(entry, 'found metadata entry'); // synthesize first so "prepare" is called - const template = SynthUtils.toCloudFormation(stack); + const template = SynthUtils.synthesize(stack).template; test.deepEqual(stack.node.resolve(entry!.data), { - path: filePath, + path: 'asset.78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197.txt', packaging: 'file', id: 'MyAsset', sourceHash: '78add9eaf468dfa2191da44a7da92a21baba4c686cf6053d772556768ef21197', @@ -195,7 +200,7 @@ export = { // THEN expect(stack).to(haveResource('My::Resource::Type', { Metadata: { - "aws:asset:path": location, + "aws:asset:path": 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2', "aws:asset:property": "PropName" } }, ResourcePart.CompleteDefinition)); @@ -225,14 +230,12 @@ export = { 'staging': { - 'copy file assets under .assets/${fingerprint}.ext'(test: Test) { + 'copy file assets under /${fingerprint}.ext'(test: Test) { const tempdir = mkdtempSync(); process.chdir(tempdir); // change current directory to somewhere in /tmp // GIVEN - const app = new App({ - context: { [cxapi.ASSET_STAGING_DIR_CONTEXT]: '.assets' } - }); + const app = new App({ outdir: tempdir }); const stack = new Stack(app, 'stack'); // WHEN @@ -246,9 +249,8 @@ export = { // THEN app.run(); - test.ok(fs.existsSync(path.join(tempdir, '.assets'))); - test.ok(fs.existsSync(path.join(tempdir, '.assets', 'a7a79cdf84b802ea8b198059ff899cffc095a1b9606e919f98e05bf80779756b.zip'))); - fs.readdirSync(path.join(tempdir, '.assets')); + test.ok(fs.existsSync(tempdir)); + test.ok(fs.existsSync(path.join(tempdir, 'asset.a7a79cdf84b802ea8b198059ff899cffc095a1b9606e919f98e05bf80779756b.zip'))); test.done(); }, @@ -257,9 +259,7 @@ export = { process.chdir(tempdir); // change current directory to somewhere in /tmp // GIVEN - const app = new App({ - context: { [cxapi.ASSET_STAGING_DIR_CONTEXT]: '.assets' } - }); + const app = new App({ outdir: tempdir }); const stack = new Stack(app, 'stack'); // WHEN @@ -269,11 +269,11 @@ export = { // THEN app.run(); - test.ok(fs.existsSync(path.join(tempdir, '.assets'))); - const hash = '6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2'; - test.ok(fs.existsSync(path.join(tempdir, '.assets', hash, 'sample-asset-file.txt'))); - test.ok(fs.existsSync(path.join(tempdir, '.assets', hash, 'sample-jar-asset.jar'))); - fs.readdirSync(path.join(tempdir, '.assets')); + test.ok(fs.existsSync(tempdir)); + const hash = 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2'; + test.ok(fs.existsSync(path.join(tempdir, hash, 'sample-asset-file.txt'))); + test.ok(fs.existsSync(path.join(tempdir, hash, 'sample-jar-asset.jar'))); + fs.readdirSync(tempdir); test.done(); }, @@ -284,8 +284,8 @@ export = { const staging = '.my-awesome-staging-directory'; const app = new App({ + outdir: staging, context: { - [cxapi.ASSET_STAGING_DIR_CONTEXT]: staging, [cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT]: 'true', } }); @@ -298,22 +298,21 @@ export = { // WHEN asset.addResourceMetadata(resource, 'PropName'); - const session = app.run(); - const template = SynthUtils.templateForStackName(session, stack.name); - + const template = SynthUtils.synthesize(stack).template; test.deepEqual(template.Resources.MyResource.Metadata, { - "aws:asset:path": `.my-awesome-staging-directory/6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2`, + "aws:asset:path": `asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2`, "aws:asset:property": "PropName" }); test.done(); }, - 'if staging directory is absolute, asset path is absolute'(test: Test) { + 'if staging is disabled, asset path is absolute'(test: Test) { // GIVEN const staging = path.resolve(mkdtempSync()); const app = new App({ + outdir: staging, context: { - [cxapi.ASSET_STAGING_DIR_CONTEXT]: staging, + [cxapi.DISABLE_ASSET_STAGING_CONTEXT]: 'true', [cxapi.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT]: 'true', } }); @@ -326,11 +325,9 @@ export = { // WHEN asset.addResourceMetadata(resource, 'PropName'); - const session = app.run(); - const template = SynthUtils.templateForStackName(session, stack.name); - + const template = SynthUtils.synthesize(stack).template; test.deepEqual(template.Resources.MyResource.Metadata, { - "aws:asset:path": `${staging}/6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2`, + "aws:asset:path": SAMPLE_ASSET_DIR, "aws:asset:property": "PropName" }); test.done(); @@ -338,27 +335,16 @@ export = { 'cdk metadata points to staged asset'(test: Test) { // GIVEN - const tempdir = mkdtempSync(); - process.chdir(tempdir); // change current directory to somewhere in /tmp - - const staging = '.stageme'; - - const app = new App({ - context: { - [cxapi.ASSET_STAGING_DIR_CONTEXT]: staging, - } - }); - + const app = new App(); const stack = new Stack(app, 'stack'); - new ZipDirectoryAsset(stack, 'MyAsset', { path: SAMPLE_ASSET_DIR }); // WHEN const session = app.run(); - const artifact = session.getArtifact(stack.name); + const artifact = session.getStack(stack.name); - const md = Object.values(artifact.metadata || {})[0][0].data; - test.deepEqual(md.path, '.stageme/6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2'); + const md = Object.values(artifact.metadata)[0][0].data; + test.deepEqual(md.path, 'asset.6b84b87243a4a01c592d78e1fd3855c4bfef39328cd0a450cc97e81717fea2a2'); test.done(); } diff --git a/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts b/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts index 13087303a8ba9..3f138b47c2369 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.deployment.ts @@ -153,7 +153,7 @@ export = { function synthesize() { stack.node.prepareTree(); - return SynthUtils.toCloudFormation(stack); + return SynthUtils.synthesize(stack).template; } }, diff --git a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts index 2b8fbe011f7d6..31b1714e697b2 100644 --- a/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts +++ b/packages/@aws-cdk/aws-apigateway/test/test.restapi.ts @@ -131,7 +131,7 @@ export = { api.root.addResource('bar').addResource('goo'); // THEN - test.throws(() => app.synthesizeStack(stack.name), /The REST API doesn't contain any methods/); + test.throws(() => app.run(), /The REST API doesn't contain any methods/); test.done(); }, diff --git a/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts b/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts index 1455fa0e84689..425cf7bb29e2a 100644 --- a/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts +++ b/packages/@aws-cdk/aws-applicationautoscaling/test/test.step-scaling-policy.ts @@ -168,7 +168,7 @@ function setupStepScaling(intervals: appscaling.ScalingInterval[]) { scalingSteps: intervals }); - return new ScalingStackTemplate(SynthUtils.toCloudFormation(stack)); + return new ScalingStackTemplate(SynthUtils.synthesize(stack).template); } class ScalingStackTemplate { diff --git a/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts b/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts index 605cc9f721033..ade48de2167f9 100644 --- a/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts +++ b/packages/@aws-cdk/aws-cloudtrail/test/test.cloudtrail.ts @@ -67,7 +67,7 @@ export = { expect(stack).to(haveResource("AWS::S3::Bucket")); expect(stack).to(haveResource("AWS::S3::BucketPolicy", ExpectedBucketPolicyProperties)); expect(stack).to(not(haveResource("AWS::Logs::LogGroup"))); - const trail: any = SynthUtils.toCloudFormation(stack).Resources.MyAmazingCloudTrail54516E8D; + const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; test.deepEqual(trail.DependsOn, ['MyAmazingCloudTrailS3Policy39C120B0']); test.done(); }, @@ -98,7 +98,7 @@ export = { PolicyName: logsRolePolicyName, Roles: [{ Ref: 'MyAmazingCloudTrailLogsRoleF2CCF977' }], })); - const trail: any = SynthUtils.toCloudFormation(stack).Resources.MyAmazingCloudTrail54516E8D; + const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; test.deepEqual(trail.DependsOn, [logsRolePolicyName, logsRoleName, 'MyAmazingCloudTrailS3Policy39C120B0']); test.done(); }, @@ -117,7 +117,7 @@ export = { expect(stack).to(haveResource("AWS::Logs::LogGroup", { RetentionInDays: 7 })); - const trail: any = SynthUtils.toCloudFormation(stack).Resources.MyAmazingCloudTrail54516E8D; + const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; test.deepEqual(trail.DependsOn, [logsRolePolicyName, logsRoleName, 'MyAmazingCloudTrailS3Policy39C120B0']); test.done(); }, @@ -135,7 +135,7 @@ export = { expect(stack).to(not(haveResource("AWS::Logs::LogGroup"))); expect(stack).to(not(haveResource("AWS::IAM::Role"))); - const trail: any = SynthUtils.toCloudFormation(stack).Resources.MyAmazingCloudTrail54516E8D; + const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; test.equals(trail.Properties.EventSelectors.length, 1); const selector = trail.Properties.EventSelectors[0]; test.equals(selector.ReadWriteType, null, "Expected selector read write type to be undefined"); @@ -161,7 +161,7 @@ export = { expect(stack).to(not(haveResource("AWS::Logs::LogGroup"))); expect(stack).to(not(haveResource("AWS::IAM::Role"))); - const trail: any = SynthUtils.toCloudFormation(stack).Resources.MyAmazingCloudTrail54516E8D; + const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; test.equals(trail.Properties.EventSelectors.length, 1); const selector = trail.Properties.EventSelectors[0]; test.equals(selector.ReadWriteType, "ReadOnly", "Expected selector read write type to be Read"); @@ -180,7 +180,7 @@ export = { new Trail(stack, 'MyAmazingCloudTrail', { managementEvents: ReadWriteType.WriteOnly }); - const trail: any = SynthUtils.toCloudFormation(stack).Resources.MyAmazingCloudTrail54516E8D; + const trail: any = SynthUtils.synthesize(stack).template.Resources.MyAmazingCloudTrail54516E8D; test.equals(trail.Properties.EventSelectors.length, 1); const selector = trail.Properties.EventSelectors[0]; test.equals(selector.ReadWriteType, "WriteOnly", "Expected selector read write type to be All"); diff --git a/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts b/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts index f691403635f84..711043a8e2ab5 100644 --- a/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts +++ b/packages/@aws-cdk/aws-codecommit/test/test.codecommit.ts @@ -1,50 +1,49 @@ -import { App, Stack } from '@aws-cdk/cdk'; +import { expect } from '@aws-cdk/assert'; +import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { Repository, RepositoryProps } from '../lib'; export = { 'CodeCommit Repositories': { 'add an SNS trigger to repository'(test: Test) { - const app = new TestApp(); + const stack = new Stack(); const props: RepositoryProps = { - repositoryName: 'MyRepository' + repositoryName: 'MyRepository' }; const snsArn = 'arn:aws:sns:*:123456789012:my_topic'; - new Repository(app.stack, 'MyRepository', props).notify(snsArn); - const template = app.synthesizeTemplate(); + new Repository(stack, 'MyRepository', props).notify(snsArn); - test.deepEqual(template, { + expect(stack).toMatch({ Resources: { MyRepository4C4BD5FC: { Type: "AWS::CodeCommit::Repository", Properties: { - RepositoryName: "MyRepository", - Triggers: [ - { - Events: [ - "all" - ], - DestinationArn: "arn:aws:sns:*:123456789012:my_topic", - Name: "MyStack/MyRepository/arn:aws:sns:*:123456789012:my_topic" - } - ] + RepositoryName: "MyRepository", + Triggers: [ + { + Events: [ + "all" + ], + DestinationArn: "arn:aws:sns:*:123456789012:my_topic", + Name: "MyRepository/arn:aws:sns:*:123456789012:my_topic" + } + ] } } - } + } }); test.done(); }, 'fails when triggers have duplicate names'(test: Test) { - const app = new TestApp(); + const stack = new Stack(); const props = { repositoryName: 'MyRepository' }; - const myRepository = new Repository(app.stack, 'MyRepository', props) - .notify('myTrigger'); + const myRepository = new Repository(stack, 'MyRepository', props).notify('myTrigger'); test.throws(() => myRepository.notify('myTrigger')); @@ -75,7 +74,7 @@ export = { // THEN test.deepEqual(repo.node.resolve(repo.repositoryArn), { - 'Fn::Join': [ '', [ + 'Fn::Join': ['', [ 'arn:', { Ref: 'AWS::Partition' }, ':codecommit:', @@ -91,13 +90,3 @@ export = { }, }, }; - -class TestApp { - private readonly app = new App(); - // tslint:disable-next-line:member-ordering - public readonly stack: Stack = new Stack(this.app, 'MyStack'); - - public synthesizeTemplate() { - return this.app.synthesizeStack(this.stack.name).template; - } -} diff --git a/packages/@aws-cdk/aws-eks/test/test.cluster.ts b/packages/@aws-cdk/aws-eks/test/test.cluster.ts index 59ada983faa02..5a212d571a77d 100644 --- a/packages/@aws-cdk/aws-eks/test/test.cluster.ts +++ b/packages/@aws-cdk/aws-eks/test/test.cluster.ts @@ -8,7 +8,7 @@ import eks = require('../lib'); export = { 'a default cluster spans all subnets'(test: Test) { // GIVEN - const [stack, vpc] = testFixture(); + const { stack, vpc } = testFixture(); // WHEN new eks.Cluster(stack, 'Cluster', { vpc }); @@ -32,7 +32,7 @@ export = { 'creating a cluster tags the private VPC subnets'(test: Test) { // GIVEN - const [stack, vpc] = testFixture(); + const { stack, vpc } = testFixture(); // WHEN new eks.Cluster(stack, 'Cluster', { vpc }); @@ -40,7 +40,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::EC2::Subnet', { Tags: [ - { Key: "Name", Value: "VPC/PrivateSubnet1" }, + { Key: "Name", Value: "Stack/VPC/PrivateSubnet1" }, { Key: "aws-cdk:subnet-name", Value: "Private" }, { Key: "aws-cdk:subnet-type", Value: "Private" }, { Key: "kubernetes.io/role/internal-elb", Value: "1" } @@ -52,7 +52,7 @@ export = { 'adding capacity creates an ASG with tags'(test: Test) { // GIVEN - const [stack, vpc] = testFixture(); + const { stack, vpc } = testFixture(); const cluster = new eks.Cluster(stack, 'Cluster', { vpc }); // WHEN @@ -66,7 +66,7 @@ export = { { Key: "Name", PropagateAtLaunch: true, - Value: "Cluster/Default" + Value: "Stack/Cluster/Default" }, { Key: { "Fn::Join": [ "", [ "kubernetes.io/cluster/", { Ref: "ClusterEB0386A7" } ] ] }, @@ -81,7 +81,7 @@ export = { 'adding capacity correctly deduces maxPods and adds userdata'(test: Test) { // GIVEN - const [stack, vpc] = testFixture(); + const { stack, vpc } = testFixture(); const cluster = new eks.Cluster(stack, 'Cluster', { vpc }); // WHEN @@ -110,8 +110,8 @@ export = { 'exercise export/import'(test: Test) { // GIVEN - const [stack1, vpc] = testFixture(); - const stack2 = new cdk.Stack(undefined, 'stack2', { env: { region: 'us-east-1' } }); + const { stack: stack1, vpc, app } = testFixture(); + const stack2 = new cdk.Stack(app, 'stack2', { env: { region: 'us-east-1' } }); const cluster = new eks.Cluster(stack1, 'Cluster', { vpc }); // WHEN @@ -141,9 +141,10 @@ export = { }, }; -function testFixture(): [cdk.Stack, ec2.Vpc] { - const stack = new cdk.Stack(undefined, 'Stack', { env: { region: 'us-east-1' }}); +function testFixture() { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack', { env: { region: 'us-east-1' }}); const vpc = new ec2.Vpc(stack, 'VPC'); - return [stack, vpc]; + return { stack, vpc, app }; } diff --git a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts index 20ddb56cff47d..98b74d3a93875 100644 --- a/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts +++ b/packages/@aws-cdk/aws-elasticloadbalancingv2/test/alb/test.security-groups.ts @@ -101,8 +101,8 @@ export = { 'SG peering works on exported/imported load balancer'(test: Test) { // GIVEN - const fixture = new TestFixture(); - const stack2 = new cdk.Stack(); + const fixture = new TestFixture(false); + const stack2 = new cdk.Stack(fixture.app, 'stack2'); const vpc2 = new ec2.Vpc(stack2, 'VPC'); const group = new elbv2.ApplicationTargetGroup(stack2, 'TargetGroup', { // We're assuming the 2nd VPC is peered to the 1st, or something. @@ -128,7 +128,7 @@ export = { 'SG peering works on exported/imported listener'(test: Test) { // GIVEN const fixture = new TestFixture(); - const stack2 = new cdk.Stack(); + const stack2 = new cdk.Stack(fixture.app, 'stack2'); const vpc2 = new ec2.Vpc(stack2, 'VPC'); const group = new elbv2.ApplicationTargetGroup(stack2, 'TargetGroup', { // We're assuming the 2nd VPC is peered to the 1st, or something. @@ -136,6 +136,7 @@ export = { port: 8008, targets: [new FakeSelfRegisteringTarget(stack2, 'Target', vpc2)], }); + fixture.listener.addTargets('default', { port: 80 }); // WHEN const listener2 = elbv2.ApplicationListener.fromApplicationListenerAttributes(stack2, 'YetAnotherListener', { @@ -237,17 +238,23 @@ function expectSGRules(stack: cdk.Stack, lbGroup: any) { } class TestFixture { + public readonly app: cdk.App; public readonly stack: cdk.Stack; public readonly vpc: ec2.Vpc; public readonly lb: elbv2.ApplicationLoadBalancer; public readonly listener: elbv2.ApplicationListener; - constructor() { - this.stack = new cdk.Stack(); + constructor(createListener?: boolean) { + this.app = new cdk.App(); + this.stack = new cdk.Stack(this.app, 'Stack'); this.vpc = new ec2.Vpc(this.stack, 'VPC', { maxAZs: 2 }); this.lb = new elbv2.ApplicationLoadBalancer(this.stack, 'LB', { vpc: this.vpc }); - this.listener = this.lb.addListener('Listener', { port: 80, open: false }); + + createListener = createListener === undefined ? true : createListener; + if (createListener) { + this.listener = this.lb.addListener('Listener', { port: 80, open: false }); + } } } diff --git a/packages/@aws-cdk/aws-events/test/test.rule.ts b/packages/@aws-cdk/aws-events/test/test.rule.ts index 352bb814d98c5..a03ec7c1ab1ba 100644 --- a/packages/@aws-cdk/aws-events/test/test.rule.ts +++ b/packages/@aws-cdk/aws-events/test/test.rule.ts @@ -97,7 +97,7 @@ export = { const app = new cdk.App(); const stack = new cdk.Stack(app, 'MyStack'); new Rule(stack, 'Rule'); - test.throws(() => app.synthesizeStack(stack.name), /Either 'eventPattern' or 'scheduleExpression' must be defined/); + test.throws(() => app.run(), /Either 'eventPattern' or 'scheduleExpression' must be defined/); test.done(); }, diff --git a/packages/@aws-cdk/aws-glue/test/test.table.ts b/packages/@aws-cdk/aws-glue/test/test.table.ts index a0c4b7c430797..a2a73d94bb69c 100644 --- a/packages/@aws-cdk/aws-glue/test/test.table.ts +++ b/packages/@aws-cdk/aws-glue/test/test.table.ts @@ -8,12 +8,13 @@ import glue = require('../lib'); export = { 'unpartitioned JSON table'(test: Test) { - const dbStack = new cdk.Stack(); + const app = new cdk.App(); + const dbStack = new cdk.Stack(app, 'db'); const database = new glue.Database(dbStack, 'Database', { databaseName: 'database' }); - const tableStack = new cdk.Stack(); + const tableStack = new cdk.Stack(app, 'table'); const table = new glue.Table(tableStack, 'Table', { database, tableName: 'table', @@ -35,7 +36,7 @@ export = { Ref: "AWS::AccountId" }, DatabaseName: { - "Fn::ImportValue": "Stack:ExportsOutputRefDatabaseB269D8BB88F4B1C4" + "Fn::ImportValue": "db:ExportsOutputRefDatabaseB269D8BB88F4B1C4" }, TableInput: { Name: "table", @@ -78,12 +79,13 @@ export = { }, 'partitioned JSON table'(test: Test) { - const dbStack = new cdk.Stack(); + const app = new cdk.App(); + const dbStack = new cdk.Stack(app, 'db'); const database = new glue.Database(dbStack, 'Database', { databaseName: 'database' }); - const tableStack = new cdk.Stack(); + const tableStack = new cdk.Stack(app, 'table'); const table = new glue.Table(tableStack, 'Table', { database, tableName: 'table', @@ -106,7 +108,7 @@ export = { Ref: "AWS::AccountId" }, DatabaseName: { - "Fn::ImportValue": "Stack:ExportsOutputRefDatabaseB269D8BB88F4B1C4" + "Fn::ImportValue": "db:ExportsOutputRefDatabaseB269D8BB88F4B1C4" }, TableInput: { Name: "table", @@ -980,8 +982,9 @@ export = { }, 'explicit s3 bucket and prefix'(test: Test) { - const dbStack = new cdk.Stack(); - const stack = new cdk.Stack(); + const app = new cdk.App(); + const dbStack = new cdk.Stack(app, 'db'); + const stack = new cdk.Stack(app, 'app'); const bucket = new s3.Bucket(stack, 'ExplicitBucket'); const database = new glue.Database(dbStack, 'Database', { databaseName: 'database' @@ -1004,7 +1007,7 @@ export = { Ref: "AWS::AccountId" }, DatabaseName: { - "Fn::ImportValue": "Stack:ExportsOutputRefDatabaseB269D8BB88F4B1C4" + "Fn::ImportValue": "db:ExportsOutputRefDatabaseB269D8BB88F4B1C4" }, TableInput: { Description: "table generated by CDK", diff --git a/packages/@aws-cdk/aws-iam/test/test.auto-cross-stack-refs.ts b/packages/@aws-cdk/aws-iam/test/test.auto-cross-stack-refs.ts index 705e98193ed07..4697f93d81167 100644 --- a/packages/@aws-cdk/aws-iam/test/test.auto-cross-stack-refs.ts +++ b/packages/@aws-cdk/aws-iam/test/test.auto-cross-stack-refs.ts @@ -1,4 +1,4 @@ -import { expect } from '@aws-cdk/assert'; +import { expect, SynthUtils } from '@aws-cdk/assert'; import cdk = require('@aws-cdk/cdk'); import { Test } from 'nodeunit'; import iam = require('../lib'); @@ -6,8 +6,9 @@ import iam = require('../lib'); export = { 'automatic exports are created when attributes are referneced across stacks'(test: Test) { // GIVEN - const stackWithUser = new cdk.Stack(); - const stackWithGroup = new cdk.Stack(); + const app = new cdk.App(); + const stackWithUser = new cdk.Stack(app, 'stack1'); + const stackWithGroup = new cdk.Stack(app, 'stack2'); const user = new iam.User(stackWithUser, 'User'); const group = new iam.Group(stackWithGroup, 'Group'); @@ -29,7 +30,7 @@ export = { User00B015A1: { Type: "AWS::IAM::User", Properties: { - Groups: [ { "Fn::ImportValue": "Stack:ExportsOutputRefGroupC77FDACD8CF7DD5B" } ] + Groups: [ { "Fn::ImportValue": "stack2:ExportsOutputRefGroupC77FDACD8CF7DD5B" } ] } } } @@ -38,7 +39,7 @@ export = { Outputs: { ExportsOutputRefGroupC77FDACD8CF7DD5B: { Value: { Ref: "GroupC77FDACD" }, - Export: { Name: "Stack:ExportsOutputRefGroupC77FDACD8CF7DD5B" } + Export: { Name: "stack2:ExportsOutputRefGroupC77FDACD8CF7DD5B" } } }, Resources: { @@ -48,5 +49,18 @@ export = { } }); test.done(); - } + }, + + 'cannot reference tokens across apps'(test: Test) { + // GIVEN + const stack1 = new cdk.Stack(); + const stack2 = new cdk.Stack(); + const user = new iam.User(stack1, 'User'); + const group = new iam.Group(stack2, 'Group'); + group.addUser(user); + + // THEN + test.throws(() => SynthUtils.synthesize(stack1), /Cannot reference across apps/); + test.done(); + }, }; \ No newline at end of file diff --git a/packages/@aws-cdk/aws-iam/test/test.group.ts b/packages/@aws-cdk/aws-iam/test/test.group.ts index 4249af4b1b591..d0ff150a69809 100644 --- a/packages/@aws-cdk/aws-iam/test/test.group.ts +++ b/packages/@aws-cdk/aws-iam/test/test.group.ts @@ -1,3 +1,4 @@ +import { expect } from '@aws-cdk/assert'; import { App, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { Group, User } from '../lib'; @@ -7,9 +8,11 @@ export = { const app = new App(); const stack = new Stack(app, 'MyStack'); new Group(stack, 'MyGroup'); - test.deepEqual(app.synthesizeStack(stack.name).template, { + + expect(stack).toMatch({ Resources: { MyGroupCBA54B1B: { Type: 'AWS::IAM::Group' } } }); + test.done(); }, @@ -22,7 +25,7 @@ export = { user1.addToGroup(group); group.addUser(user2); - test.deepEqual(app.synthesizeStack(stack.name).template, { Resources: + expect(stack).toMatch({ Resources: { MyGroupCBA54B1B: { Type: 'AWS::IAM::Group' }, User1E278A736: { Type: 'AWS::IAM::User', diff --git a/packages/@aws-cdk/aws-iam/test/test.policy.ts b/packages/@aws-cdk/aws-iam/test/test.policy.ts index 344a33198fed2..3d990a834decf 100644 --- a/packages/@aws-cdk/aws-iam/test/test.policy.ts +++ b/packages/@aws-cdk/aws-iam/test/test.policy.ts @@ -1,3 +1,4 @@ +import { expect } from '@aws-cdk/assert'; import { App, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { Group, Policy, PolicyStatement, Role, ServicePrincipal, User } from '../lib'; @@ -9,7 +10,7 @@ export = { const stack = new Stack(app, 'MyStack'); new Policy(stack, 'MyPolicy'); - test.throws(() => app.synthesizeStack(stack.name), /Policy is empty/); + test.throws(() => app.run(), /Policy is empty/); test.done(); }, @@ -24,7 +25,7 @@ export = { const group = new Group(stack, 'MyGroup'); group.attachInlinePolicy(policy); - test.deepEqual(app.synthesizeStack(stack.name).template, { Resources: + expect(stack).toMatch({ Resources: { MyPolicy39D66CF6: { Type: 'AWS::IAM::Policy', Properties: @@ -50,7 +51,7 @@ export = { const user = new User(stack, 'MyUser'); user.attachInlinePolicy(policy); - test.deepEqual(app.synthesizeStack(stack.name).template, { Resources: + expect(stack).toMatch({ Resources: { MyPolicy39D66CF6: { Type: 'AWS::IAM::Policy', Properties: @@ -84,7 +85,7 @@ export = { statements: [ new PolicyStatement().addResource('*').addAction('dynamodb:PutItem') ], }); - test.deepEqual(app.synthesizeStack(stack.name).template, { Resources: + expect(stack).toMatch({ Resources: { User1E278A736: { Type: 'AWS::IAM::User' }, Group1BEBD4686: { Type: 'AWS::IAM::Group' }, Role13A5C70C1: @@ -121,7 +122,7 @@ export = { p.attachToUser(user); p.attachToUser(user); - test.deepEqual(app.synthesizeStack(stack.name).template, { Resources: + expect(stack).toMatch({ Resources: { MyPolicy39D66CF6: { Type: 'AWS::IAM::Policy', Properties: @@ -149,7 +150,7 @@ export = { p.attachToRole(new Role(stack, 'Role1', { assumedBy: new ServicePrincipal('test.service') })); p.addStatement(new PolicyStatement().addResource('*').addAction('dynamodb:GetItem')); - test.deepEqual(app.synthesizeStack(stack.name).template, { Resources: + expect(stack).toMatch({ Resources: { MyTestPolicy316BDB50: { Type: 'AWS::IAM::Policy', Properties: @@ -191,7 +192,7 @@ export = { policy.addStatement(new PolicyStatement().addResource('*').addAction('*')); - test.deepEqual(app.synthesizeStack(stack.name).template, { Resources: + expect(stack).toMatch({ Resources: { MyPolicy39D66CF6: { Type: 'AWS::IAM::Policy', Properties: @@ -249,7 +250,7 @@ export = { const app = new App(); const stack = new Stack(app, 'MyStack'); new Policy(stack, 'MyPolicy'); - test.throws(() => app.synthesizeStack(stack.name), /Policy must be attached to at least one principal: user, group or role/); + test.throws(() => app.run(), /Policy must be attached to at least one principal: user, group or role/); test.done(); }, diff --git a/packages/@aws-cdk/aws-iam/test/test.role.ts b/packages/@aws-cdk/aws-iam/test/test.role.ts index 755a192dea6c1..c3783e814ba4d 100644 --- a/packages/@aws-cdk/aws-iam/test/test.role.ts +++ b/packages/@aws-cdk/aws-iam/test/test.role.ts @@ -1,4 +1,4 @@ -import { expect, haveResource, haveResourceLike, SynthUtils } from '@aws-cdk/assert'; +import { expect, haveResource, haveResourceLike } from '@aws-cdk/assert'; import { Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { ArnPrincipal, CompositePrincipal, FederatedPrincipal, PolicyStatement, Role, ServicePrincipal, User } from '../lib'; @@ -81,36 +81,33 @@ export = { }, 'policy is created automatically when permissions are added'(test: Test) { - const stack = new Stack(); - - const role = new Role(stack, 'MyRole', { - assumedBy: new ServicePrincipal('sns.amazonaws.com') - }); - - test.ok(!('MyRoleDefaultPolicyA36BE1DD' in SynthUtils.toCloudFormation(stack).Resources), 'initially created without a policy'); - - role.addToPolicy(new PolicyStatement().addResource('myresource').addAction('myaction')); - test.ok(SynthUtils.toCloudFormation(stack).Resources.MyRoleDefaultPolicyA36BE1DD, 'policy resource created'); - - expect(stack).toMatch({ Resources: - { MyRoleF48FFE04: - { Type: 'AWS::IAM::Role', - Properties: - { AssumeRolePolicyDocument: - { Statement: - [ { Action: 'sts:AssumeRole', - Effect: 'Allow', - Principal: { Service: 'sns.amazonaws.com' } } ], - Version: '2012-10-17' } } }, - MyRoleDefaultPolicyA36BE1DD: - { Type: 'AWS::IAM::Policy', - Properties: - { PolicyDocument: - { Statement: - [ { Action: 'myaction', Effect: 'Allow', Resource: 'myresource' } ], - Version: '2012-10-17' }, - PolicyName: 'MyRoleDefaultPolicyA36BE1DD', - Roles: [ { Ref: 'MyRoleF48FFE04' } ] } } } }); + // by default we don't expect a role policy + const before = new Stack(); + new Role(before, 'MyRole', { assumedBy: new ServicePrincipal('sns.amazonaws.com') }); + expect(before).notTo(haveResource('AWS::IAM::Policy')); + + // add a policy to the role + const after = new Stack(); + const afterRole = new Role(after, 'MyRole', { assumedBy: new ServicePrincipal('sns.amazonaws.com') }); + afterRole.addToPolicy(new PolicyStatement().addResource('myresource').addAction('myaction')); + expect(after).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: { + Statement: [ + { + Action: "myaction", + Effect: "Allow", + Resource: "myresource" + } + ], + Version: "2012-10-17" + }, + PolicyName: "MyRoleDefaultPolicyA36BE1DD", + Roles: [ + { + Ref: "MyRoleF48FFE04" + } + ] + })); test.done(); }, diff --git a/packages/@aws-cdk/aws-iam/test/test.user.ts b/packages/@aws-cdk/aws-iam/test/test.user.ts index d483669635c26..4cc7d827b122b 100644 --- a/packages/@aws-cdk/aws-iam/test/test.user.ts +++ b/packages/@aws-cdk/aws-iam/test/test.user.ts @@ -1,3 +1,4 @@ +import { expect } from '@aws-cdk/assert'; import { App, SecretValue, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { User } from '../lib'; @@ -7,7 +8,7 @@ export = { const app = new App(); const stack = new Stack(app, 'MyStack'); new User(stack, 'MyUser'); - test.deepEqual(app.synthesizeStack(stack.name).template, { + expect(stack).toMatch({ Resources: { MyUserDC45028B: { Type: 'AWS::IAM::User' } } }); test.done(); @@ -19,7 +20,8 @@ export = { new User(stack, 'MyUser', { password: SecretValue.plainText('1234') }); - test.deepEqual(app.synthesizeStack(stack.name).template, { Resources: + + expect(stack).toMatch({ Resources: { MyUserDC45028B: { Type: 'AWS::IAM::User', Properties: { LoginProfile: { Password: '1234' } } } } }); diff --git a/packages/@aws-cdk/aws-kinesis/test/test.stream.ts b/packages/@aws-cdk/aws-kinesis/test/test.stream.ts index a8f857ef5f5d6..28e69969a785f 100644 --- a/packages/@aws-cdk/aws-kinesis/test/test.stream.ts +++ b/packages/@aws-cdk/aws-kinesis/test/test.stream.ts @@ -851,10 +851,11 @@ export = { }, "cross-stack permissions": { "no encryption"(test: Test) { - const stackA = new Stack(); + const app = new App(); + const stackA = new Stack(app, 'stackA'); const streamFromStackA = new Stream(stackA, 'MyStream'); - const stackB = new Stack(); + const stackB = new Stack(app, 'stackB'); const user = new iam.User(stackB, 'UserWhoNeedsAccess'); streamFromStackA.grantRead(user); @@ -867,6 +868,19 @@ export = { "ShardCount": 1 } } + }, + "Outputs": { + "ExportsOutputFnGetAttMyStream5C050E93Arn4ABF30CD": { + "Value": { + "Fn::GetAtt": [ + "MyStream5C050E93", + "Arn" + ] + }, + "Export": { + "Name": "stackA:ExportsOutputFnGetAttMyStream5C050E93Arn4ABF30CD" + } + } } }); @@ -888,7 +902,7 @@ export = { ], "Effect": "Allow", "Resource": { - "Fn::ImportValue": "Stack:ExportsOutputFnGetAttMyStream5C050E93Arn4ABF30CD" + "Fn::ImportValue": "stackA:ExportsOutputFnGetAttMyStream5C050E93Arn4ABF30CD" } } ], diff --git a/packages/@aws-cdk/aws-kms/test/test.alias.ts b/packages/@aws-cdk/aws-kms/test/test.alias.ts index 7d52b35a63490..30547147ed52c 100644 --- a/packages/@aws-cdk/aws-kms/test/test.alias.ts +++ b/packages/@aws-cdk/aws-kms/test/test.alias.ts @@ -1,3 +1,4 @@ +import { expect, haveResource } from '@aws-cdk/assert'; import { App, Stack } from '@aws-cdk/cdk'; import { Test } from 'nodeunit'; import { Key } from '../lib'; @@ -11,13 +12,10 @@ export = { new EncryptionKeyAlias(stack, 'Alias', { key, alias: 'alias/foo' }); - test.deepEqual(app.synthesizeStack(stack.name).template.Resources.Alias325C5727, { - Type: 'AWS::KMS::Alias', - Properties: { - AliasName: 'alias/foo', - TargetKeyId: { 'Fn::GetAtt': [ 'Key961B73FD', 'Arn' ] } - } - }); + expect(stack).to(haveResource('AWS::KMS::Alias', { + AliasName: 'alias/foo', + TargetKeyId: { 'Fn::GetAtt': [ 'Key961B73FD', 'Arn' ] } + })); test.done(); }, diff --git a/packages/@aws-cdk/aws-kms/test/test.key.ts b/packages/@aws-cdk/aws-kms/test/test.key.ts index 3d1975b681472..ac1e68047a2ae 100644 --- a/packages/@aws-cdk/aws-kms/test/test.key.ts +++ b/packages/@aws-cdk/aws-kms/test/test.key.ts @@ -70,7 +70,7 @@ export = { new Key(stack, 'MyKey', { retain: false }); - expect(app.synthesizeStack(stack.name)).to(haveResource('AWS::KMS::Key', { DeletionPolicy: "Delete" }, ResourcePart.CompleteDefinition)); + expect(stack).to(haveResource('AWS::KMS::Key', { DeletionPolicy: "Delete" }, ResourcePart.CompleteDefinition)); test.done(); }, @@ -83,7 +83,7 @@ export = { p.addAwsPrincipal('arn'); key.addToResourcePolicy(p); - expect(app.synthesizeStack(stack.name)).to(exactlyMatchTemplate({ + expect(stack).to(exactlyMatchTemplate({ Resources: { MyKey6AB29FA6: { Type: "AWS::KMS::Key", @@ -252,7 +252,7 @@ export = { const alias = key.addAlias('alias/xoo'); test.ok(alias.aliasName); - test.deepEqual(app.synthesizeStack(stack.name).template, { + expect(stack).toMatch({ Resources: { MyKey6AB29FA6: { Type: "AWS::KMS::Key", diff --git a/packages/@aws-cdk/aws-lambda/test/test.code.ts b/packages/@aws-cdk/aws-lambda/test/test.code.ts index dd03afa3c9630..f89e226222286 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.code.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.code.ts @@ -63,7 +63,8 @@ export = { }); // THEN - const synthesized = app.synthesizeStack('MyStack'); + const assembly = app.run(); + const synthesized = assembly.stacks[0]; // Func1 has an asset, Func2 does not test.deepEqual(synthesized.metadata['/MyStack/Func1/Code'][0].type, 'aws:cdk:asset'); @@ -89,7 +90,7 @@ export = { // THEN expect(stack).to(haveResource('AWS::Lambda::Function', { Metadata: { - [cxapi.ASSET_RESOURCE_METADATA_PATH_KEY]: location, + [cxapi.ASSET_RESOURCE_METADATA_PATH_KEY]: 'asset.9678c34eca93259d11f2d714177347afd66c50116e1e08996eff893d3ca81232', [cxapi.ASSET_RESOURCE_METADATA_PROPERTY_KEY]: 'Code' } }, ResourcePart.CompleteDefinition)); diff --git a/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts index ded901c643994..9a4d94f959694 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts @@ -6,13 +6,15 @@ import lambda = require('../lib'); export = { 'lambda in a VPC': classFixture(class Henk { + private readonly app: cdk.App; private readonly stack: cdk.Stack; private readonly vpc: ec2.Vpc; private readonly lambda: lambda.Function; constructor() { // GIVEN - this.stack = new cdk.Stack(); + this.app = new cdk.App(); + this.stack = new cdk.Stack(this.app, 'stack'); this.vpc = new ec2.Vpc(this.stack, 'VPC'); // WHEN @@ -76,44 +78,44 @@ export = { public 'can still make Connections after export/import'(test: Test) { // GIVEN - const stack2 = new cdk.Stack(); + const stack2 = new cdk.Stack(this.app, 'stack2'); const securityGroup = new ec2.SecurityGroup(stack2, 'SomeSecurityGroup', { vpc: this.vpc }); const somethingConnectable = new SomethingConnectable(new ec2.Connections({ securityGroups: [securityGroup] })); // WHEN - this.lambda.connections.allowTo(somethingConnectable, new ec2.TcpAllPorts(), 'Lambda can call connectable'); + somethingConnectable.connections.allowFrom(this.lambda.connections, new ec2.TcpAllPorts(), 'Lambda can call connectable'); // THEN: SomeSecurityGroup accepts connections from Lambda - expect(this.stack).to(haveResource("AWS::EC2::SecurityGroupEgress", { + expect(stack2).to(haveResource("AWS::EC2::SecurityGroupEgress", { GroupId: { - "Fn::GetAtt": [ - "LambdaSecurityGroupE74659A1", - "GroupId" - ] + "Fn::ImportValue": "stack:ExportsOutputFnGetAttLambdaSecurityGroupE74659A1GroupId8F3EC6F1" }, IpProtocol: "tcp", Description: "Lambda can call connectable", DestinationSecurityGroupId: { - "Fn::ImportValue": "Stack:ExportsOutputFnGetAttSomeSecurityGroupEF219AD6GroupId09FCF7BE" + "Fn::GetAtt": [ + "SomeSecurityGroupEF219AD6", + "GroupId" + ] }, FromPort: 0, ToPort: 65535 })); // THEN: Lambda can connect to SomeSecurityGroup - expect(this.stack).to(haveResource("AWS::EC2::SecurityGroupIngress", { + expect(stack2).to(haveResource("AWS::EC2::SecurityGroupIngress", { IpProtocol: "tcp", Description: "Lambda can call connectable", FromPort: 0, GroupId: { - "Fn::ImportValue": "Stack:ExportsOutputFnGetAttSomeSecurityGroupEF219AD6GroupId09FCF7BE" - }, - SourceSecurityGroupId: { "Fn::GetAtt": [ - "LambdaSecurityGroupE74659A1", + "SomeSecurityGroupEF219AD6", "GroupId" ] }, + SourceSecurityGroupId: { + "Fn::ImportValue": "stack:ExportsOutputFnGetAttLambdaSecurityGroupE74659A1GroupId8F3EC6F1" + }, ToPort: 65535 })); diff --git a/packages/@aws-cdk/aws-route53/test/test.route53.ts b/packages/@aws-cdk/aws-route53/test/test.route53.ts index 3a5c456ba3182..f2efdd0b53ad9 100644 --- a/packages/@aws-cdk/aws-route53/test/test.route53.ts +++ b/packages/@aws-cdk/aws-route53/test/test.route53.ts @@ -9,7 +9,7 @@ export = { 'public hosted zone'(test: Test) { const app = new TestApp(); new PublicHostedZone(app.stack, 'HostedZone', { zoneName: 'test.public' }); - expect(app.synthesizeTemplate()).to(exactlyMatchTemplate({ + expect(app.stack).to(exactlyMatchTemplate({ Resources: { HostedZoneDB99F866: { Type: "AWS::Route53::HostedZone", @@ -25,7 +25,7 @@ export = { const app = new TestApp(); const vpcNetwork = new ec2.Vpc(app.stack, 'VPC'); new PrivateHostedZone(app.stack, 'HostedZone', { zoneName: 'test.private', vpc: vpcNetwork }); - expect(app.synthesizeTemplate()).to(beASupersetOfTemplate({ + expect(app.stack).to(beASupersetOfTemplate({ Resources: { HostedZoneDB99F866: { Type: "AWS::Route53::HostedZone", @@ -47,7 +47,7 @@ export = { const vpcNetworkB = new ec2.Vpc(app.stack, 'VPC2'); new PrivateHostedZone(app.stack, 'HostedZone', { zoneName: 'test.private', vpc: vpcNetworkA }) .addVpc(vpcNetworkB); - expect(app.synthesizeTemplate()).to(beASupersetOfTemplate({ + expect(app.stack).to(beASupersetOfTemplate({ Resources: { HostedZoneDB99F866: { Type: "AWS::Route53::HostedZone", @@ -207,8 +207,4 @@ class TestApp { [`${region}-1a`]); this.stack = new cdk.Stack(this.app, 'MyStack', { env: { account, region } }); } - - public synthesizeTemplate() { - return this.app.synthesizeStack(this.stack.name); - } } diff --git a/packages/@aws-cdk/aws-route53/test/test.txt-record.ts b/packages/@aws-cdk/aws-route53/test/test.txt-record.ts index c2f797a037d4b..c213b798813c2 100644 --- a/packages/@aws-cdk/aws-route53/test/test.txt-record.ts +++ b/packages/@aws-cdk/aws-route53/test/test.txt-record.ts @@ -9,7 +9,7 @@ export = { const app = new TestApp(); const zone = new PublicHostedZone(app.stack, 'HostedZone', { zoneName: 'test.public' }); new TxtRecord(zone, 'TXT', { zone, recordName: '_foo', recordValue: 'Bar!' }); - expect(app.synthesizeTemplate()).to(exactlyMatchTemplate({ + expect(app.stack).to(exactlyMatchTemplate({ Resources: { HostedZoneDB99F866: { Type: 'AWS::Route53::HostedZone', @@ -47,8 +47,4 @@ class TestApp { [`${region}-1a`]); this.stack = new Stack(this.app, 'MyStack', { env: { account, region } }); } - - public synthesizeTemplate() { - return this.app.synthesizeStack(this.stack.name); - } } diff --git a/packages/@aws-cdk/aws-route53/test/test.zone-delegation-record.ts b/packages/@aws-cdk/aws-route53/test/test.zone-delegation-record.ts index a1d3087a8a94e..03d05b2161c74 100644 --- a/packages/@aws-cdk/aws-route53/test/test.zone-delegation-record.ts +++ b/packages/@aws-cdk/aws-route53/test/test.zone-delegation-record.ts @@ -13,7 +13,7 @@ export = { delegatedZoneName: 'foo', nameServers: ['ns-1777.awsdns-30.co.uk'] }); - expect(app.synthesizeTemplate()).to(exactlyMatchTemplate({ + expect(app.stack).to(exactlyMatchTemplate({ Resources: { HostedZoneDB99F866: { Type: 'AWS::Route53::HostedZone', @@ -51,8 +51,4 @@ class TestApp { [`${region}-1a`]); this.stack = new Stack(this.app, 'MyStack', { env: { account, region } }); } - - public synthesizeTemplate() { - return this.app.synthesizeStack(this.stack.name); - } } diff --git a/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts b/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts index 982a181fa970a..a04ed46b3e405 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/notifications.test.ts @@ -291,7 +291,7 @@ test('a notification destination can specify a set of dependencies that must be bucket.addObjectCreatedNotification(dest); stack.node.prepareTree(); - expect(SynthUtils.toCloudFormation(stack).Resources.BucketNotifications8F2E257D).toEqual({ + expect(SynthUtils.synthesize(stack).template.Resources.BucketNotifications8F2E257D).toEqual({ Type: 'Custom::S3BucketNotifications', Properties: { ServiceToken: { 'Fn::GetAtt': [ 'BucketNotificationsHandler050a0587b7544547bf325f094a3db8347ECC3691', 'Arn' ] }, diff --git a/packages/@aws-cdk/aws-s3-notifications/test/queue.test.ts b/packages/@aws-cdk/aws-s3-notifications/test/queue.test.ts index a503d41629e27..1728deaa5b905 100644 --- a/packages/@aws-cdk/aws-s3-notifications/test/queue.test.ts +++ b/packages/@aws-cdk/aws-s3-notifications/test/queue.test.ts @@ -70,7 +70,7 @@ test('queues can be used as destinations', () => { // make sure the queue policy is added as a dependency to the bucket // notifications resource so it will be created first. - expect(SynthUtils.toCloudFormation(stack).Resources.BucketNotifications8F2E257D.DependsOn).toEqual(['QueuePolicy25439813', 'Queue4A7E3555']); + expect(SynthUtils.synthesize(stack).template.Resources.BucketNotifications8F2E257D.DependsOn).toEqual(['QueuePolicy25439813', 'Queue4A7E3555']); }); test('if the queue is encrypted with a custom kms key, the key resource policy is updated to allow s3 to read messages', () => { diff --git a/packages/@aws-cdk/aws-s3/test/test.bucket.ts b/packages/@aws-cdk/aws-s3/test/test.bucket.ts index 7bbc356e10de2..32ed9ba691330 100644 --- a/packages/@aws-cdk/aws-s3/test/test.bucket.ts +++ b/packages/@aws-cdk/aws-s3/test/test.bucket.ts @@ -36,7 +36,7 @@ export = { }); test.throws(() => { - SynthUtils.toCloudFormation(stack); + SynthUtils.synthesize(stack); }, /bucketName: 5 should be a string/); test.done(); @@ -571,7 +571,7 @@ export = { test.deepEqual(bucket.bucketArn, bucketArn); test.deepEqual(bucket.node.resolve(bucket.bucketName), 'my-bucket'); - test.deepEqual(SynthUtils.toCloudFormation(stack), {}, 'the ref is not a real resource'); + test.deepEqual(SynthUtils.synthesize(stack).template, {}, 'the ref is not a real resource'); test.done(); }, @@ -1000,7 +1000,7 @@ export = { bucket.grantWrite(writer); bucket.grantDelete(deleter); - const resources = SynthUtils.toCloudFormation(stack).Resources; + const resources = SynthUtils.synthesize(stack).template.Resources; const actions = (id: string) => resources[id].Properties.PolicyDocument.Statement[0].Action; test.deepEqual(actions('WriterDefaultPolicyDC585BCE'), ['s3:DeleteObject*', 's3:PutObject*', 's3:Abort*']); @@ -1010,10 +1010,11 @@ export = { }, 'cross-stack permissions'(test: Test) { - const stackA = new cdk.Stack(); + const app = new cdk.App(); + const stackA = new cdk.Stack(app, 'stackA'); const bucketFromStackA = new s3.Bucket(stackA, 'MyBucket'); - const stackB = new cdk.Stack(); + const stackB = new cdk.Stack(app, 'stackB'); const user = new iam.User(stackB, 'UserWhoNeedsAccess'); bucketFromStackA.grantRead(user); @@ -1021,7 +1022,20 @@ export = { "Resources": { "MyBucketF68F3FF0": { "Type": "AWS::S3::Bucket", - "DeletionPolicy": "Retain", + "DeletionPolicy": "Retain" + } + }, + "Outputs": { + "ExportsOutputFnGetAttMyBucketF68F3FF0Arn0F7E8E58": { + "Value": { + "Fn::GetAtt": [ + "MyBucketF68F3FF0", + "Arn" + ] + }, + "Export": { + "Name": "stackA:ExportsOutputFnGetAttMyBucketF68F3FF0Arn0F7E8E58" + } } } }); @@ -1045,14 +1059,14 @@ export = { "Effect": "Allow", "Resource": [ { - "Fn::ImportValue": "Stack:ExportsOutputFnGetAttMyBucketF68F3FF0Arn0F7E8E58" + "Fn::ImportValue": "stackA:ExportsOutputFnGetAttMyBucketF68F3FF0Arn0F7E8E58" }, { "Fn::Join": [ "", [ { - "Fn::ImportValue": "Stack:ExportsOutputFnGetAttMyBucketF68F3FF0Arn0F7E8E58" + "Fn::ImportValue": "stackA:ExportsOutputFnGetAttMyBucketF68F3FF0Arn0F7E8E58" }, "/*" ] diff --git a/packages/@aws-cdk/cdk/lib/app.ts b/packages/@aws-cdk/cdk/lib/app.ts index 45f0f43d51b84..79d021493f310 100644 --- a/packages/@aws-cdk/cdk/lib/app.ts +++ b/packages/@aws-cdk/cdk/lib/app.ts @@ -1,6 +1,10 @@ import cxapi = require('@aws-cdk/cx-api'); +import { CloudAssembly } from '@aws-cdk/cx-api'; import { Construct } from './construct'; -import { FileSystemStore, InMemoryStore, ISynthesisSession, Synthesizer } from './synthesis'; +import { collectRuntimeInformation } from './runtime-info'; +import { Synthesizer } from './synthesis'; + +const APP_SYMBOL = Symbol.for('@aws-cdk/cdk.App'); /** * Custom construction properties for a CDK program @@ -15,6 +19,26 @@ export interface AppProps { */ readonly autoRun?: boolean; + /** + * The output directory into which to emit synthesized artifacts. + * + * @default - If this value is _not_ set, considers the environment variable `CDK_OUTDIR`. + * If `CDK_OUTDIR` is not defined, uses a temp directory. + */ + readonly outdir?: string; + + /** + * Include stack traces in construct metadata entries. + * @default true stack traces are included + */ + readonly stackTraces?: boolean; + + /** + * Include runtime versioning information in cloud assembly manifest + * @default true runtime info is included + */ + readonly runtimeInfo?: boolean; + /** * Additional context values for the application * @@ -27,9 +51,14 @@ export interface AppProps { * Represents a CDK program. */ export class App extends Construct { - private _session?: ISynthesisSession; - private readonly legacyManifest: boolean; - private readonly runtimeInformation: boolean; + + public static isApp(obj: any): obj is App { + return APP_SYMBOL in obj; + } + + private _assembly?: CloudAssembly; + private readonly runtimeInfo: boolean; + private readonly outdir?: string; /** * Initializes a CDK application. @@ -38,11 +67,21 @@ export class App extends Construct { constructor(props: AppProps = {}) { super(undefined as any, ''); + Object.defineProperty(this, APP_SYMBOL, { value: true }); + this.loadContext(props.context); + if (props.stackTraces === false) { + this.node.setContext(cxapi.DISABLE_METADATA_STACK_TRACE, true); + } + + if (props.runtimeInfo === false) { + this.node.setContext(cxapi.DISABLE_VERSION_REPORTING, true); + } + // both are reverse logic - this.legacyManifest = this.node.getContext(cxapi.DISABLE_LEGACY_MANIFEST_CONTEXT) ? false : true; - this.runtimeInformation = this.node.getContext(cxapi.DISABLE_VERSION_REPORTING) ? false : true; + this.runtimeInfo = this.node.getContext(cxapi.DISABLE_VERSION_REPORTING) ? false : true; + this.outdir = props.outdir || process.env[cxapi.OUTDIR_ENV]; const autoRun = props.autoRun !== undefined ? props.autoRun : cxapi.OUTDIR_ENV in process.env; @@ -54,64 +93,27 @@ export class App extends Construct { } /** - * Runs the program. Output is written to output directory as specified in the request. + * Runs the program. Output is written to output directory as specified in the + * request. + * + * @returns a `CloudAssembly` which includes all the synthesized artifacts + * such as CloudFormation templates and assets. */ - public run(): ISynthesisSession { + public run(): CloudAssembly { // this app has already been executed, no-op for you - if (this._session) { - return this._session; - } - - const outdir = process.env[cxapi.OUTDIR_ENV]; - let store; - if (outdir) { - store = new FileSystemStore({ outdir }); - } else { - store = new InMemoryStore(); + if (this._assembly) { + return this._assembly; } const synth = new Synthesizer(); - this._session = synth.synthesize(this, { - store, - legacyManifest: this.legacyManifest, - runtimeInformation: this.runtimeInformation + const assembly = synth.synthesize(this, { + outdir: this.outdir, + runtimeInfo: this.runtimeInfo ? collectRuntimeInformation() : undefined }); - return this._session; - } - - /** - * Synthesize and validate a single stack. - * @param stackName The name of the stack to synthesize - * @deprecated This method is going to be deprecated in a future version of the CDK - */ - public synthesizeStack(stackName: string): cxapi.SynthesizedStack { - if (!this.legacyManifest) { - throw new Error('No legacy manifest available, return an old-style stack output'); - } - - const session = this.run(); - const legacy: cxapi.SynthesizeResponse = session.store.readJson(cxapi.OUTFILE_NAME); - - const res = legacy.stacks.find(s => s.name === stackName); - if (!res) { - throw new Error(`Stack "${stackName}" not found`); - } - - return res; - } - - /** - * Synthesizes multiple stacks - * @deprecated This method is going to be deprecated in a future version of the CDK - */ - public synthesizeStacks(stackNames: string[]): cxapi.SynthesizedStack[] { - const ret: cxapi.SynthesizedStack[] = []; - for (const stackName of stackNames) { - ret.push(this.synthesizeStack(stackName)); - } - return ret; + this._assembly = assembly; + return assembly; } private loadContext(defaults: { [key: string]: string } = { }) { diff --git a/packages/@aws-cdk/cdk/lib/cfn-element.ts b/packages/@aws-cdk/cdk/lib/cfn-element.ts index 1ac6e1c1540c7..c5bd71e24b2fd 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-element.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-element.ts @@ -62,8 +62,13 @@ export abstract class CfnElement extends Construct { * from the +metadata+ entry typed +aws:cdk:logicalId+, and with the bottom-most * node +internal+ entries filtered. */ - public get creationStackTrace(): string[] { - return filterStackTrace(this.node.metadata.find(md => md.type === LOGICAL_ID_MD)!.trace); + public get creationStackTrace(): string[] | undefined { + const trace = this.node.metadata.find(md => md.type === LOGICAL_ID_MD)!.trace; + if (!trace) { + return undefined; + } + + return filterStackTrace(trace); function filterStackTrace(stack: string[]): string[] { const result = Array.of(...stack); diff --git a/packages/@aws-cdk/cdk/lib/cfn-reference.ts b/packages/@aws-cdk/cdk/lib/cfn-reference.ts index 4589116757e22..1c357a1084a8e 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-reference.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-reference.ts @@ -89,6 +89,7 @@ export class CfnReference extends Reference { if (typeof(value) === 'function') { throw new Error('Reference can only hold CloudFormation intrinsics (not a function)'); } + // prepend scope path to display name super(value, `${target.node.id}.${displayName}`, target); this.originalDisplayName = displayName; @@ -113,6 +114,12 @@ export class CfnReference extends Reference { * Register a stack this references is being consumed from. */ public consumeFromStack(consumingStack: Stack, consumingConstruct: IConstruct) { + if (this.producingStack && consumingStack.node.root !== this.producingStack.node.root) { + throw this.newError( + `Cannot reference across apps. ` + + `Consuming and producing stacks must be defined within the same CDK app.`); + } + // tslint:disable-next-line:max-line-length if (this.producingStack && this.producingStack !== consumingStack && !this.replacementTokens.has(consumingStack)) { // We're trying to resolve a cross-stack reference @@ -130,7 +137,7 @@ export class CfnReference extends Reference { const producingStack = this.producingStack!; if (producingStack.env.account !== consumingStack.env.account || producingStack.env.region !== consumingStack.env.region) { - throw new Error('Can only reference cross stacks in the same region and account.'); + throw this.newError('Can only reference cross stacks in the same region and account.'); } // Ensure a singleton "Exports" scoping Construct diff --git a/packages/@aws-cdk/cdk/lib/cfn-resource.ts b/packages/@aws-cdk/cdk/lib/cfn-resource.ts index 03222c1aae72d..d04eac39c5c12 100644 --- a/packages/@aws-cdk/cdk/lib/cfn-resource.ts +++ b/packages/@aws-cdk/cdk/lib/cfn-resource.ts @@ -245,9 +245,13 @@ export class CfnResource extends CfnRefElement { // Change message e.message = `While synthesizing ${this.node.path}: ${e.message}`; // Adjust stack trace (make it look like node built it, too...) - const creationStack = ['--- resource created at ---', ...this.creationStackTrace].join('\n at '); - const problemTrace = e.stack.substr(e.stack.indexOf(e.message) + e.message.length); - e.stack = `${e.message}\n ${creationStack}\n --- problem discovered at ---${problemTrace}`; + const trace = this.creationStackTrace; + if (trace) { + const creationStack = ['--- resource created at ---', ...trace].join('\n at '); + const problemTrace = e.stack.substr(e.stack.indexOf(e.message) + e.message.length); + e.stack = `${e.message}\n ${creationStack}\n --- problem discovered at ---${problemTrace}`; + } + // Re-throw throw e; } diff --git a/packages/@aws-cdk/cdk/lib/construct.ts b/packages/@aws-cdk/cdk/lib/construct.ts index c771e05d9e8af..900df346bdb09 100644 --- a/packages/@aws-cdk/cdk/lib/construct.ts +++ b/packages/@aws-cdk/cdk/lib/construct.ts @@ -3,6 +3,7 @@ import { IAspect } from './aspect'; import { CLOUDFORMATION_TOKEN_RESOLVER, CloudFormationLang } from './cloudformation-lang'; import { IDependable } from './dependency'; import { resolve } from './resolve'; +import { createStackTrace } from './stack-trace'; import { Token } from './token'; import { makeUniqueId } from './uniqueid'; @@ -44,7 +45,7 @@ export class ConstructNode { */ private readonly _children: { [name: string]: IConstruct } = { }; private readonly context: { [key: string]: any } = { }; - private readonly _metadata = new Array(); + private readonly _metadata = new Array(); private readonly references = new Set(); private readonly dependencies = new Set(); @@ -279,7 +280,8 @@ export class ConstructNode { if (data == null) { return; } - const trace = createStackTrace(from || this.addMetadata); + + const trace = this.getContext(cxapi.DISABLE_METADATA_STACK_TRACE) ? undefined : createStackTrace(from || this.addMetadata); this._metadata.push({ type, data, trace }); } @@ -350,11 +352,18 @@ export class ConstructNode { this.aspects.push(aspect); return; } + /** - * Return the ancestors (including self) of this Construct up until and excluding the indicated component + * Return the ancestors (including self) of this Construct up until and + * excluding the indicated component + * + * @param upTo The construct to return the path components relative to, or the + * entire list of ancestors (including root) if omitted. This construct will + * not be included in the returned list. * - * @param upTo The construct to return the path components relative to, or - * the entire list of ancestors (including root) if omitted. + * @returns a list of parent scopes. The last element in the list will always + * be `this` and the first element is the oldest scope (if `upTo` is not set, + * it will be the root of the construct tree). */ public ancestors(upTo?: Construct): IConstruct[] { const ret = new Array(); @@ -368,6 +377,13 @@ export class ConstructNode { return ret; } + /** + * @returns The root of the construct tree. + */ + public get root() { + return this.ancestors()[0]; + } + /** * Throws if the `props` bag doesn't include the property `name`. * In the future we can add some type-checking here, maybe even auto-generate during compilation. @@ -633,48 +649,12 @@ export class Construct implements IConstruct { } } -/** - * An metadata entry in the construct. - */ -export interface MetadataEntry { - /** - * The type of the metadata entry. - */ - readonly type: string; - - /** - * The data. - */ - readonly data?: any; - - /** - * A stack trace for when the entry was created. - */ - readonly trace: string[]; -} - export class ValidationError { constructor(public readonly source: IConstruct, public readonly message: string) { } } -// tslint:disable-next-line:ban-types -function createStackTrace(below: Function): string[] { - const object = { stack: '' }; - const previousLimit = Error.stackTraceLimit; - try { - Error.stackTraceLimit = Number.MAX_SAFE_INTEGER; - Error.captureStackTrace(object, below); - } finally { - Error.stackTraceLimit = previousLimit; - } - if (!object.stack) { - return []; - } - return object.stack.split('\n').slice(1).map(s => s.replace(/^\s*at\s+/, '')); -} - /** * In what order to return constructs */ @@ -726,4 +706,4 @@ export interface OutgoingReference { } // Import this _after_ everything else to help node work the classes out in the correct order... -import { Reference } from './reference'; \ No newline at end of file +import { Reference } from './reference'; diff --git a/packages/@aws-cdk/cdk/lib/runtime-info.ts b/packages/@aws-cdk/cdk/lib/runtime-info.ts index c978691acc808..bfef428819b6a 100644 --- a/packages/@aws-cdk/cdk/lib/runtime-info.ts +++ b/packages/@aws-cdk/cdk/lib/runtime-info.ts @@ -4,7 +4,7 @@ import { major as nodeMajorVersion } from './node-version'; /** * Returns a list of loaded modules and their versions. */ -export function collectRuntimeInformation(): cxapi.AppRuntime { +export function collectRuntimeInformation(): cxapi.RuntimeInfo { const libraries: { [name: string]: string } = {}; for (const fileName of Object.keys(require.cache)) { diff --git a/packages/@aws-cdk/cdk/lib/stack-trace.ts b/packages/@aws-cdk/cdk/lib/stack-trace.ts new file mode 100644 index 0000000000000..ab47cf2d47a99 --- /dev/null +++ b/packages/@aws-cdk/cdk/lib/stack-trace.ts @@ -0,0 +1,16 @@ +// tslint:disable-next-line:ban-types +export function createStackTrace(below?: Function): string[] { + below = below || createStackTrace; // hide myself if nothing else + const object = { stack: '' }; + const previousLimit = Error.stackTraceLimit; + try { + Error.stackTraceLimit = Number.MAX_SAFE_INTEGER; + Error.captureStackTrace(object, below); + } finally { + Error.stackTraceLimit = previousLimit; + } + if (!object.stack) { + return []; + } + return object.stack.split('\n').slice(1).map(s => s.replace(/^\s*at\s+/, '')); +} diff --git a/packages/@aws-cdk/cdk/lib/stack.ts b/packages/@aws-cdk/cdk/lib/stack.ts index 63b9d5f367311..572736e9c782f 100644 --- a/packages/@aws-cdk/cdk/lib/stack.ts +++ b/packages/@aws-cdk/cdk/lib/stack.ts @@ -1,10 +1,11 @@ import cxapi = require('@aws-cdk/cx-api'); +import fs = require('fs'); +import path = require('path'); import { App } from './app'; import { CfnParameter } from './cfn-parameter'; import { Construct, IConstruct, PATH_SEP } from './construct'; import { Environment } from './environment'; import { HashedAddressingScheme, IAddressingScheme, LogicalIDs } from './logical-id'; -import { ISynthesisSession } from './synthesis'; import { makeUniqueId } from './uniqueid'; export interface StackProps { @@ -161,7 +162,7 @@ export class Stack extends Construct { public get environment() { const account = this.env.account || 'unknown-account'; const region = this.env.region || 'unknown-region'; - return `aws://${account}/${region}`; + return cxapi.EnvironmentUtils.format(account, region); } /** @@ -169,13 +170,13 @@ export class Stack extends Construct { * * @returns The Resource or undefined if not found */ - public findResource(path: string): CfnResource | undefined { - const r = this.node.findChild(path); + public findResource(constructPath: string): CfnResource | undefined { + const r = this.node.findChild(constructPath); if (!r) { return undefined; } // found an element, check if it's a resource (duck-type) if (!('resourceType' in r)) { - throw new Error(`Found a stack element for ${path} but it is not a resource: ${r.toString()}`); + throw new Error(`Found a stack element for ${constructPath} but it is not a resource: ${r.toString()}`); } return r as CfnResource; @@ -491,23 +492,26 @@ export class Stack extends Construct { } } - protected synthesize(session: ISynthesisSession): void { + protected synthesize(builder: cxapi.CloudAssemblyBuilder): void { const template = `${this.name}.template.json`; // write the CloudFormation template as a JSON file - session.store.writeJson(template, this._toCloudFormation()); + const outPath = path.join(builder.outdir, template); + fs.writeFileSync(outPath, JSON.stringify(this._toCloudFormation(), undefined, 2)); const deps = this.dependencies().map(s => s.name); const meta = this.collectMetadata(); + const properties: cxapi.AwsCloudFormationStackProperties = { + templateFile: template, + parameters: Object.keys(this.parameterValues).length > 0 ? this.node.resolve(this.parameterValues) : undefined + }; + // add an artifact that represents this stack - session.addArtifact(this.name, { + builder.addArtifact(this.name, { type: cxapi.ArtifactType.AwsCloudFormationStack, environment: this.environment, - properties: { - templateFile: template, - parameters: Object.keys(this.parameterValues).length > 0 ? this.node.resolve(this.parameterValues) : undefined - }, + properties, autoDeploy: this.autoDeploy ? undefined : false, dependencies: deps.length > 0 ? deps : undefined, metadata: Object.keys(meta).length > 0 ? meta : undefined, diff --git a/packages/@aws-cdk/cdk/lib/synthesis.ts b/packages/@aws-cdk/cdk/lib/synthesis.ts index 47d2cfdbd581e..32a684683b6f2 100644 --- a/packages/@aws-cdk/cdk/lib/synthesis.ts +++ b/packages/@aws-cdk/cdk/lib/synthesis.ts @@ -1,30 +1,16 @@ -import cxapi = require('@aws-cdk/cx-api'); -import fs = require('fs'); -import os = require('os'); -import path = require('path'); +import { BuildOptions, CloudAssembly, CloudAssemblyBuilder } from '@aws-cdk/cx-api'; import { ConstructOrder, IConstruct } from './construct'; -import { collectRuntimeInformation } from './runtime-info'; -import { filterUndefined } from './util'; export interface ISynthesizable { - synthesize(session: ISynthesisSession): void; + synthesize(session: CloudAssemblyBuilder): void; } -export interface ISynthesisSession { - readonly store: ISessionStore; - readonly manifest: cxapi.AssemblyManifest; - addArtifact(id: string, droplet: cxapi.Artifact): void; - addBuildStep(id: string, step: cxapi.BuildStep): void; - tryGetArtifact(id: string): cxapi.Artifact | undefined; - getArtifact(id: string): cxapi.Artifact; -} - -export interface SynthesisOptions extends ManifestOptions { +export interface SynthesisOptions extends BuildOptions { /** * The file store used for this session. - * @default InMemoryStore + * @default - creates a temporary directory */ - readonly store?: ISessionStore; + readonly outdir?: string; /** * Whether synthesis should skip the validation phase. @@ -34,8 +20,8 @@ export interface SynthesisOptions extends ManifestOptions { } export class Synthesizer { - public synthesize(root: IConstruct, options: SynthesisOptions = { }): ISynthesisSession { - const session = new SynthesisSession(options); + public synthesize(root: IConstruct, options: SynthesisOptions = { }): CloudAssembly { + const session = new CloudAssemblyBuilder(options.outdir); // the three holy phases of synthesis: prepare, validate and synthesize @@ -54,357 +40,19 @@ export class Synthesizer { // synthesize (leaves first) for (const c of root.node.findAll(ConstructOrder.PostOrder)) { - if (SynthesisSession.isSynthesizable(c)) { + if (isSynthesizable(c)) { c.synthesize(session); } } // write session manifest and lock store - session.close(options); - - return session; - } -} - -export class SynthesisSession implements ISynthesisSession { - /** - * @returns true if `obj` implements `ISynthesizable`. - */ - public static isSynthesizable(obj: any): obj is ISynthesizable { - return 'synthesize' in obj; - } - - public readonly store: ISessionStore; - - private readonly artifacts: { [id: string]: cxapi.Artifact } = { }; - private readonly buildSteps: { [id: string]: cxapi.BuildStep } = { }; - private _manifest?: cxapi.AssemblyManifest; - - constructor(options: SynthesisOptions) { - this.store = options.store || new InMemoryStore(); - } - - public get manifest() { - if (!this._manifest) { - throw new Error(`Cannot read assembly manifest before the session has been finalized`); - } - - return this._manifest; - } - - public addArtifact(id: string, artifact: cxapi.Artifact): void { - cxapi.validateArtifact(artifact); - this.artifacts[id] = filterUndefined(artifact); - } - - public tryGetArtifact(id: string): cxapi.Artifact | undefined { - return this.artifacts[id]; - } - - public getArtifact(id: string): cxapi.Artifact { - const artifact = this.tryGetArtifact(id); - if (!artifact) { - throw new Error(`Cannot find artifact ${id}`); - } - return artifact; - } - - public addBuildStep(id: string, step: cxapi.BuildStep) { - if (id in this.buildSteps) { - throw new Error(`Build step ${id} already exists`); - } - this.buildSteps[id] = filterUndefined(step); - } - - public close(options: ManifestOptions = { }): cxapi.AssemblyManifest { - const legacyManifest = options.legacyManifest !== undefined ? options.legacyManifest : false; - const runtimeInfo = options.runtimeInformation !== undefined ? options.runtimeInformation : true; - - const manifest: cxapi.AssemblyManifest = this._manifest = filterUndefined({ - version: cxapi.PROTO_RESPONSE_VERSION, - artifacts: this.artifacts, - runtime: runtimeInfo ? collectRuntimeInformation() : undefined - }); - - this.store.writeFile(cxapi.MANIFEST_FILE, JSON.stringify(manifest, undefined, 2)); - - // write build manifest if we have build steps - if (Object.keys(this.buildSteps).length > 0) { - const buildManifest: cxapi.BuildManifest = { - steps: this.buildSteps - }; - - this.store.writeFile(cxapi.BUILD_FILE, JSON.stringify(buildManifest, undefined, 2)); - } - - if (legacyManifest) { - const legacy: cxapi.SynthesizeResponse = { - ...manifest, - stacks: renderLegacyStacks(manifest, this.store) - }; - - // render the legacy manifest (cdk.out) which also contains a "stacks" attribute with all the rendered stacks. - this.store.writeFile(cxapi.OUTFILE_NAME, JSON.stringify(legacy, undefined, 2)); - } - - this.store.lock(); - - return manifest; + return session.build(options); } } -export interface ManifestOptions { - /** - * Emit the legacy manifest (`cdk.out`) when the session is closed (alongside `manifest.json`). - * @default false - */ - readonly legacyManifest?: boolean; - - /** - * Include runtime information (module versions) in manifest. - * @default true - */ - readonly runtimeInformation?: boolean; -} - -export interface ISessionStore { - /** - * Creates a directory and returns it's full path. - * @param directoryName The name of the directory to create. - * @throws if a directory by that name already exists in the session or if the session has already been finalized. - */ - mkdir(directoryName: string): string; - - /** - * Returns the list of files in a directory. - * @param directoryName The name of the artifact - * @throws if there is no directory artifact under this name - */ - readdir(directoryName: string): string[]; - - /** - * Writes a file into the store. - * @param artifactName The name of the file. - * @param data The contents of the file. - */ - writeFile(artifactName: string, data: any): void; - - /** - * Writes a formatted JSON output file to the store - * @param artifactName the name of the artifact - * @param json the JSON object - */ - writeJson(artifactName: string, json: any): void; - - /** - * Reads a file from the store. - * @param fileName The name of the file. - * @throws if the file is not found - */ - readFile(fileName: string): any; - - /** - * Reads a JSON object from the store. - */ - readJson(fileName: string): any; - - /** - * @returns true if the file `fileName` exists in the store. - * @param name The name of the file or directory to look up. - */ - exists(name: string): boolean; - - /** - * List all top-level files that were emitted to the store. - */ - list(): string[]; - - /** - * Do not allow further writes into the store. - */ - lock(): void; -} - -export interface FileSystemStoreOptions { - /** - * The output directory for synthesis artifacts - */ - readonly outdir: string; -} - /** - * Can be used to prepare and emit synthesis artifacts into an output directory. + * @returns true if `obj` implements `ISynthesizable`. */ -export class FileSystemStore implements ISessionStore { - private readonly outdir: string; - private locked = false; - - constructor(options: FileSystemStoreOptions) { - this.outdir = options.outdir; - return; - } - - public writeFile(fileName: string, data: any) { - this.canWrite(fileName); - - const p = this.pathForArtifact(fileName); - fs.writeFileSync(p, data); - } - - public writeJson(fileName: string, json: any) { - this.writeFile(fileName, JSON.stringify(json, undefined, 2)); - } - - public readFile(fileName: string): any { - const p = this.pathForArtifact(fileName); - if (!fs.existsSync(p)) { - throw new Error(`File not found: ${p}`); - } - - return fs.readFileSync(p); - } - - public readJson(fileName: string): any { - return JSON.parse(this.readFile(fileName).toString()); - } - - public exists(name: string): boolean { - const p = this.pathForArtifact(name); - return fs.existsSync(p); - } - - public mkdir(directoryName: string): string { - this.canWrite(directoryName); - const p = this.pathForArtifact(directoryName); - fs.mkdirSync(p); - return p; - } - - public readdir(directoryName: string): string[] { - if (!this.exists(directoryName)) { - throw new Error(`${directoryName} not found`); - } - - const p = this.pathForArtifact(directoryName); - return fs.readdirSync(p); - } - - public list(): string[] { - return fs.readdirSync(this.outdir).sort(); - } - - public lock() { - this.locked = true; - } - - private pathForArtifact(id: string) { - return path.join(this.outdir, id); - } - - private canWrite(artifactName: string) { - if (this.exists(artifactName)) { - throw new Error(`An artifact named ${artifactName} was already written to this session`); - } - if (this.locked) { - throw new Error('Session has already been finalized'); - } - } -} - -export class InMemoryStore implements ISessionStore { - private files: { [fileName: string]: any } = { }; - private dirs: { [dirName: string]: string } = { }; // value is path to a temporary directory - - private locked = false; - - public writeFile(fileName: string, data: any): void { - this.canWrite(fileName); - this.files[fileName] = data; - } - - public writeJson(fileName: string, json: any): void { - this.writeFile(fileName, JSON.stringify(json, undefined, 2)); - } - - public readFile(fileName: string) { - if (!(fileName in this.files)) { - throw new Error(`${fileName} not found`); - } - return this.files[fileName]; - } - - public readJson(fileName: string): any { - return JSON.parse(this.readFile(fileName).toString()); - } - - public exists(name: string) { - return name in this.files || name in this.dirs; - } - - public mkdir(directoryName: string): string { - this.canWrite(directoryName); - - const p = fs.mkdtempSync(path.join(os.tmpdir(), directoryName)); - this.dirs[directoryName] = p; - return p; - } - - public readdir(directoryName: string): string[] { - if (!this.exists(directoryName)) { - throw new Error(`${directoryName} not found`); - } - - const p = this.dirs[directoryName]; - return fs.readdirSync(p); - } - - public list(): string[] { - return [ ...Object.keys(this.files), ...Object.keys(this.dirs) ].sort(); - } - - public lock() { - this.locked = true; - } - - private canWrite(artifactName: string) { - if (this.exists(artifactName)) { - throw new Error(`An artifact named ${artifactName} was already written to this session`); - } - if (this.locked) { - throw new Error('Session has already been finalized'); - } - } -} - -export function renderLegacyStacks(manifest: cxapi.AssemblyManifest, store: ISessionStore) { - // special case for backwards compat. build a list of stacks for the manifest - const stacks = new Array(); - const artifacts = manifest.artifacts || { }; - - for (const [ id, artifact ] of Object.entries(artifacts)) { - if (artifact.type === cxapi.ArtifactType.AwsCloudFormationStack) { - const templateFile = artifact.properties && artifact.properties.templateFile; - if (!templateFile) { - throw new Error(`Invalid cloudformation artifact. Missing "template" property`); - } - const template = store.readJson(templateFile); - - const match = cxapi.AWS_ENV_REGEX.exec(artifact.environment); - if (!match) { - throw new Error(`"environment" must match regex: ${cxapi.AWS_ENV_REGEX}`); - } - - stacks.push(filterUndefined({ - name: id, - environment: { name: artifact.environment.substr('aws://'.length), account: match[1], region: match[2] }, - template, - metadata: artifact.metadata || {}, - autoDeploy: artifact.autoDeploy, - dependsOn: artifact.dependencies && artifact.dependencies.length > 0 ? artifact.dependencies : undefined, - missing: artifact.missing - })); - } - } - - return stacks; +function isSynthesizable(obj: any): obj is ISynthesizable { + return 'synthesize' in obj; } diff --git a/packages/@aws-cdk/cdk/lib/token.ts b/packages/@aws-cdk/cdk/lib/token.ts index 7d83b4dcc2587..d7d64df131c61 100644 --- a/packages/@aws-cdk/cdk/lib/token.ts +++ b/packages/@aws-cdk/cdk/lib/token.ts @@ -1,5 +1,6 @@ import { IConstruct } from "./construct"; import { unresolved } from "./encoding"; +import { createStackTrace } from './stack-trace'; import { TokenMap } from "./token-map"; /** @@ -36,6 +37,11 @@ export class Token { return unresolved(obj); } + /** + * The captured stack trace which represents the location in which this token was created. + */ + protected readonly trace: string[]; + private tokenStringification?: string; private tokenListification?: string[]; private tokenNumberification?: number; @@ -60,6 +66,7 @@ export class Token { * @param displayName A human-readable display hint for this Token */ constructor(private readonly valueOrFunction?: any, private readonly displayName?: string) { + this.trace = createStackTrace(); } /** @@ -134,7 +141,7 @@ export class Token { public toList(): string[] { const valueType = typeof this.valueOrFunction; if (valueType === 'string' || valueType === 'number' || valueType === 'boolean') { - throw new Error('Got a literal Token value; only intrinsics can ever evaluate to lists.'); + throw this.newError('Got a literal Token value; only intrinsics can ever evaluate to lists.'); } if (this.tokenListification === undefined) { @@ -159,13 +166,21 @@ export class Token { // registering a Token. if (valueType === 'number') { return this.valueOrFunction; } if (valueType !== 'function') { - throw new Error(`Token value is not number or lazy, can't represent as number: ${this.valueOrFunction}`); + throw this.newError(`Token value is not number or lazy, can't represent as number: ${this.valueOrFunction}`); } this.tokenNumberification = TokenMap.instance().registerNumber(this); } return this.tokenNumberification; } + + /** + * Creates a throwable Error object that contains the token creation stack trace. + * @param message Error message + */ + protected newError(message: string): any { + return new Error(`${message}\nToken created:\n at ${this.trace.join('\n at ')}\nError thrown:`); + } } /** diff --git a/packages/@aws-cdk/cdk/test/test.app.ts b/packages/@aws-cdk/cdk/test/test.app.ts index 04c8995d0932b..77ee96c813bb1 100644 --- a/packages/@aws-cdk/cdk/test/test.app.ts +++ b/packages/@aws-cdk/cdk/test/test.app.ts @@ -1,21 +1,22 @@ import cxapi = require('@aws-cdk/cx-api'); import { Test } from 'nodeunit'; import { CfnResource, Construct, Stack, StackProps } from '../lib'; -import { App } from '../lib/app'; +import { App, AppProps } from '../lib/app'; -function withApp(context: { [key: string]: any } | undefined, block: (app: App) => void): cxapi.SynthesizeResponse { - const app = new App({ context }); +function withApp(props: AppProps, block: (app: App) => void): cxapi.CloudAssembly { + const app = new App({ + runtimeInfo: false, + stackTraces: false, + ...props, + }); block(app); - const session = app.run(); - - // return the legacy manifest - return session.store.readJson(cxapi.OUTFILE_NAME); + return app.run(); } -function synth(context?: { [key: string]: any }): cxapi.SynthesizeResponse { - return withApp(context, app => { +function synth(context?: { [key: string]: any }): cxapi.CloudAssembly { + return withApp({ context }, app => { const stack1 = new Stack(app, 'stack1', { env: { account: '12345', region: 'us-east-1' } }); new CfnResource(stack1, 's1c1', { type: 'DummyResource', properties: { Prop1: 'Prop1' } }); const r2 = new CfnResource(stack1, 's1c2', { type: 'DummyResource', properties: { Foo: 123 } }); @@ -33,12 +34,9 @@ function synth(context?: { [key: string]: any }): cxapi.SynthesizeResponse { }); } -function synthStack(name: string, includeMetadata: boolean = false, context?: any): cxapi.SynthesizedStack { +function synthStack(name: string, includeMetadata: boolean = false, context?: any): cxapi.CloudFormationStackArtifact { const response = synth(context); - const stack = response.stacks.find(s => s.name === name); - if (!stack) { - throw new Error(`Stack ${name} not found`); - } + const stack = response.getStack(name); if (!includeMetadata) { delete (stack as any).metadata; @@ -50,289 +48,257 @@ function synthStack(name: string, includeMetadata: boolean = false, context?: an export = { 'synthesizes all stacks and returns synthesis result'(test: Test) { const response = synth(); + delete (response as any).dir; + + test.deepEqual(response.stacks.length, 2); + + const stack1 = response.stacks[0]; + test.deepEqual(stack1.name, 'stack1'); + test.deepEqual(stack1.environment.account, 12345); + test.deepEqual(stack1.environment.region, 'us-east-1'); + test.deepEqual(stack1.environment.name, 'aws://12345/us-east-1'); + test.deepEqual(stack1.template, { Resources: + { s1c1: { Type: 'DummyResource', Properties: { Prop1: 'Prop1' } }, + s1c2: { Type: 'DummyResource', Properties: { Foo: 123 } } } }); + test.deepEqual(stack1.metadata, { + '/stack1': [{ type: 'meta', data: 111 }], + '/stack1/s1c1': [{ type: 'aws:cdk:logicalId', data: 's1c1' }], + '/stack1/s1c2': + [{ type: 'aws:cdk:logicalId', data: 's1c2' }, + { type: 'aws:cdk:warning', data: 'warning1' }, + { type: 'aws:cdk:warning', data: 'warning2' }], + '/': [{ type: 'applevel', data: 123 }] + }); - // clean up metadata so assertion will be sane - response.stacks.forEach(s => delete (s as any).metadata); - delete (response as any).runtime; - delete (response as any).artifacts; - - test.deepEqual(response, { - version: '0.19.0', - stacks: - [ { name: 'stack1', - environment: - { name: '12345/us-east-1', - account: '12345', - region: 'us-east-1' }, - template: - { Resources: - { s1c1: { Type: 'DummyResource', Properties: { Prop1: 'Prop1' } }, - s1c2: { Type: 'DummyResource', Properties: { Foo: 123 } } } } }, - { name: 'stack2', - environment: - { name: 'unknown-account/unknown-region', - account: 'unknown-account', - region: 'unknown-region' }, - template: - { Resources: - { s2c1: { Type: 'DummyResource', Properties: { Prog2: 'Prog2' } }, - s1c2r1D1791C01: { Type: 'ResourceType1' }, - s1c2r25F685FFF: { Type: 'ResourceType2' } } } } ] }); - test.done(); - }, - - 'synth(name) can be used programmatically'(test: Test) { - const prog = new App(); - const stack = new Stack(prog, 'MyStack'); - new CfnResource(stack, 'MyResource', { type: 'MyResourceType' }); - - test.throws(() => prog.synthesizeStacks(['foo']), /foo/); - - test.deepEqual(prog.synthesizeStack('MyStack').template, - { Resources: { MyResource: { Type: 'MyResourceType' } } }); - test.done(); - }, - - 'synth(name) also collects metadata from all constructs in the stack'(test: Test) { - const stack = synthStack('stack1', true); - - const output = stack.metadata; - stripStackTraces(output); - - test.ok(output['/'], 'app-level metadata is included under "."'); - test.equal(output['/'][0].type, 'applevel'); - test.equal(output['/'][0].data, 123); - - test.ok(output['/stack1'], 'the construct "stack1" has metadata'); - test.equal(output['/stack1'][0].type, 'meta'); - test.equal(output['/stack1'][0].data, 111); - test.ok(output['/stack1'][0].trace.length > 0, 'trace contains multiple entries'); - - test.ok(output['/stack1/s1c2']); - test.equal(output['/stack1/s1c2'].length, 2, 'two entries'); - test.equal(output['/stack1/s1c2'][0].data, 'warning1'); - - const stack2 = synthStack('stack2', true); - const output2 = stack2.metadata; - - test.ok(output2['/stack2/s1c2']); - test.equal(output2['/stack2/s1c2'][0].type, 'meta'); - test.deepEqual(output2['/stack2/s1c2'][0].data, { key: 'value' }); - - test.done(); - }, - - 'context can be passed through CDK_CONTEXT'(test: Test) { - process.env[cxapi.CONTEXT_ENV] = JSON.stringify({ - key1: 'val1', - key2: 'val2' + const stack2 = response.stacks[1]; + test.deepEqual(stack2.name, 'stack2'); + test.deepEqual(stack2.environment.name, 'aws://unknown-account/unknown-region'); + test.deepEqual(stack2.template, { Resources: + { s2c1: { Type: 'DummyResource', Properties: { Prog2: 'Prog2' } }, + s1c2r1D1791C01: { Type: 'ResourceType1' }, + s1c2r25F685FFF: { Type: 'ResourceType2' } } }); + test.deepEqual(stack2.metadata, { + '/stack2/s2c1': [{ type: 'aws:cdk:logicalId', data: 's2c1' }], + '/stack2/s1c2': [{ type: 'meta', data: { key: 'value' } }], + '/stack2/s1c2/r1': + [{ type: 'aws:cdk:logicalId', data: 's1c2r1D1791C01' }], + '/stack2/s1c2/r2': + [{ type: 'aws:cdk:logicalId', data: 's1c2r25F685FFF' }], + '/': [{ type: 'applevel', data: 123 }] }); - const prog = new App(); - test.deepEqual(prog.node.getContext('key1'), 'val1'); - test.deepEqual(prog.node.getContext('key2'), 'val2'); - test.done(); - }, - 'context from the command line can be used when creating the stack'(test: Test) { - const output = synthStack('stack2', false, { ctx1: 'HELLO' }); + test.done(); +}, - test.deepEqual(output.template, { - Resources: { - s2c1: { +'context can be passed through CDK_CONTEXT'(test: Test) { + process.env[cxapi.CONTEXT_ENV] = JSON.stringify({ + key1: 'val1', + key2: 'val2' + }); + const prog = new App(); + test.deepEqual(prog.node.getContext('key1'), 'val1'); + test.deepEqual(prog.node.getContext('key2'), 'val2'); + test.done(); +}, + +'context from the command line can be used when creating the stack'(test: Test) { + const output = synthStack('stack2', false, { ctx1: 'HELLO' }); + + test.deepEqual(output.template, { + Resources: { + s2c1: { Type: "DummyResource", Properties: { Prog2: "Prog2" } - }, - s1c2r1D1791C01: { + }, + s1c2r1D1791C01: { Type: "ResourceType1" - }, - s1c2r25F685FFF: { + }, + s1c2r25F685FFF: { Type: "ResourceType2", Properties: { FromContext: "HELLO" } - } - } - }); - test.done(); - }, - - 'setContext(k,v) can be used to set context programmatically'(test: Test) { - const prog = new App(); - prog.node.setContext('foo', 'bar'); - test.deepEqual(prog.node.getContext('foo'), 'bar'); - test.done(); - }, - - 'setContext(k,v) cannot be called after stacks have been added because stacks may use the context'(test: Test) { - const prog = new App(); - new Stack(prog, 's1'); - test.throws(() => prog.node.setContext('foo', 'bar')); - test.done(); - }, - - 'app.synthesizeStack(stack) performs validation first (app.validateAll()) and if there are errors, it returns the errors'(test: Test) { - - class Child extends Construct { - protected validate() { - return [ `Error from ${this.node.id}` ]; } } + }); + test.done(); +}, + +'setContext(k,v) can be used to set context programmatically'(test: Test) { + const prog = new App(); + prog.node.setContext('foo', 'bar'); + test.deepEqual(prog.node.getContext('foo'), 'bar'); + test.done(); +}, + +'setContext(k,v) cannot be called after stacks have been added because stacks may use the context'(test: Test) { + const prog = new App(); + new Stack(prog, 's1'); + test.throws(() => prog.node.setContext('foo', 'bar')); + test.done(); +}, + +'app.run() performs validation first and if there are errors, it returns the errors'(test: Test) { + + class Child extends Construct { + protected validate() { + return [`Error from ${this.node.id}`]; + } + } - class Parent extends Stack { + class Parent extends Stack { - } + } - const app = new App(); + const app = new App(); - const parent = new Parent(app, 'Parent'); - new Child(parent, 'C1'); - new Child(parent, 'C2'); + const parent = new Parent(app, 'Parent'); + new Child(parent, 'C1'); + new Child(parent, 'C2'); - test.throws(() => { - app.synthesizeStacks(['Parent']); - }, /Validation failed with the following errors/); + test.throws(() => app.run(), /Validation failed with the following errors/); - test.done(); - }, - - 'app.synthesizeStack(stack) will return a list of missing contextual information'(test: Test) { - class MyStack extends Stack { - constructor(scope: App, id: string, props?: StackProps) { - super(scope, id, props); - - this.reportMissingContext('missing-context-key', { - provider: 'fake', - props: { - account: '12345689012', - region: 'ab-north-1', - }, - }, - ); - - this.reportMissingContext('missing-context-key-2', { - provider: 'fake2', - props: { - foo: 'bar', - account: '12345689012', - region: 'ab-south-1', - }, - }, - ); - } - } + test.done(); +}, - const response = withApp(undefined, app => { - new MyStack(app, 'MyStack'); - }); +'app.synthesizeStack(stack) will return a list of missing contextual information'(test: Test) { + class MyStack extends Stack { + constructor(scope: App, id: string, props?: StackProps) { + super(scope, id, props); - test.deepEqual(response.stacks[0].missing, { - "missing-context-key": { + this.reportMissingContext('missing-context-key', { provider: 'fake', props: { account: '12345689012', region: 'ab-north-1', }, }, - "missing-context-key-2": { + ); + + this.reportMissingContext('missing-context-key-2', { provider: 'fake2', props: { + foo: 'bar', account: '12345689012', region: 'ab-south-1', - foo: 'bar', }, }, - }); + ); + } + } - test.done(); - }, + const response = withApp({}, app => { + new MyStack(app, 'MyStack'); + }); - 'runtime library versions disabled'(test: Test) { - const context: any = {}; - context[cxapi.DISABLE_VERSION_REPORTING] = true; + test.deepEqual(response.stacks[0].missing, { + "missing-context-key": { + provider: 'fake', + props: { + account: '12345689012', + region: 'ab-north-1', + }, + }, + "missing-context-key-2": { + provider: 'fake2', + props: { + account: '12345689012', + region: 'ab-south-1', + foo: 'bar', + }, + }, + }); - const response = withApp(context, app => { - const stack = new Stack(app, 'stack1'); - new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); - }); + test.done(); +}, - test.equals(response.runtime, undefined); - test.done(); - }, +'runtime library versions disabled'(test: Test) { + const context: any = {}; + context[cxapi.DISABLE_VERSION_REPORTING] = true; - 'runtime library versions'(test: Test) { - const response = withApp({}, app => { - const stack = new Stack(app, 'stack1'); - new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); - }); + const response = withApp(context, app => { + const stack = new Stack(app, 'stack1'); + new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); + }); - const libs = (response.runtime && response.runtime.libraries) || { }; + test.deepEqual(response.runtime, { libraries: {} }); + test.done(); +}, - const version = require('../package.json').version; - test.deepEqual(libs['@aws-cdk/cdk'], version); - test.deepEqual(libs['@aws-cdk/cx-api'], version); - test.deepEqual(libs['jsii-runtime'], `node.js/${process.version}`); - test.done(); - }, +'runtime library versions'(test: Test) { + const response = withApp({ runtimeInfo: true }, app => { + const stack = new Stack(app, 'stack1'); + new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); + }); - 'jsii-runtime version loaded from JSII_AGENT'(test: Test) { - process.env.JSII_AGENT = 'Java/1.2.3.4'; + const libs = (response.runtime && response.runtime.libraries) || {}; - const response = withApp({}, app => { - const stack = new Stack(app, 'stack1'); - new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); - }); + const version = require('../package.json').version; + test.deepEqual(libs['@aws-cdk/cdk'], version); + test.deepEqual(libs['@aws-cdk/cx-api'], version); + test.deepEqual(libs['jsii-runtime'], `node.js/${process.version}`); + test.done(); +}, - const libs = (response.runtime && response.runtime.libraries) || { }; - test.deepEqual(libs['jsii-runtime'], `Java/1.2.3.4`); +'jsii-runtime version loaded from JSII_AGENT'(test: Test) { + process.env.JSII_AGENT = 'Java/1.2.3.4'; - delete process.env.JSII_AGENT; - test.done(); - }, + const response = withApp({ runtimeInfo: true }, app => { + const stack = new Stack(app, 'stack1'); + new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); + }); - 'version reporting includes only @aws-cdk, aws-cdk and jsii libraries'(test: Test) { - const response = withApp({}, app => { - const stack = new Stack(app, 'stack1'); - new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); - }); + const libs = (response.runtime && response.runtime.libraries) || {}; + test.deepEqual(libs['jsii-runtime'], `Java/1.2.3.4`); - const libs = (response.runtime && response.runtime.libraries) || { }; + delete process.env.JSII_AGENT; + test.done(); +}, - const version = require('../package.json').version; - test.deepEqual(libs, { - '@aws-cdk/cdk': version, - '@aws-cdk/cx-api': version, - 'jsii-runtime': `node.js/${process.version}` - }); +'version reporting includes only @aws-cdk, aws-cdk and jsii libraries'(test: Test) { + const response = withApp({ runtimeInfo: true }, app => { + const stack = new Stack(app, 'stack1'); + new CfnResource(stack, 'MyResource', { type: 'Resource::Type' }); + }); - test.done(); - }, + const libs = (response.runtime && response.runtime.libraries) || {}; - 'deep stack is shown and synthesized properly'(test: Test) { - // WHEN - const response = withApp(undefined, (app) => { - const topStack = new Stack(app, 'Stack'); - const topResource = new CfnResource(topStack, 'Res', { type: 'CDK::TopStack::Resource' }); + const version = require('../package.json').version; + test.deepEqual(libs, { + '@aws-cdk/cdk': version, + '@aws-cdk/cx-api': version, + 'jsii-runtime': `node.js/${process.version}` + }); - const bottomStack = new Stack(topResource, 'Stack'); - new CfnResource(bottomStack, 'Res', { type: 'CDK::BottomStack::Resource' }); - }); + test.done(); +}, - // THEN - test.deepEqual(response.stacks.map(s => ({ name: s.name, template: s.template })), [ - { - name: 'StackResStack7E4AFA86', - template: { Resources: { Res: { Type: 'CDK::BottomStack::Resource' } } }, - }, - { - name: 'Stack', - template: { Resources: { Res: { Type: 'CDK::TopStack::Resource' } } }, - } - ]); +'deep stack is shown and synthesized properly'(test: Test) { + // WHEN + const response = withApp({}, (app) => { + const topStack = new Stack(app, 'Stack'); + const topResource = new CfnResource(topStack, 'Res', { type: 'CDK::TopStack::Resource' }); - test.done(); - }, + const bottomStack = new Stack(topResource, 'Stack'); + new CfnResource(bottomStack, 'Res', { type: 'CDK::BottomStack::Resource' }); + }); + + // THEN + test.deepEqual(response.stacks.map(s => ({ name: s.name, template: s.template })), [ + { + name: 'Stack', + template: { Resources: { Res: { Type: 'CDK::TopStack::Resource' } } }, + }, + { + name: 'StackResStack7E4AFA86', + template: { Resources: { Res: { Type: 'CDK::BottomStack::Resource' } } }, + }, + ]); + + test.done(); +}, }; class MyConstruct extends Construct { @@ -343,12 +309,3 @@ class MyConstruct extends Construct { new CfnResource(this, 'r2', { type: 'ResourceType2', properties: { FromContext: this.node.getContext('ctx1') } }); } } - -/** - * Strip stack traces from metadata - */ -function stripStackTraces(meta: cxapi.StackMetadata) { - for (const key of Object.keys(meta)) { - meta[key] = meta[key].filter(entry => entry.type !== 'aws:cdk:logicalId'); - } -} diff --git a/packages/@aws-cdk/cdk/test/test.construct.ts b/packages/@aws-cdk/cdk/test/test.construct.ts index d49b2cb3b7fb8..b314b3b42f64e 100644 --- a/packages/@aws-cdk/cdk/test/test.construct.ts +++ b/packages/@aws-cdk/cdk/test/test.construct.ts @@ -280,7 +280,7 @@ export = { test.deepEqual(con.node.metadata[0].data, 'value'); test.deepEqual(con.node.metadata[1].data, 103); test.deepEqual(con.node.metadata[2].data, [ 123, 456 ]); - test.ok(con.node.metadata[0].trace[0].indexOf('FIND_ME') !== -1, 'First stack line should include this function\s name'); + test.ok(con.node.metadata[0].trace && con.node.metadata[0].trace[0].indexOf('FIND_ME') !== -1, 'First stack line should include this function\s name'); test.done(); }, @@ -309,7 +309,7 @@ export = { con.node.addWarning('This construct is deprecated, use the other one instead'); test.deepEqual(con.node.metadata[0].type, cxapi.WARNING_METADATA_KEY); test.deepEqual(con.node.metadata[0].data, 'This construct is deprecated, use the other one instead'); - test.ok(con.node.metadata[0].trace.length > 0); + test.ok(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0); test.done(); }, @@ -319,7 +319,7 @@ export = { con.node.addError('Stop!'); test.deepEqual(con.node.metadata[0].type, cxapi.ERROR_METADATA_KEY); test.deepEqual(con.node.metadata[0].data, 'Stop!'); - test.ok(con.node.metadata[0].trace.length > 0); + test.ok(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0); test.done(); }, @@ -329,7 +329,7 @@ export = { con.node.addInfo('Hey there, how do you do?'); test.deepEqual(con.node.metadata[0].type, cxapi.INFO_METADATA_KEY); test.deepEqual(con.node.metadata[0].data, 'Hey there, how do you do?'); - test.ok(con.node.metadata[0].trace.length > 0); + test.ok(con.node.metadata[0].trace && con.node.metadata[0].trace.length > 0); test.done(); }, @@ -462,6 +462,23 @@ export = { test.deepEqual(c1.node.findAll(ConstructOrder.PreOrder).map(x => x.node.id), [ '1', '2', '4', '5', '3' ]); test.deepEqual(c1.node.findAll(ConstructOrder.PostOrder).map(x => x.node.id), [ '4', '5', '2', '3', '1' ]); test.done(); + }, + + 'ancestors returns a list of parents up to root'(test: Test) { + const { child1, child1_1_1 } = createTree(); + + test.deepEqual(child1_1_1.node.ancestors().map(x => x.node.id), [ '', 'Child1', 'Child11', 'Child111' ]); + test.deepEqual(child1_1_1.node.ancestors(child1).map(x => x.node.id), [ 'Child11', 'Child111' ]); + test.deepEqual(child1_1_1.node.ancestors(child1_1_1), [ ]); + test.done(); + }, + + '"root" returns the root construct'(test: Test) { + const { child1, child2, child1_1_1, root } = createTree(); + test.ok(child1.node.root === root); + test.ok(child2.node.root === root); + test.ok(child1_1_1.node.root === root); + test.done(); } }; diff --git a/packages/@aws-cdk/cdk/test/test.context.ts b/packages/@aws-cdk/cdk/test/test.context.ts index 20ccfd16a6ea5..d701a725153f1 100644 --- a/packages/@aws-cdk/cdk/test/test.context.ts +++ b/packages/@aws-cdk/cdk/test/test.context.ts @@ -1,7 +1,6 @@ import cxapi = require('@aws-cdk/cx-api'); import { Test } from 'nodeunit'; -import { App, AvailabilityZoneProvider, Construct, ContextProvider, - MetadataEntry, SSMParameterProvider, Stack } from '../lib'; +import { App, AvailabilityZoneProvider, Construct, ContextProvider, SSMParameterProvider, Stack } from '../lib'; export = { 'AvailabilityZoneProvider returns a list with dummy values if the context is not available'(test: Test) { @@ -101,10 +100,11 @@ export = { test.deepEqual(new AvailabilityZoneProvider(stack).availabilityZones, [ 'dummy1a', 'dummy1b', 'dummy1c' ]); test.deepEqual(new SSMParameterProvider(child, {parameterName: 'foo'}).parameterValue(), 'dummy'); - const output = app.synthesizeStack(stack.name); - - const azError: MetadataEntry | undefined = output.metadata['/test-stack'].find(x => x.type === cxapi.ERROR_METADATA_KEY); - const ssmError: MetadataEntry | undefined = output.metadata['/test-stack/ChildConstruct'].find(x => x.type === cxapi.ERROR_METADATA_KEY); + const assembly = app.run(); + const output = assembly.getStack('test-stack'); + const metadata = output.metadata; + const azError: cxapi.MetadataEntry | undefined = metadata['/test-stack'].find(x => x.type === cxapi.ERROR_METADATA_KEY); + const ssmError: cxapi.MetadataEntry | undefined = metadata['/test-stack/ChildConstruct'].find(x => x.type === cxapi.ERROR_METADATA_KEY); test.ok(azError && (azError.data as string).includes('Cannot determine scope for context provider availability-zones')); test.ok(ssmError && (ssmError.data as string).includes('Cannot determine scope for context provider ssm')); diff --git a/packages/@aws-cdk/cdk/test/test.stack.ts b/packages/@aws-cdk/cdk/test/test.stack.ts index 6d4bc86fbd5a6..0451745f5c7ba 100644 --- a/packages/@aws-cdk/cdk/test/test.stack.ts +++ b/packages/@aws-cdk/cdk/test/test.stack.ts @@ -439,7 +439,7 @@ export = { // THEN const session = app.run(); test.deepEqual(stack.name, 'valid-stack-name'); - test.ok('valid-stack-name' in (session.manifest.artifacts || {})); + test.ok(session.tryGetArtifact('valid-stack-name')); test.done(); }, diff --git a/packages/@aws-cdk/cdk/test/test.synthesis.ts b/packages/@aws-cdk/cdk/test/test.synthesis.ts index c360054a24546..8512a07fd9613 100644 --- a/packages/@aws-cdk/cdk/test/test.synthesis.ts +++ b/packages/@aws-cdk/cdk/test/test.synthesis.ts @@ -1,17 +1,15 @@ import cxapi = require('@aws-cdk/cx-api'); +import { CloudAssemblyBuilder } from '@aws-cdk/cx-api'; import fs = require('fs'); import { Test } from 'nodeunit'; import os = require('os'); import path = require('path'); import cdk = require('../lib'); -import { Construct, FileSystemStore, InMemoryStore, ISynthesisSession, SynthesisSession, Synthesizer } from '../lib'; - -const storeTestMatrix: any = {}; +import { Construct, Synthesizer } from '../lib'; function createModernApp() { return new cdk.App({ context: { - [cxapi.DISABLE_LEGACY_MANIFEST_CONTEXT]: 'true', [cxapi.DISABLE_VERSION_REPORTING]: 'true', // for test reproducibility } }); @@ -27,8 +25,8 @@ export = { // THEN test.same(app.run(), session); // same session if we run() again - test.deepEqual(session.store.list(), [ 'manifest.json' ]); - test.deepEqual(session.store.readJson('manifest.json').artifacts, {}); + test.deepEqual(list(session.directory), [ 'manifest.json' ]); + test.deepEqual(readJson(session.directory, 'manifest.json').artifacts, {}); test.done(); }, @@ -41,7 +39,7 @@ export = { const session = app.run(); // THEN - test.deepEqual(session.store.list(), [ + test.deepEqual(list(session.directory), [ 'manifest.json', 'one-stack.template.json' ]); @@ -54,13 +52,13 @@ export = { const stack = new cdk.Stack(app, 'one-stack'); class MyConstruct extends cdk.Construct implements cdk.ISynthesizable { - public synthesize(s: cdk.ISynthesisSession) { - s.store.writeJson('foo.json', { bar: 123 }); + public synthesize(s: cxapi.CloudAssemblyBuilder) { + writeJson(s.outdir, 'foo.json', { bar: 123 }); s.addArtifact('my-random-construct', { type: cxapi.ArtifactType.AwsCloudFormationStack, environment: 'aws://12345/bar', properties: { - templateFile: 'file://boom' + templateFile: 'foo.json' } }); } @@ -72,19 +70,19 @@ export = { const session = app.run(); // THEN - test.deepEqual(session.store.list(), [ + test.deepEqual(list(session.directory), [ 'foo.json', 'manifest.json', 'one-stack.template.json' ]); - test.deepEqual(session.store.readJson('foo.json'), { bar: 123 }); + test.deepEqual(readJson(session.directory, 'foo.json'), { bar: 123 }); test.deepEqual(session.manifest, { - version: '0.19.0', + version: '0.33.0', artifacts: { 'my-random-construct': { type: 'aws:cloudformation:stack', environment: 'aws://12345/bar', - properties: { templateFile: 'file://boom' } + properties: { templateFile: 'foo.json' } }, 'one-stack': { type: 'aws:cloudformation:stack', @@ -96,37 +94,6 @@ export = { test.done(); }, - 'backwards compatibility: cdk.out contains all synthesized stacks'(test: Test) { - // GIVEN - const app = new cdk.App(); - const stack1 = new cdk.Stack(app, 'stack1'); - new cdk.CfnResource(stack1, 'Resource1', { type: 'AWS::CDK::Resource' }); - new cdk.CfnResource(stack1, 'Resource2', { type: 'AWS::CDK::Resource' }); - const stack2 = new cdk.Stack(app, 'stack2'); - new cdk.CfnResource(stack2, 'ResourceA', { type: 'AWS::CDK::Resource' }); - - // WHEN - const session = app.run(); - const legacy: cxapi.SynthesizeResponse = session.store.readJson(cxapi.OUTFILE_NAME); - - // THEN - const t1 = legacy.stacks.find(s => s.name === 'stack1')!.template; - const t2 = legacy.stacks.find(s => s.name === 'stack2')!.template; - - test.deepEqual(t1, { - Resources: { - Resource1: { Type: 'AWS::CDK::Resource' }, - Resource2: { Type: 'AWS::CDK::Resource' } - } - }); - test.deepEqual(t2, { - Resources: { - ResourceA: { Type: 'AWS::CDK::Resource' } - } - }); - test.done(); - }, - 'it should be possible to synthesize without an app'(test: Test) { const calls = new Array(); @@ -144,184 +111,29 @@ export = { calls.push('prepare'); } - protected synthesize(session: ISynthesisSession) { + protected synthesize(session: CloudAssemblyBuilder) { calls.push('synthesize'); session.addArtifact('art', { - type: cxapi.ArtifactType.AwsEcrDockerImage, + type: cxapi.ArtifactType.AwsCloudFormationStack, + properties: { templateFile: 'hey.json' }, environment: 'aws://unknown-account/us-east-1' }); - session.store.writeJson('hey.json', { hello: 123 }); + writeJson(session.outdir, 'hey.json', { hello: 123 }); } } const root = new SynthesizeMe(); const synth = new Synthesizer(); - const result = synth.synthesize(root); + const assembly = synth.synthesize(root, { outdir: fs.mkdtempSync(path.join(os.tmpdir(), 'outdir')) }); test.deepEqual(calls, [ 'prepare', 'validate', 'synthesize' ]); - test.deepEqual(result.store.readJson('hey.json'), { hello: 123 }); - test.deepEqual(result.manifest.artifacts!.art, { - type: 'aws:ecr:image', - environment: 'aws://unknown-account/us-east-1' - }); - test.done(); - }, - - 'store': storeTestMatrix -}; - -// -// all these tests will be executed for each type of store -// -const storeTests = { - 'writeFile()/readFile()'(test: Test, store: cdk.ISessionStore) { - // WHEN - store.writeFile('bla.txt', 'hello'); - store.writeFile('hey.txt', '1234'); - - // THEN - test.deepEqual(store.readFile('bla.txt').toString(), 'hello'); - test.deepEqual(store.readFile('hey.txt').toString(), '1234'); - test.throws(() => store.writeFile('bla.txt', 'override is forbidden')); - - // WHEN - store.lock(); - - // THEN - test.throws(() => store.writeFile('another.txt', 'locked!')); - test.done(); - }, - - 'exists() for files'(test: Test, store: cdk.ISessionStore) { - // WHEN - store.writeFile('A.txt', 'aaa'); - - // THEN - test.ok(store.exists('A.txt')); - test.ok(!store.exists('B.txt')); - test.done(); - }, - - 'mkdir'(test: Test, store: cdk.ISessionStore) { - // WHEN - const dir1 = store.mkdir('dir1'); - const dir2 = store.mkdir('dir2'); - - // THEN - test.ok(fs.statSync(dir1).isDirectory()); - test.ok(fs.statSync(dir2).isDirectory()); - test.throws(() => store.mkdir('dir1')); - - // WHEN - store.lock(); - test.throws(() => store.mkdir('dir3')); - test.done(); - }, - - 'list'(test: Test, store: cdk.ISessionStore) { - // WHEN - store.mkdir('dir1'); - store.writeFile('file1.txt', 'boom1'); - - // THEN - test.deepEqual(store.list(), ['dir1', 'file1.txt']); - test.done(); - }, - - 'SynthesisSession'(test: Test, store: cdk.ISessionStore) { - // GIVEN - const session = new SynthesisSession({ store }); - const templateFile = 'foo.template.json'; - - // WHEN - session.addArtifact('my-first-artifact', { - type: cxapi.ArtifactType.AwsCloudFormationStack, - environment: 'aws://1222344/us-east-1', - dependencies: ['a', 'b'], - metadata: { - foo: { bar: 123 } - }, - properties: { - templateFile, - parameters: { - prop1: '1234', - prop2: '555' - } - }, - missing: { - foo: { - provider: 'context-provider', - props: { - a: 'A', - b: 2 - } - } - } - }); - - session.addArtifact('minimal-artifact', { - type: cxapi.ArtifactType.AwsCloudFormationStack, - environment: 'aws://111/helo-world', - properties: { - templateFile - } - }); - - session.store.writeJson(templateFile, { - Resources: { - MyTopic: { - Type: 'AWS::S3::Topic' - } - } - }); - - session.close(); - - const manifest = session.store.readJson(cxapi.MANIFEST_FILE); - - // THEN - delete manifest.runtime; // deterministic tests - - // verify the manifest looks right - test.deepEqual(manifest, { - version: cxapi.PROTO_RESPONSE_VERSION, - artifacts: { - 'my-first-artifact': { - type: 'aws:cloudformation:stack', - environment: 'aws://1222344/us-east-1', - dependencies: ['a', 'b'], - metadata: { foo: { bar: 123 } }, - properties: { - templateFile: 'foo.template.json', - parameters: { - prop1: '1234', - prop2: '555' - }, - }, - missing: { - foo: { provider: 'context-provider', props: { a: 'A', b: 2 } } - } - }, - 'minimal-artifact': { - type: 'aws:cloudformation:stack', - environment: 'aws://111/helo-world', - properties: { templateFile: 'foo.template.json' } - } - } - }); - - // verify we have a template file - test.deepEqual(session.store.readJson(templateFile), { - Resources: { - MyTopic: { - Type: 'AWS::S3::Topic' - } - } - }); - + const stack = assembly.getStack('art'); + test.deepEqual(stack.template, { hello: 123 }); + test.deepEqual(stack.properties, { templateFile: 'hey.json' }); + test.deepEqual(stack.environment, { region: 'us-east-1', account: 'unknown-account', name: 'aws://unknown-account/us-east-1' }); test.done(); }, @@ -336,47 +148,22 @@ const storeTests = { // THEN const session = app.run(); - const props = (session.manifest.artifacts && session.manifest.artifacts['my-stack'].properties) || {}; + const props = session.getStack('my-stack').properties; test.deepEqual(props.parameters, { MyParam: 'Foo' }); test.done(); }, - - 'addBuildStep can be used to produce build.json'(test: Test) { - // GIVEN - const app = createModernApp(); - - // WHEN - class BuildMe extends cdk.Construct implements cdk.ISynthesizable { - public synthesize(s: cdk.ISynthesisSession) { - s.addBuildStep('step_id', { - type: 'build-step-type', - parameters: { - boom: 123 - } - }); - } - } - - new BuildMe(app, 'hey'); - - // THEN - const session = app.run(); - test.deepEqual(session.store.list(), [ 'build.json', 'manifest.json' ]); - test.deepEqual(session.store.readJson('build.json'), { - steps: { - step_id: { type: 'build-step-type', parameters: { boom: 123 } } - } - }); - test.done(); - } }; -for (const [name, fn] of Object.entries(storeTests)) { - const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'synthesis-tests')); - const fsStore = new FileSystemStore({ outdir }); - const memoryStore = new InMemoryStore(); - storeTestMatrix[`FileSystemStore - ${name}`] = (test: Test) => fn(test, fsStore); - storeTestMatrix[`InMemoryStore - ${name}`] = (test: Test) => fn(test, memoryStore); +function list(outdir: string) { + return fs.readdirSync(outdir).sort(); } + +function readJson(outdir: string, file: string) { + return JSON.parse(fs.readFileSync(path.join(outdir, file), 'utf-8')); +} + +function writeJson(outdir: string, file: string, data: any) { + fs.writeFileSync(path.join(outdir, file), JSON.stringify(data, undefined, 2)); +} \ No newline at end of file diff --git a/packages/@aws-cdk/cdk/test/test.tokens.ts b/packages/@aws-cdk/cdk/test/test.tokens.ts index 639ef3fa93387..1ea25183712e6 100644 --- a/packages/@aws-cdk/cdk/test/test.tokens.ts +++ b/packages/@aws-cdk/cdk/test/test.tokens.ts @@ -481,6 +481,48 @@ export = { test.done(); }, }, + + 'stack trace is captured at token creation'(test: Test) { + function fn1() { + function fn2() { + class ExposeTrace extends Token { + public get creationTrace() { + return this.trace; + } + } + + return new ExposeTrace(() => 'hello'); + } + + return fn2(); + } + + const token = fn1(); + test.ok(token.creationTrace.find(x => x.includes('fn1'))); + test.ok(token.creationTrace.find(x => x.includes('fn2'))); + test.done(); + }, + + 'newError returns an error with the creation stack trace'(test: Test) { + function fn1() { + function fn2() { + function fn3() { + class ThrowingToken extends Token { + public throwError(message: string) { + throw this.newError(message); + } + } + return new ThrowingToken('boom'); + } + + return fn3(); + } + return fn2(); + } + const token = fn1(); + test.throws(() => token.throwError('message!'), /Token created:/); + test.done(); + } }; class Promise2 extends Token { diff --git a/packages/@aws-cdk/cx-api/.gitignore b/packages/@aws-cdk/cx-api/.gitignore index 7cc4e93cbf77b..aa27a447d1de9 100644 --- a/packages/@aws-cdk/cx-api/.gitignore +++ b/packages/@aws-cdk/cx-api/.gitignore @@ -9,4 +9,7 @@ tslint.json .LAST_BUILD .LAST_PACKAGE -*.snk \ No newline at end of file +*.snk +.nyc_output +coverage +.nycrc \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/lib/artifacts.ts b/packages/@aws-cdk/cx-api/lib/artifacts.ts deleted file mode 100644 index ce59da7d077db..0000000000000 --- a/packages/@aws-cdk/cx-api/lib/artifacts.ts +++ /dev/null @@ -1,23 +0,0 @@ -export const AWS_ENV_REGEX = /aws\:\/\/([0-9]+|unknown-account)\/([a-z\-0-9]+)/; - -export enum ArtifactType { - AwsCloudFormationStack = 'aws:cloudformation:stack', - AwsEcrDockerImage = 'aws:ecr:image', - AwsS3Object = 'aws:s3:object' -} - -export interface Artifact { - readonly type: ArtifactType; - readonly environment: string; // format: aws://account/region - readonly metadata?: { [path: string]: any }; - readonly dependencies?: string[]; - readonly missing?: { [key: string]: any }; - readonly properties?: { [name: string]: any }; - readonly autoDeploy?: boolean; -} - -export function validateArtifact(artifcat: Artifact) { - if (!AWS_ENV_REGEX.test(artifcat.environment)) { - throw new Error(`Artifact "environment" must conform to ${AWS_ENV_REGEX}: ${artifcat.environment}`); - } -} diff --git a/packages/@aws-cdk/cx-api/lib/metadata/assets.ts b/packages/@aws-cdk/cx-api/lib/assets.ts similarity index 100% rename from packages/@aws-cdk/cx-api/lib/metadata/assets.ts rename to packages/@aws-cdk/cx-api/lib/assets.ts diff --git a/packages/@aws-cdk/cx-api/lib/build.ts b/packages/@aws-cdk/cx-api/lib/build.ts deleted file mode 100644 index 4b5ec14188ed5..0000000000000 --- a/packages/@aws-cdk/cx-api/lib/build.ts +++ /dev/null @@ -1,16 +0,0 @@ -export interface BuildStep { - readonly type: string; - readonly depends?: string[]; - readonly parameters: { - [key: string]: any - }; -} - -export interface BuildManifest { - readonly steps: { [id: string]: BuildStep }; -} - -export enum BuildStepType { - CopyFile = 'copy-file', - ZipDirectory = 'zip-directory' -} diff --git a/packages/@aws-cdk/cx-api/lib/cloud-artifact.ts b/packages/@aws-cdk/cx-api/lib/cloud-artifact.ts new file mode 100644 index 0000000000000..159b00ac161be --- /dev/null +++ b/packages/@aws-cdk/cx-api/lib/cloud-artifact.ts @@ -0,0 +1,101 @@ +import { CloudAssembly, MissingContext } from './cloud-assembly'; +import { Environment, EnvironmentUtils } from './environment'; +import { ERROR_METADATA_KEY, INFO_METADATA_KEY, MetadataEntry, SynthesisMessage, SynthesisMessageLevel, WARNING_METADATA_KEY } from './metadata'; + +export enum ArtifactType { + None = 'none', + AwsCloudFormationStack = 'aws:cloudformation:stack', +} + +export interface Artifact { + readonly type: ArtifactType; + readonly environment: string; // format: aws://account/region + readonly metadata?: { [path: string]: MetadataEntry[] }; + readonly dependencies?: string[]; + readonly missing?: { [key: string]: MissingContext }; + readonly properties?: { [name: string]: any }; + readonly autoDeploy?: boolean; +} + +export interface AwsCloudFormationStackProperties { + readonly templateFile: string; + readonly parameters?: { [id: string]: string }; +} + +export class CloudArtifact { + public static from(assembly: CloudAssembly, name: string, artifact: Artifact): CloudArtifact { + switch (artifact.type) { + case ArtifactType.AwsCloudFormationStack: + return new CloudFormationStackArtifact(assembly, name, artifact); + + default: + throw new Error(`unsupported artifact type: ${artifact.type}`); + } + } + + public readonly type: ArtifactType; + public readonly missing: { [key: string]: MissingContext }; + public readonly autoDeploy: boolean; + public readonly messages: SynthesisMessage[]; + public readonly environment: Environment; + public readonly metadata: { [path: string]: MetadataEntry[] }; + public readonly dependsIDs: string[]; + public readonly properties: { [name: string]: any }; + + private _deps?: CloudArtifact[]; // cache + + constructor(public readonly assembly: CloudAssembly, public readonly id: string, artifact: Artifact) { + this.missing = artifact.missing || { }; + this.type = artifact.type; + this.environment = EnvironmentUtils.parse(artifact.environment); + this.autoDeploy = artifact.autoDeploy === undefined ? true : artifact.autoDeploy; + this.metadata = artifact.metadata || { }; + this.messages = this.renderMessages(); + this.dependsIDs = artifact.dependencies || []; + this.properties = artifact.properties || { }; + } + + public get depends(): CloudArtifact[] { + if (this._deps) { return this._deps; } + + this._deps = this.dependsIDs.map(id => { + const dep = this.assembly.artifacts.find(a => a.id === id); + if (!dep) { + throw new Error(`Artifact ${this.id} depends on non-existing artifact ${id}`); + } + return dep; + }); + + return this._deps; + } + + private renderMessages() { + const messages = new Array(); + + for (const [ id, metadata ] of Object.entries(this.metadata)) { + for (const entry of metadata) { + let level: SynthesisMessageLevel; + switch (entry.type) { + case WARNING_METADATA_KEY: + level = SynthesisMessageLevel.WARNING; + break; + case ERROR_METADATA_KEY: + level = SynthesisMessageLevel.ERROR; + break; + case INFO_METADATA_KEY: + level = SynthesisMessageLevel.INFO; + break; + default: + continue; + } + + messages.push({ level, entry, id }); + } + } + + return messages; + } +} + +// needs to be defined at the end to avoid a cyclic dependency +import { CloudFormationStackArtifact } from './cloudformation-artifact'; diff --git a/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts b/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts new file mode 100644 index 0000000000000..b985d63790c18 --- /dev/null +++ b/packages/@aws-cdk/cx-api/lib/cloud-assembly.ts @@ -0,0 +1,204 @@ +import fs = require('fs'); +import os = require('os'); +import path = require('path'); +import { Artifact, CloudArtifact } from './cloud-artifact'; +import { CloudFormationStackArtifact } from './cloudformation-artifact'; +import { topologicalSort } from './toposort'; +import { CLOUD_ASSEMBLY_VERSION, verifyManifestVersion } from './versioning'; + +export interface AssemblyManifest { + /** + * Protocol version + */ + readonly version: string; + + /** + * The set of artifacts in this assembly. + */ + readonly artifacts?: { [id: string]: Artifact }; + + /** + * Runtime information. + */ + readonly runtime?: RuntimeInfo; +} + +/** + * The name of the root manifest file of the assembly. + */ +const MANIFEST_FILE = 'manifest.json'; + +export class CloudAssembly { + public readonly artifacts: CloudArtifact[]; + public readonly version: string; + public readonly missing?: { [key: string]: MissingContext }; + public readonly runtime: RuntimeInfo; + public readonly manifest: AssemblyManifest; + + constructor(public readonly directory: string) { + this.manifest = this.readJson(MANIFEST_FILE); + + this.version = this.manifest.version; + verifyManifestVersion(this.version); + + this.artifacts = this.renderArtifacts(); + this.missing = this.renderMissing(); + this.runtime = this.manifest.runtime || { libraries: { } }; + + // force validation of deps by accessing 'depends' on all artifacts + this.validateDeps(); + } + + public tryGetArtifact(id: string): CloudArtifact | undefined { + return this.stacks.find(a => a.id === id); + } + + public getStack(id: string): CloudFormationStackArtifact { + const artifact = this.tryGetArtifact(id); + if (!artifact) { + throw new Error(`Unable to find artifact with id "${id}"`); + } + + if (!(artifact instanceof CloudFormationStackArtifact)) { + throw new Error(`Artifact ${id} is not a CloudFormation stack`); + } + + return artifact; + } + + public readJson(filePath: string) { + return JSON.parse(fs.readFileSync(path.join(this.directory, filePath), 'utf-8')); + } + + public get stacks(): CloudFormationStackArtifact[] { + const result = new Array(); + for (const a of this.artifacts) { + if (a instanceof CloudFormationStackArtifact) { + result.push(a); + } + } + return result; + } + + private validateDeps() { + for (const artifact of this.artifacts) { + ignore(artifact.depends); + } + } + + private renderArtifacts() { + const result = new Array(); + for (const [ name, artifact ] of Object.entries(this.manifest.artifacts || { })) { + result.push(CloudArtifact.from(this, name, artifact)); + } + + return topologicalSort(result, x => x.id, x => x.dependsIDs); + } + + private renderMissing() { + const missing: { [key: string]: MissingContext } = { }; + for (const artifact of this.artifacts) { + for (const [ key, m ] of Object.entries(artifact.missing)) { + missing[key] = m; + } + } + + return Object.keys(missing).length > 0 ? missing : undefined; + } +} + +export class CloudAssemblyBuilder { + public readonly outdir: string; + + private readonly artifacts: { [id: string]: Artifact } = { }; + + constructor(outdir?: string) { + this.outdir = outdir || fs.mkdtempSync(path.join(os.tmpdir(), 'cdk.out')); + + // we leverage the fact that outdir is long-lived to avoid staging assets into it + // that were already staged (copying can be expensive). this is achieved by the fact + // that assets use a source hash as their name. other artifacts, and the manifest, + // will overwrite existing files as needed. + + if (fs.existsSync(this.outdir)) { + if (!fs.statSync(this.outdir).isDirectory()) { + throw new Error(`${this.outdir} must be a directory`); + } + } else { + fs.mkdirSync(this.outdir); + } + } + + public addArtifact(name: string, artifact: Artifact) { + this.artifacts[name] = filterUndefined(artifact); + } + + public build(options: BuildOptions = { }): CloudAssembly { + const manifest: AssemblyManifest = filterUndefined({ + version: CLOUD_ASSEMBLY_VERSION, + artifacts: this.artifacts, + runtime: options.runtimeInfo + }); + + const manifestFilePath = path.join(this.outdir, MANIFEST_FILE); + fs.writeFileSync(manifestFilePath, JSON.stringify(manifest, undefined, 2)); + + return new CloudAssembly(this.outdir); + } +} + +export interface BuildOptions { + /** + * Include runtime information (module versions) in manifest. + * @default true + */ + readonly runtimeInfo?: RuntimeInfo; +} + +/** + * Information about the application's runtime components. + */ +export interface RuntimeInfo { + /** + * The list of libraries loaded in the application, associated with their versions. + */ + readonly libraries: { [name: string]: string }; +} + +/** + * Represents a missing piece of context. + */ +export interface MissingContext { + readonly provider: string; + readonly props: { + account?: string; + region?: string; + [key: string]: any; + }; +} + +/** + * Returns a copy of `obj` without undefined values in maps or arrays. + */ +function filterUndefined(obj: any): any { + if (Array.isArray(obj)) { + return obj.filter(x => x !== undefined).map(x => filterUndefined(x)); + } + + if (typeof(obj) === 'object') { + const ret: any = { }; + for (const [key, value] of Object.entries(obj)) { + if (value === undefined) { + continue; + } + ret[key] = filterUndefined(value); + } + return ret; + } + + return obj; +} + +function ignore(_x: any) { + return; +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/lib/cloudformation-artifact.ts b/packages/@aws-cdk/cx-api/lib/cloudformation-artifact.ts new file mode 100644 index 0000000000000..ff113472cbfb1 --- /dev/null +++ b/packages/@aws-cdk/cx-api/lib/cloudformation-artifact.ts @@ -0,0 +1,55 @@ +import { ASSET_METADATA, AssetMetadataEntry } from './assets'; +import { Artifact, AwsCloudFormationStackProperties, CloudArtifact } from './cloud-artifact'; +import { CloudAssembly } from './cloud-assembly'; + +export class CloudFormationStackArtifact extends CloudArtifact { + public readonly template: any; + public readonly originalName: string; + public readonly logicalIdToPathMap: { [logicalId: string]: string }; + public readonly assets: AssetMetadataEntry[]; + + public name: string; + + constructor(assembly: CloudAssembly, name: string, artifact: Artifact) { + super(assembly, name, artifact); + + if (!artifact.properties || !artifact.properties.templateFile) { + throw new Error(`Invalid CloudFormation stack artifact. Missing "templateFile" property in cloud assembly manifest`); + } + + const properties = this.properties as AwsCloudFormationStackProperties; + this.template = this.assembly.readJson(properties.templateFile); + this.originalName = name; + this.name = this.originalName; + this.logicalIdToPathMap = this.buildLogicalToPathMap(); + this.assets = this.buildAssets(); + } + + private buildAssets() { + const assets = new Array(); + + for (const k of Object.keys(this.metadata)) { + for (const entry of this.metadata[k]) { + if (entry.type === ASSET_METADATA) { + assets.push(entry.data); + } + } + } + + return assets; + } + + private buildLogicalToPathMap() { + const map: { [id: string]: string } = {}; + for (const cpath of Object.keys(this.metadata)) { + const md = this.metadata[cpath]; + for (const e of md) { + if (e.type === 'aws:cdk:logicalId') { + const logical = e.data; + map[logical] = cpath; + } + } + } + return map; + } +} diff --git a/packages/@aws-cdk/cx-api/lib/cxapi.ts b/packages/@aws-cdk/cx-api/lib/cxapi.ts index 459e3bee879e1..2a71c151eb0f3 100644 --- a/packages/@aws-cdk/cx-api/lib/cxapi.ts +++ b/packages/@aws-cdk/cx-api/lib/cxapi.ts @@ -1,129 +1,9 @@ -/** - * File with definitions for the interface between the Cloud Executable and the CDK toolkit. - */ - -import { Artifact } from './artifacts'; -import { Environment } from './environment'; - -/** - * Bump this to the library version if and only if the CX protocol changes. - * - * We could also have used 1, 2, 3, ... here to indicate protocol versions, but - * those then still need to be mapped to software versions to be useful. So we - * might as well use the software version as protocol version and immediately - * generate a useful error message from this. - * - * Note the following: - * - * - The versions are not compared in a semver way, they are used as - * opaque ordered tokens. - * - The version needs to be set to the NEXT releasable version when it's - * updated (as the current verison in package.json has already been released!) - * - The request does not have versioning yet, only the response. - */ -export const PROTO_RESPONSE_VERSION = '0.19.0'; - -/** - * The name of the root manifest file of the assembly. - */ -export const MANIFEST_FILE = 'manifest.json'; - -/** - * The name of the root file with build instructions. - */ -export const BUILD_FILE = 'build.json'; - +// output directory into which to emit synthesis outputs. CDK doesn't allow outdir +// to be specified both through the CDK_OUTDIR environment variable and the through +// aws:cdk:outdir context. export const OUTDIR_ENV = 'CDK_OUTDIR'; export const CONTEXT_ENV = 'CDK_CONTEXT_JSON'; -/** - * Represents a missing piece of context. - */ -export interface MissingContext { - readonly provider: string; - readonly props: { - account?: string; - region?: string; - [key: string]: any; - }; -} - -export interface AssemblyManifest { - /** - * Protocol version - */ - readonly version: string; - - /** - * The set of artifacts in this assembly. - */ - readonly artifacts?: { [id: string]: Artifact }; - - /** - * Runtime information. - */ - readonly runtime?: AppRuntime; -} - -/** - * @deprecated use `AssemblyManifest` - */ -export interface SynthesizeResponse extends AssemblyManifest { - readonly stacks: SynthesizedStack[]; -} - -/** - * A complete synthesized stack - */ -export interface SynthesizedStack { - readonly name: string; - readonly environment: Environment; - readonly missing?: { [key: string]: MissingContext }; - readonly metadata: StackMetadata; - readonly template: any; - readonly autoDeploy?: boolean; - - /** - * Other stacks this stack depends on - */ - readonly dependsOn?: string[]; -} - -/** - * An metadata entry in the construct. - */ -export interface MetadataEntry { - /** - * The type of the metadata entry. - */ - readonly type: string; - - /** - * The data. - */ - readonly data?: any; - - /** - * A stack trace for when the entry was created. - */ - readonly trace: string[]; -} - -/** - * Metadata associated with the objects in the stack's Construct tree - */ -export type StackMetadata = { [path: string]: MetadataEntry[] }; - -/** - * Information about the application's runtime components. - */ -export interface AppRuntime { - /** - * The list of libraries loaded in the application, associated with their versions. - */ - readonly libraries: { [name: string]: string }; -} - /** * Context parameter for the default AWS account to use if a stack's environment is not set. */ @@ -134,52 +14,23 @@ export const DEFAULT_ACCOUNT_CONTEXT_KEY = 'aws:cdk:toolkit:default-account'; */ export const DEFAULT_REGION_CONTEXT_KEY = 'aws:cdk:toolkit:default-region'; -/** - * Metadata key used to print INFO-level messages by the toolkit when an app is syntheized. - */ -export const INFO_METADATA_KEY = 'aws:cdk:info'; - -/** - * Metadata key used to print WARNING-level messages by the toolkit when an app is syntheized. - */ -export const WARNING_METADATA_KEY = 'aws:cdk:warning'; - -/** - * Metadata key used to print ERROR-level messages by the toolkit when an app is syntheized. - */ -export const ERROR_METADATA_KEY = 'aws:cdk:error'; - -/** - * The key used when CDK path is embedded in **CloudFormation template** - * metadata. - */ -export const PATH_METADATA_KEY = 'aws:cdk:path'; - /** * Enables the embedding of the "aws:cdk:path" in CloudFormation template metadata. */ export const PATH_METADATA_ENABLE_CONTEXT = 'aws:cdk:enable-path-metadata'; /** - * Disables the emission of `cdk.out` - */ -export const DISABLE_LEGACY_MANIFEST_CONTEXT = 'aws:cdk:disable-legacy-manifest'; - -/** - * The name of the pre 0.25.0 manifest file. Will only be emitted if - * aws:cdk:disable-legacy-manifest is not defined. - * - * @deprecated Use `MANIFEST_FILE` + * Disable the collection and reporting of version information. */ -export const OUTFILE_NAME = 'cdk.out'; +export const DISABLE_VERSION_REPORTING = 'aws:cdk:disable-version-reporting'; /** - * Disable the collection and reporting of version information. + * If this is set, asset staging is disabled. This means that assets will not be copied to + * the output directory and will be referenced with absolute source paths. */ -export const DISABLE_VERSION_REPORTING = 'aws:cdk:disable-version-reporting'; +export const DISABLE_ASSET_STAGING_CONTEXT = 'aws:cdk:disable-asset-staging'; /** - * If this context key is set, the CDK will stage assets under the specified - * directory. Otherwise, assets will not be staged. + * Omits stack traces from construct metadata entries. */ -export const ASSET_STAGING_DIR_CONTEXT = 'aws:cdk:asset-staging-dir'; +export const DISABLE_METADATA_STACK_TRACE = 'aws:cdk:disable-stack-trace'; diff --git a/packages/@aws-cdk/cx-api/lib/environment.ts b/packages/@aws-cdk/cx-api/lib/environment.ts index 6dac39a141f27..5a11eaa602653 100644 --- a/packages/@aws-cdk/cx-api/lib/environment.ts +++ b/packages/@aws-cdk/cx-api/lib/environment.ts @@ -1,3 +1,8 @@ +/** + * Parser for the artifact environment field. + */ +const AWS_ENV_REGEX = /aws\:\/\/([0-9]+|unknown-account)\/([a-z\-0-9]+)/; + /** * Models an AWS execution environment, for use within the CDK toolkit. */ @@ -11,3 +16,25 @@ export interface Environment { /** The AWS region name where this environment deploys into */ readonly region: string; } + +export class EnvironmentUtils { + public static parse(environment: string): Environment { + const env = AWS_ENV_REGEX.exec(environment); + if (!env) { + throw new Error( + `Unable to parse environment specification "${environment}". ` + + `Expected format: aws://acount/region`); + } + + const [ , account, region ] = env; + if (!account || !region) { + throw new Error(`Invalid environment specification: ${environment}`); + } + + return { account, region, name: environment }; + } + + public static format(account: string, region: string): string { + return `aws://${account}/${region}`; + } +} diff --git a/packages/@aws-cdk/cx-api/lib/index.ts b/packages/@aws-cdk/cx-api/lib/index.ts index b3ebfaa8cf77a..703044a016f22 100644 --- a/packages/@aws-cdk/cx-api/lib/index.ts +++ b/packages/@aws-cdk/cx-api/lib/index.ts @@ -1,9 +1,11 @@ export * from './cxapi'; -export * from './environment'; export * from './context/hosted-zone'; export * from './context/vpc'; export * from './context/ssm-parameter'; export * from './context/availability-zones'; -export * from './metadata/assets'; -export * from './artifacts'; -export * from './build'; +export * from './cloud-artifact'; +export * from './cloudformation-artifact'; +export * from './cloud-assembly'; +export * from './assets'; +export * from './environment'; +export * from './metadata'; diff --git a/packages/@aws-cdk/cx-api/lib/metadata.ts b/packages/@aws-cdk/cx-api/lib/metadata.ts new file mode 100644 index 0000000000000..25e0f730105a4 --- /dev/null +++ b/packages/@aws-cdk/cx-api/lib/metadata.ts @@ -0,0 +1,56 @@ + +/** + * Metadata key used to print INFO-level messages by the toolkit when an app is syntheized. + */ +export const INFO_METADATA_KEY = 'aws:cdk:info'; + +/** + * Metadata key used to print WARNING-level messages by the toolkit when an app is syntheized. + */ +export const WARNING_METADATA_KEY = 'aws:cdk:warning'; + +/** + * Metadata key used to print ERROR-level messages by the toolkit when an app is syntheized. + */ +export const ERROR_METADATA_KEY = 'aws:cdk:error'; + +/** + * The key used when CDK path is embedded in **CloudFormation template** metadata (not cdk metadata). + */ +export const PATH_METADATA_KEY = 'aws:cdk:path'; + +export enum SynthesisMessageLevel { + INFO = 'info', + WARNING = 'warning', + ERROR = 'error' +} +/** + * An metadata entry in the construct. + */ +export interface MetadataEntry { + /** + * The type of the metadata entry. + */ + readonly type: string; + + /** + * The data. + */ + readonly data?: any; + + /** + * A stack trace for when the entry was created. + */ + readonly trace?: string[]; +} + +/** + * Metadata associated with the objects in the stack's Construct tree + */ +export type StackMetadata = { [path: string]: MetadataEntry[] }; + +export interface SynthesisMessage { + readonly level: SynthesisMessageLevel; + readonly id: string; + readonly entry: MetadataEntry; +} diff --git a/packages/aws-cdk/lib/api/util/toposort.ts b/packages/@aws-cdk/cx-api/lib/toposort.ts similarity index 100% rename from packages/aws-cdk/lib/api/util/toposort.ts rename to packages/@aws-cdk/cx-api/lib/toposort.ts diff --git a/packages/@aws-cdk/cx-api/lib/versioning.ts b/packages/@aws-cdk/cx-api/lib/versioning.ts new file mode 100644 index 0000000000000..c84e83f21c8f0 --- /dev/null +++ b/packages/@aws-cdk/cx-api/lib/versioning.ts @@ -0,0 +1,46 @@ +import semver = require('semver'); + +/** + * Bump this to the library version if and only if the CX protocol changes. + * + * We could also have used 1, 2, 3, ... here to indicate protocol versions, but + * those then still need to be mapped to software versions to be useful. So we + * might as well use the software version as protocol version and immediately + * generate a useful error message from this. + * + * Note the following: + * + * - The versions are not compared in a semver way, they are used as + * opaque ordered tokens. + * - The version needs to be set to the NEXT releasable version when it's + * updated (as the current verison in package.json has already been released!) + * - The request does not have versioning yet, only the response. + */ +export const CLOUD_ASSEMBLY_VERSION = '0.33.0'; + +/** + * Look at the type of response we get and upgrade it to the latest expected version + */ +export function verifyManifestVersion(manifetVersion: string) { + const frameworkVersion = parseSemver(manifetVersion); + const toolkitVersion = parseSemver(CLOUD_ASSEMBLY_VERSION); + + // if framework > cli, we require a newer cli version + if (semver.gt(frameworkVersion, toolkitVersion)) { + throw new Error(`CLI >= ${frameworkVersion} is required to interact with this app`); + } + + // if framework < cli, we require a newer framework version + if (semver.lt(frameworkVersion, toolkitVersion)) { + throw new Error(`App used framework v${frameworkVersion} but it must be >= v${CLOUD_ASSEMBLY_VERSION} in order to interact with this CLI`); + } +} + +function parseSemver(version: string) { + const ver = semver.coerce(version); + if (!ver) { + throw new Error(`Could not parse "${version}" as semver`); + } + + return ver; +} diff --git a/packages/@aws-cdk/cx-api/package-lock.json b/packages/@aws-cdk/cx-api/package-lock.json new file mode 100644 index 0000000000000..07a807238d5a5 --- /dev/null +++ b/packages/@aws-cdk/cx-api/package-lock.json @@ -0,0 +1,5035 @@ +{ + "name": "@aws-cdk/cx-api", + "version": "0.32.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/core": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.5.tgz", + "integrity": "sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helpers": "^7.4.4", + "@babel/parser": "^7.4.5", + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.4.5", + "@babel/types": "^7.4.4", + "convert-source-map": "^1.1.0", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.11", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", + "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz", + "integrity": "sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==", + "dev": true + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/helpers": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.4.tgz", + "integrity": "sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==", + "dev": true, + "requires": { + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", + "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.5.tgz", + "integrity": "sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==", + "dev": true + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", + "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", + "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/traverse": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.5.tgz", + "integrity": "sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.4.5", + "@babel/types": "^7.4.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", + "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "@cnakazawa/watch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz", + "integrity": "sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==", + "dev": true, + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, + "@jest/console": { + "version": "24.7.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.7.1.tgz", + "integrity": "sha512-iNhtIy2M8bXlAOULWVTUxmnelTLFneTNEkHCgPmgd+zNwy9zVddJ6oS5rZ9iwoscNdT5mMwUd0C51v/fSlzItg==", + "dev": true, + "requires": { + "@jest/source-map": "^24.3.0", + "chalk": "^2.0.1", + "slash": "^2.0.0" + } + }, + "@jest/core": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.8.0.tgz", + "integrity": "sha512-R9rhAJwCBQzaRnrRgAdVfnglUuATXdwTRsYqs6NMdVcAl5euG8LtWDe+fVkN27YfKVBW61IojVsXKaOmSnqd/A==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/reporters": "^24.8.0", + "@jest/test-result": "^24.8.0", + "@jest/transform": "^24.8.0", + "@jest/types": "^24.8.0", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-changed-files": "^24.8.0", + "jest-config": "^24.8.0", + "jest-haste-map": "^24.8.0", + "jest-message-util": "^24.8.0", + "jest-regex-util": "^24.3.0", + "jest-resolve-dependencies": "^24.8.0", + "jest-runner": "^24.8.0", + "jest-runtime": "^24.8.0", + "jest-snapshot": "^24.8.0", + "jest-util": "^24.8.0", + "jest-validate": "^24.8.0", + "jest-watcher": "^24.8.0", + "micromatch": "^3.1.10", + "p-each-series": "^1.0.0", + "pirates": "^4.0.1", + "realpath-native": "^1.1.0", + "rimraf": "^2.5.4", + "strip-ansi": "^5.0.0" + } + }, + "@jest/environment": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.8.0.tgz", + "integrity": "sha512-vlGt2HLg7qM+vtBrSkjDxk9K0YtRBi7HfRFaDxoRtyi+DyVChzhF20duvpdAnKVBV6W5tym8jm0U9EfXbDk1tw==", + "dev": true, + "requires": { + "@jest/fake-timers": "^24.8.0", + "@jest/transform": "^24.8.0", + "@jest/types": "^24.8.0", + "jest-mock": "^24.8.0" + } + }, + "@jest/fake-timers": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.8.0.tgz", + "integrity": "sha512-2M4d5MufVXwi6VzZhJ9f5S/wU4ud2ck0kxPof1Iz3zWx6Y+V2eJrES9jEktB6O3o/oEyk+il/uNu9PvASjWXQw==", + "dev": true, + "requires": { + "@jest/types": "^24.8.0", + "jest-message-util": "^24.8.0", + "jest-mock": "^24.8.0" + } + }, + "@jest/reporters": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.8.0.tgz", + "integrity": "sha512-eZ9TyUYpyIIXfYCrw0UHUWUvE35vx5I92HGMgS93Pv7du+GHIzl+/vh8Qj9MCWFK/4TqyttVBPakWMOfZRIfxw==", + "dev": true, + "requires": { + "@jest/environment": "^24.8.0", + "@jest/test-result": "^24.8.0", + "@jest/transform": "^24.8.0", + "@jest/types": "^24.8.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.2", + "istanbul-lib-coverage": "^2.0.2", + "istanbul-lib-instrument": "^3.0.1", + "istanbul-lib-report": "^2.0.4", + "istanbul-lib-source-maps": "^3.0.1", + "istanbul-reports": "^2.1.1", + "jest-haste-map": "^24.8.0", + "jest-resolve": "^24.8.0", + "jest-runtime": "^24.8.0", + "jest-util": "^24.8.0", + "jest-worker": "^24.6.0", + "node-notifier": "^5.2.1", + "slash": "^2.0.0", + "source-map": "^0.6.0", + "string-length": "^2.0.0" + } + }, + "@jest/source-map": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.3.0.tgz", + "integrity": "sha512-zALZt1t2ou8le/crCeeiRYzvdnTzaIlpOWaet45lNSqNJUnXbppUUFR4ZUAlzgDmKee4Q5P/tKXypI1RiHwgag==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.1.15", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.8.0.tgz", + "integrity": "sha512-+YdLlxwizlfqkFDh7Mc7ONPQAhA4YylU1s529vVM1rsf67vGZH/2GGm5uO8QzPeVyaVMobCQ7FTxl38QrKRlng==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/types": "^24.8.0", + "@types/istanbul-lib-coverage": "^2.0.0" + } + }, + "@jest/test-sequencer": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.8.0.tgz", + "integrity": "sha512-OzL/2yHyPdCHXEzhoBuq37CE99nkme15eHkAzXRVqthreWZamEMA0WoetwstsQBCXABhczpK03JNbc4L01vvLg==", + "dev": true, + "requires": { + "@jest/test-result": "^24.8.0", + "jest-haste-map": "^24.8.0", + "jest-runner": "^24.8.0", + "jest-runtime": "^24.8.0" + } + }, + "@jest/transform": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.8.0.tgz", + "integrity": "sha512-xBMfFUP7TortCs0O+Xtez2W7Zu1PLH9bvJgtraN1CDST6LBM/eTOZ9SfwS/lvV8yOfcDpFmwf9bq5cYbXvqsvA==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^24.8.0", + "babel-plugin-istanbul": "^5.1.0", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.15", + "jest-haste-map": "^24.8.0", + "jest-regex-util": "^24.3.0", + "jest-util": "^24.8.0", + "micromatch": "^3.1.10", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "2.4.1" + } + }, + "@jest/types": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.8.0.tgz", + "integrity": "sha512-g17UxVr2YfBtaMUxn9u/4+siG1ptg9IGYAYwvpwn61nBg779RXnjE/m7CxYcIzEt0AbHZZAHSEZNhkE2WxURVg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^12.0.9" + } + }, + "@types/babel__core": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.2.tgz", + "integrity": "sha512-cfCCrFmiGY/yq0NuKNxIQvZFy9kY/1immpSpTngOnyIbD4+eJOG5mxphhHDv3CHL9GltO4GcKr54kGBg3RNdbg==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.0.2.tgz", + "integrity": "sha512-NHcOfab3Zw4q5sEE2COkpfXjoE7o+PmqD9DQW4koUT3roNxwziUdXGnRndMat/LJNUtePwn1TlP4do3uoe3KZQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.6.tgz", + "integrity": "sha512-XYVgHF2sQ0YblLRMLNPB3CkFMewzFmlDsH/TneZFHUXDlABQgh88uOxuez7ZcXxayLFrqLwtDH1t+FmlFwNZxw==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", + "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", + "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", + "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "24.0.13", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.0.13.tgz", + "integrity": "sha512-3m6RPnO35r7Dg+uMLj1+xfZaOgIHHHut61djNjzwExXN4/Pm9has9C6I1KMYSfz7mahDhWUOVg4HW/nZdv5Pww==", + "dev": true, + "requires": { + "@types/jest-diff": "*" + } + }, + "@types/jest-diff": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jest-diff/-/jest-diff-20.0.1.tgz", + "integrity": "sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA==", + "dev": true + }, + "@types/semver": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.0.tgz", + "integrity": "sha512-OO0srjOGH99a4LUN2its3+r6CBYcplhJ466yLqs+zvAWgphCpS8hYZEZ797tRDP/QKcqTdb/YCN6ifASoAWkrQ==", + "dev": true + }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "dev": true + }, + "@types/yargs": { + "version": "12.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.12.tgz", + "integrity": "sha512-SOhuU4wNBxhhTHxYaiG5NY4HBhDIDnJF60GU+2LqHAdKKer86//e4yg69aENCtQ04n0ovz+tq2YPME5t5yp4pw==", + "dev": true + }, + "abab": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", + "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", + "dev": true + }, + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-globals": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz", + "integrity": "sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==", + "dev": true, + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + }, + "dependencies": { + "acorn": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", + "dev": true + }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "dev": true + }, + "babel-jest": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.8.0.tgz", + "integrity": "sha512-+5/kaZt4I9efoXzPlZASyK/lN9qdRKmmUav9smVc0ruPQD7IsfucQ87gpOE8mn2jbDuS6M/YOW6n3v9ZoIfgnw==", + "dev": true, + "requires": { + "@jest/transform": "^24.8.0", + "@jest/types": "^24.8.0", + "@types/babel__core": "^7.1.0", + "babel-plugin-istanbul": "^5.1.0", + "babel-preset-jest": "^24.6.0", + "chalk": "^2.4.2", + "slash": "^2.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.1.4.tgz", + "integrity": "sha512-dySz4VJMH+dpndj0wjJ8JPs/7i1TdSPb1nRrn56/92pKOF9VKC1FMFJmMXjzlGGusnCAqujP6PBCiKq0sVA+YQ==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.3.0", + "test-exclude": "^5.2.3" + } + }, + "babel-plugin-jest-hoist": { + "version": "24.6.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.6.0.tgz", + "integrity": "sha512-3pKNH6hMt9SbOv0F3WVmy5CWQ4uogS3k0GY5XLyQHJ9EGpAT9XWkFd2ZiXXtkwFHdAHa5j7w7kfxSP5lAIwu7w==", + "dev": true, + "requires": { + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "24.6.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.6.0.tgz", + "integrity": "sha512-pdZqLEdmy1ZK5kyRUfvBb2IfTPb2BUvIJczlPspS8fWmBQslNNDBqVfh7BW5leOVJMDZKzjD8XEyABTk6gQ5yw==", + "dev": true, + "requires": { + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "babel-plugin-jest-hoist": "^24.6.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "bser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", + "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, + "requires": { + "rsvp": "^4.8.4" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true, + "optional": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, + "cssom": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", + "dev": true + }, + "cssstyle": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.2.tgz", + "integrity": "sha512-43wY3kl1CVQSvL7wUY1qXkxVGkStjpkDmVjiIKX8R97uhajy8Bybay78uOtqvh7Q5GK75dNPfW0geWjE6qQQow==", + "dev": true, + "requires": { + "cssom": "0.3.x" + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", + "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true + }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, + "diff-sequences": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.3.0.tgz", + "integrity": "sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw==", + "dev": true + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", + "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", + "dev": true, + "requires": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + }, + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "exec-sh": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.2.tgz", + "integrity": "sha512-9sLAvzhI5nc8TpuQUh4ahMdCrWT00wPWz7j47/emR5+2qEfoZP5zzUXvx+vdx+H6ohhnsYC31iX04QLYJK8zTg==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expect": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.8.0.tgz", + "integrity": "sha512-/zYvP8iMDrzaaxHVa724eJBCKqSHmO0FA7EDkBiRHxg6OipmMn1fN+C8T9L9K8yr7UONkOifu6+LLH+z76CnaA==", + "dev": true, + "requires": { + "@jest/types": "^24.8.0", + "ansi-styles": "^3.2.0", + "jest-get-type": "^24.8.0", + "jest-matcher-utils": "^24.8.0", + "jest-message-util": "^24.8.0", + "jest-regex-util": "^24.3.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fb-watchman": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", + "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "dev": true, + "requires": { + "bser": "^2.0.0" + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true, + "optional": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, + "handlebars": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", + "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", + "dev": true, + "requires": { + "neo-async": "^2.6.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hosted-git-info": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", + "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + } + }, + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", + "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", + "dev": true, + "requires": { + "handlebars": "^4.1.2" + } + }, + "jest": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-24.8.0.tgz", + "integrity": "sha512-o0HM90RKFRNWmAWvlyV8i5jGZ97pFwkeVoGvPW1EtLTgJc2+jcuqcbbqcSZLE/3f2S5pt0y2ZBETuhpWNl1Reg==", + "dev": true, + "requires": { + "import-local": "^2.0.0", + "jest-cli": "^24.8.0" + }, + "dependencies": { + "jest-cli": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.8.0.tgz", + "integrity": "sha512-+p6J00jSMPQ116ZLlHJJvdf8wbjNbZdeSX9ptfHX06/MSNaXmKihQzx5vQcw0q2G6JsdVkUIdWbOWtSnaYs3yA==", + "dev": true, + "requires": { + "@jest/core": "^24.8.0", + "@jest/test-result": "^24.8.0", + "@jest/types": "^24.8.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "import-local": "^2.0.0", + "is-ci": "^2.0.0", + "jest-config": "^24.8.0", + "jest-util": "^24.8.0", + "jest-validate": "^24.8.0", + "prompts": "^2.0.1", + "realpath-native": "^1.1.0", + "yargs": "^12.0.2" + } + } + } + }, + "jest-changed-files": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.8.0.tgz", + "integrity": "sha512-qgANC1Yrivsq+UrLXsvJefBKVoCsKB0Hv+mBb6NMjjZ90wwxCDmU3hsCXBya30cH+LnPYjwgcU65i6yJ5Nfuug==", + "dev": true, + "requires": { + "@jest/types": "^24.8.0", + "execa": "^1.0.0", + "throat": "^4.0.0" + } + }, + "jest-config": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.8.0.tgz", + "integrity": "sha512-Czl3Nn2uEzVGsOeaewGWoDPD8GStxCpAe0zOYs2x2l0fZAgPbCr3uwUkgNKV3LwE13VXythM946cd5rdGkkBZw==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^24.8.0", + "@jest/types": "^24.8.0", + "babel-jest": "^24.8.0", + "chalk": "^2.0.1", + "glob": "^7.1.1", + "jest-environment-jsdom": "^24.8.0", + "jest-environment-node": "^24.8.0", + "jest-get-type": "^24.8.0", + "jest-jasmine2": "^24.8.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.8.0", + "jest-util": "^24.8.0", + "jest-validate": "^24.8.0", + "micromatch": "^3.1.10", + "pretty-format": "^24.8.0", + "realpath-native": "^1.1.0" + } + }, + "jest-diff": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.8.0.tgz", + "integrity": "sha512-wxetCEl49zUpJ/bvUmIFjd/o52J+yWcoc5ZyPq4/W1LUKGEhRYDIbP1KcF6t+PvqNrGAFk4/JhtxDq/Nnzs66g==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "diff-sequences": "^24.3.0", + "jest-get-type": "^24.8.0", + "pretty-format": "^24.8.0" + } + }, + "jest-docblock": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.3.0.tgz", + "integrity": "sha512-nlANmF9Yq1dufhFlKG9rasfQlrY7wINJbo3q01tu56Jv5eBU5jirylhF2O5ZBnLxzOVBGRDz/9NAwNyBtG4Nyg==", + "dev": true, + "requires": { + "detect-newline": "^2.1.0" + } + }, + "jest-each": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.8.0.tgz", + "integrity": "sha512-NrwK9gaL5+XgrgoCsd9svsoWdVkK4gnvyhcpzd6m487tXHqIdYeykgq3MKI1u4I+5Zf0tofr70at9dWJDeb+BA==", + "dev": true, + "requires": { + "@jest/types": "^24.8.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.8.0", + "jest-util": "^24.8.0", + "pretty-format": "^24.8.0" + } + }, + "jest-environment-jsdom": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.8.0.tgz", + "integrity": "sha512-qbvgLmR7PpwjoFjM/sbuqHJt/NCkviuq9vus9NBn/76hhSidO+Z6Bn9tU8friecegbJL8gzZQEMZBQlFWDCwAQ==", + "dev": true, + "requires": { + "@jest/environment": "^24.8.0", + "@jest/fake-timers": "^24.8.0", + "@jest/types": "^24.8.0", + "jest-mock": "^24.8.0", + "jest-util": "^24.8.0", + "jsdom": "^11.5.1" + } + }, + "jest-environment-node": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.8.0.tgz", + "integrity": "sha512-vIGUEScd1cdDgR6sqn2M08sJTRLQp6Dk/eIkCeO4PFHxZMOgy+uYLPMC4ix3PEfM5Au/x3uQ/5Tl0DpXXZsJ/Q==", + "dev": true, + "requires": { + "@jest/environment": "^24.8.0", + "@jest/fake-timers": "^24.8.0", + "@jest/types": "^24.8.0", + "jest-mock": "^24.8.0", + "jest-util": "^24.8.0" + } + }, + "jest-get-type": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.8.0.tgz", + "integrity": "sha512-RR4fo8jEmMD9zSz2nLbs2j0zvPpk/KCEz3a62jJWbd2ayNo0cb+KFRxPHVhE4ZmgGJEQp0fosmNz84IfqM8cMQ==", + "dev": true + }, + "jest-haste-map": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.8.0.tgz", + "integrity": "sha512-ZBPRGHdPt1rHajWelXdqygIDpJx8u3xOoLyUBWRW28r3tagrgoepPrzAozW7kW9HrQfhvmiv1tncsxqHJO1onQ==", + "dev": true, + "requires": { + "@jest/types": "^24.8.0", + "anymatch": "^2.0.0", + "fb-watchman": "^2.0.0", + "fsevents": "^1.2.7", + "graceful-fs": "^4.1.15", + "invariant": "^2.2.4", + "jest-serializer": "^24.4.0", + "jest-util": "^24.8.0", + "jest-worker": "^24.6.0", + "micromatch": "^3.1.10", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.8.0.tgz", + "integrity": "sha512-cEky88npEE5LKd5jPpTdDCLvKkdyklnaRycBXL6GNmpxe41F0WN44+i7lpQKa/hcbXaQ+rc9RMaM4dsebrYong==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^24.8.0", + "@jest/test-result": "^24.8.0", + "@jest/types": "^24.8.0", + "chalk": "^2.0.1", + "co": "^4.6.0", + "expect": "^24.8.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^24.8.0", + "jest-matcher-utils": "^24.8.0", + "jest-message-util": "^24.8.0", + "jest-runtime": "^24.8.0", + "jest-snapshot": "^24.8.0", + "jest-util": "^24.8.0", + "pretty-format": "^24.8.0", + "throat": "^4.0.0" + } + }, + "jest-leak-detector": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.8.0.tgz", + "integrity": "sha512-cG0yRSK8A831LN8lIHxI3AblB40uhv0z+SsQdW3GoMMVcK+sJwrIIyax5tu3eHHNJ8Fu6IMDpnLda2jhn2pD/g==", + "dev": true, + "requires": { + "pretty-format": "^24.8.0" + } + }, + "jest-matcher-utils": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.8.0.tgz", + "integrity": "sha512-lex1yASY51FvUuHgm0GOVj7DCYEouWSlIYmCW7APSqB9v8mXmKSn5+sWVF0MhuASG0bnYY106/49JU1FZNl5hw==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-diff": "^24.8.0", + "jest-get-type": "^24.8.0", + "pretty-format": "^24.8.0" + } + }, + "jest-message-util": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.8.0.tgz", + "integrity": "sha512-p2k71rf/b6ns8btdB0uVdljWo9h0ovpnEe05ZKWceQGfXYr4KkzgKo3PBi8wdnd9OtNh46VpNIJynUn/3MKm1g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.8.0", + "@jest/types": "^24.8.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.8.0.tgz", + "integrity": "sha512-6kWugwjGjJw+ZkK4mDa0Df3sDlUTsV47MSrT0nGQ0RBWJbpODDQ8MHDVtGtUYBne3IwZUhtB7elxHspU79WH3A==", + "dev": true, + "requires": { + "@jest/types": "^24.8.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", + "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", + "dev": true + }, + "jest-regex-util": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.3.0.tgz", + "integrity": "sha512-tXQR1NEOyGlfylyEjg1ImtScwMq8Oh3iJbGTjN7p0J23EuVX1MA8rwU69K4sLbCmwzgCUbVkm0FkSF9TdzOhtg==", + "dev": true + }, + "jest-resolve": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.8.0.tgz", + "integrity": "sha512-+hjSzi1PoRvnuOICoYd5V/KpIQmkAsfjFO71458hQ2Whi/yf1GDeBOFj8Gxw4LrApHsVJvn5fmjcPdmoUHaVKw==", + "dev": true, + "requires": { + "@jest/types": "^24.8.0", + "browser-resolve": "^1.11.3", + "chalk": "^2.0.1", + "jest-pnp-resolver": "^1.2.1", + "realpath-native": "^1.1.0" + } + }, + "jest-resolve-dependencies": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.8.0.tgz", + "integrity": "sha512-hyK1qfIf/krV+fSNyhyJeq3elVMhK9Eijlwy+j5jqmZ9QsxwKBiP6qukQxaHtK8k6zql/KYWwCTQ+fDGTIJauw==", + "dev": true, + "requires": { + "@jest/types": "^24.8.0", + "jest-regex-util": "^24.3.0", + "jest-snapshot": "^24.8.0" + } + }, + "jest-runner": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.8.0.tgz", + "integrity": "sha512-utFqC5BaA3JmznbissSs95X1ZF+d+4WuOWwpM9+Ak356YtMhHE/GXUondZdcyAAOTBEsRGAgH/0TwLzfI9h7ow==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.8.0", + "@jest/test-result": "^24.8.0", + "@jest/types": "^24.8.0", + "chalk": "^2.4.2", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-config": "^24.8.0", + "jest-docblock": "^24.3.0", + "jest-haste-map": "^24.8.0", + "jest-jasmine2": "^24.8.0", + "jest-leak-detector": "^24.8.0", + "jest-message-util": "^24.8.0", + "jest-resolve": "^24.8.0", + "jest-runtime": "^24.8.0", + "jest-util": "^24.8.0", + "jest-worker": "^24.6.0", + "source-map-support": "^0.5.6", + "throat": "^4.0.0" + } + }, + "jest-runtime": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.8.0.tgz", + "integrity": "sha512-Mq0aIXhvO/3bX44ccT+czU1/57IgOMyy80oM0XR/nyD5zgBcesF84BPabZi39pJVA6UXw+fY2Q1N+4BiVUBWOA==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.8.0", + "@jest/source-map": "^24.3.0", + "@jest/transform": "^24.8.0", + "@jest/types": "^24.8.0", + "@types/yargs": "^12.0.2", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "jest-config": "^24.8.0", + "jest-haste-map": "^24.8.0", + "jest-message-util": "^24.8.0", + "jest-mock": "^24.8.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.8.0", + "jest-snapshot": "^24.8.0", + "jest-util": "^24.8.0", + "jest-validate": "^24.8.0", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "strip-bom": "^3.0.0", + "yargs": "^12.0.2" + } + }, + "jest-serializer": { + "version": "24.4.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.4.0.tgz", + "integrity": "sha512-k//0DtglVstc1fv+GY/VHDIjrtNjdYvYjMlbLUed4kxrE92sIUewOi5Hj3vrpB8CXfkJntRPDRjCrCvUhBdL8Q==", + "dev": true + }, + "jest-snapshot": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.8.0.tgz", + "integrity": "sha512-5ehtWoc8oU9/cAPe6fez6QofVJLBKyqkY2+TlKTOf0VllBB/mqUNdARdcjlZrs9F1Cv+/HKoCS/BknT0+tmfPg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^24.8.0", + "chalk": "^2.0.1", + "expect": "^24.8.0", + "jest-diff": "^24.8.0", + "jest-matcher-utils": "^24.8.0", + "jest-message-util": "^24.8.0", + "jest-resolve": "^24.8.0", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^24.8.0", + "semver": "^5.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, + "jest-util": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.8.0.tgz", + "integrity": "sha512-DYZeE+XyAnbNt0BG1OQqKy/4GVLPtzwGx5tsnDrFcax36rVE3lTA5fbvgmbVPUZf9w77AJ8otqR4VBbfFJkUZA==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/fake-timers": "^24.8.0", + "@jest/source-map": "^24.3.0", + "@jest/test-result": "^24.8.0", + "@jest/types": "^24.8.0", + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" + } + }, + "jest-validate": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.8.0.tgz", + "integrity": "sha512-+/N7VOEMW1Vzsrk3UWBDYTExTPwf68tavEPKDnJzrC6UlHtUDU/fuEdXqFoHzv9XnQ+zW6X3qMZhJ3YexfeLDA==", + "dev": true, + "requires": { + "@jest/types": "^24.8.0", + "camelcase": "^5.0.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.8.0", + "leven": "^2.1.0", + "pretty-format": "^24.8.0" + } + }, + "jest-watcher": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.8.0.tgz", + "integrity": "sha512-SBjwHt5NedQoVu54M5GEx7cl7IGEFFznvd/HNT8ier7cCAx/Qgu9ZMlaTQkvK22G1YOpcWBLQPFSImmxdn3DAw==", + "dev": true, + "requires": { + "@jest/test-result": "^24.8.0", + "@jest/types": "^24.8.0", + "@types/yargs": "^12.0.9", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "jest-util": "^24.8.0", + "string-length": "^2.0.0" + } + }, + "jest-worker": { + "version": "24.6.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.6.0.tgz", + "integrity": "sha512-jDwgW5W9qGNvpI1tNnvajh0a5IE/PuGLFmHk6aR/BZFz8tSgGw17GsDPXAJ6p91IvYDjOw8GpFbvvZGAK+DPQQ==", + "dev": true, + "requires": { + "merge-stream": "^1.0.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true + }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "dev": true + }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.x" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "dev": true + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "dev": true, + "requires": { + "mime-db": "1.40.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + }, + "mixin-deep": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "neo-async": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", + "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "node-notifier": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", + "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", + "dev": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nwsapi": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz", + "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + }, + "dependencies": { + "minimist": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", + "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", + "dev": true + } + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-each-series": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", + "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "dev": true, + "requires": { + "p-reduce": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", + "dev": true + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "pretty-format": { + "version": "24.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.8.0.tgz", + "integrity": "sha512-P952T7dkrDEplsR+TuY7q3VXDae5Sr7zmQb12JU/NDQa/3CH7/QW0yvqLcGN6jL+zQFKaoJcPc+yJxMTGmosqw==", + "dev": true, + "requires": { + "@jest/types": "^24.8.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + } + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + }, + "prompts": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.1.0.tgz", + "integrity": "sha512-+x5TozgqYdOwWsQFZizE/Tra3fKvAoy037kOyU6cgz84n8f6zxngLOV4O32kTwt9FcLCxAqw0P/c8rOr9y+Gfg==", + "dev": true, + "requires": { + "kleur": "^3.0.2", + "sisteransi": "^1.0.0" + } + }, + "psl": { + "version": "1.1.32", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", + "integrity": "sha512-MHACAkHpihU/REGGPLj4sEfc/XKW2bheigvHO1dUqjaKigMp1C8+WLQYRGgeKFMsw5PMfegZcaN8IDXK/cD0+g==", + "dev": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true + }, + "react-is": { + "version": "16.8.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.6.tgz", + "integrity": "sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "realpath-native": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", + "dev": true, + "requires": { + "util.promisify": "^1.0.0" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "request-promise-native": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", + "dev": true, + "requires": { + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.0.tgz", + "integrity": "sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + } + }, + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rsvp": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.4.tgz", + "integrity": "sha512-6FomvYPfs+Jy9TfXmBpBuMWNH94SgCsZmJKcanySzgNNP6LjWxBvyLTa9KaMfDDM5oxRfrKDB0r/qeRsLwnBfA==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "dev": true, + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, + "semver": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.1.0.tgz", + "integrity": "sha512-kCqEOOHoBcFs/2Ccuk4Xarm/KiWRSLEX9CAZF8xkJ6ZPlIoTZ8V5f7J16vYLJqDbR7KrxTJpR2lqjIEm2Qx9cQ==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, + "sisteransi": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.0.tgz", + "integrity": "sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, + "requires": { + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.4.tgz", + "integrity": "sha512-7j8LYJLeY/Yb6ACbQ7F76qy5jHkp0U6jgBfJsk97bwWlVUnUWsAgpyaCvo17h0/RQGnQ036tVDomiwoI4pDkQA==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, + "string-length": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", + "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", + "dev": true, + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "symbol-tree": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", + "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", + "dev": true + }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "dev": true, + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + } + }, + "throat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", + "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", + "dev": true + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "uglify-js": { + "version": "3.5.15", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.5.15.tgz", + "integrity": "sha512-fe7aYFotptIddkwcm6YuA0HmknBZ52ZzOsUxZEdhhkSsz7RfjHDX2QDxwKTiv4JQ5t5NhfmpgAK+J7LiDhKSqg==", + "dev": true, + "optional": true, + "requires": { + "commander": "~2.20.0", + "source-map": "~0.6.1" + } + }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "requires": { + "browser-process-hrtime": "^0.1.2" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.x" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", + "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0" + } + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + }, + "dependencies": { + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + } + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/packages/@aws-cdk/cx-api/package.json b/packages/@aws-cdk/cx-api/package.json index 6a7a4f442718c..a3ed181c6fecc 100644 --- a/packages/@aws-cdk/cx-api/package.json +++ b/packages/@aws-cdk/cx-api/package.json @@ -42,10 +42,27 @@ "url": "https://aws.amazon.com", "organization": true }, + "dependencies": { + "semver": "^6.0.0" + }, + "jest": { + "moduleFileExtensions": [ + "js" + ], + "coverageThreshold": { + "global": { + "branches": 80, + "statements": 80 + } + } + }, "license": "Apache-2.0", "devDependencies": { "cdk-build-tools": "^0.32.0", - "pkglint": "^0.32.0" + "@types/jest": "^24.0.11", + "@types/semver": "^6.0.0", + "pkglint": "^0.32.0", + "jest": "^24.7.1" }, "repository": { "url": "https://github.com/awslabs/aws-cdk.git", @@ -57,6 +74,9 @@ "cdk" ], "homepage": "https://github.com/awslabs/aws-cdk", + "bundledDependencies": [ + "semver" + ], "engines": { "node": ">= 8.10.0" } diff --git a/packages/@aws-cdk/cx-api/test/__snapshots__/cloud-assembly.test.js.snap b/packages/@aws-cdk/cx-api/test/__snapshots__/cloud-assembly.test.js.snap new file mode 100644 index 0000000000000..8bf4506feca6e --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/__snapshots__/cloud-assembly.test.js.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`assembly with missing context 1`] = ` +Object { + "missing:context:key": Object { + "foo": 123, + }, + "missing:context:key2": Object { + "foo": 6688, + }, +} +`; + +exports[`assets 1`] = ` +Array [ + Object { + "id": "logical-id-of-the-asset", + "packaging": "zip", + "path": "asset-dir", + "sourceHash": "xoxoxox", + }, + Object { + "id": "logical-id-of-the-asset-x1234", + "packaging": "docker", + "path": "docker-asset", + "sourceHash": "docker-asset-source", + }, +] +`; + +exports[`messages 1`] = ` +Array [ + Object { + "entry": Object { + "data": "boom", + "trace": "bam", + "type": "aws:cdk:warning", + }, + "id": "foo", + "level": "warning", + }, + Object { + "entry": Object { + "data": "error!!", + "trace": "bam!Error", + "type": "aws:cdk:error", + }, + "id": "foo", + "level": "error", + }, + Object { + "entry": Object { + "data": "info?", + "trace": "bam!Error", + "type": "aws:cdk:info", + }, + "id": "bar", + "level": "info", + }, +] +`; diff --git a/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts b/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts new file mode 100644 index 0000000000000..90d67873cd137 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts @@ -0,0 +1,100 @@ +import fs = require('fs'); +import os = require('os'); +import path = require('path'); +import { ArtifactType, CloudAssemblyBuilder } from '../lib'; +import { CLOUD_ASSEMBLY_VERSION } from '../lib/versioning'; + +test('cloud assembly builder', () => { + // GIVEN + const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'cloud-assembly-builder-tests')); + const session = new CloudAssemblyBuilder(outdir); + const templateFile = 'foo.template.json'; + + // WHEN + session.addArtifact('my-first-artifact', { + type: ArtifactType.AwsCloudFormationStack, + environment: 'aws://1222344/us-east-1', + dependencies: ['minimal-artifact'], + metadata: { + foo: [ { data: 123, type: 'foo', trace: [] } ] + }, + properties: { + templateFile, + parameters: { + prop1: '1234', + prop2: '555' + } + }, + missing: { + foo: { + provider: 'context-provider', + props: { + a: 'A', + b: 2 + } + } + } + }); + + session.addArtifact('minimal-artifact', { + type: ArtifactType.AwsCloudFormationStack, + environment: 'aws://111/helo-world', + properties: { + templateFile + } + }); + + fs.writeFileSync(path.join(session.outdir, templateFile), JSON.stringify({ + Resources: { + MyTopic: { + Type: 'AWS::S3::Topic' + } + } + })); + + const assembly = session.build(); + + const manifest = assembly.manifest; + + // THEN + // verify the manifest looks right + expect(manifest).toStrictEqual({ + version: CLOUD_ASSEMBLY_VERSION, + artifacts: { + 'my-first-artifact': { + type: 'aws:cloudformation:stack', + environment: 'aws://1222344/us-east-1', + dependencies: ['minimal-artifact'], + metadata: { foo: [ { data: 123, type: 'foo', trace: [] } ] }, + properties: { + templateFile: 'foo.template.json', + parameters: { + prop1: '1234', + prop2: '555' + }, + }, + missing: { + foo: { provider: 'context-provider', props: { a: 'A', b: 2 } } + } + }, + 'minimal-artifact': { + type: 'aws:cloudformation:stack', + environment: 'aws://111/helo-world', + properties: { templateFile: 'foo.template.json' } + } + } + }); + + // verify we have a template file + expect(assembly.getStack('minimal-artifact').template).toStrictEqual({ + Resources: { + MyTopic: { + Type: 'AWS::S3::Topic' + } + } + }); +}); + +test('outdir must be a directory', () => { + expect(() => new CloudAssemblyBuilder(__filename)).toThrow('must be a directory'); +}); diff --git a/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts b/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts new file mode 100644 index 0000000000000..671abaf29aa8a --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts @@ -0,0 +1,100 @@ +import path = require('path'); +import { CloudAssembly } from '../lib'; +import { CLOUD_ASSEMBLY_VERSION, verifyManifestVersion } from '../lib/versioning'; + +const FIXTURES = path.join(__dirname, 'fixtures'); + +test('empty assembly', () => { + const assembly = new CloudAssembly(path.join(FIXTURES, 'empty')); + expect(assembly.artifacts).toEqual([]); + expect(assembly.missing).toBeUndefined(); + expect(assembly.runtime).toEqual({ libraries: { } }); + expect(assembly.stacks).toEqual([]); + expect(assembly.version).toEqual(CLOUD_ASSEMBLY_VERSION); +}); + +test('assembly a single cloudformation stack', () => { + const assembly = new CloudAssembly(path.join(FIXTURES, 'single-stack')); + expect(assembly.artifacts).toHaveLength(1); + expect(assembly.stacks).toHaveLength(1); + expect(assembly.missing).toBeUndefined(); + expect(assembly.runtime).toEqual({ libraries: { } }); + expect(assembly.version).toEqual(CLOUD_ASSEMBLY_VERSION); + expect(assembly.artifacts[0]).toEqual(assembly.stacks[0]); + + const stack = assembly.stacks[0]; + expect(stack.assets).toHaveLength(0); + expect(stack.autoDeploy).toBeTruthy(); + expect(stack.depends).toEqual([]); + expect(stack.environment).toEqual({ account: '37736633', region: 'us-region-1', name: 'aws://37736633/us-region-1' }); + expect(stack.template).toEqual({ Resources: { MyBucket: { Type: "AWS::S3::Bucket" } } }); + expect(stack.messages).toEqual([]); + expect(stack.metadata).toEqual({}); + expect(stack.missing).toEqual({}); + expect(stack.originalName).toEqual('MyStackName'); + expect(stack.name).toEqual('MyStackName'); + expect(stack.logicalIdToPathMap).toEqual({}); +}); + +test('assembly with missing context', () => { + const assembly = new CloudAssembly(path.join(FIXTURES, 'missing-context')); + expect(assembly.missing).toMatchSnapshot(); +}); + +test('assembly with multiple stacks', () => { + const assembly = new CloudAssembly(path.join(FIXTURES, 'multiple-stacks')); + expect(assembly.stacks).toHaveLength(2); + expect(assembly.artifacts).toHaveLength(2); +}); + +test('fails for invalid artifact type', () => { + expect(() => new CloudAssembly(path.join(FIXTURES, 'invalid-artifact-type'))) + .toThrow('unsupported artifact type: who:am:i'); +}); + +test('fails for invalid environment format', () => { + expect(() => new CloudAssembly(path.join(FIXTURES, 'invalid-env-format'))) + .toThrow('Unable to parse environment specification'); +}); + +test('fails if stack artifact does not have properties', () => { + expect(() => new CloudAssembly(path.join(FIXTURES, 'stack-without-params'))) + .toThrow('Invalid CloudFormation stack artifact. Missing \"templateFile\" property in cloud assembly manifest'); +}); + +test('messages', () => { + const assembly = new CloudAssembly(path.join(FIXTURES, 'messages')); + expect(assembly.stacks[0].messages).toMatchSnapshot(); +}); + +test('assets', () => { + const assembly = new CloudAssembly(path.join(FIXTURES, 'assets')); + expect(assembly.stacks[0].assets).toMatchSnapshot(); +}); + +test('logical id to path map', () => { + const assembly = new CloudAssembly(path.join(FIXTURES, 'logical-id-map')); + expect(assembly.stacks[0].logicalIdToPathMap).toEqual({ logicalIdOfFooBar: '/foo/bar' }); +}); + +test('dependencies', () => { + const assembly = new CloudAssembly(path.join(FIXTURES, 'depends')); + expect(assembly.stacks).toHaveLength(4); + + // expect stacks to be listed in topological order + expect(assembly.stacks.map(s => s.name)).toEqual([ 'StackA', 'StackD', 'StackC', 'StackB' ]); + expect(assembly.stacks[0].depends).toEqual([]); + expect(assembly.stacks[1].depends).toEqual([]); + expect(assembly.stacks[2].depends.map(x => x.id)).toEqual([ 'StackD' ]); + expect(assembly.stacks[3].depends.map(x => x.id)).toEqual([ 'StackC', 'StackD' ]); +}); + +test('fails for invalid dependencies', () => { + expect(() => new CloudAssembly(path.join(FIXTURES, 'invalid-depends'))).toThrow('Artifact StackC depends on non-existing artifact StackX'); +}); + +test('verifyManifestVersion', () => { + verifyManifestVersion('0.33.0'); + expect(() => verifyManifestVersion('0.31.0')).toThrow('App used framework v0.31.0 but it must be >= v0.33.0 in order to interact with this CLI'); + expect(() => verifyManifestVersion('0.34.0')).toThrow('CLI >= 0.34.0 is required to interact with this app'); +}); \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/assets/asset-dir/foo.txt b/packages/@aws-cdk/cx-api/test/fixtures/assets/asset-dir/foo.txt new file mode 100644 index 0000000000000..5783cb7e31483 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/assets/asset-dir/foo.txt @@ -0,0 +1 @@ +hello, assets! diff --git a/packages/@aws-cdk/cx-api/test/fixtures/assets/docker-asset/Dockerfile b/packages/@aws-cdk/cx-api/test/fixtures/assets/docker-asset/Dockerfile new file mode 100644 index 0000000000000..ceaf18ac05257 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/assets/docker-asset/Dockerfile @@ -0,0 +1 @@ +FROM ubuntu diff --git a/packages/@aws-cdk/cx-api/test/fixtures/assets/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/assets/manifest.json new file mode 100644 index 0000000000000..93c9745639042 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/assets/manifest.json @@ -0,0 +1,36 @@ +{ + "version": "0.33.0", + "artifacts": { + "MyStackName": { + "type": "aws:cloudformation:stack", + "environment": "aws://37736633/us-region-1", + "properties": { + "templateFile": "template.json" + }, + "metadata": { + "foo": [ + { + "type": "aws:cdk:asset", + "data": { + "packaging": "zip", + "id": "logical-id-of-the-asset", + "sourceHash": "xoxoxox", + "path": "asset-dir" + }, + "trace": "bam" + }, + { + "type": "aws:cdk:asset", + "data": { + "packaging": "docker", + "id": "logical-id-of-the-asset-x1234", + "sourceHash": "docker-asset-source", + "path": "docker-asset" + }, + "trace": "bam:ssss" + } + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/assets/template.json b/packages/@aws-cdk/cx-api/test/fixtures/assets/template.json new file mode 100644 index 0000000000000..284fd64cffc21 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/assets/template.json @@ -0,0 +1,7 @@ +{ + "Resources": { + "MyBucket": { + "Type": "AWS::S3::Bucket" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/depends/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/depends/manifest.json new file mode 100644 index 0000000000000..f0a43dc6cf926 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/depends/manifest.json @@ -0,0 +1,35 @@ +{ + "version": "0.33.0", + "artifacts": { + "StackA": { + "type": "aws:cloudformation:stack", + "environment": "aws://37736633/us-region-1", + "properties": { + "templateFile": "template.json" + } + }, + "StackB": { + "type": "aws:cloudformation:stack", + "environment": "aws://1111/us-region-1", + "properties": { + "templateFile": "template.json" + }, + "dependencies": [ "StackC", "StackD" ] + }, + "StackC": { + "type": "aws:cloudformation:stack", + "environment": "aws://1111/us-region-1", + "properties": { + "templateFile": "template.json" + }, + "dependencies": [ "StackD" ] + }, + "StackD": { + "type": "aws:cloudformation:stack", + "environment": "aws://1111/us-region-1", + "properties": { + "templateFile": "template.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/depends/template.json b/packages/@aws-cdk/cx-api/test/fixtures/depends/template.json new file mode 100644 index 0000000000000..284fd64cffc21 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/depends/template.json @@ -0,0 +1,7 @@ +{ + "Resources": { + "MyBucket": { + "Type": "AWS::S3::Bucket" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/empty/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/empty/manifest.json new file mode 100644 index 0000000000000..1a1c7486458ef --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/empty/manifest.json @@ -0,0 +1,3 @@ +{ + "version": "0.33.0" +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/invalid-artifact-type/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/invalid-artifact-type/manifest.json new file mode 100644 index 0000000000000..f3e4eff9bcfb8 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/invalid-artifact-type/manifest.json @@ -0,0 +1,9 @@ +{ + "version": "0.33.0", + "artifacts": { + "MyArt": { + "type": "who:am:i", + "environment": "aws://37736633/us-region-1" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/invalid-depends/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/invalid-depends/manifest.json new file mode 100644 index 0000000000000..27f637ba0aabe --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/invalid-depends/manifest.json @@ -0,0 +1,35 @@ +{ + "version": "0.33.0", + "artifacts": { + "StackA": { + "type": "aws:cloudformation:stack", + "environment": "aws://37736633/us-region-1", + "properties": { + "templateFile": "template.json" + } + }, + "StackB": { + "type": "aws:cloudformation:stack", + "environment": "aws://1111/us-region-1", + "properties": { + "templateFile": "template.json" + }, + "dependencies": [ "StackC", "StackD" ] + }, + "StackC": { + "type": "aws:cloudformation:stack", + "environment": "aws://1111/us-region-1", + "properties": { + "templateFile": "template.json" + }, + "dependencies": [ "StackX" ] + }, + "StackD": { + "type": "aws:cloudformation:stack", + "environment": "aws://1111/us-region-1", + "properties": { + "templateFile": "template.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/invalid-depends/template.json b/packages/@aws-cdk/cx-api/test/fixtures/invalid-depends/template.json new file mode 100644 index 0000000000000..284fd64cffc21 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/invalid-depends/template.json @@ -0,0 +1,7 @@ +{ + "Resources": { + "MyBucket": { + "Type": "AWS::S3::Bucket" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/invalid-env-format/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/invalid-env-format/manifest.json new file mode 100644 index 0000000000000..4533438b41f14 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/invalid-env-format/manifest.json @@ -0,0 +1,12 @@ +{ + "version": "0.33.0", + "artifacts": { + "MyStackName": { + "type": "aws:cloudformation:stack", + "environment": "awsx://37736633/us-region-1", + "properties": { + "templateFile": "template.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/logical-id-map/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/logical-id-map/manifest.json new file mode 100644 index 0000000000000..e77bd29eda177 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/logical-id-map/manifest.json @@ -0,0 +1,21 @@ +{ + "version": "0.33.0", + "artifacts": { + "MyStackName": { + "type": "aws:cloudformation:stack", + "environment": "aws://37736633/us-region-1", + "properties": { + "templateFile": "template.json" + }, + "metadata": { + "/foo/bar": [ + { + "type": "aws:cdk:logicalId", + "data": "logicalIdOfFooBar", + "trace": "bam" + } + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/logical-id-map/template.json b/packages/@aws-cdk/cx-api/test/fixtures/logical-id-map/template.json new file mode 100644 index 0000000000000..284fd64cffc21 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/logical-id-map/template.json @@ -0,0 +1,7 @@ +{ + "Resources": { + "MyBucket": { + "Type": "AWS::S3::Bucket" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/messages/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/messages/manifest.json new file mode 100644 index 0000000000000..aedd6d1b48bf4 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/messages/manifest.json @@ -0,0 +1,22 @@ +{ + "version": "0.33.0", + "artifacts": { + "MyStackName": { + "type": "aws:cloudformation:stack", + "environment": "aws://37736633/us-region-1", + "properties": { + "templateFile": "template.json" + }, + "metadata": { + "foo": [ + { "type": "aws:cdk:warning", "data": "boom", "trace": "bam" }, + { "type": "aws:cdk:error", "data": "error!!", "trace": "bam!Error" } + ], + "bar": [ + { "type": "aws:cdk:info", "data": "info?", "trace": "bam!Error" }, + { "type": "aws:foo", "data": "info?", "trace": "bam!Error" } + ] + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/messages/template.json b/packages/@aws-cdk/cx-api/test/fixtures/messages/template.json new file mode 100644 index 0000000000000..284fd64cffc21 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/messages/template.json @@ -0,0 +1,7 @@ +{ + "Resources": { + "MyBucket": { + "Type": "AWS::S3::Bucket" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/missing-context/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/missing-context/manifest.json new file mode 100644 index 0000000000000..4a5185aad4056 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/missing-context/manifest.json @@ -0,0 +1,26 @@ +{ + "version": "0.33.0", + "artifacts": { + "MyStackName": { + "type": "aws:cloudformation:stack", + "environment": "aws://37736633/us-region-1", + "missing": { + "missing:context:key": { "foo": 123 } + }, + "properties": { + "templateFile": "template.json" + } + }, + "MyStackName1234": { + "type": "aws:cloudformation:stack", + "environment": "aws://37736633/us-region-1", + "missing": { + "missing:context:key2": { "foo": 6688 } + }, + "properties": { + "templateFile": "template.json" + } + } + + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/missing-context/template.json b/packages/@aws-cdk/cx-api/test/fixtures/missing-context/template.json new file mode 100644 index 0000000000000..284fd64cffc21 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/missing-context/template.json @@ -0,0 +1,7 @@ +{ + "Resources": { + "MyBucket": { + "Type": "AWS::S3::Bucket" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/manifest.json new file mode 100644 index 0000000000000..62458941a6728 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/manifest.json @@ -0,0 +1,19 @@ +{ + "version": "0.33.0", + "artifacts": { + "MyStackName": { + "type": "aws:cloudformation:stack", + "environment": "aws://37736633/us-region-1", + "properties": { + "templateFile": "template.json" + } + }, + "MyStackName1234": { + "type": "aws:cloudformation:stack", + "environment": "aws://1111/us-region-1", + "properties": { + "templateFile": "template.2.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/template.2.json b/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/template.2.json new file mode 100644 index 0000000000000..284fd64cffc21 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/template.2.json @@ -0,0 +1,7 @@ +{ + "Resources": { + "MyBucket": { + "Type": "AWS::S3::Bucket" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/template.json b/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/template.json new file mode 100644 index 0000000000000..284fd64cffc21 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/template.json @@ -0,0 +1,7 @@ +{ + "Resources": { + "MyBucket": { + "Type": "AWS::S3::Bucket" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/single-stack/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/single-stack/manifest.json new file mode 100644 index 0000000000000..09d0cae2be4e1 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/single-stack/manifest.json @@ -0,0 +1,12 @@ +{ + "version": "0.33.0", + "artifacts": { + "MyStackName": { + "type": "aws:cloudformation:stack", + "environment": "aws://37736633/us-region-1", + "properties": { + "templateFile": "template.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/single-stack/template.json b/packages/@aws-cdk/cx-api/test/fixtures/single-stack/template.json new file mode 100644 index 0000000000000..284fd64cffc21 --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/single-stack/template.json @@ -0,0 +1,7 @@ +{ + "Resources": { + "MyBucket": { + "Type": "AWS::S3::Bucket" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/cx-api/test/fixtures/stack-without-params/manifest.json b/packages/@aws-cdk/cx-api/test/fixtures/stack-without-params/manifest.json new file mode 100644 index 0000000000000..fd4bbfc3da8fb --- /dev/null +++ b/packages/@aws-cdk/cx-api/test/fixtures/stack-without-params/manifest.json @@ -0,0 +1,9 @@ +{ + "version": "0.33.0", + "artifacts": { + "MyStackName": { + "type": "aws:cloudformation:stack", + "environment": "aws://37736633/us-region-1" + } + } +} \ No newline at end of file diff --git a/packages/aws-cdk/bin/cdk.ts b/packages/aws-cdk/bin/cdk.ts index 6d2b623e9bbf6..514ed9476de3f 100644 --- a/packages/aws-cdk/bin/cdk.ts +++ b/packages/aws-cdk/bin/cdk.ts @@ -2,20 +2,17 @@ import 'source-map-support/register'; import colors = require('colors/safe'); -import fs = require('fs-extra'); import yargs = require('yargs'); import { bootstrapEnvironment, destroyStack, SDK } from '../lib'; import { environmentsFromDescriptors, globEnvironmentsFromStacks } from '../lib/api/cxapp/environments'; import { execProgram } from '../lib/api/cxapp/exec'; -import { AppStacks, ExtendedStackSelection, listStackNames } from '../lib/api/cxapp/stacks'; +import { AppStacks, ExtendedStackSelection } from '../lib/api/cxapp/stacks'; import { CloudFormationDeploymentTarget, DEFAULT_TOOLKIT_STACK_NAME } from '../lib/api/deployment-target'; -import { leftPad } from '../lib/api/util/string-manipulation'; import { CdkToolkit } from '../lib/cdk-toolkit'; import { RequireApproval } from '../lib/diff'; import { availableInitLanguages, cliInit, printAvailableTemplates } from '../lib/init'; -import { interactive } from '../lib/interactive'; -import { data, debug, error, highlight, print, setVerbose, success } from '../lib/logging'; +import { data, debug, error, print, setVerbose, success } from '../lib/logging'; import { PluginHost } from '../lib/plugin'; import { parseRenames } from '../lib/renames'; import { serializeStructure } from '../lib/serialize'; @@ -31,7 +28,7 @@ async function parseCommandLineArguments() { return yargs .env('CDK') .usage('Usage: cdk -a COMMAND') - .option('app', { type: 'string', alias: 'a', desc: 'REQUIRED: Command-line for executing your CDK app (e.g. "node bin/my-app.js")', requiresArg: true }) + .option('app', { type: 'string', alias: 'a', desc: 'REQUIRED: command-line for executing your app or a cloud assembly directory (e.g. "node bin/my-app.js")', requiresArg: true }) .option('context', { type: 'array', alias: 'c', desc: 'Add contextual string parameter (KEY=VALUE)', nargs: 1, requiresArg: true }) .option('plugin', { type: 'array', alias: 'p', desc: 'Name or path of a node package that extend the CDK features. Can be specified multiple times', nargs: 1 }) .option('rename', { type: 'string', desc: 'Rename stack name if different from the one defined in the cloud executable ([ORIGINAL:]RENAMED)', requiresArg: true }) @@ -48,14 +45,12 @@ async function parseCommandLineArguments() { .option('asset-metadata', { type: 'boolean', desc: 'Include "aws:asset:*" CloudFormation metadata for resources that user assets (enabled by default)', default: true }) .option('role-arn', { type: 'string', alias: 'r', desc: 'ARN of Role to use when invoking CloudFormation', default: undefined, requiresArg: true }) .option('toolkit-stack-name', { type: 'string', desc: 'The name of the CDK toolkit stack', requiresArg: true }) - .option('staging', { type: 'string', desc: 'directory name for staging assets (use --no-asset-staging to disable)', default: '.cdk.staging' }) + .option('staging', { type: 'boolean', desc: 'copy assets to the output directory (use --no-staging to disable)', default: true }) + .option('output', { type: 'string', alias: 'o', desc: 'emits the synthesized cloud assembly into a directory (default: cdk.out)', requiresArg: true }) .command([ 'list', 'ls' ], 'Lists all stacks in the app', yargs => yargs .option('long', { type: 'boolean', default: false, alias: 'l', desc: 'display environment information for each stack' })) .command([ 'synthesize [STACKS..]', 'synth [STACKS..]' ], 'Synthesizes and prints the CloudFormation template for this stack', yargs => yargs - .option('exclusively', { type: 'boolean', alias: 'e', desc: 'only deploy requested stacks, don\'t include dependencies' }) - .option('interactive', { type: 'boolean', alias: 'i', desc: 'interactively watch and show template updates' }) - .option('output', { type: 'string', alias: 'o', desc: 'write CloudFormation template for requested stacks to the given directory', requiresArg: true }) - .option('numbered', { type: 'boolean', alias: 'n', desc: 'prefix filenames with numbers to indicate deployment ordering' })) + .option('exclusively', { type: 'boolean', alias: 'e', desc: 'only deploy requested stacks, don\'t include dependencies' })) .command('bootstrap [ENVIRONMENTS..]', 'Deploys the CDK toolkit stack into an AWS environment', yargs => yargs .option('toolkit-bucket-name', { type: 'string', alias: 'b', desc: 'The name of the CDK toolkit bucket', default: undefined })) .command('deploy [STACKS..]', 'Deploys the stack(s) named STACKS into your AWS account', yargs => yargs @@ -211,7 +206,7 @@ async function initCommandLine() { case 'synthesize': case 'synth': - return await cliSynthesize(args.STACKS, args.exclusively, args.interactive, args.output, args.json, args.numbered); + return await cliSynthesize(args.STACKS, args.exclusively); case 'metadata': return await cliMetadata(await findStack(args.STACK)); @@ -279,21 +274,15 @@ async function initCommandLine() { * should be supplied, where the templates will be written. */ async function cliSynthesize(stackNames: string[], - exclusively: boolean, - doInteractive: boolean, - outputDir: string|undefined, - json: boolean, - numbered: boolean): Promise { + exclusively: boolean): Promise { // Only autoselect dependencies if it doesn't interfere with user request or output options - const autoSelectDependencies = !exclusively && outputDir !== undefined; + const autoSelectDependencies = !exclusively; const stacks = await appStacks.selectStacks(stackNames, autoSelectDependencies ? ExtendedStackSelection.Upstream : ExtendedStackSelection.None); - if (doInteractive) { - if (stacks.length !== 1) { - throw new Error(`When using interactive synthesis, must select exactly one stack. Got: ${listStackNames(stacks)}`); - } - return await interactive(stacks[0], argv.verbose, (stack) => appStacks.synthesizeStack(stack)); + // if we have a single stack, print it to STDOUT + if (stacks.length === 1) { + return stacks[0].template; } // This is a slight hack; in integ mode we allow multiple stacks to be synthesized to stdout sequentially. @@ -304,33 +293,11 @@ async function initCommandLine() { // the stack names), it's not exposed as a CLI flag. Instead, it's hidden // behind an environment variable. const isIntegMode = process.env.CDK_INTEG_MODE === '1'; - - if (stacks.length > 1 && outputDir == null && !isIntegMode) { - // tslint:disable-next-line:max-line-length - throw new Error(`Multiple stacks selected (${listStackNames(stacks)}), but output is directed to stdout. Either select one stack, or use --output to send templates to a directory.`); - } - - if (outputDir == null) { - // What we return here will be printed in 'main' - if (stacks.length > 1) { - // Only possible in integ mode - return stacks.map(s => s.template); - } - return stacks[0].template; - } - - fs.mkdirpSync(outputDir); - - let i = 0; - for (const stack of stacks) { - const prefix = numbered ? leftPad(`${i}`, 3, '0') + '.' : ''; - const fileName = `${outputDir}/${prefix}${stack.name}.template.${json ? 'json' : 'yaml'}`; - highlight(fileName); - await fs.writeFile(fileName, toJsonOrYaml(stack.template)); - i++; + if (isIntegMode) { + return stacks.map(s => s.template); } - return undefined; // Nothing to print + return appStacks.assembly!.directory; } async function cliList(options: { long?: boolean } = { }) { diff --git a/packages/aws-cdk/lib/api/bootstrap-environment.ts b/packages/aws-cdk/lib/api/bootstrap-environment.ts index d2c751b36b6dc..8c9baeb090c8b 100644 --- a/packages/aws-cdk/lib/api/bootstrap-environment.ts +++ b/packages/aws-cdk/lib/api/bootstrap-environment.ts @@ -1,4 +1,7 @@ -import { Environment, SynthesizedStack } from '@aws-cdk/cx-api'; +import cxapi = require('@aws-cdk/cx-api'); +import fs = require('fs-extra'); +import os = require('os'); +import path = require('path'); import { deployStack, DeployStackResult } from './deploy-stack'; import { SDK } from './util/sdk'; @@ -7,36 +10,46 @@ import { SDK } from './util/sdk'; export const BUCKET_NAME_OUTPUT = 'BucketName'; export const BUCKET_DOMAIN_NAME_OUTPUT = 'BucketDomainName'; -export async function bootstrapEnvironment(environment: Environment, aws: SDK, toolkitStackName: string, roleArn: string | undefined, toolkitBucketName: string | undefined): Promise { - const synthesizedStack: SynthesizedStack = { - environment, - metadata: {}, - template: { - Description: "The CDK Toolkit Stack. It was created by `cdk bootstrap` and manages resources necessary for managing your Cloud Applications with AWS CDK.", - Resources: { - StagingBucket: { - Type: "AWS::S3::Bucket", - Properties: { - AccessControl: "Private", - BucketEncryption: { ServerSideEncryptionConfiguration: [{ ServerSideEncryptionByDefault: { SSEAlgorithm: "aws:kms" } }] } - } - } - }, - Outputs: { - [BUCKET_NAME_OUTPUT]: { - Description: "The name of the S3 bucket owned by the CDK toolkit stack", - Value: { Ref: "StagingBucket" } - }, - [BUCKET_DOMAIN_NAME_OUTPUT]: { - Description: "The domain name of the S3 bucket owned by the CDK toolkit stack", - Value: { "Fn::GetAtt": ["StagingBucket", "DomainName"] } +export async function bootstrapEnvironment(environment: cxapi.Environment, aws: SDK, toolkitStackName: string, roleArn: string | undefined, toolkitBucketName: string | undefined): Promise { + + const template = { + Description: "The CDK Toolkit Stack. It was created by `cdk bootstrap` and manages resources necessary for managing your Cloud Applications with AWS CDK.", + Resources: { + StagingBucket: { + Type: "AWS::S3::Bucket", + Properties: { + BucketName: toolkitBucketName, + AccessControl: "Private", + BucketEncryption: { ServerSideEncryptionConfiguration: [{ ServerSideEncryptionByDefault: { SSEAlgorithm: "aws:kms" } }] } } } }, - name: toolkitStackName, + Outputs: { + [BUCKET_NAME_OUTPUT]: { + Description: "The name of the S3 bucket owned by the CDK toolkit stack", + Value: { Ref: "StagingBucket" } + }, + [BUCKET_DOMAIN_NAME_OUTPUT]: { + Description: "The domain name of the S3 bucket owned by the CDK toolkit stack", + Value: { "Fn::GetAtt": ["StagingBucket", "DomainName"] } + } + } }; - if (toolkitBucketName) { - synthesizedStack.template.Resources.StagingBucket.Properties.BucketName = toolkitBucketName; - } - return await deployStack({ stack: synthesizedStack, sdk: aws, roleArn }); + + const outdir = await fs.mkdtemp(path.join(os.tmpdir(), 'cdk-bootstrap')); + const builder = new cxapi.CloudAssemblyBuilder(outdir); + const templateFile = `${toolkitStackName}.template.json`; + + await fs.writeJson(path.join(builder.outdir, templateFile), template, { spaces: 2 }); + + builder.addArtifact(toolkitStackName, { + type: cxapi.ArtifactType.AwsCloudFormationStack, + environment: cxapi.EnvironmentUtils.format(environment.account, environment.region), + properties: { + templateFile + }, + }); + + const assembly = builder.build(); + return await deployStack({ stack: assembly.getStack(toolkitStackName), sdk: aws, roleArn }); } diff --git a/packages/aws-cdk/lib/api/cxapp/exec.ts b/packages/aws-cdk/lib/api/cxapp/exec.ts index 72d1412c367c6..642bf11c7fcf0 100644 --- a/packages/aws-cdk/lib/api/cxapp/exec.ts +++ b/packages/aws-cdk/lib/api/cxapp/exec.ts @@ -1,15 +1,13 @@ import cxapi = require('@aws-cdk/cx-api'); import childProcess = require('child_process'); import fs = require('fs-extra'); -import os = require('os'); import path = require('path'); -import semver = require('semver'); import { debug } from '../../logging'; import { Configuration, PROJECT_CONFIG, USER_DEFAULTS } from '../../settings'; import { SDK } from '../util/sdk'; /** Invokes the cloud executable and returns JSON output */ -export async function execProgram(aws: SDK, config: Configuration): Promise { +export async function execProgram(aws: SDK, config: Configuration): Promise { const env: { [key: string]: string } = { }; const context = config.context.all; @@ -42,11 +40,15 @@ export async function execProgram(aws: SDK, config: Configuration): Promise((ok, fail) => { @@ -101,7 +103,7 @@ export async function execProgram(aws: SDK, config: Configuration): Promise { if (code === 0) { - return ok(path.join(outdir, cxapi.OUTFILE_NAME)); + return ok(); } else { return fail(new Error(`Subprocess exited with error ${code}`)); } @@ -110,37 +112,6 @@ export async function execProgram(aws: SDK, config: Configuration): Promise= ${cxapi.PROTO_RESPONSE_VERSION} is required in order to interact with this version of the Toolkit.`); - } - - const frameworkVersion = semver.coerce(response.version); - const toolkitVersion = semver.coerce(cxapi.PROTO_RESPONSE_VERSION); - - // Should not happen, but I don't trust this library 100% either, so let's check for it to be safe - if (!frameworkVersion || !toolkitVersion) { throw new Error('SemVer library could not parse versions'); } - - if (semver.gt(frameworkVersion, toolkitVersion)) { - throw new Error(`CDK Toolkit >= ${response.version} is required in order to interact with this program.`); - } - - if (semver.lt(frameworkVersion, toolkitVersion)) { - // Toolkit protocol is newer than the framework version, and we KNOW the - // version. This is a scenario in which we could potentially do some - // upgrading of the response in the future. - // - // For now though, we simply reject old responses. - throw new Error(`CDK Framework >= ${cxapi.PROTO_RESPONSE_VERSION} is required in order to interact with this version of the Toolkit.`); - } - - return response; -} - /** * If we don't have region/account defined in context, we fall back to the default SDK behavior * where region is retreived from ~/.aws/config and account is based on default credentials provider diff --git a/packages/aws-cdk/lib/api/cxapp/stacks.ts b/packages/aws-cdk/lib/api/cxapp/stacks.ts index 7d913155e2a70..5117240eff68e 100644 --- a/packages/aws-cdk/lib/api/cxapp/stacks.ts +++ b/packages/aws-cdk/lib/api/cxapp/stacks.ts @@ -6,11 +6,12 @@ import contextproviders = require('../../context-providers'); import { debug, error, print, warning } from '../../logging'; import { Renames } from '../../renames'; import { Configuration } from '../../settings'; -import cdkUtil = require('../../util'); import { SDK } from '../util/sdk'; -import { topologicalSort } from '../util/toposort'; -type Synthesizer = (aws: SDK, config: Configuration) => Promise; +/** + * @returns output directory + */ +type Synthesizer = (aws: SDK, config: Configuration) => Promise; export interface AppStacksProps { /** @@ -65,7 +66,8 @@ export class AppStacks { * Since app execution basically always synthesizes all the stacks, * we can invoke it once and cache the response for subsequent calls. */ - private cachedResponse?: cxapi.SynthesizeResponse; + public assembly?: cxapi.CloudAssembly; + private readonly renames: Renames; constructor(private readonly props: AppStacksProps) { @@ -78,28 +80,29 @@ export class AppStacks { * It's an error if there are no stacks to select, or if one of the requested parameters * refers to a nonexistant stack. */ - public async selectStacks(selectors: string[], extendedSelection: ExtendedStackSelection): Promise { + public async selectStacks(selectors: string[], extendedSelection: ExtendedStackSelection): Promise { selectors = selectors.filter(s => s != null); // filter null/undefined - const stacks: cxapi.SynthesizedStack[] = await this.listStacks(); + const stacks = await this.listStacks(); if (stacks.length === 0) { throw new Error('This app contains no stacks'); } if (selectors.length === 0) { // remove non-auto deployed Stacks - const autoDeployedStacks = stacks.filter(s => s.autoDeploy !== false); + const autoDeployedStacks = stacks.filter(s => s.autoDeploy); debug('Stack name not specified, so defaulting to all available stacks: ' + listStackNames(autoDeployedStacks)); - return this.applyRenames(autoDeployedStacks); + this.applyRenames(autoDeployedStacks); + return autoDeployedStacks; } - const allStacks = new Map(); + const allStacks = new Map(); for (const stack of stacks) { allStacks.set(stack.name, stack); } // For every selector argument, pick stacks from the list. - const selectedStacks = new Map(); + const selectedStacks = new Map(); for (const pattern of selectors) { let found = false; @@ -129,7 +132,9 @@ export class AppStacks { // Only check selected stacks for errors this.processMessages(selectedList); - return this.applyRenames(selectedList); + this.applyRenames(selectedList); + + return selectedList; } /** @@ -142,42 +147,43 @@ export class AppStacks { * * Renames are *NOT* applied in list mode. */ - public async listStacks(): Promise { + public async listStacks(): Promise { const response = await this.synthesizeStacks(); - return topologicalSort(response.stacks, s => s.name, s => s.dependsOn || []); + return response.stacks; } /** * Synthesize a single stack */ - public async synthesizeStack(stackName: string): Promise { + public async synthesizeStack(stackName: string): Promise { const resp = await this.synthesizeStacks(); const stack = resp.stacks.find(s => s.name === stackName); if (!stack) { throw new Error(`Stack ${stackName} not found`); } - return this.applyRenames([stack])[0]; + this.applyRenames([stack]); + + return stack; } /** * Synthesize a set of stacks */ - public async synthesizeStacks(): Promise { - if (this.cachedResponse) { - return this.cachedResponse; + public async synthesizeStacks(): Promise { + if (this.assembly) { + return this.assembly; } const trackVersions: boolean = this.props.configuration.settings.get(['versionReporting']); // We may need to run the cloud executable multiple times in order to satisfy all missing context while (true) { - const response: cxapi.SynthesizeResponse = await this.props.synthesizer(this.props.aws, this.props.configuration); - const allMissing = cdkUtil.deepMerge(...response.stacks.map(s => s.missing)); + const assembly = await this.props.synthesizer(this.props.aws, this.props.configuration); - if (!cdkUtil.isEmpty(allMissing)) { + if (assembly.missing) { debug(`Some context information is missing. Fetching...`); - await contextproviders.provideContextValues(allMissing, this.props.configuration.context, this.props.aws); + await contextproviders.provideContextValues(assembly.missing, this.props.configuration.context, this.props.aws); // Cache the new context to disk await this.props.configuration.saveContext(); @@ -185,9 +191,9 @@ export class AppStacks { continue; } - if (trackVersions && response.runtime) { - const modules = formatModules(response.runtime); - for (const stack of response.stacks) { + if (trackVersions && assembly.runtime) { + const modules = formatModules(assembly.runtime); + for (const stack of assembly.stacks) { if (!stack.template.Resources) { stack.template.Resources = {}; } @@ -209,10 +215,10 @@ export class AppStacks { } // All good, return - this.cachedResponse = response; - return response; + this.assembly = assembly; + return assembly; - function formatModules(runtime: cxapi.AppRuntime): string { + function formatModules(runtime: cxapi.RuntimeInfo): string { const modules = new Array(); // inject toolkit version to list of modules @@ -230,26 +236,24 @@ export class AppStacks { /** * Extracts 'aws:cdk:warning|info|error' metadata entries from the stack synthesis */ - private processMessages(stacks: cxapi.SynthesizedStack[]) { + private processMessages(stacks: cxapi.CloudFormationStackArtifact[]) { let warnings = false; let errors = false; + for (const stack of stacks) { - for (const id of Object.keys(stack.metadata)) { - const metadata = stack.metadata[id]; - for (const entry of metadata) { - switch (entry.type) { - case cxapi.WARNING_METADATA_KEY: - warnings = true; - this.printMessage(warning, 'Warning', id, entry); - break; - case cxapi.ERROR_METADATA_KEY: - errors = true; - this.printMessage(error, 'Error', id, entry); - break; - case cxapi.INFO_METADATA_KEY: - this.printMessage(print, 'Info', id, entry); - break; - } + for (const message of stack.messages) { + switch (message.level) { + case cxapi.SynthesisMessageLevel.WARNING: + warnings = true; + this.printMessage(warning, 'Warning', message.id, message.entry); + break; + case cxapi.SynthesisMessageLevel.ERROR: + errors = true; + this.printMessage(error, 'Error', message.id, message.entry); + break; + case cxapi.SynthesisMessageLevel.INFO: + this.printMessage(print, 'Info', message.id, message.entry); + break; } } } @@ -266,31 +270,23 @@ export class AppStacks { private printMessage(logFn: (s: string) => void, prefix: string, id: string, entry: cxapi.MetadataEntry) { logFn(`[${prefix} at ${id}] ${entry.data}`); - if (this.props.verbose) { + if (this.props.verbose && entry.trace) { logFn(` ${entry.trace.join('\n ')}`); } } - private applyRenames(stacks: cxapi.SynthesizedStack[]): SelectedStack[] { + private applyRenames(stacks: cxapi.CloudFormationStackArtifact[]) { this.renames.validateSelectedStacks(stacks); - - const ret = []; for (const stack of stacks) { - ret.push({ - ...stack, - originalName: stack.name, - name: this.renames.finalName(stack.name), - }); + stack.name = this.renames.finalName(stack.name); } - - return ret; } } /** * Combine the names of a set of stacks using a comma */ -export function listStackNames(stacks: cxapi.SynthesizedStack[]): string { +export function listStackNames(stacks: cxapi.CloudFormationStackArtifact[]): string { return stacks.map(s => s.name).join(', '); } @@ -319,7 +315,9 @@ export enum ExtendedStackSelection { * * Modifies `selectedStacks` in-place. */ -function includeDownstreamStacks(selectedStacks: Map, allStacks: Map) { +function includeDownstreamStacks( + selectedStacks: Map, + allStacks: Map) { const added = new Array(); let madeProgress = true; @@ -328,7 +326,7 @@ function includeDownstreamStacks(selectedStacks: Map selectedStacks.has(dependencyName))) { + if (!selectedStacks.has(name) && (stack.depends || []).some(dep => selectedStacks.has(dep.id))) { selectedStacks.set(name, stack); added.push(name); madeProgress = true; @@ -346,7 +344,9 @@ function includeDownstreamStacks(selectedStacks: Map, allStacks: Map) { +function includeUpstreamStacks( + selectedStacks: Map, + allStacks: Map) { const added = new Array(); let madeProgress = true; while (madeProgress) { @@ -354,7 +354,7 @@ function includeUpstreamStacks(selectedStacks: Map x.id)) { if (!selectedStacks.has(dependencyName) && allStacks.has(dependencyName)) { added.push(dependencyName); selectedStacks.set(dependencyName, allStacks.get(dependencyName)!); @@ -368,10 +368,3 @@ function includeUpstreamStacks(selectedStacks: Map { +async function makeBodyParameter(stack: cxapi.CloudFormationStackArtifact, toolkitInfo?: ToolkitInfo): Promise { const templateJson = toYAML(stack.template); if (toolkitInfo) { const s3KeyPrefix = `cdk/${stack.name}/`; @@ -140,7 +140,7 @@ async function makeBodyParameter(stack: cxapi.SynthesizedStack, toolkitInfo?: To } export interface DestroyStackOptions { - stack: cxapi.SynthesizedStack; + stack: cxapi.CloudFormationStackArtifact; sdk: SDK; roleArn?: string; deployName?: string; diff --git a/packages/aws-cdk/lib/api/deployment-target.ts b/packages/aws-cdk/lib/api/deployment-target.ts index 61dc56458fc42..c2ea2f63bac5b 100644 --- a/packages/aws-cdk/lib/api/deployment-target.ts +++ b/packages/aws-cdk/lib/api/deployment-target.ts @@ -1,4 +1,4 @@ -import cxapi = require('@aws-cdk/cx-api'); +import { CloudFormationStackArtifact } from '@aws-cdk/cx-api'; import { debug } from '../logging'; import { deserializeStructure } from '../serialize'; import { Mode } from './aws-auth/credentials'; @@ -16,12 +16,12 @@ export type Template = { [key: string]: any }; * Provisioners apply templates to the cloud infrastructure. */ export interface IDeploymentTarget { - readCurrentTemplate(stack: cxapi.SynthesizedStack): Promise