From c52bcfc2e469a5e9eba13a128165946a349c1220 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Wed, 29 May 2019 21:41:12 +0300 Subject: [PATCH] feat: decouple "synth" and "deploy" through cloud assemblies (#2636) Formalize the concept of a cloud assembly to allow decoupling "synth" and "deploy". the main motivation is to allow "deploy" to run in a controlled environment without needing to execute the app for security purposes. `cdk synth` now produces a self-contained assembly in the output directory, which we call a "cloud assembly". this directory includes synthesized templates (similar to current behavior) but also a "manifest.json" file and the asset staging directories. `cdk deploy -a assembly-dir` will now skip synthesis and will directly deploy the assembly. To that end, we modified the behavior of asset staging such that all synth output always goes to the output directory, which is by default `cdk.out` (can be set with `--output`). if there's a single template, it is printed to stdout, otherwise the name of output directory is printed. This PR also includes multiple clean ups and deprecations of stale APIs and modules such as: - `cdk.AppProps` includes various new options. - An error is thrown if references between stacks that are not in the same app (mostly in test cases). - Added the token creation stack trace for errors emitted by tokens - Added `ConstructNode.root` which returns the tree root. **TESTING**: verified that all integration tests passed and added a few tests to verify zip and docker assets as well as cloud assemblies. See: https://github.com/awslabs/cdk-ops/pull/364 Closes #1893 Closes #2093 Closes #1954 Closes #2310 Related #2073 Closes #1245 Closes #341 Closes #956 Closes #233 BREAKING CHANGE: *IMPORTANT*: apps created with the CDK version 0.33.0 and above cannot be used with an older CLI version. - `--interactive` has been removed - `--numbered` has been removed - `--staging` is now a boolean flag that indicates whether assets should be copied to the `--output` directory or directly referenced (`--no-staging` is useful for e.g. local debugging with SAM CLI) - Assets (e.g. Lambda code assets) are now referenced relative to the output directory. - `SynthUtils.templateForStackName` has been removed (use `SynthUtils.synthesize(stack).template`). - `cxapi.SynthesizedStack` renamed to `cxapi.CloudFormationStackArtifact` with multiple API changes. - `cdk.App.run()` now returns a `cxapi.CloudAssembly` instead of `cdk.ISynthesisSession`. - `cdk.App.synthesizeStack` and `synthesizeStacks` has been removed. The `cxapi.CloudAssembly` object returned from `app.run()` can be used as an alternative to explore a synthesized assembly programmatically (resolves #2016). - `cdk.CfnElement.creationTimeStamp` may now return `undefined` if there is no stack trace captured. - `cdk.ISynthesizable.synthesize` now accepts a `cxapi.CloudAssemblyBuilder` instead of `ISynthesisSession`. - `cdk`: The concepts of a synthesis session and session stores have been removed (`cdk.FileSystemStore`, cdk.InMemoryStore`, `cdk.SynthesisSession`, `cdk.ISynthesisSession` and `cdk.SynthesisOptions`). - No more support for legacy manifests (`cdk.ManifestOptions` member `legacyManifest` has been removed) - All support for build/bundle manifest have been removed (`cxapi.BuildManifest`, `cxapi.BuildStep`, etc). - Multiple deprecations and breaking changes in the `cxapi` module (`cxapi.AppRuntime` has been renamed to `cxapi.RuntimeInfo`, `cxapi.SynthesizeResponse`, `cxapi.SynthesizedStack` has been removed) - The `@aws-cdk/applet-js` program is no longer available. Use [decdk](https://github.com/awslabs/aws-cdk/tree/master/packages/decdk) as an alternative. --- .gitignore | 2 + packages/@aws-cdk/applet-js/README.md | 3 + .../@aws-cdk/applet-js/bin/cdk-applet-js.ts | 105 +- .../@aws-cdk/applet-js/lib/applet-helpers.ts | 52 - packages/@aws-cdk/applet-js/package.json | 8 +- .../@aws-cdk/applet-js/test/expected1.json | 35 - .../@aws-cdk/applet-js/test/expected2.json | 48 - .../@aws-cdk/applet-js/test/expected3.json | 7 - .../applet-js/test/manual-test-npm.yaml | 3 - .../applet-js/test/negative-test4.yaml | 4 - .../applet-js/test/negative-test5.yaml | 4 - .../applet-js/test/negative-test6.yaml | 4 - .../applet-js/test/negative-test7.yaml | 4 - .../@aws-cdk/applet-js/test/test-applet.ts | 44 - .../test/test-multistack-expected.json | 35 - .../applet-js/test/test-multistack.yaml | 11 - .../test/test-nonstack-expected.json | 22 - .../applet-js/test/test-nonstack.yaml | 6 - .../@aws-cdk/applet-js/test/test.applets.ts | 117 - .../@aws-cdk/applet-js/test/test.helpers.ts | 56 - packages/@aws-cdk/applet-js/test/test1.yaml | 7 - packages/@aws-cdk/applet-js/test/test2.yaml | 13 - packages/@aws-cdk/applet-js/test/test3.yaml | 6 - packages/@aws-cdk/assert/jest.ts | 10 +- packages/@aws-cdk/assert/lib/expect.ts | 42 +- packages/@aws-cdk/assert/lib/inspector.ts | 4 +- packages/@aws-cdk/assert/lib/synth-utils.ts | 31 +- .../@aws-cdk/assert/test/test.assertions.ts | 9 +- .../assert/test/test.have-resource.ts | 24 +- .../@aws-cdk/assert/test/test.synth-utils.ts | 18 + .../assets-docker/test/test.image-asset.ts | 22 +- packages/@aws-cdk/assets/lib/staging.ts | 33 +- packages/@aws-cdk/assets/test/test.asset.ts | 86 +- .../aws-apigateway/test/test.deployment.ts | 2 +- .../aws-apigateway/test/test.restapi.ts | 2 +- .../test/test.step-scaling-policy.ts | 2 +- .../aws-cloudtrail/test/test.cloudtrail.ts | 12 +- .../aws-codecommit/test/test.codecommit.ts | 51 +- .../@aws-cdk/aws-eks/test/test.cluster.ts | 23 +- .../test/alb/test.security-groups.ts | 19 +- .../@aws-cdk/aws-events/test/test.rule.ts | 2 +- packages/@aws-cdk/aws-glue/test/test.table.ts | 21 +- .../test/test.auto-cross-stack-refs.ts | 26 +- packages/@aws-cdk/aws-iam/test/test.group.ts | 7 +- packages/@aws-cdk/aws-iam/test/test.policy.ts | 17 +- packages/@aws-cdk/aws-iam/test/test.role.ts | 59 +- packages/@aws-cdk/aws-iam/test/test.user.ts | 6 +- .../@aws-cdk/aws-kinesis/test/test.stream.ts | 20 +- packages/@aws-cdk/aws-kms/test/test.alias.ts | 12 +- packages/@aws-cdk/aws-kms/test/test.key.ts | 6 +- .../@aws-cdk/aws-lambda/test/test.code.ts | 5 +- .../aws-lambda/test/test.vpc-lambda.ts | 30 +- .../@aws-cdk/aws-route53/test/test.route53.ts | 10 +- .../aws-route53/test/test.txt-record.ts | 6 +- .../test/test.zone-delegation-record.ts | 6 +- .../test/notifications.test.ts | 2 +- .../aws-s3-notifications/test/queue.test.ts | 2 +- packages/@aws-cdk/aws-s3/test/test.bucket.ts | 30 +- packages/@aws-cdk/cdk/lib/app.ts | 114 +- packages/@aws-cdk/cdk/lib/cfn-element.ts | 9 +- packages/@aws-cdk/cdk/lib/cfn-reference.ts | 9 +- packages/@aws-cdk/cdk/lib/cfn-resource.ts | 10 +- packages/@aws-cdk/cdk/lib/construct.ts | 64 +- packages/@aws-cdk/cdk/lib/runtime-info.ts | 2 +- packages/@aws-cdk/cdk/lib/stack-trace.ts | 16 + packages/@aws-cdk/cdk/lib/stack.ts | 28 +- packages/@aws-cdk/cdk/lib/synthesis.ts | 376 +- packages/@aws-cdk/cdk/lib/token.ts | 19 +- packages/@aws-cdk/cdk/test/test.app.ts | 461 +- packages/@aws-cdk/cdk/test/test.construct.ts | 25 +- packages/@aws-cdk/cdk/test/test.context.ts | 12 +- packages/@aws-cdk/cdk/test/test.stack.ts | 2 +- packages/@aws-cdk/cdk/test/test.synthesis.ts | 277 +- packages/@aws-cdk/cdk/test/test.tokens.ts | 42 + packages/@aws-cdk/cx-api/.gitignore | 5 +- packages/@aws-cdk/cx-api/lib/artifacts.ts | 23 - .../cx-api/lib/{metadata => }/assets.ts | 0 packages/@aws-cdk/cx-api/lib/build.ts | 16 - .../@aws-cdk/cx-api/lib/cloud-artifact.ts | 101 + .../@aws-cdk/cx-api/lib/cloud-assembly.ts | 204 + .../cx-api/lib/cloudformation-artifact.ts | 55 + packages/@aws-cdk/cx-api/lib/cxapi.ts | 169 +- packages/@aws-cdk/cx-api/lib/environment.ts | 27 + packages/@aws-cdk/cx-api/lib/index.ts | 10 +- packages/@aws-cdk/cx-api/lib/metadata.ts | 56 + .../util => @aws-cdk/cx-api/lib}/toposort.ts | 0 packages/@aws-cdk/cx-api/lib/versioning.ts | 46 + packages/@aws-cdk/cx-api/package-lock.json | 5035 +++++++++++++++++ packages/@aws-cdk/cx-api/package.json | 22 +- .../__snapshots__/cloud-assembly.test.js.snap | 61 + .../test/cloud-assembly-builder.test.ts | 100 + .../cx-api/test/cloud-assembly.test.ts | 100 + .../test/fixtures/assets/asset-dir/foo.txt | 1 + .../fixtures/assets/docker-asset/Dockerfile | 1 + .../cx-api/test/fixtures/assets/manifest.json | 36 + .../cx-api/test/fixtures/assets/template.json | 7 + .../test/fixtures/depends/manifest.json | 35 + .../test/fixtures/depends/template.json | 7 + .../cx-api/test/fixtures/empty/manifest.json | 3 + .../invalid-artifact-type/manifest.json | 9 + .../fixtures/invalid-depends/manifest.json | 35 + .../fixtures/invalid-depends/template.json | 7 + .../fixtures/invalid-env-format/manifest.json | 12 + .../fixtures/logical-id-map/manifest.json | 21 + .../fixtures/logical-id-map/template.json | 7 + .../test/fixtures/messages/manifest.json | 22 + .../test/fixtures/messages/template.json | 7 + .../fixtures/missing-context/manifest.json | 26 + .../fixtures/missing-context/template.json | 7 + .../fixtures/multiple-stacks/manifest.json | 19 + .../fixtures/multiple-stacks/template.2.json | 7 + .../fixtures/multiple-stacks/template.json | 7 + .../test/fixtures/single-stack/manifest.json | 12 + .../test/fixtures/single-stack/template.json | 7 + .../stack-without-params/manifest.json | 9 + packages/aws-cdk/bin/cdk.ts | 63 +- .../aws-cdk/lib/api/bootstrap-environment.ts | 71 +- packages/aws-cdk/lib/api/cxapp/exec.ts | 75 +- packages/aws-cdk/lib/api/cxapp/stacks.ts | 129 +- packages/aws-cdk/lib/api/deploy-stack.ts | 8 +- packages/aws-cdk/lib/api/deployment-target.ts | 8 +- packages/aws-cdk/lib/api/toolkit-info.ts | 2 +- .../cloudformation/stack-activity-monitor.ts | 4 +- packages/aws-cdk/lib/api/util/sdk.ts | 13 +- packages/aws-cdk/lib/assets.ts | 66 +- packages/aws-cdk/lib/commands/context.ts | 2 +- packages/aws-cdk/lib/diff.ts | 28 +- packages/aws-cdk/lib/docker.ts | 14 +- .../app/csharp/.template.gitignore | 1 + .../app/fsharp/.template.gitignore | 1 + .../app/java/.template.gitignore | 1 + .../app/javascript/.template.gitignore | 1 + .../app/javascript/.template.npmignore | 1 + .../lib/init-templates/app/python/.gitignore | 1 + .../app/typescript/.template.gitignore | 1 + .../app/typescript/.template.npmignore | 1 + .../lib/typescript/.template.gitignore | 1 + .../lib/typescript/.template.npmignore | 1 + .../sample-app/javascript/.template.gitignore | 1 + .../sample-app/javascript/.template.npmignore | 1 + .../sample-app/python/.gitignore | 1 + .../sample-app/typescript/.template.gitignore | 1 + .../sample-app/typescript/.template.npmignore | 1 + packages/aws-cdk/lib/interactive.ts | 53 - packages/aws-cdk/lib/renames.ts | 2 +- packages/aws-cdk/lib/settings.ts | 12 +- packages/aws-cdk/package-lock.json | 18 +- .../aws-cdk/test/api/test.deploy-stack.ts | 9 +- packages/aws-cdk/test/api/test.stacks.ts | 71 +- packages/aws-cdk/test/test.assets.ts | 107 +- packages/aws-cdk/test/test.diff.ts | 30 +- packages/aws-cdk/test/test.docker.ts | 49 +- packages/aws-cdk/test/util.ts | 47 + packages/decdk/package.json | 2 +- packages/decdk/test/synth.test.ts | 3 +- 155 files changed, 7371 insertions(+), 2583 deletions(-) delete mode 100644 packages/@aws-cdk/applet-js/lib/applet-helpers.ts delete mode 100644 packages/@aws-cdk/applet-js/test/expected1.json delete mode 100644 packages/@aws-cdk/applet-js/test/expected2.json delete mode 100644 packages/@aws-cdk/applet-js/test/expected3.json delete mode 100644 packages/@aws-cdk/applet-js/test/manual-test-npm.yaml delete mode 100755 packages/@aws-cdk/applet-js/test/negative-test4.yaml delete mode 100755 packages/@aws-cdk/applet-js/test/negative-test5.yaml delete mode 100755 packages/@aws-cdk/applet-js/test/negative-test6.yaml delete mode 100755 packages/@aws-cdk/applet-js/test/negative-test7.yaml delete mode 100644 packages/@aws-cdk/applet-js/test/test-applet.ts delete mode 100644 packages/@aws-cdk/applet-js/test/test-multistack-expected.json delete mode 100644 packages/@aws-cdk/applet-js/test/test-multistack.yaml delete mode 100644 packages/@aws-cdk/applet-js/test/test-nonstack-expected.json delete mode 100644 packages/@aws-cdk/applet-js/test/test-nonstack.yaml delete mode 100644 packages/@aws-cdk/applet-js/test/test.applets.ts delete mode 100644 packages/@aws-cdk/applet-js/test/test.helpers.ts delete mode 100644 packages/@aws-cdk/applet-js/test/test1.yaml delete mode 100644 packages/@aws-cdk/applet-js/test/test2.yaml delete mode 100755 packages/@aws-cdk/applet-js/test/test3.yaml create mode 100644 packages/@aws-cdk/assert/test/test.synth-utils.ts create mode 100644 packages/@aws-cdk/cdk/lib/stack-trace.ts delete mode 100644 packages/@aws-cdk/cx-api/lib/artifacts.ts rename packages/@aws-cdk/cx-api/lib/{metadata => }/assets.ts (100%) delete mode 100644 packages/@aws-cdk/cx-api/lib/build.ts create mode 100644 packages/@aws-cdk/cx-api/lib/cloud-artifact.ts create mode 100644 packages/@aws-cdk/cx-api/lib/cloud-assembly.ts create mode 100644 packages/@aws-cdk/cx-api/lib/cloudformation-artifact.ts create mode 100644 packages/@aws-cdk/cx-api/lib/metadata.ts rename packages/{aws-cdk/lib/api/util => @aws-cdk/cx-api/lib}/toposort.ts (100%) create mode 100644 packages/@aws-cdk/cx-api/lib/versioning.ts create mode 100644 packages/@aws-cdk/cx-api/package-lock.json create mode 100644 packages/@aws-cdk/cx-api/test/__snapshots__/cloud-assembly.test.js.snap create mode 100644 packages/@aws-cdk/cx-api/test/cloud-assembly-builder.test.ts create mode 100644 packages/@aws-cdk/cx-api/test/cloud-assembly.test.ts create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/assets/asset-dir/foo.txt create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/assets/docker-asset/Dockerfile create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/assets/manifest.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/assets/template.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/depends/manifest.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/depends/template.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/empty/manifest.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/invalid-artifact-type/manifest.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/invalid-depends/manifest.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/invalid-depends/template.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/invalid-env-format/manifest.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/logical-id-map/manifest.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/logical-id-map/template.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/messages/manifest.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/messages/template.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/missing-context/manifest.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/missing-context/template.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/manifest.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/template.2.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/multiple-stacks/template.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/single-stack/manifest.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/single-stack/template.json create mode 100644 packages/@aws-cdk/cx-api/test/fixtures/stack-without-params/manifest.json delete mode 100644 packages/aws-cdk/lib/interactive.ts create mode 100644 packages/aws-cdk/test/util.ts 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