diff --git a/.gitignore b/.gitignore index 353e290..c453f15 100644 --- a/.gitignore +++ b/.gitignore @@ -27,5 +27,6 @@ build/Release node_modules # Other local things to ignore -cloudformation-helpers.zip .DS_Store + +/out diff --git a/aws/apiGateway.js b/aws/apiGateway.js deleted file mode 100644 index 51f0783..0000000 --- a/aws/apiGateway.js +++ /dev/null @@ -1,134 +0,0 @@ -var Promise = require('bluebird'), - AWS = require('aws-sdk'), - base = require('lib/base'), - apiGateway = Promise.promisifyAll(new AWS.APIGateway()); - -// Exposes the SNS.subscribe API method -function CreateApi(event, context) { - base.Handler.call(this, event, context); -} -CreateApi.prototype = Object.create(base.Handler.prototype); -CreateApi.prototype.handleCreate = function() { - var p = this.event.ResourceProperties; - var rootObject = this; - return apiGateway.createRestApiAsync({ - name: p.name, - description: p.description - }) - .then(function(apiData) { - return rootObject.setReferenceData({ restApiId: apiData.id }) // Set this immediately, in case later calls fail - .then(function() { - return apiGateway.getResourcesAsync({ - restApiId: apiData.id - }) - .then(function(resourceData) { - return setupEndpoints(p.endpoints, resourceData.items[0].id, apiData.id) - .then(function(endpointsData) { - return apiGateway.createDeploymentAsync({ - restApiId: apiData.id, - stageName: p.version - }) - .then(function(deploymentData) { - // Total hack: there are limits to the number of API Gateway API calls you can make. So this function - // will fail if a CloudFormation template includes two or more APIs. Attempting to avoid this by blocking. - var until = new Date(); - until.setSeconds(until.getSeconds() + 60); - while (new Date() < until) { - // Wait... - } - // AWS.config.region is a bit of a hack, but I can't figure out how else to dynamically - // detect the region of the API - seems to be nothing in API Gateway or AWS Lambda context. - // Could possibly get it from the CloudFormation stack, but that seems wrong. - return { - baseUrl: "https://" + apiData.id + ".execute-api." + AWS.config.region + ".amazonaws.com/" + p.version, - restApiId: apiData.id - }; - }); - }); - }); - }); - }); -} -CreateApi.prototype.handleDelete = function(referenceData) { - return Promise.try(function() { - if (referenceData && referenceData.restApiId) { - // Can simply delete the entire API - don't need to delete each individual component - return apiGateway.deleteRestApiAsync({ - restApiId: referenceData.restApiId - }); - } - }) -} -function setupEndpoints(config, parentResourceId, restApiId) { - return Promise.map( - Object.keys(config), - function(key) { - switch (key.toUpperCase()) { - case 'GET': - case 'HEAD': - case 'DELETE': - case 'OPTIONS': - case 'PATCH': - case 'POST': - case 'PUT': - var params = config[key]; - params["httpMethod"] = key.toUpperCase() - params["resourceId"] = parentResourceId - params["restApiId"] = restApiId - params["apiKeyRequired"] = params["apiKeyRequired"] == "true" // Passing through CloudFormation, booleans become strings :( - var integration = params["integration"] - delete params.integration - return apiGateway.putMethodAsync(params) - .then(function() { - return Promise.try(function() { - if (integration) { - var contentType = integration["contentType"] - if (!contentType) { - throw "Integration config must include response contentType." - } - delete integration.contentType - integration["httpMethod"] = key.toUpperCase() - integration["resourceId"] = parentResourceId - integration["restApiId"] = restApiId - return apiGateway.putIntegrationAsync(integration) - .then(function(integrationData) { - var responseContentTypes = {} - responseContentTypes[contentType] = "Empty" - return apiGateway.putMethodResponseAsync({ - httpMethod: key.toUpperCase(), - resourceId: parentResourceId, - restApiId: restApiId, - statusCode: '200', - responseModels: responseContentTypes - }) - .then(function(methodResponseData) { - responseContentTypes[contentType] = "" - return apiGateway.putIntegrationResponseAsync({ - httpMethod: key.toUpperCase(), - resourceId: parentResourceId, - restApiId: restApiId, - statusCode: '200', - responseTemplates: responseContentTypes - }); - }); - }); - } - }); - }); - default: - return apiGateway.createResourceAsync({ - parentId: parentResourceId, - pathPart: key, - restApiId: restApiId, - }) - .then(function(resourceData) { - return setupEndpoints(config[key], resourceData.id, restApiId); - }); - } - } - ); -} -exports.createApi = function(event, context) { - handler = new CreateApi(event, context); - handler.handle(); -} diff --git a/aws/dynamo.js b/aws/dynamo.js deleted file mode 100644 index c5614d4..0000000 --- a/aws/dynamo.js +++ /dev/null @@ -1,73 +0,0 @@ -var Promise = require('bluebird'), - AWS = require('aws-sdk'), - base = require('lib/base'), - helpers = require('lib/helpers'), - dynamoDB = Promise.promisifyAll(new AWS.DynamoDB()); - -// Exposes the SNS.subscribe API method -function PutItems(event, context) { - base.Handler.call(this, event, context); -} -PutItems.prototype = Object.create(base.Handler.prototype); -PutItems.prototype.handleCreate = function() { - var p = this.event.ResourceProperties; - return dynamoDB.describeTableAsync({ - TableName: p.TableName - }) - .then(function(tableData) { - return Promise - .map( - p.Items, - function(item) { - return dynamoDB.putItemAsync({ - TableName: p.TableName, - Item: helpers.formatForDynamo(item, true) - }) - .then(function() { - var key = {}; - tableData.Table.KeySchema.forEach(function(keyMember) { - key[keyMember.AttributeName] = item[keyMember.AttributeName] - }); - return { - TableName: p.TableName, - Key: key - }; - }); - } - ) - .then(function(itemsInserted) { - return { - ItemsInserted: itemsInserted - } - }); - }); -} -PutItems.prototype.handleDelete = function(referenceData) { - return Promise.try(function() { - if (referenceData) { - return Promise - .map( - referenceData.ItemsInserted, - function(item) { - return dynamoDB - .deleteItemAsync({ - TableName: item.TableName, - Key: helpers.formatForDynamo(item.Key, true) - }) - .then(function(data) { - return item.Key; - }); - } - ) - .then(function(itemsDeleted) { - return { - ItemsDeleted: itemsDeleted - } - }); - } - }); -} -exports.putItems = function(event, context) { - handler = new PutItems(event, context); - handler.handle(); -} diff --git a/aws/kinesis.js b/aws/kinesis.js deleted file mode 100644 index db7516c..0000000 --- a/aws/kinesis.js +++ /dev/null @@ -1,61 +0,0 @@ -var Promise = require('bluebird'), - AWS = require('aws-sdk'), - base = require('lib/base'), - kinesis = Promise.promisifyAll(new AWS.Kinesis()); - -// Exposes the SNS.subscribe API method -function CreateStream(event, context) { - base.Handler.call(this, event, context); -} -CreateStream.prototype = Object.create(base.Handler.prototype); -CreateStream.prototype.handleCreate = function() { - var p = this.event.ResourceProperties; - delete p.ServiceToken; - p.ShardCount = parseInt(p.ShardCount); - return kinesis.createStreamAsync(p) - .then(function() { - return waitWhileStatus(p.StreamName, "CREATING"); - }) - .then(function(arn) { - return { - StreamName: p.StreamName, - Arn: arn - } - }); -} -CreateStream.prototype.handleDelete = function(referenceData) { - var p = this.event.ResourceProperties; - return kinesis.deleteStreamAsync({StreamName: p.StreamName}) - .then(function() { - return waitWhileStatus(p.StreamName, "DELETING") - }) - .catch(function(err) { - return err; - }); -} -exports.createStream = function(event, context) { - handler = new CreateStream(event, context); - handler.handle(); -} -// Watch until the given status is no longer the status of the stream. -function waitWhileStatus(streamName, status) { - return Promise.try(function() { - var validStatuses = ["CREATING", "DELETING", "ACTIVE", "UPDATING"] - if (validStatuses.indexOf(status) >= 0) { - return kinesis.describeStreamAsync({StreamName: streamName}) - .then(function(data) { - console.log("Current status for [" + streamName +"]: " + data.StreamDescription.StreamStatus); - if (data.StreamDescription.StreamStatus == status) { - return Promise.delay(2000) - .then(function() { - return waitWhileStatus(streamName, status); - }); - } else { - return data.StreamDescription.StreamARN; - } - }); - } else { - throw "status [" + status + "] not one of [" + validStatuses.join(", ") + "]"; - } - }); -} \ No newline at end of file diff --git a/aws/s3.js b/aws/s3.js deleted file mode 100644 index a130282..0000000 --- a/aws/s3.js +++ /dev/null @@ -1,80 +0,0 @@ -var Promise = require('bluebird'), - AWS = require('aws-sdk'), - base = require('lib/base'), - helpers = require('lib/helpers'), - s3 = Promise.promisifyAll(new AWS.S3()); - -// Exposes the SNS.subscribe API method -function PutObject(event, context) { - base.Handler.call(this, event, context); -} -PutObject.prototype = Object.create(base.Handler.prototype); -PutObject.prototype.handleCreate = function() { - var p = this.event.ResourceProperties; - delete p.ServiceToken; - return s3.putObjectAsync(p); -} -PutObject.prototype.handleDelete = function(referenceData) { - var p = this.event.ResourceProperties; - return Promise.try(function() { - if (p.Key.endsWith("/")) { - s3.listObjectsAsync({ - Bucket: p.Bucket, - Prefix: p.Key - }) - .then(function(subObjects) { - return Promise - .map( - subObjects.Contents, - function(item) { - return s3.deleteObjectAsync({ - Bucket: p.Bucket, - Key: item.Key - }) - } - ) - }) - } - }) - .then(function() { - return s3.deleteObjectAsync({ - Bucket: p.Bucket, - Key: p.Key - }); - }); -} -exports.putObject = function(event, context) { - handler = new PutObject(event, context); - handler.handle(); -} - -// Exposes the S3.putBucketPolicy API method -function PutBucketPolicy(event, context) { - base.Handler.call(this, event, context); -} -PutBucketPolicy.prototype = Object.create(base.Handler.prototype); -PutBucketPolicy.prototype.handleCreate = function() { - var p = this.event.ResourceProperties; - delete p.ServiceToken; - return s3.putBucketPolicyAsync(p) - .then(function() { - return { - BucketName : p.Bucket - } - }); -} -PutBucketPolicy.prototype.handleDelete = function(referencedData) { - return Promise.try(function() { - if(referencedData) { - return s3.deleteBucketPolicyAsync({ - Bucket : referencedData.BucketName - }); - } - }); -} -exports.putBucketPolicy = function(event, context) { - console.log(JSON.stringify(event)); - handler = new PutBucketPolicy(event, context); - handler.handle(); -} - diff --git a/aws/ses.js b/aws/ses.js deleted file mode 100644 index 78120a6..0000000 --- a/aws/ses.js +++ /dev/null @@ -1,39 +0,0 @@ -var Promise = require('bluebird'), - AWS = require('aws-sdk'), - base = require('lib/base'), - helpers = require('lib/helpers'), - ses = Promise.promisifyAll(new AWS.SES()); - -// Exposes the SES.createReceiptRule API method -function CreateReceiptRule(event, context) { - base.Handler.call(this, event, context); -} -CreateReceiptRule.prototype = Object.create(base.Handler.prototype); -CreateReceiptRule.prototype.handleCreate = function() { - var p = this.event.ResourceProperties; - delete p.ServiceToken; - p.Rule.Enabled = ("true" === p.Rule.Enabled ); - p.Rule.ScanEnabled = ("true" === p.Rule.ScanEnabled ); - return ses.createReceiptRuleAsync(p) - .then(function() { - return { - RuleSetName : p.RuleSetName, - RuleName : p.Rule.Name - } - }); -} -CreateReceiptRule.prototype.handleDelete = function(referenceData) { - return Promise.try(function() { - if (referenceData) { - return ses.deleteReceiptRuleAsync({ - RuleSetName : referenceData.RuleSetName, - RuleName : referenceData.RuleName - }); - } - }); -} -exports.createReceiptRule = function(event, context) { - console.log(JSON.stringify(event)); - handler = new CreateReceiptRule(event, context); - handler.handle(); -} diff --git a/aws/sns.js b/aws/sns.js deleted file mode 100644 index 63f40bf..0000000 --- a/aws/sns.js +++ /dev/null @@ -1,31 +0,0 @@ -var Promise = require('bluebird'), - AWS = require('aws-sdk'), - base = require('lib/base'), - sns = Promise.promisifyAll(new AWS.SNS()); - -// Exposes the SNS.subscribe API method -function Subscribe(event, context) { - base.Handler.call(this, event, context); -} -Subscribe.prototype = Object.create(base.Handler.prototype); -Subscribe.prototype.handleCreate = function() { - var p = this.event.ResourceProperties; - return sns.subscribeAsync({ - Endpoint: p.Endpoint, - Protocol: p.Protocol, - TopicArn: p.TopicArn - }); -} -Subscribe.prototype.handleDelete = function(referenceData) { - return Promise.try(function() { - if (referenceData && referenceData.SubscriptionArn) { - return sns.unsubscribeAsync({ - SubscriptionArn: referenceData.SubscriptionArn - }); - } - }); -} -exports.subscribe = function(event, context) { - handler = new Subscribe(event, context); - handler.handle(); -} diff --git a/create_cloudformation_helper_functions.template b/create_cloudformation_helper_functions.template deleted file mode 100644 index 82fb4f5..0000000 --- a/create_cloudformation_helper_functions.template +++ /dev/null @@ -1,533 +0,0 @@ -{ - "AWSTemplateFormatVersion": "2010-09-09", - "Resources": { - "ReferenceDB": { - "Type": "AWS::DynamoDB::Table", - "Properties": { - "AttributeDefinitions": [ - { - "AttributeName": "key", - "AttributeType": "S" - } - ], - "KeySchema": [ - { - "AttributeName": "key", - "KeyType": "HASH" - } - ], - "ProvisionedThroughput": { - "ReadCapacityUnits": 1, - "WriteCapacityUnits": 1 - }, - "TableName": { "Fn::Join": [ "-", [ { "Ref" : "AWS::StackName" }, "reference" ] ] } - } - }, - "RoleBasePolicy": { - "Type": "AWS::IAM::ManagedPolicy", - "Properties": { - "Description" : { "Fn::Join": [ " ", [ "Base policy for all Lambda function roles in", { "Ref" : "AWS::StackName" }, "." ] ] }, - "PolicyDocument" : { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "logs:CreateLogGroup", - "logs:CreateLogStream", - "logs:PutLogEvents" - ], - "Resource": "arn:aws:logs:*:*:*" - }, - { - "Effect": "Allow", - "Action": [ - "dynamodb:GetItem", - "dynamodb:PutItem", - "dynamodb:Scan" - ], - "Resource": { "Fn::Join": [ "", [ "arn:aws:dynamodb:", { "Ref": "AWS::Region" }, ":", { "Ref": "AWS::AccountId" } , ":table/", { "Ref": "ReferenceDB" } ] ] } - } - ] - } - } - }, - "ApiGatewayCreateApiFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": [ "lambda.amazonaws.com" ] - }, - "Action": [ "sts:AssumeRole" ] - } - ] - }, - "ManagedPolicyArns": [ - { "Ref": "RoleBasePolicy" } - ], - "Policies": [ - { - "PolicyName": "ApiGatewayWriter", - "PolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "apigateway:*" - ], - "Resource": "*" - } - ] - } - } - ] - } - }, - "ApiGatewayCreateApiFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { "Fn::Join": [ ".", [ "com.gilt.public.backoffice", { "Ref" : "AWS::Region" } ] ] }, - "S3Key": "lambda_functions/cloudformation-helpers.zip" - }, - "Description": "Used to create a full API in Api Gateway.", - "Handler": "aws/apiGateway.createApi", - "Role": {"Fn::GetAtt" : [ "ApiGatewayCreateApiFunctionRole", "Arn" ] }, - "Runtime": "nodejs4.3", - "Timeout": 30 - }, - "DependsOn": [ - "ApiGatewayCreateApiFunctionRole" - ] - }, - "CloudWatchLogsPutMetricFilterFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": [ "lambda.amazonaws.com" ] - }, - "Action": [ "sts:AssumeRole" ] - } - ] - }, - "ManagedPolicyArns": [ - { "Ref": "RoleBasePolicy" } - ], - "Policies": [ - { - "PolicyName": "LogFilterCreator", - "PolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "logs:DeleteMetricFilter", - "logs:PutMetricFilter" - ], - "Resource": "*" - } - ] - } - } - ] - } - }, - "CloudWatchLogsPutMetricFilterFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { "Fn::Join": [ ".", [ "com.gilt.public.backoffice", { "Ref" : "AWS::Region" } ] ] }, - "S3Key": "lambda_functions/cloudformation-helpers.zip" - }, - "Description": "Used to populate a DynamoDB database from CloudFormation", - "Handler": "aws/cloudWatchLogs.putMetricFilter", - "Role": {"Fn::GetAtt" : [ "CloudWatchLogsPutMetricFilterFunctionRole", "Arn" ] }, - "Runtime": "nodejs4.3", - "Timeout": 30 - }, - "DependsOn": [ - "CloudWatchLogsPutMetricFilterFunctionRole" - ] - }, - "DynamoDBPutItemsFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": [ "lambda.amazonaws.com" ] - }, - "Action": [ "sts:AssumeRole" ] - } - ] - }, - "ManagedPolicyArns": [ - { "Ref": "RoleBasePolicy" } - ], - "Policies": [ - { - "PolicyName": "DBWriter", - "PolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "dynamodb:DeleteItem", - "dynamodb:DescribeTable", - "dynamodb:PutItem" - ], - "Resource": { "Fn::Join": [ "", [ "arn:aws:dynamodb:", { "Ref": "AWS::Region" }, ":", { "Ref": "AWS::AccountId" } , ":table/*" ] ] } - } - ] - } - } - ] - } - }, - "DynamoDBPutItemsFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { "Fn::Join": [ ".", [ "com.gilt.public.backoffice", { "Ref" : "AWS::Region" } ] ] }, - "S3Key": "lambda_functions/cloudformation-helpers.zip" - }, - "Description": "Used to populate a DynamoDB database from CloudFormation", - "Handler": "aws/dynamo.putItems", - "Role": {"Fn::GetAtt" : [ "DynamoDBPutItemsFunctionRole", "Arn" ] }, - "Runtime": "nodejs4.3", - "Timeout": 30 - }, - "DependsOn": [ - "DynamoDBPutItemsFunctionRole" - ] - }, - "KinesisCreateStreamFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": [ "lambda.amazonaws.com" ] - }, - "Action": [ "sts:AssumeRole" ] - } - ] - }, - "ManagedPolicyArns": [ - { "Ref": "RoleBasePolicy" } - ], - "Policies": [ - { - "PolicyName": "KinesisCreator", - "PolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "kinesis:CreateStream", - "kinesis:DeleteStream", - "kinesis:DescribeStream" - ], - "Resource": "*" - } - ] - } - } - ] - } - }, - "KinesisCreateStreamFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { "Fn::Join": [ ".", [ "com.gilt.public.backoffice", { "Ref" : "AWS::Region" } ] ] }, - "S3Key": "lambda_functions/cloudformation-helpers.zip" - }, - "Description": "Used to create a Kinesis stream", - "Handler": "aws/kinesis.createStream", - "Role": {"Fn::GetAtt" : [ "KinesisCreateStreamFunctionRole", "Arn" ] }, - "Runtime": "nodejs4.3", - "Timeout": 180 - }, - "DependsOn": [ - "KinesisCreateStreamFunctionRole" - ] - }, - "S3PutObjectFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": [ "lambda.amazonaws.com" ] - }, - "Action": [ "sts:AssumeRole" ] - } - ] - }, - "ManagedPolicyArns": [ - { "Ref": "RoleBasePolicy" } - ], - "Policies": [ - { - "PolicyName": "S3Writer", - "PolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:DeleteObject", - "s3:ListBucket", - "s3:PutObject" - ], - "Resource": "*" - } - ] - } - } - ] - } - }, - "S3PutObjectFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { "Fn::Join": [ ".", [ "com.gilt.public.backoffice", { "Ref" : "AWS::Region" } ] ] }, - "S3Key": "lambda_functions/cloudformation-helpers.zip" - }, - "Description": "Used to put objects into S3.", - "Handler": "aws/s3.putObject", - "Role": {"Fn::GetAtt" : [ "S3PutObjectFunctionRole", "Arn" ] }, - "Runtime": "nodejs4.3", - "Timeout": 30 - }, - "DependsOn": [ - "S3PutObjectFunctionRole" - ] - }, - "S3PutBucketPolicyFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": [ "lambda.amazonaws.com" ] - }, - "Action": [ "sts:AssumeRole" ] - } - ] - }, - "ManagedPolicyArns": [ - { "Ref": "RoleBasePolicy" } - ], - "Policies": [ - { - "PolicyName": "S3PolicyWriter", - "PolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "s3:ListBucket", - "s3:PutBucketPolicy", - "s3:DeleteBucketPolicy" - ], - "Resource": "*" - } - ] - } - } - ] - } - }, - "S3PutBucketPolicyFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": "com.gilt.public.backoffice", - "S3Key": "lambda_functions/cloudformation-helpers.zip" - }, - "Description": "Used to put S3 bucket policy.", - "Handler": "aws/s3.putBucketPolicy", - "Role": {"Fn::GetAtt" : [ "S3PutBucketPolicyFunctionRole", "Arn" ] }, - "Runtime": "nodejs4.3", - "Timeout": 30 - }, - "DependsOn": [ - "S3PutBucketPolicyFunctionRole" - ] - }, - "SnsSubscribeFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": [ "lambda.amazonaws.com" ] - }, - "Action": [ "sts:AssumeRole" ] - } - ] - }, - "ManagedPolicyArns": [ - { "Ref": "RoleBasePolicy" } - ], - "Policies": [ - { - "PolicyName": "SNSSubscriber", - "PolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "sns:subscribe", - "sns:unsubscribe" - ], - "Resource": "*" - } - ] - } - } - ] - } - }, - "SnsSubscribeFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": { "Fn::Join": [ ".", [ "com.gilt.public.backoffice", { "Ref" : "AWS::Region" } ] ] }, - "S3Key": "lambda_functions/cloudformation-helpers.zip" - }, - "Description": "Used to subscribe to existing SNS topics.", - "Handler": "aws/sns.subscribe", - "Role": {"Fn::GetAtt" : [ "SnsSubscribeFunctionRole", "Arn" ] }, - "Runtime": "nodejs4.3", - "Timeout": 30 - }, - "DependsOn": [ - "SnsSubscribeFunctionRole" - ] - }, - "SesCreateReceiptRuleFunctionRole": { - "Type": "AWS::IAM::Role", - "Properties": { - "AssumeRolePolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Principal": { - "Service": [ "lambda.amazonaws.com" ] - }, - "Action": [ "sts:AssumeRole" ] - } - ] - }, - "ManagedPolicyArns": [ - { "Ref": "RoleBasePolicy" } - ], - "Policies": [ - { - "PolicyName": "SESReceiptRuleModifier", - "PolicyDocument": { - "Version" : "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ses:CreateReceiptRule", - "ses:DeleteReceiptRule" - ], - "Resource": "*" - } - ] - } - } - ] - } - }, - "SesCreateReceiptRuleFunction": { - "Type": "AWS::Lambda::Function", - "Properties": { - "Code": { - "S3Bucket": "com.gilt.public.backoffice", - "S3Key": "lambda_functions/cloudformation-helpers.zip" - }, - "Description": "Used to create SES receipt rules.", - "Handler": "aws/ses.createReceiptRule", - "Role": {"Fn::GetAtt" : [ "SesCreateReceiptRuleFunctionRole", "Arn" ] }, - "Runtime": "nodejs4.3", - "Timeout": 30 - }, - "DependsOn": [ - "SesCreateReceiptRuleFunctionRole" - ] - } - }, - "Outputs": { - "ApiGatewayCreateApiFunctionArn": { - "Description": "The ARN of the ApiGatewayCreateApiFunction, for use in other CloudFormation templates.", - "Value": { "Fn::GetAtt" : ["ApiGatewayCreateApiFunction", "Arn"] } - }, - "CloudWatchLogsPutMetricFilterFunctionArn": { - "Description": "The ARN of the CloudWatchLogsPutMetricFilterFunction, for use in other CloudFormation templates.", - "Value": { "Fn::GetAtt" : ["CloudWatchLogsPutMetricFilterFunction", "Arn"] } - }, - "DynamoDBPutItemsFunctionArn": { - "Description": "The ARN of the DynamoDBPutItemsFunction, for use in other CloudFormation templates.", - "Value": { "Fn::GetAtt" : ["DynamoDBPutItemsFunction", "Arn"] } - }, - "KinesisCreateStreamFunctionArn": { - "Description": "The ARN of the KinesisCreateStreamFunction, for use in other CloudFormation templates.", - "Value": { "Fn::GetAtt" : ["KinesisCreateStreamFunction", "Arn"] } - }, - "SnsSubscribeFunctionArn": { - "Description": "The ARN of the SnsSubscribeFunction, for use in other CloudFormation templates.", - "Value": { "Fn::GetAtt" : ["SnsSubscribeFunction", "Arn"] } - }, - "S3PutObjectFunctionArn": { - "Description": "The ARN of the S3PutObjectFunction, for use in other CloudFormation templates.", - "Value": { "Fn::GetAtt" : ["S3PutObjectFunction", "Arn"] } - }, - "S3PutBucketPolicyFunctionArn": { - "Description": "The ARN of the S3PutBucketPolicyFunction, for use in other CloudFormation templates.", - "Value": { "Fn::GetAtt" : ["S3PutBucketPolicyFunction", "Arn"] } - }, - "SesCreateReceiptRuleFunctionArn": { - "Description": "The ARN of the SesCreateReceiptRuleFunction, for use in other CloudFormation templates.", - "Value": { "Fn::GetAtt" : ["SesCreateReceiptRuleFunction", "Arn"] } - } - } -} diff --git a/create_cloudformation_helper_functions.template.yml b/create_cloudformation_helper_functions.template.yml new file mode 100644 index 0000000..21af3bf --- /dev/null +++ b/create_cloudformation_helper_functions.template.yml @@ -0,0 +1,345 @@ +AWSTemplateFormatVersion: 2010-09-09 +Transform: 'AWS::Serverless-2016-10-31' + +Resources: + ReferenceDB: + Type: 'AWS::DynamoDB::Table' + Properties: + AttributeDefinitions: + - AttributeName: key + AttributeType: S + KeySchema: + - AttributeName: key + KeyType: HASH + ProvisionedThroughput: + ReadCapacityUnits: 1 + WriteCapacityUnits: 1 + TableName: !Sub '${AWS::StackName}-reference' + RoleBasePolicy: + Type: 'AWS::IAM::ManagedPolicy' + Properties: + Description: !Sub 'Base policy for all Lambda function roles in ${AWS::StackName}.' + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + Resource: 'arn:aws:logs:*:*:*' + - Effect: Allow + Action: + - dynamodb:GetItem + - dynamodb:PutItem + - dynamodb:Scan + Resource: !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${ReferenceDB}' + ApiGatewayCreateApiFunctionRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - 'sts:AssumeRole' + ManagedPolicyArns: + - !Ref 'RoleBasePolicy' + Policies: + - PolicyName: ApiGatewayWriter + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 'apigateway:*' + Resource: '*' + ApiGatewayCreateApiFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: out/cloudformation-helpers + Description: Used to create a full API in Api Gateway. + Handler: aws/apiGateway.createApi + Runtime: nodejs12.x + Role: !GetAtt 'ApiGatewayCreateApiFunctionRole.Arn' + Timeout: 30 + DependsOn: + - ApiGatewayCreateApiFunctionRole + CloudWatchLogsPutMetricFilterFunctionRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - 'sts:AssumeRole' + ManagedPolicyArns: + - !Ref 'RoleBasePolicy' + Policies: + - PolicyName: LogFilterCreator + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 'logs:DeleteMetricFilter' + - 'logs:PutMetricFilter' + Resource: '*' + CloudWatchLogsPutMetricFilterFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: out/cloudformation-helpers + Description: Used to populate a DynamoDB database from CloudFormation + Handler: aws/cloudWatchLogs.putMetricFilter + Runtime: nodejs12.x + Role: !GetAtt 'CloudWatchLogsPutMetricFilterFunctionRole.Arn' + Timeout: 30 + DependsOn: + - CloudWatchLogsPutMetricFilterFunctionRole + DynamoDBPutItemsFunctionRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - 'sts:AssumeRole' + ManagedPolicyArns: + - !Ref 'RoleBasePolicy' + Policies: + - PolicyName: DBWriter + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 'dynamodb:DeleteItem' + - 'dynamodb:DescribeTable' + - 'dynamodb:PutItem' + Resource: !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/*' + DynamoDBPutItemsFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: out/cloudformation-helpers + Description: Used to populate a DynamoDB database from CloudFormation + Handler: aws/dynamo.putItems + Runtime: nodejs12.x + Role: !GetAtt 'DynamoDBPutItemsFunctionRole.Arn' + Timeout: 30 + DependsOn: + - DynamoDBPutItemsFunctionRole + KinesisCreateStreamFunctionRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - 'sts:AssumeRole' + ManagedPolicyArns: + - !Ref 'RoleBasePolicy' + Policies: + - PolicyName: KinesisCreator + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 'kinesis:CreateStream' + - 'kinesis:DeleteStream' + - 'kinesis:DescribeStream' + Resource: '*' + KinesisCreateStreamFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: out/cloudformation-helpers + Description: Used to create a Kinesis stream + Handler: aws/kinesis.createStream + Runtime: nodejs12.x + Role: !GetAtt 'KinesisCreateStreamFunctionRole.Arn' + Timeout: 180 + DependsOn: + - KinesisCreateStreamFunctionRole + S3PutObjectFunctionRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - 'sts:AssumeRole' + ManagedPolicyArns: + - !Ref 'RoleBasePolicy' + Policies: + - PolicyName: S3Writer + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 's3:DeleteObject' + - 's3:ListBucket' + - 's3:PutObject' + Resource: '*' + S3PutObjectFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: out/cloudformation-helpers + Description: Used to put objects into S3. + Handler: aws/s3.putObject + Runtime: nodejs12.x + Role: !GetAtt 'S3PutObjectFunctionRole.Arn' + Timeout: 30 + DependsOn: + - S3PutObjectFunctionRole + S3PutBucketPolicyFunctionRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - 'sts:AssumeRole' + ManagedPolicyArns: + - !Ref 'RoleBasePolicy' + Policies: + - PolicyName: S3PolicyWriter + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 's3:ListBucket' + - 's3:PutBucketPolicy' + - 's3:DeleteBucketPolicy' + Resource: '*' + S3PutBucketPolicyFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: out/cloudformation-helpers + Description: Used to put S3 bucket policy. + Handler: aws/s3.putBucketPolicy + Runtime: nodejs12.x + Role: !GetAtt 'S3PutBucketPolicyFunctionRole.Arn' + Timeout: 30 + DependsOn: + - S3PutBucketPolicyFunctionRole + SnsSubscribeFunctionRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - 'sts:AssumeRole' + ManagedPolicyArns: + - !Ref 'RoleBasePolicy' + Policies: + - PolicyName: SNSSubscriber + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 'sns:subscribe' + - 'sns:unsubscribe' + Resource: '*' + SnsSubscribeFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: out/cloudformation-helpers + Description: Used to subscribe to existing SNS topics + Handler: aws/sns.subscribe + Runtime: nodejs12.x + Role: !GetAtt 'SnsSubscribeFunctionRole.Arn' + Timeout: 30 + DependsOn: + - SnsSubscribeFunctionRole + SesCreateReceiptRuleFunctionRole: + Type: 'AWS::IAM::Role' + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - 'sts:AssumeRole' + ManagedPolicyArns: + - !Ref 'RoleBasePolicy' + Policies: + - PolicyName: SESReceiptRuleModifier + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Action: + - 'ses:CreateReceiptRule' + - 'ses:DeleteReceiptRule' + Resource: '*' + SesCreateReceiptRuleFunction: + Type: 'AWS::Serverless::Function' + Properties: + CodeUri: out/cloudformation-helpers + Description: Used to create SES receipt rules + Handler: aws/ses.createReceiptRule + Runtime: nodejs12.x + Role: !GetAtt 'SesCreateReceiptRuleFunctionRole.Arn' + Timeout: 30 + DependsOn: + - SesCreateReceiptRuleFunctionRole + +Outputs: + ApiGatewayCreateApiFunctionArn: + Description: The ARN of the ApiGatewayCreateApiFunction, for use in other CloudFormation templates + Value: !GetAtt 'ApiGatewayCreateApiFunction.Arn' + CloudWatchLogsPutMetricFilterFunctionArn: + Description: The ARN of the CloudWatchLogsPutMetricFilterFunction, for use in other CloudFormation templates + Value: !GetAtt 'CloudWatchLogsPutMetricFilterFunction.Arn' + DynamoDBPutItemsFunctionArn: + Description: The ARN of the DynamoDBPutItemsFunction, for use in other CloudFormation templates. + Value: !GetAtt 'DynamoDBPutItemsFunction.Arn' + KinesisCreateStreamFunctionArn: + Description: The ARN of the KinesisCreateStreamFunction, for use in other CloudFormation templates + Value: !GetAtt 'KinesisCreateStreamFunction.Arn' + SnsSubscribeFunctionArn: + Description: The ARN of the SnsSubscribeFunction, for use in other CloudFormation templates. + Value: !GetAtt 'SnsSubscribeFunction.Arn' + S3PutObjectFunctionArn: + Description: The ARN of the S3PutObjectFunction, for use in other CloudFormation templates. + Value: !GetAtt 'S3PutObjectFunction.Arn' + S3PutBucketPolicyFunctionArn: + Description: The ARN of the S3PutBucketPolicyFunction, for use in other CloudFormation templates. + Value: !GetAtt 'S3PutBucketPolicyFunction.Arn' + SesCreateReceiptRuleFunctionArn: + Description: The ARN of the SesCreateReceiptRuleFunction, for use in other CloudFormation templates. + Value: !GetAtt 'SesCreateReceiptRuleFunction.Arn' diff --git a/package.json b/package.json deleted file mode 100644 index c284faf..0000000 --- a/package.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "cloudformation-helpers", - "version": "0.0.0", - "description": "A set of helper methods to fill in the gaps in existing CloudFormation support.", - "repository": { - "type": "git", - "url": "https://github.com/gilt/cloudformation-helpers" - }, - "keywords": [ - "cloudformation" - ], - "author": "Ryan Martin", - "license": "Apache 2.0", - "bugs": { - "url": "https://github.com/gilt/cloudformation-helpers/issues" - }, - "dependencies": { - "bluebird": "3.x" - } -} diff --git a/src/.eslintrc.js b/src/.eslintrc.js new file mode 100644 index 0000000..2b4167b --- /dev/null +++ b/src/.eslintrc.js @@ -0,0 +1,20 @@ +module.exports = + { + "env": { + "node": true, + "es6": true, + }, + "globals": { + "handler": "readonly", + }, + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "module", + }, + "rules": { + "no-undef": "error", + "no-unused-vars": "error", + "no-var": "error", + "semi": ["error", "always" ], + } + }; diff --git a/src/aws/apiGateway.js b/src/aws/apiGateway.js new file mode 100644 index 0000000..8a21419 --- /dev/null +++ b/src/aws/apiGateway.js @@ -0,0 +1,114 @@ +const + utilPromisifyAll = require('util-promisifyall'), + AWS = require('aws-sdk'), + base = require('lib/base'), + apiGateway = utilPromisifyAll(new AWS.APIGateway()); + +// Exposes the SNS.subscribe API method +function CreateApi(event, context) { + base.Handler.call(this, event, context); +} +CreateApi.prototype = Object.create(base.Handler.prototype); +CreateApi.prototype.handleCreate = async function() { + const p = this.event.ResourceProperties; + const apiData = await apiGateway.createRestApiAsync({ + name: p.name, + description: p.description + }); + await this.setReferenceData({ restApiId: apiData.id }); // Set this immediately, in case later calls fail + const resourceData = await apiGateway.getResourcesAsync({ + restApiId: apiData.id + }); + await setupEndpoints(p.endpoints, resourceData.items[0].id, apiData.id); + await apiGateway.createDeploymentAsync({ + restApiId: apiData.id, + stageName: p.version + }); + // Total hack: there are limits to the number of API Gateway API calls you can make. So this function + // will fail if a CloudFormation template includes two or more APIs. Attempting to avoid this by blocking. + const until = new Date(); + until.setSeconds(until.getSeconds() + 60); + while (new Date() < until) { + // Wait... + } + // AWS.config.region is a bit of a hack, but I can't figure out how else to dynamically + // detect the region of the API - seems to be nothing in API Gateway or AWS Lambda context. + // Could possibly get it from the CloudFormation stack, but that seems wrong. + return { + baseUrl: "https://" + apiData.id + ".execute-api." + AWS.config.region + ".amazonaws.com/" + p.version, + restApiId: apiData.id + }; +}; +CreateApi.prototype.handleDelete = async function(referenceData) { + if (referenceData && referenceData.restApiId) { + // Can simply delete the entire API - don't need to delete each individual component + return await apiGateway.deleteRestApiAsync({ + restApiId: referenceData.restApiId + }); + } +}; +async function setupEndpoints(config, parentResourceId, restApiId) { + return await Promise.all( + Object.keys(config).map(async key => { + switch (key.toUpperCase()) { + case 'GET': + case 'HEAD': + case 'DELETE': + case 'OPTIONS': + case 'PATCH': + case 'POST': + case 'PUT': + const params = config[key]; + params["httpMethod"] = key.toUpperCase(); + params["resourceId"] = parentResourceId; + params["restApiId"] = restApiId; + params["apiKeyRequired"] = params["apiKeyRequired"] == "true"; // Passing through CloudFormation, booleans become strings :/ + const integration = params["integration"]; + delete params.integration; + await apiGateway.putMethodAsync(params); + if (integration) { + const contentType = integration["contentType"]; + if (!contentType) { + throw "Integration config must include response contentType."; + } + delete integration.contentType; + integration["httpMethod"] = key.toUpperCase(); + integration["resourceId"] = parentResourceId; + integration["restApiId"] = restApiId; + apiGateway.putIntegrationAsync(integration); + const responseContentTypes = {}; + responseContentTypes[contentType] = "Empty"; + await apiGateway.putMethodResponseAsync({ + httpMethod: key.toUpperCase(), + resourceId: parentResourceId, + restApiId: restApiId, + statusCode: '200', + responseModels: responseContentTypes + }); + responseContentTypes[contentType] = ""; + return await apiGateway.putIntegrationResponseAsync({ + httpMethod: key.toUpperCase(), + resourceId: parentResourceId, + restApiId: restApiId, + statusCode: '200', + responseTemplates: responseContentTypes + }); + } + else { + return; + } + default: + const resourceData = await apiGateway.createResourceAsync({ + parentId: parentResourceId, + pathPart: key, + restApiId: restApiId, + }); + return await setupEndpoints(config[key], resourceData.id, restApiId); + } + }) + ); +} +exports.createApi = function(event, context) { + handler = new CreateApi(event, context); + handler.handle(); +}; diff --git a/src/aws/dynamo.js b/src/aws/dynamo.js new file mode 100644 index 0000000..b1ba638 --- /dev/null +++ b/src/aws/dynamo.js @@ -0,0 +1,64 @@ +const + utilPromisifyAll = require('util-promisifyall'), + AWS = require('aws-sdk'), + base = require('lib/base'), + helpers = require('lib/helpers'), + dynamoDB = utilPromisifyAll(new AWS.DynamoDB()); + +// Exposes the SNS.subscribe API method +function PutItems(event, context) { + base.Handler.call(this, event, context); +} +PutItems.prototype = Object.create(base.Handler.prototype); +PutItems.prototype.handleCreate = async function() { + const p = this.event.ResourceProperties; + + const tableData = await dynamoDB.describeTableAsync({ + TableName: p.TableName + }); + + const itemsInserted = await Promise.all( + p.Items.map(async item => { + await dynamoDB.putItemAsync({ + TableName: p.TableName, + Item: helpers.formatForDynamo(item, true) + }); + + const key = {}; + tableData.Table.KeySchema.forEach(function(keyMember) { + key[keyMember.AttributeName] = item[keyMember.AttributeName]; + }); + + return { + TableName: p.TableName, + Key: key + }; + }) + ); + + return { + ItemsInserted: itemsInserted + }; +}; +PutItems.prototype.handleDelete = async function(referenceData) { + if (referenceData) { + const itemsDeleted = await Promise.all( + referenceData.itemsInserted.map(async item => { + await dynamoDB.deleteItemAsync({ + TableName: item.TableName, + Key: helpers.formatForDynamo(item.Key, true) + }); + + return item.Key; + }) + ); + + return { + ItemsDeleted: itemsDeleted + }; + } +}; +exports.putItems = function(event, context) { + handler = new PutItems(event, context); + handler.handle(); +}; diff --git a/src/aws/kinesis.js b/src/aws/kinesis.js new file mode 100644 index 0000000..7580fe5 --- /dev/null +++ b/src/aws/kinesis.js @@ -0,0 +1,54 @@ +const + utilPromisifyAll = require('util-promisifyall'), + AWS = require('aws-sdk'), + base = require('lib/base'), + kinesis = utilPromisifyAll(new AWS.Kinesis()); + +// Exposes the SNS.subscribe API method +function CreateStream(event, context) { + base.Handler.call(this, event, context); +} +CreateStream.prototype = Object.create(base.Handler.prototype); +CreateStream.prototype.handleCreate = async function() { + const p = this.event.ResourceProperties; + delete p.ServiceToken; + p.ShardCount = parseInt(p.ShardCount); + await kinesis.createStreamAsync(p); + const arn = await waitWhileStatus(p.StreamName, "CREATING"); + return { + StreamName: p.StreamName, + Arn: arn + }; +}; +// eslint-disable-next-line no-unused-vars +CreateStream.prototype.handleDelete = async function(referenceData) { + try { + const p = this.event.ResourceProperties; + await kinesis.deleteStreamAsync({StreamName: p.StreamName}); + return await waitWhileStatus(p.StreamName, "DELETING"); + } + catch(err) { + return err; + } +}; +exports.createStream = function(event, context) { + handler = new CreateStream(event, context); + handler.handle(); +}; +// Watch until the given status is no longer the status of the stream. +async function waitWhileStatus(streamName, status) { + const validStatuses = ["CREATING", "DELETING", "ACTIVE", "UPDATING"]; + if (validStatuses.indexOf(status) >= 0) { + const data = await kinesis.describeStreamAsync({StreamName: streamName}); + + console.log("Current status for [" + streamName +"]: " + data.StreamDescription.StreamStatus); + if (data.StreamDescription.StreamStatus == status) { + await new Promise((resolve) => setTimeout(resolve, 2000)); + return await waitWhileStatus(streamName, status); + } else { + return data.StreamDescription.StreamARN; + } + } else { + throw "status [" + status + "] not one of [" + validStatuses.join(", ") + "]"; + } +} \ No newline at end of file diff --git a/src/aws/s3.js b/src/aws/s3.js new file mode 100644 index 0000000..bc4f91f --- /dev/null +++ b/src/aws/s3.js @@ -0,0 +1,72 @@ +const + utilPromisifyAll = require('util-promisifyall'), + AWS = require('aws-sdk'), + base = require('lib/base'), + s3 = utilPromisifyAll(new AWS.S3()); + +// Exposes the SNS.subscribe API method +function PutObject(event, context) { + base.Handler.call(this, event, context); +} +PutObject.prototype = Object.create(base.Handler.prototype); +PutObject.prototype.handleCreate = async function() { + const p = this.event.ResourceProperties; + delete p.ServiceToken; + return await s3.putObjectAsync(p); +}; +// eslint-disable-next-line no-unused-vars +PutObject.prototype.handleDelete = async function(referenceData) { + const p = this.event.ResourceProperties; + if (p.Key.endsWith("/")) { + const subObjects = await s3.listObjectsAsync({ + Bucket: p.Bucket, + Prefix: p.Key + }); + + await Promise.all( + subObjects.Contents.map(async item => { + return await s3.deleteObjectAsync({ + Bucket: p.Bucket, + Key: item.Key + }); + }) + ); + } + + return s3.deleteObjectAsync({ + Bucket: p.Bucket, + Key: p.Key + }); +}; +exports.putObject = function(event, context) { + handler = new PutObject(event, context); + handler.handle(); +}; + +// Exposes the S3.putBucketPolicy API method +function PutBucketPolicy(event, context) { + base.Handler.call(this, event, context); +} +PutBucketPolicy.prototype = Object.create(base.Handler.prototype); +PutBucketPolicy.prototype.handleCreate = async function() { + const p = this.event.ResourceProperties; + delete p.ServiceToken; + await s3.putBucketPolicyAsync(p); + + return { + BucketName : p.Bucket + }; +}; +PutBucketPolicy.prototype.handleDelete = async function(referencedData) { + if(referencedData) { + return await s3.deleteBucketPolicyAsync({ + Bucket : referencedData.BucketName + }); + } +}; +exports.putBucketPolicy = function(event, context) { + console.log(JSON.stringify(event)); + handler = new PutBucketPolicy(event, context); + handler.handle(); +}; + diff --git a/src/aws/ses.js b/src/aws/ses.js new file mode 100644 index 0000000..2091c47 --- /dev/null +++ b/src/aws/ses.js @@ -0,0 +1,36 @@ +const + utilPromisifyAll = require('util-promisifyall'), + AWS = require('aws-sdk'), + base = require('lib/base'), + ses = utilPromisifyAll(new AWS.SES()); + +// Exposes the SES.createReceiptRule API method +function CreateReceiptRule(event, context) { + base.Handler.call(this, event, context); +} +CreateReceiptRule.prototype = Object.create(base.Handler.prototype); +CreateReceiptRule.prototype.handleCreate = async function() { + const p = this.event.ResourceProperties; + delete p.ServiceToken; + p.Rule.Enabled = ("true" === p.Rule.Enabled ); + p.Rule.ScanEnabled = ("true" === p.Rule.ScanEnabled ); + await ses.createReceiptRuleAsync(p); + + return { + RuleSetName : p.RuleSetName, + RuleName : p.Rule.Name + }; +}; +CreateReceiptRule.prototype.handleDelete = async function(referenceData) { + if (referenceData) { + return await ses.deleteReceiptRuleAsync({ + RuleSetName : referenceData.RuleSetName, + RuleName : referenceData.RuleName + }); + } +}; +exports.createReceiptRule = function(event, context) { + console.log(JSON.stringify(event)); + handler = new CreateReceiptRule(event, context); + handler.handle(); +}; diff --git a/src/aws/sns.js b/src/aws/sns.js new file mode 100644 index 0000000..cfbc4a1 --- /dev/null +++ b/src/aws/sns.js @@ -0,0 +1,30 @@ +const + utilPromisifyAll = require('util-promisifyall'), + AWS = require('aws-sdk'), + base = require('lib/base'), + sns = utilPromisifyAll(new AWS.SNS()); + +// Exposes the SNS.subscribe API method +function Subscribe(event, context) { + base.Handler.call(this, event, context); +} +Subscribe.prototype = Object.create(base.Handler.prototype); +Subscribe.prototype.handleCreate = async function() { + const p = this.event.ResourceProperties; + return await sns.subscribeAsync({ + Endpoint: p.Endpoint, + Protocol: p.Protocol, + TopicArn: p.TopicArn + }); +}; +Subscribe.prototype.handleDelete = async function(referenceData) { + if (referenceData && referenceData.SubscriptionArn) { + return await sns.unsubscribeAsync({ + SubscriptionArn: referenceData.SubscriptionArn + }); + } +}; +exports.subscribe = function(event, context) { + handler = new Subscribe(event, context); + handler.handle(); +}; diff --git a/lib/base.js b/src/lib/base.js similarity index 51% rename from lib/base.js rename to src/lib/base.js index 25f5164..1142b79 100644 --- a/lib/base.js +++ b/src/lib/base.js @@ -1,49 +1,45 @@ // Implement this class for every new handler. -var Promise = require('bluebird'), - helpers = require('lib/helpers'), - response = require('lib/cfn-response'), - AWS = require('aws-sdk'), - dynamoDB = Promise.promisifyAll(new AWS.DynamoDB()); +const + utilPromisifyAll = require('util-promisifyall'), + helpers = require('lib/helpers'), + response = require('lib/cfn-response'), + AWS = require('aws-sdk'), + dynamoDB = utilPromisifyAll(new AWS.DynamoDB()); exports.Handler = function(event, context) { this.event = event; this.context = context; -} +}; -exports.Handler.prototype.handle = function() { - var outer = this; - Promise.try(function() { - switch (outer.event.RequestType) { +exports.Handler.prototype.handle = async function() { + try { + let data; + let referenceData; + switch (this.event.RequestType) { case 'Create': - return outer.handleCreate() - .then(function(data) { - return outer.setReferenceData(data) - .then(function() { - return data; - }); - }); + const created = await this.handleCreate(); + await this.setReferenceData(created); + data = created; + break; case 'Delete': - return outer.getReferenceData() - .then(function(data) { - return outer.handleDelete(data); - }); + referenceData = await this.getReferenceData(); + data = await this.handleDelete(referenceData); + break; case 'Update': - return outer.getReferenceData() - .then(function(data) { - return outer.handleUpdate(); - }); + await this.getReferenceData(); + data = await this.handleUpdate(); + break; default: - throw "Unrecognized RequestType [" + outer.event.RequestType + "]"; + throw "Unrecognized RequestType [" + this.event.RequestType + "]"; } - }) - .then(function(data) { - response.send(outer.event, outer.context, response.SUCCESS, data); - }) - .catch(function(err) { - outer.error(err); - }); -} + + response.send(this.event, this.context, response.SUCCESS, data); + } + catch(err) { + this.error(err); + } +}; /* When implemented, these should all return a Promise, which will then be completed by the handle() @@ -54,59 +50,56 @@ exports.Handler.prototype.handle = function() { */ exports.Handler.prototype.handleCreate = function() { throw "create method not implemented"; -} +}; +// eslint-disable-next-line no-unused-vars exports.Handler.prototype.handleDelete = function(referenceData) { throw "delete method not implemented"; -} +}; -exports.Handler.prototype.handleUpdate = function(referenceData) { - var self = this; - return this.handleDelete(referenceData) - .then(function() { - return self.handleCreate(); - }); -} +exports.Handler.prototype.handleUpdate = async function(referenceData) { + await this.handleDelete(referenceData); + return await this.handleCreate(); +}; exports.Handler.prototype.error = function(message) { console.error(message); response.send(this.event, this.context, response.FAILED, { Error: message }); throw message; -} +}; exports.Handler.prototype.getStackName = function() { - var functionName = this.context.functionName; + const functionName = this.context.functionName; // Assume functionName is: stackName-resourceLogicalId-randomString. // Per http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/resources-section-structure.html, // resourceLogicalId cannot include a '-'; randomString seems to also be alphanumeric. // Thus it seems safe to search for the two dashes in order to find the stackName. - var i = functionName.lastIndexOf("-", functionName.lastIndexOf("-") - 1); + const i = functionName.lastIndexOf("-", functionName.lastIndexOf("-") - 1); if (i >= 0) return functionName.substr(0, i); else return functionName; -} +}; -exports.Handler.prototype.getReferenceData = function() { - return dynamoDB.getItemAsync( +exports.Handler.prototype.getReferenceData = async function() { + const data = await dynamoDB.getItemAsync( { TableName: this.getStackName() + "-reference", Key: helpers.formatForDynamo({ key: this.event.StackId + this.event.LogicalResourceId }, true) } - ) - .then(function(data) { - data = helpers.formatFromDynamo(data); - if (data && data.Item && data.Item.value) - return data.Item.value; - else - return null; - }) -} + ); -exports.Handler.prototype.setReferenceData = function(data) { - return dynamoDB.putItemAsync( + const formattedData = helpers.formatFromDynamo(data); + if (formattedData && formattedData.Item && formattedData.Item.value) + return formattedData.Item.value; + else + return null; +}; + +exports.Handler.prototype.setReferenceData = async function(data) { + return await dynamoDB.putItemAsync( { TableName: this.getStackName() + "-reference", Item: helpers.formatForDynamo({ @@ -115,4 +108,4 @@ exports.Handler.prototype.setReferenceData = function(data) { }, true) } ); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/cfn-response.js b/src/lib/cfn-response.js similarity index 89% rename from lib/cfn-response.js rename to src/lib/cfn-response.js index b2d227d..ebd6e8e 100644 --- a/lib/cfn-response.js +++ b/src/lib/cfn-response.js @@ -10,7 +10,7 @@ exports.FAILED = "FAILED"; exports.send = function(event, context, responseStatus, responseData, physicalResourceId) { - var responseBody = JSON.stringify({ + const responseBody = JSON.stringify({ Status: responseStatus, Reason: "See the details in CloudWatch Log Stream: " + context.logStreamName, PhysicalResourceId: physicalResourceId || context.logStreamName, @@ -22,15 +22,15 @@ exports.send = function(event, context, responseStatus, responseData, physicalRe console.log("Response body:\n", responseBody); - var https = require("https"); - var url = require("url"); + const https = require("https"); + const url = require("url"); // This script comes from http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html#cfn-lambda-function-code-cfnresponsemodule // The only change is this 'if' statement around this block of code, so it doesn't // fail when the ResonseURL is missing (i.e. during manual testing). if (event.ResponseURL) { - var parsedUrl = url.parse(event.ResponseURL); - var options = { + const parsedUrl = url.parse(event.ResponseURL); + const options = { hostname: parsedUrl.hostname, port: 443, path: parsedUrl.path, @@ -41,7 +41,7 @@ exports.send = function(event, context, responseStatus, responseData, physicalRe } }; - var request = https.request(options, function(response) { + const request = https.request(options, function(response) { console.log("Status code: " + response.statusCode); console.log("Status message: " + response.statusMessage); context.done(); @@ -55,4 +55,4 @@ exports.send = function(event, context, responseStatus, responseData, physicalRe request.write(responseBody); request.end(); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/lib/helpers.js b/src/lib/helpers.js similarity index 80% rename from lib/helpers.js rename to src/lib/helpers.js index 7663e5d..00674ab 100644 --- a/lib/helpers.js +++ b/src/lib/helpers.js @@ -1,5 +1,3 @@ -var Promise = require('bluebird'); - // Translates from raw JSON into DynamoDB-formatted JSON. This is more than a // convenience thing: the original iteration of this accepted DyanamoDB-JSON Items, // to avoid complications in translation. But when CloudFormation passes the parameters @@ -8,44 +6,44 @@ var Promise = require('bluebird'); // it is 'true' or 'false') as a 'BOOL' value. So some translation was needed, and // it seemed best to then simplify things for the client by accepting raw JSON. exports.formatForDynamo = function(value, topLevel) { - var result = undefined; + let result = undefined; if (value == 'true' || value == 'false') { - result = {'BOOL': value == 'true'} + result = {'BOOL': value == 'true'}; } else if (!isNaN(value) && value.trim() != '') { - result = {'N': value} + result = {'N': value}; } else if (Array.isArray(value)) { - var arr = []; - for (var i = 0; i < value.length; i++) { + const arr = []; + for (let i = 0; i < value.length; i++) { arr.push(exports.formatForDynamo(value[i], false)); } result = {'L': arr}; } else if (typeof value === "object") { - var map = {}; + const map = {}; Object.keys(value).forEach(function(key) { - map[key] = exports.formatForDynamo(value[key], false) + map[key] = exports.formatForDynamo(value[key], false); }); if (topLevel) result = map; - else result = {'M': map} + else result = {'M': map}; } else { - result = {'S': value} + result = {'S': value}; } return result; -} +}; exports.formatFromDynamo = function(value) { - var result = undefined; + let result = undefined; if (typeof value === "string" || typeof value === 'boolean' || typeof value === 'number') { result = value; } else if (Array.isArray(value)) { - var arr = []; - for (var i = 0; i < value.length; i++) { + const arr = []; + for (let i = 0; i < value.length; i++) { arr.push(exports.formatFromDynamo(value[i])); } result = arr; } else if (typeof value === "object") { - var map = {}; + const map = {}; Object.keys(value).forEach(function(key) { - var v = exports.formatFromDynamo(value[key]); + const v = exports.formatFromDynamo(value[key]); switch (key) { case 'B': throw "Unsupported Mongo type [B]"; @@ -65,7 +63,7 @@ exports.formatFromDynamo = function(value) { break; case 'NS': result = []; - for (var i = 0; i < v.length; i++) { + for (let i = 0; i < v.length; i++) { result.push(Number(value[i])); }; break; @@ -85,16 +83,16 @@ exports.formatFromDynamo = function(value) { throw "Unrecognized type [" + (typeof value) + "]"; } return result; -} +}; if (!String.prototype.endsWith) { String.prototype.endsWith = function(searchString, position) { - var subjectString = this.toString(); + const subjectString = this.toString(); if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; } position -= searchString.length; - var lastIndex = subjectString.indexOf(searchString, position); + const lastIndex = subjectString.indexOf(searchString, position); return lastIndex !== -1 && lastIndex === position; }; } \ No newline at end of file diff --git a/src/package.json b/src/package.json new file mode 100644 index 0000000..878899a --- /dev/null +++ b/src/package.json @@ -0,0 +1,33 @@ +{ + "name": "cloudformation-helpers", + "version": "0.0.0", + "description": "A set of helper methods to fill in the gaps in existing CloudFormation support.", + "repository": { + "type": "git", + "url": "https://github.com/empathicqubit/cloudformation-helpers" + }, + "keywords": [ + "cloudformation" + ], + "author": "Ryan Martin, EmpathicQubit", + "license": "Apache 2.0", + "bugs": { + "url": "https://github.com/empathicquibt/cloudformation-helpers/issues" + }, + "scripts": { + "package": "shx mkdir -p ../out && shx rm -rf ../out/cloudformation-helpers && shx cp -r ../src ../out/cloudformation-helpers && cd ../out/cloudformation-helpers && yarn install --production", + "lint": "eslint .", + "deploy": "cd .. && cross-env-shell \"aws cloudformation package --s3-bucket $CF_S3_BUCKET --template-file create_cloudformation_helper_functions.template.yml --output-template-file out/create_cloudformation_helper_functions.template.yml\" && cross-env-shell \"aws cloudformation deploy --template-file out/create_cloudformation_helper_functions.template.yml --capabilities CAPABILITY_NAMED_IAM --stack-name $CF_STACK_NAME\"", + "publish": "npm-run-all lint package deploy" + }, + "private": true, + "dependencies": { + "util-promisifyall": "^1.0.6" + }, + "devDependencies": { + "cross-env": "^7.0.2", + "eslint": "^7.2.0", + "npm-run-all": "^4.1.5", + "shx": "^0.3.2" + } +} diff --git a/src/yarn.lock b/src/yarn.lock new file mode 100644 index 0000000..7a4ddf3 --- /dev/null +++ b/src/yarn.lock @@ -0,0 +1,1231 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@babel/code-frame@^7.0.0": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.1.tgz#d5481c5095daa1c57e16e54c6f9198443afb49ff" + integrity sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw== + dependencies: + "@babel/highlight" "^7.10.1" + +"@babel/helper-validator-identifier@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz#5770b0c1a826c4f53f5ede5e153163e0318e94b5" + integrity sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw== + +"@babel/highlight@^7.10.1": + version "7.10.1" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.1.tgz#841d098ba613ba1a427a2b383d79e35552c38ae0" + integrity sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg== + dependencies: + "@babel/helper-validator-identifier" "^7.10.1" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + +acorn-jsx@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.2.0.tgz#4c66069173d6fdd68ed85239fc256226182b2ebe" + integrity sha512-HiUX/+K2YpkpJ+SzBffkM/AQ2YE03S0U1kjTLVpoJdhZMOWy8qvXVN9JdLqv2QsaQ6MPYQIuNmwD8zOiYUofLQ== + +acorn@^7.2.0: + version "7.3.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd" + integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA== + +ajv@^6.10.0, ajv@^6.10.2: + version "6.12.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd" + integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ansi-escapes@^4.2.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" + integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + dependencies: + type-fest "^0.11.0" + +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +chalk@^2.0.0, chalk@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" + integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-width@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" + integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +cross-env@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.2.tgz#bd5ed31339a93a3418ac4f3ca9ca3403082ae5f9" + integrity sha512-KZP/bMEOJEDCkDQAyRhu3RL2ZO/SUVrxQVI0G3YEQ+OLbRA3c6zgixe8Mq8a/z7+HKlNEjo8oiLUs8iRijY2Rw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.1, cross-spawn@^7.0.2: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +deep-is@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +emoji-regex@^7.0.1: + version "7.0.3" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" + integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.17.0-next.1, es-abstract@^1.17.5: + version "1.17.6" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" + integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.0" + is-regex "^1.1.0" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es6-object-assign@^1.0.3: + version "1.1.0" + resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" + integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +eslint-scope@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5" + integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-utils@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27" + integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg== + dependencies: + eslint-visitor-keys "^1.1.0" + +eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.2.0.tgz#74415ac884874495f78ec2a97349525344c981fa" + integrity sha512-WFb4ihckKil6hu3Dp798xdzSfddwKKU3+nGniKF6HfeW6OLd2OUDEPP7TcHtB5+QXOKg2s6B2DaMPE1Nn/kxKQ== + +eslint@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.2.0.tgz#d41b2e47804b30dbabb093a967fb283d560082e6" + integrity sha512-B3BtEyaDKC5MlfDa2Ha8/D6DsS4fju95zs0hjS3HdGazw+LNayai38A25qMppK37wWGWNYSPOR6oYzlz5MHsRQ== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.0.1" + doctrine "^3.0.0" + eslint-scope "^5.1.0" + eslint-utils "^2.0.0" + eslint-visitor-keys "^1.2.0" + espree "^7.1.0" + esquery "^1.2.0" + esutils "^2.0.2" + file-entry-cache "^5.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^5.0.0" + globals "^12.1.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^7.0.0" + is-glob "^4.0.0" + js-yaml "^3.13.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash "^4.17.14" + minimatch "^3.0.4" + natural-compare "^1.4.0" + optionator "^0.9.1" + progress "^2.0.0" + regexpp "^3.1.0" + semver "^7.2.1" + strip-ansi "^6.0.0" + strip-json-comments "^3.1.0" + table "^5.2.3" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-7.1.0.tgz#a9c7f18a752056735bf1ba14cb1b70adc3a5ce1c" + integrity sha512-dcorZSyfmm4WTuTnE5Y7MEN1DyoPYy1ZR783QW1FJoenn7RailyWFsq/UL6ZAAA7uXurN9FIpYyUs3OfiIW+Qw== + dependencies: + acorn "^7.2.0" + acorn-jsx "^5.2.0" + eslint-visitor-keys "^1.2.0" + +esprima@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.3.1.tgz#b78b5828aa8e214e29fb74c4d5b752e1c033da57" + integrity sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== + dependencies: + estraverse "^4.1.0" + +estraverse@^4.1.0, estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.1.0.tgz#374309d39fd935ae500e7b92e8a6b4c720e59642" + integrity sha512-FyohXK+R0vE+y1nHLoBM7ZTyqRpqAlhdZHCWIWEviFLiGB8b04H6bQs8G+XTthacvT8VuwvteiP7RJSxMs8UEw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c" + integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g== + dependencies: + flat-cache "^2.0.1" + +flat-cache@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-2.0.1.tgz#5d296d6f04bda44a4630a301413bdbc2ec085ec0" + integrity sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA== + dependencies: + flatted "^2.0.0" + rimraf "2.6.3" + write "1.0.3" + +flatted@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" + integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + +glob-parent@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" + integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + dependencies: + is-glob "^4.0.1" + +glob@^7.0.0, glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + 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@^12.1.0: + version "12.4.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8" + integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg== + dependencies: + type-fest "^0.8.1" + +graceful-fs@^4.1.2: + version "4.2.4" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb" + integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-symbols@^1.0.0, has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hosted-git-info@^2.1.4: + version "2.8.8" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" + integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +import-fresh@^3.0.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" + integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inquirer@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.2.0.tgz#63ce99d823090de7eb420e4bb05e6f3449aa389a" + integrity sha512-E0c4rPwr9ByePfNlTIB8z51kK1s2n6jrHuJeEHENl/sbq2G/S1auvibgEwNR4uSyiU+PiYHqSwsgGiXjG8p5ZQ== + dependencies: + ansi-escapes "^4.2.1" + chalk "^3.0.0" + cli-cursor "^3.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.15" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.5.3" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= + +is-callable@^1.1.4, is-callable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" + integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== + +is-date-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" + integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.0, is-glob@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" + integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== + dependencies: + is-extglob "^2.1.1" + +is-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" + integrity sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw== + dependencies: + has-symbols "^1.0.1" + +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +json-parse-better-errors@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +load-json-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" + integrity sha1-L19Fq5HjMhYjT9U62rZo607AmTs= + dependencies: + graceful-fs "^4.1.2" + parse-json "^4.0.0" + pify "^3.0.0" + strip-bom "^3.0.0" + +lodash@^4.17.14, lodash@^4.17.15: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +memorystream@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2" + integrity sha1-htcJCzDORV1j+64S3aUaR93K+bI= + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.2.0, minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mkdirp@^0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +ms@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +normalize-package-data@^2.3.2: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +npm-run-all@^4.1.5: + version "4.1.5" + resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba" + integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ== + dependencies: + ansi-styles "^3.2.1" + chalk "^2.4.1" + cross-spawn "^6.0.5" + memorystream "^0.3.1" + minimatch "^3.0.4" + pidtree "^0.3.0" + read-pkg "^3.0.0" + shell-quote "^1.6.1" + string.prototype.padend "^3.0.0" + +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + +object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +onetime@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5" + integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q== + dependencies: + mimic-fn "^2.1.0" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-json@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" + integrity sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA= + dependencies: + error-ex "^1.3.1" + json-parse-better-errors "^1.0.1" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + dependencies: + pify "^3.0.0" + +pidtree@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a" + integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA== + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +read-pkg@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" + integrity sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k= + dependencies: + load-json-file "^4.0.0" + normalize-package-data "^2.3.2" + path-type "^3.0.0" + +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q= + dependencies: + resolve "^1.1.6" + +regexpp@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2" + integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q== + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve@^1.1.6, resolve@^1.10.0: + version "1.17.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" + integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== + dependencies: + path-parse "^1.0.6" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +rimraf@2.6.3: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== + dependencies: + glob "^7.1.3" + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +rxjs@^6.5.3: + version "6.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" + integrity sha512-WfQI+1gohdf0Dai/Bbmk5L5ItH5tYqm3ki2c5GdWhKjalzjg93N3avFjVStyZZz+A2Em+ZxKH5bNghw9UeylGQ== + dependencies: + tslib "^1.9.0" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +"semver@2 || 3 || 4 || 5", semver@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@^7.2.1: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +shell-quote@^1.6.1: + version "1.7.2" + resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2" + integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg== + +shelljs@^0.8.1: + version "0.8.4" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2" + integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + +shx@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/shx/-/shx-0.3.2.tgz#40501ce14eb5e0cbcac7ddbd4b325563aad8c123" + integrity sha512-aS0mWtW3T2sHAenrSrip2XGv39O9dXIFUqxAEWHEOS1ePtGIBavdPJY1kE2IHl14V/4iCbUiNDPGdyYTtmhSoA== + dependencies: + es6-object-assign "^1.0.3" + minimist "^1.2.0" + shelljs "^0.8.1" + +signal-exit@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" + integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== + +slice-ansi@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.5" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654" + integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q== + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + +string-width@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" + integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== + dependencies: + emoji-regex "^7.0.1" + is-fullwidth-code-point "^2.0.0" + strip-ansi "^5.1.0" + +string-width@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + +string.prototype.padend@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.0.tgz#dc08f57a8010dc5c153550318f67e13adbb72ac3" + integrity sha512-3aIv8Ffdp8EZj8iLwREGpQaUZiPyrWrpzMBHvkiSW/bK/EGve9np07Vwy7IJ5waydpGXzQZu/F8Oze2/IWkBaA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + +string.prototype.trimend@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +string.prototype.trimstart@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + +strip-ansi@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= + +strip-json-comments@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.0.tgz#7638d31422129ecf4457440009fba03f9f9ac180" + integrity sha512-e6/d0eBu7gHtdCqFt0xJr642LdToM5/cN4Qb9DbHjVx1CP5RyeM+zH7pbecEmDv/lBqb0QH+6Uqq75rxFPkM0w== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1" + integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g== + dependencies: + has-flag "^4.0.0" + +table@^5.2.3: + version "5.4.6" + resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" + integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== + dependencies: + ajv "^6.10.2" + lodash "^4.17.14" + slice-ansi "^2.1.0" + string-width "^3.0.0" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tslib@^1.9.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043" + integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-fest@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" + integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +uri-js@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== + dependencies: + punycode "^2.1.0" + +util-promisifyall@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/util-promisifyall/-/util-promisifyall-1.0.6.tgz#03439a5a76461e1f71f76954d945cffc8bd6aad8" + integrity sha512-l+o62sbaqStC1xt7oEhlafC4jWBgkOjBXvlPwxkvOYmNqpY8dNXuKdOa+VHjkYz2Fw98e0HvJtNKUg0+6hfP2w== + +v8-compile-cache@^2.0.3: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" + integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +word-wrap@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +write@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3" + integrity sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig== + dependencies: + mkdirp "^0.5.1"