diff --git a/.appveyor.yml b/.appveyor.yml index 7cd3b0bf202..f87758ceb3b 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -10,6 +10,7 @@ install: # Get the version of Node.js - ps: Install-Product node $Env:NODEJS_VERSION # install modules + - rm -rf node_modules - npm install - node --version - npm --version diff --git a/.gitignore b/.gitignore index 4d98e961769..ce3de79186c 100755 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ node_modules # IDE stuff **/.idea +**/.vs # OS stuff .DS_Store @@ -52,3 +53,8 @@ jest # VIM *.swp + +# DotNet +obj/ +[Bb]in/ +[Oo]bj/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 98352edea00..7a91e61b504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,47 @@ +# 1.28.0 (04.07.2018) +- [Add SQS event integration](https://github.com/serverless/serverless/pull/5074) +- [Integration with the Serverless Dashboard](https://github.com/serverless/serverless/pull/5043) +- [Add APIG resource policy](https://github.com/serverless/serverless/pull/5071) +- [Add PRIVATE endpoint type](https://github.com/serverless/serverless/pull/5080) +- [Added ability to create custom stack names and API names](https://github.com/serverless/serverless/pull/4951) +- [Add print options to allow digging, transforming and formatting](https://github.com/serverless/serverless/pull/5036) +- [only use json-cycles when opt-in, for state serialization](https://github.com/serverless/serverless/pull/5029) +- [Make function tags inherit provider tags](https://github.com/serverless/serverless/pull/5007) +- [Make local plugins folder configurable](https://github.com/serverless/serverless/pull/4892) +- [More flexible version constraint for AWS Lambda Go library](https://github.com/serverless/serverless/pull/5045) +- [Update aws-java-maven template to use Log4J2 as recommended by AWS](https://github.com/serverless/serverless/pull/5032) +- [Fix binary support for pre-flight requests (OPTIONS method)](https://github.com/serverless/serverless/pull/4895) + +## Meta +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.27.0...v1.28.0) + +# 1.27.0 (02.05.2018) +- [Add maxAge option for CORS](https://github.com/serverless/serverless/pull/4639) +- [Add fn integration](https://github.com/serverless/serverless/pull/4934) +- [iamManagedPolicies merging with Vpc config](https://github.com/serverless/serverless/pull/4879) +- [Support arrays in function definition too](https://github.com/serverless/serverless/pull/4847) +- [Add iam managed policies](https://github.com/serverless/serverless/pull/4793) +- [Pass authorizer custom context to target lambda](https://github.com/serverless/serverless/pull/4773) +- [Allow UsagePlan's to be created without ApiKeys defined](https://github.com/serverless/serverless/pull/4768) +- [Added name property to cloudwatchEvent CF template](https://github.com/serverless/serverless/pull/4763) +- [Java maven templates for OpenWhisk](https://github.com/serverless/serverless/pull/4758) +- [Pass serverless variable when calling function in referenced file](https://github.com/serverless/serverless/pull/4743) +- [Eliminate/Report Hung Promises, Prepopulate Stage and Region, Handle Quoted Strings](https://github.com/serverless/serverless/pull/4713) +- [Restricting alexaSkill functions to specific Alexa skills](https://github.com/serverless/serverless/pull/4701) +- [Add support for concurrency option in AWS Lambda](https://github.com/serverless/serverless/pull/4694) +- [Fix concurrency upload](https://github.com/serverless/serverless/pull/4677) +- [Support AWS GovCloud and China region deployments](https://github.com/serverless/serverless/pull/4665) + +## Meta +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.26.1...v1.27.0) + + +# 1.26.1 (27.02.2018) +- [Fix lambda integration regression](https://github.com/serverless/serverless/pull/4775) + +## Meta +- [Comparison since last release](https://github.com/serverless/serverless/compare/v1.26.0...v1.26.1) + # 1.26.0 (29.01.2018) - [AWS Go support](https://github.com/serverless/serverless/pull/4669) - [Support for using an existing ApiGateway and Resources](https://github.com/serverless/serverless/pull/4247) diff --git a/LICENSE.txt b/LICENSE.txt index 625b94f65af..77f46e06518 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,12 +1,5 @@ -The MIT License (MIT) - Copyright (c) 2018 Serverless, Inc. http://www.serverless.com -The following license applies to all parts of this software except as -documented below: - -==== - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights @@ -24,10 +17,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -==== - -All files located in the node_modules and external directories are -externally maintained libraries used by this software which have their -own licenses; we recommend you read them, as their terms may differ from -the terms above. diff --git a/README.md b/README.md index 044fb4b6c81..5f190c31b9f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Serverless Application Framework AWS Lambda API Gateway](./assets/framework_repo.png)](http://serverless.com) +[![Serverless Application Framework AWS Lambda API Gateway](https://s3.amazonaws.com/assets.github.serverless/readme-serverless-framework.jpg)](http://serverless.com) [![serverless](http://public.serverless.com/badges/v3.svg)](http://www.serverless.com) [![Build Status](https://travis-ci.org/serverless/serverless.svg?branch=master)](https://travis-ci.org/serverless/serverless) @@ -8,11 +8,11 @@ [![dependencies](https://img.shields.io/david/serverless/serverless.svg)](https://www.npmjs.com/package/serverless) [![license](https://img.shields.io/npm/l/serverless.svg)](https://www.npmjs.com/package/serverless) -[Website](http://www.serverless.com) • [Docs](https://serverless.com/framework/docs/) • [Newsletter](http://eepurl.com/b8dv4P) • [Gitter](https://gitter.im/serverless/serverless) • [Forum](http://forum.serverless.com) • [Meetups](https://github.com/serverless-meetups/main) • [Twitter](https://twitter.com/goserverless) • [We're Hiring](https://serverless.com/company/jobs/) +[Website](http://www.serverless.com) • [Docs](https://serverless.com/framework/docs/) • [Newsletter](http://eepurl.com/b8dv4P) • [Gitter](https://gitter.im/serverless/serverless) • [Forum](http://forum.serverless.com) • [Meetups](https://github.com/serverless/meetups) • [Twitter](https://twitter.com/goserverless) • [We're Hiring](https://serverless.com/company/jobs/) **The Serverless Framework** – Build applications comprised of microservices that run in response to events, auto-scale for you, and only charge you when they run. This lowers the total cost of maintaining your apps, enabling you to build more logic, faster. -The Framework uses new event-driven compute services, like AWS Lambda, Google CloudFunctions, and more. It's a command-line tool, providing scaffolding, workflow automation and best practices for developing and deploying your serverless architecture. It's also completely extensible via plugins. +The Framework uses new event-driven compute services, like AWS Lambda, Google Cloud Functions, and more. It's a command-line tool, providing scaffolding, workflow automation and best practices for developing and deploying your serverless architecture. It's also completely extensible via plugins. Serverless is an MIT open-source project, actively maintained by a full-time, venture-backed team. @@ -33,6 +33,7 @@ Serverless is an MIT open-source project, actively maintained by a full-time, ve * [Contributing](#contributing) * [Community](#community) * [Consultants](#consultants) +* [Licensing](#licensing) * [Previous Version 0.5.x](#v.5) ## Quick Start @@ -154,21 +155,35 @@ Use these plugins to extend or overwrite the Framework's functionality. This table is generated from https://github.com/serverless/plugins/blob/master/plugins.json please make additions there --> | Plugin | Author | -|:-------|:------:| +| :----- | :----: | +| **[Go Serverless](https://github.com/thepauleh/goserverless)**
GoFormation for Serverless. Create serverless configs with Go Structs. | [thepauleh](http://github.com/thepauleh) | | **[Raml Serverless](https://github.com/andrewcurioso/raml-serverless)**
Serverless plugin to work with RAML API spec documents | [andrewcurioso](http://github.com/andrewcurioso) | | **[Serverless Alexa Plugin](https://github.com/rajington/serverless-alexa-plugin)**
Serverless plugin to support Alexa Lambda events | [rajington](http://github.com/rajington) | +| **[Serverless Alexa Skills](https://github.com/marcy-terui/serverless-alexa-skills)**
Manage your Alexa Skills with Serverless Framework. | [marcy-terui](http://github.com/marcy-terui) | +| **[Serverless Aliyun Function Compute](https://github.com/aliyun/serverless-aliyun-function-compute)**
Serverless Alibaba Cloud Function Compute Plugin | [aliyun](http://github.com/aliyun) | +| **[Serverless Api Cloudfront](https://github.com/Droplr/serverless-api-cloudfront)**
Plugin that adds CloudFront distribution in front of your API Gateway for custom domain, CDN caching and access log. | [Droplr](http://github.com/Droplr) | | **[Serverless Api Stage](https://github.com/leftclickben/serverless-api-stage)**
Serverless API Stage plugin, enables stage variables and logging for AWS API Gateway. | [leftclickben](http://github.com/leftclickben) | -| **[Serverless Apig S3](https://github.com/sdd/serverless-apig-s3)**
Serve static front-end content from S3 via the API Gatewy and deploy client bundle to S3. | [sdd](http://github.com/sdd) | +| **[Serverless Apib Validator](https://github.com/onlicar/serverless-apib-validator)**
Validate that an API Blueprint has full coverage over a Serverless config | [onlicar](http://github.com/onlicar) | +| **[Serverless Apig S 3](https://github.com/sdd/serverless-apig-s3)**
Serve static front-end content from S3 via the API Gateway and deploy client bundle to S3. | [sdd](http://github.com/sdd) | | **[Serverless Apigateway Plugin](https://github.com/GFG/serverless-apigateway-plugin)**
Configure the AWS api gateway: Binary support, Headers and Body template mappings | [GFG](http://github.com/GFG) | | **[Serverless Apigw Binary](https://github.com/maciejtreder/serverless-apigw-binary)**
Plugin to enable binary support in AWS API Gateway. | [maciejtreder](http://github.com/maciejtreder) | | **[Serverless Apigwy Binary](https://github.com/ryanmurakami/serverless-apigwy-binary)**
Serverless plugin for configuring API Gateway to return binary responses | [ryanmurakami](http://github.com/ryanmurakami) | +| **[Serverless Appsync Plugin](https://github.com/sid88in/serverless-appsync-plugin)**
Serverless Plugin to deploy AppSync GraphQL API | [sid88in](http://github.com/sid88in) | +| **[Serverless Attach Managed Policy](https://github.com/Nordstrom/serverless-attach-managed-policy)**
A Serverless plugin to automatically attach an AWS Managed IAM Policy (or Policies) to all IAM Roles created by the Service. | [Nordstrom](http://github.com/Nordstrom) | | **[Serverless Aws Alias](https://github.com/HyperBrain/serverless-aws-alias)**
This plugin enables use of AWS aliases on Lambda functions. | [HyperBrain](http://github.com/HyperBrain) | | **[Serverless Aws Documentation](https://github.com/9cookies/serverless-aws-documentation)**
Serverless plugin to add documentation and models to the serverless generated API Gateway | [9cookies](http://github.com/9cookies) | +| **[Serverless Aws Nested Stacks](https://github.com/concon121/serverless-plugin-nested-stacks)**
Yet another AWS nested stack plugin! | [concon121](http://github.com/concon121) | +| **[Serverless Aws Resource Names](https://github.com/concon121/serverless-plugin-aws-resource-names)**
Serverless plugin to alter the default naming conventions for sls resources via a simple mapping file. | [concon121](http://github.com/concon121) | | **[Serverless Build Plugin](https://github.com/nfour/serverless-build-plugin)**
A Node.js focused build plugin for serverless. | [nfour](http://github.com/nfour) | | **[Serverless Cf Vars](https://gitlab.com/kabo/serverless-cf-vars)**
Enables use of AWS pseudo functions and Fn::Sub string substitution | [kabo](http://github.com/kabo) | | **[Serverless Cljs Plugin](https://github.com/nervous-systems/serverless-cljs-plugin)**
Enables Clojurescript as an implementation language for Lambda handlers | [nervous-systems](http://github.com/nervous-systems) | +| **[Serverless Cloudformation Changesets](https://github.com/trek10inc/serverless-cloudformation-changesets)**
Natively deploy to CloudFormation via Change sets, instead of directly. Allowing you to queue changes, and safely require escalated roles for final deployment. | [trek10inc](http://github.com/trek10inc) | +| **[Serverless Cloudformation Parameter Setter](https://github.com/trek10inc/serverless-cloudformation-parameter-setter)**
Set CloudFormation parameters when deploying. | [trek10inc](http://github.com/trek10inc) | +| **[Serverless Cloudformation Resource Counter](https://github.com/drexler/serverless-cloudformation-resource-counter)**
A plugin to count the resources generated in the AWS CloudFormation stack after deployment. | [drexler](http://github.com/drexler) | +| **[Serverless Cloudformation Sub Variables](https://github.com/santiagocardenas/serverless-cloudformation-sub-variables)**
Serverless framework plugin for easily supporting AWS CloudFormation Sub function variables | [santiagocardenas](http://github.com/santiagocardenas) | | **[Serverless Coffeescript](https://github.com/duanefields/serverless-coffeescript)**
A Serverless plugin to compile your CoffeeScript into JavaScript at deployment | [duanefields](http://github.com/duanefields) | | **[Serverless Command Line Event Args](https://github.com/horike37/serverless-command-line-event-args)**
This module is Serverless Framework plugin. Event JSON passes to your Lambda function in commandline. | [horike37](http://github.com/horike37) | +| **[Serverless Content Encoding](https://github.com/xeno-dohai/serverless-content-encoding)**
Enable Content Encoding in AWS API Gateway during deployment | [xeno-dohai](http://github.com/xeno-dohai) | | **[Serverless Crypt](https://github.com/marcy-terui/serverless-crypt)**
Securing the secrets on Serverless Framework by AWS KMS encryption. | [marcy-terui](http://github.com/marcy-terui) | | **[Serverless Custom Packaging Plugin](https://github.com/hypoport/serverless-custom-packaging-plugin)**
Plugin to package your sourcecode using a custom target path inside the zip. | [hypoport](http://github.com/hypoport) | | **[Serverless Dir Config Plugin](https://github.com/economysizegeek/serverless-dir-config-plugin)**
EXPERIMENTAL - Serverless plugin to load function and resource definitions from a directory. | [economysizegeek](http://github.com/economysizegeek) | @@ -181,55 +196,83 @@ This table is generated from https://github.com/serverless/plugins/blob/master/p | **[Serverless Dynamodb Ttl](https://github.com/Jimdo/serverless-dynamodb-ttl)**
Configure DynamoDB TTL in serverless.yml (until CloudFormation supports this). | [Jimdo](http://github.com/Jimdo) | | **[Serverless Enable Api Logs](https://github.com/paulSambolin/serverless-enable-api-logs)**
Enables Coudwatch logging for API Gateway events | [paulSambolin](http://github.com/paulSambolin) | | **[Serverless Env Generator](https://github.com/DieProduktMacher/serverless-env-generator)**
Manage environment variables with YAML and load them with dotenv. Supports variable encryption with KMS, multiple stages and custom profiles. | [DieProduktMacher](http://github.com/DieProduktMacher) | +| **[Serverless Ephemeral](https://github.com/Accenture/serverless-ephemeral)**
Build and include custom stateless libraries for any language | [Accenture](http://github.com/Accenture) | | **[Serverless Event Constant Inputs](https://github.com/dittto/serverless-event-constant-inputs)**
Allows you to add constant inputs to events in Serverless 1.0. For more info see [constant values in Cloudwatch](https://aws.amazon.com/blogs/compute/simply-serverless-use-constant-values-in-cloudwatch-event-triggered-lambda-functions/) | [dittto](http://github.com/dittto) | | **[Serverless Export Env](https://github.com/arabold/serverless-export-env)**
Export environment variables into a .env file with automatic AWS CloudFormation reference resolution. | [arabold](http://github.com/arabold) | +| **[Serverless Express](https://github.com/mikestaub/serverless-express)**
Making express app development compatible with serverless framework. | [mikestaub](http://github.com/mikestaub) | +| **[Serverless Functions Base Path](https://github.com/kevinrambaud/serverless-functions-base-path)**
Easily define a base path where your serverless functions are located. | [kevinrambaud](http://github.com/kevinrambaud) | +| **[Serverless Go Build](https://github.com/sean9keenan/serverless-go-build)**
Build go source files (or public functions) using yml definition file | [sean9keenan](http://github.com/sean9keenan) | | **[Serverless Gulp](https://github.com/rhythminme/serverless-gulp)**
A thin task wrapper around @goserverless that allows you to automate build, test and deploy tasks using gulp | [rhythminme](http://github.com/rhythminme) | +| **[Serverless Haskell](https://github.com/seek-oss/serverless-haskell)**
Deploying Haskell applications to AWS Lambda with Serverless | [seek-oss](http://github.com/seek-oss) | | **[Serverless Hooks Plugin](https://github.com/uswitch/serverless-hooks-plugin)**
Run arbitrary commands on any lifecycle event in serverless | [uswitch](http://github.com/uswitch) | +| **[Serverless Iam Roles Per Function](https://github.com/functionalone/serverless-iam-roles-per-function)**
Serverless Plugin for easily defining IAM roles per function via the use of iamRoleStatements at the function level. | [functionalone](http://github.com/functionalone) | +| **[Serverless Iot Local](https://github.com/tradle/serverless-iot-local)**
AWS Iot events with serverless-offline | [tradle](http://github.com/tradle) | | **[Serverless Jest Plugin](https://github.com/SC5/serverless-jest-plugin)**
A Serverless Plugin for the Serverless Framework which adds support for test-driven development using Jest | [SC5](http://github.com/SC5) | | **[Serverless Kms Secrets](https://github.com/SC5/serverless-kms-secrets)**
Allows to easily encrypt and decrypt secrets using KMS from the serverless CLI | [SC5](http://github.com/SC5) | | **[Serverless Kubeless](https://github.com/serverless/serverless-kubeless)**
Serverless plugin for deploying functions to Kubeless. | [serverless](http://github.com/serverless) | | **[Serverless Local Dev Server](https://github.com/DieProduktMacher/serverless-local-dev-server)**
Speeds up development of Alexa Skills, Chatbots and APIs by exposing your functions as local HTTP endpoints and mapping received events. | [DieProduktMacher](http://github.com/DieProduktMacher) | +| **[Serverless Local Environment](https://github.com/piercus/serverless-local-environment)**
Serverless plugin to set local environment variables and remote environment variable to different values | [piercus](http://github.com/piercus) | +| **[Serverless Local Schedule](https://github.com/UnitedIncome/serverless-local-schedule)**
Schedule AWS CloudWatch Event based invocations in local time(with DST support!) | [UnitedIncome](http://github.com/UnitedIncome) | | **[Serverless Log Forwarding](https://github.com/amplify-education/serverless-log-forwarding)**
Serverless plugin for forwarding CloudWatch logs to another Lambda function. | [amplify-education](http://github.com/amplify-education) | +| **[Serverless Micro](https://github.com/barstoolsports/serverless-micro)**
Plugin to help manage multiple micro services under one main service. | [barstoolsports](http://github.com/barstoolsports) | | **[Serverless Mocha Plugin](https://github.com/SC5/serverless-mocha-plugin)**
A Serverless Plugin for the Serverless Framework which adds support for test-driven development using Mocha | [SC5](http://github.com/SC5) | +| **[Serverless Multi Dotnet](https://github.com/tsibelman/serverless-multi-dotnet)**
A serverless plugin to pack C# lambdas functions that are spread to multiple CS projects. | [tsibelman](http://github.com/tsibelman) | | **[Serverless Nested Stack](https://github.com/jagdish-176/serverless-nested-stack)**
A plugin to Workaround for Cloudformation 200 resource limit | [jagdish-176](http://github.com/jagdish-176) | | **[Serverless Offline](https://github.com/dherault/serverless-offline)**
Emulate AWS λ and API Gateway locally when developing your Serverless project | [dherault](http://github.com/dherault) | +| **[Serverless Offline Direct Lambda](https://github.com/civicteam/serverless-offline-direct-lambda)**
Allow offline direct lambda-to-lambda interactions by exposing lambdas with no API Gateway event via HTTP. | [civicteam](http://github.com/civicteam) | | **[Serverless Offline Scheduler](https://github.com/ajmath/serverless-offline-scheduler)**
Runs scheduled functions offline while integrating with serverless-offline | [ajmath](http://github.com/ajmath) | +| **[Serverless Offline Sns](https://github.com/mj1618/serverless-offline-sns)**
Serverless plugin to run a local SNS server and call serverless SNS handlers with events notifications. | [mj1618](http://github.com/mj1618) | +| **[Serverless Offline Ssm](https://github.com/janders223/serverless-offline-ssm)**
Read SSM parameters from a .env file instead of AWS | [janders223](http://github.com/janders223) | +| **[Serverless Package Common](https://github.com/onlicar/serverless-package-common)**
Deploy microservice Python Serverless services with common code | [onlicar](http://github.com/onlicar) | | **[Serverless Package Python Functions](https://github.com/ubaniabalogun/serverless-package-python-functions)**
Packaging Python Lambda functions with only the dependencies/requirements they need. | [ubaniabalogun](http://github.com/ubaniabalogun) | | **[Serverless Parameters](https://github.com/svdgraaf/serverless-parameters)**
Add parameters to the generated cloudformation templates | [svdgraaf](http://github.com/svdgraaf) | +| **[Serverless Plugin Api Docs](https://github.com/8select/serverless-plugin-api-docs)**
Serverless plugin to automatically create a lambda function which returns Swagger-UI HTML API Documentation Page based on the given swagger spec JSON file. | [8select](http://github.com/8select) | | **[Serverless Plugin Aws Alerts](https://github.com/ACloudGuru/serverless-plugin-aws-alerts)**
A Serverless plugin to easily add CloudWatch alarms to functions | [ACloudGuru](http://github.com/ACloudGuru) | | **[Serverless Plugin Aws Resolvers](https://github.com/DopplerLabs/serverless-plugin-aws-resolvers)**
Resolves variables from ESS, RDS, or Kinesis for serverless services | [DopplerLabs](http://github.com/DopplerLabs) | | **[Serverless Plugin Bespoken](https://github.com/bespoken/serverless-plugin-bespoken)**
Creates a local server and a proxy so you don't have to deploy anytime you want to test your code | [bespoken](http://github.com/bespoken) | | **[Serverless Plugin Bind Deployment Id](https://github.com/jacob-meacham/serverless-plugin-bind-deployment-id)**
A Serverless plugin to bind the randomly generated deployment resource to your custom resources | [jacob-meacham](http://github.com/jacob-meacham) | | **[Serverless Plugin Browserifier](https://github.com/digitalmaas/serverless-plugin-browserifier)**
Reduce the size and speed up your Node.js based lambda's using browserify. | [digitalmaas](http://github.com/digitalmaas) | | **[Serverless Plugin Browserify](https://github.com/doapp-ryanp/serverless-plugin-browserify)**
Speed up your node based lambda's | [doapp-ryanp](http://github.com/doapp-ryanp) | +| **[Serverless Plugin Canary Deployments](https://github.com/davidgf/serverless-plugin-canary-deployments)**
A Serverless plugin to implement canary deployments of Lambda functions | [davidgf](http://github.com/davidgf) | | **[Serverless Plugin Cfauthorizer](https://github.com/SC5/serverless-plugin-cfauthorizer)**
This plugin allows you to define your own API Gateway Authorizers as the Serverless CloudFormation resources and apply them to HTTP endpoints. | [SC5](http://github.com/SC5) | +| **[Serverless Plugin Chrome](https://github.com/adieuadieu/serverless-chrome/tree/master/packages/serverless-plugin)**
Plugin which bundles and ensures that Headless Chrome/Chromium is running when your AWS Lambda function handler is invoked. | [adieuadieu](http://github.com/adieuadieu) | | **[Serverless Plugin Cloudwatch Sumologic](https://github.com/ACloudGuru/serverless-plugin-cloudwatch-sumologic)**
Plugin which auto-subscribes a log delivery lambda function to lambda log groups created by serverless | [ACloudGuru](http://github.com/ACloudGuru) | | **[Serverless Plugin Common Excludes](https://github.com/dougmoscrop/serverless-plugin-common-excludes)**
Adds commonly excluded files to package.excludes | [dougmoscrop](http://github.com/dougmoscrop) | | **[Serverless Plugin Custom Domain](https://github.com/dougmoscrop/serverless-plugin-custom-domain)**
Reliably sets a BasePathMapping to an API Gateway Custom Domain | [dougmoscrop](http://github.com/dougmoscrop) | | **[Serverless Plugin Deploy Environment](https://github.com/DopplerLabs/serverless-plugin-deploy-environment)**
Plugin to manage deployment environment across stages | [DopplerLabs](http://github.com/DopplerLabs) | | **[Serverless Plugin Diff](https://github.com/nicka/serverless-plugin-diff)**
Compares your local AWS CloudFormation templates against deployed ones. | [nicka](http://github.com/nicka) | +| **[Serverless Plugin Dynamodb Autoscaling](https://github.com/medikoo/serverless-plugin-dynamodb-autoscaling)**
Auto generate auto scaling configuration for configured DynamoDB tables | [medikoo](http://github.com/medikoo) | | **[Serverless Plugin Elastic Beanstalk](https://github.com/rawphp/serverless-plugin-elastic-beanstalk)**
A serverless plugin to deploy applications to AWS ElasticBeanstalk. | [rawphp](http://github.com/rawphp) | | **[Serverless Plugin Encode Env Var Objects](https://github.com/yonomi/serverless-plugin-encode-env-var-objects)**
Serverless plugin to encode any environment variable objects. | [yonomi](http://github.com/yonomi) | | **[Serverless Plugin External Sns Events](https://github.com/silvermine/serverless-plugin-external-sns-events)**
Add ability for functions to use existing or external SNS topics as an event source | [silvermine](http://github.com/silvermine) | | **[Serverless Plugin Git Variables](https://github.com/jacob-meacham/serverless-plugin-git-variables)**
A Serverless plugin to expose git variables (branch name, HEAD description, full commit hash) to your serverless services | [jacob-meacham](http://github.com/jacob-meacham) | | **[Serverless Plugin Graphiql](https://github.com/bencooling/serverless-plugin-graphiql)**
A Serverless plugin to run a local http server for graphiql and your graphql handler | [bencooling](http://github.com/bencooling) | | **[Serverless Plugin Include Dependencies](https://github.com/dougmoscrop/serverless-plugin-include-dependencies)**
This is a Serverless plugin that should make your deployed functions smaller. | [dougmoscrop](http://github.com/dougmoscrop) | +| **[Serverless Plugin Inject Dependencies](https://github.com/loanmarket/serverless-plugin-inject-dependencies)**
Painlessly deploy serverless projects with only the required dependencies. | [loanmarket](http://github.com/loanmarket) | | **[Serverless Plugin Iopipe](https://github.com/iopipe/serverless-plugin-iopipe)**
See inside your Lambda functions with high fidelity metrics and monitoring. | [iopipe](http://github.com/iopipe) | | **[Serverless Plugin Lambda Dead Letter](https://github.com/gmetzker/serverless-plugin-lambda-dead-letter)**
A Serverless plugin that can configure a lambda with a dead letter queue or topic | [gmetzker](http://github.com/gmetzker) | | **[Serverless Plugin Log Subscription](https://github.com/dougmoscrop/serverless-plugin-log-subscription)**
Adds a CloudWatch LogSubscription for functions | [dougmoscrop](http://github.com/dougmoscrop) | +| **[Serverless Plugin Metric](https://github.com/alex20465/serverless-plugin-metric)**
Creates dynamically AWS metric-filter resources with custom patterns | [alex20465](http://github.com/alex20465) | | **[Serverless Plugin Multiple Responses](https://github.com/silvermine/serverless-plugin-multiple-responses)**
Enable multiple content-types for Response template | [silvermine](http://github.com/silvermine) | +| **[Serverless Plugin Node Shim](https://github.com/jzimmek/serverless-plugin-node-shim)**
Serverless plugin to run your functions in nodejs version (8 LTS, 9+) on aws lambda | [jzimmek](http://github.com/jzimmek) | | **[Serverless Plugin Offline Kinesis Events](https://github.com/DopplerLabs/serverless-plugin-offline-kinesis-events)**
Plugin that works with serverless-offline to allow offline testing of serverless functions that are triggered by Kinesis events. | [DopplerLabs](http://github.com/DopplerLabs) | | **[Serverless Plugin Optimize](https://github.com/FidelLimited/serverless-plugin-optimize)**
Bundle with Browserify, transpile with Babel to ES5 and minify with Uglify your Serverless functions. | [FidelLimited](http://github.com/FidelLimited) | | **[Serverless Plugin Package Dotenv File](https://github.com/ACloudGuru/serverless-plugin-package-dotenv-file)**
A Serverless plugin to copy a .env file into the serverless package | [ACloudGuru](http://github.com/ACloudGuru) | +| **[Serverless Plugin Provider Groups](https://github.com/loanmarket/serverless-plugin-provider-groups)**
A plugin to allow management of grouped settings within large serverless projects. | [loanmarket](http://github.com/loanmarket) | +| **[Serverless Plugin Reducer](https://github.com/medikoo/serverless-plugin-reducer)**
Reduce Node.js lambda package so it contains only lambda dependencies | [medikoo](http://github.com/medikoo) | | **[Serverless Plugin Scripts](https://github.com/mvila/serverless-plugin-scripts)**
Add scripting capabilities to the Serverless Framework | [mvila](http://github.com/mvila) | | **[Serverless Plugin Select](https://github.com/FidelLimited/serverless-plugin-select)**
Select which functions are to be deployed based on region and stage. | [FidelLimited](http://github.com/FidelLimited) | | **[Serverless Plugin Simulate](https://github.com/gertjvr/serverless-plugin-simulate)**
Simulate AWS Lambda and API Gateway locally using Docker | [gertjvr](http://github.com/gertjvr) | | **[Serverless Plugin Split Stacks](https://github.com/dougmoscrop/serverless-plugin-split-stacks)**
Migrate certain resources to nested stacks | [dougmoscrop](http://github.com/dougmoscrop) | | **[Serverless Plugin Stack Config](https://github.com/rawphp/serverless-plugin-stack-config)**
A serverless plugin to manage configurations for a stack across micro-services. | [rawphp](http://github.com/rawphp) | | **[Serverless Plugin Stack Outputs](https://github.com/svdgraaf/serverless-plugin-stack-outputs)**
Displays stack outputs for your serverless stacks when `sls info` is ran | [svdgraaf](http://github.com/svdgraaf) | +| **[Serverless Plugin Stackstorm](https://github.com/StackStorm/serverless-plugin-stackstorm)**
Reusable Functions from StackStorm Exchange | [StackStorm](http://github.com/StackStorm) | | **[Serverless Plugin Stage Variables](https://github.com/svdgraaf/serverless-plugin-stage-variables)**
Add stage variables for Serverless 1.x to ApiGateway, so you can use variables in your Lambda's | [svdgraaf](http://github.com/svdgraaf) | | **[Serverless Plugin Subscription Filter](https://github.com/tsub/serverless-plugin-subscription-filter)**
A serverless plugin to register AWS CloudWatchLogs subscription filter | [tsub](http://github.com/tsub) | +| **[Serverless Plugin Tracer](https://github.com/enykeev/serverless-plugin-tracer/)**
Trace serverless hooks as they execute | [enykeev](http://github.com/enykeev) | +| **[Serverless Plugin Transpiler](https://github.com/medikoo/serverless-plugin-transpiler)**
Transpile lambda files during packaging step | [medikoo](http://github.com/medikoo) | | **[Serverless Plugin Typescript](https://github.com/graphcool/serverless-plugin-typescript)**
Serverless plugin for zero-config Typescript support. | [graphcool](http://github.com/graphcool) | +| **[Serverless Plugin Vpc Eni Cleanup](https://github.com/medikoo/serverless-plugin-vpc-eni-cleanup)**
Automatic cleanup of VPC network interfaces on stage removal | [medikoo](http://github.com/medikoo) | | **[Serverless Plugin Warmup](https://github.com/FidelLimited/serverless-plugin-warmup)**
Keep your lambdas warm during Winter. | [FidelLimited](http://github.com/FidelLimited) | | **[Serverless Plugin Webpack](https://github.com/goldwasserexchange/serverless-plugin-webpack)**
A serverless plugin to automatically bundle your functions individually with webpack | [goldwasserexchange](http://github.com/goldwasserexchange) | | **[Serverless Plugin Write Env Vars](https://github.com/silvermine/serverless-plugin-write-env-vars)**
Write environment variables out to a file that is compatible with dotenv | [silvermine](http://github.com/silvermine) | @@ -237,20 +280,31 @@ This table is generated from https://github.com/serverless/plugins/blob/master/p | **[Serverless Pseudo Parameters](https://github.com/svdgraaf/serverless-pseudo-parameters)**
Use ${AWS::AccountId} and other cloudformation pseudo parameters in your serverless.yml values | [svdgraaf](http://github.com/svdgraaf) | | **[Serverless Python Individually](https://github.com/cfchou/serverless-python-individually)**
A serverless framework plugin to install multiple lambda functions written in python | [cfchou](http://github.com/cfchou) | | **[Serverless Python Requirements](https://github.com/UnitedIncome/serverless-python-requirements)**
Serverless plugin to bundle Python packages | [UnitedIncome](http://github.com/UnitedIncome) | +| **[Serverless Reqvalidator Plugin](https://github.com/RafPe/serverless-reqvalidator-plugin)**
Serverless plugin to add request validator to API Gateway methods | [RafPe](http://github.com/RafPe) | | **[Serverless Resources Env](https://github.com/rurri/serverless-resources-env)**
After Deploy, this plugin fetches cloudformation resource identifiers and sets them on AWS lambdas, and creates local .-env file | [rurri](http://github.com/rurri) | | **[Serverless Run Function Plugin](https://github.com/lithin/serverless-run-function-plugin)**
Run serverless function locally | [lithin](http://github.com/lithin) | -| **[Serverless S3 Remover](https://github.com/sinofseven/serverless-s3-remover)**
A serverless plugin to make s3 buckets empty before deleting cloudformation stack when ```sls remove``` | [sinofseven](http://github.com/sinofseven) | -| **[Serverless S3 Sync](https://github.com/k1LoW/serverless-s3-sync)**
A plugin to sync local directories and S3 prefixes for Serverless Framework, | [k1LoW](http://github.com/k1LoW) | -| **[Serverless S3bucket Sync](https://github.com/sbstjn/serverless-s3bucket-sync)**
Sync a local folder with a S3 bucket after sls deploy | [sbstjn](http://github.com/sbstjn) | +| **[Serverless S 3 Encryption](https://github.com/tradle/serverless-s3-encryption)**
Set or remove the encryption settings on your s3 buckets | [tradle](http://github.com/tradle) | +| **[Serverless S 3 Remover](https://github.com/sinofseven/serverless-s3-remover)**
A serverless plugin to make s3 buckets empty before deleting cloudformation stack when ```sls remove``` | [sinofseven](http://github.com/sinofseven) | +| **[Serverless S 3 Sync](https://github.com/k1LoW/serverless-s3-sync)**
A plugin to sync local directories and S3 prefixes for Serverless Framework, | [k1LoW](http://github.com/k1LoW) | +| **[Serverless S 3 Bucket Sync](https://github.com/sbstjn/serverless-s3bucket-sync)**
Sync a local folder with a S3 bucket after sls deploy | [sbstjn](http://github.com/sbstjn) | | **[Serverless Sam](https://github.com/SAPessi/serverless-sam)**
Exports an AWS SAM template for a service created with the Serverless Framework. | [SAPessi](http://github.com/SAPessi) | | **[Serverless Scriptable Plugin](https://github.com/weixu365/serverless-scriptable-plugin)**
Customize Serverless behavior without writing a plugin. | [weixu365](http://github.com/weixu365) | | **[Serverless Sentry](https://github.com/arabold/serverless-sentry-plugin)**
Automatic monitoring of memory usage, execution timeouts and forwarding of Lambda errors to Sentry (https://sentry.io). | [arabold](http://github.com/arabold) | | **[Serverless Shell](https://github.com/UnitedIncome/serverless-shell)**
Drop to a runtime shell with all the environment variables set that you'd have in lambda. | [UnitedIncome](http://github.com/UnitedIncome) | +| **[Serverless Sns Filter](https://github.com/MechanicalRock/serverless-sns-filter)**
Serverless plugin to add SNS Subscription Filters to events | [MechanicalRock](http://github.com/MechanicalRock) | +| **[Serverless Spa](https://github.com/gilmarsquinelato/serverless-spa)**
Serverless plugin to deploy your website to AWS S3 using Webpack to bundle it. | [gilmarsquinelato](http://github.com/gilmarsquinelato) | | **[Serverless Sqs Alarms Plugin](https://github.com/sbstjn/serverless-sqs-alarms-plugin)**
Wrapper to setup CloudWatch Alarms on SQS queue length | [sbstjn](http://github.com/sbstjn) | | **[Serverless Sqs Fifo](https://github.com/vortarian/serverless-sqs-fifo)**
A serverless plugin to handle creation of sqs fifo queue's in aws (stop-gap) | [vortarian](http://github.com/vortarian) | +| **[Serverless Ssm Fetch](https://github.com/gozup/serverless-ssm-fetch)**
Sets "AWS Systems Manager Parameter Store (SSM)" parameters into functions' environment variables. | [gozup](http://github.com/gozup) | | **[Serverless Stack Output](https://github.com/sbstjn/serverless-stack-output)**
Store output from your AWS CloudFormation Stack in JSON/YAML/TOML files, or to pass it to a JavaScript function for further processing. | [sbstjn](http://github.com/sbstjn) | +| **[Serverless Stage Manager](https://github.com/jeremydaly/serverless-stage-manager)**
Super simple Serverless plugin for validating stage names before deployment | [jeremydaly](http://github.com/jeremydaly) | +| **[Serverless Static](https://github.com/iliasbhal/serverless-static)**
Easily serve files from a folder while developing on localhost with the serverless-offline plugin | [iliasbhal](http://github.com/iliasbhal) | | **[Serverless Step Functions](https://github.com/horike37/serverless-step-functions)**
AWS Step Functions with Serverless Framework. | [horike37](http://github.com/horike37) | +| **[Serverless Sthree Env](https://github.com/StyleTributeIT/serverless-sthree-env)**
Serverless plugin to get config from a json formatted file in S3 and copy them to environment variable | [StyleTributeIT](http://github.com/StyleTributeIT) | | **[Serverless Subscription Filter](https://github.com/blackevil245/serverless-subscription-filter)**
Serverless plugin to register subscription filter for Lambda logs. Register and pipe the logs of one lambda to another to process. | [blackevil245](http://github.com/blackevil245) | +| **[Serverless Tag Api Gateway](https://github.com/gfragoso/serverless-tag-api-gateway)**
Serverless plugin to tag API Gateway | [gfragoso](http://github.com/gfragoso) | +| **[Serverless Tag Cloud Watch Logs](https://github.com/gfragoso/serverless-tag-cloud-watch-logs)**
Serverless plugin to tag CloudWatchLogs | [gfragoso](http://github.com/gfragoso) | +| **[Serverless Tag Sqs](https://github.com/gfragoso/serverless-tag-sqs)**
Serverless plugin to tag SQS - Simple Queue Service | [gfragoso](http://github.com/gfragoso) | | **[Serverless Vpc Discovery](https://github.com/amplify-education/serverless-vpc-discovery)**
Serverless plugin for discovering VPC / Subnet / Security Group configuration by name. | [amplify-education](http://github.com/amplify-education) | | **[Serverless Webpack](https://github.com/serverless-heaven/serverless-webpack)**
Serverless plugin to bundle your lambdas with Webpack | [serverless-heaven](http://github.com/serverless-heaven) | | **[Serverless Wsgi](https://github.com/logandk/serverless-wsgi)**
Serverless plugin to deploy WSGI applications (Flask/Django/Pyramid etc.) and bundle Python packages | [logandk](http://github.com/logandk) | @@ -262,15 +316,18 @@ This table is generated from https://github.com/serverless/plugins/blob/master/p This table is generated from https://github.com/serverless/examples/blob/master/community-examples.json please make additions there --> | Project Name | Author | -|:-------------|:------:| -| **[Jwtauthorizr](https://github.com/serverlessbuch/jwtAuthorizr)**
Custom JWT Authorizer Lambda function for Amazon API Gateway with Bearer JWT | [serverlessbuch](http://github.com/serverlessbuch) | +| :----------- | :----: | +| **[Serverless Architecture Boilerplate](https://github.com/msfidelis/serverless-architecture-boilerplate)**
Boilerplate to organize and deploy big projects using Serverless and CloudFormation on AWS | [msfidelis](http://github.com/msfidelis) | +| **[Babelbot](https://github.com/abiglobalhealth/babelbot)**
Lambda + API Gateway: Zero-to-chatbot in <10 lines of JS. Built-in integrations for Messenger, Telegram, Kik, Line, Twilio, Skype, and Wechat. Or roll your own! | [abiglobalhealth](http://github.com/abiglobalhealth) | +| **[Jwt Authorizr](https://github.com/serverlessbuch/jwtAuthorizr)**
Custom JWT Authorizer Lambda function for Amazon API Gateway with Bearer JWT | [serverlessbuch](http://github.com/serverlessbuch) | +| **[Slack Signup Serverless](https://github.com/dzimine/slack-signup-serverless)**
Serverless signup to Slack and more. Lambda with Python, StepFunctions, and Web front end. Python boilerplate included. | [dzimine](http://github.com/dzimine) | | **[Serverless Graphql Api](https://github.com/boazdejong/serverless-graphql-api)**
Serverless GraphQL API using Lambda and DynamoDB | [boazdejong](http://github.com/boazdejong) | | **[Serverless Screenshot](https://github.com/svdgraaf/serverless-screenshot)**
Serverless Screenshot Service using PhantomJS | [svdgraaf](http://github.com/svdgraaf) | | **[Serverless Postgraphql](https://github.com/rentrop/serverless-postgraphql)**
GraphQL endpoint for PostgreSQL using postgraphql | [rentrop](http://github.com/rentrop) | | **[Serverless Messenger Boilerplate](https://github.com/SC5/serverless-messenger-boilerplate)**
Serverless messenger bot boilerplate | [SC5](http://github.com/SC5) | | **[Serverless Npm Registry](https://github.com/craftship/yith)**
Serverless private npm registry, proxy and cache. | [craftship](http://github.com/craftship) | | **[Serverless Pokego](https://github.com/jch254/pokego-serverless)**
Serverless-powered API to fetch nearby Pokemon Go data | [jch254](http://github.com/jch254) | -| **[Serverless Weekly2pocket App](https://github.com/s0enke/weekly2pocket)**
Serverless-powered API for sending posts to pocket app | [s0enke](http://github.com/s0enke) | +| **[Serverless Weekly 2 Pocket App](https://github.com/s0enke/weekly2pocket)**
Serverless-powered API for sending posts to pocket app | [s0enke](http://github.com/s0enke) | | **[Serverless Facebook Quotebot](https://github.com/pmuens/quotebot)**
100% Serverless Facebook messenger chatbot which will respond with inspiring quotes | [pmuens](http://github.com/pmuens) | | **[Serverless Slack Trevorbot](https://github.com/conveyal/trevorbot)**
Slack bot for info on where in the world is Trevor Gerhardt? | [conveyal](http://github.com/conveyal) | | **[Serverless Garden Aid](https://github.com/garden-aid/web-bff)**
IoT Garden Aid Backend | [garden-aid](http://github.com/garden-aid) | @@ -288,12 +345,12 @@ This table is generated from https://github.com/serverless/examples/blob/master/ | **[Sls Form Mail](https://github.com/takahashim/sls-form-mail)**
Send SNS email from form data | [takahashim](http://github.com/takahashim) | | **[Serverless Python Sample](https://github.com/bennybauer/serverless-python-sample)**
A simple serverless python sample with REST API endpoints and dependencies | [bennybauer](http://github.com/bennybauer) | | **[Serverless Msg Gateway](https://github.com/yonahforst/msg-gateway)**
A messaging aggregator for kik, skype, twilio, telegram, & messenger. Send and receive messages in a standard format. | [yonahforst](http://github.com/yonahforst) | -| **[Serverless Aws Rekognition Finpics](https://github.com/rgfindl/finpics)**
Use AWS Rekognition to provide a faces search of finpics.com | [rgfindl](http://github.com/rgfindl) | +| **[Serverless AWS Rekognition Finpics](https://github.com/rgfindl/finpics)**
Use AWS Rekognition to provide a faces search of finpics.com | [rgfindl](http://github.com/rgfindl) | | **[Serverless Slack Emojibot](https://github.com/markhobson/emojibot)**
Serverless slack bot for emoji | [markhobson](http://github.com/markhobson) | | **[Keboola Developer Portal](https://github.com/keboola/developer-portal)**
Keboola developer portal built with Serverless | [keboola](http://github.com/keboola) | | **[Serverless Cloudwatch Rds Custom Metrics](https://github.com/AndrewFarley/serverless-cloudwatch-rds-custom-metrics)**
A NodeJS-based MySQL RDS Data Collection script to push Custom Metrics to Cloudwatch with Serverless | [AndrewFarley](http://github.com/AndrewFarley) | | **[Jrestless Examples](https://github.com/bbilger/jrestless-examples)**
[JRestless](https://github.com/bbilger/jrestless) (Java / JAX-RS) examples for [API Gateway Functions](https://github.com/bbilger/jrestless-examples/tree/master/aws/gateway) ([plain JAX-RS](https://github.com/bbilger/jrestless-examples/blob/master/aws/gateway/aws-gateway-showcase), [Spring](https://github.com/bbilger/jrestless-examples/blob/master/aws/gateway/aws-gateway-spring), [binary data requests/responses](https://github.com/bbilger/jrestless-examples/blob/master/aws/gateway/aws-gateway-binary), [custom authorizers](https://github.com/bbilger/jrestless-examples/blob/master/aws/gateway/aws-gateway-security-custom-authorizer) and [Cognito User Pool authorizers](https://github.com/bbilger/jrestless-examples/blob/master/aws/gateway/aws-gateway-security-cognito-authorizer)), [SNS Functions](https://github.com/bbilger/jrestless-examples/blob/master/aws/sns/aws-sns-usage-example) (asynchronous communication between functions) and [Service Functions](https://github.com/bbilger/jrestless-examples/blob/master/aws/service/aws-service-usage-example) (synchronous HTTP-like communication between functions - transparent through Feign) | [bbilger](http://github.com/bbilger) | -| **[Sc5 Serverless Boilerplate](https://github.com/SC5/sc5-serverless-boilerplate)**
A boilerplate that contains setup for test-driven development | [SC5](http://github.com/SC5) | +| **[Sc 5 Serverless Boilerplate](https://github.com/SC5/sc5-serverless-boilerplate)**
A boilerplate that contains setup for test-driven development | [SC5](http://github.com/SC5) | | **[Serverless Blog To Podcast](https://github.com/SC5/serverless-blog-to-podcast)**
Service that reads RSS feed and converts the entries to a podcast feed and audio files using Amazon Polly | [SC5](http://github.com/SC5) | | **[Offset Trump](https://github.com/FLGMwt/offset-trump)**
Single page app using Serverless (C# runtime) and S3 site hosting. Pledge to do a good thing for the next four years to offset the potential negative effects of the US Presidency | [FLGMwt](http://github.com/FLGMwt) | | **[Serverless Url Shortener](https://github.com/aletheia/serverless-url-shortener)**
A simple url-shortener, using Serverless framework | [aletheia](http://github.com/aletheia) | @@ -303,20 +360,38 @@ This table is generated from https://github.com/serverless/examples/blob/master/ | **[Adoptable Pet Bot](https://github.com/lynnaloo/adoptable-pet-bot)**
Tweets adoptable pets using Serverless (Node.js) and AWS Lambda | [lynnaloo](http://github.com/lynnaloo) | | **[Owntracks Serverless](https://github.com/dschep/owntracks-serverless)**
A serverless implementation of the OwnTracks HTTP backend | [dschep](http://github.com/dschep) | | **[Serverless Modern Koa](https://github.com/barczaG/serverless-modern-koa)**
Serverless modern koa starter kit | [barczaG](http://github.com/barczaG) | -| **[Serverless Reactjs Universal Rendering Boilerplate](https://github.com/TylorShin/react-universal-in-serverless)**
ReactJS web app Starter kit does universal (isomorphic) rendering with Serverless | [TylorShin](http://github.com/TylorShin) | +| **[Serverless React JS Universal Rendering Boilerplate](https://github.com/TylorShin/react-universal-in-serverless)**
ReactJS web app Starter kit does universal (isomorphic) rendering with Serverless | [TylorShin](http://github.com/TylorShin) | | **[Open Bot](https://github.com/open-bot/open-bot)**
An unoptionated Github bot driven by a configuration file in the repository | [open-bot](http://github.com/open-bot) | | **[Aws Ses Serverless Example](https://github.com/lakshmantgld/aws-ses-serverless-example)**
AWS SES example in NodeJS using lambda | [lakshmantgld](http://github.com/lakshmantgld) | -| **[Aws Api Gateway Serverless Project Written In Go](https://github.com/yunspace/serverless-golang)**
A serverless project that contains an API Gateway endpoint powered by a Lambda function written in golang and built using [eawsy/aws-lambda-go-shim](https://github.com/eawsy/aws-lambda-go-shim). | [yunspace](http://github.com/yunspace) | +| **[AWS API Gateway Serverless Project Written In Go](https://github.com/yunspace/serverless-golang)**
A serverless project that contains an API Gateway endpoint powered by a Lambda function written in golang and built using [eawsy/aws-lambda-go-shim](https://github.com/eawsy/aws-lambda-go-shim). | [yunspace](http://github.com/yunspace) | | **[Video Preview And Analysis Service](https://github.com/laardee/video-preview-and-analysis-service)**
An event-driven service that generates labels using Amazon Rekognition and creates preview GIF animation from a video file. | [laardee](http://github.com/laardee) | -| **[Serverless Es6/7 Crud Api](https://github.com/AnomalyInnovations/serverless-stack-demo-api)**
[Serverless Stack](http://serverless-stack.com) examples of backend CRUD APIs (DynamoDB + Lambda + API Gateway + Cognito User Pool authorizer) for [React.js single-page app](http://demo.serverless-stack.com) | [AnomalyInnovations](http://github.com/AnomalyInnovations) | -| **[Sqs Worker With Aws Lambda And Cloudwatch Alarms](https://github.com/sbstjn/sqs-worker-serverless)**
Process messages stored in SQS with an [auto-scaled AWS Lambda worker](https://sbstjn.com/serverless-sqs-worker-with-aws-lambda.html) function. | [sbstjn](http://github.com/sbstjn) | -| **[Aws Lambda Power Tuning (Powered By Step Functions)](https://github.com/alexcasalboni/aws-lambda-power-tuning)**
Build a [Step Functions](https://aws.amazon.com/step-functions/) state machine to optimize your AWS Lambda Function memory/power configuration. | [alexcasalboni](http://github.com/alexcasalboni) | +| **[Serverless ES 6 7 CRUD API](https://github.com/AnomalyInnovations/serverless-stack-demo-api)**
[Serverless Stack](http://serverless-stack.com) examples of backend CRUD APIs (DynamoDB + Lambda + API Gateway + Cognito User Pool authorizer) for [React.js single-page app](http://demo.serverless-stack.com) | [AnomalyInnovations](http://github.com/AnomalyInnovations) | +| **[SQS Worker With AWS Lambda And Cloud Watch Alarms](https://github.com/sbstjn/sqs-worker-serverless)**
Process messages stored in SQS with an [auto-scaled AWS Lambda worker](https://sbstjn.com/serverless-sqs-worker-with-aws-lambda.html) function. | [sbstjn](http://github.com/sbstjn) | +| **[Serverless Image Manager](https://github.com/TylorShin/lambda-image-manager)**
image upload / download with resizing. Used API gateway's binary support & serverless | [TylorShin](http://github.com/TylorShin) | +| **[AWS Lambda Power Tuning Powered By Step Functions](https://github.com/alexcasalboni/aws-lambda-power-tuning)**
Build a [Step Functions](https://aws.amazon.com/step-functions/) state machine to optimize your AWS Lambda Function memory/power configuration. | [alexcasalboni](http://github.com/alexcasalboni) | | **[Amazon Kinesis Streams Fan Out Via Kinesis Analytics](https://github.com/alexcasalboni/kinesis-streams-fan-out-kinesis-analytics)**
Use [Amazon Kinesis Analytics](https://aws.amazon.com/kinesis/analytics/) to fan-out your Kinesis Streams and avoid read throttling. | [alexcasalboni](http://github.com/alexcasalboni) | | **[Grants Api Serverless](https://github.com/comicrelief/grants-api-serverless)**
ES6 API to consume data from an external API, ingest into Elasticsearch and return a queryable endpoint on top of Elasticsearch | [comicrelief](http://github.com/comicrelief) | -| **[Honeylambda](https://github.com/0x4D31/honeyLambda)**
a simple, serverless application designed to create and monitor URL {honey}tokens, on top of AWS Lambda and Amazon API Gateway | [0x4D31](http://github.com/0x4D31) | +| **[Honey Lambda](https://github.com/0x4D31/honeyLambda)**
a simple, serverless application designed to create and monitor URL {honey}tokens, on top of AWS Lambda and Amazon API Gateway | [0x4D31](http://github.com/0x4D31) | +| **[Faultline](https://github.com/faultline/faultline)**
Error tracking tool on AWS managed services. | [faultline](http://github.com/faultline) | | **[Stack Overflow Monitor](https://github.com/picsoung/stackoverflowmonitor)**
Monitor Stack Overflow questions and post them in a Slack channel | [picsoung](http://github.com/picsoung) | -| **[React & Stripe Serverless Ecommerce](https://github.com/patrick-michelberger/serverless-shop)**
Serverless E-Commerce App with AWS Lambda, Stripe and React | [patrick-michelberger](http://github.com/patrick-michelberger) | -| **[Serverless + Medium Text To Speech](https://github.com/RafalWilinski/serverless-medium-text-to-speech)**
Serverless-based, text-to-speech service for Medium articles | [RafalWilinski](http://github.com/RafalWilinski) | +| **[Serverless Analytics](https://github.com/sbstjn/serverless-analytics)**
Write your own Google Analytics clone and track website visitors serverless with API Gateway, Kinesis, Lambda, and DynamoDB. | [sbstjn](http://github.com/sbstjn) | +| **[React Stripe Serverless Ecommerce](https://github.com/patrick-michelberger/serverless-shop)**
Serverless E-Commerce App with AWS Lambda, Stripe and React | [patrick-michelberger](http://github.com/patrick-michelberger) | +| **[Serverless Medium Text To Speech](https://github.com/RafalWilinski/serverless-medium-text-to-speech)**
Serverless-based, text-to-speech service for Medium articles | [RafalWilinski](http://github.com/RafalWilinski) | +| **[Serverless Java Dynamo DB Imlementation Example](https://github.com/igorbakman/java-lambda-dynamodb)**
example for java programmers that want to work with AWS-Lambda and DynamoDB | [igorbakman](http://github.com/igorbakman) | +| **[AWS Cognito Custom User Pool Example](https://github.com/bsdkurt/aws-node-custom-user-pool)**
Example CloudFormation custom resource backed by a lambda using Cognito User Pools | [bsdkurt](http://github.com/bsdkurt) | +| **[Serverless Lambda Protobuf Responses](https://github.com/theburningmonk/lambda-protobuf-demo)**
Demo using API Gateway and Lambda with Protocol Buffer | [theburningmonk](http://github.com/theburningmonk) | +| **[Serverless Telegram Bot](https://github.com/jonatasbaldin/serverless-telegram-bot)**
This example demonstrates how to setup an echo Telegram Bot using the Serverless Framework ⚡🤖 | [jonatasbaldin](http://github.com/jonatasbaldin) | +| **[Serverless Dashboard For Atom Editor](https://github.com/horike37/serverless-dashboard-for-atom)**
Atom editor package which allows you to deploy and visualize your serverless services with Serverless Framework on your editor. | [horike37](http://github.com/horike37) | +| **[Serverless Lambda Vpc Nat Redis](https://github.com/ittus/aws-lambda-vpc-nat-examples)**
Demo using API Gateway and Lambda with VPC and NAT to access Internet and AWS Resource | [ittus](http://github.com/ittus) | +| **[Serverless Gitlab CI](https://github.com/bvincent1/serverless-gitlab-ci)**
Simple Gitlab CI template for automatic testing and deployments | [bvincent1](http://github.com/bvincent1) | +| **[Serverless Ffmpeg](https://github.com/kvaggelakos/serverless-ffmpeg)**
Bucket event driven FFMPEG using serverless. Input bucket => Serverless ffmpeg => Output bucket. | [kvaggelakos](http://github.com/kvaggelakos) | +| **[Serverless SSH Command](https://github.com/upgle/serverless-openwhisk-ssh)**
Example of executing ssh command with OpenWhisk | [upgle](http://github.com/upgle) | +| **[Realtime WW 2 Alexa Skill](https://github.com/ceilfors/realtime-ww2-alexa)**
An alexa skill project that's using Alexa SDK. Can also be used for a working example of serverless-webpack (with use of async/await via babel). | [ceilfors](http://github.com/ceilfors) | +| **[Serverless Kakao Bot](https://github.com/JisuPark/serverless-kakao-bot)**
Easy development for Kakaotalk Bot with Serverless | [JisuPark](http://github.com/JisuPark) | +| **[Serverless Q A Example](https://github.com/jacksoncharles/serverless-qa-template-api)**
Inspired by the AWS example forum. A multitenancy Q&A template for surveys, forums and more | [jacksoncharles](http://github.com/jacksoncharles) | +| **[Personal Access Tokens Cron Check](https://github.com/madtrick/cfpat-audit)**
Audit for leaked PAT in your Contentful organization. How to use serverless as cronjobs to keep your Personal Access Tokens secure | [madtrick](http://github.com/madtrick) | +| **[Slack Lunch Club](https://github.com/mikestaub/slack-lunch-club)**
Weekly lunch matches with your slack teams. PWA built with Serverless, React, GraphQL, ArangoDB, and AWS | [mikestaub](http://github.com/mikestaub) | +| **[Slack GitHub stats bot](https://github.com/threadheap/github-stats-bot)**
Slack bot that returns contributions stats for selected repository. Build with Typescript, React, PhantomJS and Nivo | [pavelvlasov](https://github.com/pavelvlasov) | ## Contributing @@ -337,6 +412,7 @@ Check out our [help wanted](https://github.com/serverless/serverless/labels/help ## Consultants These consultants use the Serverless Framework and can help you build your serverless projects. +* [Andrew Griffiths](https://www.andrewgriffithsonline.com/) - Independent consultant specialising in serverless technology * [Trek10](https://www.trek10.com/) * [Parallax](https://parall.ax/) – they also built the [David Guetta Campaign](https://serverlesscode.com/post/david-guetta-online-recording-with-lambda/) * [SC5 Online](https://sc5.io) @@ -358,8 +434,15 @@ These consultants use the Serverless Framework and can help you build your serve * [Emerging Technology Advisors](https://www.emergingtechnologyadvisors.com) * [OneSpeed](https://onespeed.io/) * [Seraro](http://www.seraro.com/) - Who also runs Atlanta Serverless Meetup (https://www.meetup.com/Atlanta-CABI-Camp-Cloud-AI-Blockchain-IOT) and Delhi Serverless Meetup (https://www.meetup.com/Delhi-NCR-Serverless-Architecture-Meetup/) +* [superluminar](https://superluminar.io) - runs serverlessdays Hamburg and Serverless Meetup Hamburg ---- +## Licensing + +Serverless is licensed under the [MIT License](./LICENSE.txt). + +All files located in the node_modules and external directories are externally maintained libraries used by this software which have their own licenses; we recommend you read them, as their terms may differ from the terms in the MIT License. + # Previous Serverless Version 0.5.x You can find projects and plugins relating to version 0.5 [here](./0.5.x-RESOURCES.md). Note that these are not compatible with v1.0 but we are working diligently on updating them. [Guide on building v1.0 plugins](./docs/providers/aws/guide/plugins.md). diff --git a/assets/create-application.png b/assets/create-application.png new file mode 100644 index 00000000000..1b8587376ec Binary files /dev/null and b/assets/create-application.png differ diff --git a/assets/tenant.png b/assets/tenant.png new file mode 100644 index 00000000000..b02e873fefc Binary files /dev/null and b/assets/tenant.png differ diff --git a/docker-compose.yml b/docker-compose.yml index 98c398c8f73..cbc760b010d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,11 +56,11 @@ services: volumes: - ./tmp/serverless-integration-test-aws-scala-sbt:/app aws-csharp: - image: microsoft/dotnet:1.0.4-sdk + image: microsoft/dotnet:2.0-sdk volumes: - ./tmp/serverless-integration-test-aws-csharp:/app aws-fsharp: - image: microsoft/dotnet:1.0.4-sdk + image: microsoft/dotnet:2.0-sdk volumes: - ./tmp/serverless-integration-test-aws-fsharp:/app aws-go: @@ -99,7 +99,7 @@ services: - ./tmp/serverless-integration-test-spotinst-ruby:/app spotinst-java8: image: maven:3-jdk-8 - volumes: + volumes: - ./tmp/serverless-integration-test-spotinst-java8:/app webtasks-nodejs: image: node:6.10.3 diff --git a/docs/README.md b/docs/README.md index 448002d85da..3189bfba559 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,6 +7,7 @@ menuItems: - {menuText: Providers, path: /framework/docs/providers} - {menuText: "- AWS", path: /framework/docs/providers/aws/} - {menuText: "- Azure", path: /framework/docs/providers/azure/} + - {menuText: "- fn", path: /framework/docs/providers/fn/} - {menuText: "- Google", path: /framework/docs/providers/google/} - {menuText: "- OpenWhisk", path: /framework/docs/providers/openwhisk/} - {menuText: "- Kubeless" , path: /framework/docs/providers/kubeless/} @@ -141,4 +142,19 @@ Already using AWS or another cloud provider? Read on. +
+
+ + + +
+
+ +
+
diff --git a/docs/getting-started.md b/docs/getting-started.md index 81a10bba0f9..2cd79a29edd 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -6,6 +6,7 @@ menuOrder: 0 menuItems: - {menuText: AWS Guide, path: /framework/docs/providers/aws/guide/quick-start} - {menuText: Azure Functions Guide, path: /framework/docs/providers/azure/guide/quick-start} + - {menuText: Fn Guide, path: /framework/docs/providers/fn/guide/quick-start} - {menuText: OpenWhisk Guide, path: /framework/docs/providers/openwhisk/guide/quick-start} - {menuText: Google Functions Guide, path: /framework/docs/providers/google/guide/quick-start} - {menuText: Kubeless Guide, path: /framework/docs/providers/kubeless/guide/quick-start} @@ -101,5 +102,14 @@ Next up, it's time to choose where you'd like your serverless service to run. Webtasks
Quick Start Guide
- +
+
+ + + +
+
+ Fn
Quick Start Guide
+
+
diff --git a/docs/platform/README.md b/docs/platform/README.md index 860e64770ea..ae74c540041 100644 --- a/docs/platform/README.md +++ b/docs/platform/README.md @@ -15,55 +15,12 @@ menuItems: # Serverless Platform (Beta) -The Serverless Platform is currently in experimental beta. If you'd like to participate in the beta, simply follow the instructions below. - -## Set-Up - -Make sure you have Node.js installed and run: - -```sh -$ npm i serverless -g -``` - -Then, check the version to make sure you are using V1.20.0, or later: - -```sh -$ serverless -v -``` - -## Usage - -First, register or log in to the Serverless platform in via the CLI - -```sh -$ serverless login -``` - -After logging into the platform via the Serverless framework CLI every deploy will be published **privately** to the Serverless Platform. It allows you to view and share your deployed services. - -Give it a try with a new service, or an existing service: - -```sh -$ serverless deploy -``` - -Then visit https://platform.serverless.com/ in your browser. - -**Note:** You can toggle auto-publishing by adding the `publish` config in `serverless.yml`: - -```yml -service: - name: my-service - publish: false # disable auto-publishing -``` +The Serverless Platform is currently in experimental beta. If you'd like to participate in the beta, please refer to the [full platform docs.](https://github.com/serverless/platform) ## Beta CLI Commands Logging in to the platform enables access to beta features of the Serverless framework. -### [`serverless run`](./commands/run.md) -Start local development mode for a Serverless service. This mode downloads and installs the [event-gateway](https://github.com/serverless/event-gateway) and the [serverless emulator](https://github.com/serverless/emulator). Both of these are used to emulate a serverless service and develop against them locally. - ### [`serverless emit`](./commands/emit.md) Emit an event to an event-gateway. diff --git a/docs/platform/commands/run.md b/docs/platform/commands/run.md deleted file mode 100644 index a04fd376cfc..00000000000 --- a/docs/platform/commands/run.md +++ /dev/null @@ -1,39 +0,0 @@ - - - -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/platform/commands/run) - - -# Run - -The `run` command starts the serverless service locally. - -```bash -serverless run - -# Shorthand -sls run -``` - -## Supported Languages -NOTE: *currently supports node js 6.3+ only* - -## Supported Providers -- AWS Lambda -- Google Cloud Functions (Pub/Sub only, HTTP coming soon) - -## Options -- `--debug` or `-d` Start the emulator in debug mode -- `--eport` or `-e` The Event Gateway API port. Defaults to 4000 -- `--cport` or `-c` The Event Gateway configuration port. Defaults to 4001 -- `--lport` or `-l` The Emulator port. Defaults to 4002 - - -## Provided lifecycle events -- `run:run` diff --git a/docs/providers/README.md b/docs/providers/README.md index 35ff1af2856..9ea9f6efbe8 100644 --- a/docs/providers/README.md +++ b/docs/providers/README.md @@ -83,6 +83,16 @@ Under the hood, the serverless framework is deploying your code to a cloud provi Auth0 Webtasks Docs +
+
+ + + +
+
+ Fn Docs +
+

diff --git a/docs/providers/aws/README.md b/docs/providers/aws/README.md index 8639edb2dab..b31e6769dfa 100644 --- a/docs/providers/aws/README.md +++ b/docs/providers/aws/README.md @@ -12,7 +12,7 @@ layout: Doc Welcome to the Serverless AWS Functions documentation! -If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](https://forum.serverless.com/) +If you have any questions, [search the forums](https://forum.serverless.com?utm_source=framework-docs) or [start your own thread](https://forum.serverless.com?utm_source=framework-docs) **Note:** [AWS system credentials](./guide/credentials.md) are required for using serverless + AWS. diff --git a/docs/providers/aws/cli-reference/logs.md b/docs/providers/aws/cli-reference/logs.md index 880a4efc0a4..31e9b2b6e81 100644 --- a/docs/providers/aws/cli-reference/logs.md +++ b/docs/providers/aws/cli-reference/logs.md @@ -62,6 +62,11 @@ This command returns as many log events as can fit in 1MB (up to 10,000 log even **Note:** There's a small lag between invoking the function and actually having the log event registered in CloudWatch. So it takes a few seconds for the logs to show up right after invoking the function. +```bash +serverless logs -f hello +``` +This will fetch the logs from last 10 minutes as startTime was not given. + ```bash serverless logs -f hello --startTime 5h ``` @@ -76,7 +81,7 @@ This will fetch the logs that happened starting at epoch `1469694264`. serverless logs -f hello -t ``` -Serverless will tail the CloudWatch log output and print new log messages coming in. +Serverless will tail the CloudWatch log output and print new log messages coming in starting from 10 seconds ago. ```bash serverless logs -f hello --filter serverless diff --git a/docs/providers/aws/cli-reference/print.md b/docs/providers/aws/cli-reference/print.md index 53bcb602ff9..4009dac2998 100644 --- a/docs/providers/aws/cli-reference/print.md +++ b/docs/providers/aws/cli-reference/print.md @@ -26,7 +26,9 @@ serverless print ## Options -- None +- `format` Print configuration in given format ("yaml", "json", "text"). Default: yaml +- `path` Period-separated path to print a sub-value (eg: "provider.name") +- `transform` Transform-function to apply to the value (currently only "keys" is supported) ## Examples: @@ -76,3 +78,15 @@ resources: Properties: BucketName: test # <-- Resolved ``` + +This prints the provider name: + +```bash +sls print --path provider.name --format text +``` + +And this prints all function names: + +```bash +sls print --path functions --transform keys --format text +``` diff --git a/docs/providers/aws/events/alexa-skill.md b/docs/providers/aws/events/alexa-skill.md index 915464bcf1b..cdd0a6df730 100644 --- a/docs/providers/aws/events/alexa-skill.md +++ b/docs/providers/aws/events/alexa-skill.md @@ -1,7 +1,7 @@ @@ -14,14 +14,41 @@ layout: Doc ## Event definition -This will enable your Lambda function to be called by an Alexa skill kit. +This will enable your Lambda function to be called by an Alexa Skill kit. +`amzn1.ask.skill.xx-xx-xx-xx-xx` is a skill ID for Alexa Skills kit. You receive a skill ID once you register and create a skill in [Amazon Developer Console](https://developer.amazon.com/). +After deploying, add your deployed Lambda function ARN to which this event is attached to the Service Endpoint under Configuration on Amazon Developer Console. ```yml functions: mySkill: handler: mySkill.handler events: - - alexaSkill + - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx-xx ``` You can find detailed guides on how to create an Alexa Skill with Serverless using NodeJS [here](https://github.com/serverless/examples/tree/master/aws-node-alexa-skill) as well as in combination with Python [here](https://github.com/serverless/examples/tree/master/aws-python-alexa-skill). + +## Enabling / Disabling + +**Note:** `alexaSkill` events are enabled by default. + +This will create and attach a alexaSkill event for the `mySkill` function which is disabled. If enabled it will call +the `mySkill` function by an Alexa Skill. + +```yaml +functions: + mySkill: + handler: mySkill.handler + events: + - alexaSkill: + appId: amzn1.ask.skill.xx-xx-xx-xx + enabled: false +``` + +## Backwards compatibility + +Previous syntax of this event didn't require a skill ID as parameter, but according to [Amazon's documentation](https://developer.amazon.com/docs/custom-skills/host-a-custom-skill-as-an-aws-lambda-function.html#configuring-the-alexa-skills-kit-trigger) you should restrict your lambda function to be executed only by your skill. + +Omitting the skill id will make your Lambda function available for the public, allowing any other skill developer to invoke it. + +(This is important, as [opposing to custom HTTPS endpoints](https://developer.amazon.com/docs/custom-skills/handle-requests-sent-by-alexa.html#request-verify), there's no way to validate the request was sent by your skill.) diff --git a/docs/providers/aws/events/alexa-smart-home.md b/docs/providers/aws/events/alexa-smart-home.md index f6557597ff1..ca50850c28f 100644 --- a/docs/providers/aws/events/alexa-smart-home.md +++ b/docs/providers/aws/events/alexa-smart-home.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/aws/events/apigateway.md b/docs/providers/aws/events/apigateway.md index 7da3daf1b42..453d095542a 100644 --- a/docs/providers/aws/events/apigateway.md +++ b/docs/providers/aws/events/apigateway.md @@ -12,6 +12,39 @@ layout: Doc # API Gateway +- [API Gateway](#api-gateway) + - [Lambda Proxy Integration](#lambda-proxy-integration) + - [Simple HTTP Endpoint](#simple-http-endpoint) + - [Example "LAMBDA-PROXY" event (default)](#example-lambda-proxy-event-default) + - [HTTP Endpoint with Extended Options](#http-endpoint-with-extended-options) + - [Enabling CORS](#enabling-cors) + - [HTTP Endpoints with `AWS_IAM` Authorizers](#http-endpoints-with-aws-iam-authorizers) + - [HTTP Endpoints with Custom Authorizers](#http-endpoints-with-custom-authorizers) + - [Catching Exceptions In Your Lambda Function](#catching-exceptions-in-your-lambda-function) + - [Setting API keys for your Rest API](#setting-api-keys-for-your-rest-api) + - [Configuring endpoint types](#configuring-endpoint-types) + - [Request Parameters](#request-parameters) + - [Lambda Integration](#lambda-integration) + - [Example "LAMBDA" event (before customization)](#example-lambda-event-before-customization) + - [Request templates](#request-templates) + - [Default Request Templates](#default-request-templates) + - [Custom Request Templates](#custom-request-templates) + - [Pass Through Behavior](#pass-through-behavior) + - [Responses](#responses) + - [Custom Response Headers](#custom-response-headers) + - [Custom Response Templates](#custom-response-templates) + - [Status Codes](#status-codes) + - [Available Status Codes](#available-status-codes) + - [Using Status Codes](#using-status-codes) + - [Custom Status Codes](#custom-status-codes) + - [Setting an HTTP Proxy on API Gateway](#setting-an-http-proxy-on-api-gateway) + - [Share API Gateway and API Resources](#share-api-gateway-and-api-resources) + - [Easiest and CI/CD friendly example of using shared API Gateway and API Resources.](#easiest-and-ci-cd-friendly-example-of-using-shared-api-gateway-and-api-resources) + - [Manually Configuring shared API Gateway](#manually-configuring-shared-api-gateway) + - [Note while using authorizers with shared API Gateway](#note-while-using-authorizers-with-shared-api-gateway) + - [Share Authorizer](#share-authorizer) + - [Resource Policy](#resource-policy) + _Are you looking for tutorials on using API Gateway? Check out the following resources:_ > - [Add a custom domain for your API Gateway](https://serverless.com/blog/serverless-api-gateway-domain/) @@ -198,6 +231,21 @@ functions: Configuring the `cors` property sets [Access-Control-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin), [Access-Control-Allow-Headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers), [Access-Control-Allow-Methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods),[Access-Control-Allow-Credentials](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials) headers in the CORS preflight response. +To enable the `Access-Control-Max-Age` preflight response header, set the `maxAge` property in the `cors` object: + +```yml +functions: + hello: + handler: handler.hello + events: + - http: + path: hello + method: get + cors: + origin: '*' + maxAge: 86400 +``` + If you want to use CORS with the lambda-proxy integration, remember to include the `Access-Control-Allow-*` headers in your headers object, like this: ```javascript @@ -321,7 +369,7 @@ functions: identityValidationExpression: someRegex ``` -You can also use the Request Type Authorizer by setting the `type` property. In this case, your `identitySource` could contain multiple entries for you policy cache. The default `type` is 'token'. +You can also use the Request Type Authorizer by setting the `type` property. In this case, your `identitySource` could contain multiple entries for your policy cache. The default `type` is 'token'. ```yml functions: @@ -425,7 +473,7 @@ Clients connecting to this Rest API will then need to set any of these API keys API Gateway [supports regional endpoints](https://aws.amazon.com/about-aws/whats-new/2017/11/amazon-api-gateway-supports-regional-api-endpoints/) for associating your API Gateway REST APIs with a particular region. This can reduce latency if your requests originate from the same region as your REST API and can be helpful in building multi-region applications. -By default, the Serverless Framework deploys your REST API using the EDGE endpoint configuration. If you would like to use the REGIONAL configuration, set the `endpointType` parameter in your `provider` block. +By default, the Serverless Framework deploys your REST API using the EDGE endpoint configuration. If you would like to use the REGIONAL or PRIVATE configuration, set the `endpointType` parameter in your `provider` block. Here's an example configuration for setting the endpoint configuration for your service Rest API: @@ -497,6 +545,7 @@ This method is more complicated and involves a lot more configuration of the `ht "cognitoPoolClaims": { "sub": "" }, + "enhancedAuthContext": {}, "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, br", @@ -633,33 +682,7 @@ See the [api gateway documentation](https://docs.aws.amazon.com/apigateway/lates **Notes:** - A missing/empty request Content-Type is considered to be the API Gateway default (`application/json`) - - [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/apigateway) -- [API Gateway](#api-gateway) - - [Lambda Proxy Integration](#lambda-proxy-integration) - - [Simple HTTP Endpoint](#simple-http-endpoint) - - [Example "LAMBDA-PROXY" event (default)](#example-lambda-proxy-event-default) - - [HTTP Endpoint with Extended Options](#http-endpoint-with-extended-options) - - [Enabling CORS](#enabling-cors) - - [HTTP Endpoints with `AWS_IAM` Authorizers](#http-endpoints-with-awsiam-authorizers) - - [HTTP Endpoints with Custom Authorizers](#http-endpoints-with-custom-authorizers) - - [Catching Exceptions In Your Lambda Function](#catching-exceptions-in-your-lambda-function) - - [Setting API keys for your Rest API](#setting-api-keys-for-your-rest-api) - - [Request Parameters](#request-parameters) - - [Lambda Integration](#lambda-integration) - - [Example "LAMBDA" event (before customization)](#example-lambda-event-before-customization) - - [Request templates](#request-templates) - - [Default Request Templates](#default-request-templates) - - [Custom Request Templates](#custom-request-templates) - - [Pass Through Behavior](#pass-through-behavior) - - [Responses](#responses) - - [Custom Response Headers](#custom-response-headers) - - [Custom Response Templates](#custom-response-templates) - - [Status codes](#status-codes) - - [Available Status Codes](#available-status-codes) - - [Using Status Codes](#using-status-codes) - - [Custom Status Codes](#custom-status-codes) - - [Setting an HTTP Proxy on API Gateway](#setting-an-http-proxy-on-api-gateway) - - [Share API Gateway and API Resources](#share-api-gateway-and-api-resources) +- API Gateway docs refer to "WHEN_NO_TEMPLATE" (singular), but this will fail during creation as the actual value should be "WHEN_NO_TEMPLATES" (plural) ### Responses @@ -857,11 +880,11 @@ Now that you have these two CloudFormation templates defined in your `serverless ## Share API Gateway and API Resources -As you application grows, you will have idea to break it out into multiple services. However, each serverless project generates new API Gateway by default. If you want to share same API Gateway for muliple projects, you 'll need to reference REST API ID and Root Resource ID into serverless.yml files +As your application grows, you will likely need to break it out into multiple, smaller services. By default, each Serverless project generates a new API Gateway. However, you can share the same API Gateway between multiple projects by referencing its REST API ID and Root Resource ID in `serverless.yml` as follows: ```yml service: service-name -provider: +provider: name: aws apiGateway: restApiId: xxxxxxxxxx # REST API resource ID. Default is generated by the framework @@ -872,13 +895,14 @@ functions: ``` -In case the application has many chilren and grandchildren paths, you also want to break them out into smaller services. + +If your application has many nested paths, you might also want to break them out into smaller services. ```yml service: service-a -provider: +provider: apiGateway: - restApiId: xxxxxxxxxx + restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx functions: @@ -892,10 +916,10 @@ functions: ```yml service: service-b -provider: +provider: apiGateway: - restApiId: xxxxxxxxxx - restApiRootResourceId: xxxxxxxxxx + restApiId: xxxxxxxxxx + restApiRootResourceId: xxxxxxxxxx functions: create: @@ -906,17 +930,17 @@ functions: path: /posts/{id}/comments ``` -They reference the same parent path `/posts`. Cloudformation will throw error if we try to generate existed one. To avoid that, we must reference source ID of `/posts`. +The above example services both reference the same parent path `/posts`. However, Cloudformation will throw an error if we try to generate an existing path resource. To avoid that, we reference the resource ID of `/posts`: ```yml service: service-a -provider: +provider: apiGateway: - restApiId: xxxxxxxxxx + restApiId: xxxxxxxxxx restApiRootResourceId: xxxxxxxxxx restApiResources: /posts: xxxxxxxxxx - + functions: ... @@ -924,10 +948,10 @@ functions: ```yml service: service-b -provider: +provider: apiGateway: - restApiId: xxxxxxxxxx - restApiRootResourceId: xxxxxxxxxx + restApiId: xxxxxxxxxx + restApiRootResourceId: xxxxxxxxxx restApiResources: /posts: xxxxxxxxxx @@ -936,18 +960,19 @@ functions: ``` -You can define more than one path resource. Otherwise, serverless will generate paths from root resource. `restApiRootResourceId` can be optional if there isn't path that need to be generated from the root +You can define more than one path resource, but by default, Serverless will generate them from the root resource. +`restApiRootResourceId` is optional if a path resource isn't required for the root (`/`). ```yml service: service-a -provider: +provider: apiGateway: - restApiId: xxxxxxxxxx + restApiId: xxxxxxxxxx # restApiRootResourceId: xxxxxxxxxx # Optional restApiResources: /posts: xxxxxxxxxx /categories: xxxxxxxxx - + functions: listPosts: @@ -966,4 +991,186 @@ functions: ``` -For best practice and CI, CD friendly, we should define Cloudformation resources from early service, then use Cross-Stack References for another ones. +### Easiest and CI/CD friendly example of using shared API Gateway and API Resources. + +You can define your API Gateway resource in one of the former service and export the `restApiId` and `restApiRootResourceId` using cloudformation cross-stack references. + +```yml +service: service-a + +resources: + Resources: + YourApiGateway: + Type: AWS::ApiGateway::RestApi + Properties: + Name: YourApiGatewayName + + Outputs: + apiGatewayRestApiId: + Value: + Ref: YourApiGatewayName + Export: + Name: apiGateway-restApiId + + apiGatewayRestApiRootResourceId: + Value: + Fn::GetAtt: + - YourApiGateway + - RootResourceId + Export: + Name: apiGateway-rootResourceId + + provider: + apiGateway: + restApiId: + Ref: YourApiGatewayName + restApiResources: + Fn::GetAtt: + - YourApiGateway + - RootResourceId + +functions: ...... +``` + +This creates API gateway and then exports the `restApiId` and `rootResourceId` values using cloudformation cross stack output. +We will import this and reference in future services. + +```yml +service: service-b + +provider: + apiGateway: + restApiId: + 'Fn::ImportValue': apiGateway-restApiId + restApiRootResourceId: + 'Fn::ImportValue': apiGateway-rootResourceId + +``` + +You can use this method to share your API Gateway across services in same region. Read about this limitation [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-importvalue.html). + + +### Manually Configuring shared API Gateway + +Use AWS console on browser, navigate to the API Gateway console. Select your already existing API Gateway. +Top Navbar should look like this + +``` + APIs>apigateway-Name (xxxxxxxxxx)>Resources>/ (yyyyyyyyyy) +``` + +Here xxxxxxxxx is your restApiId and yyyyyyyyyy the restApiRootResourceId. + +#### Note while using authorizers with shared API Gateway + +AWS API Gateway allows only 1 Authorizer for 1 ARN, This is okay when you use conventional serverless setup, because each stage and service will create different API Gateway. But this can cause problem when using authorizers with shared API Gateway. If we use the same authorizer directly in different services like this. + +```yml +service: service-c + +provider: + apiGateway: + restApiId: + 'Fn::ImportValue': apiGateway-restApiId + restApiRootResourceId: + 'Fn::ImportValue': apiGateway-rootResourceId + +functions: + deleteUser: + events: + - http: + path: /users/{userId} + authorizer: + arn: xxxxxxxxxxxxxxxxx #cognito/custom authorizer arn +``` + + +```yml +service: service-d + +provider: + apiGateway: + restApiId: + 'Fn::ImportValue': apiGateway-restApiId + restApiRootResourceId: + 'Fn::ImportValue': apiGateway-rootResourceId + +functions: + deleteProject: + events: + - http: + path: /project/{projectId} + authorizer: + arn: xxxxxxxxxxxxxxxxx #cognito/custom authorizer arn +``` + +we encounter error from cloudformation as reported [here](https://github.com/serverless/serverless/issues/4711). + +A proper fix for this is work is using [Share Authorizer](#share-authorizer) or you can add a unique `name` attribute to `authorizer` in each function. This creates different API Gateway authorizer for each function, bound to the same API Gateway. However, there is a limit of 10 authorizers per RestApi, and they are forced to contact AWS to request a limit increase to unblock development. + +## Share Authorizer + +Auto-created Authorizer is convenient for conventional setup. However, when you need to define your custom Authorizer, or use `COGNITO_USER_POOLS` authorizer with shared API Gateway, it is painful because of AWS limitation. Sharing Authorizer is a better way to do. + +```yml +functions: + createUser: + ... + events: + - http: + path: /users + ... + authorizer: + # Provide both type and authorizerId + type: COGNITO_USER_POOLS # TOKEN or COGNITO_USER_POOLS, same as AWS Cloudformation documentation + authorizerId: + Ref: ApiGatewayAuthorizer # or hard-code Authorizer ID + + deleteUser: + ... + events: + - http: + path: /users/{userId} + ... + # Provide both type and authorizerId + type: COGNITO_USER_POOLS # TOKEN or COGNITO_USER_POOLS, same as AWS Cloudformation documentation + authorizerId: + Ref: ApiGatewayAuthorizer # or hard-code Authorizer ID + +resources: + Resources: + ApiGatewayAuthorizer: + Type: AWS::ApiGateway::Authorizer + Properties: + AuthorizerResultTtlInSeconds: 300 + IdentitySource: method.request.header.Authorization + Name: Cognito + RestApiId: + Ref: YourApiGatewayName + Type: COGNITO_USER_POOLS + ProviderARNs: + - arn:aws:cognito-idp:${self:provider.region}:xxxxxx:userpool/abcdef + +``` + +## Resource Policy + +Resource policies are policy documents that are used to control the invocation of the API. Find more use cases from the [Apigateway Resource Policies](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-resource-policies.html) documentation. + +```yml +provider: + name: aws + runtime: nodejs6.10 + + resourcePolicy: + - Effect: Allow + Principal: "*" + Action: execute-api:Invoke + Resource: + - execute-api:/*/*/* + Condition: + IpAddress: + aws:SourceIp: + - "123.123.123.123" + +``` \ No newline at end of file diff --git a/docs/providers/aws/events/cloudwatch-event.md b/docs/providers/aws/events/cloudwatch-event.md index 3d75da37476..56d5c504c80 100644 --- a/docs/providers/aws/events/cloudwatch-event.md +++ b/docs/providers/aws/events/cloudwatch-event.md @@ -1,7 +1,7 @@ @@ -111,3 +111,24 @@ functions: state: - pending ``` + +## Specifying a Name + +You can also specify a CloudWatch Event name. Keep in mind that the name must begin with a letter; contain only ASCII letters, digits, and hyphens; and not end with a hyphen or contain two consecutive hyphens. More infomation [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-name.html). + +```yml +functions: + myCloudWatch: + handler: myCloudWatch.handler + events: + - cloudwatchEvent: + name: 'my-cloudwatch-event-name' + event: + source: + - "aws.ec2" + detail-type: + - "EC2 Instance State-change Notification" + detail: + state: + - pending +``` diff --git a/docs/providers/aws/events/cloudwatch-log.md b/docs/providers/aws/events/cloudwatch-log.md index 2072598ab27..0601bf609ed 100644 --- a/docs/providers/aws/events/cloudwatch-log.md +++ b/docs/providers/aws/events/cloudwatch-log.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/aws/events/cognito-user-pool.md b/docs/providers/aws/events/cognito-user-pool.md index 2af34ca39d3..981a382833c 100644 --- a/docs/providers/aws/events/cognito-user-pool.md +++ b/docs/providers/aws/events/cognito-user-pool.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/aws/events/iot.md b/docs/providers/aws/events/iot.md index 4ba21e9a77e..ed61363734f 100644 --- a/docs/providers/aws/events/iot.md +++ b/docs/providers/aws/events/iot.md @@ -1,7 +1,7 @@ diff --git a/docs/providers/aws/events/sqs.md b/docs/providers/aws/events/sqs.md new file mode 100644 index 00000000000..e66ab749867 --- /dev/null +++ b/docs/providers/aws/events/sqs.md @@ -0,0 +1,49 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/events/sqs) + + +# SQS Queues + +In the following example, we specify that the `compute` function should be triggered whenever there are messages in the given SQS Queue. + +The ARN for the queue can be specified as a string, the reference to the ARN of a resource by logical ID, or the import of an ARN that was exported by a different service or CloudFormation stack. + +**Note:** The `sqs` event will hook up your existing SQS Queue to a Lambda function. Serverless won't create a new queue for you. + +```yml +functions: + compute: + handler: handler.compute + events: + - sqs: arn:aws:sqs:region:XXXXXX:MyFirstQueue + - sqs: + arn: + Fn::GetAtt: + - MySecondQueue + - Arn + - sqs: + arn: + Fn::ImportValue: MyExportedQueueArnId +``` + +## Setting the BatchSize + +For the SQS event integration, you can set the `batchSize`, which effects how many SQS messages will be included in a single Lambda invocation. The default `batchSize` is 10, and the max `batchSize` is 10. + +```yml +functions: + preprocess: + handler: handler.preprocess + events: + - sqs: + arn: arn:aws:sqs:region:XXXXXX:myQueue + batchSize: 10 +``` diff --git a/docs/providers/aws/examples/hello-world/csharp/Handler.cs b/docs/providers/aws/examples/hello-world/csharp/Handler.cs deleted file mode 100644 index cf4ff95490a..00000000000 --- a/docs/providers/aws/examples/hello-world/csharp/Handler.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Amazon.Lambda.Core; -using System; - -[assembly:LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))] - -namespace AwsDotnetCsharp -{ - public class Handler - { - public Response Hello(Request request) - { - return new Response("Go Serverless v1.0! Your function executed successfully!", request); - } - } - - public class Response - { - public string Message {get; set;} - public Request Request {get; set;} - - public Response(string message, Request request){ - Message = message; - Request = request; - } - } - - public class Request - { - public string Key1 {get; set;} - public string Key2 {get; set;} - public string Key3 {get; set;} - - public Request(string key1, string key2, string key3){ - Key1 = key1; - Key2 = key2; - Key3 = key3; - } - } -} diff --git a/docs/providers/aws/examples/hello-world/csharp/README.md b/docs/providers/aws/examples/hello-world/csharp/README.md index 9f0d170a4dc..f0a617e5ebf 100644 --- a/docs/providers/aws/examples/hello-world/csharp/README.md +++ b/docs/providers/aws/examples/hello-world/csharp/README.md @@ -13,13 +13,34 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. + +If `sls` command retuns an error in PowerShell, please user `serverless` command. + +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + ## 1. Create a service -`serverless create --template aws-csharp --path myService` or `sls create --template aws-csharp --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. -## 2. Build using .NET CLI tools and create zip package +``` +sls create --template aws-csharp --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-csharp with the `--template` or shorthand `-t` flag. + +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. + +## 2. Build using .NET Core 2.X CLI tools and create zip package ``` -# Linux or OSX +# Linux or Mac OS ./build.sh ``` @@ -29,14 +50,23 @@ Make sure `serverless` is installed. [See installation guide](../../../guide/ins ``` ## 3. Deploy -`serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command + +``` +sls deploy +``` + +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. + ## 4. Invoke deployed function -`serverless invoke --function hello` or `serverless invoke -f hello` -`-f` is shorthand for `--function` +``` +sls invoke -f hello +``` + +Invoke deployed function with command `invoke` and `--function` or shorthand `-f`. -In your terminal window you should see the response from AWS Lambda +In your terminal window you should see the response from AWS Lambda. ```bash { @@ -49,4 +79,4 @@ In your terminal window you should see the response from AWS Lambda } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/examples/hello-world/csharp/csharp.csproj b/docs/providers/aws/examples/hello-world/csharp/csharp.csproj deleted file mode 100644 index c0d2e4c69dc..00000000000 --- a/docs/providers/aws/examples/hello-world/csharp/csharp.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - netcoreapp1.0 - CsharpHandlers - csharp - - - - - - - - - - - - diff --git a/docs/providers/aws/examples/hello-world/csharp/serverless.yml b/docs/providers/aws/examples/hello-world/csharp/serverless.yml deleted file mode 100644 index 3e622e1f56f..00000000000 --- a/docs/providers/aws/examples/hello-world/csharp/serverless.yml +++ /dev/null @@ -1,85 +0,0 @@ -# Welcome to Serverless! -# -# This file is the main config file for your service. -# It's very minimal at this point and uses default values. -# You can always add more config options for more control. -# We've included some commented out config examples here. -# Just uncomment any of them to get that config option. -# -# For full config options, check the docs: -# docs.serverless.com -# -# Happy Coding! - -service: aws-csharp # NOTE: update this with your service name - -# You can pin your service to only deploy with a specific Serverless version -# Check out our docs for more details -# frameworkVersion: "=X.X.X" - -provider: - name: aws - runtime: dotnetcore1.0 - -# you can overwrite defaults here -# stage: dev -# region: us-east-1 - -# you can add statements to the Lambda function's IAM Role here -# iamRoleStatements: -# - Effect: "Allow" -# Action: -# - "s3:ListBucket" -# Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } -# - Effect: "Allow" -# Action: -# - "s3:PutObject" -# Resource: -# Fn::Join: -# - "" -# - - "arn:aws:s3:::" -# - "Ref" : "ServerlessDeploymentBucket" -# - "/*" - -# you can define service wide environment variables here -# environment: -# variable1: value1 - -# you can add packaging information here -package: - artifact: bin/release/netcoreapp1.0/deploy-package.zip -# exclude: -# - exclude-me.js -# - exclude-me-dir/** - -functions: - hello: - handler: CsharpHandlers::AwsDotnetCsharp.Handler::Hello - -# The following are a few example events you can configure -# NOTE: Please make sure to change your handler code to work with those events -# Check the event documentation for details -# events: -# - http: -# path: users/create -# method: get -# - s3: ${env:BUCKET} -# - schedule: rate(10 minutes) -# - sns: greeter-topic -# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 - -# Define function environment variables here -# environment: -# variable2: value2 - -# you can add CloudFormation resource templates here -#resources: -# Resources: -# NewResource: -# Type: AWS::S3::Bucket -# Properties: -# BucketName: my-new-bucket -# Outputs: -# NewOutput: -# Description: "Description for the output" -# Value: "Some output value" diff --git a/docs/providers/aws/examples/hello-world/fsharp/Handler.fs b/docs/providers/aws/examples/hello-world/fsharp/Handler.fs deleted file mode 100644 index b9e1806fea2..00000000000 --- a/docs/providers/aws/examples/hello-world/fsharp/Handler.fs +++ /dev/null @@ -1,17 +0,0 @@ -namespace AwsDotnetFsharp -open Amazon.Lambda.Core - -[)>] -do () - -type Request = { Key1 : string; Key2 : string; Key3 : string } -type Response = { Message : string; Request : Request } - -module Handler = - open System - open System.IO - open System.Text - - let hello(request:Request) = - { Message="Go Serverless v1.0! Your function executed successfully!" - Request=request } \ No newline at end of file diff --git a/docs/providers/aws/examples/hello-world/fsharp/README.md b/docs/providers/aws/examples/hello-world/fsharp/README.md index 7c7de6acb48..f1e14c97d60 100644 --- a/docs/providers/aws/examples/hello-world/fsharp/README.md +++ b/docs/providers/aws/examples/hello-world/fsharp/README.md @@ -5,33 +5,75 @@ description: Create a F# Hello World Lambda function layout: Doc --> + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/aws/examples/hello-world/fsharp/) + + # Hello World F# Example -## Prerequisites +Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). + +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. + +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + +## 1. Create a service + +``` +sls create --template aws-fsharp --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-fsharp with the `--template` or shorthand `-t` flag. -* Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). -* [.Net Core 1.0.1 SDK](https://www.microsoft.com/net/download/core) - * 1.1 isn't currently supported by AWS Lambda -* [NodeJS v4 or higher](https://nodejs.org/en/) -* An AWS Account +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. -## Build and Package +## 2. Build using .NET Core 2.X CLI tools and create zip package -From the root of this directory, run `build.cmd`, or `build.sh` if on Linux / Mac. +``` +# Linux or Mac OS +./build.sh +``` -This will produce your deployment package at `bin/release/netcoreapp1.0/deploy-package.zip`. +``` +# Windows PowerShell +./build.cmd +``` -## Deployment and Invocation +## 3. Deploy -Once packaged, you can follow [these instructions](https://github.com/serverless/serverless#quick-start) to deploy and remotely invoke the function on AWS Lambda. +``` +sls deploy +``` -In short the commands you will need to run are (from the root of this directory): +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. +## 4. Invoke deployed function + +``` +sls invoke -f hello ``` -serverless config credentials --provider aws --key {YourAwsAccessKey} --secret {YourAwsSecret} -serverless deploy -v -serverless invoke -f hello -l -serverless remove + +Invoke deployed function with command `invoke` and `--function` or shorthand `-f`. + +In your terminal window you should see the response from AWS Lambda. + +```bash +{ + "Message": "Go Serverless v1.0! Your function executed successfully!", + "Request": { + "Key1": null, + "Key2": null, + "Key3": null + } +} ``` -By default this template deploys to us-east-1, you can change that in "serverless.yml" under the `region: us-east-1` key. \ No newline at end of file +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/examples/hello-world/fsharp/aws-fsharp.fsproj b/docs/providers/aws/examples/hello-world/fsharp/aws-fsharp.fsproj deleted file mode 100644 index f3a1aafd44b..00000000000 --- a/docs/providers/aws/examples/hello-world/fsharp/aws-fsharp.fsproj +++ /dev/null @@ -1,24 +0,0 @@ - - - - netcoreapp1.0 - FsharpHandlers - aws-fsharp - - - - - - - - - - - - - - - - - - diff --git a/docs/providers/aws/examples/hello-world/fsharp/serverless.yml b/docs/providers/aws/examples/hello-world/fsharp/serverless.yml deleted file mode 100644 index c90e1089a71..00000000000 --- a/docs/providers/aws/examples/hello-world/fsharp/serverless.yml +++ /dev/null @@ -1,87 +0,0 @@ -# Welcome to Serverless! -# -# This file is the main config file for your service. -# It's very minimal at this point and uses default values. -# You can always add more config options for more control. -# We've included some commented out config examples here. -# Just uncomment any of them to get that config option. -# -# For full config options, check the docs: -# docs.serverless.com -# -# Happy Coding! - -service: aws-fsharp # NOTE: update this with your service name - -# You can pin your service to only deploy with a specific Serverless version -# Check out our docs for more details -# frameworkVersion: "=X.X.X" - -provider: - name: aws - runtime: dotnetcore1.0 - -# you can overwrite defaults here -# stage: dev -# region: us-east-1 - -# you can add statements to the Lambda function's IAM Role here -# iamRoleStatements: -# - Effect: "Allow" -# Action: -# - "s3:ListBucket" -# Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] } -# - Effect: "Allow" -# Action: -# - "s3:PutObject" -# Resource: -# Fn::Join: -# - "" -# - - "arn:aws:s3:::" -# - "Ref" : "ServerlessDeploymentBucket" - -# you can define service wide environment variables here -# environment: -# variable1: value1 - -# you can add packaging information here -package: - artifact: bin/release/netcoreapp1.0/deploy-package.zip -# exclude: -# - exclude-me.js -# - exclude-me-dir/** - -functions: - hello: - handler: FsharpHandlers::AwsDotnetFsharp.Handler::hello - -# The following are a few example events you can configure -# NOTE: Please make sure to change your handler code to work with those events -# Check the event documentation for details -# events: -# - http: -# path: users/create -# method: get -# - s3: ${env:BUCKET} -# - schedule: rate(10 minutes) -# - sns: greeter-topic -# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill -# - iot: -# sql: "SELECT * FROM 'some_topic'" - -# Define function environment variables here -# environment: -# variable2: value2 - -# you can add CloudFormation resource templates here -#resources: -# Resources: -# NewResource: -# Type: AWS::S3::Bucket -# Properties: -# BucketName: my-new-bucket -# Outputs: -# NewOutput: -# Description: "Description for the output" -# Value: "Some output value" diff --git a/docs/providers/aws/examples/hello-world/go/README.md b/docs/providers/aws/examples/hello-world/go/README.md index 8c522ccaecd..05a233a33f1 100644 --- a/docs/providers/aws/examples/hello-world/go/README.md +++ b/docs/providers/aws/examples/hello-world/go/README.md @@ -13,20 +13,45 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. + +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + You should also have [go](https://golang.org/doc/install) and [make](https://www.gnu.org/software/make/) -It is always good practice to organise your `go` projects within [GOPATH](https://golang.org/doc/code.html#GOPATH), to maximise the benefits of go tooling. +It is always good practice to organize your `go` projects within [GOPATH](https://golang.org/doc/code.html#GOPATH), to maximize the benefits of go tooling. ## 1. Create a service -There are two templates for `go`: -1. [aws-go](https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-go) - `serverless create --template aws-go --path myService` -2. [aws-go-dep](https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-go-dep) - `serverless create --template aws-go-dep --path myService` +The Serverless Framework includes starter templates for various languages and providers. There are two templates for `go`. + +#### [aws-go](https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-go) + +`aws-go` fetches dependencies using standard `go get`. + +``` +sls create --template aws-go --path myService +``` + +#### [aws-go-dep](https://github.com/serverless/serverless/tree/master/lib/plugins/create/templates/aws-go-dep) + +`aws-go-dep` uses [go dep](https://github.com/golang/dep) and requires your project to be in `$GOPATH/src` + +``` +sls create --template aws-go-dep --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-go-dep with the `--template` or shorthand `-t` flag. -where: -- 'aws-go' fetches dependencies using standard `go get`. -- 'aws-go-dep' uses [go dep](https://github.com/golang/dep) and requires your project to be in `$GOPATH/src` -- 'myService' is a new folder to be created with template service files. +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into 'myService' folder and you can see this project has 2 handler functions: `hello` and `world` split into 2 separate go packages (folders): @@ -52,21 +77,39 @@ Run `make build` to build both functions. Successful build should generate the f ``` ## 3. Deploy -`serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command + +``` +sls deploy +``` + +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. ## 4. Invoke deployed function -Invoking the both functions should return a successful results: + +``` +sls invoke -f hello +``` + +``` +sls invoke -f world +``` + +Invoke either deployed function with command `invoke` and `--function` or shorthand `-f`. + +In your terminal window you should see the response from AWS Lambda. ```bash serverless invoke -f hello + { "message": "Go Serverless v1.0! Your function executed successfully!" } -serverless invoke --f world +serverless invoke -f world + { "message": "Okay so your other function also executed successfully!" } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/examples/hello-world/node/README.md b/docs/providers/aws/examples/hello-world/node/README.md index 792bd4e8d51..fb927951f61 100644 --- a/docs/providers/aws/examples/hello-world/node/README.md +++ b/docs/providers/aws/examples/hello-world/node/README.md @@ -13,18 +13,45 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. + +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + ## 1. Create a service -`serverless create --template aws-nodejs --path myService` or `sls create --template aws-nodejs --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. + +``` +sls create --template aws-nodejs --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-nodejs with the `--template` or shorthand `-t` flag. + +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. ## 2. Deploy -`serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command + +``` +sls deploy +``` + +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. ## 3. Invoke deployed function -`serverless invoke --function hello` or `serverless invoke -f hello` -`-f` is shorthand for `--function` +``` +sls invoke -f hello +``` + +Invoke deployed function with command `invoke` and `--function` or shorthand `-f`. -In your terminal window you should see the response from AWS Lambda +In your terminal window you should see the response from AWS Lambda. ```bash { @@ -33,4 +60,4 @@ In your terminal window you should see the response from AWS Lambda } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/examples/hello-world/python/README.md b/docs/providers/aws/examples/hello-world/python/README.md index d1e432b603a..c6380de149b 100644 --- a/docs/providers/aws/examples/hello-world/python/README.md +++ b/docs/providers/aws/examples/hello-world/python/README.md @@ -13,18 +13,45 @@ layout: Doc Make sure `serverless` is installed. [See installation guide](../../../guide/installation.md). +Once installed the Serverless CLI can be called with `serverless` or the shorthand `sls` command. + +``` +$ sls + +Commands +* You can run commands with "serverless" or the shortcut "sls" +* Pass "--verbose" to this command to get in-depth plugin info +* Pass "--no-color" to disable CLI colors +* Pass "--help" after any for contextual help +``` + ## 1. Create a service -`serverless create --template aws-python --path myService` or `sls create --template aws-python --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. + +``` +sls create --template aws-python --path myService +``` + +Using the `create` command we can specify one of the available [templates](https://serverless.com/framework/docs/providers/aws/cli-reference/create#available-templates). For this example use aws-python with the `--template` or shorthand `-t` flag. + +The `--path` or shorthand `-p` is the location to be created with the template service files. Change directories into this new folder. ## 2. Deploy -`serverless deploy` or `sls deploy`. `sls` is shorthand for the serverless CLI command + +``` +sls deploy +``` + +This will deploy your function to AWS Lambda based on the settings in `serverless.yml`. ## 3. Invoke deployed function -`serverless invoke --function hello` or `serverless invoke -f hello` -`-f` is shorthand for `--function` +``` +sls invoke -f hello +``` + +Invoke deployed function with command `invoke` and `--function` or shorthand `-f`. -In your terminal window you should see the response from AWS Lambda +In your terminal window you should see the response from AWS Lambda. ```bash { @@ -33,4 +60,4 @@ In your terminal window you should see the response from AWS Lambda } ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/aws/guide/credentials.md b/docs/providers/aws/guide/credentials.md index 91ab63bd203..db79d92ca39 100644 --- a/docs/providers/aws/guide/credentials.md +++ b/docs/providers/aws/guide/credentials.md @@ -180,4 +180,4 @@ custom: **Be aware!** Due to the way AWS IAM and the local environment works, if you invoke your lambda functions locally using the CLI command `serverless invoke local -f ...` the IAM role/profile could be (and probably is) different from the one set in the `serverless.yml` configuration file. Thus, most likely, a different set of permissions will be in place, altering the interaction between your lambda functions and other AWS resources. -*Please, refer to the `invoke local` CLI command documentation for more details.* +*Please, refer to the [`invoke local`](https://serverless.com/framework/docs/providers/aws/cli-reference/invoke-local/#aws---invoke-local) CLI command documentation for more details.* diff --git a/docs/providers/aws/guide/functions.md b/docs/providers/aws/guide/functions.md index 2d6a970ed68..ebf78e81d11 100644 --- a/docs/providers/aws/guide/functions.md +++ b/docs/providers/aws/guide/functions.md @@ -37,6 +37,7 @@ functions: runtime: python2.7 # optional overwrite, default is provider runtime memorySize: 512 # optional, in MB, default is 1024 timeout: 10 # optional, in seconds, default is 6 + reservedConcurrency: 5 # optional, reserved concurrency limit for this function. By default, AWS uses account concurrency limit ``` The `handler` property points to the file and module containing the code you want to run in your function. @@ -99,6 +100,26 @@ functions: memorySize: 512 # function specific ``` +You can specify an array of functions, which is useful if you separate your functions in to different files: + +```yml +# serverless.yml +... + +functions: + - ${file(./foo-functions.yml)} + - ${file(./bar-functions.yml)} +``` + +```yml +# foo-functions.yml +getFoo: + handler: handler.foo +deleteFoo: + handler: handler.foo +``` + + ## Permissions Every AWS Lambda function needs permission to interact with other AWS infrastructure resources within your account. These permissions are set via an AWS IAM Role. You can set permission policy statements within this role via the `provider.iamRoleStatements` property. @@ -233,7 +254,7 @@ In order for other services such as Kinesis streams to be made available, a NAT ## Environment Variables -You can add environment variable configuration to a specific function in `serverless.yml` by adding an `environment` object property in the function configuration. This object should contain a key/value collection of strings: +You can add environment variable configuration to a specific function in `serverless.yml` by adding an `environment` object property in the function configuration. This object should contain a key-value pairs of string to string: ```yml # serverless.yml @@ -269,6 +290,7 @@ functions: environment: TABLE_NAME: tableName2 ``` +If you want your function's environment variables to have the same values from your machine's environment variables, please read the documentation about [Referencing Environment Variables](./variables.md). ## Tags @@ -284,6 +306,28 @@ functions: foo: bar ``` +Or if you want to apply tags configuration to all functions in your service, you can add the configuration to the higher level `provider` object. Tags configured at the function level are merged with those at the provider level, so your function with specific tags will get the tags defined at the provider level. If a tag with the same key is defined at both the function and provider levels, the function-specific value overrides the provider-level default value. For exemple: + +```yml +# serverless.yml +service: service-name +provider: + name: aws + tags: + foo: bar + baz: qux + +functions: + hello: + # this function will inherit the service level tags config above + handler: handler.hello + users: + # this function will overwrite the foo tag and inherit the baz tag + handler: handler.users + tags: + foo: quux +``` + Real-world use cases where tagging your functions is helpful include: - Cost estimations (tag functions with an environment tag: `environment: Production`) diff --git a/docs/providers/aws/guide/iam.md b/docs/providers/aws/guide/iam.md index 19aacd6f389..3f4d07853b6 100644 --- a/docs/providers/aws/guide/iam.md +++ b/docs/providers/aws/guide/iam.md @@ -45,7 +45,17 @@ provider: - "/*" ``` +Alongside `provider.iamRoleStatements` managed policies can also be added to this service-wide Role, define managed policies in `provider.iamManagedPolicies`. These will also be merged into the generated IAM Role so you can use `Join`, `Ref` or any other CloudFormation method or feature here too. +```yml +service: new-service +provider: + name: aws + iamManagedPolicies: + - 'some:aws:arn:xxx:*:*' + - 'someOther:aws:arn:xxx:*:*' + - { 'Fn::Join': [':', ['arn:aws:iam:', { Ref: 'AWSAccountId' }, 'some/path']] } +``` ## Custom IAM Roles **WARNING:** You need to take care of the overall role setup as soon as you define custom roles. @@ -87,7 +97,7 @@ resources: Type: AWS::IAM::Role Properties: Path: /my/default/path/ - RoleName: MyDefaultRole + RoleName: MyDefaultRole # required if you want to use 'serverless deploy --function' later on AssumeRolePolicyDocument: Version: '2012-10-17' Statement: diff --git a/docs/providers/aws/guide/plugins.md b/docs/providers/aws/guide/plugins.md index f7a57a76a72..3cea0015727 100644 --- a/docs/providers/aws/guide/plugins.md +++ b/docs/providers/aws/guide/plugins.md @@ -33,9 +33,25 @@ We need to tell Serverless that we want to use the plugin inside our service. We plugins: - custom-serverless-plugin ``` +The `plugins` section supports two formats: -Plugins might want to add extra information which should be accessible to Serverless. The `custom` section in the `serverless.yml` file is the place where you can add necessary -configurations for your plugins (the plugins author / documentation will tell you if you need to add anything there): +Array object: +```yml +plugins: + - plugin1 + - plugin2 +``` + +Enhanced plugins object: +```yml +plugins: + localPath: './custom_serverless_plugins' + modules: + - plugin1 + - plugin2 +``` + +Plugins might want to add extra information which should be accessible to Serverless. The `custom` section in the `serverless.yml` file is the place where you can add necessary configurations for your plugins (the plugins author / documentation will tell you if you need to add anything there): ```yml plugins: @@ -47,9 +63,24 @@ custom: ## Service local plugin -If you are working on a plugin or have a plugin that is just designed for one project you can add them to the `.serverless_plugins` directory at the root of your service, and in the `plugins` array in `serverless.yml`. +If you are working on a plugin or have a plugin that is just designed for one project they can be loaded from the local folder. Local plugins can be added in the `plugins` array in `serverless.yml`. + +By default local plugins can be added to the `.serverless_plugins` directory at the root of your service, and in the `plugins` array in `serverless.yml`. +```yml +plugins: + - custom-serverless-plugin +``` + +Local plugins folder can be changed by enhancing `plugins` object: +```yml +plugins: + localPath: './custom_serverless_plugins' + modules: + - custom-serverless-plugin +``` +The `custom-serverless-plugin` will be loaded from the `custom_serverless_plugins` directory at the root of your service. If the `localPath` is not provided or empty `.serverless_plugins` directory will be taken as the `localPath`. -The plugin will be loaded based on being named `custom-serverless-plugin.js` or `custom-serverless-plugin\index.js` in the root of `.serverless_plugins` folder. +The plugin will be loaded based on being named `custom-serverless-plugin.js` or `custom-serverless-plugin\index.js` in the root of `localPath` folder (`.serverless_plugins` by default). ### Load Order diff --git a/docs/providers/aws/guide/quick-start.md b/docs/providers/aws/guide/quick-start.md index 900a23aff2e..366719c1ba4 100644 --- a/docs/providers/aws/guide/quick-start.md +++ b/docs/providers/aws/guide/quick-start.md @@ -64,7 +64,7 @@ $ cd my-service 4. **Fetch the Function Logs** - Open up a separate tab in your console and stream all logs for a specific Function using this command. + Open up a separate tab in your console, set your [Provider Credentials](./credentials.md) and stream all logs for a specific Function using this command. ```bash serverless logs -f hello -t ``` diff --git a/docs/providers/aws/guide/resources.md b/docs/providers/aws/guide/resources.md index 5d2d65fe4a3..c6dbaf84f18 100644 --- a/docs/providers/aws/guide/resources.md +++ b/docs/providers/aws/guide/resources.md @@ -18,7 +18,7 @@ Using the Serverless Framework, you can define the infrastructure resources you ## Configuration -Every `serverless.yml` using the `aws` provider is a single AWS CloudFormation stack. This is where your AWS Lambda functions and their event configurations are defined and it's how they are deployed. When you add `resources` those resources are added into your CloudFormation stack upon `serverless deploy`. +Every stage you deploy to with `serverless.yml` using the `aws` provider is a single AWS CloudFormation stack. This is where your AWS Lambda functions and their event configurations are defined and it's how they are deployed. When you add `resources` those resources are added into your CloudFormation stack upon `serverless deploy`. Define your AWS resources in a property titled `resources`. What goes in this property is raw CloudFormation template syntax, in YAML, like this: @@ -29,7 +29,7 @@ service: usersCrud provider: aws functions: -resources: // CloudFormation template syntax +resources: # CloudFormation template syntax Resources: usersTable: Type: AWS::DynamoDB::Table diff --git a/docs/providers/aws/guide/serverless.yml.md b/docs/providers/aws/guide/serverless.yml.md index c9f1c35a771..d0bcb131ef3 100644 --- a/docs/providers/aws/guide/serverless.yml.md +++ b/docs/providers/aws/guide/serverless.yml.md @@ -28,6 +28,8 @@ provider: runtime: nodejs6.10 stage: dev # Set the default stage used. Default is dev region: us-east-1 # Overwrite the default region used. Default is us-east-1 + stackName: custom-stack-name # Use a custom name for the CloudFormation stack + apiName: custom-api-name # Use a custom name for the API Gateway API profile: production # The default profile to use with this service memorySize: 512 # Overwrite the default memory size. Default is 1024 timeout: 10 # The default is 6 seconds. Note: API Gateway current maximum is 30 seconds @@ -62,6 +64,8 @@ provider: rateLimit: 100 stackTags: # Optional CF stack tags key: value + iamManagedPolicies: # Optional IAM Managed Policies, which allows to include the policies into IAM Role + - arn:aws:iam:*****:policy/some-managed-policy iamRoleStatements: # IAM role statements so that services can be accessed in the AWS account - Effect: 'Allow' Action: @@ -78,6 +82,7 @@ provider: Resource: "*" - Effect: Deny Principal: "*" + Resource: "*" Action: - Update:Replace - Update:Delete @@ -92,6 +97,21 @@ provider: subnetIds: - subnetId1 - subnetId2 + notificationArns: # List of existing Amazon SNS topics in the same region where notifications about stack events are sent. + - 'arn:aws:sns:us-east-1:XXXXXX:mytopic' + resourcePolicy: + - Effect: Allow + Principal: "*" + Action: execute-api:Invoke + Resource: + - execute-api:/*/*/* + Condition: + IpAddress: + aws:SourceIp: + - "123.123.123.123" + tags: # Optional service wide function tags + foo: bar + baz: qux package: # Optional deployment packaging configuration include: # Specify the directories and files which should be included in the deployment package @@ -101,6 +121,8 @@ package: # Optional deployment packaging configuration - .git/** - .travis.yml excludeDevDependencies: false # Config if Serverless should automatically exclude dev dependencies in the deployment package. Defaults to true + artifact: path/to/my-artifact.zip # Own package that should be used. You must provide this file. + individually: true # Enables individual packaging for each function. If true you must provide package for each function. Defaults to false functions: @@ -125,6 +147,15 @@ functions: subnetIds: - subnetId1 - subnetId2 + package: + include: # Specify the directories and files which should be included in the deployment package for this specific function. + - src/** + - handler.js + exclude: # Specify the directories and files which should be excluded in the deployment package for this specific function. + - .git/** + - .travis.yml + artifact: path/to/my-artifact.zip # Own package that should be use for this specific function. You must provide this file. + individually: true # Enables individual packaging for specific function. If true you must provide package for each function. Defaults to false events: # The Events that trigger this Function - http: # This creates an API Gateway HTTP endpoint which can be used to trigger this function. Learn more in "events/apigateway" path: users/create # Path for this endpoint @@ -144,6 +175,8 @@ functions: - prefix: uploads/ - suffix: .jpg - schedule: + name: my scheduled event + description: a description of my scheduled event's purpose rate: rate(10 minutes) enabled: false input: @@ -151,6 +184,7 @@ functions: key2: value2 stageParams: stage: dev + inputPath: '$.stageVariables' - sns: topicName: aggregate displayName: Data aggregation pipeline @@ -159,7 +193,9 @@ functions: batchSize: 100 startingPosition: LATEST enabled: false - - alexaSkill + - alexaSkill: + appId: amzn1.ask.skill.xx-xx-xx-xx + enabled: true - alexaSmartHome: appId: amzn1.ask.skill.xx-xx-xx-xx enabled: true diff --git a/docs/providers/aws/guide/services.md b/docs/providers/aws/guide/services.md index 24d33dab159..57e2975e918 100644 --- a/docs/providers/aws/guide/services.md +++ b/docs/providers/aws/guide/services.md @@ -116,6 +116,7 @@ provider: Action: - Update:Replace - Update:Delete + Resource: "*" Condition: StringEquals: ResourceType: diff --git a/docs/providers/aws/guide/variables.md b/docs/providers/aws/guide/variables.md index 70f20e32d0f..fb5d7fad771 100644 --- a/docs/providers/aws/guide/variables.md +++ b/docs/providers/aws/guide/variables.md @@ -285,7 +285,7 @@ functions: You can reference JavaScript files to add dynamic data into your variables. -References can be either named or unnamed exports. To use the exported `someModule` in `myFile.js` you'd use the following code `${file(./myFile.js):someModule}`. For an unnamed export you'd write `${file(./myFile.js)}`. +References can be either named or unnamed exports. To use the exported `someModule` in `myFile.js` you'd use the following code `${file(./myFile.js):someModule}`. For an unnamed export you'd write `${file(./myFile.js)}`. The first argument to your export will be a reference to the Serverless object, containing your configuration. Here are other examples: @@ -299,7 +299,9 @@ module.exports.rate = () => { ```js // config.js -module.exports = () => { +module.exports = (serverless) => { + serverless.cli.consoleLog('You can access Serverless config and methods as well!'); + return { property1: 'some value', property2: 'some other value' @@ -426,6 +428,7 @@ provider: name: aws runtime: nodejs6.10 variableSyntax: "\\${{([ ~:a-zA-Z0-9._\\'\",\\-\\/\\(\\)]+?)}}" # notice the double quotes for yaml to ignore the escape characters! +# variableSyntax: "\\${((?!AWS)[ ~:a-zA-Z0-9._'\",\\-\\/\\(\\)]+?)}" # Use this for allowing CloudFormation Pseudo-Parameters in your serverless.yml -- e.g. ${AWS::Region}. All other Serverless variables work as usual. custom: myStage: ${{opt:stage}} diff --git a/docs/providers/aws/guide/workflow.md b/docs/providers/aws/guide/workflow.md index aafff64922b..59d826bc975 100644 --- a/docs/providers/aws/guide/workflow.md +++ b/docs/providers/aws/guide/workflow.md @@ -12,7 +12,7 @@ layout: Doc # Workflow -Intro. Quick recommendations and tips for various processes. +Quick recommendations and tips for various processes. ### Development Workflow diff --git a/docs/providers/azure/README.md b/docs/providers/azure/README.md index e7ad1f63a69..0bd1b70bed0 100644 --- a/docs/providers/azure/README.md +++ b/docs/providers/azure/README.md @@ -12,7 +12,7 @@ layout: Doc Welcome to the Serverless Azure Functions documentation! -If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](https://forum.serverless.com/) +If you have any questions, [search the forums](https://forum.serverless.com?utm_source=framework-docs) or [start your own thread](https://forum.serverless.com?utm_source=framework-docs) **Note:** [Azure Functions system credentials](./guide/credentials.md) are required for using serverless + Azure Functions. diff --git a/docs/providers/azure/cli-reference/print.md b/docs/providers/azure/cli-reference/print.md index 5a4c65077da..e402e8e7fed 100644 --- a/docs/providers/azure/cli-reference/print.md +++ b/docs/providers/azure/cli-reference/print.md @@ -26,7 +26,9 @@ serverless print ## Options -- None +- `format` Print configuration in given format ("yaml", "json", "text"). Default: yaml +- `path` Period-separated path to print a sub-value (eg: "provider.name") +- `transform` Transform-function to apply to the value (currently only "keys" is supported) ## Examples: @@ -67,3 +69,15 @@ functions: events: - timer: cron(0 * * * *) # <-- Resolved ``` + +This prints the provider name: + +```bash +sls print --path provider --format text +``` + +And this prints all function names: + +```bash +sls print --path functions --transform keys --format text +``` diff --git a/docs/providers/azure/examples/hello-world/node/README.md b/docs/providers/azure/examples/hello-world/node/README.md index 22fc16bb826..9c18f181873 100644 --- a/docs/providers/azure/examples/hello-world/node/README.md +++ b/docs/providers/azure/examples/hello-world/node/README.md @@ -39,4 +39,4 @@ In your terminal window you should see the response from azure } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/azure/guide/functions.md b/docs/providers/azure/guide/functions.md index 537d17c113e..699eb6fd99e 100644 --- a/docs/providers/azure/guide/functions.md +++ b/docs/providers/azure/guide/functions.md @@ -62,3 +62,22 @@ functions: functionThree: handler: handler.functionThree ``` + +You can specify an array of functions, which is useful if you separate your functions in to different files: + +```yml +# serverless.yml +... + +functions: + - ${file(./foo-functions.yml)} + - ${file(./bar-functions.yml)} +``` + +```yml +# foo-functions.yml +getFoo: + handler: handler.foo +deleteFoo: + handler: handler.foo +``` diff --git a/docs/providers/azure/guide/intro.md b/docs/providers/azure/guide/intro.md index f212424a91b..f33a846c772 100644 --- a/docs/providers/azure/guide/intro.md +++ b/docs/providers/azure/guide/intro.md @@ -78,7 +78,7 @@ functions: # Your "Functions" x-azure-settings: name: req methods: - - POST + - post route: /users/create usersDelete: events: @@ -86,7 +86,7 @@ functions: # Your "Functions" x-azure-settings: name: req methods: - - DELETE + - delete route: /users/delete ``` diff --git a/docs/providers/azure/guide/plugins.md b/docs/providers/azure/guide/plugins.md index b80c6df001c..5263fa3b97a 100644 --- a/docs/providers/azure/guide/plugins.md +++ b/docs/providers/azure/guide/plugins.md @@ -42,11 +42,25 @@ do this by adding the name of the Plugin to the `plugins` section in the plugins: - custom-serverless-plugin ``` +The `plugins` section supports two formats: -Plugins might want to add extra information which should be accessible to -Serverless. The `custom` section in the `serverless.yml` file is the place where -you can add necessary configurations for your plugins (the plugins -author/documentation will tell you if you need to add anything there): +Array object: +```yml +plugins: + - plugin1 + - plugin2 +``` + +Enhanced plugins object: +```yml +plugins: + localPath: './custom_serverless_plugins' + modules: + - plugin1 + - plugin2 +``` + +Plugins might want to add extra information which should be accessible to Serverless. The `custom` section in the `serverless.yml` file is the place where you can add necessary configurations for your plugins (the plugins author / documentation will tell you if you need to add anything there): ```yml plugins: @@ -58,18 +72,28 @@ custom: ## Service local plugin -If you are working on a plugin or have a plugin that is just designed for one -project you can add them to the `.serverless_plugins` directory at the root of -your service, and in the `plugins` array in `serverless.yml`. +If you are working on a plugin or have a plugin that is just designed for one project they can be loaded from the local folder. Local plugins can be added in the `plugins` array in `serverless.yml`. + +By default local plugins can be added to the `.serverless_plugins` directory at the root of your service, and in the `plugins` array in `serverless.yml`. +```yml +plugins: + - custom-serverless-plugin +``` + +Local plugins folder can be changed by enhancing `plugins` object: +```yml +plugins: + localPath: './custom_serverless_plugins' + modules: + - custom-serverless-plugin +``` +The `custom-serverless-plugin` will be loaded from the `custom_serverless_plugins` directory at the root of your service. If the `localPath` is not provided or empty `.serverless_plugins` directory will be taken as the `localPath`. -The plugin will be loaded based on being named `custom-serverless-plugin.js` or -`custom-serverless-plugin\index.js` in the root of `.serverless_plugins` folder. +The plugin will be loaded based on being named `custom-serverless-plugin.js` or `custom-serverless-plugin\index.js` in the root of `localPath` folder (`.serverless_plugins` by default). ### Load Order -Keep in mind that the order you define your plugins matters. When Serverless -loads all the core plugins and then the custom plugins in the order you've -defined them. +Keep in mind that the order you define your plugins matters. When Serverless loads all the core plugins and then the custom plugins in the order you've defined them. ```yml # serverless.yml diff --git a/docs/providers/azure/guide/quick-start.md b/docs/providers/azure/guide/quick-start.md index 2e51d831b11..2665d0682ed 100644 --- a/docs/providers/azure/guide/quick-start.md +++ b/docs/providers/azure/guide/quick-start.md @@ -20,7 +20,7 @@ layout: Doc ## Create a new service Create a new service using the Node.js template, specifying a unique name and an -optional path for your service. +optional path for your service. Make sure you enter a globally unique name for the `--name` argument. ```bash $ serverless create --template azure-nodejs --path my-service --name my-unique-name diff --git a/docs/providers/fn/README.md b/docs/providers/fn/README.md new file mode 100644 index 00000000000..c263b37f2ea --- /dev/null +++ b/docs/providers/fn/README.md @@ -0,0 +1,73 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/) + + +# Fn Provider Documentation + +Welcome to the Serverless Fn documentation! + +If you have any questions, [search the forums](https://forum.serverless.com?utm_source=framework-docs) or [start your own thread](https://forum.serverless.com?utm_source=framework-docs) + + diff --git a/docs/providers/fn/cli-reference/README.md b/docs/providers/fn/cli-reference/README.md new file mode 100644 index 00000000000..100b43b03c2 --- /dev/null +++ b/docs/providers/fn/cli-reference/README.md @@ -0,0 +1,15 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/) + + +# Serverless Fn CLI Reference + +Welcome to the Serverless Fn CLI Reference! Please select a section on the left to get started. + +If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](http://forum.serverless.com/). diff --git a/docs/providers/fn/cli-reference/create.md b/docs/providers/fn/cli-reference/create.md new file mode 100644 index 00000000000..ebe93c90b04 --- /dev/null +++ b/docs/providers/fn/cli-reference/create.md @@ -0,0 +1,84 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/create) + + +# Fn - Create + +Creates a new Serverless service in the current working directory based on the provided template. + +**Create service in current working directory:** + +```bash +serverless create --template fn-nodejs +``` + +```bash +serverless create --template fn-go +``` + +**Create service in new folder:** + +```bash +serverless create --template fn-nodejs --path my-service +``` + +```bash +serverless create --template fn-nodejs --path my-service +``` + +## Options +- `--template` or `-t` The name of one of the available templates. **Required if --template-url and --template-path are not present**. +- `--template-url` or `-u` The name of one of the available templates. **Required if --template and --template-path are not present**. +- `--template-path` The local path of your template. **Required if --template and --template-url are not present**. +- `--path` or `-p` The path where the service should be created. +- `--name` or `-n` the name of the service in `serverless.yml`. + +## Provided lifecycle events +- `create:create` + +## Available Templates for Fn + +To see a list of available templates run `serverless create --help` + +These are the current available templates for Fn: + +- fn-nodejs +- fn-go + +## Examples + +### Creating a new Serverless service + +```bash +serverless create --template fn-nodejs --name my-special-service +``` + +This example will generate scaffolding for a service with `Fn` as a provider and `nodejs` as runtime. The scaffolding will be generated in the current working directory. + +The provider which is used for deployment later on is Fn. + +### Creating a named service in a (new) directory + +```bash +serverless create --template fn-nodejs --path my-new-service +``` + +This example will generate scaffolding for a service with `Fn` as a provider and `nodejs` as runtime. The scaffolding will be generated in the `my-new-service` directory. This directory will be created if not present. Otherwise Serverless will use the already present directory. + +Additionally Serverless will rename the service according to the path you provide. In this example the service will be renamed to `my-new-service`. + +### Creating a new service using a local template + +```bash +serverless create --template-path path/to/my/template/folder --path path/to/my/service --name my-new-service +``` + +This will copy the `path/to/my/template/folder` folder into `path/to/my/service` and rename the service to `my-new-service`. diff --git a/docs/providers/fn/cli-reference/deploy.md b/docs/providers/fn/cli-reference/deploy.md new file mode 100644 index 00000000000..1e1f632c6a8 --- /dev/null +++ b/docs/providers/fn/cli-reference/deploy.md @@ -0,0 +1,31 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/deploy) + + +# Fn - Deploy + +The `sls deploy` command deploys your entire service via the Fn API. Run this command when you have made service changes (i.e., you edited `serverless.yml`). + +Use `serverless deploy function -f my-function` when you have made code changes and you want to quickly upload your updated code to your Fn server or cluster. + +```bash +serverless deploy +``` + +This is the simplest deployment usage possible. With this command Serverless will deploy your service to the configured Fn server. + +## Options +- `--verbose` or `-v` Shows all stack events during deployment, and display any Stack Output. +- `--function` or `-f` Invoke `deploy function` (see above). Convenience shortcut + +## Provided lifecycle events +- `deploy:deploy` +- `deploy:function:deploy` diff --git a/docs/providers/fn/cli-reference/info.md b/docs/providers/fn/cli-reference/info.md new file mode 100644 index 00000000000..8288ce57ca6 --- /dev/null +++ b/docs/providers/fn/cli-reference/info.md @@ -0,0 +1,71 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/info) + + +# Fn - Info + +Displays information about the deployed service. + +```bash +serverless info +``` + +## Provided lifecycle events +- `info:info` + +## Examples + +On Fn the info plugin uses the Fn API to gather the necessary +information about deployed functions and service. See the example +below for an example output. + +**Example:** + +```bash +$ serverless info +{ + "name": "boom", + "created_at": "2018-04-23T18:50:17.755Z", + "updated_at": "2018-04-23T18:50:17.755Z" +} +{ + "path": "/boomer", + "image": "someuser/hello:1.0.28", + "memory": 256, + "cpus": "", + "type": "sync", + "format": "http", + "config": { + "boom": "Hello", + "password": "green" + }, + "timeout": 30, + "idle_timeout": 45, + "created_at": "2018-04-23T18:50:17.757Z", + "updated_at": "2018-04-23T18:50:17.757Z" +} +{ + "path": "/hi", + "image": "someuser/hi:1.0.23", + "memory": 128, + "cpus": "", + "type": "sync", + "format": "json", + "config": { + "something": "important", + }, + "timeout": 30, + "idle_timeout": 30, + "created_at": "2018-04-23T18:50:21.691Z", + "updated_at": "2018-04-23T18:50:21.691Z" +} + +``` diff --git a/docs/providers/fn/cli-reference/invoke.md b/docs/providers/fn/cli-reference/invoke.md new file mode 100644 index 00000000000..a3bfdf030c6 --- /dev/null +++ b/docs/providers/fn/cli-reference/invoke.md @@ -0,0 +1,62 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/invoke) + + +# Fn - Invoke + +Invokes deployed function. It allows to send event data to the function, read logs and display other important information of the function invocation. + +```bash +serverless invoke --function functionName +``` + +## Options +- `--function` or `-f` The name of the function in your service that you want to invoke. **Required**. +- `--data` or `-d` String data to be passed as an event to your function. By default data is read from standard input. +- `--path` or `-p` The path to a json file with input data to be passed to the invoked function. This path is relative to the root directory of the service. +- `--log` or `-l` If set to `true`, it will output logging data of the invocation. Default is `false`. + +## Provided lifecycle events +- `invoke:invoke` + +## Examples + +### Fn + +```bash +serverless invoke --function functionName +``` + +This example will invoke your deployed function on the configured Fn Api Url +endpoint. This will output the result of the invocation in your terminal. + +#### Function invocation with data + +```bash +serverless invoke --function functionName --data '{"name": "Bernie"}' +``` + +#### Function invocation with logging + +```bash +serverless invoke --function functionName --log +``` + +Just like the first example, but will also outputs logging information about your invocation. + +#### Function invocation with data passing + +```bash +serverless invoke --function functionName --path lib/data.json +``` + +This example will pass the json data in the `lib/data.json` file (relative to the root of the service) while invoking +the specified/deployed function. diff --git a/docs/providers/fn/cli-reference/logs.md b/docs/providers/fn/cli-reference/logs.md new file mode 100644 index 00000000000..19d89b80e4e --- /dev/null +++ b/docs/providers/fn/cli-reference/logs.md @@ -0,0 +1,30 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/logs) + + +# Fn - Logs + +Lets you watch the logs of a specific function. + +```bash +serverless logs -f hello +``` + +## Options + +- `--function` or `-f` The function you want to fetch the logs for. **Required** + +## Examples + +```bash +serverless logs -f hello +``` +This will fetch the logs for hello for the most recent calls to it. diff --git a/docs/providers/fn/cli-reference/plugin-install.md b/docs/providers/fn/cli-reference/plugin-install.md new file mode 100644 index 00000000000..432922f8a6d --- /dev/null +++ b/docs/providers/fn/cli-reference/plugin-install.md @@ -0,0 +1,42 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/plugin-install) + + +# Plugin Install + +Install a Serverless plugin and add it to the services `plugins` array. By default, a latest version is installed. +If you want a specific version, you can specify `@` as name option. + +**Note:** You might want to change the order of the plugin in the services `plugins` array. + +```bash +serverless plugin install --name pluginName +``` + +## Options +- `--name` or `-n` The plugins name. **Required**. + +## Provided lifecycle events +- `plugin:install:install` + +## Examples + +### Install the `serverless-webpack` plugin + +```bash +serverless plugin install --name serverless-webpack +``` + +### Install a specific version + +```bash +serverless plugin install --name serverless-webpack@3.0.0-rc.2 +``` diff --git a/docs/providers/fn/cli-reference/plugin-list.md b/docs/providers/fn/cli-reference/plugin-list.md new file mode 100644 index 00000000000..d8282edbf49 --- /dev/null +++ b/docs/providers/fn/cli-reference/plugin-list.md @@ -0,0 +1,25 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/plugin-list) + + +# Plugin List + +List all available plugins on the terminal. Connected to the [Serverless plugin registry](https://github.com/serverless/plugins). + +```bash +serverless plugin list +``` + +## Options +- *None* + +## Provided lifecycle events +- `plugin:list:list` diff --git a/docs/providers/fn/cli-reference/plugin-search.md b/docs/providers/fn/cli-reference/plugin-search.md new file mode 100644 index 00000000000..c73b944f9d1 --- /dev/null +++ b/docs/providers/fn/cli-reference/plugin-search.md @@ -0,0 +1,33 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/plugin-search) + + +# Plugin Search + +Search for a specific plugin based on a search query. Connected to the [Serverless plugin registry](https://github.com/serverless/plugins). + +```bash +serverless plugin search --query query +``` + +## Options +- `--query` or `-q` The query you want to use for your search. **Required**. + +## Provided lifecycle events +- `plugin:search:search` + +## Examples + +### Search for a `sqs` plugin + +```bash +serverless plugin search --query sqs +``` diff --git a/docs/providers/fn/cli-reference/plugin-uninstall.md b/docs/providers/fn/cli-reference/plugin-uninstall.md new file mode 100644 index 00000000000..f19402e9bbd --- /dev/null +++ b/docs/providers/fn/cli-reference/plugin-uninstall.md @@ -0,0 +1,33 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/plugin-uninstall) + + +# Plugin Uninstall + +Uninstall a Serverless plugin and remove it from the services `plugins` array. + +```bash +serverless plugin uninstall --name pluginName +``` + +## Options +- `--name` or `-n` The plugins name. **Required**. + +## Provided lifecycle events +- `plugin:uninstall:uninstall` + +## Examples + +### Remove the `serverless-webpack` plugin + +```bash +serverless plugin uninstall --name serverless-webpack +``` diff --git a/docs/providers/fn/cli-reference/remove.md b/docs/providers/fn/cli-reference/remove.md new file mode 100644 index 00000000000..0738d220da8 --- /dev/null +++ b/docs/providers/fn/cli-reference/remove.md @@ -0,0 +1,24 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/cli-reference/remove) + + +# Fn - Remove + +The `sls remove` command will remove the deployed service, defined in your current working directory, from the provider. + +```bash +serverless remove +``` + +It will remove the Fn Function functions from your Fn server. + +## Provided lifecycle events +- `remove:remove` diff --git a/docs/providers/fn/events/README.md b/docs/providers/fn/events/README.md new file mode 100644 index 00000000000..d87c6c69820 --- /dev/null +++ b/docs/providers/fn/events/README.md @@ -0,0 +1,18 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/events/) + + +# Serverless Fn Events + +Welcome to the Serverless Fn Events Glossary! + +Please select a section on the left to get started. + +If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](http://forum.serverless.com/) + diff --git a/docs/providers/fn/events/http.md b/docs/providers/fn/events/http.md new file mode 100644 index 00000000000..6d37b4ad970 --- /dev/null +++ b/docs/providers/fn/events/http.md @@ -0,0 +1,37 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/events/http) + + +# Fn HTTP Events + +The first type of events that you can create in Fn are HTTP events. + +When creating HTTP events you just call the endpoints you have made with http. + +## Serverless Yaml + +When creating a service your serverless yaml will define which endpoint is used for your functions. + +```yaml +service: hello-world + +functions: # Your "Functions" + hello: + name: hi + version: 0.0.1 + runtime: go + events: + - http: + path: /hello +``` + +The events section in the yaml above makes it so that the Function hi will be +used for request to the `FN_API_URL/r/hello-world/hello` diff --git a/docs/providers/fn/guide/README.md b/docs/providers/fn/guide/README.md new file mode 100644 index 00000000000..3a725c7c133 --- /dev/null +++ b/docs/providers/fn/guide/README.md @@ -0,0 +1,17 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/guide/) + + +# Serverless Fn Guide + +Welcome to the Serverless Fn Guide! + +Get started with the **[Introduction to the Serverless framework](./intro.md)** + +If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](http://forum.serverless.com/) diff --git a/docs/providers/fn/guide/debugging.md b/docs/providers/fn/guide/debugging.md new file mode 100644 index 00000000000..ba10ed4c64e --- /dev/null +++ b/docs/providers/fn/guide/debugging.md @@ -0,0 +1,78 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/guide/debugging) + + +# Fn - Debugging + +How can we debug errors in our Fn functions? + +Let's imagine that we have deployed the following Nodejs code as a Fn function using Serverless: + +```javascript +const fdk = require('@fnproject/fdk'); + +fdk.handle((input) => { + input = JSON.parse(input) + let name = 'World'; + if (input.name) { + name = input.name; + } + const response = { message: `Hello ${name}` }; + console.error(`I show up in the logs name was: ${name}`); + return response; +}); +``` + +And its corresponding Serverless YAML file: + +```yml +service: + name: hello-world + +provider: + name: fn + +plugins: + - serverless-fn +functions: + hello: + name: hello + version: 0.0.1 + runtime: node + format: json + events: + - http: + path: /hello +``` + +Let's invoke correctly that function + +``` +serverless invoke --function hello --data '{"name":"Bob"}' -l + +# Output +Serverless: Calling Function: hello +{ message: 'Hello Bob' } +I show up in the logs name was: Bob +``` + +If we were to call the above function with an incorrect json data you would get `Hello World` back instead of `Hello Bob` +In order to debug this since there is nothing fatal happening and no stack trace is appearing in the logs you would need to +add more console.error calls until you figure it out. If there were a major error +that caused a stacktrace and the entire function to fail then you could easily call + +``` +serverless logs --function hello +``` + +That would print any stack traces that may have gone to stderr + +This is a very basic example of debugging a Fn function, but it should hopefully highlight the basic principles. Obviously, in production environments, you would want to have more formal and sophisticated error handling built into your code. diff --git a/docs/providers/fn/guide/deploying.md b/docs/providers/fn/guide/deploying.md new file mode 100644 index 00000000000..40463c8540e --- /dev/null +++ b/docs/providers/fn/guide/deploying.md @@ -0,0 +1,62 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/guide/deploying) + + +# Fn - Deploying + +The Serverless Framework was designed to provision your Fn Functions and Events. It does this via a couple of methods designed for different types of deployments. + +## Deploy All + +This is the main method for doing deployments with the Serverless Framework: + +```bash +serverless deploy +``` + +Use this method when you have updated your Function, Event or Resource configuration in `serverless.yml` and you want to deploy that change (or multiple changes at the same time) to your Fn cluster. + +### How It Works + +The Serverless Framework translates all syntax in `serverless.yml` to [Fn](https://github.com/fnproject/fn) calls to provision your Functions. + +For each function in your `serverless.yml` file, Fn will create an Fn Function. + +For example, let's take the following example `serverless.yml` file: + +```yaml + +service: hello-world + +functions: # Your "Functions" + hello: + name: hi + version: 0.0.1 + runtime: go + events: + - http: + path: /hello + +``` + +When deploying that file FN will provide you with one endpoint that you can hit at: `FN_API_URL/r/hello-world/hello` + +## Deploy Function + +This deployment method updates or deploys a single function. It performs the platform API call to deploy your package without the other resources. It is much faster than redeploying your whole service each time. + +```bash +serverless deploy --function myFunction +``` + +### Tips + +Check out the [deploy command docs](../cli-reference/deploy.md) for all details and options. diff --git a/docs/providers/fn/guide/events.md b/docs/providers/fn/guide/events.md new file mode 100644 index 00000000000..f9910e4910f --- /dev/null +++ b/docs/providers/fn/guide/events.md @@ -0,0 +1,19 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/guide/events) + + +# Fn - Events + +Simply put, events are the things that trigger your functions to run. + +If you are using Fn as your provider, all `events` in the service are anything in Fn that can trigger your Functions, like HTTP endpoints or message queues. + +[View the Fn events section for a list of supported events](../events) diff --git a/docs/providers/fn/guide/installation.md b/docs/providers/fn/guide/installation.md new file mode 100644 index 00000000000..d3803b8d096 --- /dev/null +++ b/docs/providers/fn/guide/installation.md @@ -0,0 +1,47 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/guide/installation) + + +# Fn - Installation + +## Installing Fn + +[Fn](https://github.com/fnproject/fn) can be run in its simplest form as a docker container: + +``` +docker run --name fnserver --rm -i -v $(pwd)/data:/app/data -v \ +/var/run/docker.sock:/var/run/docker.sock --privileged -p 8080:8080 \ +--entrypoint ./fnserver fnproject/fnserver +``` + +For more advanced and robust install options see: [Operating FN](https://github.com/fnproject/fn/blob/master/docs/README.md#for-operators) + +## Installing the Serverless Framework + +Next, install the Serverless Framework via [npm](https://npmjs.org) which was already installed when you installed Node.js. + +Open up a terminal and type `npm install -g serverless` to install Serverless. + +```bash +npm install -g serverless +``` + +Once the installation process is done you can verify that Serverless is installed successfully by running the following command in your terminal: + +```bash +serverless +``` + +To see which version of serverless you have installed run: + +```bash +serverless --version +``` diff --git a/docs/providers/fn/guide/intro.md b/docs/providers/fn/guide/intro.md new file mode 100644 index 00000000000..a18a032c52c --- /dev/null +++ b/docs/providers/fn/guide/intro.md @@ -0,0 +1,62 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/guide/intro) + + +# Fn - Introduction + +The Serverless Framework helps you develop and deploy serverless applications using Fn. It's a CLI that offers structure, automation and best practices out-of-the-box, allowing you to focus on building sophisticated, event-driven, serverless architectures, comprised of [Functions](#functions) and [Events](#events). + +The Serverless Framework is different than other application frameworks because: +* It manages your code as well as your infrastructure +* It supports multiple languages (Node.js, Python, Ruby, Go) + +## Core Concepts + +Here are the Serverless Framework's main concepts and how they pertain to Fn. + +### Functions + +A Function is a [Fn Function](http://fnproject.io/). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: + +* *Saving a user to the database* +* *Processing a file in a database* +* *Performing a scheduled task* (To be added in newer versions) + +You can perform multiple jobs in your code, but we don't recommend doing that without good reason. Separation of concerns is best and the Framework is designed to help you easily develop and deploy Functions, as well as manage lots of them. + +### Events + +Anything that triggers an Fn Event to execute is regarded by the Framework as an **Event**. Events are platform events on Fn such as: + +* *An API Gateway HTTP endpoint (e.g., for a REST API)* +* *A Kafka queue message (e.g., a message)* +* *A scheduled timer (e.g., run every 5 minutes)* (To be added in newer versions) + +### Services + +A **Service** is the Serverless Framework's unit of organization. You can think of it as a project file, though you can have multiple services for a single application. It's where you define your Functions and the Events that trigger them, all in one file entitled `serverless.yml` (or `serverless.json` or `serverless.js`). It looks like this: + +```yml +# serverless.yml + +service: hello-world + +functions: # Your "Functions" + hello: + name: hi + version: 0.0.1 + runtime: go + events: + - http: + path: /hello +``` + +When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` is deployed at once. diff --git a/docs/providers/fn/guide/quick-start.md b/docs/providers/fn/guide/quick-start.md new file mode 100644 index 00000000000..f5949e9d3eb --- /dev/null +++ b/docs/providers/fn/guide/quick-start.md @@ -0,0 +1,73 @@ + + +# Fn - Quick Start + +## Pre-requisites + +1. Node.js `v6.5.0` or later. +2. Serverless CLI `v1.20` or later. You can run +`npm install -g serverless` to install it. +3. Install Fn & Dependencies(./installation.md). + +## Create a new service + +Create a new service using the Nodejs template, specifying a unique name and an optional path for your service. + +```bash +# Create a new Serverless Service/Project +$ serverless create --template fn-nodejs --path new-project +# Change into the newly created directory +$ cd new-project +# Install npm dependencies +$ npm install +``` + +## Deploy, test and diagnose your service + +1. **Deploy the Service** + + Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. + + ```bash + serverless deploy -v + ``` + +2. **Deploy the Function** + + Use this to quickly upload and overwrite your function code, allowing you to develop faster. + + ```bash + serverless deploy -f hello + ``` + +3. **Invoke the Function** + + Invokes the Function and returns results. + + ```bash + $ serverless invoke --function hello --data '{"name":"Bob"}' -l + Serverless: Calling Function: hello + { message: 'Hello Bob' } + I show up in the logs name was: Bob + ``` + +4. **Fetch the Function Logs** + + Open up a separate tab in your console and view logs for a specific Function using this command. + ```bash + serverless logs -f hello + ``` + +## Cleanup + +If at any point, you no longer need your service, you can run the following command to remove the Functions, Events and Resources that were created. + +```bash +serverless remove +``` diff --git a/docs/providers/fn/guide/workflow.md b/docs/providers/fn/guide/workflow.md new file mode 100644 index 00000000000..00c31597d21 --- /dev/null +++ b/docs/providers/fn/guide/workflow.md @@ -0,0 +1,65 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/fn/guide/workflow) + + +# Fn - Workflow + +Intro. Quick recommendations and tips for various processes. + +### Development Workflow + +1. Write your functions +2. Use `serverless deploy` only when you've made changes to `serverless.yml` and in CI/CD systems. +3. Use `serverless deploy -f myFunction` to rapidly deploy changes when you are working on a specific Fn Function. +4. Use `serverless invoke -f myFunction -l` to test your Fn Functions. +6. Write tests to run locally. + +### Larger Projects +* Break your application/project into multiple Serverless Services. +* Model your Serverless Services around Data Models or Workflows. +* Keep the Functions and Resources in your Serverless Services to a minimum. + +## Cheat Sheet +A handy list of commands to use when developing with the Serverless Framework. + +##### Create A Service: +Creates a new Service + +``` +serverless create -p [SERVICE NAME] -t fn-nodejs +``` +``` +serverless create -p [SERVICE NAME] -t fn-go +``` + +##### Deploy All +Use this when you have made changes to your Functions, Events or Resources in `serverless.yml` or you simply want to deploy all changes within your Service at the same time. +``` +serverless deploy +``` + +##### Deploy Function +Use this to quickly overwrite your Fn Functinos, allowing you to develop faster. +``` +serverless deploy -f [FUNCTION NAME] +``` + +##### Invoke Function +Invokes an Fn Function and returns logs. +``` +serverless invoke -f [FUNCTION NAME] -l +``` + +##### Streaming Logs +Open up a separate tab in your console and stream all logs for a specific Function using this command. +``` +serverless logs -f [FUNCTION NAME] +``` diff --git a/docs/providers/google/README.md b/docs/providers/google/README.md index e355f2ec856..521bc3cf997 100644 --- a/docs/providers/google/README.md +++ b/docs/providers/google/README.md @@ -12,7 +12,7 @@ layout: Doc Welcome to the Serverless Google Cloud Functions documentation! -If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](https://forum.serverless.com/) +If you have any questions, [search the forums](https://forum.serverless.com?utm_source=framework-docs) or [start your own thread](https://forum.serverless.com?utm_source=framework-docs) **Note:** [Google Cloud system credentials](./guide/credentials.md) are required for using the CLI. diff --git a/docs/providers/google/cli-reference/print.md b/docs/providers/google/cli-reference/print.md index c71b0410a8b..bfeb5427d5f 100644 --- a/docs/providers/google/cli-reference/print.md +++ b/docs/providers/google/cli-reference/print.md @@ -26,7 +26,9 @@ serverless print ## Options -- None +- `format` Print configuration in given format ("yaml", "json", "text"). Default: yaml +- `path` Period-separated path to print a sub-value (eg: "provider.name") +- `transform` Transform-function to apply to the value (currently only "keys" is supported) ## Examples: @@ -78,3 +80,15 @@ functions: eventType: providers/cloud.pubsub/eventTypes/topics.publish resource: projects/*/topics/my-topic # <-- Resolved. ``` + +This prints the provider name: + +```bash +sls print --path provider --format text +``` + +And this prints all function names: + +```bash +sls print --path functions --transform keys --format text +``` diff --git a/docs/providers/google/examples/hello-world/node/README.md b/docs/providers/google/examples/hello-world/node/README.md index 51f5af2044b..5802b97794d 100644 --- a/docs/providers/google/examples/hello-world/node/README.md +++ b/docs/providers/google/examples/hello-world/node/README.md @@ -35,4 +35,4 @@ Update the `credentials` and your `project` property in the `serverless.yml` fil In your terminal window you should see a response from the Google Cloud -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/google/guide/functions.md b/docs/providers/google/guide/functions.md index 587bb7f4942..47ac6f7899d 100644 --- a/docs/providers/google/guide/functions.md +++ b/docs/providers/google/guide/functions.md @@ -35,6 +35,26 @@ functions: - http: path ``` +You can specify an array of functions, which is useful if you separate your functions in to different files: + +```yml +# serverless.yml +... + +functions: + - ${file(./foo-functions.yml)} + - ${file(./bar-functions.yml)} +``` + +```yml +# foo-functions.yml +getFoo: + handler: handler.foo +deleteFoo: + handler: handler.foo +``` + + ## Handler The `handler` property should be the function name you've exported in your entrypoint file. @@ -96,3 +116,43 @@ exports.event = (event, callback) => { callback(); }; ``` + +## Labels + +Google Cloud Platform supports [labels to assist in organizing resources](https://cloud.google.com/resource-manager/docs/creating-managing-labels). +Serverless allows you to define labels to be applied to functions upon deploy. +Labels are defined as key-value pairs. + +Labels can be applied globally, to all functions in your configuration file, and to specific functions. + +```yml +# serverless.yml + +provider: + name: google + labels: + application: Serverless Example + +functions: + first: + handler: httpFirst + events: + - http: path + labels: + team: GCF Team + second: + handler: httpSecond + events: + - http: path + labels: + application: Serverless Example - Documentation +``` + +With the above configuration the `httpFirst` function would have two labels applied, `application` and `team`. +The value of the `application` label would be `Serverless Example`, the value of the `team` label would be `GCF Team`. + +The `httpSecond` function would have only one label applied, `application`, and it would have a value of `Serverless Example - Documentation`. + +Labels defined under the `provider` object are applied to all functions in the file. +Labels defined under specific functions only apply to that function. +The labels defined under a function's object will override global labels for that function. diff --git a/docs/providers/google/guide/intro.md b/docs/providers/google/guide/intro.md index 4fbf37d6a16..6f03ea68423 100644 --- a/docs/providers/google/guide/intro.md +++ b/docs/providers/google/guide/intro.md @@ -25,7 +25,7 @@ Here are the Framework's main concepts and how they pertain to Google Cloud Func ### Functions -A Function is an [Google Cloud Function](https://cloud.google.com/functions/). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: +A Function is a [Google Cloud Function](https://cloud.google.com/functions/). It's an independent unit of deployment, like a microservice. It's merely code, deployed in the cloud, that is most often written to perform a single job such as: - *Saving a user to the database* - *Processing a file in a database* diff --git a/docs/providers/google/guide/plugins.md b/docs/providers/google/guide/plugins.md index aaf032b125e..78c8a6e24ed 100644 --- a/docs/providers/google/guide/plugins.md +++ b/docs/providers/google/guide/plugins.md @@ -33,6 +33,23 @@ We need to tell Serverless that we want to use the plugin inside our service. We plugins: - custom-serverless-plugin ``` +The `plugins` section supports two formats: + +Array object: +```yml +plugins: + - plugin1 + - plugin2 +``` + +Enhanced plugins object: +```yml +plugins: + localPath: './custom_serverless_plugins' + modules: + - plugin1 + - plugin2 +``` Plugins might want to add extra information which should be accessible to Serverless. The `custom` section in the `serverless.yml` file is the place where you can add necessary configurations for your plugins (the plugins author / documentation will tell you if you need to add anything there): @@ -46,9 +63,24 @@ custom: ## Service local plugin -If you are working on a plugin or have a plugin that is just designed for one project you can add them to the `.serverless_plugins` directory at the root of your service, and in the `plugins` array in `serverless.yml`. +If you are working on a plugin or have a plugin that is just designed for one project they can be loaded from the local folder. Local plugins can be added in the `plugins` array in `serverless.yml`. + +By default local plugins can be added to the `.serverless_plugins` directory at the root of your service, and in the `plugins` array in `serverless.yml`. +```yml +plugins: + - custom-serverless-plugin +``` + +Local plugins folder can be changed by enhancing `plugins` object: +```yml +plugins: + localPath: './custom_serverless_plugins' + modules: + - custom-serverless-plugin +``` +The `custom-serverless-plugin` will be loaded from the `custom_serverless_plugins` directory at the root of your service. If the `localPath` is not provided or empty `.serverless_plugins` directory will be taken as the `localPath`. -The plugin will be loaded based on being named `custom-serverless-plugin.js` or `custom-serverless-plugin\index.js` in the root of `.serverless_plugins` folder. +The plugin will be loaded based on being named `custom-serverless-plugin.js` or `custom-serverless-plugin\index.js` in the root of `localPath` folder (`.serverless_plugins` by default). ### Load Order diff --git a/docs/providers/kubeless/README.md b/docs/providers/kubeless/README.md index aaf72767710..49489494aae 100644 --- a/docs/providers/kubeless/README.md +++ b/docs/providers/kubeless/README.md @@ -12,7 +12,7 @@ layout: Doc Welcome to the Serverless Kubeless documentation! -If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](https://gitter.im/serverless/serverless) +If you have any questions, [search the forums](https://forum.serverless.com?utm_source=framework-docs) or [start your own thread](https://forum.serverless.com?utm_source=framework-docs)
diff --git a/docs/providers/kubeless/events/http.md b/docs/providers/kubeless/events/http.md index 8196f46f2b4..3bb72182ed2 100644 --- a/docs/providers/kubeless/events/http.md +++ b/docs/providers/kubeless/events/http.md @@ -80,10 +80,10 @@ functions: ``` -If the events HTTP definitions contain a `path` attribute, when deploying this Serverless YAML definition, Kubeless will create the needed [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) rules to redirect each of the requests to the right service: +If the events HTTP definitions contain a `path` attribute, when deploying this Serverless YAML definition, Kubeless will create the needed [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) rules to redirect each of the requests to the right service. You will need to create an [Ingress Controller](https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers) to make use of your Ingress rule(s): ``` kubectl get ingress NAME HOSTS ADDRESS PORTS AGE ingress-1506350705094 192.168.99.100.nip.io 80 28s -``` \ No newline at end of file +``` diff --git a/docs/providers/kubeless/events/pubsub.md b/docs/providers/kubeless/events/pubsub.md index d3ef2065b74..cd2f707aeef 100644 --- a/docs/providers/kubeless/events/pubsub.md +++ b/docs/providers/kubeless/events/pubsub.md @@ -49,4 +49,4 @@ serverless logs -f hello hello world! ``` -You can install the Kubeless CLI tool following the [../guide/installation](installation guide). +You can install the Kubeless CLI tool following the [installation guide](../guide/installation.md). diff --git a/docs/providers/kubeless/guide/debugging.md b/docs/providers/kubeless/guide/debugging.md index 1822efdeeac..4dc5b4eb312 100644 --- a/docs/providers/kubeless/guide/debugging.md +++ b/docs/providers/kubeless/guide/debugging.md @@ -20,8 +20,8 @@ Let's imagine that we have deployed the following Python code as a Kubeless func import urllib2 import json -def find(request): - term = request.json["term"] +def find(event, context): + term = event['data']['term'] url = "https://feeds.capitalbikeshare.com/stations/stations.json" response = urllib2.urlopen(url) stations = json.loads(response.read()) @@ -126,7 +126,7 @@ Traceback (most recent call last): File "/kubeless.py", line 35, in handler return func(bottle.request) File "/kubeless/handler.py", line 5, in find - term = request.json["term"] + term = event['data']['term'] KeyError: 'term' 172.17.0.1 - - [25/Aug/2017:08:46:16 +0000] "POST / HTTP/1.1" 500 746 "" "" 0/6703 172.17.0.1 - - [25/Aug/2017:08:46:34 +0000] "GET /healthz HTTP/1.1" 200 2 "" "Go-http-client/1.1" 0/122 @@ -145,7 +145,7 @@ Traceback (most recent call last): File "/kubeless.py", line 35, in handler return func(bottle.request) File "/kubeless/handler.py", line 5, in find - term = request.json["term"] + term = event['data']['term'] KeyError: 'term' ``` diff --git a/docs/providers/kubeless/guide/functions.md b/docs/providers/kubeless/guide/functions.md index 8881110c865..7dbf3aea87f 100644 --- a/docs/providers/kubeless/guide/functions.md +++ b/docs/providers/kubeless/guide/functions.md @@ -85,6 +85,25 @@ functions: handler: handler.hello_two ``` +You can specify an array of functions, which is useful if you separate your functions in to different files: + +```yml +# serverless.yml +... + +functions: + - ${file(./foo-functions.yml)} + - ${file(./bar-functions.yml)} +``` + +```yml +# foo-functions.yml +getFoo: + handler: handler.foo +deleteFoo: + handler: handler.foo +``` + ## Runtimes The Kubeless provider plugin supports the following runtimes. diff --git a/docs/providers/kubeless/guide/quick-start.md b/docs/providers/kubeless/guide/quick-start.md index 141dc194f7a..bcbb80944bb 100644 --- a/docs/providers/kubeless/guide/quick-start.md +++ b/docs/providers/kubeless/guide/quick-start.md @@ -17,11 +17,11 @@ layout: Doc ## Create a new service -Create a new service using the Python template, specifying a unique name and an optional path for your service. +Create a new service using the NodeJS template, specifying a unique name and an optional path for your service. ```bash # Create a new Serverless Service/Project -$ serverless create --template kubeless-python --path new-project +$ serverless create --template kubeless-nodejs --path new-project # Change into the newly created directory $ cd new-project # Install npm dependencies diff --git a/docs/providers/openwhisk/README.md b/docs/providers/openwhisk/README.md index 73330b2b5f7..f6da72ed7ec 100644 --- a/docs/providers/openwhisk/README.md +++ b/docs/providers/openwhisk/README.md @@ -12,7 +12,7 @@ layout: Doc Welcome to the Serverless Apache OpenWhisk documentation! -If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](https://forum.serverless.com/) +If you have any questions, [search the forums](https://forum.serverless.com?utm_source=framework-docs) or [start your own thread](https://forum.serverless.com?utm_source=framework-docs) **Note:** [Apache OpenWhisk system credentials](./guide/credentials.md) are required for using serverless + openwhisk. diff --git a/docs/providers/openwhisk/cli-reference/print.md b/docs/providers/openwhisk/cli-reference/print.md index e10ced46519..b48882404f9 100644 --- a/docs/providers/openwhisk/cli-reference/print.md +++ b/docs/providers/openwhisk/cli-reference/print.md @@ -26,7 +26,9 @@ serverless print ## Options -- None +- `format` Print configuration in given format ("yaml", "json", "text"). Default: yaml +- `path` Period-separated path to print a sub-value (eg: "provider.name") +- `transform` Transform-function to apply to the value (currently only "keys" is supported) ## Examples: @@ -68,3 +70,15 @@ functions: events: - schedule: cron(0 * * * *) # <-- Resolved ``` + +This prints the provider name: + +```bash +sls print --path provider --format text +``` + +And this prints all function names: + +```bash +sls print --path functions --transform keys --format text +``` diff --git a/docs/providers/openwhisk/events/apigateway.md b/docs/providers/openwhisk/events/apigateway.md index 91192614724..e448ceb0141 100644 --- a/docs/providers/openwhisk/events/apigateway.md +++ b/docs/providers/openwhisk/events/apigateway.md @@ -124,3 +124,31 @@ HTTP event configuration supports the following parameters. **Note:** All HTTP endpoints defined in this manner have cross-site requests enabled for all source domains. + +### URL Path Parameters + +The API Gateway service [supports path parameters](https://github.com/apache/incubator-openwhisk/blob/master/docs/apigateway.md#exposing-multiple-web-actions) in user-defined HTTP paths. This allows functions to handle URL paths which include templated values, like resource identifiers. + +Path parameters are identified using the `{param_name}` format in the URL path. The API Gateway sends the full matched path value in the `__ow_path` field of the event parameters. + +```yaml +functions: + retrieve_users: + handler: users.get + events: + - http: + method: GET + path: /users/{id} + resp: http +``` + +This feature comes with the following restrictions: + +- *Path parameters are only supported when `resp` is configured as`http`.* +- *Individual path parameter values are not included as separate event parameters. Users have to manually parse values from the full `__ow_path` value.* + +### Security + +Functions exposed through the API Gateway service are automatically converted +into Web Actions during deployment. The framework [secures Web Actions for HTTP endpoints](https://github.com/apache/incubator-openwhisk/blob/master/docs/webactions.md#securing-web-actions) using the `require-whisk-auth` annotation. If the `require-whisk-auth` +annotation is manually configured, the existing annotation value is used, otherwise a random token is automatically generated. diff --git a/docs/providers/openwhisk/examples/hello-world/node/README.md b/docs/providers/openwhisk/examples/hello-world/node/README.md index f9a6d399900..c2d1ab8cee2 100644 --- a/docs/providers/openwhisk/examples/hello-world/node/README.md +++ b/docs/providers/openwhisk/examples/hello-world/node/README.md @@ -17,7 +17,7 @@ Make sure `serverless` is installed. [See installation guide](../../../guide/ins `serverless create --template openwhisk-nodejs --path myService` or `sls create --template openwhisk-nodejs --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. ## 2. Install Provider Plugin -`npm install -g serverless-openwhisk` followed by `npm install` in the service directory. +`npm install ` in the service directory. ## 3. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command @@ -35,4 +35,4 @@ In your terminal window you should see the response from Apache OpenWhisk } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/openwhisk/examples/hello-world/node/package.json b/docs/providers/openwhisk/examples/hello-world/node/package.json index d41fc10e851..c2c6bf950d4 100644 --- a/docs/providers/openwhisk/examples/hello-world/node/package.json +++ b/docs/providers/openwhisk/examples/hello-world/node/package.json @@ -2,8 +2,7 @@ "name": "serverless-openwhisk-hello-world", "version": "0.1.0", "description": "Hello World example for OpenWhisk provider with Serverless Framework.", - "scripts": { - "postinstall": "npm link serverless-openwhisk", - "test": "echo \"Error: no test specified\" && exit 1" + "devDependencies": { + "serverless-openwhisk": ">=0.13.0" } } diff --git a/docs/providers/openwhisk/examples/hello-world/php/README.md b/docs/providers/openwhisk/examples/hello-world/php/README.md index c254887bf06..944b0e6e3dc 100644 --- a/docs/providers/openwhisk/examples/hello-world/php/README.md +++ b/docs/providers/openwhisk/examples/hello-world/php/README.md @@ -17,7 +17,7 @@ Make sure `serverless` is installed. [See installation guide](../../../guide/ins `serverless create --template openwhisk-php --path myService` or `sls create --template openwhisk-php --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. ## 2. Install Provider Plugin -`npm install -g serverless-openwhisk` followed by `npm install` in the service directory. +Run `npm install` in the service directory. ## 3. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command @@ -35,4 +35,4 @@ In your terminal window you should see the response from Apache OpenWhisk } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/openwhisk/examples/hello-world/php/package.json b/docs/providers/openwhisk/examples/hello-world/php/package.json index d41fc10e851..c2c6bf950d4 100644 --- a/docs/providers/openwhisk/examples/hello-world/php/package.json +++ b/docs/providers/openwhisk/examples/hello-world/php/package.json @@ -2,8 +2,7 @@ "name": "serverless-openwhisk-hello-world", "version": "0.1.0", "description": "Hello World example for OpenWhisk provider with Serverless Framework.", - "scripts": { - "postinstall": "npm link serverless-openwhisk", - "test": "echo \"Error: no test specified\" && exit 1" + "devDependencies": { + "serverless-openwhisk": ">=0.13.0" } } diff --git a/docs/providers/openwhisk/examples/hello-world/python/README.md b/docs/providers/openwhisk/examples/hello-world/python/README.md index 1056a80600d..dd09ceed738 100644 --- a/docs/providers/openwhisk/examples/hello-world/python/README.md +++ b/docs/providers/openwhisk/examples/hello-world/python/README.md @@ -17,7 +17,7 @@ Make sure `serverless` is installed. [See installation guide](../../../guide/ins `serverless create --template openwhisk-python --path myService` or `sls create --template openwhisk-python --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. ## 2. Install Provider Plugin -`npm install -g serverless-openwhisk` followed by `npm install` in the service directory. +`npm install` in the service directory. ## 3. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command @@ -35,4 +35,4 @@ In your terminal window you should see the response from Apache OpenWhisk } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/openwhisk/examples/hello-world/python/package.json b/docs/providers/openwhisk/examples/hello-world/python/package.json index d41fc10e851..c2c6bf950d4 100644 --- a/docs/providers/openwhisk/examples/hello-world/python/package.json +++ b/docs/providers/openwhisk/examples/hello-world/python/package.json @@ -2,8 +2,7 @@ "name": "serverless-openwhisk-hello-world", "version": "0.1.0", "description": "Hello World example for OpenWhisk provider with Serverless Framework.", - "scripts": { - "postinstall": "npm link serverless-openwhisk", - "test": "echo \"Error: no test specified\" && exit 1" + "devDependencies": { + "serverless-openwhisk": ">=0.13.0" } } diff --git a/docs/providers/openwhisk/examples/hello-world/swift/README.md b/docs/providers/openwhisk/examples/hello-world/swift/README.md index 93dec3d3087..55904219f2b 100644 --- a/docs/providers/openwhisk/examples/hello-world/swift/README.md +++ b/docs/providers/openwhisk/examples/hello-world/swift/README.md @@ -17,7 +17,7 @@ Make sure `serverless` is installed. [See installation guide](../../../guide/ins `serverless create --template openwhisk-swift --path myService` or `sls create --template openwhisk-swift --path myService`, where 'myService' is a new folder to be created with template service files. Change directories into this new folder. ## 2. Install Provider Plugin -`npm install -g serverless-openwhisk` followed by `npm install` in the service directory. +`npm install` in the service directory. ## 3. Deploy `serverless deploy` or `sls deploy`. `sls` is shorthand for the Serverless CLI command @@ -35,4 +35,4 @@ In your terminal window you should see the response from Apache OpenWhisk } ``` -Congrats you have just deployed and run your Hello World function! +Congrats you have deployed and ran your Hello World function! diff --git a/docs/providers/openwhisk/examples/hello-world/swift/package.json b/docs/providers/openwhisk/examples/hello-world/swift/package.json index d41fc10e851..c2c6bf950d4 100644 --- a/docs/providers/openwhisk/examples/hello-world/swift/package.json +++ b/docs/providers/openwhisk/examples/hello-world/swift/package.json @@ -2,8 +2,7 @@ "name": "serverless-openwhisk-hello-world", "version": "0.1.0", "description": "Hello World example for OpenWhisk provider with Serverless Framework.", - "scripts": { - "postinstall": "npm link serverless-openwhisk", - "test": "echo \"Error: no test specified\" && exit 1" + "devDependencies": { + "serverless-openwhisk": ">=0.13.0" } } diff --git a/docs/providers/openwhisk/guide/credentials.md b/docs/providers/openwhisk/guide/credentials.md index 1d8f50a30a2..5ae174e3e9c 100644 --- a/docs/providers/openwhisk/guide/credentials.md +++ b/docs/providers/openwhisk/guide/credentials.md @@ -18,45 +18,69 @@ OpenWhisk is an open-source serverless platform. This means you can either choos Here we'll provide setup instructions for both options, just pick the one that you're using. -## Register with OpenWhisk platform (IBM Bluemix) +## Register with IBM Cloud Functions -IBM's Bluemix cloud platform provides a hosted serverless solution based upon Apache OpenWhisk. +[IBM's Cloud platform](https://console.bluemix.net/) provides a hosted serverless solution ([IBM Cloud Functions](https://console.bluemix.net/openwhisk/)) based upon Apache OpenWhisk. Here's how to get started… -- Sign up for a free account @ [https://bluemix.net](https://console.ng.bluemix.net/registration/) +- Sign up for a free account @ [IBM Cloud](https://console.bluemix.net/) -IBM Bluemix comes with a [free trial](https://www.ibm.com/cloud-computing/bluemix/pricing?cm_mc_uid=22424350960514851832143&cm_mc_sid_50200000=1485183214) that doesn't need credit card details for the first 30 days. Following the trial, developers have to enrol using a credit card but get a free tier for the platform and services. +IBM Cloud comes with a [lite account](https://console.bluemix.net/registration/) that does not need credit card details to register. Lite accounts provide free access to certain platform services and do not expire after a limited time period. -**All IBM Bluemix users get access to the [Free Tier for OpenWhisk](https://console.ng.bluemix.net/openwhisk/learn/pricing). This includes 400,000 GB-seconds of serverless function compute time per month.** +**All IBM Cloud users get access to the [Free Tier for IBM Cloud Functions](https://console.ng.bluemix.net/openwhisk/learn/pricing). This includes 400,000 GB-seconds of serverless function compute time per month.** Additional execution time is charged at $0.000017 per GB-second of execution, rounded to the nearest 100ms. -### Access Account Credentials +### Install the IBM Cloud CLI -Once you have signed up for IBM Bluemix, we need to retrieve your account credentials. These are available on [the page](https://console.ng.bluemix.net/openwhisk/learn/cli) about installing the command-line tool from the [service homepage](https://console.ng.bluemix.net/openwhisk/). +Following the [instructions on this page](https://console.bluemix.net/docs/cli/index.html#overview) to download and install the IBM Cloud CLI. -The second point in the instructions contains a command-line which includes the platform endpoint and authentication keys. +*On Linux, you can run this command:* ``` -wsk property set --apihost openwhisk.ng.bluemix.net --auth XXX:YYY +curl -fsSL https://clis.ng.bluemix.net/install/linux | sh ``` -**Make a note of the `apihost` and `auth` command flag values.** +*On OS X, you can run this command:* -### (optional) Install command-line utility +``` +curl -fsSL https://clis.ng.bluemix.net/install/osx | sh +``` + +### Install the IBM Cloud Functions Plugin -The command-line utility is linked from [the previous page](https://console.ng.bluemix.net/openwhisk/learn/cli). Download and install the binary into a location in your [shell path](http://unix.stackexchange.com/questions/26047/how-to-correctly-add-a-path-to-path). +``` +ibmcloud plugin install Cloud-Functions -r Bluemix +``` -### (optional) Authenticate with API gateway +### Authenticate with the CLI -OpenWhisk on IBM Bluemix uses a third-party API gateway service. An access token is needed to add HTTP endpoints to your functions. This can be retrieved automatically using the `wsk` command-line. +Log into the CLI to create local authentication credentials. The framework plugin automatically uses these credentials when interacting with IBM Cloud Functions. ``` -wsk bluemix login +ibmcloud login -a -o -s ``` -After running the login command, you will be prompted to enter your authentication credentials. The access token will be stored in the `.wskprops` file under your home directory, using the key (`APIGW_ACCESS_TOKEN`). +**Replace `<..>` values with your [platform region endpoint, account organisation and space](https://console.bluemix.net/docs/account/orgs_spaces.html#orgsspacesusers).** + +For example.... + +``` +ibmcloud login -a api.ng.bluemix.net -o user@email_host.com -s dev +``` + +#### regions + +Cloud Functions is available with the following regions US-South (`api.ng.bluemix.net`), London (`api.eu-gb.bluemix.net`), Frankfurt (` api.eu-de.bluemix.net`). Use the appropriate [API endpoint](https://console.bluemix.net/docs/overview/ibm-cloud.html#ov_intro_reg) to target Cloud Functions in that region. + +#### organisations and spaces + +Organisations and spaces for your account can be viewed on this page: [https://console.bluemix.net/account/organizations](https://console.bluemix.net/account/organizations) + +Accounts normally have a default organisation using the account email address. Default space name is usually `dev`. + +*After running the login command, authentication credentials will be stored in the `.wskprops` file under your home directory.* ## Register with OpenWhisk platform (Self-Hosted) @@ -103,15 +127,17 @@ Executables for other operating system, and CPU architectures are located in the Download and install the correct binary into a location in your [shell path](http://unix.stackexchange.com/questions/26047/how-to-correctly-add-a-path-to-path). +## Using Account Credentials +You can configure the Serverless Framework to use your OpenWhisk credentials in a few ways: -## Using Account Credentials +#### IBM Cloud Functions -You can configure the Serverless Framework to use your OpenWhisk credentials in two ways: +Provided you have logged into the IBM Cloud CLI, authenticated credentials will be already stored in the `~/.wskprops` file. If this file is available, the provider plugin will automatically read those credentials and you don't need to do anything else! -#### Quick Setup +#### Environment Variables Setup -As a quick setup to get started you can export them as environment variables so they would be accessible to Serverless Framework: +Access credentials can be provided as environment variables. ```bash # mandatory parameters @@ -125,16 +151,14 @@ serverless deploy #### Using Configuration File -For a more permanent solution you can also set up credentials through a configuration file. Here are different methods you can use to do so. +Credentials can be stored in a local configuration file, using either the CLI or manually creating the file. ##### Setup with the `wsk` cli -If you have followed the instructions above to install the `wsk` command-line utility, run the following command to create the configuration file. +If you are using a self-hosted platform and have followed the instructions above to install the `wsk` command-line utility, run the following command to create the configuration file. ```bash $ wsk property set --apihost PLATFORM_API_HOST --auth USER_AUTH_KEY -// followed by this command if you want to use the api gateway on bluemix -$ wsk bluemix login ``` Credentials are stored in `~/.wskprops`, which you can edit directly if needed. diff --git a/docs/providers/openwhisk/guide/functions.md b/docs/providers/openwhisk/guide/functions.md index bae71fa5568..2f0aacbda2f 100644 --- a/docs/providers/openwhisk/guide/functions.md +++ b/docs/providers/openwhisk/guide/functions.md @@ -35,6 +35,8 @@ functions: runtime: nodejs # optional overwrite, default is provider runtime memory: 512 # optional overwrite, default is 256 timeout: 10 # optional overwrite, default is 60 + parameters: + foo: bar // default parameters ``` The `handler` property points to the file and module containing the code you want to run in your function. @@ -94,14 +96,122 @@ functions: functionOne: handler: handler.functionOne memory: 512 # function specific + parameters: + foo: bar // default parameters ``` +You can specify an array of functions, which is useful if you separate your functions in to different files: + +```yml +# serverless.yml +... + +functions: + - ${file(./foo-functions.yml)} + - ${file(./bar-functions.yml)} +``` + +```yml +# foo-functions.yml +getFoo: + handler: handler.foo +deleteFoo: + handler: handler.foo +``` + +## Packages + +OpenWhisk provides a concept called "packages" to manage related actions. Packages can contain multiple actions under a common identifier in a namespace. Configuration values needed by all actions in a package can be set as default properties on the package, rather than individually on each action. + +*Packages are identified using the following format:* `/namespaceName/packageName/actionName`. + +### Implicit Packages + +Functions can be assigned to packages by setting the function `name` with a package reference. + +```yaml +functions: + foo: + handler: handler.foo + name: "myPackage/foo" + bar: + handler: handler.bar + name: "myPackage/bar" +``` + +In this example, two new actions (`foo` & `bar`) will be created using the `myPackage` package. + +Packages which do not exist will be automatically created during deployments. When using the `remove` command, any packages referenced in the `serverless.yml` will be deleted. + +### Explicit Packages + +Packages can also be defined explicitly to set shared configuration parameters. Default package parameters are merged into event parameters for each invocation. + +```yaml +functions: + foo: + handler: handler.foo + name: "myPackage/foo" + +resources: + packages: + myPackage: + parameters: + hello: world +``` + +*Explicit packages support the following properties: `parameters`, `annotations` and `shared`.* + +## Binding Services (IBM Cloud Functions) + +***This feature requires the [IBM Cloud CLI](https://console.bluemix.net/docs/cli/reference/bluemix_cli/download_cli.html#download_install) and [IBM Cloud Functions plugin](https://console.bluemix.net/openwhisk/learn/cli) to be installed.*** + +IBM Cloud Functions supports [automatic binding of service credentials](https://console.bluemix.net/docs/openwhisk/binding_services.html#binding_services) to actions using the CLI. + +Bound service credentials will be passed as the `__bx_creds` parameter in the invocation parameters. + +This feature is also available through the `serverless.yaml` file using the `bind` property for each function. + +```yaml +functions: + my_function: + handler: file_name.handler + bind: + - service: + name: cloud-object-storage + instance: my-cos-storage +``` + +The `service` configuration supports the following properties. + +- `name`: identifier for the cloud service +- `instance`: instance name for service (*optional*) +- `key`: key name for instance and service (*optional*) + +*If the `instance` or `key` properties are missing, the first available instance and key found will be used.* + +Binding services removes the need to manually create default parameters for service keys from platform services. + +More details on binding service credentials to actions can be found in the [official documentation](https://console.bluemix.net/docs/openwhisk/binding_services.html#binding_services) and [this blog post](http://jamesthom.as/blog/2018/06/05/binding-iam-services-to-ibm-cloud-functions/). + +Packages defined in the `resources` section can bind services using the same configuration properties. + +```yaml +resources: + packages: + myPackage: + bind: + - service: + name: cloud-object-storage + instance: my-cos-storage +``` ## Runtimes The OpenWhisk provider plugin supports the following runtimes. - Node.js - Python +- Java - Php - Swift - Binary diff --git a/docs/providers/openwhisk/guide/installation.md b/docs/providers/openwhisk/guide/installation.md index 627f7f6d48e..c7f1cb0b532 100644 --- a/docs/providers/openwhisk/guide/installation.md +++ b/docs/providers/openwhisk/guide/installation.md @@ -44,19 +44,17 @@ To see which version of serverless you have installed run: serverless --version ``` - - ### Installing OpenWhisk Provider Plugin -Now we need to install the provider plugin to allow the framework to deploy services to the platform. This plugin is also [published](http://npmjs.com/package/serverless-openwhisk) on [npm](https://npmjs.org) and can installed using the same `npm install` command. +Now we need to install the provider plugin to allow the framework to deploy services to the platform. This plugin is also [published](http://npmjs.com/package/serverless-openwhisk) on [npm](https://npmjs.org) and can installed as a project dependency using the `npm install --save-dev` command. ``` -npm install -g serverless-openwhisk +npm install serverless-openwhisk --save-dev ``` -*Due to an [outstanding issue](https://github.com/serverless/serverless/issues/2895) with provider plugins, the [OpenWhisk provider](https://github.com/serverless/serverless-openwhisk) must be installed as a global module.* - +Project templates already have this dependency listed in the `package.json` file allowing you to just `npm install` in the service directory. +The `serverless-openwhisk` plugin must be saved as a `devDependency` in the project's `package.json` to ensure it is not bundled in the deployment package. ### Setting up OpenWhisk diff --git a/docs/providers/openwhisk/guide/plugins.md b/docs/providers/openwhisk/guide/plugins.md index f233ccbe977..c0d27405fc0 100644 --- a/docs/providers/openwhisk/guide/plugins.md +++ b/docs/providers/openwhisk/guide/plugins.md @@ -33,9 +33,25 @@ We need to tell Serverless that we want to use the plugin inside our service. We plugins: - custom-serverless-plugin ``` +The `plugins` section supports two formats: -Plugins might want to add extra information which should be accessible to Serverless. The `custom` section in the `serverless.yml` file is the place where you can add necessary -configurations for your plugins (the plugins author / documentation will tell you if you need to add anything there): +Array object: +```yml +plugins: + - plugin1 + - plugin2 +``` + +Enhanced plugins object: +```yml +plugins: + localPath: './custom_serverless_plugins' + modules: + - plugin1 + - plugin2 +``` + +Plugins might want to add extra information which should be accessible to Serverless. The `custom` section in the `serverless.yml` file is the place where you can add necessary configurations for your plugins (the plugins author / documentation will tell you if you need to add anything there): ```yml plugins: @@ -47,9 +63,24 @@ custom: ## Service local plugin -If you are working on a plugin or have a plugin that is just designed for one project you can add them to the `.serverless_plugins` directory at the root of your service, and in the `plugins` array in `serverless.yml`. +If you are working on a plugin or have a plugin that is just designed for one project they can be loaded from the local folder. Local plugins can be added in the `plugins` array in `serverless.yml`. + +By default local plugins can be added to the `.serverless_plugins` directory at the root of your service, and in the `plugins` array in `serverless.yml`. +```yml +plugins: + - custom-serverless-plugin +``` + +Local plugins folder can be changed by enhancing `plugins` object: +```yml +plugins: + localPath: './custom_serverless_plugins' + modules: + - custom-serverless-plugin +``` +The `custom-serverless-plugin` will be loaded from the `custom_serverless_plugins` directory at the root of your service. If the `localPath` is not provided or empty `.serverless_plugins` directory will be taken as the `localPath`. -The plugin will be loaded based on being named `custom-serverless-plugin.js` or `custom-serverless-plugin\index.js` in the root of `.serverless_plugins` folder. +The plugin will be loaded based on being named `custom-serverless-plugin.js` or `custom-serverless-plugin\index.js` in the root of `localPath` folder (`.serverless_plugins` by default). ### Load Order diff --git a/docs/providers/openwhisk/guide/web-actions.md b/docs/providers/openwhisk/guide/web-actions.md index 6df5c30dabf..79b640b8383 100644 --- a/docs/providers/openwhisk/guide/web-actions.md +++ b/docs/providers/openwhisk/guide/web-actions.md @@ -25,8 +25,7 @@ functions: Functions with this annotation can be invoked through a URL template with the following parameters. ``` -https://{APIHOST}/api/v1/experimental/web/{USER_NAMESPACE}/{PACKAGE}/{ACTION_NAME}.{TYPE} - +https://{APIHOST}/api/v1/web/{USER_NAMESPACE}/{PACKAGE}/{ACTION_NAME}.{TYPE} ``` - *APIHOST* - platform endpoint e.g. *openwhisk.ng.bluemix.net.* @@ -67,10 +66,10 @@ function main() { Functions can access request parameters using the following environment variables. -1. `**__ow_meta_verb:**` the HTTP method of the request. -2. `**__ow_meta_headers:**` the request headers. -3. `**__ow_meta_path:**` the unmatched path of the request. - -Full details on this new feature are available in this [blog post](https://medium.com/openwhisk/serverless-http-handlers-with-openwhisk-90a986cc7cdd#.2x09176m8). +1. `__ow_method` - HTTP method of the request. +2. `__ow_headers` - HTTP request headers. +3. `__ow_path` - Unmatched URL path of the request. +4. `__ow_body` - Body entity from request. +5. `__ow_query` - Query parameters from the request. -**\*IMPORTANT: [Web Actions](http://bit.ly/2xSRbOQ) is currently experimental and may be subject to breaking changes.*** +**Full details on this feature are available in this [here](https://github.com/apache/incubator-openwhisk/blob/master/docs/webactions.md).** diff --git a/docs/providers/spotinst/README.md b/docs/providers/spotinst/README.md index 5fbe8fba63a..945f713c527 100755 --- a/docs/providers/spotinst/README.md +++ b/docs/providers/spotinst/README.md @@ -12,7 +12,7 @@ layout: Doc Welcome to the Serverless Spotinst documentation! -If you have questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](https://forum.serverless.com/) +If you have any questions, [search the forums](https://forum.serverless.com?utm_source=framework-docs) or [start your own thread](https://forum.serverless.com?utm_source=framework-docs) diff --git a/docs/providers/spotinst/cli-reference/stage.md b/docs/providers/spotinst/cli-reference/stage.md index e26da0f5532..479708f51e1 100644 --- a/docs/providers/spotinst/cli-reference/stage.md +++ b/docs/providers/spotinst/cli-reference/stage.md @@ -12,14 +12,30 @@ layout: Doc # Spotinst Functions - Stage Variables -You are able to set a stage variable in your function to distinguish between the multiple stages that your function maybe going through. The function is initially set to 'dev' for development but there are two ways you can change the stage if you so need. +Serverless allows you to specify different stages to deploy your project to. Changing the stage will change the environment your function is running on, which is helpful when you wish to keep production code partitioned from your development environment. + +Your function's stage is set to 'dev' by default. You can update the stage when deploying the function, either from the command line using the serverless framework, or by modifying the serverless.yml in your project. When utilizing this feature, remember to include a config file that holds the environment IDs associated with your stages. An example config.json would look something like this: +```json +{ + "dev": "env-abcd1234", + "prod": "env-defg5678" +} +``` -## Through Serverless Framwork +## Through Serverless Framework To change the stage through the serverless framework you simply need to enter the command ```bash serverless deploy --stage #{Your Stage Name} ``` +You will also need to update the environment parameter to point to the config.json: +```yaml + spotinst: + environment: ${file(./config.json):${opt:stage, self:provider.stage, 'dev'}} +``` +Note that while I am using 'dev' as the default stage, you may change this parameter to a custom default stage. + + ## Through the .yml File @@ -32,4 +48,5 @@ provider: spotinst: environment: #{Your Environment ID} ``` +Be sure to also modify your environment ID when you change the stage if you are not working with a config file. diff --git a/docs/providers/spotinst/examples/java8/README.md b/docs/providers/spotinst/examples/java8/README.md index 34e9f2b30d1..8de2ab42bef 100644 --- a/docs/providers/spotinst/examples/java8/README.md +++ b/docs/providers/spotinst/examples/java8/README.md @@ -32,7 +32,7 @@ In your terminal window you should see the response {"hello":"null"} ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have deployed and ran your Hello World function! ## Short Hand Guide diff --git a/docs/providers/spotinst/examples/node/README.md b/docs/providers/spotinst/examples/node/README.md index a8345f43017..cd8f956d786 100644 --- a/docs/providers/spotinst/examples/node/README.md +++ b/docs/providers/spotinst/examples/node/README.md @@ -14,7 +14,7 @@ layout: Doc Make sure `serverless` is installed. ## 1. Create a service -`serverless create --template spotinst-nodejs --path serviceName` `serviceName` is going to be a new directory there the JavaScript template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. +`serverless create --template spotinst-nodejs --path serviceName` `serviceName` is going to be a new directory where the JavaScript template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. ## 2. Deploy ```bash @@ -32,7 +32,7 @@ In your terminal window you should see the response {"hello":"from NodeJS8.3 function"} ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have deployed and ran your Hello World function! ## Short Hand Guide @@ -42,4 +42,4 @@ Congrats you have just deployed and ran your Hello World function! `-t` is short hand for `--template` -`-p` is short hang for `--path` \ No newline at end of file +`-p` is short hang for `--path` diff --git a/docs/providers/spotinst/examples/python/README.md b/docs/providers/spotinst/examples/python/README.md index c4f58a6284c..a6ae892364d 100644 --- a/docs/providers/spotinst/examples/python/README.md +++ b/docs/providers/spotinst/examples/python/README.md @@ -34,7 +34,7 @@ In your terminal window you should see the response '{"hello":"from Python2.7 function"}' ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have deployed and ran your Hello World function! ## Short Hand Guide diff --git a/docs/providers/spotinst/examples/ruby/README.md b/docs/providers/spotinst/examples/ruby/README.md index e0d7d2e2dd3..a7325a394c6 100644 --- a/docs/providers/spotinst/examples/ruby/README.md +++ b/docs/providers/spotinst/examples/ruby/README.md @@ -11,21 +11,21 @@ layout: Doc # Hello World Ruby Example -Make sure `serverless` is installed. +Make sure `serverless` is installed. ## 1. Create a service `serverless create --template spotinst-ruby --path serviceName` `serviceName` is going to be a new directory there the Ruby template will be loaded. Once the download is complete change into that directory. Next you will need to install the Spotinst Serverless Functions plugin by running `npm install` in the root directory. You will need to go into the serverless.yml file and add in the environment variable that you want to deploy into. ## 2. Deploy -```bash +```bash serverless deploy ``` ## 3. Invoke deployed function ```bash serverless invoke --function hello -``` +``` In your terminal window you should see the response @@ -33,14 +33,14 @@ In your terminal window you should see the response '{"hello":"from Ruby2.4.1 function"}' ``` -Congrats you have just deployed and ran your Hello World function! +Congrats you have deployed and ran your Hello World function! ## Short Hand Guide -`sls` is short hand for serverless cli commands +`sls` is short hand for serverless cli commands `-f` is short hand for `--function` `-t` is short hand for `--template` -`-p` is short hang for `--path` \ No newline at end of file +`-p` is short hang for `--path` diff --git a/docs/providers/spotinst/guide/IAM-roles.md b/docs/providers/spotinst/guide/IAM-roles.md new file mode 100644 index 00000000000..ba2ec0516a1 --- /dev/null +++ b/docs/providers/spotinst/guide/IAM-roles.md @@ -0,0 +1,44 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/iam-roles) + + +# Spotinst Functions - IAM roles +Functions sometimes rely on outside services from Amazon such as S3, and accessing these resources often requires authorization using IAM. Spotinst Functions can be configured with the relevant permissions with the inclusion of IAM role information in the serverless.yml file. See [Amazon's documentation][amazon-docs-url] for more information on IAM roles. + +## Requirements +- You will need to create an IAM role on your AWS account and attach policies with the relevant permissions. +- A spotinst role will be generated and linked with your AWS role +- Only one Spotinst role per function. +- Multiple functions can be associated with the same Spotinst role. + +## YML +```yaml +functions: + example: + runtime: nodejs8.3 + handler: handler.main + memory: 128 + timeout: 30 + access: private + iamRoleConfig: + roleId: ${role-id} +``` + +## Parameters +- roleId: the role created on the console + - ex : sfr-5ea76784 + + + +For more information on how to set up IAM roles, check out our documentation [here][spotinst-help-center] + +[amazon-docs-url]: https://aws.amazon.com/iam/?sc_channel=PS&sc_campaign=acquisition_US&sc_publisher=google&sc_medium=iam_b&sc_content=amazon_iam_e&sc_detail=amazon%20iam&sc_category=iam&sc_segment=208382128687&sc_matchtype=e&sc_country=US&s_kwcid=AL!4422!3!208382128687!e!!g!!amazon%20iam&ef_id=WoypCQAABVVgCzd0:20180220230233:s +[spotinst-help-center]: https://help.spotinst.com/hc/en-us/articles/360000317585?flash_digest=59d5566c556b5d4def591c69a62a56b6c1e16c61 diff --git a/docs/providers/spotinst/guide/active-versions.md b/docs/providers/spotinst/guide/active-versions.md new file mode 100644 index 00000000000..d0a7168b8b0 --- /dev/null +++ b/docs/providers/spotinst/guide/active-versions.md @@ -0,0 +1,82 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/active-versions) + + +# Spotinst Functions - Active Versions + +Every time you update your function, a new version is being created by default. Version numbers have a unique ID that starts at 0 and incrementes by one each update. Each function version is immutable and cannot be changed. + +## Latest Version +The 'Latest' version refers to the most recent version created by the last update. Unless otherwise specified, all incoming traffic is routed to the latest version. + +*Please note: the 'Latest' tag will point to a different version number each and every time you update your function.* + +Default configuration for activeVersions when a new function is created: +```yaml +activeVersions: + - version: $LATEST + percentage: 100.0 +``` + +## Active Version +The 'Active' version can point to more than one version of your function, including 'Latest'. This allows you to distribute your incoming traffic between multiple versions and dictate what percentage is sent to each version. + +For example, say you wanted to test a new version of your function to determine if it was production-ready. You could specify that 10% of the traffic be routed to that new version, and route the remaining 90% to the stable version. You can gradually route more traffic to the new version as you become more confident in its performance. + +### Examples +```yaml +activeVersions: + - version: $LATEST + percentage: 100.0 +``` + +100% of traffic will go to the most recently published update. + +```yaml +activeVersions: + - version: $LATEST + percentage: 80.0 + - version: 2 + percentage: 20.0 +``` +80% of traffic goes to the most recently published update, and 20% goes to version 2. + +```yaml +activeVersions: + - version: 5 + percentage: 50.0 + - version: 3 + percentage: 25.0 + - version: 1 + percentage: 25.0 +``` +Traffic is split between versions 1. 3, and 5. Changes made to your latest version will not affect production traffic. + +### Configure Active Version +You can configure active versions in the serverless.yml file, but you can also use the Spotinst Console to configure the versions without deploying a new update. In the event you would like to change your 'Active' version configuration without updating your function, you can use the 'Configure Active Version' action from the console and the API +- Console: + 1. Navigate to your function + 2. Click 'Actions' + 3. Select 'Configure Active Version' + +- API: (update function) +```yaml +activeVersions: + - version: $LATEST + percentage: 70.0 + - version: 2 + percentage: 30.0 +``` + +### Requirements +- The sum of all percentages must be 100% +- You can set up to two decimal digits in the percentage +- Changes made to the ratio using the Spotinst Console will be overwritten by the contents of activeVersions in your serverless.yml file. diff --git a/docs/providers/spotinst/guide/cors.md b/docs/providers/spotinst/guide/cors.md new file mode 100644 index 00000000000..95e23a9b54d --- /dev/null +++ b/docs/providers/spotinst/guide/cors.md @@ -0,0 +1,42 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/cors) + + +# Spotinst Functions - CORS +Cross-Origin Resource Sharing is a mechanism that allows restricted resources on a web page to be requested from a domain outside of the original. CORS defines a way in which a web service and server can interact to determine whether or not it is safe to allow a cross-origin request. Enabling CORS for your function allows you to specify safe domains, and enables out-of-the-box support for preflight HTTP requests (via the OPTIONS method) that will return the needed ‘access-control-*’ headers specified below. The actual HTTP request will return the ‘access-control-allow-origin’ method. +You can enable CORS for cross-domain HTTP requests with Spotinst Functions. Add the required fields to you serverless.yml file. + +### Example CORS object: +```yml + cors: + - enabled: true + origin: "http://foo.example" + headers: "Content-Type,X-PINGOTHER" + methods: "PUT,POST" +``` + +### Parameters: + - enabled: Boolean + - Specify if CORS is enabled for the function. + - default: false + - origin: String + - Specifies a domain/origin that may access the resource. A wildcard '*' may be used to allow any origin to access the resource. + - default: '*' + - methods: String + - Comma-separated list of HTTP methods that are allowed to access the resource. This is used in response to a preflight request. + - default: 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' + - headers: String + - Comma-separated list of allowed headers. + - default: 'Content-Type,Authorization' + + + + diff --git a/docs/providers/spotinst/guide/create-token.md b/docs/providers/spotinst/guide/create-token.md index 9fbaf1d9b87..46e8bb87cee 100644 --- a/docs/providers/spotinst/guide/create-token.md +++ b/docs/providers/spotinst/guide/create-token.md @@ -7,7 +7,7 @@ layout: Doc --> -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/create-token) # Spotinst Functions - Create Token diff --git a/docs/providers/spotinst/guide/document-store-sdk.md b/docs/providers/spotinst/guide/document-store-sdk.md deleted file mode 100644 index 0ffa6b13d8a..00000000000 --- a/docs/providers/spotinst/guide/document-store-sdk.md +++ /dev/null @@ -1,72 +0,0 @@ - - - -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/document-store-sdk) - - -# Spotinst Functions - Document Store SDK - -We have encapsulated the Document Store API calls for retrieving your documents so you will not have to make an API call within the given function. This will allow for you as the user to access your documents using thegetDoc/get_doc method located in the context variable. Additionally this will also eliminate the need for authentication within the function for accessing your documents. - -## Node -```basg -module.exports.main = (event, context, callback) => { - context.getDoc("myKey", function(err, res) { - if(res) { - console.log('res: ' + res); //myValue - var body = { - res: res - }; - - callback(null, { - statusCode: 200, - body: JSON.stringify(body), - headers: {"Content-Type": "application/json"} - }); - } - }); -} -``` - -## Python -```bash -def main(event, context): - print ('context: %s' % context) - - doc = context.get_doc('myKey') - print(doc) #myValue - - res = { - 'statusCode': 200, - 'body': 'res: %s' % doc, - 'headers': {"Content-Type": "application/json"} - } - return res -``` - -## Java 8 -```bash -public class Java8Template implements RequestHandler { - @Override - public Response handleRequest(Request request, Context context) { - String value = context.getDoc("myKey"); - System.out.println(value); //myValue - - Response response = new Response(200, String.format("value: %s", value)); - - Map headers = new HashMap<>(); - headers.put("Content-Type", "application/json"); - - response.setHeaders(headers); - - return response; - } -} -``` - diff --git a/docs/providers/spotinst/guide/document-store.md b/docs/providers/spotinst/guide/document-store.md deleted file mode 100644 index f5887f5939b..00000000000 --- a/docs/providers/spotinst/guide/document-store.md +++ /dev/null @@ -1,175 +0,0 @@ - - - -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) - - -# Spotinst Functions - Document Store API - -Document Store is a way for you to save information across function calls within the same environment without having to access an external database. It is secured by your Spotinst account credentials and can only be accesses within a function. Because you do not need to access an external database you are able to fetch stored documents with low latency (< ~5ms) - -To access the document store you will need to make an API request inside a funciton formatted bellow. - -## Add New Value - -This is how to insert a new key/value pair into your document store in a specific environment - -**HTTPS Request:** - -```bash -POST environment/${environmentId}/userDocument?accountId=${accountId} -``` - -**Host:** - -```bash -api.spotinst.io/functions/ -``` - -**Header:** - -```bash -{ - "Content-Type": "application/json", - "Authorization": "Bearer ${Spotinst API Token}" -} -``` - -**Body:** - -```bash -{ - "userDocument" : { - "key": “${Your Key}”, - "value": “${Your Value}” - } -} -``` - - -## Update Value - -This is how to update a current key/value pair in your document store in a specific environment - -**HTTPS Request:** - -```bash -PUT environment/${environmentId}/userDocument/${Key}?accountId=${accountId} -``` - -**Endpoint:** - -```bash -api.spotinst.io/functions/ -``` - -**Header:** - -```bash -{ - "Content-Type": "application/json", - "Authorization": "Bearer ${Spotinst API Token}" -} -``` - -**Body:** - -```bash -{ - "userDocument" : { - "value": “${Your Value}” - } -} -``` - - -## Get Values - -There are two ways to get the documents from your store, either by specifing a key which will return both the key and the value or you can just leave this out and you will get all the keys in the environment - -### 1. Get Sinlge Key Pair - -**HTTPS Request:** - -```bash -GET environment/${environmentId}/userDocument/${Key}?accountId=${accountId} -``` - -**Endpoint:** - -```bash -api.spotinst.io/functions/ -``` - -**Header:** - -```bash -{ - "Content-Type": "application/json", - "Authorization": "Bearer ${Spotinst API Token}" -} -``` - -### 2. Get All Keys - -**HTTPS Request:** - -```bash -GET environment/${environmentId}/userDocument?accountId=${accountId} -``` - -**Endpoint:** - -```bash -api.spotinst.io/functions/ -``` - -**Header:** - -```bash -{ - "Content-Type": "application/json", - "Authorization": "Bearer ${Spotinst API Token}" -} -``` - - -## Delete Value - -This is how to delete a specific key value pair from your document store - -**HTTPS Request:** - -```bash -DELETE environment/${environmentId}/userDocument/${Key}?accountId=${accountId} -``` - -**Endpoint:** - -```bash -https://api.spotinst.io/functions/ -``` - -**Header:** - -```bash -{ - "Content-Type": "application/json", - "Authorization": "Bearer ${Spotinst API Token}" -} -``` - - -## GitHub - -Check out some examples to help you get started! - -[Get All Values Function](https://github.com/spotinst/spotinst-functions-examples/tree/master/node-docstore-getAll) - -[Insert New Value Function](https://github.com/spotinst/spotinst-functions-examples/tree/master/node-docstore-newValue) \ No newline at end of file diff --git a/docs/providers/spotinst/guide/endpoint-api.md b/docs/providers/spotinst/guide/endpoint-api.md deleted file mode 100644 index 33790666268..00000000000 --- a/docs/providers/spotinst/guide/endpoint-api.md +++ /dev/null @@ -1,202 +0,0 @@ - - - -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) - - -# Spotinst Functions - Endpoint API Documentation - -Here is the full list of API calls that you can make to set alias and patterns. Please check out the full article on Setting Up Endpoints first because it will make more sense. - -## Alias -### Create Alias -Create a new alias to point to your environment - -#### HTTPS Request -```bash -POST alias?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Body -```bash -{ - "alias": { - "host": "myAlias.com", - "environmentId": ${Environment ID} - } -} -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json - ``` - -### Get Alias -Returns a single alias - -#### HTTPS Request -```bash -GET alias/${Alias ID}?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -### Get All Alias -Returns all the alias in your account - -#### HTTPS Request -```bash -GET alias?accountId=${accountId} -``` -##### Host -```bash -api.spotinst.io/functions/ -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -### Delete Alias -Deletes a single alias - -#### HTTPS Request -```bash -DELETE alias/${Alias ID}?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - - -## Pattern -### Create Pattern -Create a new pattern that maps to a function - -#### HTTPS Request -```bash -POST pattern?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Body -```bash -{ - "pattern": { - "environmentId":${Environment ID}, - "method": "ALL", - "pattern": "/*", - "functionId": ${Function ID} - } -} -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -### Update Pattern -Update and existing pattern - -#### HTTPS Request -```bash -PUT pattern/${Pattern ID}?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Body -```bash -{ - "pattern": { - "environmentId":${Environment ID}, - "method": "ALL", - "pattern": "/*", - "functionId": ${Function ID} - } -} -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -### Get Pattern -Returns a single pattern - -#### HTTPS Request -```bash -GET pattern/${Pattern ID}?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -### Get All Patterns -Returns all the patterns your account - -#### HTTPS Request -```bash -POST pattern?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -### Delete Pattern -Delete a single pattern - -#### HTTPS Request -```bash -DELETE pattern/${Pattern ID}?accountId=${accountId} -``` -#### Host -```bash -api.spotinst.io/functions/ -``` -#### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` diff --git a/docs/providers/spotinst/guide/endpoint-setup.md b/docs/providers/spotinst/guide/endpoint-setup.md deleted file mode 100644 index 19d3c5568ac..00000000000 --- a/docs/providers/spotinst/guide/endpoint-setup.md +++ /dev/null @@ -1,85 +0,0 @@ - - - -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) - - -# Spotinst Functions - Endpoint Setup - -You are able to set an alias URL name as an endpoint for your serverless function to make it more accessible to your users. The way this works is you will point the domain of your choosing to your environment URL's then you will set paths to each of the functions in that environment you wish to bundle in together. To do this you will first need a valid domain. For this example I will be using 'myAlias.com'. - -## Set DNS Record -First you will want to create a DNS record set that will point to your environment URL. Your environment URL can be found in the Spotinst console. When you select the environment you wish to connect you will see a list of functions and their individual URL's. Like this -```bash -https://app-123xyz-raffleapp-execute-function1.spotinst.io/fx-abc987 -``` -We only want the URL starting at app and ending before the function id. Like this -```bash -app-123xyz-raffleapp-execute-function1.spotinst.io -``` -With this you will need to go to a DNS record setter and point your domain to this URL. I used AWS Route 53 to set this up. - -## Set Alias -Next you will need to set the alias in your Spotinst environment by making an API call. This does not need to be done within a function and can be set anyway you are most comfortable. The API request is connecting your domain the environment that you want. This is the API request - -### HTTPS Request -```bash -POST alias?accountId=${accountId} -``` -### Host -```bash -api.spotinst.io/functions/ -``` -### Body -```bash -{ - "alias": { - "host": "myAlias.com", - "environmentId": ${Your Environment ID} - } -} -``` -### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -**Note:** You are able to connect multiple alias to the same environment - -## Set Up Pattern -After you have an alias set up you will need to set up pattern to connect to all the functions in the application. This is again another API call and can be done from anywhere. You specify the pattern that you want, the method that will trigger the function, the function ID and the environment ID. The pattern is what will appear after the domain. For example '/home' would point to 'myAlias.com/home'. The methods you can select are any of the usual HTTP request methods: GET, PUT, POST, DELETE , OPTIONS, PATCH, ALL where “ALL” matches every method - -### HTTPS Request -```bash -POST pattern?accountId=${accountId} -``` -### Host -```bash -api.spotinst.io/functions/ -``` -### Body -``` bash -{ - "pattern": { - "environmentId": ${Your Environment ID}, - "method": "ALL", - "pattern": "/*", - "functionId": ${Your Function ID} - } -} -``` -### Headers -```bash -Authorization: Bearer ${Spotinst API Token} -Content-Type: application/json -``` - -## API Documentation -The full API documentation has information like delete and get alias and patterns. Check it out [here](./endpoint-api.md) diff --git a/docs/providers/spotinst/guide/endpoints.md b/docs/providers/spotinst/guide/endpoints.md new file mode 100644 index 00000000000..004a8077e1c --- /dev/null +++ b/docs/providers/spotinst/guide/endpoints.md @@ -0,0 +1,32 @@ + + + +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) + + +# Spotinst Functions - Endpoints + +You are able to set your custom endpoint path in the serverless.yml file if you do not want to use the console or API. You will have to set up your environment Alias in the console but here you can set the path and method for your individual functions to be mapped to. + +Here is a sample function from a yml file. As you can see at the bottom of the file we have listed an endpoint with a path and method. Both of these will need to be set in order to be deployed properly + +```yml + hello: + runtime: nodejs8.3 + handler: handler.main + memory: 128 + timeout: 30 + access: public + endpoint: + path: /home + method: get + +``` + +For more information on how to set up endpoint alias and patterns check out our documentation [here](https://help.spotinst.com/hc/en-us/articles/115005893709) \ No newline at end of file diff --git a/docs/providers/spotinst/guide/intro.md b/docs/providers/spotinst/guide/intro.md index ea915470093..f6b2a1ac5ea 100644 --- a/docs/providers/spotinst/guide/intro.md +++ b/docs/providers/spotinst/guide/intro.md @@ -92,11 +92,10 @@ functions: timeout: 30 # access: private # cron: # Setup scheduled trigger with cron expression -# active: true -# value: '* * * * *' -# environmentVariables: { -# Key: "Value", -# } +# active: true +# value: '* * * * *' +# environmentVariables: +# key: value ``` When you deploy with the Framework by running `serverless deploy`, everything in `serverless.yml` is deployed at once. diff --git a/docs/providers/spotinst/guide/quick-start.md b/docs/providers/spotinst/guide/quick-start.md index 077ab90ad14..2292eb30369 100644 --- a/docs/providers/spotinst/guide/quick-start.md +++ b/docs/providers/spotinst/guide/quick-start.md @@ -6,6 +6,10 @@ description: Getting started with the Serverless Framework on AWS Lambda layout: Doc --> + +### [Read this on the main serverless docs site](https://serverless.com/framework/docs/providers/spotinst/guide/quick-start/) + + # Spotinst - Quick Start Here is a quick guide on how to create a new serverless project using the Spotinst NodeJS template. For more detailed instruction please check out the other reference material provided. diff --git a/docs/providers/spotinst/guide/serverless.yml.md b/docs/providers/spotinst/guide/serverless.yml.md index a53db4f0af0..8fe0c6e1212 100644 --- a/docs/providers/spotinst/guide/serverless.yml.md +++ b/docs/providers/spotinst/guide/serverless.yml.md @@ -5,23 +5,34 @@ menuOrder: 5 description: Serverless.yml reference layout: Doc --> + + +### [Read this on the main serverless docs site](https://serverless.com/framework/docs/providers/spotinst/guide/serverless.yml/) + + # Serverless.yml Reference This is an outline of a `serverless.yml` file with descriptions of the properties for reference ```yml -# serverless.yml - -# The service can be whatever you choose. You can have multiple functions -# under one service +# Welcome to Serverless! +# +# This file is the main config file for your service. +# It's very minimal at this point and uses default values. +# You can always add more config options for more control. +# We've included some commented out config examples here. +# Just uncomment any of them to get that config option. +# +# For full config options, check the docs: +# docs.serverless.com +# +# Happy Coding! -service: your-service - -# The provider is Spotinst and the Environment ID can be found on the -# Spotinst Console under Functions +service: four provider: name: spotinst + #stage: #Optional setting. By default it is set to 'dev' spotinst: environment: #{Your Environment ID} @@ -38,19 +49,27 @@ provider: functions: function-name: - runtime: nodejs4.8 + runtime: nodejs8.3 handler: handler.main memory: 128 timeout: 30 -# access: public -# cron: -# active: false -# value: '*/1 * * * *' -# environmentVariables: { -# Key: "Value", -# } - + access: private +# activeVersions: +# - version: $LATEST +# percentage: 100.0 +# cors: +# enabled: # false by default +# origin: # '*' by default +# headers: # 'Content-Type,Authorization' by default +# methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default +# cron: # Setup scheduled trigger with cron expression +# active: true +# value: '* * * * *' +# environmentVariables: +# key: value +# extend the framework using plugins listed here: +# https://github.com/serverless/plugins plugins: - serverless-spotinst-functions ``` diff --git a/docs/providers/spotinst/guide/variables.md b/docs/providers/spotinst/guide/variables.md index a120acc0901..eaf7722ca29 100644 --- a/docs/providers/spotinst/guide/variables.md +++ b/docs/providers/spotinst/guide/variables.md @@ -7,7 +7,7 @@ layout: Doc --> -### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/credentials) +### [Read this on the main serverless docs site](https://www.serverless.com/framework/docs/providers/spotinst/guide/variables) # Spotinst Functions - Variables @@ -26,9 +26,8 @@ Also you are able to enter in environment variables in the serverless.yml file. functions: test: handler: handler.main - environmentVariables: { - Key: "Value" - } + environmentVariables: + key: value ``` To access your variables in your code you just need to put `process.env['{Your Key}']` as needed in the handler file. @@ -39,8 +38,8 @@ URL parameters can be use when a POST request is made to the endpoint of your fu ### 1. Node JS -To access URL parameters in your NodeJS code you just need to put `event.query.['{Your Parameter Name}']` as needed +To access URL parameters in your NodeJS code you just need to put `event.query['{Your Parameter Name}']` as needed ### 2. Python -To access URL parameters in your NodeJS code you just need to put `os.environ['{Your Parameter Name}']` as needed +To access URL parameters in your Python code you just need to put `os.environ['{Your Parameter Name}']` as needed diff --git a/docs/providers/webtasks/README.md b/docs/providers/webtasks/README.md index c6cd4c733b0..930332cbf89 100755 --- a/docs/providers/webtasks/README.md +++ b/docs/providers/webtasks/README.md @@ -12,7 +12,9 @@ layout: Doc Welcome to the Serverless Webtasks documentation! -If you have questions specific to the Webtasks provider, please join our [Slack](http://chat.webtask.io). For general Serverless Framework questions, join the [chat in gitter](https://gitter.im/serverless/serverless) or [post over on the forums](http://forum.serverless.com/) +If you have questions specific to the Webtasks provider, please join our [Slack](http://chat.webtask.io). + +For general Serverless Framework questions, [search the forums](https://forum.serverless.com?utm_source=framework-docs) or [start your own thread](https://forum.serverless.com?utm_source=framework-docs) #### Quick Start diff --git a/lib/Serverless.js b/lib/Serverless.js index 9b874221bd0..db9595571c1 100644 --- a/lib/Serverless.js +++ b/lib/Serverless.js @@ -4,6 +4,7 @@ const path = require('path'); const BbPromise = require('bluebird'); const os = require('os'); const updateNotifier = require('update-notifier'); +const platform = require('@serverless/platform-sdk'); const pkg = require('../package.json'); const CLI = require('./classes/CLI'); const Config = require('./classes/Config'); @@ -14,6 +15,7 @@ const Service = require('./classes/Service'); const Variables = require('./classes/Variables'); const ServerlessError = require('./classes/Error').ServerlessError; const Version = require('./../package.json').version; +const configUtils = require('./utils/config'); class Serverless { constructor(config) { @@ -75,6 +77,25 @@ class Serverless { } run() { + const config = configUtils.getConfig(); + const currentId = config.userId; + const globalConfig = configUtils.getGlobalConfig(); + + let isTokenExpired = false; + if (globalConfig + && globalConfig.users + && globalConfig.users[currentId] + && globalConfig.users[currentId].auth + && globalConfig.users[currentId].auth.id_token + && !globalConfig.users[currentId].dashboard) { + isTokenExpired = true; + } + + if (isTokenExpired && !this.processedInput.commands[0] === 'login') { + this.cli + .log('WARNING: Your login token has expired. Please run "serverless login" to login.'); + } + this.utils.logStat(this).catch(() => BbPromise.resolve()); if (this.cli.displayHelp(this.processedInput)) { @@ -87,14 +108,54 @@ class Serverless { // populate variables after --help, otherwise help may fail to print // (https://github.com/serverless/serverless/issues/2041) return this.variables.populateService(this.pluginManager.cliOptions).then(() => { + if ((!this.processedInput.commands.includes('deploy') && + !this.processedInput.commands.includes('remove')) || !this.config.servicePath) { + return BbPromise.resolve(); + } + + let username = null; + let idToken = null; + if (globalConfig + && globalConfig.users + && globalConfig.users[currentId] + && globalConfig.users[currentId].dashboard + && globalConfig.users[currentId].dashboard.username + && globalConfig.users[currentId].dashboard.idToken) { + username = globalConfig.users[currentId].dashboard.username; + idToken = globalConfig.users[currentId].dashboard.idToken; + } + if (!username || !idToken) { + return BbPromise.resolve(); + } + + if (!this.service.tenant && !this.service.app) { + this.cli.log('WARNING: Missing "tenant" and "app" properties in serverless.yml. Without these properties, you can not publish the service to the Serverless Platform.'); // eslint-disable-line + return BbPromise.resolve(); + } else if (this.service.tenant && !this.service.app) { + const errorMessage = ['Missing "app" property in serverless.yml'].join(''); + throw new this.classes.Error(errorMessage); + } else if (!this.service.tenant && this.service.app) { + const errorMessage = ['Missing "tenant" property in serverless.yml'].join(''); + throw new this.classes.Error(errorMessage); + } + + return platform.listTenants({ idToken, username }).then((tenants) => { + const tenantsList = tenants.map(tenant => tenant.tenantName); + if (!tenantsList.includes(this.service.tenant)) { + const errorMessage = [`tenant "${this.service + .tenant}" does not exist.`].join(''); + throw new this.classes.Error(errorMessage); + } + }); + }).then(() => { + // merge arrays after variables have been populated + // (https://github.com/serverless/serverless/issues/3511) + this.service.mergeArrays(); + // populate function names after variables are loaded in case functions were externalized // (https://github.com/serverless/serverless/issues/2997) this.service.setFunctionNames(this.processedInput.options); - // merge custom resources after variables have been populated - // (https://github.com/serverless/serverless/issues/3511) - this.service.mergeResourceArrays(); - // validate the service configuration, now that variables are loaded this.service.validate(); diff --git a/lib/classes/Error.js b/lib/classes/Error.js index 47e21897c94..9329cb9dc32 100644 --- a/lib/classes/Error.js +++ b/lib/classes/Error.js @@ -19,7 +19,7 @@ const writeMessage = (messageType, message) => { consoleLog(' '); if (message) { - consoleLog(chalk.white(` ${message}`)); + consoleLog(` ${message}`); } consoleLog(' '); @@ -61,17 +61,15 @@ module.exports.logError = (e) => { consoleLog(' '); } - const platform = chalk.white(process.platform); - const nodeVersion = chalk.white(process.version.replace(/^[v|V]/, '')); - const slsVersion = chalk.white(version); + const platform = process.platform; + const nodeVersion = process.version.replace(/^[v|V]/, ''); + const slsVersion = version; consoleLog(chalk.yellow(' Get Support --------------------------------------------')); - consoleLog(`${chalk.yellow(' Docs: ')}${chalk.white('docs.serverless.com')}`); - consoleLog(`${chalk.yellow(' Bugs: ')}${chalk - .white('github.com/serverless/serverless/issues')}`); - consoleLog(`${chalk.yellow(' Forums: ')}${chalk.white('forum.serverless.com')}`); - consoleLog(`${chalk.yellow(' Chat: ')}${chalk - .white('gitter.im/serverless/serverless')}`); + consoleLog(`${chalk.yellow(' Docs: ')}${'docs.serverless.com'}`); + consoleLog(`${chalk.yellow(' Bugs: ')}${ + 'github.com/serverless/serverless/issues'}`); + consoleLog(`${chalk.yellow(' Issues: ')}${'forum.serverless.com'}`); consoleLog(' '); consoleLog(chalk.yellow(' Your Environment Information -----------------------------')); diff --git a/lib/classes/PluginManager.js b/lib/classes/PluginManager.js index 1325ec9a91d..2e10cf0b080 100644 --- a/lib/classes/PluginManager.js +++ b/lib/classes/PluginManager.js @@ -54,9 +54,9 @@ class PluginManager { let pluginProvider = null; // check if plugin is provider agnostic if (pluginInstance.provider) { - if (typeof pluginInstance.provider === 'string') { + if (_.isString(pluginInstance.provider)) { pluginProvider = pluginInstance.provider; - } else if (typeof pluginInstance.provider === 'object') { + } else if (_.isObject(pluginInstance.provider)) { pluginProvider = pluginInstance.provider.constructor.getProviderName(); } } @@ -130,16 +130,34 @@ class PluginManager { } loadServicePlugins(servicePlugs) { - const servicePlugins = Array.isArray(servicePlugs) ? servicePlugs : []; - // eslint-disable-next-line no-underscore-dangle module.paths = Module._nodeModulePaths(process.cwd()); + const pluginsObject = this.parsePluginsObject(servicePlugs); // we want to load plugins installed locally in the service - if (this.serverless && this.serverless.config && this.serverless.config.servicePath) { - module.paths.unshift(path.join(this.serverless.config.servicePath, '.serverless_plugins')); + if (pluginsObject.localPath) { + module.paths.unshift(pluginsObject.localPath); + } + this.loadPlugins(pluginsObject.modules); + } + + parsePluginsObject(servicePlugs) { + let localPath = (this.serverless && this.serverless.config && + this.serverless.config.servicePath) && + path.join(this.serverless.config.servicePath, '.serverless_plugins'); + let modules = []; + + if (_.isArray(servicePlugs)) { + modules = servicePlugs; + } else if (servicePlugs) { + localPath = servicePlugs.localPath && + _.isString(servicePlugs.localPath) ? servicePlugs.localPath : localPath; + if (_.isArray(servicePlugs.modules)) { + modules = servicePlugs.modules; + } } - this.loadPlugins(servicePlugins); + + return { modules, localPath }; } createCommandAlias(alias, command) { @@ -430,7 +448,7 @@ class PluginManager { if (_.isPlainObject(value.customValidation) && value.customValidation.regularExpression instanceof RegExp && - typeof value.customValidation.errorMessage === 'string' && + _.isString(value.customValidation.errorMessage) && !value.customValidation.regularExpression.test(this.cliOptions[key])) { throw new this.serverless.classes.Error(value.customValidation.errorMessage); } diff --git a/lib/classes/PluginManager.test.js b/lib/classes/PluginManager.test.js index 437a515ffd0..7a19f227d28 100644 --- a/lib/classes/PluginManager.test.js +++ b/lib/classes/PluginManager.test.js @@ -779,6 +779,64 @@ describe('PluginManager', () => { }); }); + describe('#parsePluginsObject()', () => { + const parsePluginsObjectAndVerifyResult = (servicePlugins, expectedResult) => { + const result = pluginManager.parsePluginsObject(servicePlugins); + expect(result).to.deep.equal(expectedResult); + }; + + it('should parse array object', () => { + const servicePlugins = ['ServicePluginMock1', 'ServicePluginMock2']; + + parsePluginsObjectAndVerifyResult(servicePlugins, { + modules: servicePlugins, + localPath: path.join(serverless.config.servicePath, '.serverless_plugins'), + }); + }); + + it('should parse plugins object', () => { + const servicePlugins = { + modules: ['ServicePluginMock1', 'ServicePluginMock2'], + localPath: './myplugins', + }; + + parsePluginsObjectAndVerifyResult(servicePlugins, { + modules: servicePlugins.modules, + localPath: servicePlugins.localPath, + }); + }); + + it('should parse plugins object if format is not correct', () => { + const servicePlugins = {}; + + parsePluginsObjectAndVerifyResult(servicePlugins, { + modules: [], + localPath: path.join(serverless.config.servicePath, '.serverless_plugins'), + }); + }); + + it('should parse plugins object if modules property is not an array', () => { + const servicePlugins = { modules: {} }; + + parsePluginsObjectAndVerifyResult(servicePlugins, { + modules: [], + localPath: path.join(serverless.config.servicePath, '.serverless_plugins'), + }); + }); + + it('should parse plugins object if localPath is not correct', () => { + const servicePlugins = { + modules: ['ServicePluginMock1', 'ServicePluginMock2'], + localPath: {}, + }; + + parsePluginsObjectAndVerifyResult(servicePlugins, { + modules: servicePlugins.modules, + localPath: path.join(serverless.config.servicePath, '.serverless_plugins'), + }); + }); + }); + describe('command aliases', () => { describe('#getAliasCommandTarget', () => { it('should return an alias target', () => { @@ -1511,6 +1569,66 @@ describe('PluginManager', () => { }); }); + describe('Plugin / Load local plugins', () => { + const cwd = process.cwd(); + let serviceDir; + let tmpDir; + beforeEach(function () { // eslint-disable-line prefer-arrow-callback + tmpDir = testUtils.getTmpDirPath(); + serviceDir = path.join(tmpDir, 'service'); + fse.mkdirsSync(serviceDir); + process.chdir(serviceDir); + pluginManager.serverless.config.servicePath = serviceDir; + }); + + it('should load plugins from .serverless_plugins', () => { + const localPluginDir = path.join(serviceDir, '.serverless_plugins', 'local-plugin'); + testUtils.installPlugin(localPluginDir, SynchronousPluginMock); + + pluginManager.loadServicePlugins(['local-plugin']); + expect(pluginManager.plugins).to.satisfy(plugins => + plugins.some(plugin => plugin.constructor.name === 'SynchronousPluginMock')); + }); + + it('should load plugins from custom folder', () => { + const localPluginDir = path.join(serviceDir, 'serverless-plugins-custom', 'local-plugin'); + testUtils.installPlugin(localPluginDir, SynchronousPluginMock); + + pluginManager.loadServicePlugins({ + localPath: path.join(serviceDir, 'serverless-plugins-custom'), + modules: ['local-plugin'], + }); + // Had to use contructor.name because the class will be loaded via + // require and the reference will not match with SynchronousPluginMock + expect(pluginManager.plugins).to.satisfy(plugins => + plugins.some(plugin => plugin.constructor.name === 'SynchronousPluginMock')); + }); + + it('should load plugins from custom folder outside of serviceDir', () => { + serviceDir = path.join(tmpDir, 'serverless-plugins-custom'); + const localPluginDir = path.join(serviceDir, 'local-plugin'); + testUtils.installPlugin(localPluginDir, SynchronousPluginMock); + + pluginManager.loadServicePlugins({ + localPath: serviceDir, + modules: ['local-plugin'], + }); + // Had to use contructor.name because the class will be loaded via + // require and the reference will not match with SynchronousPluginMock + expect(pluginManager.plugins).to.satisfy(plugins => plugins.some(plugin => + plugin.constructor.name === 'SynchronousPluginMock')); + }); + + afterEach(function () { // eslint-disable-line prefer-arrow-callback + process.chdir(cwd); + try { + fse.removeSync(tmpDir); + } catch (e) { + // Couldn't delete temporary file + } + }); + }); + describe('Plugin / CLI integration', function () { this.timeout(0); @@ -1559,6 +1677,11 @@ describe('PluginManager', () => { afterEach(function () { // eslint-disable-line prefer-arrow-callback process.chdir(cwd); + try { + fse.removeSync(serviceDir); + } catch (e) { + // Couldn't delete temporary file + } }); }); }); diff --git a/lib/classes/PromiseTracker.js b/lib/classes/PromiseTracker.js new file mode 100644 index 00000000000..ea710720bbc --- /dev/null +++ b/lib/classes/PromiseTracker.js @@ -0,0 +1,58 @@ +'use strict'; + +const logWarning = require('./Error').logWarning; + +class PromiseTracker { + constructor() { + this.reset(); + } + reset() { + this.promiseList = []; + this.promiseMap = {}; + this.startTime = Date.now(); + } + start() { + this.reset(); + this.interval = setInterval(this.report.bind(this), 2500); + } + report() { + const delta = Date.now() - this.startTime; + logWarning('################################################################################'); + logWarning(`# ${delta}: ${this.getSettled().length} of ${ + this.getAll().length} promises have settled`); + const pending = this.getPending(); + logWarning(`# ${delta}: ${pending.length} unsettled promises:`); + pending.forEach((promise) => { + logWarning(`# ${delta}: ${promise.waitList}`); + }); + logWarning('################################################################################'); + } + stop() { + clearInterval(this.interval); + this.reset(); + } + add(variable, prms, specifier) { + const promise = prms; + promise.waitList = `${variable} waited on by: ${specifier}`; + promise.state = 'pending'; + promise.then( // creates a promise with the following effects but that we otherwise ignore + () => { promise.state = 'resolved'; }, + () => { promise.state = 'rejected'; }); + this.promiseList.push(promise); + this.promiseMap[variable] = promise; + return promise; + } + contains(variable) { + return variable in this.promiseMap; + } + get(variable, specifier) { + const promise = this.promiseMap[variable]; + promise.waitList += ` ${specifier}`; + return promise; + } + getPending() { return this.promiseList.filter(p => (p.state === 'pending')); } + getSettled() { return this.promiseList.filter(p => (p.state !== 'pending')); } + getAll() { return this.promiseList; } +} + +module.exports = PromiseTracker; diff --git a/lib/classes/PromiseTracker.test.js b/lib/classes/PromiseTracker.test.js new file mode 100644 index 00000000000..0202770d833 --- /dev/null +++ b/lib/classes/PromiseTracker.test.js @@ -0,0 +1,64 @@ +'use strict'; + +/* eslint-disable no-unused-expressions */ + +const BbPromise = require('bluebird'); +const chai = require('chai'); + +const PromiseTracker = require('../../lib/classes/PromiseTracker'); + +chai.use(require('chai-as-promised')); + +const expect = chai.expect; + +/** + * Mostly this class is tested by its use in peer ~/lib/classes/Variables.js + * + * Mostly, I'm creating coverage but if errors are discovered, coverage for the specific cases + * can be created here. + */ +describe('PromiseTracker', () => { + let promiseTracker; + beforeEach(() => { + promiseTracker = new PromiseTracker(); + }); + it('logs a warning without throwing', () => { + promiseTracker.add('foo', BbPromise.resolve(), '${foo:}'); + promiseTracker.add('foo', BbPromise.delay(10), '${foo:}'); + promiseTracker.report(); // shouldn't throw + }); + it('reports no pending promises when none have been added', () => { + const promises = promiseTracker.getPending(); + expect(promises).to.be.an.instanceof(Array); + expect(promises.length).to.equal(0); + }); + it('reports one pending promise when one has been added', () => { + let resolve; + const promise = new BbPromise((rslv) => { resolve = rslv; }); + promiseTracker.add('foo', promise, '${foo:}'); + return BbPromise.delay(1).then(() => { + const promises = promiseTracker.getPending(); + expect(promises).to.be.an.instanceof(Array); + expect(promises.length).to.equal(1); + expect(promises[0]).to.equal(promise); + }).then(() => { resolve(); }); + }); + it('reports no settled promises when none have been added', () => { + const promises = promiseTracker.getSettled(); + expect(promises).to.be.an.instanceof(Array); + expect(promises.length).to.equal(0); + }); + it('reports one settled promise when one has been added', () => { + const promise = BbPromise.resolve(); + promiseTracker.add('foo', promise, '${foo:}'); + promise.state = 'resolved'; + const promises = promiseTracker.getSettled(); + expect(promises).to.be.an.instanceof(Array); + expect(promises.length).to.equal(1); + expect(promises[0]).to.equal(promise); + }); + it('reports no promises when none have been added', () => { + const promises = promiseTracker.getAll(); + expect(promises).to.be.an('array').that.is.empty; + }); +}); diff --git a/lib/classes/Service.js b/lib/classes/Service.js index 68991debd7e..f4bb771d340 100644 --- a/lib/classes/Service.js +++ b/lib/classes/Service.js @@ -8,6 +8,9 @@ const semver = require('semver'); class Service { constructor(serverless, data) { + // ####################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/plugins/print/print.js ## + // ####################################################################### this.serverless = serverless; // Default properties @@ -20,6 +23,7 @@ class Service { }; this.custom = {}; this.plugins = []; + this.pluginsData = {}; this.functions = {}; this.resources = {}; this.package = {}; @@ -107,6 +111,14 @@ class Service { throw new ServerlessError(`"provider" property is missing in ${serviceFilename}`); } + // ####################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/plugins/print/print.js ## + // ####################################################################### + // ##################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/classes/Variables.js ## + // ## there, see `getValueFromSelf` ## + // ## here, see below ## + // ##################################################################### if (!_.isObject(serverlessFile.provider)) { const providerName = serverlessFile.provider; serverlessFile.provider = { @@ -122,6 +134,8 @@ class Service { that.service = serverlessFile.service; } + that.app = serverlessFile.app; + that.tenant = serverlessFile.tenant; that.custom = serverlessFile.custom; that.plugins = serverlessFile.plugins; that.resources = serverlessFile.resources; @@ -163,19 +177,21 @@ class Service { }); } - mergeResourceArrays() { - if (Array.isArray(this.resources)) { - this.resources = this.resources.reduce((memo, value) => { - if (value) { - if (typeof value === 'object') { - return _.merge(memo, value); + mergeArrays() { + ['resources', 'functions'].forEach(key => { + if (Array.isArray(this[key])) { + this[key] = this[key].reduce((memo, value) => { + if (value) { + if (typeof value === 'object') { + return _.merge(memo, value); + } + throw new Error(`Non-object value specified in ${key} array: ${value}`); } - throw new Error(`Non-object value specified in resources array: ${value}`); - } - return memo; - }, {}); - } + return memo; + }, {}); + } + }); } validate() { @@ -228,6 +244,11 @@ class Service { getAllEventsInFunction(functionName) { return this.getFunction(functionName).events; } + + publish(dataParam) { + const data = dataParam || {}; + this.pluginsData = _.merge(this.pluginsData, data); + } } module.exports = Service; diff --git a/lib/classes/Service.test.js b/lib/classes/Service.test.js index 96bf7529fad..446208a2f5b 100644 --- a/lib/classes/Service.test.js +++ b/lib/classes/Service.test.js @@ -669,7 +669,7 @@ describe('Service', () => { }); }); - describe('#mergeResourceArrays', () => { + describe('#mergeArrays', () => { it('should merge resources given as an array', () => { const serverless = new Serverless(); const serviceInstance = new Service(serverless); @@ -691,7 +691,7 @@ describe('Service', () => { }, ]; - serviceInstance.mergeResourceArrays(); + serviceInstance.mergeArrays(); expect(serviceInstance.resources).to.be.an('object'); expect(serviceInstance.resources.Resources).to.be.an('object'); @@ -708,7 +708,7 @@ describe('Service', () => { Resources: 'foo', }; - serviceInstance.mergeResourceArrays(); + serviceInstance.mergeArrays(); expect(serviceInstance.resources).to.deep.eql({ Resources: 'foo', @@ -728,7 +728,7 @@ describe('Service', () => { }, ]; - serviceInstance.mergeResourceArrays(); + serviceInstance.mergeArrays(); expect(serviceInstance.resources).to.deep.eql({ aws: { resourcesProp: 'value', @@ -744,7 +744,7 @@ describe('Service', () => { 42, ]; - expect(() => serviceInstance.mergeResourceArrays()).to.throw(Error); + expect(() => serviceInstance.mergeArrays()).to.throw(Error); }); it('should throw when given a string', () => { @@ -755,7 +755,24 @@ describe('Service', () => { 'string', ]; - expect(() => serviceInstance.mergeResourceArrays()).to.throw(Error); + expect(() => serviceInstance.mergeArrays()).to.throw(Error); + }); + + it('should merge functions given as an array', () => { + const serverless = new Serverless(); + const serviceInstance = new Service(serverless); + + serviceInstance.functions = [{ + a: {}, + }, { + b: {}, + }]; + + serviceInstance.mergeArrays(); + + expect(serviceInstance.functions).to.be.an('object'); + expect(serviceInstance.functions.a).to.be.an('object'); + expect(serviceInstance.functions.b).to.be.an('object'); }); }); diff --git a/lib/classes/Utils.js b/lib/classes/Utils.js index 921a5ee596e..c0b7bb4545c 100644 --- a/lib/classes/Utils.js +++ b/lib/classes/Utils.js @@ -16,6 +16,7 @@ const isDockerContainer = require('is-docker'); const version = require('../../package.json').version; const segment = require('../utils/segment'); const configUtils = require('../utils/config'); +const awsArnRegExs = require('../plugins/aws/utils/arnRegularExpressions'); class Utils { constructor(serverless) { @@ -38,15 +39,15 @@ class Utils { return fse.mkdirsSync(path.dirname(filePath)); } - writeFileSync(filePath, contents) { - return writeFileSync(filePath, contents); + writeFileSync(filePath, contents, cycles) { + return writeFileSync(filePath, contents, cycles); } - writeFile(filePath, contents) { + writeFile(filePath, contents, cycles) { const that = this; return new BbPromise((resolve, reject) => { try { - that.writeFileSync(filePath, contents); + that.writeFileSync(filePath, contents, cycles); } catch (e) { reject(e); } @@ -204,11 +205,11 @@ class Utils { } // For HTTP events, see what authorizer types are enabled - if (event.http && event.http.authorizer) { - if ((typeof event.http.authorizer === 'string' - && event.http.authorizer.toUpperCase() === 'AWS_IAM') - || (event.http.authorizer.type - && event.http.authorizer.type.toUpperCase() === 'AWS_IAM')) { + if (_.has(event, 'http.authorizer')) { + if ((_.isString(event.http.authorizer) + && _.toUpper(event.http.authorizer) === 'AWS_IAM') + || (event.http.authorizer.type + && _.toUpper(event.http.authorizer.type) === 'AWS_IAM')) { hasIAMAuthorizer = true; } // There are three ways a user can specify a Custom authorizer: @@ -217,18 +218,19 @@ class Utils { // 2) By listing the name of a function in the same service for the name property // in the authorizer object. // 3) By listing a function's ARN in the arn property of the authorizer object. - if ((typeof event.http.authorizer === 'string' - && event.http.authorizer.toUpperCase() !== 'AWS_IAM' - && !event.http.authorizer.includes('arn:aws:cognito-idp')) - || event.http.authorizer.name - || (event.http.authorizer.arn - && event.http.authorizer.arn.includes('arn:aws:lambda'))) { + + if ((_.isString(event.http.authorizer) + && _.toUpper(event.http.authorizer) !== 'AWS_IAM' + && !awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer)) + || event.http.authorizer.name + || (event.http.authorizer.arn + && awsArnRegExs.lambdaArnExpr.test(event.http.authorizer.arn))) { hasCustomAuthorizer = true; } - if ((typeof event.http.authorizer === 'string' - && event.http.authorizer.includes('arn:aws:cognito-idp')) - || (event.http.authorizer.arn - && event.http.authorizer.arn.includes('arn:aws:cognito-idp'))) { + if ((_.isString(event.http.authorizer) + && awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer)) + || (event.http.authorizer.arn + && awsArnRegExs.cognitoIdpArnExpr.test(event.http.authorizer.arn))) { hasCognitoAuthorizer = true; } } diff --git a/lib/classes/Variables.js b/lib/classes/Variables.js index 738a0347cc8..7cebde7aa7c 100644 --- a/lib/classes/Variables.js +++ b/lib/classes/Variables.js @@ -1,34 +1,117 @@ 'use strict'; const _ = require('lodash'); -const path = require('path'); -const replaceall = require('replaceall'); -const logWarning = require('./Error').logWarning; const BbPromise = require('bluebird'); const os = require('os'); +const path = require('path'); +const replaceall = require('replaceall'); + const fse = require('../utils/fs/fse'); +const logWarning = require('./Error').logWarning; +const PromiseTracker = require('./PromiseTracker'); +/** + * Maintainer's notes: + * + * This is a tricky class to modify and maintain. A few rules on how it works... + * + * 0. The population of a service consists of pre-population, followed by population. Pre- + * population consists of populating only those settings which are required for variable + * resolution. Current examples include region and stage as they must be resolved to a + * concrete value before they can be used in the provider's `request` method (used by + * `getValueFromCf`, `getValueFromS3`, and `getValueFromSsm`) to resolve the associated values. + * Original issue: #4725 + * 1. All variable populations occur in generations. Each of these generations resolves each + * present variable in the given object or property (i.e. terminal string properties and/or + * property parts) once. This is to say that recursive resolutions should not be made. This is + * because cyclic references are allowed [i.e. ${self:} and the like]) and doing so leads to + * dependency and dead-locking issues. This leads to a problem with deep value population (i.e. + * populating ${self:foo.bar} when ${self:foo} has a value of {opt:bar}). To pause that, one must + * pause population, noting the continued depth to traverse. This motivated "deep" variables. + * Original issue #4687 + * 2. The resolution of variables can get very confusing if the same object is used multiple times. + * An example of this is the print command which *implicitly/invisibly* populates the + * serverless.yml and then *explicitly/visibily* renders the same file again, without the + * adornments that the framework's components make to the originally loaded service. As a result, + * it is important to reset this object for each use. + * 3. Note that despite some AWS code herein that this class is used in all plugins. Obviously + * users avoid the AWS-specific variable types when targetting other clouds. + */ class Variables { - constructor(serverless) { this.serverless = serverless; this.service = this.serverless.service; - this.cache = {}; + this.tracker = new PromiseTracker(); - this.overwriteSyntax = RegExp(/,/g); + this.deep = []; + this.deepRefSyntax = RegExp(/(\${)?deep:\d+(\.[^}]+)*()}?/); + this.overwriteSyntax = RegExp(/\s*(?:,\s*)+/g); this.fileRefSyntax = RegExp(/^file\((~?[a-zA-Z0-9._\-/]+?)\)/g); this.envRefSyntax = RegExp(/^env:/g); this.optRefSyntax = RegExp(/^opt:/g); this.selfRefSyntax = RegExp(/^self:/g); - this.cfRefSyntax = RegExp(/^cf:/g); - this.s3RefSyntax = RegExp(/^s3:(.+?)\/(.+)$/); - this.stringRefSyntax = RegExp(/('.*')|(".*")/g); - this.ssmRefSyntax = RegExp(/^ssm:([a-zA-Z0-9_.\-/]+)[~]?(true|false)?/); + this.stringRefSyntax = RegExp(/(?:('|").*?\1)/g); + this.cfRefSyntax = RegExp(/^(?:\${)?cf:/g); + this.s3RefSyntax = RegExp(/^(?:\${)?s3:(.+?)\/(.+)$/); + this.ssmRefSyntax = RegExp(/^(?:\${)?ssm:([a-zA-Z0-9_.\-/]+)[~]?(true|false)?/); } loadVariableSyntax() { this.variableSyntax = RegExp(this.service.provider.variableSyntax, 'g'); } + + initialCall(func) { + this.deep = []; + this.tracker.start(); + return func().finally(() => { + this.tracker.stop(); + this.deep = []; + }); + } + + // ############# + // ## SERVICE ## + // ############# + disableDepedentServices(func) { + const dependentServices = [ + { name: 'CloudFormation', method: 'getValueFromCf', original: this.getValueFromCf }, + { name: 'S3', method: 'getValueFromS3', original: this.getValueFromS3 }, + { name: 'SSM', method: 'getValueFromSsm', original: this.getValueFromSsm }, + ]; + const dependencyMessage = (configValue, serviceName) => + `Variable dependency failure: variable '${configValue}' references service ${ + serviceName} but using that service requires a concrete value to be called.`; + // replace and then restore the methods for obtaining values from dependent services. the + // replacement naturally rejects dependencies on these services that occur during prepopulation. + // prepopulation is, of course, the process of obtaining the required configuration for using + // these services. + dependentServices.forEach((dependentService) => { // knock out + this[dependentService.method] = (variableString) => BbPromise.reject( + dependencyMessage(variableString, dependentService.name)); + }); + return func() + .finally(() => { + dependentServices.forEach((dependentService) => { // restore + this[dependentService.method] = dependentService.original; + }); + }); + } + prepopulateService() { + const provider = this.serverless.getProvider('aws'); + if (provider) { + const requiredConfigs = [ + _.assign({ name: 'region' }, provider.getRegionSourceValue()), + _.assign({ name: 'stage' }, provider.getStageSourceValue()), + ]; + return this.disableDepedentServices(() => { + const prepopulations = requiredConfigs.map(config => + this.populateValue(config.value, true) // populate + .then(populated => _.assign(config, { populated }))); + return this.assignProperties(provider, prepopulations); + }); + } + return BbPromise.resolve(); + } /** * Populate all variables in the service, conviently remove and restore the service attributes * that confuse the population methods. @@ -36,20 +119,124 @@ class Variables { * @returns {Promise.|*} A promise resolving to the populated service. */ populateService(processedOptions) { + // ####################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/plugins/print/print.js ## + // ####################################################################### this.options = processedOptions || {}; this.loadVariableSyntax(); // store const variableSyntaxProperty = this.service.provider.variableSyntax; // remove - this.service.provider.variableSyntax = true; // matches itself - this.serverless.service.serverless = null; - return this.populateObject(this.service).then(() => { - // restore - this.service.provider.variableSyntax = variableSyntaxProperty; - this.serverless.service.serverless = this.serverless; - return BbPromise.resolve(this.service); - }); + this.service.provider.variableSyntax = undefined; // otherwise matches itself + this.service.serverless = undefined; + return this.initialCall(() => + this.prepopulateService() + .then(() => this.populateObjectImpl(this.service) + .finally(() => { + // restore + this.service.serverless = this.serverless; + this.service.provider.variableSyntax = variableSyntaxProperty; + })) + .then(() => this.service)); + } + // ############ + // ## OBJECT ## + // ############ + /** + * The declaration of a terminal property. This declaration includes the path and value of the + * property. + * Example Input: + * { + * foo: { + * bar: 'baz' + * } + * } + * Example Result: + * [ + * { + * path: ['foo', 'bar'] + * value: 'baz + * } + * ] + * @typedef {Object} TerminalProperty + * @property {String[]} path The path to the terminal property + * @property {Date|RegEx|String} The value of the terminal property + */ + /** + * Generate an array of objects noting the terminal properties of the given root object and their + * paths + * @param root The object to generate a terminal property path/value set for + * @param current The current part of the given root that terminal properties are being sought + * within + * @param [context] An array containing the path to the current object root (intended for internal + * use) + * @param [results] An array of current results (intended for internal use) + * @returns {TerminalProperty[]} The terminal properties of the given root object, with the path + * and value of each + */ + getProperties(root, atRoot, current, cntxt, rslts) { + let context = cntxt; + if (!context) { + context = []; + } + let results = rslts; + if (!results) { + results = []; + } + const addContext = (value, key) => + this.getProperties(root, false, value, context.concat(key), results); + if ( + _.isArray(current) + ) { + _.map(current, addContext); + } else if ( + _.isObject(current) && + !_.isDate(current) && + !_.isRegExp(current) && + !_.isFunction(current) + ) { + if (atRoot || current !== root) { + _.mapValues(current, addContext); + } + } else { + results.push({ path: context, value: current }); + } + return results; + } + + /** + * @typedef {TerminalProperty} TerminalPropertyPopulated + * @property {Object} populated The populated value of the value at the path + */ + /** + * Populate the given terminal properties, returning promises to do so + * @param properties The terminal properties to populate + * @returns {Promise[]} The promises that will resolve to the + * populated values of the given terminal properties + */ + populateVariables(properties) { + const variables = properties.filter(property => + _.isString(property.value) && + property.value.match(this.variableSyntax)); + return _.map(variables, + variable => this.populateValue(variable.value, false) + .then(populated => _.assign({}, variable, { populated }))); + } + /** + * Assign the populated values back to the target object + * @param target The object to which the given populated terminal properties should be applied + * @param populations The fully populated terminal properties + * @returns {Promise} resolving with the number of changes that were applied to the given + * target + */ + assignProperties(target, populations) { // eslint-disable-line class-methods-use-this + return BbPromise.all(populations) + .then((results) => results.forEach((result) => { + if (result.value !== result.populated) { + _.set(target, result.path, result.populated); + } + })); } /** * Populate the variables in the given object. @@ -57,78 +244,126 @@ class Variables { * @returns {Promise.|*} A promise resolving to the in-place populated object. */ populateObject(objectToPopulate) { - // Map terminal values of given root (i.e. for every leaf value...) - const forEachLeaf = (root, context, callback) => { - const addContext = (value, key) => forEachLeaf(value, context.concat(key), callback); - if ( - _.isArray(root) - ) { - return _.map(root, addContext); - } else if ( - _.isObject(root) && - !_.isDate(root) && - !_.isRegExp(root) && - !_.isFunction(root) - ) { - return _.extend({}, root, _.mapValues(root, addContext)); - } - return callback(root, context); - }; - // For every leaf value... - const pendingLeaves = []; - forEachLeaf( - objectToPopulate, - [], - (leafValue, leafPath) => { - if (typeof leafValue === 'string') { - pendingLeaves.push(this - .populateProperty(leafValue, true) - .then(leafValuePopulated => _.set(objectToPopulate, leafPath, leafValuePopulated)) - ); - } - } - ); - return BbPromise.all(pendingLeaves).then(() => objectToPopulate); + return this.initialCall(() => this.populateObjectImpl(objectToPopulate)); } + populateObjectImpl(objectToPopulate) { + const leaves = this.getProperties(objectToPopulate, true, objectToPopulate); + const populations = this.populateVariables(leaves); + if (populations.length === 0) { + return BbPromise.resolve(objectToPopulate); + } + return this.assignProperties(objectToPopulate, populations) + .then(() => this.populateObjectImpl(objectToPopulate)); + } + // ############## + // ## PROPERTY ## + // ############## /** - * Populate variables, in-place if specified, in the given property value. - * @param propertyToPopulate The property to populate (only strings with variables are altered). - * @param populateInPlace Whether to deeply clone the given property prior to population. - * @returns {Promise.|*} A promise resolving to the populated result. + * Standard logic for cleaning a variable + * Example: cleanVariable('${opt:foo}') => 'opt:foo' + * @param match The variable match instance variable part + * @returns {string} The cleaned variable match + */ + cleanVariable(match) { + return match.replace( + this.variableSyntax, + (context, contents) => contents.trim() + ).replace(/\s/g, ''); + } + /** + * @typedef {Object} MatchResult + * @property {String} match The original property value that matched the variable syntax + * @property {String} variable The cleaned variable string that specifies the origin for the + * property value + */ + /** + * Get matches against the configured variable syntax + * @param property The property value to attempt extracting matches from + * @returns {Object|String|MatchResult[]} The given property or the identified matches */ - populateProperty(propertyToPopulate, populateInPlace) { - let property = propertyToPopulate; - if (!populateInPlace) { - property = _.cloneDeep(propertyToPopulate); + getMatches(property) { + if (typeof property !== 'string') { + return property; } - if ( - typeof property !== 'string' || - !property.match(this.variableSyntax) - ) { + const matches = property.match(this.variableSyntax); + if (!matches || !matches.length) { + return property; + } + return _.map(matches, match => ({ + match, + variable: this.cleanVariable(match), + })); + } + /** + * Populate the given matches, returning an array of Promises which will resolve to the populated + * values of the given matches + * @param {MatchResult[]} matches The matches to populate + * @returns {Promise[]} Promises for the eventual populated values of the given matches + */ + populateMatches(matches, property) { + return _.map(matches, (match) => this.splitAndGet(match.variable, property)); + } + /** + * Render the given matches and their associated results to the given value + * @param value The value into which to render the given results + * @param matches The matches on the given value where the results are to be rendered + * @param results The results that are to be rendered to the given value + * @returns {*} The populated value with the given results rendered according to the given matches + */ + renderMatches(value, matches, results) { + let result = value; + for (let i = 0; i < matches.length; i += 1) { + this.warnIfNotFound(matches[i].variable, results[i]); + result = this.populateVariable(result, matches[i].match, results[i]); + } + return result; + } + /** + * Populate the given value, recursively if root is true + * @param valueToPopulate The value to populate variables within + * @param root Whether the caller is the root populator and thereby whether to recursively + * populate + * @returns {PromiseLike} A promise that resolves to the populated value, recursively if root + * is true + */ + populateValue(valueToPopulate, root) { + const property = valueToPopulate; + const matches = this.getMatches(property); + if (!_.isArray(matches)) { return BbPromise.resolve(property); } - const pendingMatches = []; - property.match(this.variableSyntax).forEach((matchedString) => { - const variableString = matchedString - .replace(this.variableSyntax, (match, varName) => varName.trim()) - .replace(/\s/g, ''); - - let pendingMatch; - if (variableString.match(this.overwriteSyntax)) { - pendingMatch = this.overwrite(variableString); - } else { - pendingMatch = this.getValueFromSource(variableString); - } - pendingMatches.push(pendingMatch.then(matchedValue => { - this.warnIfNotFound(variableString, matchedValue); - return this.populateVariable(property, matchedString, matchedValue) - .then((populatedProperty) => { - property = populatedProperty; - }); - })); - }); - return BbPromise.all(pendingMatches) - .then(() => this.populateProperty(property, true)); + const populations = this.populateMatches(matches, valueToPopulate); + return BbPromise.all(populations) + .then(results => this.renderMatches(property, matches, results)) + .then((result) => { + if (root && matches.length) { + return this.populateValue(result, root); + } + return result; + }); + } + /** + * Populate variables in the given property. + * @param propertyToPopulate The property to populate (replace variables with their values). + * @returns {Promise.|*} A promise resolving to the populated result. + */ + populateProperty(propertyToPopulate) { + return this.initialCall(() => this.populateValue(propertyToPopulate, true)); + } + + /** + * Split the cleaned variable string containing one or more comma delimited variables and get a + * final value for the entirety of the string + * @param varible The variable string to split and get a final value for + * @param property The original property string the given variable was extracted from + * @returns {Promise} A promise resolving to the final value of the given variable + */ + splitAndGet(variable, property) { + const parts = this.splitByComma(variable); + if (parts.length > 1) { + return this.overwrite(parts, property); + } + return this.getValueFromSource(parts[0], property); } /** * Populate a given property, given the matched string to replace and the value to replace the @@ -155,20 +390,63 @@ class Variables { ].join(''); throw new this.serverless.classes.Error(errorMessage); } - return BbPromise.resolve(property); + return property; + } + // ############### + // ## VARIABLES ## + // ############### + /** + * Split a given string by whitespace padded commas excluding those within single or double quoted + * strings. + * @param string The string to split by comma. + */ + splitByComma(string) { + const input = string.trim(); + const stringMatches = []; + let match = this.stringRefSyntax.exec(input); + while (match) { + stringMatches.push({ + start: match.index, + end: this.stringRefSyntax.lastIndex, + }); + match = this.stringRefSyntax.exec(input); + } + const commaReplacements = []; + const contained = commaMatch => // curry the current commaMatch + stringMatch => // check whether stringMatch containing the commaMatch + stringMatch.start < commaMatch.index && + this.overwriteSyntax.lastIndex < stringMatch.end; + match = this.overwriteSyntax.exec(input); + while (match) { + const matchContained = contained(match); + const containedBy = stringMatches.find(matchContained); + if (!containedBy) { // if uncontained, this comma respresents a splitting location + commaReplacements.push({ + start: match.index, + end: this.overwriteSyntax.lastIndex, + }); + } + match = this.overwriteSyntax.exec(input); + } + let prior = 0; + const results = []; + commaReplacements.forEach((replacement) => { + results.push(input.slice(prior, replacement.start)); + prior = replacement.end; + }); + results.push(input.slice(prior)); + return results; } /** - * Overwrite the given variable string, resolve each variable and resolve to the first valid - * value. + * Resolve the given variable string that expresses a series of fallback values in case the + * initial values are not valid, resolving each variable and resolving to the first valid value. * @param variableStringsString The overwrite string of variables to populate and choose from. * @returns {Promise.|*} A promise resolving to the first validly populating variable * in the given variable strings string. */ - overwrite(variableStringsString) { - const variableStrings = variableStringsString.split(','); + overwrite(variableStrings, propertyString) { const variableValues = variableStrings.map(variableString => - this.getValueFromSource(variableString) - ); + this.getValueFromSource(variableString, propertyString)); const validValue = value => ( value !== null && typeof value !== 'undefined' && @@ -176,53 +454,50 @@ class Variables { ); return BbPromise.all(variableValues) .then(values => // find and resolve first valid value, undefined if none - BbPromise.resolve(values.find(validValue)) - ); + BbPromise.resolve(values.find(validValue))); } /** * Given any variable string, return the value it should be populated with. * @param variableString The variable string to retrieve a value for. * @returns {Promise.|*} A promise resolving to the given variables value. */ - getValueFromSource(variableString) { - if (!(variableString in this.cache)) { - let value; + getValueFromSource(variableString, propertyString) { + let ret; + if (this.tracker.contains(variableString)) { + ret = this.tracker.get(variableString, propertyString); + } else { if (variableString.match(this.envRefSyntax)) { - value = this.getValueFromEnv(variableString); + ret = this.getValueFromEnv(variableString); } else if (variableString.match(this.optRefSyntax)) { - value = this.getValueFromOptions(variableString); + ret = this.getValueFromOptions(variableString); } else if (variableString.match(this.selfRefSyntax)) { - value = this.getValueFromSelf(variableString); + ret = this.getValueFromSelf(variableString); } else if (variableString.match(this.fileRefSyntax)) { - value = this.getValueFromFile(variableString); + ret = this.getValueFromFile(variableString); } else if (variableString.match(this.cfRefSyntax)) { - value = this.getValueFromCf(variableString); + ret = this.getValueFromCf(variableString); } else if (variableString.match(this.s3RefSyntax)) { - value = this.getValueFromS3(variableString); + ret = this.getValueFromS3(variableString); } else if (variableString.match(this.stringRefSyntax)) { - value = this.getValueFromString(variableString); + ret = this.getValueFromString(variableString); } else if (variableString.match(this.ssmRefSyntax)) { - value = this.getValueFromSsm(variableString); + ret = this.getValueFromSsm(variableString); + } else if (variableString.match(this.deepRefSyntax)) { + ret = this.getValueFromDeep(variableString); } else { const errorMessage = [ `Invalid variable reference syntax for variable ${variableString}.`, ' You can only reference env vars, options, & files.', ' You can check our docs for more info.', ].join(''); - throw new this.serverless.classes.Error(errorMessage); + ret = BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } - this.cache[variableString] = BbPromise.resolve(value) - .then(variableValue => { - if (_.isObject(variableValue) && variableValue !== this.service) { - return this.populateObject(variableValue); - } - return variableValue; - }); + ret = this.tracker.add(variableString, ret, propertyString); } - return this.cache[variableString]; + return ret; } - getValueFromEnv(variableString) { + getValueFromEnv(variableString) { // eslint-disable-line class-methods-use-this const requestedEnvVar = variableString.split(':')[1]; let valueToPopulate; if (requestedEnvVar !== '' || '' in process.env) { @@ -233,7 +508,7 @@ class Variables { return BbPromise.resolve(valueToPopulate); } - getValueFromString(variableString) { + getValueFromString(variableString) { // eslint-disable-line class-methods-use-this const valueToPopulate = variableString.replace(/^['"]|['"]$/g, ''); return BbPromise.resolve(valueToPopulate); } @@ -250,9 +525,24 @@ class Variables { } getValueFromSelf(variableString) { + const selfServiceRex = /self:service\./; + let variable = variableString; + // ################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/classes/Service.js ## + // ## there, see `loadServiceFileParam` ## + // ################################################################### + // The loaded service is altered during load in ~/lib/classes/Service (see loadServiceFileParam) + // Account for these so that user's reference to their file populate properly + if (variable === 'self:service.name') { + variable = 'self:service'; + } else if (variable.match(selfServiceRex)) { + variable = variable.replace(selfServiceRex, 'self:serviceObject.'); + } else if (variable === 'self:provider') { + variable = 'self:provider.name'; + } const valueToPopulate = this.service; - const deepProperties = variableString.split(':')[1].split('.'); - return this.getDeepValue(deepProperties, valueToPopulate); + const deepProperties = variable.split(':')[1].split('.').filter(property => property); + return this.getDeeperValue(deepProperties, valueToPopulate); } getValueFromFile(variableString) { @@ -262,13 +552,13 @@ class Variables { .replace('~', os.homedir()); let referencedFileFullPath = (path.isAbsolute(referencedFileRelativePath) ? - referencedFileRelativePath : - path.join(this.serverless.config.servicePath, referencedFileRelativePath)); + referencedFileRelativePath : + path.join(this.serverless.config.servicePath, referencedFileRelativePath)); // Get real path to handle potential symlinks (but don't fatal error) referencedFileFullPath = fse.existsSync(referencedFileFullPath) ? - fse.realpathSync(referencedFileFullPath) : - referencedFileFullPath; + fse.realpathSync(referencedFileFullPath) : + referencedFileFullPath; let fileExtension = referencedFileRelativePath.split('.'); fileExtension = fileExtension[fileExtension.length - 1]; @@ -281,7 +571,8 @@ class Variables { // Process JS files if (fileExtension === 'js') { - const jsFile = require(referencedFileFullPath); // eslint-disable-line global-require + // eslint-disable-next-line global-require, import/no-dynamic-require + const jsFile = require(referencedFileFullPath); const variableArray = variableString.split(':'); let returnValueFunction; if (variableArray[1]) { @@ -293,29 +584,28 @@ class Variables { } if (typeof returnValueFunction !== 'function') { - throw new this.serverless.classes - .Error([ - 'Invalid variable syntax when referencing', - ` file "${referencedFileRelativePath}".`, - ' Check if your javascript is exporting a function that returns a value.', - ].join('')); + const errorMessage = [ + 'Invalid variable syntax when referencing', + ` file "${referencedFileRelativePath}".`, + ' Check if your javascript is exporting a function that returns a value.', + ].join(''); + return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } - valueToPopulate = returnValueFunction.call(jsFile); + valueToPopulate = returnValueFunction.call(jsFile, this.serverless); - return BbPromise.resolve(valueToPopulate).then(valueToPopulateResolved => { + return BbPromise.resolve(valueToPopulate).then((valueToPopulateResolved) => { let deepProperties = variableString.replace(matchedFileRefString, ''); deepProperties = deepProperties.slice(1).split('.'); deepProperties.splice(0, 1); - return this.getDeepValue(deepProperties, valueToPopulateResolved) - .then(deepValueToPopulateResolved => { + return this.getDeeperValue(deepProperties, valueToPopulateResolved) + .then((deepValueToPopulateResolved) => { if (typeof deepValueToPopulateResolved === 'undefined') { const errorMessage = [ 'Invalid variable syntax when referencing', ` file "${referencedFileRelativePath}".`, ' Check if your javascript is returning the correct data.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } return BbPromise.resolve(deepValueToPopulateResolved); }); @@ -334,11 +624,10 @@ class Variables { ` file "${referencedFileRelativePath}" sub properties`, ' Please use ":" to reference sub properties.', ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } deepProperties = deepProperties.slice(1).split('.'); - return this.getDeepValue(deepProperties, valueToPopulate); + return this.getDeeperValue(deepProperties, valueToPopulate); } } return BbPromise.resolve(valueToPopulate); @@ -352,9 +641,8 @@ class Variables { .request('CloudFormation', 'describeStacks', { StackName: stackName }, - { useCache: true } // Use request cache - ) - .then(result => { + { useCache: true })// Use request cache + .then((result) => { const outputs = result.Stacks[0].Outputs; const output = outputs.find(x => x.OutputKey === outputLogicalId); @@ -364,11 +652,9 @@ class Variables { ` Stack name: "${stackName}"`, ` Requested variable: "${outputLogicalId}".`, ].join(''); - throw new this.serverless.classes - .Error(errorMessage); + return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); } - - return output.OutputValue; + return BbPromise.resolve(output.OutputValue); }); } @@ -376,62 +662,113 @@ class Variables { const groups = variableString.match(this.s3RefSyntax); const bucket = groups[1]; const key = groups[2]; - return this.serverless.getProvider('aws') - .request('S3', + return this.serverless.getProvider('aws').request( + 'S3', 'getObject', { Bucket: bucket, Key: key, }, - { useCache: true } // Use request cache - ) - .then( - response => response.Body.toString(), - err => { + { useCache: true }) // Use request cache + .then(response => BbPromise.resolve(response.Body.toString())) + .catch((err) => { const errorMessage = `Error getting value for ${variableString}. ${err.message}`; - throw new this.serverless.classes.Error(errorMessage); - } - ); + return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); + }); } getValueFromSsm(variableString) { const groups = variableString.match(this.ssmRefSyntax); const param = groups[1]; const decrypt = (groups[2] === 'true'); - return this.serverless.getProvider('aws') - .request('SSM', + return this.serverless.getProvider('aws').request( + 'SSM', 'getParameter', { Name: param, WithDecryption: decrypt, }, - { useCache: true } // Use request cache - ) - .then( - response => BbPromise.resolve(response.Parameter.Value), - err => { + { useCache: true }) // Use request cache + .then(response => BbPromise.resolve(response.Parameter.Value)) + .catch((err) => { const expectedErrorMessage = `Parameter ${param} not found.`; if (err.message !== expectedErrorMessage) { - throw new this.serverless.classes.Error(err.message); + return BbPromise.reject(new this.serverless.classes.Error(err.message)); } return BbPromise.resolve(undefined); - } - ); + }); } - getDeepValue(deepProperties, valueToPopulate) { - return BbPromise.reduce(deepProperties, (computedValueToPopulateParam, subProperty) => { - let computedValueToPopulate = computedValueToPopulateParam; - if (typeof computedValueToPopulate === 'undefined') { - computedValueToPopulate = {}; - } else if (subProperty !== '' || '' in computedValueToPopulate) { - computedValueToPopulate = computedValueToPopulate[subProperty]; - } - if (typeof computedValueToPopulate === 'string' && - computedValueToPopulate.match(this.variableSyntax)) { - return this.populateProperty(computedValueToPopulate, true); + getDeepIndex(variableString) { + const deepIndexReplace = RegExp(/^deep:|(\.[^}]+)*$/g); + return variableString.replace(deepIndexReplace, ''); + } + getVariableFromDeep(variableString) { + const index = this.getDeepIndex(variableString); + return this.deep[index]; + } + getValueFromDeep(variableString) { + const deepPrefixReplace = RegExp(/(?:^deep:)\d+\.?/g); + const variable = this.getVariableFromDeep(variableString); + const deepRef = variableString.replace(deepPrefixReplace, ''); + let ret = this.populateValue(variable); + if (deepRef.length) { // if there is a deep reference remaining + ret = ret.then((result) => { + if (_.isString(result) && result.match(this.variableSyntax)) { + const deepVariable = this.makeDeepVariable(result); + return BbPromise.resolve(this.appendDeepVariable(deepVariable, deepRef)); + } + return this.getDeeperValue(deepRef.split('.'), result); + }); + } + return ret; + } + + makeDeepVariable(variable) { + let index = this.deep.findIndex((item) => variable === item); + if (index < 0) { + index = this.deep.push(variable) - 1; + } + const variableContainer = variable.match(this.variableSyntax)[0]; + const variableString = this.cleanVariable(variableContainer); + return variableContainer + .replace(/\s/g, '') + .replace(variableString, `deep:${index}`); + } + appendDeepVariable(variable, subProperty) { + return `${variable.slice(0, variable.length - 1)}.${subProperty}}`; + } + + /** + * Get a value that is within the given valueToPopulate. The deepProperties specify what value + * to retrieve from the given valueToPopulate. The trouble is that anywhere along this chain a + * variable can be discovered. If this occurs, to avoid cyclic dependencies, the resolution of + * the deep value from the given valueToPopulate must be halted. The discovered variable is thus + * set aside into a "deep variable" (see makeDeepVariable). The indexing into the given + * valueToPopulate is then resolved with a replacement ${deep:${index}.${remaining.properties}} + * variable (e.g. ${deep:1.foo}). This pauses the population for continuation during the next + * generation of evaluation (see getValueFromDeep) + * @param deepProperties The "path" of properties to follow in obtaining the deeper value + * @param valueToPopulate The value from which to obtain the deeper value + * @returns {Promise} A promise resolving to the deeper value or to a `deep` variable that + * will later resolve to the deeper value + */ + getDeeperValue(deepProperties, valueToPopulate) { + return BbPromise.reduce(deepProperties, (reducedValueParam, subProperty) => { + let reducedValue = reducedValueParam; + if (_.isString(reducedValue) && reducedValue.match(this.deepRefSyntax)) { // build mode + reducedValue = this.appendDeepVariable(reducedValue, subProperty); + } else { // get mode + if (typeof reducedValue === 'undefined') { + reducedValue = {}; + } else if (subProperty !== '' || '' in reducedValue) { + reducedValue = reducedValue[subProperty]; + } + if (typeof reducedValue === 'string' && reducedValue.match(this.variableSyntax)) { + reducedValue = this.makeDeepVariable(reducedValue); + } } - return BbPromise.resolve(computedValueToPopulate); + return BbPromise.resolve(reducedValue); }, valueToPopulate); } @@ -453,10 +790,10 @@ class Variables { } else if (variableString.match(this.ssmRefSyntax)) { varType = 'SSM parameter'; } - logWarning( - `A valid ${varType} to satisfy the declaration '${variableString}' could not be found.` - ); + logWarning(`A valid ${varType} to satisfy the declaration '${ + variableString}' could not be found.`); } + return valueToPopulate; } } diff --git a/lib/classes/Variables.test.js b/lib/classes/Variables.test.js index e59b552aada..1c8ec69eb24 100644 --- a/lib/classes/Variables.test.js +++ b/lib/classes/Variables.test.js @@ -2,36 +2,43 @@ /* eslint-disable no-unused-expressions */ +const BbPromise = require('bluebird'); +const chai = require('chai'); const jc = require('json-cycle'); +const os = require('os'); const path = require('path'); const proxyquire = require('proxyquire'); +const sinon = require('sinon'); const YAML = require('js-yaml'); -const chai = require('chai'); -const Variables = require('../../lib/classes/Variables'); -const Utils = require('../../lib/classes/Utils'); + +const AwsProvider = require('../plugins/aws/provider/awsProvider'); const fse = require('../utils/fs/fse'); const Serverless = require('../../lib/Serverless'); -const sinon = require('sinon'); -const testUtils = require('../../tests/utils'); const slsError = require('./Error'); -const AwsProvider = require('../plugins/aws/provider/awsProvider'); -const BbPromise = require('bluebird'); -const os = require('os'); +const testUtils = require('../../tests/utils'); +const Utils = require('../../lib/classes/Utils'); +const Variables = require('../../lib/classes/Variables'); + +BbPromise.longStackTraces(true); chai.use(require('chai-as-promised')); chai.use(require('sinon-chai')); +chai.should(); + const expect = chai.expect; + describe('Variables', () => { + let serverless; + beforeEach(() => { + serverless = new Serverless(); + }); describe('#constructor()', () => { - const serverless = new Serverless(); - it('should attach serverless instance', () => { const variablesInstance = new Variables(serverless); - expect(typeof variablesInstance.serverless.version).to.be.equal('string'); + expect(variablesInstance.serverless).to.equal(serverless); }); - it('should not set variableSyntax in constructor', () => { const variablesInstance = new Variables(serverless); expect(variablesInstance.variableSyntax).to.be.undefined; @@ -40,110 +47,244 @@ describe('Variables', () => { describe('#loadVariableSyntax()', () => { it('should set variableSyntax', () => { - const serverless = new Serverless(); - + // eslint-disable-next-line no-template-curly-in-string serverless.service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}}'; - serverless.variables.loadVariableSyntax(); expect(serverless.variables.variableSyntax).to.be.a('RegExp'); }); }); describe('#populateService()', () => { - it('should call populateProperty method', () => { - const serverless = new Serverless(); - - const populatePropertyStub = sinon - .stub(serverless.variables, 'populateObject').resolves(); - - return expect(serverless.variables.populateService()).to.be.fulfilled - .then(() => { - expect(populatePropertyStub.called).to.be.true; - }) - .finally(() => serverless.variables.populateObject.restore()); + it('should remove problematic attributes bofore calling populateObjectImpl with the service', + () => { + const prepopulateServiceStub = sinon.stub(serverless.variables, 'prepopulateService') + .returns(BbPromise.resolve()); + const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl', (val) => { + expect(val).to.equal(serverless.service); + expect(val.provider.variableSyntax).to.be.undefined; + expect(val.serverless).to.be.undefined; + return BbPromise.resolve(); + }); + return serverless.variables.populateService().should.be.fulfilled + .then().finally(() => { + prepopulateServiceStub.restore(); + populateObjectStub.restore(); + }); + }); + it('should clear caches and remaining state *before* [pre]populating service', + () => { + const prepopulateServiceStub = sinon.stub(serverless.variables, 'prepopulateService', + (val) => { + expect(serverless.variables.deep).to.eql([]); + expect(serverless.variables.tracker.getAll()).to.eql([]); + return BbPromise.resolve(val); + }); + const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl', + (val) => { + expect(serverless.variables.deep).to.eql([]); + expect(serverless.variables.tracker.getAll()).to.eql([]); + return BbPromise.resolve(val); + }); + serverless.variables.deep.push('${foo:}'); + const prms = BbPromise.resolve('foo'); + serverless.variables.tracker.add('foo:', prms, '${foo:}'); + prms.state = 'resolved'; + return serverless.variables.populateService().should.be.fulfilled + .then().finally(() => { + prepopulateServiceStub.restore(); + populateObjectStub.restore(); + }); + }); + it('should clear caches and remaining *after* [pre]populating service', + () => { + const prepopulateServiceStub = sinon.stub(serverless.variables, 'prepopulateService', + (val) => { + serverless.variables.deep.push('${foo:}'); + const promise = BbPromise.resolve(val); + serverless.variables.tracker.add('foo:', promise, '${foo:}'); + promise.state = 'resolved'; + return BbPromise.resolve(); + }); + const populateObjectStub = sinon.stub(serverless.variables, 'populateObjectImpl', + (val) => { + serverless.variables.deep.push('${bar:}'); + const promise = BbPromise.resolve(val); + serverless.variables.tracker.add('bar:', promise, '${bar:}'); + promise.state = 'resolved'; + return BbPromise.resolve(); + }); + return serverless.variables.populateService().should.be.fulfilled + .then(() => { + expect(serverless.variables.deep).to.eql([]); + expect(serverless.variables.tracker.getAll()).to.eql([]); + }) + .finally(() => { + prepopulateServiceStub.restore(); + populateObjectStub.restore(); + }); + }); + }); + describe('#prepopulateService', () => { + // TL;DR: call populateService to test prepopulateService (note addition of 'pre') + // + // The prepopulateService method basically assumes invocation of of populateService (i.e. that + // variable syntax is loaded, and that the service object is cleaned up. Just use + // populateService to do that work. + let awsProvider; + let populateObjectImplStub; + let requestStub; // just in case... don't want to actually call... + beforeEach(() => { + awsProvider = new AwsProvider(serverless, {}); + populateObjectImplStub = sinon.stub(serverless.variables, 'populateObjectImpl'); + populateObjectImplStub.withArgs(serverless.variables.service).returns(BbPromise.resolve()); + requestStub = sinon.stub(awsProvider, 'request', () => + BbPromise.reject(new Error('unexpected'))); }); - - it('should use variableSyntax', () => { - const serverless = new Serverless(); - - const variableSyntax = '\\${{([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}}'; - const fooValue = '${clientId()}'; - const barValue = 'test'; - - serverless.service.provider.variableSyntax = variableSyntax; - - serverless.service.custom = { - var: barValue, - }; - - serverless.service.resources = { - foo: fooValue, - bar: '${{self:custom.var}}', - }; - - return serverless.variables.populateService().then(() => { - expect(serverless.service.provider.variableSyntax).to.equal(variableSyntax); - expect(serverless.service.resources.foo).to.equal(fooValue); - expect(serverless.service.resources.bar).to.equal(barValue); + afterEach(() => { + populateObjectImplStub.restore(); + requestStub.restore(); + }); + const prepopulatedProperties = [ + { name: 'region', getter: (provider) => provider.getRegion() }, + { name: 'stage', getter: (provider) => provider.getStage() }, + ]; + describe('basic population tests', () => { + prepopulatedProperties.forEach((property) => { + it(`should populate variables in ${property.name} values`, () => { + awsProvider.options[property.name] = '${self:foobar, "default"}'; + return serverless.variables.populateService().should.be.fulfilled + .then(() => expect(property.getter(awsProvider)).to.be.eql('default')); + }); + }); + }); + // + describe('dependent service rejections', () => { + const dependentConfigs = [ + { value: '${cf:stack.value}', name: 'CloudFormation' }, + { value: '${s3:bucket/key}', name: 'S3' }, + { value: '${ssm:/path/param}', name: 'SSM' }, + ]; + prepopulatedProperties.forEach(property => { + dependentConfigs.forEach(config => { + it(`should reject ${config.name} variables in ${property.name} values`, () => { + awsProvider.options[property.name] = config.value; + return serverless.variables.populateService() + .should.be.rejectedWith('Variable dependency failure'); + }); + it(`should reject recursively dependent ${config.name} service dependencies`, () => { + serverless.variables.service.custom = { + settings: config.value, + }; + awsProvider.options.region = '${self:custom.settings.region}'; + return serverless.variables.populateService() + .should.be.rejectedWith('Variable dependency failure'); + }); + }); + }); + }); + describe('dependent service non-interference', () => { + const stateCombinations = [ + { region: 'foo', state: 'bar' }, + { region: 'foo', state: '${self:bar, "bar"}' }, + { region: '${self:foo, "foo"}', state: 'bar' }, + { region: '${self:foo, "foo"}', state: '${self:bar, "bar"}' }, + ]; + stateCombinations.forEach((combination) => { + it('must leave the dependent services in their original state', () => { + const dependentMethods = [ + { name: 'getValueFromCf', original: serverless.variables.getValueFromCf }, + { name: 'getValueFromS3', original: serverless.variables.getValueFromS3 }, + { name: 'getValueFromSsm', original: serverless.variables.getValueFromSsm }, + ]; + awsProvider.options.region = combination.region; + awsProvider.options.state = combination.state; + return serverless.variables.populateService().should.be.fulfilled + .then(() => { + dependentMethods.forEach((method) => { + expect(serverless.variables[method.name]).to.equal(method.original); + }); + }); + }); }); }); }); - describe('#populateObject()', () => { - it('should call populateProperty method', () => { - const serverless = new Serverless(); - const object = { - stage: '${opt:stage}', + describe('#getProperties', () => { + it('extracts all terminal properties of an object', () => { + const date = new Date(); + const regex = /^.*$/g; + const func = () => {}; + const obj = { + foo: { + bar: 'baz', + biz: 'buz', + }, + b: [ + { c: 'd' }, + { e: 'f' }, + ], + g: date, + h: regex, + i: func, }; - - const populatePropertyStub = sinon - .stub(serverless.variables, 'populateProperty').resolves('prod'); - - return serverless.variables.populateObject(object).then(() => { - expect(populatePropertyStub.called).to.be.true; - }) - .finally(() => serverless.variables.populateProperty.restore()); + const expected = [ + { path: ['foo', 'bar'], value: 'baz' }, + { path: ['foo', 'biz'], value: 'buz' }, + { path: ['b', 0, 'c'], value: 'd' }, + { path: ['b', 1, 'e'], value: 'f' }, + { path: ['g'], value: date }, + { path: ['h'], value: regex }, + { path: ['i'], value: func }, + ]; + const result = serverless.variables.getProperties(obj, true, obj); + expect(result).to.eql(expected); }); + it('ignores self references', () => { + const obj = {}; + obj.self = obj; + const expected = []; + const result = serverless.variables.getProperties(obj, true, obj); + expect(result).to.eql(expected); + }); + }); + describe('#populateObject()', () => { + beforeEach(() => { + serverless.variables.loadVariableSyntax(); + }); it('should populate object and return it', () => { - const serverless = new Serverless(); const object = { - stage: '${opt:stage}', + stage: '${opt:stage}', // eslint-disable-line no-template-curly-in-string }; const expectedPopulatedObject = { stage: 'prod', }; - sinon.stub(serverless.variables, 'populateProperty').resolves('prod'); + sinon.stub(serverless.variables, 'populateValue').resolves('prod'); - return serverless.variables.populateObject(object).then(populatedObject => { + return serverless.variables.populateObject(object).then((populatedObject) => { expect(populatedObject).to.deep.equal(expectedPopulatedObject); }) - .finally(() => serverless.variables.populateProperty.restore()); + .finally(() => serverless.variables.populateValue.restore()); }); it('should persist keys with dot notation', () => { - const serverless = new Serverless(); const object = { - stage: '${opt:stage}', + stage: '${opt:stage}', // eslint-disable-line no-template-curly-in-string }; object['some.nested.key'] = 'hello'; const expectedPopulatedObject = { stage: 'prod', }; expectedPopulatedObject['some.nested.key'] = 'hello'; - - const populatePropertyStub = sinon.stub(serverless.variables, 'populateProperty'); - populatePropertyStub.onCall(0).resolves('prod'); - populatePropertyStub.onCall(1).resolves('hello'); - - return serverless.variables.populateObject(object).then(populatedObject => { - expect(populatedObject).to.deep.equal(expectedPopulatedObject); - }) - .finally(() => serverless.variables.populateProperty.restore()); + const populateValueStub = sinon.stub(serverless.variables, 'populateValue', + // eslint-disable-next-line no-template-curly-in-string + val => (val === '${opt:stage}' ? BbPromise.resolve('prod') : BbPromise.resolve(val))); + return serverless.variables.populateObject(object) + .should.become(expectedPopulatedObject) + .then().finally(() => populateValueStub.restore()); }); describe('significant variable usage corner cases', () => { - let serverless; let service; const makeDefault = () => ({ service: 'my-service', @@ -152,8 +293,8 @@ describe('Variables', () => { }, }); beforeEach(() => { - serverless = new Serverless(); service = makeDefault(); + // eslint-disable-next-line no-template-curly-in-string service.provider.variableSyntax = '\\${([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}'; // default serverless.variables.service = service; serverless.variables.loadVariableSyntax(); @@ -161,7 +302,7 @@ describe('Variables', () => { }); it('should properly replace self-references', () => { service.custom = { - me: '${self:}', + me: '${self:}', // eslint-disable-line no-template-curly-in-string }; const expected = makeDefault(); expected.custom = { @@ -174,7 +315,7 @@ describe('Variables', () => { it('should properly populate embedded variables', () => { service.custom = { val0: 'my value 0', - val1: '0', + val1: '0', // eslint-disable-next-line no-template-curly-in-string val2: '${self:custom.val${self:custom.val1}}', }; const expected = { @@ -188,7 +329,7 @@ describe('Variables', () => { }); it('should properly populate an overwrite with a default value that is a string', () => { service.custom = { - val0: 'my value', + val0: 'my value', // eslint-disable-next-line no-template-curly-in-string val1: '${self:custom.NOT_A_VAL1, self:custom.NOT_A_VAL2, "string"}', }; const expected = { @@ -201,7 +342,7 @@ describe('Variables', () => { }); it('should properly populate overwrites where the first value is valid', () => { service.custom = { - val0: 'my value', + val0: 'my value', // eslint-disable-next-line no-template-curly-in-string val1: '${self:custom.val0, self:custom.NOT_A_VAL1, self:custom.NOT_A_VAL2}', }; const expected = { @@ -214,7 +355,7 @@ describe('Variables', () => { }); it('should properly populate overwrites where the middle value is valid', () => { service.custom = { - val0: 'my value', + val0: 'my value', // eslint-disable-next-line no-template-curly-in-string val1: '${self:custom.NOT_A_VAL1, self:custom.val0, self:custom.NOT_A_VAL2}', }; const expected = { @@ -227,7 +368,7 @@ describe('Variables', () => { }); it('should properly populate overwrites where the last value is valid', () => { service.custom = { - val0: 'my value', + val0: 'my value', // eslint-disable-next-line no-template-curly-in-string val1: '${self:custom.NOT_A_VAL1, self:custom.NOT_A_VAL2, self:custom.val0}', }; const expected = { @@ -241,7 +382,7 @@ describe('Variables', () => { it('should properly populate overwrites with nested variables in the first value', () => { service.custom = { val0: 'my value', - val1: 0, + val1: 0, // eslint-disable-next-line no-template-curly-in-string val2: '${self:custom.val${self:custom.val1}, self:custom.NO_1, self:custom.NO_2}', }; const expected = { @@ -256,7 +397,7 @@ describe('Variables', () => { it('should properly populate overwrites with nested variables in the middle value', () => { service.custom = { val0: 'my value', - val1: 0, + val1: 0, // eslint-disable-next-line no-template-curly-in-string val2: '${self:custom.NO_1, self:custom.val${self:custom.val1}, self:custom.NO_2}', }; const expected = { @@ -271,7 +412,7 @@ describe('Variables', () => { it('should properly populate overwrites with nested variables in the last value', () => { service.custom = { val0: 'my value', - val1: 0, + val1: 0, // eslint-disable-next-line no-template-curly-in-string val2: '${self:custom.NO_1, self:custom.NO_2, self:custom.val${self:custom.val1}}', }; const expected = { @@ -286,8 +427,8 @@ describe('Variables', () => { it('should properly replace duplicate variable declarations', () => { service.custom = { val0: 'my value', - val1: '${self:custom.val0}', - val2: '${self:custom.val0}', + val1: '${self:custom.val0}', // eslint-disable-line no-template-curly-in-string + val2: '${self:custom.val0}', // eslint-disable-line no-template-curly-in-string }; const expected = { val0: 'my value', @@ -300,10 +441,10 @@ describe('Variables', () => { }); it('should recursively populate, regardless of order and duplication', () => { service.custom = { - val1: '${self:custom.depVal}', - depVal: '${self:custom.val0}', + val1: '${self:custom.depVal}', // eslint-disable-line no-template-curly-in-string + depVal: '${self:custom.val0}', // eslint-disable-line no-template-curly-in-string val0: 'my value', - val2: '${self:custom.depVal}', + val2: '${self:custom.depVal}', // eslint-disable-line no-template-curly-in-string }; const expected = { val1: 'my value', @@ -315,11 +456,210 @@ describe('Variables', () => { expect(result).to.eql(expected); })).to.be.fulfilled; }); - const pathAsyncLoadJs = 'async.load.js'; - const makeAsyncLoadJs = () => { - const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); - const fileContent = `'use strict'; + // see https://github.com/serverless/serverless/pull/4713#issuecomment-366975172 + it('should handle deep references into deep variables', () => { + service.provider.stage = 'dev'; + service.custom = { + stage: '${env:stage, self:provider.stage}', + secrets: '${self:custom.${self:custom.stage}}', + dev: { + SECRET: 'secret', + }, + environment: { + SECRET: '${self:custom.secrets.SECRET}', + }, + }; + const expected = { + stage: 'dev', + secrets: { + SECRET: 'secret', + }, + dev: { + SECRET: 'secret', + }, + environment: { + SECRET: 'secret', + }, + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle deep variables that reference overrides', () => { + service.custom = { + val1: '${self:not.a.value, "bar"}', + val2: '${self:custom.val1}', + }; + const expected = { + val1: 'bar', + val2: 'bar', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle deep references into deep variables', () => { + service.custom = { + val0: { + foo: 'bar', + }, + val1: '${self:custom.val0}', + val2: '${self:custom.val1.foo}', + }; + const expected = { + val0: { + foo: 'bar', + }, + val1: { + foo: 'bar', + }, + val2: 'bar', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle deep variables that reference overrides', () => { + service.custom = { + val1: '${self:not.a.value, "bar"}', + val2: 'foo${self:custom.val1}', + }; + const expected = { + val1: 'bar', + val2: 'foobar', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle referenced deep variables that reference overrides', () => { + service.custom = { + val1: '${self:not.a.value, "bar"}', + val2: '${self:custom.val1}', + val3: '${self:custom.val2}', + }; + const expected = { + val1: 'bar', + val2: 'bar', + val3: 'bar', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle partial referenced deep variables that reference overrides', () => { + service.custom = { + val1: '${self:not.a.value, "bar"}', + val2: '${self:custom.val1}', + val3: 'foo${self:custom.val2}', + }; + const expected = { + val1: 'bar', + val2: 'bar', + val3: 'foobar', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle referenced contained deep variables that reference overrides', () => { + service.custom = { + val1: '${self:not.a.value, "bar"}', + val2: 'foo${self:custom.val1}', + val3: '${self:custom.val2}', + }; + const expected = { + val1: 'bar', + val2: 'foobar', + val3: 'foobar', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle multiple referenced contained deep variables referencing overrides', () => { + service.custom = { + val0: '${self:not.a.value, "foo"}', + val1: '${self:not.a.value, "bar"}', + val2: '${self:custom.val0}:${self:custom.val1}', + val3: '${self:custom.val2}', + }; + const expected = { + val0: 'foo', + val1: 'bar', + val2: 'foo:bar', + val3: 'foo:bar', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle deep variables regardless of custom variableSyntax', () => { + service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._\\\'",\\-\\/\\(\\)]+?)}}'; + serverless.variables.loadVariableSyntax(); + delete service.provider.variableSyntax; + service.custom = { + my0thStage: 'DEV', + my1stStage: '${{self:custom.my0thStage}}', + my2ndStage: '${{self:custom.my1stStage}}', + }; + const expected = { + my0thStage: 'DEV', + my1stStage: 'DEV', + my2ndStage: 'DEV', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle deep variables regardless of recursion into custom variableSyntax', () => { + service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._\\\'",\\-\\/\\(\\)]+?)}}'; + serverless.variables.loadVariableSyntax(); + delete service.provider.variableSyntax; + service.custom = { + my0thIndex: '0th', + my1stIndex: '1st', + my0thStage: 'DEV', + my1stStage: '${{self:custom.my${{self:custom.my0thIndex}}Stage}}', + my2ndStage: '${{self:custom.my${{self:custom.my1stIndex}}Stage}}', + }; + const expected = { + my0thIndex: '0th', + my1stIndex: '1st', + my0thStage: 'DEV', + my1stStage: 'DEV', + my2ndStage: 'DEV', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should handle deep variables in complex recursions of custom variableSyntax', () => { + service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._\\\'",\\-\\/\\(\\)]+?)}}'; + serverless.variables.loadVariableSyntax(); + delete service.provider.variableSyntax; + service.custom = { + i0: '0', + s0: 'DEV', + s1: '${{self:custom.s0}}! ${{self:custom.s0}}', + s2: 'I am a ${{self:custom.s0}}! A ${{self:custom.s${{self:custom.i0}}}}!', + s3: '${{self:custom.s0}}!, I am a ${{self:custom.s1}}!, ${{self:custom.s2}}', + }; + const expected = { + i0: '0', + s0: 'DEV', + s1: 'DEV! DEV', + s2: 'I am a DEV! A DEV!', + s3: 'DEV!, I am a DEV! DEV!, I am a DEV! A DEV!', + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + describe('file reading cases', () => { + let tmpDirPath; + beforeEach(() => { + tmpDirPath = testUtils.getTmpDirPath(); + fse.mkdirsSync(tmpDirPath); + serverless.config.update({ servicePath: tmpDirPath }); + }); + afterEach(() => { + fse.removeSync(tmpDirPath); + }); + const makeTempFile = (fileName, fileContent) => { + fse.writeFileSync(path.join(tmpDirPath, fileName), fileContent); + }; + const asyncFileName = 'async.load.js'; + const asyncContent = `'use strict'; let i = 0 const str = () => new Promise((resolve) => { setTimeout(() => { @@ -341,506 +681,453 @@ module.exports = { obj, }; `; - SUtils.writeFileSync(path.join(tmpDirPath, pathAsyncLoadJs), fileContent); - serverless.config.update({ servicePath: tmpDirPath }); - }; - it('should populate any given variable only once', () => { - makeAsyncLoadJs(); - service.custom = { - val1: '${self:custom.val0}', - val2: '${self:custom.val1}', - val0: `\${file(${pathAsyncLoadJs}):str}`, - }; - const expected = { - val1: 'my-async-value-1', - val2: 'my-async-value-1', - val0: 'my-async-value-1', - }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; - }); - it('should populate any given variable only once regardless of ordering or reference count', - () => { - makeAsyncLoadJs(); + it('should populate any given variable only once', () => { + makeTempFile(asyncFileName, asyncContent); service.custom = { - val9: '${self:custom.val7}', - val7: '${self:custom.val5}', - val5: '${self:custom.val3}', - val3: '${self:custom.val1}', - val1: '${self:custom.val0}', - val2: '${self:custom.val1}', - val4: '${self:custom.val3}', - val6: '${self:custom.val5}', - val8: '${self:custom.val7}', - val0: `\${file(${pathAsyncLoadJs}):str}`, + val1: '${self:custom.val0}', // eslint-disable-line no-template-curly-in-string + val2: '${self:custom.val1}', // eslint-disable-line no-template-curly-in-string + val0: `\${file(${asyncFileName}):str}`, }; const expected = { - val9: 'my-async-value-1', - val7: 'my-async-value-1', - val5: 'my-async-value-1', - val3: 'my-async-value-1', val1: 'my-async-value-1', val2: 'my-async-value-1', - val4: 'my-async-value-1', - val6: 'my-async-value-1', - val8: 'my-async-value-1', val0: 'my-async-value-1', }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; - } - ); - it('should populate async objects with contained variables', - () => { - makeAsyncLoadJs(); - serverless.variables.options = { - stage: 'dev', - }; - service.custom = { - obj: `\${file(${pathAsyncLoadJs}):obj}`, - }; - const expected = { - obj: { + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should populate any given variable only once regardless of ordering or reference count', + () => { + makeTempFile(asyncFileName, asyncContent); + service.custom = { + val9: '${self:custom.val7}', // eslint-disable-line no-template-curly-in-string + val7: '${self:custom.val5}', // eslint-disable-line no-template-curly-in-string + val5: '${self:custom.val3}', // eslint-disable-line no-template-curly-in-string + val3: '${self:custom.val1}', // eslint-disable-line no-template-curly-in-string + val1: '${self:custom.val0}', // eslint-disable-line no-template-curly-in-string + val2: '${self:custom.val1}', // eslint-disable-line no-template-curly-in-string + val4: '${self:custom.val3}', // eslint-disable-line no-template-curly-in-string + val6: '${self:custom.val5}', // eslint-disable-line no-template-curly-in-string + val8: '${self:custom.val7}', // eslint-disable-line no-template-curly-in-string + val0: `\${file(${asyncFileName}):str}`, + }; + const expected = { + val9: 'my-async-value-1', + val7: 'my-async-value-1', + val5: 'my-async-value-1', + val3: 'my-async-value-1', + val1: 'my-async-value-1', + val2: 'my-async-value-1', + val4: 'my-async-value-1', + val6: 'my-async-value-1', + val8: 'my-async-value-1', val0: 'my-async-value-1', - val1: 'dev', - }, - }; - return expect(serverless.variables.populateObject(service.custom).then((result) => { - expect(result).to.eql(expected); - })).to.be.fulfilled; - } - ); - const pathEmptyJs = 'empty.js'; - const makeEmptyJs = () => { - const SUtils = new Utils(); - const tmpDirPath = testUtils.getTmpDirPath(); - const fileContent = `'use strict'; + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + it('should populate async objects with contained variables', + () => { + makeTempFile(asyncFileName, asyncContent); + serverless.variables.options = { + stage: 'dev', + }; + service.custom = { + obj: `\${file(${asyncFileName}):obj}`, + }; + const expected = { + obj: { + val0: 'my-async-value-1', + val1: 'dev', + }, + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + const selfFileName = 'self.yml'; + const selfContent = `foo: baz +bar: \${self:custom.self.foo} +`; + it('should populate a "cyclic" reference across an unresolved dependency (issue #4687)', + () => { + makeTempFile(selfFileName, selfContent); + service.custom = { + self: `\${file(${selfFileName})}`, + }; + const expected = { + self: { + foo: 'baz', + bar: 'baz', + }, + }; + return serverless.variables.populateObject(service.custom) + .should.become(expected); + }); + const emptyFileName = 'empty.js'; + const emptyContent = `'use strict'; module.exports = { func: () => ({ value: 'a value' }), } `; - SUtils.writeFileSync(path.join(tmpDirPath, pathEmptyJs), fileContent); - serverless.config.update({ servicePath: tmpDirPath }); - }; - it('should reject population of an attribute not exported from a file', - () => { - makeEmptyJs(); - service.custom = { - val: `\${file(${pathEmptyJs}):func.notAValue}`, - }; - return expect(serverless.variables.populateObject(service.custom)) - .to.eventually.be.rejected; - } - ); + it('should reject population of an attribute not exported from a file', + () => { + makeTempFile(emptyFileName, emptyContent); + service.custom = { + val: `\${file(${emptyFileName}):func.notAValue}`, + }; + return serverless.variables.populateObject(service.custom) + .should.be.rejectedWith(serverless.classes.Error, + 'Invalid variable syntax when referencing file'); + }); + }); }); }); describe('#populateProperty()', () => { - let serverless; - let overwriteStub; - let populateObjectStub; - let getValueFromSourceStub; - let populateVariableStub; - beforeEach(() => { - serverless = new Serverless(); - overwriteStub = sinon.stub(serverless.variables, 'overwrite'); - populateObjectStub = sinon.stub(serverless.variables, 'populateObject'); - getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); - populateVariableStub = sinon.stub(serverless.variables, 'populateVariable'); - }); - - afterEach(() => { - serverless.variables.overwrite.restore(); - serverless.variables.populateObject.restore(); - serverless.variables.getValueFromSource.restore(); - serverless.variables.populateVariable.restore(); + serverless.variables.loadVariableSyntax(); }); it('should call overwrite if overwrite syntax provided', () => { + // eslint-disable-next-line no-template-curly-in-string const property = 'my stage is ${opt:stage, self:provider.stage}'; - - serverless.variables.loadVariableSyntax(); - - overwriteStub.resolves('dev'); - populateVariableStub.resolves('my stage is dev'); - - return serverless.variables.populateProperty(property).then(newProperty => { - expect(overwriteStub.called).to.equal(true); - expect(populateVariableStub.called).to.equal(true); - expect(newProperty).to.equal('my stage is dev'); - - return BbPromise.resolve(); - }); + serverless.variables.options = { stage: 'dev' }; + serverless.service.provider.stage = 'prod'; + return serverless.variables.populateProperty(property) + .should.eventually.eql('my stage is dev'); }); it('should allow a single-quoted string if overwrite syntax provided', () => { + // eslint-disable-next-line no-template-curly-in-string const property = "my stage is ${opt:stage, 'prod'}"; - - serverless.variables.loadVariableSyntax(); - - overwriteStub.resolves('\'prod\''); - populateVariableStub.resolves('my stage is prod'); - - return expect(serverless.variables.populateProperty(property)).to.be.fulfilled - .then(newProperty => expect(newProperty).to.equal('my stage is prod')); + serverless.variables.options = {}; + return serverless.variables.populateProperty(property) + .should.eventually.eql('my stage is prod'); }); it('should allow a double-quoted string if overwrite syntax provided', () => { + // eslint-disable-next-line no-template-curly-in-string const property = 'my stage is ${opt:stage, "prod"}'; - - serverless.variables.loadVariableSyntax(); - - overwriteStub.resolves('\'prod\''); - populateVariableStub.resolves('my stage is prod'); - - return expect(serverless.variables.populateProperty(property)).to.be.fulfilled - .then(newProperty => expect(newProperty).to.equal('my stage is prod')); + serverless.variables.options = {}; + return serverless.variables.populateProperty(property) + .should.eventually.eql('my stage is prod'); }); it('should call getValueFromSource if no overwrite syntax provided', () => { + // eslint-disable-next-line no-template-curly-in-string const property = 'my stage is ${opt:stage}'; - - serverless.variables.loadVariableSyntax(); - - getValueFromSourceStub.resolves('prod'); - populateVariableStub.resolves('my stage is prod'); - - return serverless.variables.populateProperty(property).then(newProperty => { - expect(getValueFromSourceStub.called).to.be.true; - expect(populateVariableStub.called).to.be.true; - expect(newProperty).to.equal('my stage is prod'); - - return BbPromise.resolve(); - }); - }); - - it('should NOT call populateObject if variable value is a circular object', () => { - serverless.variables.options = { - stage: 'prod', - }; - const property = '${opt:stage}'; - const variableValue = { - stage: '${opt:stage}', - }; - const variableValuePopulated = { - stage: 'prod', - }; - - serverless.variables.cache['opt:stage'] = variableValuePopulated; - - serverless.variables.loadVariableSyntax(); - - populateObjectStub.resolves(variableValuePopulated); - getValueFromSourceStub.resolves(variableValue); - populateVariableStub.resolves(variableValuePopulated); - - return serverless.variables.populateProperty(property).then(newProperty => { - expect(populateObjectStub.called).to.equal(false); - expect(getValueFromSourceStub.called).to.equal(true); - expect(populateVariableStub.called).to.equal(true); - expect(newProperty).to.deep.equal(variableValuePopulated); - - return BbPromise.resolve(); - }); + serverless.variables.options = { stage: 'prod' }; + return serverless.variables.populateProperty(property) + .should.eventually.eql('my stage is prod'); }); it('should warn if an SSM parameter does not exist', () => { - const awsProvider = new AwsProvider(serverless, { stage: 'prod', region: 'us-west-2' }); - const param = '/some/path/to/invalidparam'; - const property = `\${ssm:${param}}`; - const error = new Error(`Parameter ${param} not found.`); - - serverless.variables.options = { + const options = { stage: 'prod', region: 'us-east-1', }; - serverless.variables.loadVariableSyntax(); - - serverless.variables.getValueFromSource.restore(); - serverless.variables.populateVariable.restore(); + serverless.variables.options = options; + const awsProvider = new AwsProvider(serverless, options); + const param = '/some/path/to/invalidparam'; + const property = `\${ssm:${param}}`; + const error = new Error(`Parameter ${param} not found.`, 123); const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); const warnIfNotFoundSpy = sinon.spy(serverless.variables, 'warnIfNotFound'); - - return expect(serverless.variables.populateProperty(property) - .then(newProperty => { + return serverless.variables.populateProperty(property) + .should.become(undefined) + .then(() => { expect(requestStub.callCount).to.equal(1); expect(warnIfNotFoundSpy.callCount).to.equal(1); - expect(newProperty).to.be.undefined; }) .finally(() => { - getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); - populateVariableStub = sinon.stub(serverless.variables, 'populateVariable'); - })).to.be.fulfilled; + requestStub.restore(); + warnIfNotFoundSpy.restore(); + }); }); it('should throw an Error if the SSM request fails', () => { - const awsProvider = new AwsProvider(serverless, { stage: 'prod', region: 'us-west-2' }); - const param = '/some/path/to/invalidparam'; - const property = `\${ssm:${param}}`; - const error = new Error('Some random failure.'); - - serverless.variables.options = { + const options = { stage: 'prod', region: 'us-east-1', }; - serverless.variables.loadVariableSyntax(); - - serverless.variables.getValueFromSource.restore(); + serverless.variables.options = options; + const awsProvider = new AwsProvider(serverless, options); + const param = '/some/path/to/invalidparam'; + const property = `\${ssm:${param}}`; + const error = new serverless.classes.Error('Some random failure.', 123); const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); - - return expect(serverless.variables.populateProperty(property) - .finally(() => { - getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); - expect(requestStub.callCount).to.equal(1); - })).to.be.rejectedWith(serverless.classes.Error); + return serverless.variables.populateProperty(property) + .should.be.rejectedWith(serverless.classes.Error) + .then(() => expect(requestStub.callCount).to.equal(1)) + .finally(() => requestStub.restore()); }); it('should run recursively if nested variables provided', () => { - const property = 'my stage is ${env:${opt.name}}'; - - serverless.variables.loadVariableSyntax(); - - getValueFromSourceStub.onCall(0).resolves('stage'); - getValueFromSourceStub.onCall(1).resolves('dev'); - populateVariableStub.onCall(0).resolves('my stage is ${env:stage}'); - populateVariableStub.onCall(1).resolves('my stage is dev'); - - return serverless.variables.populateProperty(property).then(newProperty => { - expect(getValueFromSourceStub.callCount).to.equal(2); - expect(populateVariableStub.callCount).to.equal(2); - expect(newProperty).to.equal('my stage is dev'); - }); + // eslint-disable-next-line no-template-curly-in-string + const property = 'my stage is ${env:${opt:name}}'; + process.env.TEST_VAR = 'dev'; + serverless.variables.options = { name: 'TEST_VAR' }; + return serverless.variables.populateProperty(property) + .should.eventually.eql('my stage is dev') + .then().finally(() => { delete process.env.TEST_VAR; }); + }); + it('should run recursively through many nested variables', () => { + // eslint-disable-next-line no-template-curly-in-string + const property = 'my stage is ${env:${opt:name}}'; + process.env.TEST_VAR = 'dev'; + serverless.variables.options = { + name: 'T${opt:lvl0}', + lvl0: 'E${opt:lvl1}', + lvl1: 'S${opt:lvl2}', + lvl2: 'T${opt:lvl3}', + lvl3: '_${opt:lvl4}', + lvl4: 'V${opt:lvl5}', + lvl5: 'A${opt:lvl6}', + lvl6: 'R', + }; + return serverless.variables.populateProperty(property) + .should.eventually.eql('my stage is dev') + .then().finally(() => { delete process.env.TEST_VAR; }); }); }); describe('#populateVariable()', () => { it('should populate string variables as sub string', () => { - const serverless = new Serverless(); const valueToPopulate = 'dev'; - const matchedString = '${opt:stage}'; + const matchedString = '${opt:stage}'; // eslint-disable-line no-template-curly-in-string + // eslint-disable-next-line no-template-curly-in-string const property = 'my stage is ${opt:stage}'; - - return serverless.variables.populateVariable(property, matchedString, valueToPopulate) - .then(newProperty => { - expect(newProperty).to.equal('my stage is dev'); - }); + serverless.variables.populateVariable(property, matchedString, valueToPopulate) + .should.eql('my stage is dev'); }); it('should populate number variables as sub string', () => { - const serverless = new Serverless(); const valueToPopulate = 5; - const matchedString = '${opt:number}'; + const matchedString = '${opt:number}'; // eslint-disable-line no-template-curly-in-string + // eslint-disable-next-line no-template-curly-in-string const property = 'your account number is ${opt:number}'; - - return serverless.variables.populateVariable(property, matchedString, valueToPopulate) - .then(newProperty => { - expect(newProperty).to.equal('your account number is 5'); - }); + serverless.variables.populateVariable(property, matchedString, valueToPopulate) + .should.eql('your account number is 5'); }); it('should populate non string variables', () => { - const serverless = new Serverless(); const valueToPopulate = 5; - const matchedString = '${opt:number}'; - const property = '${opt:number}'; - + const matchedString = '${opt:number}'; // eslint-disable-line no-template-curly-in-string + const property = '${opt:number}'; // eslint-disable-line no-template-curly-in-string return serverless.variables.populateVariable(property, matchedString, valueToPopulate) - .then(newProperty => { - expect(newProperty).to.equal(5); - }); + .should.equal(5); }); it('should throw error if populating non string or non number variable as sub string', () => { - const serverless = new Serverless(); const valueToPopulate = {}; - const matchedString = '${opt:object}'; + const matchedString = '${opt:object}'; // eslint-disable-line no-template-curly-in-string + // eslint-disable-next-line no-template-curly-in-string const property = 'your account number is ${opt:object}'; - expect(() => serverless.variables - .populateVariable(property, matchedString, valueToPopulate)) - .to.throw(Error); + return expect(() => + serverless.variables.populateVariable(property, matchedString, valueToPopulate)) + .to.throw(serverless.classes.Error); + }); + }); + + describe('#splitByComma', () => { + it('should return a given empty string', () => { + const input = ''; + const expected = [input]; + expect(serverless.variables.splitByComma(input)).to.eql(expected); + }); + it('should return a undelimited string', () => { + const input = 'foo:bar'; + const expected = [input]; + expect(serverless.variables.splitByComma(input)).to.eql(expected); + }); + it('should split basic comma delimited strings', () => { + const input = 'my,values,to,split'; + const expected = ['my', 'values', 'to', 'split']; + expect(serverless.variables.splitByComma(input)).to.eql(expected); + }); + it('should remove leading and following white space', () => { + const input = ' \t\nfoobar\n\t '; + const expected = ['foobar']; + expect(serverless.variables.splitByComma(input)).to.eql(expected); + }); + it('should remove white space surrounding commas', () => { + const input = 'a,b ,c , d, e , f\t,g\n,h,\ti,\nj,\t\n , \n\tk'; + const expected = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k']; + expect(serverless.variables.splitByComma(input)).to.eql(expected); + }); + it('should ignore quoted commas', () => { + const input = '",", \',\', ",\', \',\'", "\',\', \',\'", \',", ","\', \'",", ","\''; + const expected = [ + '","', + '\',\'', + '",\', \',\'"', + '"\',\', \',\'"', + '\',", ","\'', + '\'",", ","\'', + ]; + expect(serverless.variables.splitByComma(input)).to.eql(expected); + }); + it('should deal with a combination of these cases', () => { + const input = ' \t\n\'a\'\t\n , \n\t"foo,bar", opt:foo, ",", \',\', "\',\', \',\'", foo\n\t '; + const expected = ['\'a\'', '"foo,bar"', 'opt:foo', '","', '\',\'', '"\',\', \',\'"', 'foo']; + expect(serverless.variables.splitByComma(input)).to.eql(expected); }); }); describe('#overwrite()', () => { it('should overwrite undefined and null values', () => { - const serverless = new Serverless(); - const getValueFromSourceStub = sinon - .stub(serverless.variables, 'getValueFromSource'); - + const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves(undefined); getValueFromSourceStub.onCall(1).resolves(null); getValueFromSourceStub.onCall(2).resolves('variableValue'); - - return serverless.variables.overwrite('opt:stage,env:stage,self:provider.stage') - .then(valueToPopulate => { + return serverless.variables.overwrite(['opt:stage', 'env:stage', 'self:provider.stage']) + .should.be.fulfilled + .then((valueToPopulate) => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromSourceStub).to.have.been.calledThrice; }) - .finally(() => serverless.variables.getValueFromSource.restore()); + .finally(() => getValueFromSourceStub.restore()); }); it('should overwrite empty object values', () => { - const serverless = new Serverless(); - const getValueFromSourceStub = sinon - .stub(serverless.variables, 'getValueFromSource'); - + const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves({}); getValueFromSourceStub.onCall(1).resolves('variableValue'); - - return serverless.variables.overwrite('opt:stage,env:stage').then(valueToPopulate => { - expect(valueToPopulate).to.equal('variableValue'); - expect(getValueFromSourceStub).to.have.been.calledTwice; - }) - .finally(() => serverless.variables.getValueFromSource.restore()); + return serverless.variables.overwrite(['opt:stage', 'env:stage']).should.be.fulfilled + .then((valueToPopulate) => { + expect(valueToPopulate).to.equal('variableValue'); + expect(getValueFromSourceStub).to.have.been.calledTwice; + }) + .finally(() => getValueFromSourceStub.restore()); }); it('should not overwrite 0 values', () => { - const serverless = new Serverless(); - const getValueFromSourceStub = sinon - .stub(serverless.variables, 'getValueFromSource'); - + const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves(0); getValueFromSourceStub.onCall(1).resolves('variableValue'); - getValueFromSourceStub.onCall(2).resolves('variableValue2'); - return serverless.variables.overwrite('opt:stage,env:stage,self:provider.stage') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal(0); - }) - .finally(() => serverless.variables.getValueFromSource.restore()); + return serverless.variables.overwrite(['opt:stage', 'env:stage']).should.become(0) + .then().finally(() => getValueFromSourceStub.restore()); }); it('should not overwrite false values', () => { - const serverless = new Serverless(); - const getValueFromSourceStub = sinon - .stub(serverless.variables, 'getValueFromSource'); - + const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves(false); getValueFromSourceStub.onCall(1).resolves('variableValue'); - getValueFromSourceStub.onCall(2).resolves('variableValue2'); - - return serverless.variables.overwrite('opt:stage,env:stage,self:provider.stage') - .then(valueToPopulate => { - expect(valueToPopulate).to.be.false; - }) - .finally(() => serverless.variables.getValueFromSource.restore()); + return serverless.variables.overwrite(['opt:stage', 'env:stage']).should.become(false) + .then().finally(() => getValueFromSourceStub.restore()); }); it('should skip getting values once a value has been found', () => { - const serverless = new Serverless(); - const getValueFromSourceStub = sinon - .stub(serverless.variables, 'getValueFromSource'); - + const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource'); getValueFromSourceStub.onCall(0).resolves(undefined); getValueFromSourceStub.onCall(1).resolves('variableValue'); getValueFromSourceStub.onCall(2).resolves('variableValue2'); - - return serverless.variables.overwrite('opt:stage,env:stage,self:provider.stage') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal('variableValue'); - }) - .finally(() => serverless.variables.getValueFromSource.restore()); + return serverless.variables.overwrite(['opt:stage', 'env:stage', 'self:provider.stage']) + .should.be.fulfilled + .then(valueToPopulate => expect(valueToPopulate).to.equal('variableValue')) + .finally(() => getValueFromSourceStub.restore()); + }); + it('should properly handle string values containing commas', () => { + const str = '"foo,bar"'; + const getValueFromSourceStub = sinon.stub(serverless.variables, 'getValueFromSource') + .resolves(undefined); + return serverless.variables.overwrite(['opt:stage', str]) + .should.be.fulfilled + .then(() => expect(getValueFromSourceStub.getCall(1).args[0]).to.eql(str)) + .finally(() => getValueFromSourceStub.restore()); }); }); describe('#getValueFromSource()', () => { it('should call getValueFromEnv if referencing env var', () => { - const serverless = new Serverless(); - const getValueFromEnvStub = sinon - .stub(serverless.variables, 'getValueFromEnv').resolves('variableValue'); - return serverless.variables.getValueFromSource('env:TEST_VAR') - .then(valueToPopulate => { + const getValueFromEnvStub = sinon.stub(serverless.variables, 'getValueFromEnv') + .resolves('variableValue'); + return serverless.variables.getValueFromSource('env:TEST_VAR').should.be.fulfilled + .then((valueToPopulate) => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromEnvStub).to.have.been.called; - expect(getValueFromEnvStub.calledWith('env:TEST_VAR')).to.equal(true); + expect(getValueFromEnvStub).to.have.been.calledWith('env:TEST_VAR'); }) - .finally(() => serverless.variables.getValueFromEnv.restore()); + .finally(() => getValueFromEnvStub.restore()); }); it('should call getValueFromOptions if referencing an option', () => { - const serverless = new Serverless(); const getValueFromOptionsStub = sinon .stub(serverless.variables, 'getValueFromOptions') .resolves('variableValue'); - - return serverless.variables.getValueFromSource('opt:stage') - .then(valueToPopulate => { + return serverless.variables.getValueFromSource('opt:stage').should.be.fulfilled + .then((valueToPopulate) => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromOptionsStub).to.have.been.called; - expect(getValueFromOptionsStub.calledWith('opt:stage')).to.equal(true); + expect(getValueFromOptionsStub).to.have.been.calledWith('opt:stage'); }) - .finally(() => serverless.variables.getValueFromOptions.restore()); + .finally(() => getValueFromOptionsStub.restore()); }); it('should call getValueFromSelf if referencing from self', () => { - const serverless = new Serverless(); - const getValueFromSelfStub = sinon - .stub(serverless.variables, 'getValueFromSelf').resolves('variableValue'); - - return serverless.variables.getValueFromSource('self:provider') - .then(valueToPopulate => { + const getValueFromSelfStub = sinon.stub(serverless.variables, 'getValueFromSelf') + .resolves('variableValue'); + return serverless.variables.getValueFromSource('self:provider').should.be.fulfilled + .then((valueToPopulate) => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromSelfStub).to.have.been.called; - expect(getValueFromSelfStub.calledWith('self:provider')).to.equal(true); + expect(getValueFromSelfStub).to.have.been.calledWith('self:provider'); }) - .finally(() => serverless.variables.getValueFromSelf.restore()); + .finally(() => getValueFromSelfStub.restore()); }); it('should call getValueFromFile if referencing from another file', () => { - const serverless = new Serverless(); - const getValueFromFileStub = sinon - .stub(serverless.variables, 'getValueFromFile').resolves('variableValue'); - - return serverless.variables.getValueFromSource('file(./config.yml)') - .then(valueToPopulate => { + const getValueFromFileStub = sinon.stub(serverless.variables, 'getValueFromFile') + .resolves('variableValue'); + return serverless.variables.getValueFromSource('file(./config.yml)').should.be.fulfilled + .then((valueToPopulate) => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromFileStub).to.have.been.called; expect(getValueFromFileStub).to.have.been.calledWith('file(./config.yml)'); }) - .finally(() => serverless.variables.getValueFromFile.restore()); + .finally(() => getValueFromFileStub.restore()); }); it('should call getValueFromCf if referencing CloudFormation Outputs', () => { - const serverless = new Serverless(); - const getValueFromCfStub = sinon - .stub(serverless.variables, 'getValueFromCf').resolves('variableValue'); - return serverless.variables.getValueFromSource('cf:test-stack.testOutput') - .then(valueToPopulate => { + const getValueFromCfStub = sinon.stub(serverless.variables, 'getValueFromCf') + .resolves('variableValue'); + return serverless.variables.getValueFromSource('cf:test-stack.testOutput').should.be.fulfilled + .then((valueToPopulate) => { expect(valueToPopulate).to.equal('variableValue'); expect(getValueFromCfStub).to.have.been.called; expect(getValueFromCfStub).to.have.been.calledWith('cf:test-stack.testOutput'); }) - .finally(() => serverless.variables.getValueFromCf.restore()); + .finally(() => getValueFromCfStub.restore()); }); it('should call getValueFromS3 if referencing variable in S3', () => { - const serverless = new Serverless(); - const getValueFromS3Stub = sinon - .stub(serverless.variables, 'getValueFromS3').resolves('variableValue'); + const getValueFromS3Stub = sinon.stub(serverless.variables, 'getValueFromS3') + .resolves('variableValue'); return serverless.variables.getValueFromSource('s3:test-bucket/path/to/key') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal('variableValue'); - expect(getValueFromS3Stub).to.have.been.called; - expect(getValueFromS3Stub).to.have.been.calledWith('s3:test-bucket/path/to/key'); - }) - .finally(() => serverless.variables.getValueFromS3.restore()); + .should.be.fulfilled + .then((valueToPopulate) => { + expect(valueToPopulate).to.equal('variableValue'); + expect(getValueFromS3Stub).to.have.been.called; + expect(getValueFromS3Stub).to.have.been.calledWith('s3:test-bucket/path/to/key'); + }) + .finally(() => getValueFromS3Stub.restore()); }); it('should call getValueFromSsm if referencing variable in SSM', () => { - const serverless = new Serverless(); - const getValueFromSsmStub = sinon - .stub(serverless.variables, 'getValueFromSsm').resolves('variableValue'); + const getValueFromSsmStub = sinon.stub(serverless.variables, 'getValueFromSsm') + .resolves('variableValue'); return serverless.variables.getValueFromSource('ssm:/test/path/to/param') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal('variableValue'); - expect(getValueFromSsmStub).to.have.been.called; - expect(getValueFromSsmStub).to.have.been.calledWith('ssm:/test/path/to/param'); - }) - .finally(() => serverless.variables.getValueFromSsm.restore()); + .should.be.fulfilled + .then((valueToPopulate) => { + expect(valueToPopulate).to.equal('variableValue'); + expect(getValueFromSsmStub).to.have.been.called; + expect(getValueFromSsmStub).to.have.been.calledWith('ssm:/test/path/to/param'); + }) + .finally(() => getValueFromSsmStub.restore()); }); - + it('should reject invalid sources', () => + serverless.variables.getValueFromSource('weird:source') + .should.be.rejectedWith(serverless.classes.Error)); describe('caching', () => { const sources = [ { function: 'getValueFromEnv', variableString: 'env:NODE_ENV' }, @@ -853,182 +1140,106 @@ module.exports = { ]; sources.forEach((source) => { it(`should only call ${source.function} once, returning the cached value otherwise`, () => { - const serverless = new Serverless(); - const getValueFunctionStub = sinon - .stub(serverless.variables, source.function).resolves('variableValue'); - const firstCall = serverless.variables.getValueFromSource(source.variableString); - const secondCall = BbPromise.delay(100) - .then(() => serverless.variables.getValueFromSource(source.variableString)); - return BbPromise.all([firstCall, secondCall]) - .then(valueToPopulate => { - expect(valueToPopulate).to.deep.equal(['variableValue', 'variableValue']); + const value = 'variableValue'; + const getValueFunctionStub = sinon.stub(serverless.variables, source.function) + .resolves(value); + return BbPromise.all([ + serverless.variables.getValueFromSource(source.variableString).should.become(value), + BbPromise.delay(100).then(() => + serverless.variables.getValueFromSource(source.variableString).should.become(value)), + ]).then(() => { expect(getValueFunctionStub).to.have.been.calledOnce; expect(getValueFunctionStub).to.have.been.calledWith(source.variableString); - }) - .finally(() => serverless.variables[source.function].restore()); + }).finally(() => + getValueFunctionStub.restore()); }); }); }); - - it('should call populateObject if variable value is an object', () => { - const serverless = new Serverless(); - serverless.variables.options = { - stage: 'prod', - }; - const property = 'self:stage'; - const variableValue = { - stage: '${opt:stage}', - }; - const variableValuePopulated = { - stage: 'prod', - }; - - serverless.variables.loadVariableSyntax(); - - const populateObjectStub = sinon - .stub(serverless.variables, 'populateObject') - .resolves(variableValuePopulated); - const getValueFromSelfStub = sinon - .stub(serverless.variables, 'getValueFromSelf') - .resolves(variableValue); - - return serverless.variables.getValueFromSource(property) - .then(newProperty => { - expect(populateObjectStub.called).to.equal(true); - expect(getValueFromSelfStub.called).to.equal(true); - expect(newProperty).to.deep.equal(variableValuePopulated); - - return BbPromise.resolve(); - }) - .finally(() => { - serverless.variables.populateObject.restore(); - serverless.variables.getValueFromSelf.restore(); - }); - }); - - it('should NOT call populateObject if variable value is already cached', () => { - const serverless = new Serverless(); - serverless.variables.options = { - stage: 'prod', - }; - const property = 'opt:stage'; - const variableValue = { - stage: '${opt:stage}', - }; - const variableValuePopulated = { - stage: 'prod', - }; - - serverless.variables.cache['opt:stage'] = BbPromise.resolve(variableValuePopulated); - - serverless.variables.loadVariableSyntax(); - - const populateObjectStub = sinon - .stub(serverless.variables, 'populateObject') - .resolves(variableValuePopulated); - const getValueFromOptionsStub = sinon - .stub(serverless.variables, 'getValueFromOptions') - .resolves(variableValue); - - return serverless.variables.getValueFromSource(property) - .then(newProperty => { - expect(populateObjectStub.called).to.equal(false); - expect(getValueFromOptionsStub.called).to.equal(false); - expect(newProperty).to.deep.equal(variableValuePopulated); - - return BbPromise.resolve(); - }) - .finally(() => { - serverless.variables.populateObject.restore(); - serverless.variables.getValueFromOptions.restore(); - }); - }); - - it('should throw error if referencing an invalid source', () => { - const serverless = new Serverless(); - expect(() => serverless.variables.getValueFromSource('weird:source')) - .to.throw(Error); - }); }); describe('#getValueFromEnv()', () => { it('should get variable from environment variables', () => { - const serverless = new Serverless(); process.env.TEST_VAR = 'someValue'; - return serverless.variables.getValueFromEnv('env:TEST_VAR').then(valueToPopulate => { - expect(valueToPopulate).to.be.equal('someValue'); - }) - .finally(() => { - delete process.env.TEST_VAR; - }); + return serverless.variables.getValueFromEnv('env:TEST_VAR') + .finally(() => { delete process.env.TEST_VAR; }) + .should.become('someValue'); }); it('should allow top-level references to the environment variables hive', () => { - const serverless = new Serverless(); process.env.TEST_VAR = 'someValue'; - return serverless.variables.getValueFromEnv('env:').then(valueToPopulate => { + return serverless.variables.getValueFromEnv('env:').then((valueToPopulate) => { expect(valueToPopulate.TEST_VAR).to.be.equal('someValue'); }) - .finally(() => { - delete process.env.TEST_VAR; - }); + .finally(() => { delete process.env.TEST_VAR; }); }); }); describe('#getValueFromOptions()', () => { it('should get variable from options', () => { - const serverless = new Serverless(); - serverless.variables.options = { - stage: 'prod', - }; - return serverless.variables.getValueFromOptions('opt:stage').then(valueToPopulate => { - expect(valueToPopulate).to.be.equal('prod'); - }); + serverless.variables.options = { stage: 'prod' }; + return serverless.variables.getValueFromOptions('opt:stage').should.become('prod'); }); it('should allow top-level references to the options hive', () => { - const serverless = new Serverless(); - serverless.variables.options = { - stage: 'prod', - }; - return serverless.variables.getValueFromOptions('opt:').then(valueToPopulate => { - expect(valueToPopulate.stage).to.be.equal('prod'); - }); + serverless.variables.options = { stage: 'prod' }; + return serverless.variables.getValueFromOptions('opt:') + .should.become(serverless.variables.options); }); }); describe('#getValueFromSelf()', () => { + beforeEach(() => { + serverless.service.provider.variableSyntax = '\\${{([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}}'; + serverless.variables.loadVariableSyntax(); + delete serverless.service.provider.variableSyntax; + }); it('should get variable from self serverless.yml file', () => { - const serverless = new Serverless(); serverless.variables.service = { service: 'testService', provider: serverless.service.provider, }; - serverless.variables.loadVariableSyntax(); - return serverless.variables.getValueFromSelf('self:service').then(valueToPopulate => { - expect(valueToPopulate).to.be.equal('testService'); - }); + return serverless.variables.getValueFromSelf('self:service').should.become('testService'); + }); + it('should redirect ${self:service.name} to ${self:service}', () => { + serverless.variables.service = { + service: 'testService', + provider: serverless.service.provider, + }; + return serverless.variables.getValueFromSelf('self:service.name') + .should.become('testService'); + }); + it('should redirect ${self:provider} to ${self:provider.name}', () => { + serverless.variables.service = { + service: 'testService', + provider: { name: 'aws' }, + }; + return serverless.variables.getValueFromSelf('self:provider').should.become('aws'); + }); + it('should redirect ${self:service.awsKmsKeyArn} to ${self:serviceObject.awsKmsKeyArn}', () => { + const keyArn = 'arn:aws:kms:us-east-1:xxxxxxxxxxxx:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'; + serverless.variables.service = { + service: 'testService', + serviceObject: { + name: 'testService', + awsKmsKeyArn: keyArn, + }, + }; + return serverless.variables.getValueFromSelf('self:service.awsKmsKeyArn') + .should.become(keyArn); }); - it('should handle self-references to the root of the serverless.yml file', () => { - const serverless = new Serverless(); serverless.variables.service = { service: 'testService', provider: 'testProvider', defaults: serverless.service.defaults, }; - - serverless.variables.loadVariableSyntax(); - - return serverless.variables.getValueFromSelf('self:').then(valueToPopulate => { - expect(valueToPopulate.provider).to.be.equal('testProvider'); - }); + return serverless.variables.getValueFromSelf('self:') + .should.eventually.equal(serverless.variables.service); }); }); describe('#getValueFromFile()', () => { it('should work for absolute paths with ~ ', () => { - const serverless = new Serverless(); const expectedFileName = `${os.homedir()}/somedir/config.yml`; const configYml = { test: 1, @@ -1038,17 +1249,11 @@ module.exports = { prob: 'prob', }, }; - const fileExistsStub = sinon - .stub(serverless.utils, 'fileExistsSync').returns(true); - - const realpathSync = sinon - .stub(fse, 'realpathSync').returns(expectedFileName); - - const readFileSyncStub = sinon - .stub(serverless.utils, 'readFileSync').returns(configYml); - - return serverless.variables.getValueFromFile('file(~/somedir/config.yml)') - .then(valueToPopulate => { + const fileExistsStub = sinon.stub(serverless.utils, 'fileExistsSync').returns(true); + const realpathSync = sinon.stub(fse, 'realpathSync').returns(expectedFileName); + const readFileSyncStub = sinon.stub(serverless.utils, 'readFileSync').returns(configYml); + return serverless.variables.getValueFromFile('file(~/somedir/config.yml)').should.be.fulfilled + .then((valueToPopulate) => { expect(realpathSync).to.not.have.been.called; expect(fileExistsStub).to.have.been.calledWithMatch(expectedFileName); expect(readFileSyncStub).to.have.been.calledWithMatch(expectedFileName); @@ -1062,7 +1267,6 @@ module.exports = { }); it('should populate an entire variable file', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const configYml = { @@ -1073,28 +1277,19 @@ module.exports = { prob: 'prob', }, }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), - YAML.dump(configYml)); - + SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), YAML.dump(configYml)); serverless.config.update({ servicePath: tmpDirPath }); - - return serverless.variables.getValueFromFile('file(./config.yml)').then(valueToPopulate => { - expect(valueToPopulate).to.deep.equal(configYml); - }); + return serverless.variables.getValueFromFile('file(./config.yml)') + .should.eventually.eql(configYml); }); it('should get undefined if non existing file and the second argument is true', () => { - const serverless = new Serverless(); const tmpDirPath = testUtils.getTmpDirPath(); - serverless.config.update({ servicePath: tmpDirPath }); - const realpathSync = sinon.spy(fse, 'realpathSync'); const existsSync = sinon.spy(fse, 'existsSync'); - - return serverless.variables.getValueFromFile('file(./non-existing.yml)') - .then(valueToPopulate => { + return serverless.variables.getValueFromFile('file(./non-existing.yml)').should.be.fulfilled + .then((valueToPopulate) => { expect(realpathSync).to.not.have.been.called; expect(existsSync).to.have.been.calledOnce; expect(valueToPopulate).to.be.undefined; @@ -1106,166 +1301,113 @@ module.exports = { }); it('should populate non json/yml files', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); - - SUtils.writeFileSync(path.join(tmpDirPath, 'someFile'), - 'hello world'); - + SUtils.writeFileSync(path.join(tmpDirPath, 'someFile'), 'hello world'); serverless.config.update({ servicePath: tmpDirPath }); - - return serverless.variables.getValueFromFile('file(./someFile)').then(valueToPopulate => { - expect(valueToPopulate).to.equal('hello world'); - }); + return serverless.variables.getValueFromFile('file(./someFile)') + .should.become('hello world'); }); it('should populate symlinks', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const realFilePath = path.join(tmpDirPath, 'someFile'); const symlinkPath = path.join(tmpDirPath, 'refSomeFile'); SUtils.writeFileSync(realFilePath, 'hello world'); fse.ensureSymlinkSync(realFilePath, symlinkPath); - serverless.config.update({ servicePath: tmpDirPath }); - - return expect(serverless.variables.getValueFromFile('file(./refSomeFile)')).to.be.fulfilled - .then(valueToPopulate => { - expect(valueToPopulate).to.equal('hello world'); - }) - .finally(() => { - fse.removeSync(realFilePath); - fse.removeSync(symlinkPath); - }); + return serverless.variables.getValueFromFile('file(./refSomeFile)') + .should.become('hello world') + .then().finally(() => { + fse.removeSync(realFilePath); + fse.removeSync(symlinkPath); + }); }); it('should trim trailing whitespace and new line character', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); - - SUtils.writeFileSync(path.join(tmpDirPath, 'someFile'), - 'hello world \n'); - + SUtils.writeFileSync(path.join(tmpDirPath, 'someFile'), 'hello world \n'); serverless.config.update({ servicePath: tmpDirPath }); - - return serverless.variables.getValueFromFile('file(./someFile)').then(valueToPopulate => { - expect(valueToPopulate).to.equal('hello world'); - }); + return serverless.variables.getValueFromFile('file(./someFile)') + .should.become('hello world'); }); it('should populate from another file when variable is of any type', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const configYml = { - test: 1, - test2: 'test2', - testObj: { + test0: 0, + test1: 'test1', + test2: { sub: 2, prob: 'prob', }, }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), - YAML.dump(configYml)); - + SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), YAML.dump(configYml)); serverless.config.update({ servicePath: tmpDirPath }); - - return serverless.variables.getValueFromFile('file(./config.yml):testObj.sub') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal(2); - }); + return serverless.variables.getValueFromFile('file(./config.yml):test2.sub') + .should.become(configYml.test2.sub); }); it('should populate from a javascript file', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const jsData = 'module.exports.hello=function(){return "hello world";};'; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.config.update({ servicePath: tmpDirPath }); - return serverless.variables.getValueFromFile('file(./hello.js):hello') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal('hello world'); - }); + .should.become('hello world'); }); it('should populate an entire variable exported by a javascript file', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const jsData = 'module.exports=function(){return { hello: "hello world" };};'; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.config.update({ servicePath: tmpDirPath }); - return serverless.variables.getValueFromFile('file(./hello.js)') - .then(valueToPopulate => { - expect(valueToPopulate.hello).to.equal('hello world'); - }); + .should.become({ hello: 'hello world' }); }); it('should throw if property exported by a javascript file is not a function', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const jsData = 'module.exports={ hello: "hello world" };'; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.config.update({ servicePath: tmpDirPath }); - - expect(() => serverless.variables - .getValueFromFile('file(./hello.js)')).to.throw(Error); + return serverless.variables.getValueFromFile('file(./hello.js)') + .should.be.rejectedWith(serverless.classes.Error); }); it('should populate deep object from a javascript file', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const jsData = `module.exports.hello=function(){ return {one:{two:{three: 'hello world'}}} };`; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.config.update({ servicePath: tmpDirPath }); serverless.variables.loadVariableSyntax(); - return serverless.variables.getValueFromFile('file(./hello.js):hello.one.two.three') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal('hello world'); - }); + .should.become('hello world'); }); it('should preserve the exported function context when executing', () => { - const serverless = new Serverless(); const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const jsData = ` module.exports.one = {two: {three: 'hello world'}} module.exports.hello=function(){ return this; };`; - SUtils.writeFileSync(path.join(tmpDirPath, 'hello.js'), jsData); - serverless.config.update({ servicePath: tmpDirPath }); serverless.variables.loadVariableSyntax(); - return serverless.variables.getValueFromFile('file(./hello.js):hello.one.two.three') - .then(valueToPopulate => { - expect(valueToPopulate).to.equal('hello world'); - }); + .should.become('hello world'); }); - it('should throw error if not using ":" syntax', () => { - const serverless = new Serverless(); + it('should file variable not using ":" syntax', () => { const SUtils = new Utils(); const tmpDirPath = testUtils.getTmpDirPath(); const configYml = { @@ -1276,20 +1418,15 @@ module.exports = { prob: 'prob', }, }; - - SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), - YAML.dump(configYml)); - + SUtils.writeFileSync(path.join(tmpDirPath, 'config.yml'), YAML.dump(configYml)); serverless.config.update({ servicePath: tmpDirPath }); - - expect(() => serverless.variables - .getValueFromFile('file(./config.yml).testObj.sub')).to.throw(Error); + return serverless.variables.getValueFromFile('file(./config.yml).testObj.sub') + .should.be.rejectedWith(serverless.classes.Error); }); }); describe('#getValueFromCf()', () => { it('should get variable from CloudFormation', () => { - const serverless = new Serverless(); const options = { stage: 'prod', region: 'us-west-2', @@ -1305,27 +1442,22 @@ module.exports = { }], }], }; - - const cfStub = sinon.stub(serverless.getProvider('aws'), 'request') - .resolves(awsResponseMock); + const cfStub = sinon.stub(serverless.getProvider('aws'), 'request', + () => BbPromise.resolve(awsResponseMock)); return serverless.variables.getValueFromCf('cf:some-stack.MockExport') - .then(valueToPopulate => { - expect(valueToPopulate).to.be.equal('MockValue'); + .should.become('MockValue') + .then(() => { expect(cfStub).to.have.been.calledOnce; expect(cfStub).to.have.been.calledWithExactly( 'CloudFormation', 'describeStacks', - { - StackName: 'some-stack', - }, - { useCache: true } - ); + { StackName: 'some-stack' }, + { useCache: true }); }) - .finally(() => serverless.getProvider('aws').request.restore()); + .finally(() => cfStub.restore()); }); - it('should throw an error when variable from CloudFormation does not exist', () => { - const serverless = new Serverless(); + it('should reject CloudFormation variables that do not exist', () => { const options = { stage: 'prod', region: 'us-west-2', @@ -1341,35 +1473,26 @@ module.exports = { }], }], }; - - const cfStub = sinon.stub(serverless.getProvider('aws'), 'request') - .resolves(awsResponseMock); - + const cfStub = sinon.stub(serverless.getProvider('aws'), 'request', + () => BbPromise.resolve(awsResponseMock)); return serverless.variables.getValueFromCf('cf:some-stack.DoestNotExist') - .then() - .catch(error => { + .should.be.rejectedWith(serverless.classes.Error, + /to request a non exported variable from CloudFormation/) + .then(() => { expect(cfStub).to.have.been.calledOnce; expect(cfStub).to.have.been.calledWithExactly( 'CloudFormation', 'describeStacks', - { - StackName: 'some-stack', - }, - { useCache: true } - ); - expect(error).to.be.an.instanceof(Error); - expect(error.message).to.match(/to request a non exported variable from CloudFormation/); + { StackName: 'some-stack' }, + { useCache: true }); }) - .finally(() => serverless.getProvider('aws').request.restore()); + .finally(() => cfStub.restore()); }); }); describe('#getValueFromS3()', () => { - let serverless; let awsProvider; - beforeEach(() => { - serverless = new Serverless(); const options = { stage: 'prod', region: 'us-west-2', @@ -1378,45 +1501,48 @@ module.exports = { serverless.setProvider('aws', awsProvider); serverless.variables.options = options; }); - it('should get variable from S3', () => { const awsResponseMock = { Body: 'MockValue', }; - const s3Stub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - - return serverless.variables.getValueFromS3('s3:some.bucket/path/to/key').then(value => { - expect(value).to.be.equal('MockValue'); - expect(s3Stub).to.have.been.calledOnce; - expect(s3Stub).to.have.been.calledWithExactly( - 'S3', - 'getObject', - { - Bucket: 'some.bucket', - Key: 'path/to/key', - }, - { useCache: true } - ); - }) - .finally(() => serverless.getProvider('aws').request.restore()); + const s3Stub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + return serverless.variables.getValueFromS3('s3:some.bucket/path/to/key') + .should.become('MockValue') + .then(() => { + expect(s3Stub).to.have.been.calledOnce; + expect(s3Stub).to.have.been.calledWithExactly( + 'S3', + 'getObject', + { + Bucket: 'some.bucket', + Key: 'path/to/key', + }, + { useCache: true }); + }) + .finally(() => s3Stub.restore()); }); it('should throw error if error getting value from S3', () => { const error = new Error('The specified bucket is not valid'); - sinon.stub(awsProvider, 'request').rejects(error); - + const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); return expect(serverless.variables.getValueFromS3('s3:some.bucket/path/to/key')) - .to.be.rejectedWith('Error getting value for s3:some.bucket/path/to/key. ' + - 'The specified bucket is not valid'); + .to.be.rejectedWith( + serverless.classes.Error, + 'Error getting value for s3:some.bucket/path/to/key. The specified bucket is not valid') + .then().finally(() => requestStub.restore()); }); }); describe('#getValueFromSsm()', () => { - let serverless; + const param = 'Param-01_valid.chars'; + const value = 'MockValue'; + const awsResponseMock = { + Parameter: { + Value: value, + }, + }; let awsProvider; - beforeEach(() => { - serverless = new Serverless(); const options = { stage: 'prod', region: 'us-west-2', @@ -1425,154 +1551,112 @@ module.exports = { serverless.setProvider('aws', awsProvider); serverless.variables.options = options; }); - it('should get variable from Ssm using regular-style param', () => { - const param = 'Param-01_valid.chars'; - const value = 'MockValue'; - const awsResponseMock = { - Parameter: { - Value: value, - }, - }; - const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - - return serverless.variables.getValueFromSsm(`ssm:${param}`).then(resolved => { - expect(resolved).to.be.equal(value); - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - }); + const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + return serverless.variables.getValueFromSsm(`ssm:${param}`) + .should.become(value) + .then(() => { + expect(ssmStub).to.have.been.calledOnce; + expect(ssmStub).to.have.been.calledWithExactly( + 'SSM', + 'getParameter', + { + Name: param, + WithDecryption: false, + }, + { useCache: true }); + }) + .finally(() => ssmStub.restore()); }); - it('should get variable from Ssm using path-style param', () => { - const param = '/path/to/Param-01_valid.chars'; - const value = 'MockValue'; - const awsResponseMock = { - Parameter: { - Value: value, - }, - }; - const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - - return serverless.variables.getValueFromSsm(`ssm:${param}`).then(resolved => { - expect(resolved).to.be.equal(value); - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - }); + const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + return serverless.variables.getValueFromSsm(`ssm:${param}`) + .should.become(value) + .then(() => { + expect(ssmStub).to.have.been.calledOnce; + expect(ssmStub).to.have.been.calledWithExactly( + 'SSM', + 'getParameter', + { + Name: param, + WithDecryption: false, + }, + { useCache: true }); + }) + .finally(() => ssmStub.restore()); }); - it('should get encrypted variable from Ssm using extended syntax', () => { - const param = '/path/to/Param-01_valid.chars'; - const value = 'MockValue'; - const awsResponseMock = { - Parameter: { - Value: value, - }, - }; - const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - - return serverless.variables.getValueFromSsm(`ssm:${param}~true`).then(resolved => { - expect(resolved).to.be.equal(value); - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: true, - }, - { useCache: true } - ); - }); + const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + return serverless.variables.getValueFromSsm(`ssm:${param}~true`) + .should.become(value) + .then(() => { + expect(ssmStub).to.have.been.calledOnce; + expect(ssmStub).to.have.been.calledWithExactly( + 'SSM', + 'getParameter', + { + Name: param, + WithDecryption: true, + }, + { useCache: true }); + }) + .finally(() => ssmStub.restore()); }); - it('should get unencrypted variable from Ssm using extended syntax', () => { - const param = '/path/to/Param-01_valid.chars'; - const value = 'MockValue'; - const awsResponseMock = { - Parameter: { - Value: value, - }, - }; - const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - - return serverless.variables.getValueFromSsm(`ssm:${param}~false`).then(resolved => { - expect(resolved).to.be.equal(value); - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - }); + const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + return serverless.variables.getValueFromSsm(`ssm:${param}~false`) + .should.become(value) + .then(() => { + expect(ssmStub).to.have.been.calledOnce; + expect(ssmStub).to.have.been.calledWithExactly( + 'SSM', + 'getParameter', + { + Name: param, + WithDecryption: false, + }, + { useCache: true }); + }) + .finally(() => ssmStub.restore()); }); - it('should ignore bad values for extended syntax', () => { - const param = '/path/to/Param-01_valid.chars'; - const value = 'MockValue'; - const awsResponseMock = { - Parameter: { - Value: value, - }, - }; - const ssmStub = sinon.stub(awsProvider, 'request').resolves(awsResponseMock); - - return serverless.variables.getValueFromSsm(`ssm:${param}~badvalue`).then(resolved => { - expect(resolved).to.be.equal(value); - expect(ssmStub).to.have.been.calledOnce; - expect(ssmStub).to.have.been.calledWithExactly( - 'SSM', - 'getParameter', - { - Name: param, - WithDecryption: false, - }, - { useCache: true } - ); - }); + const ssmStub = sinon.stub(awsProvider, 'request', () => BbPromise.resolve(awsResponseMock)); + return serverless.variables.getValueFromSsm(`ssm:${param}~badvalue`) + .should.become(value) + .then(() => { + expect(ssmStub).to.have.been.calledOnce; + expect(ssmStub).to.have.been.calledWithExactly( + 'SSM', + 'getParameter', + { + Name: param, + WithDecryption: false, + }, + { useCache: true }); + }) + .finally(() => ssmStub.restore()); }); it('should return undefined if SSM parameter does not exist', () => { - const param = 'ssm:/some/path/to/invalidparam'; const error = new Error(`Parameter ${param} not found.`); - sinon.stub(awsProvider, 'request').rejects(error); - - return expect(() => serverless.variables.getValueFromSsm(param).to.be(undefined)); + const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); + return serverless.variables.getValueFromSsm(`ssm:${param}`) + .should.become(undefined) + .then().finally(() => requestStub.restore()); }); - it('should throw exception if SSM request returns unexpected error', () => { - const param = 'ssm:/some/path/to/invalidparam'; + it('should reject if SSM request returns unexpected error', () => { const error = new Error( 'User: is not authorized to perform: ssm:GetParameter on resource: '); - sinon.stub(awsProvider, 'request').rejects(error); - - return expect(() => serverless.variables.getValueFromSsm(param).to.throw(error)); + const requestStub = sinon.stub(awsProvider, 'request', () => BbPromise.reject(error)); + return serverless.variables.getValueFromSsm(`ssm:${param}`) + .should.be.rejected + .then().finally(() => requestStub.restore()); }); }); - describe('#getDeepValue()', () => { + describe('#getDeeperValue()', () => { it('should get deep values', () => { - const serverless = new Serverless(); - const valueToPopulateMock = { service: 'testService', custom: { @@ -1581,116 +1665,98 @@ module.exports = { }, }, }; - serverless.variables.loadVariableSyntax(); - - return serverless.variables.getDeepValue(['custom', 'subProperty', 'deep'], - valueToPopulateMock).then(valueToPopulate => { - expect(valueToPopulate).to.be.equal('deepValue'); - }); + return serverless.variables.getDeeperValue(['custom', 'subProperty', 'deep'], + valueToPopulateMock).should.become('deepValue'); }); - it('should not throw error if referencing invalid properties', () => { - const serverless = new Serverless(); - const valueToPopulateMock = { service: 'testService', custom: { subProperty: 'hello', }, }; - serverless.variables.loadVariableSyntax(); - - return serverless.variables.getDeepValue(['custom', 'subProperty', 'deep', 'deeper'], - valueToPopulateMock).then(valueToPopulate => { - expect(valueToPopulate).to.deep.equal({}); - }); + return serverless.variables.getDeeperValue(['custom', 'subProperty', 'deep', 'deeper'], + valueToPopulateMock).should.eventually.deep.equal({}); }); - - it('should get deep values with variable references', () => { - const serverless = new Serverless(); - + it('should return a simple deep variable when final deep value is variable', () => { serverless.variables.service = { service: 'testService', custom: { - anotherVar: '${self:custom.var}', subProperty: { + // eslint-disable-next-line no-template-curly-in-string deep: '${self:custom.anotherVar.veryDeep}', }, - var: { - veryDeep: 'someValue', - }, }, provider: serverless.service.provider, }; - serverless.variables.loadVariableSyntax(); - - return serverless.variables.getDeepValue(['custom', 'subProperty', 'deep'], - serverless.variables.service).then(valueToPopulate => { - expect(valueToPopulate).to.be.equal('someValue'); - }); + return serverless.variables.getDeeperValue( + ['custom', 'subProperty', 'deep'], + serverless.variables.service + ).should.become('${deep:0}'); + }); + it('should return a deep continuation when middle deep value is variable', () => { + serverless.variables.service = { + service: 'testService', + custom: { + anotherVar: '${self:custom.var}', // eslint-disable-line no-template-curly-in-string + }, + provider: serverless.service.provider, + }; + serverless.variables.loadVariableSyntax(); + return serverless.variables.getDeeperValue( + ['custom', 'anotherVar', 'veryDeep'], + serverless.variables.service) + .should.become('${deep:0.veryDeep}'); }); }); - describe('#warnIfNotFound()', () => { let logWarningSpy; let consoleLogStub; let varProxy; - beforeEach(() => { logWarningSpy = sinon.spy(slsError, 'logWarning'); consoleLogStub = sinon.stub(console, 'log').returns(); - const ProxyQuiredVariables = proxyquire('./Variables.js', { - './Error': logWarningSpy, - }); - varProxy = new ProxyQuiredVariables(new Serverless()); + const ProxyQuiredVariables = proxyquire('./Variables.js', { './Error': logWarningSpy }); + varProxy = new ProxyQuiredVariables(serverless); }); - afterEach(() => { logWarningSpy.restore(); consoleLogStub.restore(); }); - it('should do nothing if variable has valid value.', () => { varProxy.warnIfNotFound('self:service', 'a-valid-value'); expect(logWarningSpy).to.not.have.been.calledOnce; }); - it('should log if variable has null value.', () => { varProxy.warnIfNotFound('self:service', null); expect(logWarningSpy).to.have.been.calledOnce; }); - it('should log if variable has undefined value.', () => { varProxy.warnIfNotFound('self:service', undefined); expect(logWarningSpy).to.have.been.calledOnce; }); - it('should log if variable has empty object value.', () => { varProxy.warnIfNotFound('self:service', {}); expect(logWarningSpy).to.have.been.calledOnce; }); - it('should detect the "environment variable" variable type', () => { varProxy.warnIfNotFound('env:service', null); expect(logWarningSpy).to.have.been.calledOnce; expect(logWarningSpy.args[0][0]).to.contain('environment variable'); }); - it('should detect the "option" variable type', () => { varProxy.warnIfNotFound('opt:service', null); expect(logWarningSpy).to.have.been.calledOnce; expect(logWarningSpy.args[0][0]).to.contain('option'); }); - it('should detect the "service attribute" variable type', () => { varProxy.warnIfNotFound('self:service', null); expect(logWarningSpy).to.have.been.calledOnce; expect(logWarningSpy.args[0][0]).to.contain('service attribute'); }); - it('should detect the "file" variable type', () => { varProxy.warnIfNotFound('file(service)', null); expect(logWarningSpy).to.have.been.calledOnce; diff --git a/lib/plugins/Plugins.json b/lib/plugins/Plugins.json index 161303d0de9..15ef563af29 100644 --- a/lib/plugins/Plugins.json +++ b/lib/plugins/Plugins.json @@ -46,6 +46,7 @@ "./aws/package/compile/events/cloudWatchEvent/index.js", "./aws/package/compile/events/cloudWatchLog/index.js", "./aws/package/compile/events/cognitoUserPool/index.js", + "./aws/package/compile/events/sqs/index.js", "./aws/deployFunction/index.js", "./aws/deployList/index.js", "./aws/invokeLocal/index.js", diff --git a/lib/plugins/aws/deploy/lib/cleanupS3Bucket.js b/lib/plugins/aws/deploy/lib/cleanupS3Bucket.js index 47bc280c92a..e5440360946 100644 --- a/lib/plugins/aws/deploy/lib/cleanupS3Bucket.js +++ b/lib/plugins/aws/deploy/lib/cleanupS3Bucket.js @@ -33,7 +33,7 @@ module.exports = { removeObjects(objectsToRemove) { if (objectsToRemove && objectsToRemove.length) { - this.serverless.cli.log('Removing old service versions...'); + this.serverless.cli.log('Removing old service artifacts from S3...'); return this.provider.request('S3', 'deleteObjects', diff --git a/lib/plugins/aws/deploy/lib/createStack.js b/lib/plugins/aws/deploy/lib/createStack.js index de10370e8b8..f37c2379b3b 100644 --- a/lib/plugins/aws/deploy/lib/createStack.js +++ b/lib/plugins/aws/deploy/lib/createStack.js @@ -32,6 +32,10 @@ module.exports = { params.RoleARN = this.serverless.service.provider.cfnRole; } + if (this.serverless.service.provider.notificationArns) { + params.NotificationARNs = this.serverless.service.provider.notificationArns; + } + return this.provider.request( 'CloudFormation', 'createStack', diff --git a/lib/plugins/aws/deploy/lib/createStack.test.js b/lib/plugins/aws/deploy/lib/createStack.test.js index 28d5820e99b..eff833e4587 100644 --- a/lib/plugins/aws/deploy/lib/createStack.test.js +++ b/lib/plugins/aws/deploy/lib/createStack.test.js @@ -6,7 +6,6 @@ const path = require('path'); const AwsProvider = require('../../provider/awsProvider'); const AwsDeploy = require('../index'); const Serverless = require('../../../../Serverless'); -const BbPromise = require('bluebird'); const testUtils = require('../../../../../tests/utils'); describe('createStack', () => { @@ -73,6 +72,22 @@ describe('createStack', () => { .to.equal('arn:aws:iam::123456789012:role/myrole'); }); }); + + it('should use use notificationArns if it is specified', () => { + const mytopicArn = 'arn:aws:sns::123456789012:mytopic'; + awsDeploy.serverless.service.provider.notificationArns = [mytopicArn]; + + const createStackStub = sinon + .stub(awsDeploy.provider, 'request').resolves(); + sinon.stub(awsDeploy, 'monitorStack').resolves(); + + return awsDeploy.create().then(() => { + expect(createStackStub.args[0][2].NotificationARNs) + .to.deep.equal([mytopicArn]); + awsDeploy.provider.request.restore(); + awsDeploy.monitorStack.restore(); + }); + }); }); describe('#createStack()', () => { @@ -98,8 +113,7 @@ describe('createStack', () => { it('should set the createLater flag and resolve if deployment bucket is provided', () => { awsDeploy.serverless.service.provider.deploymentBucket = 'serverless'; - sandbox.stub(awsDeploy.provider, 'request') - .returns(BbPromise.reject({ message: 'does not exist' })); + sandbox.stub(awsDeploy.provider, 'request').rejects(new Error('does not exist')); return awsDeploy.createStack().then(() => { expect(awsDeploy.createLater).to.equal(true); diff --git a/lib/plugins/aws/deploy/lib/extendedValidate.test.js b/lib/plugins/aws/deploy/lib/extendedValidate.test.js index 7b1d3d9adcf..7cbabdc5fb9 100644 --- a/lib/plugins/aws/deploy/lib/extendedValidate.test.js +++ b/lib/plugins/aws/deploy/lib/extendedValidate.test.js @@ -1,6 +1,6 @@ 'use strict'; -const expect = require('chai').expect; +const chai = require('chai'); const sinon = require('sinon'); const path = require('path'); const AwsProvider = require('../../provider/awsProvider'); @@ -8,6 +8,10 @@ const AwsDeploy = require('../index'); const Serverless = require('../../../../Serverless'); const testUtils = require('../../../../../tests/utils'); +chai.use(require('sinon-chai')); + +const expect = chai.expect; + describe('extendedValidate', () => { let awsDeploy; const tmpDirPath = testUtils.getTmpDirPath(); @@ -60,8 +64,8 @@ describe('extendedValidate', () => { }); afterEach(() => { - awsDeploy.serverless.utils.fileExistsSync.restore(); - awsDeploy.serverless.utils.readFileSync.restore(); + fileExistsSyncStub.restore(); + readFileSyncStub.restore(); }); it('should throw error if state file does not exist', () => { diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.js index 951d11ace3e..ed9d2e10092 100644 --- a/lib/plugins/aws/deploy/lib/uploadArtifacts.js +++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.js @@ -2,6 +2,7 @@ /* eslint-disable no-use-before-define */ +const _ = require('lodash'); const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); @@ -9,6 +10,8 @@ const BbPromise = require('bluebird'); const filesize = require('filesize'); const normalizeFiles = require('../../lib/normalizeFiles'); +const NUM_CONCURRENT_UPLOADS = 3; + module.exports = { uploadArtifacts() { return BbPromise.bind(this) @@ -76,36 +79,36 @@ module.exports = { }, uploadFunctions() { - let shouldUploadService = false; this.serverless.cli.log('Uploading artifacts...'); + const functionNames = this.serverless.service.getAllFunctions(); - return BbPromise.map(functionNames, (name) => { - const functionArtifactFileName = this.provider.naming.getFunctionArtifactName(name); - const functionObject = this.serverless.service.getFunction(name); - functionObject.package = functionObject.package || {}; - let artifactFilePath = functionObject.package.artifact || - this.serverless.service.package.artifact; - if (!artifactFilePath || - (this.serverless.service.artifact && !functionObject.package.artifact)) { - if (this.serverless.service.package.individually || functionObject.package.individually) { - const artifactFileName = functionArtifactFileName; - artifactFilePath = path.join(this.packagePath, artifactFileName); - return this.uploadZipFile(artifactFilePath); + const artifactFilePaths = _.uniq( + _.map(functionNames, (name) => { + const functionArtifactFileName = this.provider.naming.getFunctionArtifactName(name); + const functionObject = this.serverless.service.getFunction(name); + functionObject.package = functionObject.package || {}; + const artifactFilePath = functionObject.package.artifact || + this.serverless.service.package.artifact; + + if (!artifactFilePath || + (this.serverless.service.artifact && !functionObject.package.artifact)) { + if (this.serverless.service.package.individually || functionObject.package.individually) { + const artifactFileName = functionArtifactFileName; + return path.join(this.packagePath, artifactFileName); + } + return path.join(this.packagePath, this.provider.naming.getServiceArtifactName()); } - shouldUploadService = true; - return BbPromise.resolve(); - } + + return artifactFilePath; + }) + ); + + return BbPromise.map(artifactFilePaths, (artifactFilePath) => { + const stats = fs.statSync(artifactFilePath); + this.serverless.cli.log(`Uploading service .zip file to S3 (${filesize(stats.size)})...`); return this.uploadZipFile(artifactFilePath); - }, { concurrency: 3 }).then(() => { - if (shouldUploadService) { - const artifactFileName = this.provider.naming.getServiceArtifactName(); - const artifactFilePath = path.join(this.packagePath, artifactFileName); - const stats = fs.statSync(artifactFilePath); - this.serverless.cli.log(`Uploading service .zip file to S3 (${filesize(stats.size)})...`); - return this.uploadZipFile(artifactFilePath); - } - return BbPromise.resolve(); - }); + }, { concurrency: NUM_CONCURRENT_UPLOADS } + ); }, }; diff --git a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js index df96e23ff68..a54249fe6fe 100644 --- a/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js +++ b/lib/plugins/aws/deploy/lib/uploadArtifacts.test.js @@ -90,8 +90,8 @@ describe('uploadArtifacts', () => { }); afterEach(() => { - normalizeFiles.normalizeCloudFormationTemplate.restore(); - awsDeploy.provider.request.restore(); + normalizeCloudFormationTemplateStub.restore(); + uploadStub.restore(); }); it('should upload the CloudFormation file to the S3 bucket', () => { @@ -159,8 +159,8 @@ describe('uploadArtifacts', () => { }); afterEach(() => { - fs.readFileSync.restore(); - awsDeploy.provider.request.restore(); + readFileSyncStub.restore(); + uploadStub.restore(); }); it('should throw for null artifact paths', () => { @@ -225,19 +225,46 @@ describe('uploadArtifacts', () => { }); describe('#uploadFunctions()', () => { + let uploadZipFileStub; + + beforeEach(() => { + sinon.stub(fs, 'statSync').returns({ size: 1024 }); + uploadZipFileStub = sinon.stub(awsDeploy, 'uploadZipFile').resolves(); + }); + + afterEach(() => { + fs.statSync.restore(); + uploadZipFileStub.restore(); + }); + it('should upload the service artifact file to the S3 bucket', () => { awsDeploy.serverless.config.servicePath = 'some/path'; awsDeploy.serverless.service.service = 'new-service'; - sinon.stub(fs, 'statSync').returns({ size: 0 }); + return awsDeploy.uploadFunctions().then(() => { + expect(uploadZipFileStub.calledOnce).to.be.equal(true); + const expectedPath = path.join('foo', '.serverless', 'new-service.zip'); + expect(uploadZipFileStub.args[0][0]).to.be.equal(expectedPath); + }); + }); - const uploadZipFileStub = sinon - .stub(awsDeploy, 'uploadZipFile').resolves(); + it('should upload a single .zip file to the S3 bucket when not packaging individually', () => { + awsDeploy.serverless.service.functions = { + first: { + package: { + artifact: 'artifact.zip', + }, + }, + second: { + package: { + artifact: 'artifact.zip', + }, + }, + }; return awsDeploy.uploadFunctions().then(() => { expect(uploadZipFileStub.calledOnce).to.be.equal(true); - fs.statSync.restore(); - awsDeploy.uploadZipFile.restore(); + expect(uploadZipFileStub.args[0][0]).to.be.equal('artifact.zip'); }); }); @@ -256,16 +283,12 @@ describe('uploadArtifacts', () => { }, }; - const uploadZipFileStub = sinon - .stub(awsDeploy, 'uploadZipFile').resolves(); - return awsDeploy.uploadFunctions().then(() => { expect(uploadZipFileStub.calledTwice).to.be.equal(true); expect(uploadZipFileStub.args[0][0]) .to.be.equal(awsDeploy.serverless.service.functions.first.package.artifact); expect(uploadZipFileStub.args[1][0]) .to.be.equal(awsDeploy.serverless.service.functions.second.package.artifact); - awsDeploy.uploadZipFile.restore(); }); }); @@ -284,18 +307,12 @@ describe('uploadArtifacts', () => { }, }; - const uploadZipFileStub = sinon - .stub(awsDeploy, 'uploadZipFile').resolves(); - sinon.stub(fs, 'statSync').returns({ size: 1024 }); - return awsDeploy.uploadFunctions().then(() => { expect(uploadZipFileStub.calledTwice).to.be.equal(true); expect(uploadZipFileStub.args[0][0]) .to.be.equal(awsDeploy.serverless.service.functions.first.package.artifact); expect(uploadZipFileStub.args[1][0]) .to.be.equal(awsDeploy.serverless.service.package.artifact); - awsDeploy.uploadZipFile.restore(); - fs.statSync.restore(); }); }); @@ -303,16 +320,11 @@ describe('uploadArtifacts', () => { awsDeploy.serverless.config.servicePath = 'some/path'; awsDeploy.serverless.service.service = 'new-service'; - sinon.stub(fs, 'statSync').returns({ size: 1024 }); - sinon.stub(awsDeploy, 'uploadZipFile').resolves(); sinon.spy(awsDeploy.serverless.cli, 'log'); return awsDeploy.uploadFunctions().then(() => { const expected = 'Uploading service .zip file to S3 (1 KB)...'; expect(awsDeploy.serverless.cli.log.calledWithExactly(expected)).to.be.equal(true); - - fs.statSync.restore(); - awsDeploy.uploadZipFile.restore(); }); }); }); diff --git a/lib/plugins/aws/deploy/lib/validateTemplate.js b/lib/plugins/aws/deploy/lib/validateTemplate.js index 85265f685e8..8c1626447d2 100644 --- a/lib/plugins/aws/deploy/lib/validateTemplate.js +++ b/lib/plugins/aws/deploy/lib/validateTemplate.js @@ -1,14 +1,15 @@ 'use strict'; +const getS3EndpointForRegion = require('../../utils/getS3EndpointForRegion'); module.exports = { validateTemplate() { const bucketName = this.bucketName; const artifactDirectoryName = this.serverless.service.package.artifactDirectoryName; const compiledTemplateFileName = 'compiled-cloudformation-template.json'; - + const s3Endpoint = getS3EndpointForRegion(this.provider.getRegion()); this.serverless.cli.log('Validating template...'); const params = { - TemplateURL: `https://s3.amazonaws.com/${bucketName}/${artifactDirectoryName}/${compiledTemplateFileName}`, + TemplateURL: `https://${s3Endpoint}/${bucketName}/${artifactDirectoryName}/${compiledTemplateFileName}`, }; return this.provider.request( diff --git a/lib/plugins/aws/deployFunction/index.js b/lib/plugins/aws/deployFunction/index.js index a5b4d34f940..f0ce80ccba7 100644 --- a/lib/plugins/aws/deployFunction/index.js +++ b/lib/plugins/aws/deployFunction/index.js @@ -57,19 +57,19 @@ class AwsDeployFunction { 'getFunction', params ) - .then((result) => { - this.serverless.service.provider.remoteFunctionData = result; - return result; - }) - .catch(() => { - const errorMessage = [ - `The function "${this.options.function}" you want to update is not yet deployed.`, - ' Please run "serverless deploy" to deploy your service.', - ' After that you can redeploy your services functions with the', - ' "serverless deploy function" command.', - ].join(''); - throw new this.serverless.classes.Error(errorMessage); - }); + .then((result) => { + this.serverless.service.provider.remoteFunctionData = result; + return result; + }) + .catch(() => { + const errorMessage = [ + `The function "${this.options.function}" you want to update is not yet deployed.`, + ' Please run "serverless deploy" to deploy your service.', + ' After that you can redeploy your services functions with the', + ' "serverless deploy function" command.', + ].join(''); + throw new this.serverless.classes.Error(errorMessage); + }); } normalizeArnRole(role) { @@ -84,8 +84,8 @@ class AwsDeployFunction { const roleProperties = roleResource.Properties; const compiledFullRoleName = `${roleProperties.Path || '/'}${roleProperties.RoleName}`; - return this.provider.getAccountId().then((accountId) => - `arn:aws:iam::${accountId}:role${compiledFullRoleName}` + return this.provider.getAccountInfo().then((result) => + `arn:${result.partition}:iam::${result.accountId}:role${compiledFullRoleName}` ); } @@ -164,6 +164,10 @@ class AwsDeployFunction { const errorMessage = 'Invalid characters in environment variable'; throw new this.serverless.classes.Error(errorMessage); } + if (!_.isString(params.Environment.Variables[key])) { + const errorMessage = `Environment variable ${key} must contain strings`; + throw new this.serverless.classes.Error(errorMessage); + } }); } } diff --git a/lib/plugins/aws/deployFunction/index.test.js b/lib/plugins/aws/deployFunction/index.test.js index ecbf65ddd0f..526607c92d1 100644 --- a/lib/plugins/aws/deployFunction/index.test.js +++ b/lib/plugins/aws/deployFunction/index.test.js @@ -1,5 +1,5 @@ 'use strict'; - +/* eslint-disable no-unused-expressions */ const chai = require('chai'); const sinon = require('sinon'); const path = require('path'); @@ -120,13 +120,13 @@ describe('AwsDeployFunction', () => { }); describe('#normalizeArnRole', () => { - let getAccountIdStub; + let getAccountInfoStub; let getRoleStub; beforeEach(() => { - getAccountIdStub = sinon - .stub(awsDeployFunction.provider, 'getAccountId') - .resolves('123456789012'); + getAccountInfoStub = sinon + .stub(awsDeployFunction.provider, 'getAccountInfo') + .resolves({ accountId: '123456789012', partition: 'aws' }); getRoleStub = sinon .stub(awsDeployFunction.provider, 'request') .resolves({ Arn: 'arn:aws:iam::123456789012:role/role_2' }); @@ -144,7 +144,7 @@ describe('AwsDeployFunction', () => { }); afterEach(() => { - awsDeployFunction.provider.getAccountId.restore(); + awsDeployFunction.provider.getAccountInfo.restore(); awsDeployFunction.provider.request.restore(); serverless.service.resources = undefined; }); @@ -153,7 +153,7 @@ describe('AwsDeployFunction', () => { const arn = 'arn:aws:iam::123456789012:role/role'; return awsDeployFunction.normalizeArnRole(arn).then((result) => { - expect(getAccountIdStub.calledOnce).to.be.equal(false); + expect(getAccountInfoStub).to.not.have.been.called; expect(result).to.be.equal(arn); }); }); @@ -162,7 +162,7 @@ describe('AwsDeployFunction', () => { const roleName = 'MyCustomRole'; return awsDeployFunction.normalizeArnRole(roleName).then((result) => { - expect(getAccountIdStub.calledOnce).to.be.equal(true); + expect(getAccountInfoStub).to.have.been.called; expect(result).to.be.equal('arn:aws:iam::123456789012:role/role_123'); }); }); @@ -177,7 +177,7 @@ describe('AwsDeployFunction', () => { return awsDeployFunction.normalizeArnRole(roleObj).then((result) => { expect(getRoleStub.calledOnce).to.be.equal(true); - expect(getAccountIdStub.calledOnce).to.be.equal(false); + expect(getAccountInfoStub).to.not.have.been.called; expect(result).to.be.equal('arn:aws:iam::123456789012:role/role_2'); }); }); @@ -336,7 +336,7 @@ describe('AwsDeployFunction', () => { awsDeployFunction.options = options; return expect(awsDeployFunction.updateFunctionConfiguration()).to.be.fulfilled - .then(() => expect(updateFunctionConfigurationStub).to.not.be.called); + .then(() => expect(updateFunctionConfigurationStub).to.not.be.called); }); it('should fail when using invalid characters in environment variable', () => { @@ -353,6 +353,20 @@ describe('AwsDeployFunction', () => { expect(() => awsDeployFunction.updateFunctionConfiguration()).to.throw(Error); }); + it('should fail when using non-string values as environment variables', () => { + options.functionObj = { + name: 'first', + description: 'change', + environment: { + COUNTER: 6, + }, + }; + + awsDeployFunction.options = options; + + expect(() => awsDeployFunction.updateFunctionConfiguration()).to.throw(Error); + }); + it('should inherit provider-level config', () => { options.functionObj = { name: 'first', diff --git a/lib/plugins/aws/invoke/index.js b/lib/plugins/aws/invoke/index.js index 58cd837d902..2354a1d282d 100644 --- a/lib/plugins/aws/invoke/index.js +++ b/lib/plugins/aws/invoke/index.js @@ -83,12 +83,12 @@ class AwsInvoke { } log(invocationReply) { - const color = !invocationReply.FunctionError ? 'white' : 'red'; + const color = !invocationReply.FunctionError ? (x => x) : chalk.red; if (invocationReply.Payload) { const response = JSON.parse(invocationReply.Payload); - this.consoleLog(chalk[color](JSON.stringify(response, null, 4))); + this.consoleLog(color(JSON.stringify(response, null, 4))); } if (invocationReply.LogResult) { diff --git a/lib/plugins/aws/invoke/index.test.js b/lib/plugins/aws/invoke/index.test.js index 8f3a7bcda88..91591169587 100644 --- a/lib/plugins/aws/invoke/index.test.js +++ b/lib/plugins/aws/invoke/index.test.js @@ -6,7 +6,6 @@ const path = require('path'); const AwsInvoke = require('./index'); const AwsProvider = require('../provider/awsProvider'); const Serverless = require('../../../Serverless'); -const chalk = require('chalk'); const testUtils = require('../../../../tests/utils'); describe('AwsInvoke', () => { @@ -271,7 +270,7 @@ describe('AwsInvoke', () => { }; return awsInvoke.log(invocationReplyMock).then(() => { - const expectedPayloadMessage = `${chalk.white('{\n "testProp": "testValue"\n}')}`; + const expectedPayloadMessage = '{\n "testProp": "testValue"\n}'; expect(consoleLogStub.calledWith(expectedPayloadMessage)).to.equal(true); }); diff --git a/lib/plugins/aws/invokeLocal/fixture/handlerWithLoadingError.js b/lib/plugins/aws/invokeLocal/fixture/handlerWithLoadingError.js new file mode 100644 index 00000000000..a945eb0fdf1 --- /dev/null +++ b/lib/plugins/aws/invokeLocal/fixture/handlerWithLoadingError.js @@ -0,0 +1,2 @@ +'use strict'; +throw new Error('loading exception'); diff --git a/lib/plugins/aws/invokeLocal/index.js b/lib/plugins/aws/invokeLocal/index.js index ecc355cf91d..ae181fa018f 100644 --- a/lib/plugins/aws/invokeLocal/index.js +++ b/lib/plugins/aws/invokeLocal/index.js @@ -130,8 +130,9 @@ class AwsInvokeLocal { } if (runtime === 'python2.7' || runtime === 'python3.6') { - const handlerPath = handler.split('.')[0]; - const handlerName = handler.split('.')[1]; + const handlerComponents = handler.split(/\./); + const handlerPath = handlerComponents.slice(0, -1).join('.'); + const handlerName = handlerComponents.pop(); return this.invokeLocalPython( process.platform === 'win32' ? 'python.exe' : runtime, handlerPath, @@ -248,24 +249,24 @@ class AwsInvokeLocal { invokeLocalNodeJs(handlerPath, handlerName, event, customContext) { let lambda; - + let pathToHandler; try { /* * we need require() here to load the handler from the file system * which the user has to supply by passing the function name */ - + pathToHandler = path.join( + this.serverless.config.servicePath, + this.options.extraServicePath || '', + handlerPath + ); const handlersContainer = require( // eslint-disable-line global-require - path.join( - this.serverless.config.servicePath, - this.options.extraServicePath || '', - handlerPath - ) + pathToHandler ); lambda = handlersContainer[handlerName]; } catch (error) { this.serverless.cli.consoleLog(error); - process.exit(0); + throw new Error(`Exception encountered when loading ${pathToHandler}`); } const callback = (err, result) => { diff --git a/lib/plugins/aws/invokeLocal/index.test.js b/lib/plugins/aws/invokeLocal/index.test.js index c2e83743507..87be0e5841b 100644 --- a/lib/plugins/aws/invokeLocal/index.test.js +++ b/lib/plugins/aws/invokeLocal/index.test.js @@ -503,6 +503,13 @@ describe('AwsInvokeLocal', () => { expect(serverless.cli.consoleLog.lastCall.args[0]).to.contain('"errorMessage": "failed"'); }); + + it('should throw when module loading error', () => { + awsInvokeLocal.serverless.config.servicePath = __dirname; + + expect(() => awsInvokeLocal.invokeLocalNodeJs('fixture/handlerWithLoadingError', + 'anyMethod')).to.throw(/Exception encountered when loading/); + }); }); // Ignored because it fails in CI diff --git a/lib/plugins/aws/lib/naming.js b/lib/plugins/aws/lib/naming.js index f2c81c776e4..8e25ec3c588 100644 --- a/lib/plugins/aws/lib/naming.js +++ b/lib/plugins/aws/lib/naming.js @@ -46,6 +46,10 @@ module.exports = { // Stack getStackName() { + if (this.provider.serverless.service.provider.stackName && + _.isString(this.provider.serverless.service.provider.stackName)) { + return `${this.provider.serverless.service.provider.stackName}`; + } return `${this.provider.serverless.service.service}-${this.provider.getStage()}`; }, @@ -145,6 +149,10 @@ module.exports = { // API Gateway getApiGatewayName() { + if (this.provider.serverless.service.provider.apiName && + _.isString(this.provider.serverless.service.provider.apiName)) { + return `${this.provider.serverless.service.provider.apiName}`; + } return `${this.provider.getStage()}-${this.provider.serverless.service.service}`; }, generateApiGatewayDeploymentLogicalId() { @@ -257,6 +265,15 @@ module.exports = { return `CognitoUserPool${this.normalizeNameToAlphaNumericOnly(poolId)}`; }, + // SQS + getQueueLogicalId(functionName, queueName) { + return `${ + this.getNormalizedFunctionName(functionName) + }EventSourceMappingSQS${ + this.normalizeNameToAlphaNumericOnly(queueName) + }`; + }, + // Permissions getLambdaS3PermissionLogicalId(functionName, bucketName) { return `${this.getNormalizedFunctionName(functionName)}LambdaPermission${this @@ -282,8 +299,9 @@ module.exports = { getLambdaApiGatewayPermissionLogicalId(functionName) { return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionApiGateway`; }, - getLambdaAlexaSkillPermissionLogicalId(functionName) { - return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSkill`; + getLambdaAlexaSkillPermissionLogicalId(functionName, alexaSkillIndex) { + return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSkill${ + alexaSkillIndex || '0'}`; }, getLambdaAlexaSmartHomePermissionLogicalId(functionName, alexaSmartHomeIndex) { return `${this.getNormalizedFunctionName(functionName)}LambdaPermissionAlexaSmartHome${ diff --git a/lib/plugins/aws/lib/naming.test.js b/lib/plugins/aws/lib/naming.test.js index 7becb143864..5c76568b3bd 100644 --- a/lib/plugins/aws/lib/naming.test.js +++ b/lib/plugins/aws/lib/naming.test.js @@ -94,11 +94,18 @@ describe('#naming()', () => { }); describe('#getStackName()', () => { - it('should use the service name and stage from the service and config', () => { + it('should use the service name & stage if custom stack name not provided', () => { serverless.service.service = 'myService'; expect(sdk.naming.getStackName()).to.equal(`${serverless.service.service}-${ sdk.naming.provider.getStage()}`); }); + + it('should use the custom stack name if provided', () => { + serverless.service.provider.stackName = 'app-dev-testApp'; + serverless.service.service = 'myService'; + serverless.service.provider.stage = sdk.naming.provider.getStage(); + expect(sdk.naming.getStackName()).to.equal('app-dev-testApp'); + }); }); describe('#getRolePath()', () => { @@ -214,11 +221,18 @@ describe('#naming()', () => { }); describe('#getApiGatewayName()', () => { - it('should return the composition of stage and service name', () => { + it('should return the composition of stage & service name if custom name not provided', () => { serverless.service.service = 'myService'; expect(sdk.naming.getApiGatewayName()) .to.equal(`${sdk.naming.provider.getStage()}-${serverless.service.service}`); }); + + it('should return the custom api name if provided', () => { + serverless.service.provider.apiName = 'app-dev-testApi'; + serverless.service.service = 'myService'; + serverless.service.provider.stage = sdk.naming.provider.getStage(); + expect(sdk.naming.getApiGatewayName()).to.equal('app-dev-testApi'); + }); }); describe('#generateApiGatewayDeploymentLogicalId()', () => { @@ -464,9 +478,15 @@ describe('#naming()', () => { describe('#getLambdaAlexaSkillPermissionLogicalId()', () => { it('should normalize the function name and append the standard suffix', + () => { + expect(sdk.naming.getLambdaAlexaSkillPermissionLogicalId('functionName', 2)) + .to.equal('FunctionNameLambdaPermissionAlexaSkill2'); + }); + + it('should normalize the function name and append a default suffix if not defined', () => { expect(sdk.naming.getLambdaAlexaSkillPermissionLogicalId('functionName')) - .to.equal('FunctionNameLambdaPermissionAlexaSkill'); + .to.equal('FunctionNameLambdaPermissionAlexaSkill0'); }); }); @@ -502,4 +522,11 @@ describe('#naming()', () => { )).to.equal('FunctionNameLambdaPermissionCognitoUserPoolPool1TriggerSourceCustomMessage'); }); }); + + describe('#getQueueLogicalId()', () => { + it('should normalize the function name and add the standard suffix', () => { + expect(sdk.naming.getQueueLogicalId('functionName', 'MyQueue')) + .to.equal('FunctionNameEventSourceMappingSQSMyQueue'); + }); + }); }); diff --git a/lib/plugins/aws/lib/updateStack.js b/lib/plugins/aws/lib/updateStack.js index 5ac87bf5993..5d53811c49b 100644 --- a/lib/plugins/aws/lib/updateStack.js +++ b/lib/plugins/aws/lib/updateStack.js @@ -2,6 +2,7 @@ const _ = require('lodash'); const BbPromise = require('bluebird'); +const getS3EndpointForRegion = require('../utils/getS3EndpointForRegion'); const NO_UPDATE_MESSAGE = 'No updates are to be performed.'; @@ -13,7 +14,8 @@ module.exports = { const stackName = this.provider.naming.getStackName(); let stackTags = { STAGE: this.provider.getStage() }; const compiledTemplateFileName = 'compiled-cloudformation-template.json'; - const templateUrl = `https://s3.amazonaws.com/${this.bucketName}/${this.serverless.service.package.artifactDirectoryName}/${compiledTemplateFileName}`; + const s3Endpoint = getS3EndpointForRegion(this.provider.getRegion()); + const templateUrl = `https://${s3Endpoint}/${this.bucketName}/${this.serverless.service.package.artifactDirectoryName}/${compiledTemplateFileName}`; // Merge additional stack tags if (typeof this.serverless.service.provider.stackTags === 'object') { @@ -36,6 +38,10 @@ module.exports = { params.RoleARN = this.serverless.service.provider.cfnRole; } + if (this.serverless.service.provider.notificationArns) { + params.NotificationARNs = this.serverless.service.provider.notificationArns; + } + return this.provider.request('CloudFormation', 'createStack', params) @@ -44,7 +50,8 @@ module.exports = { update() { const compiledTemplateFileName = 'compiled-cloudformation-template.json'; - const templateUrl = `https://s3.amazonaws.com/${this.bucketName}/${this.serverless.service.package.artifactDirectoryName}/${compiledTemplateFileName}`; + const s3Endpoint = getS3EndpointForRegion(this.provider.getRegion()); + const templateUrl = `https://${s3Endpoint}/${this.bucketName}/${this.serverless.service.package.artifactDirectoryName}/${compiledTemplateFileName}`; this.serverless.cli.log('Updating Stack...'); const stackName = this.provider.naming.getStackName(); @@ -70,9 +77,13 @@ module.exports = { params.RoleARN = this.serverless.service.provider.cfnRole; } + if (this.serverless.service.provider.notificationArns) { + params.NotificationARNs = this.serverless.service.provider.notificationArns; + } + // Policy must have at least one statement, otherwise no updates would be possible at all if (this.serverless.service.provider.stackPolicy && - this.serverless.service.provider.stackPolicy.length) { + !_.isEmpty(this.serverless.service.provider.stackPolicy)) { params.StackPolicyBody = JSON.stringify({ Statement: this.serverless.service.provider.stackPolicy, }); diff --git a/lib/plugins/aws/lib/updateStack.test.js b/lib/plugins/aws/lib/updateStack.test.js index ad172de4e12..bf692981308 100644 --- a/lib/plugins/aws/lib/updateStack.test.js +++ b/lib/plugins/aws/lib/updateStack.test.js @@ -92,6 +92,21 @@ describe('updateStack', () => { awsDeploy.monitorStack.restore(); }); }); + + it('should use use notificationArns if it is specified', () => { + const mytopicArn = 'arn:aws:sns::123456789012:mytopic'; + awsDeploy.serverless.service.provider.notificationArns = [mytopicArn]; + + const createStackStub = sinon.stub(awsDeploy.provider, 'request').resolves(); + sinon.stub(awsDeploy, 'monitorStack').resolves(); + + return awsDeploy.createFallback().then(() => { + expect(createStackStub.args[0][2].NotificationARNs) + .to.deep.equal([mytopicArn]); + awsDeploy.provider.request.restore(); + awsDeploy.monitorStack.restore(); + }); + }); }); describe('#update()', () => { @@ -168,6 +183,16 @@ describe('updateStack', () => { .to.equal('arn:aws:iam::123456789012:role/myrole'); }); }); + + it('should use use notificationArns if it is specified', () => { + const mytopicArn = 'arn:aws:sns::123456789012:mytopic'; + awsDeploy.serverless.service.provider.notificationArns = [mytopicArn]; + + return awsDeploy.update().then(() => { + expect(updateStackStub.args[0][2].NotificationARNs) + .to.deep.equal([mytopicArn]); + }); + }); }); describe('#updateStack()', () => { diff --git a/lib/plugins/aws/logs/index.js b/lib/plugins/aws/logs/index.js index 2d4a42c05e6..0eea806b015 100644 --- a/lib/plugins/aws/logs/index.js +++ b/lib/plugins/aws/logs/index.js @@ -87,6 +87,11 @@ class AwsLogs { } else { params.startTime = moment.utc(this.options.startTime).valueOf(); } + } else { + params.startTime = moment().subtract(10, 'm').valueOf(); + if (this.options.tail) { + params.startTime = moment().subtract(10, 's').valueOf(); + } } return this.provider diff --git a/lib/plugins/aws/logs/index.test.js b/lib/plugins/aws/logs/index.test.js index 0883a012b3d..343459124bf 100644 --- a/lib/plugins/aws/logs/index.test.js +++ b/lib/plugins/aws/logs/index.test.js @@ -157,10 +157,11 @@ describe('AwsLogs', () => { describe('#showLogs()', () => { let clock; + const fakeTime = new Date(Date.UTC(2016, 9, 1)).getTime(); beforeEach(() => { - // new Date() => return the fake Date 'Sat Sep 01 2016 00:00:00' - clock = sinon.useFakeTimers(new Date(Date.UTC(2016, 9, 1)).getTime()); + // set the fake Date 'Sat Sep 01 2016 00:00:00' + clock = sinon.useFakeTimers(fakeTime); }); afterEach(() => { @@ -209,7 +210,7 @@ describe('AwsLogs', () => { interleaved: true, logStreamNames: logStreamNamesMock, filterPattern: 'error', - startTime: 1475269200000, + startTime: fakeTime - (3 * 60 * 60 * 1000), // -3h } )).to.be.equal(true); awsLogs.provider.request.restore(); @@ -264,5 +265,98 @@ describe('AwsLogs', () => { awsLogs.provider.request.restore(); }); }); + + it('should call filterLogEvents API with latest 10 minutes if startTime not given', () => { + const replyMock = { + events: [ + { + logStreamName: '2016/07/28/[$LATEST]83f5206ab2a8488290349b9c1fbfe2ba', + timestamp: 1469687512311, + message: 'test', + }, + { + logStreamName: '2016/07/28/[$LATEST]83f5206ab2a8488290349b9c1fbfe2ba', + timestamp: 1469687512311, + message: 'test', + }, + ], + }; + const logStreamNamesMock = [ + '2016/07/28/[$LATEST]83f5206ab2a8488290349b9c1fbfe2ba', + '2016/07/28/[$LATEST]83f5206ab2a8488290349b9c1fbfe2ba', + ]; + const filterLogEventsStub = sinon.stub(awsLogs.provider, 'request').resolves(replyMock); + awsLogs.serverless.service.service = 'new-service'; + awsLogs.options = { + stage: 'dev', + region: 'us-east-1', + function: 'first', + logGroupName: awsLogs.provider.naming.getLogGroupName('new-service-dev-first'), + }; + + return awsLogs.showLogs(logStreamNamesMock) + .then(() => { + expect(filterLogEventsStub.calledOnce).to.be.equal(true); + expect(filterLogEventsStub.calledWithExactly( + 'CloudWatchLogs', + 'filterLogEvents', + { + logGroupName: awsLogs.provider.naming.getLogGroupName('new-service-dev-first'), + interleaved: true, + logStreamNames: logStreamNamesMock, + startTime: fakeTime - (10 * 60 * 1000), // fakeTime - 10 minutes + } + )).to.be.equal(true); + + awsLogs.provider.request.restore(); + }); + }); + + it('should call filterLogEvents API which starts 10 seconds in the past if tail given', () => { + const replyMock = { + events: [ + { + logStreamName: '2016/07/28/[$LATEST]83f5206ab2a8488290349b9c1fbfe2ba', + timestamp: 1469687512311, + message: 'test', + }, + { + logStreamName: '2016/07/28/[$LATEST]83f5206ab2a8488290349b9c1fbfe2ba', + timestamp: 1469687512311, + message: 'test', + }, + ], + }; + const logStreamNamesMock = [ + '2016/07/28/[$LATEST]83f5206ab2a8488290349b9c1fbfe2ba', + '2016/07/28/[$LATEST]83f5206ab2a8488290349b9c1fbfe2ba', + ]; + const filterLogEventsStub = sinon.stub(awsLogs.provider, 'request').resolves(replyMock); + awsLogs.serverless.service.service = 'new-service'; + awsLogs.options = { + stage: 'dev', + region: 'us-east-1', + function: 'first', + logGroupName: awsLogs.provider.naming.getLogGroupName('new-service-dev-first'), + tail: true, + }; + + return awsLogs.showLogs(logStreamNamesMock) + .then(() => { + expect(filterLogEventsStub.calledOnce).to.be.equal(true); + expect(filterLogEventsStub.calledWithExactly( + 'CloudWatchLogs', + 'filterLogEvents', + { + logGroupName: awsLogs.provider.naming.getLogGroupName('new-service-dev-first'), + interleaved: true, + logStreamNames: logStreamNamesMock, + startTime: fakeTime - (10 * 1000), // fakeTime - 10 minutes + } + )).to.be.equal(true); + + awsLogs.provider.request.restore(); + }); + }); }); }); diff --git a/lib/plugins/aws/package/compile/events/alexaSkill/index.js b/lib/plugins/aws/package/compile/events/alexaSkill/index.js index ecc32cb4451..89c863bb4b2 100644 --- a/lib/plugins/aws/package/compile/events/alexaSkill/index.js +++ b/lib/plugins/aws/package/compile/events/alexaSkill/index.js @@ -15,10 +15,45 @@ class AwsCompileAlexaSkillEvents { compileAlexaSkillEvents() { this.serverless.service.getAllFunctions().forEach((functionName) => { const functionObj = this.serverless.service.getFunction(functionName); + let alexaSkillNumberInFunction = 0; if (functionObj.events) { functionObj.events.forEach(event => { - if (event === 'alexaSkill') { + if (event === 'alexaSkill' || event.alexaSkill) { + let enabled = true; + let appId; + if (event === 'alexaSkill') { + const warningMessage = [ + 'Warning! You are using an old syntax for alexaSkill which doesn\'t', + ' restrict the invocation solely to your skill.', + ' Please refer to the documentation for additional information.', + ].join(''); + this.serverless.cli.log(warningMessage); + } else if (_.isString(event.alexaSkill)) { + appId = event.alexaSkill; + } else if (_.isPlainObject(event.alexaSkill)) { + if (!_.isString(event.alexaSkill.appId)) { + const errorMessage = [ + `Missing "appId" property for alexaSkill event in function ${functionName}`, + ' The correct syntax is: appId: amzn1.ask.skill.xx-xx-xx-xx-xx', + ' OR an object with "appId" property.', + ' Please check the docs for more info.', + ].join(''); + throw new this.serverless.classes.Error(errorMessage); + } + appId = event.alexaSkill.appId; + // Parameter `enabled` is optional, hence the explicit non-equal check for false. + enabled = event.alexaSkill.enabled !== false; + } else { + const errorMessage = [ + `Alexa Skill event of function "${functionName}" is not an object or string.`, + ' The correct syntax is: alexaSkill.', + ' Please check the docs for more info.', + ].join(''); + throw new this.serverless.classes.Error(errorMessage); + } + alexaSkillNumberInFunction++; + const lambdaLogicalId = this.provider.naming .getLambdaLogicalId(functionName); @@ -31,13 +66,18 @@ class AwsCompileAlexaSkillEvents { 'Arn', ], }, - Action: 'lambda:InvokeFunction', + Action: enabled ? 'lambda:InvokeFunction' : 'lambda:DisableInvokeFunction', Principal: 'alexa-appkit.amazon.com', }, }; + if (appId) { + permissionTemplate.Properties.EventSourceToken = appId.replace(/\\n|\\r/g, ''); + } + const lambdaPermissionLogicalId = this.provider.naming - .getLambdaAlexaSkillPermissionLogicalId(functionName); + .getLambdaAlexaSkillPermissionLogicalId(functionName, + alexaSkillNumberInFunction); const permissionCloudForamtionResource = { [lambdaPermissionLogicalId]: permissionTemplate, @@ -45,13 +85,6 @@ class AwsCompileAlexaSkillEvents { _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, permissionCloudForamtionResource); - } else if (event.alexaSkill) { - const errorMessage = [ - `Alexa Skill event of function "${functionName}" is not an object or string.`, - ' The correct syntax is: alexaSkill.', - ' Please check the docs for more info.', - ].join(''); - throw new this.serverless.classes.Error(errorMessage); } }); } diff --git a/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js b/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js index 338a9fd0838..cb9090ae6cc 100644 --- a/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js +++ b/lib/plugins/aws/package/compile/events/alexaSkill/index.test.js @@ -1,5 +1,7 @@ 'use strict'; +/* eslint-disable no-unused-expressions */ + const expect = require('chai').expect; const AwsProvider = require('../../../../provider/awsProvider'); const AwsCompileAlexaSkillEvents = require('./index'); @@ -8,11 +10,19 @@ const Serverless = require('../../../../../../Serverless'); describe('AwsCompileAlexaSkillEvents', () => { let serverless; let awsCompileAlexaSkillEvents; + let consolePrinted; beforeEach(() => { serverless = new Serverless(); serverless.service.provider.compiledCloudFormationTemplate = { Resources: {} }; serverless.setProvider('aws', new AwsProvider(serverless)); + consolePrinted = ''; + serverless.cli = { + // serverless.cli isn't available in tests, so we will mimic it. + log: txt => { + consolePrinted += `${txt}\r\n`; + }, + }; awsCompileAlexaSkillEvents = new AwsCompileAlexaSkillEvents(serverless); }); @@ -25,7 +35,42 @@ describe('AwsCompileAlexaSkillEvents', () => { }); describe('#compileAlexaSkillEvents()', () => { - it('should throw an error if alexaSkill event is not an string', () => { + it('should show a warning if alexaSkill appId is not specified', () => { + awsCompileAlexaSkillEvents.serverless.service.functions = { + first: { + events: [ + 'alexaSkill', + ], + }, + }; + + awsCompileAlexaSkillEvents.compileAlexaSkillEvents(); + + expect(consolePrinted).to.contain.string('old syntax for alexaSkill'); + + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Type + ).to.equal('AWS::Lambda::Permission'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName + ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.Action + ).to.equal('lambda:InvokeFunction'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.Principal + ).to.equal('alexa-appkit.amazon.com'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken + ).to.be.undefined; + }); + + it('should throw an error if alexaSkill event is not a string or an object', () => { awsCompileAlexaSkillEvents.serverless.service.functions = { first: { events: [ @@ -39,11 +84,36 @@ describe('AwsCompileAlexaSkillEvents', () => { expect(() => awsCompileAlexaSkillEvents.compileAlexaSkillEvents()).to.throw(Error); }); - it('should create corresponding resources when a alexaSkill event is provided', () => { + it('should throw an error if alexaSkill event appId is not a string', () => { awsCompileAlexaSkillEvents.serverless.service.functions = { first: { events: [ - 'alexaSkill', + { + alexaSkill: { + appId: 42, + }, + }, + ], + }, + }; + + expect(() => awsCompileAlexaSkillEvents.compileAlexaSkillEvents()).to.throw(Error); + }); + + it('should create corresponding resources when multiple alexaSkill events are provided', () => { + const skillId1 = 'amzn1.ask.skill.xx-xx-xx-xx'; + const skillId2 = 'amzn1.ask.skill.yy-yy-yy-yy'; + awsCompileAlexaSkillEvents.serverless.service.functions = { + first: { + events: [ + { + alexaSkill: skillId1, + }, + { + alexaSkill: { + appId: skillId2, + }, + }, ], }, }; @@ -52,20 +122,84 @@ describe('AwsCompileAlexaSkillEvents', () => { expect(awsCompileAlexaSkillEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill.Type + .FirstLambdaPermissionAlexaSkill1.Type ).to.equal('AWS::Lambda::Permission'); expect(awsCompileAlexaSkillEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill.Properties.FunctionName + .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); expect(awsCompileAlexaSkillEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill.Properties.Action + .FirstLambdaPermissionAlexaSkill1.Properties.Action ).to.equal('lambda:InvokeFunction'); expect(awsCompileAlexaSkillEvents.serverless.service .provider.compiledCloudFormationTemplate.Resources - .FirstLambdaPermissionAlexaSkill.Properties.Principal + .FirstLambdaPermissionAlexaSkill1.Properties.Principal ).to.equal('alexa-appkit.amazon.com'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken + ).to.equal(skillId1); + + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill2.Type + ).to.equal('AWS::Lambda::Permission'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill2.Properties.FunctionName + ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill2.Properties.Action + ).to.equal('lambda:InvokeFunction'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill2.Properties.Principal + ).to.equal('alexa-appkit.amazon.com'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill2.Properties.EventSourceToken + ).to.equal(skillId2); + }); + + it('should create corresponding resources when a disabled alexaSkill event is provided', () => { + const skillId1 = 'amzn1.ask.skill.xx-xx-xx-xx'; + awsCompileAlexaSkillEvents.serverless.service.functions = { + first: { + events: [ + { + alexaSkill: { + appId: skillId1, + enabled: false, + }, + }, + ], + }, + }; + + awsCompileAlexaSkillEvents.compileAlexaSkillEvents(); + + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Type + ).to.equal('AWS::Lambda::Permission'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.FunctionName + ).to.deep.equal({ 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.Action + ).to.equal('lambda:DisableInvokeFunction'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.Principal + ).to.equal('alexa-appkit.amazon.com'); + expect(awsCompileAlexaSkillEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionAlexaSkill1.Properties.EventSourceToken + ).to.equal(skillId1); }); it('should not create corresponding resources when alexaSkill event is not given', () => { @@ -82,5 +216,22 @@ describe('AwsCompileAlexaSkillEvents', () => { .compiledCloudFormationTemplate.Resources ).to.deep.equal({}); }); + + it('should not not throw error when other events are present', () => { + awsCompileAlexaSkillEvents.serverless.service.functions = { + first: { + events: [ + { + http: { + method: 'get', + path: '/', + }, + }, + ], + }, + }; + + expect(() => awsCompileAlexaSkillEvents.compileAlexaSkillEvents()).to.not.throw(); + }); }); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.js index 5d15ad887ef..118b60325d5 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.js @@ -2,6 +2,7 @@ const BbPromise = require('bluebird'); const _ = require('lodash'); +const awsArnRegExs = require('../../../../../utils/arnRegularExpressions'); module.exports = { compileAuthorizers() { @@ -23,20 +24,25 @@ module.exports = { const authorizerLogicalId = this.provider.naming.getAuthorizerLogicalId(authorizer.name); - if (typeof authorizer.arn === 'string' && authorizer.arn.match(/^arn:aws:cognito-idp/)) { + if (typeof authorizer.arn === 'string' + && awsArnRegExs.cognitoIdpArnExpr.test(authorizer.arn)) { authorizerProperties.Type = 'COGNITO_USER_POOLS'; authorizerProperties.ProviderARNs = [authorizer.arn]; } else { authorizerProperties.AuthorizerUri = - { 'Fn::Join': ['', - [ - 'arn:aws:apigateway:', - { Ref: 'AWS::Region' }, - ':lambda:path/2015-03-31/functions/', - authorizer.arn, - '/invocations', + { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + authorizer.arn, + '/invocations', + ], ], - ] }; + }; authorizerProperties.Type = authorizer.type ? authorizer.type.toUpperCase() : 'TOKEN'; } diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.test.js index ee84ad8a24b..5ec1dc89366 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/authorizers.test.js @@ -39,15 +39,18 @@ describe('#compileAuthorizers()', () => { expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer'); expect(resource.Properties.AuthorizerResultTtlInSeconds).to.equal(300); - expect(resource.Properties.AuthorizerUri).to.deep.equal({ 'Fn::Join': ['', - [ - 'arn:aws:apigateway:', - { Ref: 'AWS::Region' }, - ':lambda:path/2015-03-31/functions/', - { 'Fn::GetAtt': ['SomeLambdaFunction', 'Arn'] }, - '/invocations', + expect(resource.Properties.AuthorizerUri).to.deep.equal({ + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + { 'Fn::GetAtt': ['SomeLambdaFunction', 'Arn'] }, + '/invocations', + ], ], - ], }); expect(resource.Properties.IdentitySource).to.equal('method.request.header.Authorization'); expect(resource.Properties.IdentityValidationExpression).to.equal(undefined); @@ -77,15 +80,18 @@ describe('#compileAuthorizers()', () => { .compiledCloudFormationTemplate.Resources.AuthorizerApiGatewayAuthorizer; expect(resource.Type).to.equal('AWS::ApiGateway::Authorizer'); - expect(resource.Properties.AuthorizerUri).to.deep.equal({ 'Fn::Join': ['', - [ - 'arn:aws:apigateway:', - { Ref: 'AWS::Region' }, - ':lambda:path/2015-03-31/functions/', - 'foo', - '/invocations', + expect(resource.Properties.AuthorizerUri).to.deep.equal({ + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', + { Ref: 'AWS::Region' }, + ':lambda:path/2015-03-31/functions/', + 'foo', + '/invocations', + ], ], - ], }); expect(resource.Properties.AuthorizerResultTtlInSeconds).to.equal(500); expect(resource.Properties.IdentitySource).to.equal('method.request.header.Custom'); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js index 167d113fd0a..99797e4cfa8 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.js @@ -25,6 +25,16 @@ module.exports = { 'Access-Control-Allow-Credentials': `'${config.allowCredentials}'`, }; + // Enable CORS Max Age usage if set + if (_.has(config, 'maxAge')) { + if (_.isInteger(config.maxAge) && config.maxAge > 0) { + preflightHeaders['Access-Control-Max-Age'] = `'${config.maxAge}'`; + } else { + const errorMessage = 'maxAge should be an integer over 0'; + throw new this.serverless.classes.Error(errorMessage); + } + } + if (_.includes(config.methods, 'ANY')) { preflightHeaders['Access-Control-Allow-Methods'] = preflightHeaders['Access-Control-Allow-Methods'] @@ -44,6 +54,7 @@ module.exports = { RequestTemplates: { 'application/json': '{statusCode:200}', }, + ContentHandling: 'CONVERT_TO_TEXT', IntegrationResponses: this.generateCorsIntegrationResponses(preflightHeaders), }, ResourceId: resourceRef, diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js index db2d06c876b..64f65624b4c 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/cors.test.js @@ -67,18 +67,21 @@ describe('#compileCors()', () => { headers: ['*'], methods: ['OPTIONS', 'PUT'], allowCredentials: false, + maxAge: 86400, }, 'users/create': { origins: ['*', 'http://example.com'], headers: ['*'], methods: ['OPTIONS', 'POST'], allowCredentials: true, + maxAge: 86400, }, 'users/delete': { origins: ['*'], headers: ['CustomHeaderA', 'CustomHeaderB'], methods: ['OPTIONS', 'DELETE'], allowCredentials: false, + maxAge: 86400, }, 'users/any': { origins: ['http://example.com'], @@ -117,6 +120,13 @@ describe('#compileCors()', () => { .ResponseParameters['method.response.header.Access-Control-Allow-Credentials'] ).to.equal('\'true\''); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.ApiGatewayMethodUsersCreateOptions + .Properties.Integration.IntegrationResponses[0] + .ResponseParameters['method.response.header.Access-Control-Max-Age'] + ).to.equal('\'86400\''); + // users/update expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate @@ -139,6 +149,13 @@ describe('#compileCors()', () => { .ResponseParameters['method.response.header.Access-Control-Allow-Credentials'] ).to.equal('\'false\''); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.ApiGatewayMethodUsersUpdateOptions + .Properties.Integration.IntegrationResponses[0] + .ResponseParameters['method.response.header.Access-Control-Max-Age'] + ).to.equal('\'86400\''); + // users/delete expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate @@ -168,6 +185,13 @@ describe('#compileCors()', () => { .ResponseParameters['method.response.header.Access-Control-Allow-Credentials'] ).to.equal('\'false\''); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.ApiGatewayMethodUsersDeleteOptions + .Properties.Integration.IntegrationResponses[0] + .ResponseParameters['method.response.header.Access-Control-Max-Age'] + ).to.equal('\'86400\''); + // users/any expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate @@ -198,4 +222,34 @@ describe('#compileCors()', () => { ).to.equal('\'false\''); }); }); + + it('should throw error if maxAge is not an integer greater than 0', () => { + awsCompileApigEvents.validated.corsPreflight = { + 'users/update': { + origin: 'http://example.com', + headers: ['*'], + methods: ['OPTIONS', 'PUT'], + allowCredentials: false, + maxAge: -1, + }, + }; + + expect(() => awsCompileApigEvents.compileCors()) + .to.throw(Error, 'maxAge should be an integer over 0'); + }); + + it('should throw error if maxAge is not an integer', () => { + awsCompileApigEvents.validated.corsPreflight = { + 'users/update': { + origin: 'http://example.com', + headers: ['*'], + methods: ['OPTIONS', 'PUT'], + allowCredentials: false, + maxAge: 'five', + }, + }; + + expect(() => awsCompileApigEvents.compileCors()) + .to.throw(Error, 'maxAge should be an integer over 0'); + }); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js index dd1d804a9a0..9eb923ba596 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.js @@ -28,11 +28,9 @@ module.exports = { [ 'https://', this.provider.getApiGatewayRestApiId(), - `.execute-api.${ - this.provider.getRegion() - }.amazonaws.com/${ - this.provider.getStage() - }`, + `.execute-api.${this.provider.getRegion()}.`, + { Ref: 'AWS::URLSuffix' }, + `/${this.provider.getStage()}`, ], ], }, diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js index 3013f1e1144..98be9ee278e 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/deployment.test.js @@ -63,7 +63,9 @@ describe('#compileDeployment()', () => { [ 'https://', { Ref: awsCompileApigEvents.apiGatewayRestApiLogicalId }, - '.execute-api.us-east-1.amazonaws.com/dev', + '.execute-api.us-east-1.', + { Ref: 'AWS::URLSuffix' }, + '/dev', ], ], }, diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/authorization.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/authorization.js index 62e0acfa61d..c9314397b7a 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/authorization.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/authorization.js @@ -1,6 +1,7 @@ 'use strict'; const _ = require('lodash'); +const awsArnRegExs = require('../../../../../../utils/arnRegularExpressions'); module.exports = { getMethodAuthorization(http) { @@ -13,12 +14,22 @@ module.exports = { } if (http.authorizer) { + if (http.authorizer.type && http.authorizer.authorizerId) { + return { + Properties: { + AuthorizationType: http.authorizer.type, + AuthorizerId: http.authorizer.authorizerId, + }, + }; + } + const authorizerLogicalId = this.provider.naming .getAuthorizerLogicalId(http.authorizer.name); let authorizationType; const authorizerArn = http.authorizer.arn; - if (typeof authorizerArn === 'string' && authorizerArn.match(/^arn:aws:cognito-idp/)) { + if (typeof authorizerArn === 'string' + && awsArnRegExs.cognitoIdpArnExpr.test(authorizerArn)) { authorizationType = 'COGNITO_USER_POOLS'; } else { authorizationType = 'CUSTOM'; diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js index 2f47e5c50e4..5b985331a9c 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/index.test.js @@ -399,6 +399,32 @@ describe('#compileMethods()', () => { }); }); + it('should set custom authorizer config with authorizeId', () => { + awsCompileApigEvents.validated.events = [ + { + functionName: 'First', + http: { + path: 'users/create', + method: 'post', + authorizer: { + type: 'COGNITO_USER_POOLS', + authorizerId: 'gy7lyj', + }, + }, + }, + ]; + return awsCompileApigEvents.compileMethods().then(() => { + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizationType + ).to.equal('COGNITO_USER_POOLS'); + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.ApiGatewayMethodUsersCreatePost.Properties.AuthorizerId + ).to.equal('gy7lyj'); + }); + }); + it('should set authorizer config if given as ARN string', () => { awsCompileApigEvents.validated.events = [ { @@ -407,6 +433,7 @@ describe('#compileMethods()', () => { authorizer: { name: 'Authorizer', }, + integration: 'AWS', path: 'users/create', method: 'post', }, @@ -456,7 +483,7 @@ describe('#compileMethods()', () => { awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.ApiGatewayMethodUsersCreatePost.Properties .Integration.RequestTemplates['application/json'] - ).to.not.match(/undefined/); + ).to.not.match(/undefined/); }); }); @@ -479,8 +506,8 @@ describe('#compileMethods()', () => { return awsCompileApigEvents.compileMethods().then(() => { const jsonRequestTemplatesString = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.ApiGatewayMethodUsersCreatePost.Properties - .Integration.RequestTemplates['application/json']; + .compiledCloudFormationTemplate.Resources.ApiGatewayMethodUsersCreatePost.Properties + .Integration.RequestTemplates['application/json']; const cognitoPoolClaimsRegex = /"cognitoPoolClaims"\s*:\s*(\{[^}]*\})/; const cognitoPoolClaimsString = jsonRequestTemplatesString.match(cognitoPoolClaimsRegex)[1]; const cognitoPoolClaims = JSON.parse(cognitoPoolClaimsString); @@ -507,8 +534,8 @@ describe('#compileMethods()', () => { return awsCompileApigEvents.compileMethods().then(() => { const jsonRequestTemplatesString = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.ApiGatewayMethodUsersCreatePost.Properties - .Integration.RequestTemplates['application/json']; + .compiledCloudFormationTemplate.Resources.ApiGatewayMethodUsersCreatePost.Properties + .Integration.RequestTemplates['application/json']; const cognitoPoolClaimsRegex = /"cognitoPoolClaims"\s*:\s*(\{[^}]*\})/; const cognitoPoolClaimsString = jsonRequestTemplatesString.match(cognitoPoolClaimsRegex)[1]; const cognitoPoolClaims = JSON.parse(cognitoPoolClaimsString); @@ -536,8 +563,8 @@ describe('#compileMethods()', () => { return awsCompileApigEvents.compileMethods().then(() => { const jsonRequestTemplatesString = awsCompileApigEvents.serverless.service.provider - .compiledCloudFormationTemplate.Resources.ApiGatewayMethodUsersCreatePost.Properties - .Integration.RequestTemplates['application/json']; + .compiledCloudFormationTemplate.Resources.ApiGatewayMethodUsersCreatePost.Properties + .Integration.RequestTemplates['application/json']; const cognitoPoolClaimsRegex = /"cognitoPoolClaims"\s*:\s*(\{[^}]*\})/; const cognitoPoolClaimsString = jsonRequestTemplatesString.match(cognitoPoolClaimsRegex)[1]; const cognitoPoolClaims = JSON.parse(cognitoPoolClaimsString); @@ -568,7 +595,7 @@ describe('#compileMethods()', () => { awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.ApiGatewayMethodUsersCreatePost.Properties .Integration.RequestTemplates['application/json'] - ).to.not.match(/extraCognitoPoolClaims/); + ).to.not.match(/extraCognitoPoolClaims/); }); }); @@ -665,7 +692,9 @@ describe('#compileMethods()', () => { ).to.deep.equal({ 'Fn::Join': [ '', [ - 'arn:aws:apigateway:', { Ref: 'AWS::Region' }, + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', { Ref: 'AWS::Region' }, ':lambda:path/2015-03-31/functions/', { 'Fn::GetAtt': ['FirstLambdaFunction', 'Arn'] }, '/invocations', ], @@ -677,7 +706,9 @@ describe('#compileMethods()', () => { ).to.deep.equal({ 'Fn::Join': [ '', [ - 'arn:aws:apigateway:', { Ref: 'AWS::Region' }, + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', { Ref: 'AWS::Region' }, ':lambda:path/2015-03-31/functions/', { 'Fn::GetAtt': ['SecondLambdaFunction', 'Arn'] }, '/invocations', ], diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js index 1a900bd9d9b..d9e76d3fded 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/method/integration.js @@ -26,6 +26,9 @@ const DEFAULT_COMMON_TEMPLATE = ` "sub": "$context.authorizer.claims.sub" }, + #set( $map = $context.authorizer ) + "enhancedAuthContext": $loop, + #set( $map = $input.params().header ) "headers": $loop, @@ -62,7 +65,9 @@ module.exports = { Uri: { 'Fn::Join': ['', [ - 'arn:aws:apigateway:', + 'arn:', + { Ref: 'AWS::Partition' }, + ':apigateway:', { Ref: 'AWS::Region' }, ':lambda:path/2015-03-31/functions/', { 'Fn::GetAtt': [lambdaLogicalId, 'Arn'] }, @@ -180,7 +185,6 @@ module.exports = { return !_.isEmpty(integrationRequestTemplates) ? integrationRequestTemplates : undefined; }, - getIntegrationRequestParameters(http) { const parameters = {}; if (http.request && http.request.parameters) { diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.js index 2c09c0c7ba4..61641b53505 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.js @@ -2,6 +2,7 @@ const _ = require('lodash'); const BbPromise = require('bluebird'); +const awsArnRegExs = require('../../../../../utils/arnRegularExpressions'); module.exports = { @@ -18,29 +19,34 @@ module.exports = { 'Fn::GetAtt': [singlePermissionMapping.lambdaLogicalId, 'Arn'], }, Action: 'lambda:InvokeFunction', - Principal: 'apigateway.amazonaws.com', - SourceArn: { 'Fn::Join': ['', - [ - 'arn:aws:execute-api:', - { Ref: 'AWS::Region' }, - ':', - { Ref: 'AWS::AccountId' }, - ':', - this.provider.getApiGatewayRestApiId(), - '/*/*', + Principal: { 'Fn::Join': ['', ['apigateway.', { Ref: 'AWS::URLSuffix' }]] }, + SourceArn: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':execute-api:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':', + this.provider.getApiGatewayRestApiId(), + '/*/*', + ], ], - ] }, + }, }, }, }); if (singlePermissionMapping.event.http.authorizer && - singlePermissionMapping.event.http.authorizer.arn) { + singlePermissionMapping.event.http.authorizer.arn) { const authorizer = singlePermissionMapping.event.http.authorizer; const authorizerPermissionLogicalId = this.provider.naming .getLambdaApiGatewayPermissionLogicalId(authorizer.name); - if (typeof authorizer.arn === 'string' && authorizer.arn.match(/^arn:aws:cognito-idp/)) { + if (typeof authorizer.arn === 'string' + && awsArnRegExs.cognitoIdpArnExpr.test(authorizer.arn)) { return; } @@ -50,7 +56,7 @@ module.exports = { Properties: { FunctionName: authorizer.arn, Action: 'lambda:InvokeFunction', - Principal: 'apigateway.amazonaws.com', + Principal: { 'Fn::Join': ['', ['apigateway.', { Ref: 'AWS::URLSuffix' }]] }, }, }, }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.test.js index 3602cab8f5f..a0ff32a6749 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/permissions.test.js @@ -48,17 +48,76 @@ describe('#awsCompilePermissions()', () => { .Resources.FirstLambdaPermissionApiGateway .Properties.FunctionName['Fn::GetAtt'][0]).to.equal('FirstLambdaFunction'); - const deepObj = { 'Fn::Join': ['', - [ - 'arn:aws:execute-api:', - { Ref: 'AWS::Region' }, - ':', - { Ref: 'AWS::AccountId' }, - ':', - { Ref: 'ApiGatewayRestApi' }, - '/*/*', + const deepObj = { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':execute-api:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':', + { Ref: 'ApiGatewayRestApi' }, + '/*/*', + ], ], - ] }; + }; + + expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionApiGateway + .Properties.SourceArn).to.deep.equal(deepObj); + }); + }); + + it('should create limited permission resource scope to REST API with restApiId provided', () => { + awsCompileApigEvents.serverless.service.provider.apiGateway = { + restApiId: 'xxxxx', + }; + awsCompileApigEvents.validated.events = [ + { + functionName: 'First', + http: { + path: 'foo/bar', + method: 'post', + }, + }, + ]; + awsCompileApigEvents.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi'; + awsCompileApigEvents.permissionMapping = [ + { + lambdaLogicalId: 'FirstLambdaFunction', + resourceName: 'FooBar', + event: { + http: { + path: 'foo/bar', + method: 'post', + }, + functionName: 'First', + }, + }, + ]; + + return awsCompileApigEvents.compilePermissions().then(() => { + expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstLambdaPermissionApiGateway + .Properties.FunctionName['Fn::GetAtt'][0]).to.equal('FirstLambdaFunction'); + + const deepObj = { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':execute-api:', + { Ref: 'AWS::Region' }, + ':', + { Ref: 'AWS::AccountId' }, + ':', + 'xxxxx', + '/*/*', + ], + ], + }; expect(awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources.FirstLambdaPermissionApiGateway diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.js index fc3dd0af46f..d0cfb52a85b 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.js @@ -15,7 +15,7 @@ module.exports = { let endpointType = 'EDGE'; if (this.serverless.service.provider.endpointType) { - const validEndpointTypes = ['REGIONAL', 'EDGE']; + const validEndpointTypes = ['REGIONAL', 'EDGE', 'PRIVATE']; endpointType = this.serverless.service.provider.endpointType; if (typeof endpointType !== 'string') { @@ -23,8 +23,8 @@ module.exports = { } - if (!validEndpointTypes.includes(endpointType.toUpperCase())) { - const message = 'endpointType must be one of "REGIONAL" or "EDGE". ' + + if (!_.includes(validEndpointTypes, endpointType.toUpperCase())) { + const message = 'endpointType must be one of "REGIONAL" or "EDGE" or "PRIVATE". ' + `You provided ${endpointType}.`; throw new this.serverless.classes.Error(message); } @@ -43,6 +43,17 @@ module.exports = { }, }); + if (!_.isEmpty(this.serverless.service.provider.resourcePolicy)) { + const policy = { + Version: '2012-10-17', + Statement: this.serverless.service.provider.resourcePolicy, + }; + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate + .Resources[this.apiGatewayRestApiLogicalId].Properties, { + Policy: policy, + }); + } + return BbPromise.resolve(); }, }; diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js index eb3aa7e8a9b..4aa68dd8c98 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/restApi.test.js @@ -16,8 +16,36 @@ describe('#compileRestApi()', () => { Properties: { Name: 'dev-new-service', EndpointConfiguration: { - Types: [ - 'EDGE', + Types: ['EDGE'], + }, + }, + }, + }, + }; + + const serviceResourcesAwsResourcesObjectWithResourcePolicyMock = { + Resources: { + ApiGatewayRestApi: { + Type: 'AWS::ApiGateway::RestApi', + Properties: { + Name: 'dev-new-service', + EndpointConfiguration: { + Types: ['EDGE'], + }, + Policy: { + Version: '2012-10-17', + Statement: [ + { + Effect: 'Allow', + Principal: '*', + Action: 'execute-api:Invoke', + Resource: ['execute-api:/*/*/*'], + Condition: { + IpAddress: { + 'aws:SourceIp': ['123.123.123.123'], + }, + }, + }, ], }, }, @@ -49,40 +77,66 @@ describe('#compileRestApi()', () => { }; }); - it('should create a REST API resource', () => awsCompileApigEvents - .compileRestApi().then(() => { - expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources - ).to.deep.equal( - serviceResourcesAwsResourcesObjectMock.Resources - ); - }) - ); + it('should create a REST API resource', () => + awsCompileApigEvents.compileRestApi().then(() => { + expect(awsCompileApigEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources).to.deep.equal( + serviceResourcesAwsResourcesObjectMock.Resources + ); + })); - it('should ignore REST API resource creation if there is predefined restApi config', - () => { - awsCompileApigEvents.serverless.service.provider.apiGateway = { - restApiId: '6fyzt1pfpk', - restApiRootResourceId: 'z5d4qh4oqi', - }; - return awsCompileApigEvents - .compileRestApi().then(() => { - expect( - awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate - .Resources - ).to.deep.equal({}); - }); - } - ); + it('should create a REST API resource with resource policy', () => { + awsCompileApigEvents.serverless.service.provider.resourcePolicy = [ + { + Effect: 'Allow', + Principal: '*', + Action: 'execute-api:Invoke', + Resource: ['execute-api:/*/*/*'], + Condition: { + IpAddress: { + 'aws:SourceIp': ['123.123.123.123'], + }, + }, + }, + ]; + return awsCompileApigEvents.compileRestApi().then(() => { + expect(awsCompileApigEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources).to.deep.equal( + serviceResourcesAwsResourcesObjectWithResourcePolicyMock.Resources + ); + }); + }); + + it('should ignore REST API resource creation if there is predefined restApi config', () => { + awsCompileApigEvents.serverless.service.provider.apiGateway = { + restApiId: '6fyzt1pfpk', + restApiRootResourceId: 'z5d4qh4oqi', + }; + return awsCompileApigEvents.compileRestApi().then(() => { + expect(awsCompileApigEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources).to.deep.equal( + {} + ); + }); + }); it('throw error if endpointType property is not a string', () => { awsCompileApigEvents.serverless.service.provider.endpointType = ['EDGE']; expect(() => awsCompileApigEvents.compileRestApi()).to.throw(Error); }); + it('should compile if endpointType property is REGIONAL', () => { + awsCompileApigEvents.serverless.service.provider.endpointType = 'REGIONAL'; + expect(() => awsCompileApigEvents.compileRestApi()).to.not.throw(Error); + }); + + it('should compile if endpointType property is PRIVATE', () => { + awsCompileApigEvents.serverless.service.provider.endpointType = 'PRIVATE'; + expect(() => awsCompileApigEvents.compileRestApi()).to.not.throw(Error); + }); + it('throw error if endpointType property is not EDGE or REGIONAL', () => { awsCompileApigEvents.serverless.service.provider.endpointType = 'Testing'; - expect(() => awsCompileApigEvents.compileRestApi()).to.throw(Error); + expect(() => awsCompileApigEvents.compileRestApi()).to.throw('endpointType must be one of'); }); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js index 7bc199bb34e..4206370ddcf 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.js @@ -5,7 +5,7 @@ const BbPromise = require('bluebird'); module.exports = { compileUsagePlan() { - if (this.serverless.service.provider.apiKeys) { + if (this.serverless.service.provider.usagePlan || this.serverless.service.provider.apiKeys) { this.apiGatewayUsagePlanLogicalId = this.provider.naming.getUsagePlanLogicalId(); _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, { [this.apiGatewayUsagePlanLogicalId]: { @@ -14,9 +14,7 @@ module.exports = { Properties: { ApiStages: [ { - ApiId: { - Ref: this.apiGatewayRestApiLogicalId, - }, + ApiId: this.provider.getApiGatewayRestApiId(), Stage: this.provider.getStage(), }, ], diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js index 370a8c59471..168d83f5424 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/usagePlan.test.js @@ -17,21 +17,6 @@ describe('#compileUsagePlan()', () => { serverless = new Serverless(); serverless.setProvider('aws', new AwsProvider(serverless, options)); serverless.service.service = 'first-service'; - serverless.service.provider = { - name: 'aws', - apiKeys: ['1234567890'], - usagePlan: { - quota: { - limit: 500, - offset: 10, - period: 'MONTH', - }, - throttle: { - burstLimit: 200, - rateLimit: 100, - }, - }, - }; serverless.service.provider.compiledCloudFormationTemplate = { Resources: {}, Outputs: {}, @@ -41,8 +26,67 @@ describe('#compileUsagePlan()', () => { awsCompileApigEvents.apiGatewayRestApiLogicalId = 'ApiGatewayRestApi'; }); - it('should compile usage plan resource', () => - awsCompileApigEvents.compileUsagePlan().then(() => { + it('should compile default usage plan resource', () => { + serverless.service.provider.apiKeys = ['1234567890']; + return awsCompileApigEvents.compileUsagePlan().then(() => { + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[ + awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() + ].Type + ).to.equal('AWS::ApiGateway::UsagePlan'); + + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[ + awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() + ].DependsOn + ).to.equal('ApiGatewayDeploymentTest'); + + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[ + awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() + ].Properties.ApiStages[0].ApiId.Ref + ).to.equal('ApiGatewayRestApi'); + + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[ + awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() + ].Properties.ApiStages[0].Stage + ).to.equal('dev'); + + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[ + awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() + ].Properties.Description + ).to.equal('Usage plan for first-service dev stage'); + + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[ + awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() + ].Properties.UsagePlanName + ).to.equal('first-service-dev'); + }); + }); + + it('should compile custom usage plan resource', () => { + serverless.service.provider.usagePlan = { + quota: { + limit: 500, + offset: 10, + period: 'MONTH', + }, + throttle: { + burstLimit: 200, + rateLimit: 100, + }, + }; + + return awsCompileApigEvents.compileUsagePlan().then(() => { expect( awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate .Resources[ @@ -105,6 +149,22 @@ describe('#compileUsagePlan()', () => { awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() ].Properties.UsagePlanName ).to.equal('first-service-dev'); - }) - ); + }); + }); + + it('should compile custom usage plan resource with restApiId provided', () => { + serverless.service.provider.apiKeys = ['1234567890']; + awsCompileApigEvents.serverless.service.provider.apiGateway = { + restApiId: 'xxxxx', + }; + + return awsCompileApigEvents.compileUsagePlan().then(() => { + expect( + awsCompileApigEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources[ + awsCompileApigEvents.provider.naming.getUsagePlanLogicalId() + ].Properties.ApiStages[0].ApiId + ).to.equal('xxxxx'); + }); + }); }); diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js index bbafbb75163..f70b893184c 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.js @@ -1,6 +1,7 @@ 'use strict'; const _ = require('lodash'); +const awsArnRegExs = require('../../../../../utils/arnRegularExpressions'); const NOT_FOUND = -1; const DEFAULT_STATUS_CODES = { @@ -61,6 +62,11 @@ module.exports = { cors.origin = http.cors.origin || '*'; cors.allowCredentials = cors.allowCredentials || http.cors.allowCredentials; + // when merging, last one defined wins + if (_.has(http.cors, 'maxAge')) { + cors.maxAge = http.cors.maxAge; + } + corsPreflight[http.path] = cors; } @@ -78,6 +84,11 @@ module.exports = { http.request = this.getRequest(http); http.request.passThrough = this.getRequestPassThrough(http); http.response = this.getResponse(http); + if (http.integration === 'AWS' && _.isEmpty(http.response)) { + http.response = { + statusCodes: DEFAULT_STATUS_CODES, + }; + } } else if (http.integration === 'AWS_PROXY' || http.integration === 'HTTP_PROXY') { // show a warning when request / response config is used with AWS_PROXY (LAMBDA-PROXY) if (http.request) { @@ -204,6 +215,7 @@ module.exports = { let resultTtlInSeconds; let identityValidationExpression; let claims; + let authorizerId; if (typeof authorizer === 'string') { if (authorizer.toUpperCase() === 'AWS_IAM') { @@ -216,7 +228,10 @@ module.exports = { name = this.provider.naming.extractAuthorizerNameFromArn(arn); } } else if (typeof authorizer === 'object') { - if (authorizer.type && authorizer.type.toUpperCase() === 'AWS_IAM') { + if (authorizer.type && authorizer.authorizerId) { + type = authorizer.type; + authorizerId = authorizer.authorizerId; + } else if (authorizer.type && authorizer.type.toUpperCase() === 'AWS_IAM') { type = 'AWS_IAM'; } else if (authorizer.arn) { arn = authorizer.arn; @@ -258,7 +273,9 @@ module.exports = { const integration = this.getIntegration(http); if (integration === 'AWS_PROXY' - && typeof arn === 'string' && arn.match(/^arn:aws:cognito-idp/) && authorizer.claims) { + && typeof arn === 'string' + && awsArnRegExs.cognitoIdpArnExpr.test(arn) + && authorizer.claims) { const errorMessage = [ 'Cognito claims can only be filtered when using the lambda integration type', ]; @@ -269,6 +286,7 @@ module.exports = { type, name, arn, + authorizerId, resultTtlInSeconds, identitySource, identityValidationExpression, @@ -327,10 +345,15 @@ module.exports = { if (cors.methods.indexOf(http.method.toUpperCase()) === NOT_FOUND) { cors.methods.push(http.method.toUpperCase()); } + if (_.has(cors, 'maxAge')) { + if (!_.isInteger(cors.maxAge) || cors.maxAge < 1) { + const errorMessage = 'maxAge should be an integer over 0'; + throw new this.serverless.classes.Error(errorMessage); + } + } } else { cors.methods.push(http.method.toUpperCase()); } - return cors; }, diff --git a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js index a2132f048e0..6cbbfaac553 100644 --- a/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js +++ b/lib/plugins/aws/package/compile/events/apiGateway/lib/validate.test.js @@ -643,6 +643,7 @@ describe('#validate()', () => { headers: ['X-Foo-Bar'], origins: ['acme.com'], methods: ['POST', 'OPTIONS'], + maxAge: 86400, }, }, }, @@ -657,10 +658,11 @@ describe('#validate()', () => { methods: ['POST', 'OPTIONS'], origins: ['acme.com'], allowCredentials: false, + maxAge: 86400, }); }); - it('should merge all preflight origins, method, headers and allowCredentials for a path', () => { + it('should merge all preflight cors options for a path', () => { awsCompileApigEvents.serverless.service.functions = { first: { events: [ @@ -673,6 +675,7 @@ describe('#validate()', () => { 'http://example.com', ], allowCredentials: true, + maxAge: 10000, }, }, }, { @@ -683,6 +686,7 @@ describe('#validate()', () => { origins: [ 'http://example2.com', ], + maxAge: 86400, }, }, }, { @@ -717,12 +721,35 @@ describe('#validate()', () => { .to.deep.equal(['http://example2.com', 'http://example.com']); expect(validated.corsPreflight['users/{id}'].headers) .to.deep.equal(['TestHeader2', 'TestHeader']); + expect(validated.corsPreflight.users.maxAge) + .to.equal(86400); expect(validated.corsPreflight.users.allowCredentials) .to.equal(true); expect(validated.corsPreflight['users/{id}'].allowCredentials) .to.equal(false); }); + it('should throw an error if the maxAge is not a positive integer', () => { + awsCompileApigEvents.serverless.service.functions = { + first: { + events: [ + { + http: { + method: 'POST', + path: '/foo/bar', + cors: { + origin: '*', + maxAge: -1, + }, + }, + }, + ], + }, + }; + + expect(() => awsCompileApigEvents.validate()).to.throw(Error); + }); + it('should add default statusCode to custom statusCodes', () => { awsCompileApigEvents.serverless.service.functions = { first: { @@ -1728,4 +1755,54 @@ describe('#validate()', () => { expect(validated.events).to.be.an('Array').with.length(1); expect(validated.events[0].http.request.passThrough).to.equal(undefined); }); + + it('should set default statusCodes to response for lambda by default', () => { + awsCompileApigEvents.serverless.service.functions = { + first: { + events: [ + { + http: { + method: 'GET', + path: 'users/list', + integration: 'lambda', + integrationMethod: 'GET', + }, + }, + ], + }, + }; + + const validated = awsCompileApigEvents.validate(); + expect(validated.events).to.be.an('Array').with.length(1); + expect(validated.events[0].http.response.statusCodes).to.deep.equal({ + 200: { + pattern: '', + }, + 400: { + pattern: '[\\s\\S]*\\[400\\][\\s\\S]*', + }, + 401: { + pattern: '[\\s\\S]*\\[401\\][\\s\\S]*', + }, + 403: { + pattern: '[\\s\\S]*\\[403\\][\\s\\S]*', + }, + 404: { + pattern: '[\\s\\S]*\\[404\\][\\s\\S]*', + }, + 422: { + pattern: '[\\s\\S]*\\[422\\][\\s\\S]*', + }, + 500: { + pattern: + '[\\s\\S]*(Process\\s?exited\\s?before\\s?completing\\s?request|\\[500\\])[\\s\\S]*', + }, + 502: { + pattern: '[\\s\\S]*\\[502\\][\\s\\S]*', + }, + 504: { + pattern: '([\\s\\S]*\\[504\\][\\s\\S]*)|(^[Task timed out].*)', + }, + }); + }); }); diff --git a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js index d8ef7b2e998..6f7fd8acba2 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.js @@ -26,6 +26,7 @@ class AwsCompileCloudWatchEventEvents { let Input; let InputPath; let Description; + let Name; if (typeof event.cloudwatchEvent === 'object') { if (!event.cloudwatchEvent.event) { @@ -45,6 +46,7 @@ class AwsCompileCloudWatchEventEvents { Input = event.cloudwatchEvent.input; InputPath = event.cloudwatchEvent.inputPath; Description = event.cloudwatchEvent.description; + Name = event.cloudwatchEvent.name; if (Input && InputPath) { const errorMessage = [ @@ -87,6 +89,7 @@ class AwsCompileCloudWatchEventEvents { "EventPattern": ${EventPattern.replace(/\\n|\\r/g, '')}, "State": "${State}", ${Description ? `"Description": "${Description}",` : ''} + ${Name ? `"Name": "${Name}",` : ''} "Targets": [{ ${Input ? `"Input": "${Input.replace(/\\n|\\r/g, '')}",` : ''} ${InputPath ? `"InputPath": "${InputPath.replace(/\r?\n/g, '')}",` : ''} @@ -104,7 +107,7 @@ class AwsCompileCloudWatchEventEvents { "FunctionName": { "Fn::GetAtt": ["${ lambdaLogicalId}", "Arn"] }, "Action": "lambda:InvokeFunction", - "Principal": "events.amazonaws.com", + "Principal": { "Fn::Join": ["", ["events.", { "Ref": "AWS::URLSuffix" }]] }, "SourceArn": { "Fn::GetAtt": ["${cloudWatchLogicalId}", "Arn"] } } } diff --git a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js index 081bc08c7d3..0dd5123a2d7 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchEvent/index.test.js @@ -246,6 +246,34 @@ describe('awsCompileCloudWatchEventEvents', () => { ).to.equal('test description'); }); + it('should respect name variable', () => { + awsCompileCloudWatchEventEvents.serverless.service.functions = { + first: { + events: [ + { + cloudwatchEvent: { + event: { + source: ['aws.ec2'], + 'detail-type': ['EC2 Instance State-change Notification'], + detail: { state: ['pending'] }, + }, + enabled: false, + input: '{"key":"value"}', + name: 'test-event-name', + }, + }, + ], + }, + }; + + awsCompileCloudWatchEventEvents.compileCloudWatchEventEvents(); + + expect(awsCompileCloudWatchEventEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventsRuleCloudWatchEvent1 + .Properties.Name + ).to.equal('test-event-name'); + }); + it('should respect input variable as an object', () => { awsCompileCloudWatchEventEvents.serverless.service.functions = { first: { diff --git a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js index 697d9736d09..f356d0bb779 100644 --- a/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js +++ b/lib/plugins/aws/package/compile/events/cloudWatchLog/index.js @@ -76,7 +76,7 @@ class AwsCompileCloudWatchLogEvents { .getCloudWatchLogLogicalId(functionName, cloudWatchLogNumberInFunction); const lambdaPermissionLogicalId = this.provider.naming .getLambdaCloudWatchLogPermissionLogicalId(functionName, - cloudWatchLogNumberInFunction); + cloudWatchLogNumberInFunction); // unescape quotes once when the first quote is detected escaped const idxFirstSlash = FilterPattern.indexOf('\\'); @@ -98,33 +98,36 @@ class AwsCompileCloudWatchLogEvents { `; const permissionTemplate = ` - { - "Type": "AWS::Lambda::Permission", - "Properties": { - "FunctionName": { "Fn::GetAtt": ["${ + { + "Type": "AWS::Lambda::Permission", + "Properties": { + "FunctionName": { "Fn::GetAtt": ["${ lambdaLogicalId}", "Arn"] }, - "Action": "lambda:InvokeFunction", - "Principal": { - "Fn::Join": [ "", [ - "logs.", - { "Ref": "AWS::Region" }, - ".amazonaws.com" - ] ] - }, - "SourceArn": { - "Fn::Join": [ "", [ - "arn:aws:logs:", - { "Ref": "AWS::Region" }, - ":", - { "Ref": "AWS::AccountId" }, - ":log-group:", - "${LogGroupName}", - ":*" - ] ] - } + "Action": "lambda:InvokeFunction", + "Principal": { + "Fn::Join": [ "", [ + "logs.", + { "Ref": "AWS::Region" }, + ".", + { "Ref": "AWS::URLSuffix" } + ] ] + }, + "SourceArn": { + "Fn::Join": [ "", [ + "arn:", + { "Ref": "AWS::Partition" }, + ":logs:", + { "Ref": "AWS::Region" }, + ":", + { "Ref": "AWS::AccountId" }, + ":log-group:", + "${LogGroupName}", + ":*" + ] ] } } - `; + } + `; const newCloudWatchLogRuleObject = { [cloudWatchLogLogicalId]: JSON.parse(cloudWatchLogRuleTemplate), diff --git a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js index d94063701c0..a85c67a4c40 100644 --- a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js +++ b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.js @@ -20,19 +20,20 @@ class AwsCompileCognitoUserPoolEvents { this.hooks = { 'package:compileEvents': this.compileCognitoUserPoolEvents.bind(this), + 'after:package:finalize': this.mergeWithCustomResources.bind(this), }; } - compileCognitoUserPoolEvents() { + findUserPoolsAndFunctions() { const userPools = []; const cognitoUserPoolTriggerFunctions = []; // Iterate through all functions declared in `serverless.yml` - this.serverless.service.getAllFunctions().forEach((functionName) => { + _.forEach(this.serverless.service.getAllFunctions(), (functionName) => { const functionObj = this.serverless.service.getFunction(functionName); if (functionObj.events) { - functionObj.events.forEach(event => { + _.forEach(functionObj.events, (event) => { if (event.cognitoUserPool) { // Check event definition for `cognitoUserPool` object if (typeof event.cognitoUserPool === 'object') { @@ -80,51 +81,61 @@ class AwsCompileCognitoUserPoolEvents { } }); - // Generate CloudFormation templates for Cognito User Pool changes - _.forEach(userPools, (poolName) => { - // Create a `LambdaConfig` object for the CloudFormation template - const currentPoolTriggerFunctions = _.filter(cognitoUserPoolTriggerFunctions, { - poolName, - }); - - const lambdaConfig = _.reduce(currentPoolTriggerFunctions, (result, value) => { - const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(value.functionName); + return { cognitoUserPoolTriggerFunctions, userPools }; + } - // Return a new object to avoid lint errors - return Object.assign({}, result, { - [value.triggerSource]: { - 'Fn::GetAtt': [ - lambdaLogicalId, - 'Arn', - ], - }, - }); - }, {}); + generateTemplateForPool(poolName, currentPoolTriggerFunctions) { + const lambdaConfig = _.reduce(currentPoolTriggerFunctions, (result, value) => { + const lambdaLogicalId = this.provider.naming.getLambdaLogicalId(value.functionName); + + // Return a new object to avoid lint errors + return Object.assign({}, result, { + [value.triggerSource]: { + 'Fn::GetAtt': [ + lambdaLogicalId, + 'Arn', + ], + }, + }); + }, {}); - const userPoolLogicalId = this.provider.naming.getCognitoUserPoolLogicalId(poolName); + const userPoolLogicalId = this.provider.naming.getCognitoUserPoolLogicalId(poolName); - const DependsOn = _.map(currentPoolTriggerFunctions, (value) => this - .provider.naming.getLambdaLogicalId(value.functionName)); + // Attach `DependsOn` for any relevant Lambdas + const DependsOn = _.map(currentPoolTriggerFunctions, (value) => this + .provider.naming.getLambdaLogicalId(value.functionName)); - const userPoolTemplate = { + return { + [userPoolLogicalId]: { Type: 'AWS::Cognito::UserPool', Properties: { UserPoolName: poolName, LambdaConfig: lambdaConfig, }, DependsOn, - }; + }, + }; + } - const userPoolCFResource = { - [userPoolLogicalId]: userPoolTemplate, - }; + compileCognitoUserPoolEvents() { + const result = this.findUserPoolsAndFunctions(); + const cognitoUserPoolTriggerFunctions = result.cognitoUserPoolTriggerFunctions; + const userPools = result.userPools; + + // Generate CloudFormation templates for Cognito User Pool changes + _.forEach(userPools, (poolName) => { + const currentPoolTriggerFunctions = _.filter(cognitoUserPoolTriggerFunctions, { poolName }); + const userPoolCFResource = this.generateTemplateForPool( + poolName, + currentPoolTriggerFunctions + ); _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, userPoolCFResource); }); // Generate CloudFormation templates for IAM permissions to allow Cognito to trigger Lambda - cognitoUserPoolTriggerFunctions.forEach((cognitoUserPoolTriggerFunction) => { + _.forEach(cognitoUserPoolTriggerFunctions, (cognitoUserPoolTriggerFunction) => { const userPoolLogicalId = this.provider.naming .getCognitoUserPoolLogicalId(cognitoUserPoolTriggerFunction.poolName); const lambdaLogicalId = this.provider.naming @@ -140,7 +151,7 @@ class AwsCompileCognitoUserPoolEvents { ], }, Action: 'lambda:InvokeFunction', - Principal: 'cognito-idp.amazonaws.com', + Principal: { 'Fn::Join': ['', ['cognito-idp.', { Ref: 'AWS::URLSuffix' }]] }, SourceArn: { 'Fn::GetAtt': [ userPoolLogicalId, @@ -151,7 +162,7 @@ class AwsCompileCognitoUserPoolEvents { }; const lambdaPermissionLogicalId = this.provider.naming .getLambdaCognitoUserPoolPermissionLogicalId(cognitoUserPoolTriggerFunction.functionName, - cognitoUserPoolTriggerFunction.poolName, cognitoUserPoolTriggerFunction.triggerSource); + cognitoUserPoolTriggerFunction.poolName, cognitoUserPoolTriggerFunction.triggerSource); const permissionCFResource = { [lambdaPermissionLogicalId]: permissionTemplate, }; @@ -159,6 +170,43 @@ class AwsCompileCognitoUserPoolEvents { permissionCFResource); }); } + + mergeWithCustomResources() { + const result = this.findUserPoolsAndFunctions(); + const cognitoUserPoolTriggerFunctions = result.cognitoUserPoolTriggerFunctions; + const userPools = result.userPools; + + _.forEach(userPools, (poolName) => { + const currentPoolTriggerFunctions = _.filter(cognitoUserPoolTriggerFunctions, { poolName }); + const userPoolLogicalId = this.provider.naming.getCognitoUserPoolLogicalId(poolName); + + // If overrides exist in `Resources`, merge them in + if (_.has(this.serverless.service.resources, userPoolLogicalId)) { + const customUserPool = this.serverless.service.resources[userPoolLogicalId]; + const generatedUserPool = this.generateTemplateForPool( + poolName, + currentPoolTriggerFunctions + )[userPoolLogicalId]; + + // Merge `DependsOn` clauses + const customUserPoolDependsOn = _.get(customUserPool, 'DependsOn', []); + const DependsOn = generatedUserPool.DependsOn.concat(customUserPoolDependsOn); + + // Merge default and custom resources, and `DependsOn` clause + const mergedTemplate = Object.assign( + {}, + _.merge(generatedUserPool, customUserPool), + { DependsOn } + ); + + // Merge resource back into `Resources` + _.merge( + this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + { [userPoolLogicalId]: mergedTemplate } + ); + } + }); + } } module.exports = AwsCompileCognitoUserPoolEvents; diff --git a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js index 6ec9a6c2805..a67e80bb712 100644 --- a/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js +++ b/lib/plugins/aws/package/compile/events/cognitoUserPool/index.test.js @@ -1,5 +1,6 @@ 'use strict'; +const _ = require('lodash'); const expect = require('chai').expect; const AwsProvider = require('../../../../provider/awsProvider'); const AwsCompileCognitoUserPoolEvents = require('./index'); @@ -115,9 +116,15 @@ describe('AwsCompileCognitoUserPoolEvents', () => { expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources .FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type @@ -153,9 +160,15 @@ describe('AwsCompileCognitoUserPoolEvents', () => { expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources .FirstLambdaPermissionCognitoUserPoolMyUserPool1TriggerSourcePreSignUp.Type @@ -195,6 +208,9 @@ describe('AwsCompileCognitoUserPoolEvents', () => { expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool1 .Properties.LambdaConfig.PreSignUp['Fn::GetAtt'][0] @@ -204,6 +220,9 @@ describe('AwsCompileCognitoUserPoolEvents', () => { expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.Type ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2.DependsOn + ).to.have.lengthOf(1); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources.CognitoUserPoolMyUserPool2 .Properties.LambdaConfig.PreSignUp['Fn::GetAtt'][0] @@ -250,10 +269,14 @@ describe('AwsCompileCognitoUserPoolEvents', () => { .compiledCloudFormationTemplate.Resources .CognitoUserPoolMyUserPool.Type ).to.equal('AWS::Cognito::UserPool'); - expect(Object.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources - .CognitoUserPoolMyUserPool.Properties.LambdaConfig).length - ).to.equal(2); + .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + ).to.have.lengthOf(2); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.DependsOn + ).to.have.lengthOf(2); expect(awsCompileCognitoUserPoolEvents.serverless.service.provider .compiledCloudFormationTemplate.Resources .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type @@ -279,4 +302,145 @@ describe('AwsCompileCognitoUserPoolEvents', () => { ).to.deep.equal({}); }); }); + + describe('#mergeWithCustomResources()', () => { + it('does not merge if no custom resource is found in Resources', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool', + trigger: 'PreSignUp', + }, + }, + ], + }, + }; + awsCompileCognitoUserPoolEvents.serverless.service.resources = {}; + + awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + awsCompileCognitoUserPoolEvents.mergeWithCustomResources(); + + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties) + ).to.have.lengthOf(2); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + ).to.have.lengthOf(1); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type + ).to.equal('AWS::Lambda::Permission'); + }); + + it('should merge custom resources found in Resources', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool', + trigger: 'PreSignUp', + }, + }, + ], + }, + }; + awsCompileCognitoUserPoolEvents.serverless.service.resources = { + CognitoUserPoolMyUserPool: { + Type: 'AWS::Cognito::UserPool', + Properties: { + UserPoolName: 'ProdMyUserPool', + MfaConfiguration: 'OFF', + EmailVerificationSubject: 'Your verification code', + EmailVerificationMessage: 'Your verification code is {####}.', + SmsVerificationMessage: 'Your verification code is {####}.', + }, + }, + }; + + awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + awsCompileCognitoUserPoolEvents.mergeWithCustomResources(); + + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties) + ).to.have.lengthOf(6); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.DependsOn + ).to.have.lengthOf(1); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + ).to.have.lengthOf(1); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type + ).to.equal('AWS::Lambda::Permission'); + }); + + it('should merge `DependsOn` clauses correctly if being overridden from Resources', () => { + awsCompileCognitoUserPoolEvents.serverless.service.functions = { + first: { + events: [ + { + cognitoUserPool: { + pool: 'MyUserPool', + trigger: 'PreSignUp', + }, + }, + ], + }, + }; + awsCompileCognitoUserPoolEvents.serverless.service.resources = { + CognitoUserPoolMyUserPool: { + DependsOn: ['Something', 'SomethingElse', ['Nothing', 'NothingAtAll']], + Type: 'AWS::Cognito::UserPool', + Properties: { + UserPoolName: 'ProdMyUserPool', + MfaConfiguration: 'OFF', + EmailVerificationSubject: 'Your verification code', + EmailVerificationMessage: 'Your verification code is {####}.', + SmsVerificationMessage: 'Your verification code is {####}.', + }, + }, + }; + + awsCompileCognitoUserPoolEvents.compileCognitoUserPoolEvents(); + awsCompileCognitoUserPoolEvents.mergeWithCustomResources(); + + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Type + ).to.equal('AWS::Cognito::UserPool'); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.DependsOn + ).to.have.lengthOf(4); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties) + ).to.have.lengthOf(6); + expect(_.keys(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .CognitoUserPoolMyUserPool.Properties.LambdaConfig) + ).to.have.lengthOf(1); + expect(awsCompileCognitoUserPoolEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .FirstLambdaPermissionCognitoUserPoolMyUserPoolTriggerSourcePreSignUp.Type + ).to.equal('AWS::Lambda::Permission'); + }); + }); }); diff --git a/lib/plugins/aws/package/compile/events/iot/index.js b/lib/plugins/aws/package/compile/events/iot/index.js index 64633ebc7aa..d8d5ef892e0 100644 --- a/lib/plugins/aws/package/compile/events/iot/index.js +++ b/lib/plugins/aws/package/compile/events/iot/index.js @@ -80,10 +80,12 @@ class AwsCompileIoTEvents { "Properties": { "FunctionName": { "Fn::GetAtt": ["${lambdaLogicalId}", "Arn"] }, "Action": "lambda:InvokeFunction", - "Principal": "iot.amazonaws.com", + "Principal": { "Fn::Join": ["", [ "iot.", { "Ref": "AWS::URLSuffix" } ]] }, "SourceArn": { "Fn::Join": ["", [ - "arn:aws:iot:", + "arn:", + { "Ref": "AWS::Partition" }, + ":iot:", { "Ref": "AWS::Region" }, ":", { "Ref": "AWS::AccountId" }, diff --git a/lib/plugins/aws/package/compile/events/s3/index.js b/lib/plugins/aws/package/compile/events/s3/index.js index 707d84aaeb9..4c2c9dc4a0b 100644 --- a/lib/plugins/aws/package/compile/events/s3/index.js +++ b/lib/plugins/aws/package/compile/events/s3/index.js @@ -147,7 +147,7 @@ class AwsCompileS3Events { _.forEach(dependsOnToCreate, (item) => { const lambdaPermissionLogicalId = this.provider.naming .getLambdaS3PermissionLogicalId(item.functionName, - item.bucketName); + item.bucketName); bucketTemplate.DependsOn.push(lambdaPermissionLogicalId); }); @@ -177,17 +177,21 @@ class AwsCompileS3Events { ], }, Action: 'lambda:InvokeFunction', - Principal: 's3.amazonaws.com', - SourceArn: { 'Fn::Join': ['', - [ - `arn:aws:s3:::${s3EnabledFunction.bucketName}`, + Principal: { 'Fn::Join': ['', ['s3.', { Ref: 'AWS::URLSuffix' }]] }, + SourceArn: { + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + `:s3:::${s3EnabledFunction.bucketName}`, + ], ], - ] }, + }, }, }; const lambdaPermissionLogicalId = this.provider.naming .getLambdaS3PermissionLogicalId(s3EnabledFunction.functionName, - s3EnabledFunction.bucketName); + s3EnabledFunction.bucketName); const permissionCFResource = { [lambdaPermissionLogicalId]: permissionTemplate, }; diff --git a/lib/plugins/aws/package/compile/events/schedule/index.js b/lib/plugins/aws/package/compile/events/schedule/index.js index b115262af2a..5ec91bd4646 100644 --- a/lib/plugins/aws/package/compile/events/schedule/index.js +++ b/lib/plugins/aws/package/compile/events/schedule/index.js @@ -114,7 +114,7 @@ class AwsCompileScheduledEvents { "FunctionName": { "Fn::GetAtt": ["${ lambdaLogicalId}", "Arn"] }, "Action": "lambda:InvokeFunction", - "Principal": "events.amazonaws.com", + "Principal": { "Fn::Join": ["", [ "events.", { "Ref": "AWS::URLSuffix" } ]] }, "SourceArn": { "Fn::GetAtt": ["${scheduleLogicalId}", "Arn"] } } } diff --git a/lib/plugins/aws/package/compile/events/sns/index.js b/lib/plugins/aws/package/compile/events/sns/index.js index 69325ec4064..cde37bd2f63 100644 --- a/lib/plugins/aws/package/compile/events/sns/index.js +++ b/lib/plugins/aws/package/compile/events/sns/index.js @@ -123,7 +123,9 @@ class AwsCompileSNSEvents { topicArn = { 'Fn::Join': ['', [ - 'arn:aws:sns:', + 'arn:', + { Ref: 'AWS::Partition' }, + ':sns:', { Ref: 'AWS::Region' }, ':', { Ref: 'AWS::AccountId' }, @@ -166,7 +168,7 @@ class AwsCompileSNSEvents { Properties: { FunctionName: endpoint, Action: 'lambda:InvokeFunction', - Principal: 'sns.amazonaws.com', + Principal: { 'Fn::Join': ['', ['sns.', { Ref: 'AWS::URLSuffix' }]] }, SourceArn: topicArn, }, }, diff --git a/lib/plugins/aws/package/compile/events/sqs/index.js b/lib/plugins/aws/package/compile/events/sqs/index.js new file mode 100644 index 00000000000..3c728f06c3b --- /dev/null +++ b/lib/plugins/aws/package/compile/events/sqs/index.js @@ -0,0 +1,172 @@ +'use strict'; + +const _ = require('lodash'); + +class AwsCompileSQSEvents { + constructor(serverless) { + this.serverless = serverless; + this.provider = this.serverless.getProvider('aws'); + + this.hooks = { + 'package:compileEvents': this.compileSQSEvents.bind(this), + }; + } + + compileSQSEvents() { + this.serverless.service.getAllFunctions().forEach((functionName) => { + const functionObj = this.serverless.service.getFunction(functionName); + + if (functionObj.events) { + const sqsStatement = { + Effect: 'Allow', + Action: [ + 'sqs:ReceiveMessage', + 'sqs:DeleteMessage', + 'sqs:GetQueueAttributes', + ], + Resource: [], + }; + + functionObj.events.forEach(event => { + if (event.sqs) { + let EventSourceArn; + let BatchSize = 10; + let Enabled = 'True'; + + // TODO validate arn syntax + if (typeof event.sqs === 'object') { + if (!event.sqs.arn) { + const errorMessage = [ + `Missing "arn" property for sqs event in function "${functionName}"`, + ' The correct syntax is: sqs: ', + ' OR an object with an "arn" property.', + ' Please check the docs for more info.', + ].join(''); + throw new this.serverless.classes + .Error(errorMessage); + } + if (typeof event.sqs.arn !== 'string') { + // for dynamic arns (GetAtt/ImportValue) + if (Object.keys(event.sqs.arn).length !== 1 + || !(_.has(event.sqs.arn, 'Fn::ImportValue') + || _.has(event.sqs.arn, 'Fn::GetAtt'))) { + const errorMessage = [ + `Bad dynamic ARN property on sqs event in function "${functionName}"`, + ' If you use a dynamic "arn" (such as with Fn::GetAtt or Fn::ImportValue)', + ' there must only be one key (either Fn::GetAtt or Fn::ImportValue) in the arn', + ' object. Please check the docs for more info.', + ].join(''); + throw new this.serverless.classes + .Error(errorMessage); + } + } + EventSourceArn = event.sqs.arn; + BatchSize = event.sqs.batchSize + || BatchSize; + if (typeof event.sqs.enabled !== 'undefined') { + Enabled = event.sqs.enabled ? 'True' : 'False'; + } + } else if (typeof event.sqs === 'string') { + EventSourceArn = event.sqs; + } else { + const errorMessage = [ + `SQS event of function "${functionName}" is not an object nor a string`, + ' The correct syntax is: sqs: ', + ' OR an object with an "arn" property.', + ' Please check the docs for more info.', + ].join(''); + throw new this.serverless.classes + .Error(errorMessage); + } + + const queueName = (function () { + if (EventSourceArn['Fn::GetAtt']) { + return EventSourceArn['Fn::GetAtt'][0]; + } else if (EventSourceArn['Fn::ImportValue']) { + return EventSourceArn['Fn::ImportValue']; + } + return EventSourceArn.split(':').pop(); + }()); + + const lambdaLogicalId = this.provider.naming + .getLambdaLogicalId(functionName); + const queueLogicalId = this.provider.naming + .getQueueLogicalId(functionName, queueName); + + const funcRole = functionObj.role || this.serverless.service.provider.role; + let dependsOn = '"IamRoleLambdaExecution"'; + if (funcRole) { + if ( // check whether the custom role is an ARN + typeof funcRole === 'string' && + funcRole.indexOf(':') !== -1 + ) { + dependsOn = '[]'; + } else if ( // otherwise, check if we have an in-service reference to a role ARN + typeof funcRole === 'object' && + 'Fn::GetAtt' in funcRole && + Array.isArray(funcRole['Fn::GetAtt']) && + funcRole['Fn::GetAtt'].length === 2 && + typeof funcRole['Fn::GetAtt'][0] === 'string' && + typeof funcRole['Fn::GetAtt'][1] === 'string' && + funcRole['Fn::GetAtt'][1] === 'Arn' + ) { + dependsOn = `"${funcRole['Fn::GetAtt'][0]}"`; + } else if ( // otherwise, check if we have an import + typeof funcRole === 'object' && + 'Fn::ImportValue' in funcRole + ) { + dependsOn = '[]'; + } else if (typeof funcRole === 'string') { + dependsOn = `"${funcRole}"`; + } + } + const sqsTemplate = ` + { + "Type": "AWS::Lambda::EventSourceMapping", + "DependsOn": ${dependsOn}, + "Properties": { + "BatchSize": ${BatchSize}, + "EventSourceArn": ${JSON.stringify(EventSourceArn)}, + "FunctionName": { + "Fn::GetAtt": [ + "${lambdaLogicalId}", + "Arn" + ] + }, + "Enabled": "${Enabled}" + } + } + `; + + // add event source ARNs to PolicyDocument statements + sqsStatement.Resource.push(EventSourceArn); + + const newSQSObject = { + [queueLogicalId]: JSON.parse(sqsTemplate), + }; + + _.merge(this.serverless.service.provider.compiledCloudFormationTemplate.Resources, + newSQSObject); + } + }); + + // update the PolicyDocument statements (if default policy is used) + if (this.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution) { + const statement = this.serverless.service.provider.compiledCloudFormationTemplate + .Resources + .IamRoleLambdaExecution + .Properties + .Policies[0] + .PolicyDocument + .Statement; + if (sqsStatement.Resource.length) { + statement.push(sqsStatement); + } + } + } + }); + } +} + +module.exports = AwsCompileSQSEvents; diff --git a/lib/plugins/aws/package/compile/events/sqs/index.test.js b/lib/plugins/aws/package/compile/events/sqs/index.test.js new file mode 100644 index 00000000000..a251c9d8723 --- /dev/null +++ b/lib/plugins/aws/package/compile/events/sqs/index.test.js @@ -0,0 +1,547 @@ +'use strict'; + +const expect = require('chai').expect; +const AwsProvider = require('../../../../provider/awsProvider'); +const AwsCompileSQSEvents = require('./index'); +const Serverless = require('../../../../../../Serverless'); + +describe('AwsCompileSQSEvents', () => { + let serverless; + let awsCompileSQSEvents; + + beforeEach(() => { + serverless = new Serverless(); + serverless.service.provider.compiledCloudFormationTemplate = { + Resources: { + IamRoleLambdaExecution: { + Properties: { + Policies: [ + { + PolicyDocument: { + Statement: [], + }, + }, + ], + }, + }, + }, + }; + serverless.setProvider('aws', new AwsProvider(serverless)); + awsCompileSQSEvents = new AwsCompileSQSEvents(serverless); + awsCompileSQSEvents.serverless.service.service = 'new-service'; + }); + + describe('#constructor()', () => { + it('should set the provider variable to be an instance of AwsProvider', () => + expect(awsCompileSQSEvents.provider).to.be.instanceof(AwsProvider)); + }); + + describe('#compileSQSEvents()', () => { + it('should throw an error if sqs event type is not a string or an object', () => { + awsCompileSQSEvents.serverless.service.functions = { + first: { + events: [ + { + sqs: 42, + }, + ], + }, + }; + + expect(() => awsCompileSQSEvents.compileSQSEvents()).to.throw(Error); + }); + + it('should throw an error if the "arn" property is not given', () => { + awsCompileSQSEvents.serverless.service.functions = { + first: { + events: [ + { + sqs: { + arn: null, + }, + }, + ], + }, + }; + + expect(() => awsCompileSQSEvents.compileSQSEvents()).to.throw(Error); + }); + + it('should not throw error or merge role statements if default policy is not present', () => { + awsCompileSQSEvents.serverless.service.functions = { + first: { + events: [ + { + sqs: 'arn:aws:sqs:region:account:queueName', + }, + ], + }, + }; + + // pretend that the default IamRoleLambdaExecution is not in place + awsCompileSQSEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution = null; + + expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); + expect(awsCompileSQSEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution + ).to.equal(null); + }); + + it('should not throw error if custom IAM role is set in function', () => { + awsCompileSQSEvents.serverless.service.functions = { + first: { + role: 'arn:aws:iam::account:role/foo', + events: [ + { + sqs: 'arn:aws:sqs:region:account:MyQueue', + }, + ], + }, + }; + + // pretend that the default IamRoleLambdaExecution is not in place + awsCompileSQSEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution = null; + + expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); + expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn).to.be.instanceof(Array); + expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn.length).to.equal(0); + expect(awsCompileSQSEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution + ).to.equal(null); + }); + + it('should not throw error if custom IAM role name reference is set in function', () => { + const roleLogicalId = 'RoleLogicalId'; + awsCompileSQSEvents.serverless.service.functions = { + first: { + role: roleLogicalId, + events: [ + { + sqs: 'arn:aws:sqs:region:account:MyQueue', + }, + ], + }, + }; + + // pretend that the default IamRoleLambdaExecution is not in place + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution = null; + + expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); + expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn).to.equal(roleLogicalId); + expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution).to.equal(null); + }); + + it('should not throw error if custom IAM role reference is set in function', () => { + const roleLogicalId = 'RoleLogicalId'; + awsCompileSQSEvents.serverless.service.functions = { + first: { + role: { 'Fn::GetAtt': [roleLogicalId, 'Arn'] }, + events: [ + { + sqs: 'arn:aws:sqs:region:account:MyQueue', + }, + ], + }, + }; + + // pretend that the default IamRoleLambdaExecution is not in place + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution = null; + + expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); + expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn).to.equal(roleLogicalId); + expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution).to.equal(null); + }); + + it('should not throw error if custom IAM role is set in provider', () => { + awsCompileSQSEvents.serverless.service.functions = { + first: { + events: [ + { + sqs: 'arn:aws:sqs:region:account:MyQueue', + }, + ], + }, + }; + + // pretend that the default IamRoleLambdaExecution is not in place + awsCompileSQSEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution = null; + + awsCompileSQSEvents.serverless.service.provider + .role = 'arn:aws:iam::account:role/foo'; + + expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); + expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn).to.be.instanceof(Array); + expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn.length).to.equal(0); + expect(awsCompileSQSEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution + ).to.equal(null); + }); + + it('should not throw error if IAM role is imported', () => { + awsCompileSQSEvents.serverless.service.functions = { + first: { + role: { 'Fn::ImportValue': 'ExportedRoleId' }, + events: [ + { + sqs: 'arn:aws:sqs:region:account:MyQueue', + }, + ], + }, + }; + + // pretend that the default IamRoleLambdaExecution is not in place + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution = null; + + expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); + expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn.length).to.equal(0); + expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution).to.equal(null); + }); + + + it('should not throw error if custom IAM role reference is set in provider', () => { + const roleLogicalId = 'RoleLogicalId'; + awsCompileSQSEvents.serverless.service.functions = { + first: { + events: [ + { + sqs: 'arn:aws:sqs:region:account:MyQueue', + }, + ], + }, + }; + + // pretend that the default IamRoleLambdaExecution is not in place + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution = null; + + awsCompileSQSEvents.serverless.service.provider + .role = { 'Fn::GetAtt': [roleLogicalId, 'Arn'] }; + + expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); + expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn).to.equal(roleLogicalId); + expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution).to.equal(null); + }); + + it('should not throw error if custom IAM role name reference is set in provider', () => { + const roleLogicalId = 'RoleLogicalId'; + awsCompileSQSEvents.serverless.service.functions = { + first: { + events: [ + { + sqs: 'arn:aws:sqs:region:account:MyQueue', + }, + ], + }, + }; + + // pretend that the default IamRoleLambdaExecution is not in place + awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution = null; + + awsCompileSQSEvents.serverless.service.provider + .role = roleLogicalId; + + expect(() => { awsCompileSQSEvents.compileSQSEvents(); }).to.not.throw(Error); + expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FirstEventSourceMappingSQSMyQueue.DependsOn).to.equal(roleLogicalId); + expect(awsCompileSQSEvents.serverless.service.provider.compiledCloudFormationTemplate + .Resources.IamRoleLambdaExecution).to.equal(null); + }); + + describe('when a queue ARN is given', () => { + it('should create event source mappings when a queue ARN is given', () => { + awsCompileSQSEvents.serverless.service.functions = { + first: { + events: [ + { + sqs: { + arn: 'arn:aws:sqs:region:account:MyFirstQueue', + batchSize: 1, + enabled: false, + }, + }, + { + sqs: { + arn: 'arn:aws:sqs:region:account:MySecondQueue', + }, + }, + { + sqs: 'arn:aws:sqs:region:account:MyThirdQueue', + }, + ], + }, + }; + + awsCompileSQSEvents.compileSQSEvents(); + + // event 1 + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyFirstQueue + .Type + ).to.equal('AWS::Lambda::EventSourceMapping'); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyFirstQueue + .DependsOn + ).to.equal('IamRoleLambdaExecution'); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyFirstQueue + .Properties.EventSourceArn + ).to.equal( + awsCompileSQSEvents.serverless.service.functions.first.events[0] + .sqs.arn + ); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyFirstQueue + .Properties.BatchSize + ).to.equal( + awsCompileSQSEvents.serverless.service.functions.first.events[0] + .sqs.batchSize + ); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyFirstQueue + .Properties.Enabled + ).to.equal('False'); + + // event 2 + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMySecondQueue + .Type + ).to.equal('AWS::Lambda::EventSourceMapping'); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMySecondQueue + .DependsOn + ).to.equal('IamRoleLambdaExecution'); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMySecondQueue + .Properties.EventSourceArn + ).to.equal( + awsCompileSQSEvents.serverless.service.functions.first.events[1] + .sqs.arn + ); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMySecondQueue + .Properties.BatchSize + ).to.equal(10); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMySecondQueue + .Properties.Enabled + ).to.equal('True'); + + // event 3 + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyThirdQueue + .Type + ).to.equal('AWS::Lambda::EventSourceMapping'); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyThirdQueue + .DependsOn + ).to.equal('IamRoleLambdaExecution'); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyThirdQueue + .Properties.EventSourceArn + ).to.equal( + awsCompileSQSEvents.serverless.service.functions.first.events[2] + .sqs + ); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyThirdQueue + .Properties.BatchSize + ).to.equal(10); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.FirstEventSourceMappingSQSMyThirdQueue + .Properties.Enabled + ).to.equal('True'); + }); + + it('should allow specifying SQS Queues as CFN reference types', () => { + awsCompileSQSEvents.serverless.service.functions = { + first: { + events: [ + { + sqs: { + arn: { 'Fn::GetAtt': ['SomeQueue', 'Arn'] }, + }, + }, + { + sqs: { + arn: { 'Fn::ImportValue': 'ForeignQueue' }, + }, + }, + ], + }, + }; + + awsCompileSQSEvents.compileSQSEvents(); + + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSSomeQueue.Properties.EventSourceArn + ).to.deep.equal( + { 'Fn::GetAtt': ['SomeQueue', 'Arn'] } + ); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources.IamRoleLambdaExecution + .Properties.Policies[0].PolicyDocument.Statement[0] + ).to.deep.equal( + { + Action: [ + 'sqs:ReceiveMessage', + 'sqs:DeleteMessage', + 'sqs:GetQueueAttributes', + ], + Effect: 'Allow', + Resource: [ + { + 'Fn::GetAtt': [ + 'SomeQueue', + 'Arn', + ], + }, + { + 'Fn::ImportValue': 'ForeignQueue', + }, + ], + } + ); + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + .FirstEventSourceMappingSQSForeignQueue.Properties.EventSourceArn + ).to.deep.equal( + { 'Fn::ImportValue': 'ForeignQueue' } + ); + }); + + it('fails if keys other than Fn::GetAtt/ImportValue are used for dynamic queue ARN', () => { + awsCompileSQSEvents.serverless.service.functions = { + first: { + events: [ + { + sqs: { + arn: { + 'Fn::GetAtt': ['SomeQueue', 'Arn'], + batchSize: 1, + }, + }, + }, + ], + }, + }; + + expect(() => awsCompileSQSEvents.compileSQSEvents()).to.throw(Error); + }); + + it('should add the necessary IAM role statements', () => { + awsCompileSQSEvents.serverless.service.functions = { + first: { + events: [ + { + sqs: 'arn:aws:sqs:region:account:MyFirstQueue', + }, + { + sqs: 'arn:aws:sqs:region:account:MySecondQueue', + }, + ], + }, + }; + + const iamRoleStatements = [ + { + Effect: 'Allow', + Action: [ + 'sqs:ReceiveMessage', + 'sqs:DeleteMessage', + 'sqs:GetQueueAttributes', + ], + Resource: [ + 'arn:aws:sqs:region:account:MyFirstQueue', + 'arn:aws:sqs:region:account:MySecondQueue', + ], + }, + ]; + + awsCompileSQSEvents.compileSQSEvents(); + + expect(awsCompileSQSEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution.Properties.Policies[0] + .PolicyDocument.Statement + ).to.deep.equal(iamRoleStatements); + }); + }); + + it('should not create event source mapping when sqs events are not given', () => { + awsCompileSQSEvents.serverless.service.functions = { + first: { + events: [], + }, + }; + + awsCompileSQSEvents.compileSQSEvents(); + + // should be 1 because we've mocked the IamRoleLambdaExecution above + expect( + Object.keys(awsCompileSQSEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources).length + ).to.equal(1); + }); + + it('should not add the IAM role statements when sqs events are not given', () => { + awsCompileSQSEvents.serverless.service.functions = { + first: { + events: [], + }, + }; + + awsCompileSQSEvents.compileSQSEvents(); + + expect( + awsCompileSQSEvents.serverless.service.provider + .compiledCloudFormationTemplate.Resources + .IamRoleLambdaExecution.Properties.Policies[0] + .PolicyDocument.Statement.length + ).to.equal(0); + }); + + it('should remove all non-alphanumerics from queue names for the resource logical ids', () => { + awsCompileSQSEvents.serverless.service.functions = { + first: { + events: [ + { + sqs: 'arn:aws:sqs:region:account:some-queue-name', + }, + ], + }, + }; + + awsCompileSQSEvents.compileSQSEvents(); + + expect(awsCompileSQSEvents.serverless.service + .provider.compiledCloudFormationTemplate.Resources + ).to.have.any.keys('FirstEventSourceMappingSQSSomequeuename'); + }); + }); +}); diff --git a/lib/plugins/aws/package/compile/functions/index.js b/lib/plugins/aws/package/compile/functions/index.js index 971bf1e2bd6..90f68b80b95 100644 --- a/lib/plugins/aws/package/compile/functions/index.js +++ b/lib/plugins/aws/package/compile/functions/index.js @@ -123,13 +123,23 @@ class AwsCompileFunctions { newFunction.Properties.Timeout = Timeout; newFunction.Properties.Runtime = Runtime; + // publish these properties to the platform + this.serverless.service.functions[functionName].memory = MemorySize; + this.serverless.service.functions[functionName].timeout = Timeout; + this.serverless.service.functions[functionName].runtime = Runtime; + if (functionObject.description) { newFunction.Properties.Description = functionObject.description; } - if (functionObject.tags && typeof functionObject.tags === 'object') { + if (functionObject.tags || this.serverless.service.provider.tags) { + const tags = Object.assign( + {}, + this.serverless.service.provider.tags, + functionObject.tags + ); newFunction.Properties.Tags = []; - _.forEach(functionObject.tags, (Value, Key) => { + _.forEach(tags, (Value, Key) => { newFunction.Properties.Tags.push({ Key, Value }); }); } @@ -249,6 +259,13 @@ class AwsCompileFunctions { invalidEnvVar = `Invalid characters in environment variable ${key}`; return false; // break loop with lodash } + const value = newFunction.Properties.Environment.Variables[key]; + const isCFRef = _.isObject(value) && + !_.some(value, (v, k) => k !== 'Ref' && !_.startsWith(k, 'Fn::')); + if (!isCFRef && !_.isString(value)) { + invalidEnvVar = `Environment variable ${key} must contain string`; + return false; + } } ); @@ -279,6 +296,19 @@ class AwsCompileFunctions { delete newFunction.Properties.VpcConfig; } + if (functionObject.reservedConcurrency) { + if (_.isInteger(functionObject.reservedConcurrency)) { + newFunction.Properties.ReservedConcurrentExecutions = functionObject.reservedConcurrency; + } else { + const errorMessage = [ + 'You should use integer as reservedConcurrency value on function: ', + `${newFunction.Properties.FunctionName}`, + ].join(''); + + return BbPromise.reject(new this.serverless.classes.Error(errorMessage)); + } + } + newFunction.DependsOn = [this.provider.naming.getLogGroupLogicalId(functionName)] .concat(newFunction.DependsOn || []); diff --git a/lib/plugins/aws/package/compile/functions/index.test.js b/lib/plugins/aws/package/compile/functions/index.test.js index b5e18c68424..c743626b74a 100644 --- a/lib/plugins/aws/package/compile/functions/index.test.js +++ b/lib/plugins/aws/package/compile/functions/index.test.js @@ -25,6 +25,7 @@ describe('AwsCompileFunctions', () => { }; serverless = new Serverless(options); serverless.setProvider('aws', new AwsProvider(serverless, options)); + serverless.cli = new serverless.classes.CLI(); awsCompileFunctions = new AwsCompileFunctions(serverless, options); awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate = { Resources: {}, @@ -521,7 +522,104 @@ describe('AwsCompileFunctions', () => { }); }); - it('should create a function resource with tags', () => { + it('should create a function resource with provider level tags', () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep).pop(); + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + }, + }; + + awsCompileFunctions.serverless.service.provider.tags = { + foo: 'bar', + baz: 'qux', + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: [ + 'FuncLogGroup', + 'IamRoleLambdaExecution', + ], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs4.3', + Timeout: 6, + Tags: [ + { Key: 'foo', Value: 'bar' }, + { Key: 'baz', Value: 'qux' }, + ], + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled + .then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should create a function resource with function level tags', () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep).pop(); + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + tags: { + foo: 'bar', + baz: 'qux', + }, + }, + }; + + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: [ + 'FuncLogGroup', + 'IamRoleLambdaExecution', + ], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 1024, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs4.3', + Timeout: 6, + Tags: [ + { Key: 'foo', Value: 'bar' }, + { Key: 'baz', Value: 'qux' }, + ], + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled + .then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should create a function resource with provider and function level tags', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact .split(path.sep).pop(); @@ -536,6 +634,11 @@ describe('AwsCompileFunctions', () => { }, }; + awsCompileFunctions.serverless.service.provider.tags = { + foo: 'quux', + corge: 'uier', + }; + const compiledFunction = { Type: 'AWS::Lambda::Function', DependsOn: [ @@ -555,6 +658,7 @@ describe('AwsCompileFunctions', () => { Timeout: 6, Tags: [ { Key: 'foo', Value: 'bar' }, + { Key: 'corge', Value: 'uier' }, { Key: 'baz', Value: 'qux' }, ], }, @@ -1352,6 +1456,53 @@ describe('AwsCompileFunctions', () => { return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(Error); }); + it('should throw an error if environment variable is not a string', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + environment: { + counter: 18, + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(Error); + }); + + it('should accept an environment variable with CF ref and functions', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + environment: { + counter: { + Ref: 'TestVariable', + }, + list: { + 'Fn::Join:': [', ', ['a', 'b', 'c']], + }, + }, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled + .then(() => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + environment: { + counter: { + NotRef: 'TestVariable', + }, + }, + }, + }; + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(Error); + }); + }); + it('should consider function based config when creating a function resource', () => { const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; const s3FileName = awsCompileFunctions.serverless.service.package.artifact @@ -1730,6 +1881,65 @@ describe('AwsCompileFunctions', () => { ); }); }); + + it('should set function declared reserved concurrency limit', () => { + const s3Folder = awsCompileFunctions.serverless.service.package.artifactDirectoryName; + const s3FileName = awsCompileFunctions.serverless.service.package.artifact + .split(path.sep).pop(); + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + reservedConcurrency: 5, + }, + }; + const compiledFunction = { + Type: 'AWS::Lambda::Function', + DependsOn: [ + 'FuncLogGroup', + 'IamRoleLambdaExecution', + ], + Properties: { + Code: { + S3Key: `${s3Folder}/${s3FileName}`, + S3Bucket: { Ref: 'ServerlessDeploymentBucket' }, + }, + FunctionName: 'new-service-dev-func', + Handler: 'func.function.handler', + MemorySize: 1024, + ReservedConcurrentExecutions: 5, + Role: { 'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'] }, + Runtime: 'nodejs4.3', + Timeout: 6, + }, + }; + + return expect(awsCompileFunctions.compileFunctions()).to.be.fulfilled + .then(() => { + expect( + awsCompileFunctions.serverless.service.provider.compiledCloudFormationTemplate + .Resources.FuncLambdaFunction + ).to.deep.equal(compiledFunction); + }); + }); + + it('should throw an informative error message if non-integer reserved concurrency limit set ' + + 'on function', () => { + awsCompileFunctions.serverless.service.functions = { + func: { + handler: 'func.function.handler', + name: 'new-service-dev-func', + reservedConcurrency: '1', + }, + }; + + const errorMessage = [ + 'You should use integer as reservedConcurrency value on function: ', + 'new-service-dev-func', + ].join(''); + + return expect(awsCompileFunctions.compileFunctions()).to.be.rejectedWith(errorMessage); + }); }); describe('#compileRole()', () => { diff --git a/lib/plugins/aws/package/lib/generateCoreTemplate.js b/lib/plugins/aws/package/lib/generateCoreTemplate.js index fae3c200071..98341552c09 100644 --- a/lib/plugins/aws/package/lib/generateCoreTemplate.js +++ b/lib/plugins/aws/package/lib/generateCoreTemplate.js @@ -24,6 +24,7 @@ module.exports = { ); const bucketName = this.serverless.service.provider.deploymentBucket; + const isS3TransferAccelerationSupported = this.provider.isS3TransferAccelerationSupported(); const isS3TransferAccelerationEnabled = this.provider.isS3TransferAccelerationEnabled(); const isS3TransferAccelerationDisabled = this.provider.isS3TransferAccelerationDisabled(); @@ -53,7 +54,7 @@ module.exports = { }); } - if (isS3TransferAccelerationEnabled) { + if (isS3TransferAccelerationEnabled && isS3TransferAccelerationSupported) { // enable acceleration via CloudFormation this.serverless.service.provider.compiledCloudFormationTemplate .Resources.ServerlessDeploymentBucket.Properties = { @@ -64,7 +65,7 @@ module.exports = { // keep track of acceleration status via CloudFormation Output this.serverless.service.provider.compiledCloudFormationTemplate .Outputs.ServerlessDeploymentBucketAccelerated = { Value: true }; - } else if (isS3TransferAccelerationDisabled) { + } else if (isS3TransferAccelerationDisabled && isS3TransferAccelerationSupported) { // explicitly disable acceleration via CloudFormation this.serverless.service.provider.compiledCloudFormationTemplate .Resources.ServerlessDeploymentBucket.Properties = { diff --git a/lib/plugins/aws/package/lib/generateCoreTemplate.test.js b/lib/plugins/aws/package/lib/generateCoreTemplate.test.js index e4cb5eba22f..72a2ae361ef 100644 --- a/lib/plugins/aws/package/lib/generateCoreTemplate.test.js +++ b/lib/plugins/aws/package/lib/generateCoreTemplate.test.js @@ -161,6 +161,21 @@ describe('#generateCoreTemplate()', () => { }); }); + it('should exclude AccelerateConfiguration for govcloud region', () => { + sinon.stub(awsPlugin.provider, 'request').resolves(); + sinon.stub(serverless.utils, 'writeFileSync').resolves(); + serverless.config.servicePath = './'; + awsPlugin.provider.options.region = 'us-gov-west-1'; + + return awsPlugin.generateCoreTemplate() + .then(() => { + const template = serverless.service.provider.coreCloudFormationTemplate; + expect(template.Resources.ServerlessDeploymentBucket).to.be.deep.equal({ + Type: 'AWS::S3::Bucket', + }); + }); + }); + it('should explode if transfer acceleration is both enabled and disabled', () => { sinon.stub(awsPlugin.provider, 'request').resolves(); sinon.stub(serverless.utils, 'writeFileSync').resolves(); diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.js b/lib/plugins/aws/package/lib/mergeIamTemplates.js index a17e2dffd8a..0f28c782619 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.js @@ -7,6 +7,7 @@ const path = require('path'); module.exports = { mergeIamTemplates() { this.validateStatements(this.serverless.service.provider.iamRoleStatements); + this.validateManagedPolicies(this.serverless.service.provider.iamManagedPolicies); return this.merge(); }, @@ -32,7 +33,7 @@ module.exports = { if (_.has(this.serverless.service.provider, 'logRetentionInDays')) { if (_.isInteger(this.serverless.service.provider.logRetentionInDays) && - this.serverless.service.provider.logRetentionInDays > 0) { + this.serverless.service.provider.logRetentionInDays > 0) { newLogGroup[logGroupLogicalId].Properties.RetentionInDays = this.serverless.service.provider.logRetentionInDays; } else { @@ -91,8 +92,10 @@ module.exports = { .PolicyDocument .Statement[0] .Resource - .push({ 'Fn::Sub': 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}' + - `:log-group:${this.provider.naming.getLogGroupName(functionObject.name)}:*` }); + .push({ + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + + `:log-group:${this.provider.naming.getLogGroupName(functionObject.name)}:*`, + }); this.serverless.service.provider.compiledCloudFormationTemplate .Resources[this.provider.naming.getRoleLogicalId()] @@ -101,8 +104,10 @@ module.exports = { .PolicyDocument .Statement[1] .Resource - .push({ 'Fn::Sub': 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}' + - `:log-group:${this.provider.naming.getLogGroupName(functionObject.name)}:*:*` }); + .push({ + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}' + + `:log-group:${this.provider.naming.getLogGroupName(functionObject.name)}:*:*`, + }); }); if (this.serverless.service.provider.iamRoleStatements) { @@ -120,6 +125,14 @@ module.exports = { .Statement.concat(this.serverless.service.provider.iamRoleStatements); } + if (this.serverless.service.provider.iamManagedPolicies) { + // add iam managed policies + const iamManagedPolicies = this.serverless.service.provider.iamManagedPolicies; + if (iamManagedPolicies.length > 0) { + this.mergeManagedPolicies(iamManagedPolicies); + } + } + // check if one of the functions contains vpc configuration const vpcConfigProvided = []; this.serverless.service.getAllFunctions().forEach((functionName) => { @@ -131,17 +144,30 @@ module.exports = { if (_.includes(vpcConfigProvided, true) || this.serverless.service.provider.vpc) { // add managed iam policy to allow ENI management - this.serverless.service.provider.compiledCloudFormationTemplate - .Resources[this.provider.naming.getRoleLogicalId()] - .Properties - .ManagedPolicyArns = [ - 'arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole', - ]; + this.mergeManagedPolicies([{ + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole', + ], + ], + }]); } return BbPromise.resolve(); }, + mergeManagedPolicies(managedPolicies) { + const resource = this.serverless.service.provider.compiledCloudFormationTemplate + .Resources[this.provider.naming.getRoleLogicalId()] + .Properties; + if (!_.has(resource, 'ManagedPolicyArns') || _.isEmpty(resource.ManagedPolicyArns)) { + resource.ManagedPolicyArns = []; + } + resource.ManagedPolicyArns = resource.ManagedPolicyArns.concat(managedPolicies); + }, + validateStatements(statements) { // Verify that iamRoleStatements (if present) is an array of { Effect: ..., // Action: ..., Resource: ... } objects. @@ -154,7 +180,7 @@ module.exports = { } else { const descriptions = statements.map((statement, i) => { const missing = ['Effect', 'Action', 'Resource'].filter( - prop => statement[prop] === undefined); + prop => statement[prop] === undefined); return missing.length === 0 ? null : `statement ${i} is missing the following properties: ${missing.join(', ')}`; }); @@ -173,4 +199,14 @@ module.exports = { throw new this.serverless.classes.Error(errorMessage); } }, + + validateManagedPolicies(iamManagedPolicies) { + // Verify that iamManagedPolicies (if present) is an array + if (!iamManagedPolicies) { + return; + } + if (!_.isArray(iamManagedPolicies)) { + throw new this.serverless.classes.Error('iamManagedPolicies should be an array of arns'); + } + }, }; diff --git a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js index 1407989251f..1a236a74311 100644 --- a/lib/plugins/aws/package/lib/mergeIamTemplates.test.js +++ b/lib/plugins/aws/package/lib/mergeIamTemplates.test.js @@ -95,7 +95,7 @@ describe('#mergeIamTemplates()', () => { ], Resource: [ { - 'Fn::Sub': 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:' + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + `log-group:/aws/lambda/${qualifiedFunction}:*`, }, ], @@ -107,7 +107,7 @@ describe('#mergeIamTemplates()', () => { ], Resource: [ { - 'Fn::Sub': 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:' + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + `log-group:/aws/lambda/${qualifiedFunction}:*:*`, }, ], @@ -155,6 +155,56 @@ describe('#mergeIamTemplates()', () => { }); }); + it('should add managed policy arns', () => { + awsPackage.serverless.service.provider.iamManagedPolicies = [ + 'some:aws:arn:xxx:*:*', + 'someOther:aws:arn:xxx:*:*', + { 'Fn::Join': [':', ['arn:aws:iam:', { Ref: 'AWSAccountId' }, 'some/path']] }, + ]; + return awsPackage.mergeIamTemplates() + .then(() => { + expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate + .Resources[awsPackage.provider.naming.getRoleLogicalId()] + .Properties + .ManagedPolicyArns + ).to.deep.equal(awsPackage.serverless.service.provider.iamManagedPolicies); + }); + }); + + it('should merge managed policy arns when vpc config supplied', () => { + awsPackage.serverless.service.provider.vpc = { + securityGroupIds: ['xxx'], + subnetIds: ['xxx'], + }; + const iamManagedPolicies = [ + 'some:aws:arn:xxx:*:*', + 'someOther:aws:arn:xxx:*:*', + { 'Fn::Join': [':', ['arn:aws:iam:', { Ref: 'AWSAccountId' }, 'some/path']] }, + ]; + awsPackage.serverless.service.provider.iamManagedPolicies = iamManagedPolicies; + const expectedManagedPolicyArns = [ + 'some:aws:arn:xxx:*:*', + 'someOther:aws:arn:xxx:*:*', + { 'Fn::Join': [':', ['arn:aws:iam:', { Ref: 'AWSAccountId' }, 'some/path']] }, + { 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole', + ], + ], + }, + ]; + return awsPackage.mergeIamTemplates() + .then(() => { + expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate + .Resources[awsPackage.provider.naming.getRoleLogicalId()] + .Properties + .ManagedPolicyArns + ).to.deep.equal(expectedManagedPolicyArns); + }); + }); + it('should throw error if custom IAM policy statements is not an array', () => { awsPackage.serverless.service.provider.iamRoleStatements = { policy: 'some_value', @@ -179,7 +229,7 @@ describe('#mergeIamTemplates()', () => { }]; expect(() => awsPackage.mergeIamTemplates()).to.throw( - 'missing the following properties: Effect'); + 'missing the following properties: Effect'); }); it('should throw error if a custom IAM policy statement does not have an Action field', () => { @@ -189,7 +239,7 @@ describe('#mergeIamTemplates()', () => { }]; expect(() => awsPackage.mergeIamTemplates()).to.throw( - 'missing the following properties: Action'); + 'missing the following properties: Action'); }); it('should throw error if a custom IAM policy statement does not have a Resource field', () => { @@ -199,7 +249,7 @@ describe('#mergeIamTemplates()', () => { }]; expect(() => awsPackage.mergeIamTemplates()).to.throw( - 'missing the following properties: Resource'); + 'missing the following properties: Resource'); }); it('should throw an error describing all problematics custom IAM policy statements', () => { @@ -222,6 +272,12 @@ describe('#mergeIamTemplates()', () => { .to.throw(/statement 0 is missing.*Resource; statement 2 is missing.*Effect, Action/); }); + it('should throw error if managed policies is not an array', () => { + awsPackage.serverless.service.provider.iamManagedPolicies = 'a string'; + expect(() => awsPackage.mergeIamTemplates()) + .to.throw('iamManagedPolicies should be an array of arns'); + }); + it('should add a CloudWatch LogGroup resource', () => { const normalizedName = awsPackage.provider.naming.getLogGroupLogicalId(functionName); return awsPackage.mergeIamTemplates().then(() => { @@ -234,44 +290,44 @@ describe('#mergeIamTemplates()', () => { LogGroupName: awsPackage.provider.naming.getLogGroupName(functionName), }, } - ); + ); }); }); it('should add RetentionInDays to a CloudWatch LogGroup resource if logRetentionInDays is given' - , () => { - awsPackage.serverless.service.provider.logRetentionInDays = 5; - const normalizedName = awsPackage.provider.naming.getLogGroupLogicalId(functionName); - return awsPackage.mergeIamTemplates().then(() => { - expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[normalizedName] - ).to.deep.equal( - { - Type: 'AWS::Logs::LogGroup', - Properties: { - LogGroupName: awsPackage.provider.naming.getLogGroupName(functionName), - RetentionInDays: 5, - }, - } - ); + , () => { + awsPackage.serverless.service.provider.logRetentionInDays = 5; + const normalizedName = awsPackage.provider.naming.getLogGroupLogicalId(functionName); + return awsPackage.mergeIamTemplates().then(() => { + expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate + .Resources[normalizedName] + ).to.deep.equal( + { + Type: 'AWS::Logs::LogGroup', + Properties: { + LogGroupName: awsPackage.provider.naming.getLogGroupName(functionName), + RetentionInDays: 5, + }, + } + ); + }); }); - }); it('should throw error if RetentionInDays is 0 or not an integer' - , () => { - awsPackage.serverless.service.provider.logRetentionInDays = 0; - expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); - awsPackage.serverless.service.provider.logRetentionInDays = 'string'; - expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); - awsPackage.serverless.service.provider.logRetentionInDays = []; - expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); - awsPackage.serverless.service.provider.logRetentionInDays = {}; - expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); - awsPackage.serverless.service.provider.logRetentionInDays = undefined; - expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); - awsPackage.serverless.service.provider.logRetentionInDays = null; - expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); - }); + , () => { + awsPackage.serverless.service.provider.logRetentionInDays = 0; + expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); + awsPackage.serverless.service.provider.logRetentionInDays = 'string'; + expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); + awsPackage.serverless.service.provider.logRetentionInDays = []; + expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); + awsPackage.serverless.service.provider.logRetentionInDays = {}; + expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); + awsPackage.serverless.service.provider.logRetentionInDays = undefined; + expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); + awsPackage.serverless.service.provider.logRetentionInDays = null; + expect(() => awsPackage.mergeIamTemplates()).to.throw('should be an integer'); + }); it('should add a CloudWatch LogGroup resource if all functions use custom roles', () => { awsPackage.serverless.service.functions[functionName].role = 'something'; @@ -300,7 +356,7 @@ describe('#mergeIamTemplates()', () => { LogGroupName: awsPackage.provider.naming.getLogGroupName(f.func0.name), }, } - ); + ); expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate .Resources[normalizedNames[1]] ).to.deep.equal( @@ -310,7 +366,7 @@ describe('#mergeIamTemplates()', () => { LogGroupName: awsPackage.provider.naming.getLogGroupName(f.func1.name), }, } - ); + ); }); }); @@ -326,7 +382,7 @@ describe('#mergeIamTemplates()', () => { .Resource ).to.deep.equal([ { - 'Fn::Sub': 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:' + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + `log-group:/aws/lambda/${qualifiedFunction}:*`, }, ]); @@ -339,7 +395,7 @@ describe('#mergeIamTemplates()', () => { .Resource ).to.deep.equal([ { - 'Fn::Sub': 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:' + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + `log-group:/aws/lambda/${qualifiedFunction}:*:*`, }, ]); @@ -367,12 +423,16 @@ describe('#mergeIamTemplates()', () => { .Resource ).to.deep.equal( [ - { 'Fn::Sub': 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:' - + 'log-group:/aws/lambda/func0:*' }, - { 'Fn::Sub': 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:' - + 'log-group:/aws/lambda/func1:*' }, + { + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + + 'log-group:/aws/lambda/func0:*', + }, + { + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + + 'log-group:/aws/lambda/func1:*', + }, ] - ); + ); expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate .Resources[awsPackage.provider.naming.getRoleLogicalId()] .Properties @@ -382,12 +442,16 @@ describe('#mergeIamTemplates()', () => { .Resource ).to.deep.equal( [ - { 'Fn::Sub': 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:' - + 'log-group:/aws/lambda/func0:*:*' }, - { 'Fn::Sub': 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:' - + 'log-group:/aws/lambda/func1:*:*' }, + { + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + + 'log-group:/aws/lambda/func0:*:*', + }, + { + 'Fn::Sub': 'arn:${AWS::Partition}:logs:${AWS::Region}:${AWS::AccountId}:' + + 'log-group:/aws/lambda/func1:*:*', + }, ] - ); + ); }); }); @@ -409,7 +473,7 @@ describe('#mergeIamTemplates()', () => { .then(() => expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate .Resources[awsPackage.provider.naming.getRoleLogicalId()] ).to.exist - ); + ); }); it('should not add the default role if role is defined on a provider level', () => { @@ -449,7 +513,7 @@ describe('#mergeIamTemplates()', () => { .then(() => expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate .Resources[awsPackage.provider.naming.getRoleLogicalId()] ).to.not.exist - ); + ); }); describe('ManagedPolicyArns property', () => { @@ -463,8 +527,8 @@ describe('#mergeIamTemplates()', () => { return awsPackage.mergeIamTemplates() .then(() => expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()].Properties.ManagedPolicyArns - ).to.not.exist + .Resources[awsPackage.provider.naming.getRoleLogicalId()].Properties.ManagedPolicyArns + ).to.not.exist ); }); @@ -478,9 +542,15 @@ describe('#mergeIamTemplates()', () => { .then(() => { expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate .Resources[awsPackage.provider.naming.getRoleLogicalId()].Properties.ManagedPolicyArns - ).to.deep.equal([ - 'arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole', - ]); + ).to.deep.equal([{ + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole', + ], + ], + }]); }); }); @@ -504,9 +574,15 @@ describe('#mergeIamTemplates()', () => { .then(() => { expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate .Resources[awsPackage.provider.naming.getRoleLogicalId()].Properties.ManagedPolicyArns - ).to.deep.equal([ - 'arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole', - ]); + ).to.deep.equal([{ + 'Fn::Join': ['', + [ + 'arn:', + { Ref: 'AWS::Partition' }, + ':iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole', + ], + ], + }]); }); }); @@ -525,8 +601,8 @@ describe('#mergeIamTemplates()', () => { return awsPackage.mergeIamTemplates() .then(() => expect(awsPackage.serverless.service.provider.compiledCloudFormationTemplate - .Resources[awsPackage.provider.naming.getRoleLogicalId()] - ).to.not.exist + .Resources[awsPackage.provider.naming.getRoleLogicalId()] + ).to.not.exist ); }); }); diff --git a/lib/plugins/aws/package/lib/saveServiceState.js b/lib/plugins/aws/package/lib/saveServiceState.js index 46756bd6271..7d6afe2fbb6 100644 --- a/lib/plugins/aws/package/lib/saveServiceState.js +++ b/lib/plugins/aws/package/lib/saveServiceState.js @@ -36,7 +36,7 @@ module.exports = { }, }; - this.serverless.utils.writeFileSync(serviceStateFilePath, state); + this.serverless.utils.writeFileSync(serviceStateFilePath, state, true); return BbPromise.resolve(); }, diff --git a/lib/plugins/aws/package/lib/saveServiceState.test.js b/lib/plugins/aws/package/lib/saveServiceState.test.js index db9b02bd1f0..064fb861c0f 100644 --- a/lib/plugins/aws/package/lib/saveServiceState.test.js +++ b/lib/plugins/aws/package/lib/saveServiceState.test.js @@ -63,7 +63,7 @@ describe('#saveServiceState()', () => { }; expect(getServiceStateFileNameStub.calledOnce).to.equal(true); - expect(writeFileSyncStub.calledWithExactly(filePath, expectedStateFileContent)) + expect(writeFileSyncStub.calledWithExactly(filePath, expectedStateFileContent, true)) .to.equal(true); }); }); @@ -97,7 +97,7 @@ describe('#saveServiceState()', () => { }; expect(getServiceStateFileNameStub.calledOnce).to.equal(true); - expect(writeFileSyncStub.calledWithExactly(filePath, expectedStateFileContent)) + expect(writeFileSyncStub.calledWithExactly(filePath, expectedStateFileContent, true)) .to.equal(true); }); }); diff --git a/lib/plugins/aws/provider/awsProvider.js b/lib/plugins/aws/provider/awsProvider.js index 6eae516f637..50f7a33c01b 100644 --- a/lib/plugins/aws/provider/awsProvider.js +++ b/lib/plugins/aws/provider/awsProvider.js @@ -12,6 +12,7 @@ const https = require('https'); const fs = require('fs'); const objectHash = require('object-hash'); const PromiseQueue = require('promise-queue'); +const getS3EndpointForRegion = require('../utils/getS3EndpointForRegion'); const constants = { providerName: 'aws', @@ -80,8 +81,8 @@ const impl = { const profileCredentials = new AWS.SharedIniFileCredentials(params); if (!(profileCredentials.accessKeyId - || profileCredentials.sessionToken - || profileCredentials.roleArn)) { + || profileCredentials.sessionToken + || profileCredentials.roleArn)) { throw new Error(`Profile ${profile} does not exist`); } @@ -126,9 +127,7 @@ class AwsProvider { || process.env.https_proxy; if (proxy) { - const proxyOptions = url.parse(proxy); - proxyOptions.secureEndpoint = true; - AWS.config.httpOptions.agent = new HttpsProxyAgent(proxyOptions); + AWS.config.httpOptions.agent = new HttpsProxyAgent(url.parse(proxy)); } const ca = process.env.ca @@ -200,20 +199,27 @@ class AwsProvider { const requestOptions = _.isObject(options) ? options : {}; const shouldCache = _.get(requestOptions, 'useCache', false); const paramsHash = objectHash.sha1(params); + const MAX_TRIES = 4; const persistentRequest = (f) => new BbPromise((resolve, reject) => { - const doCall = () => { + const doCall = (numTry) => { f() - .then(resolve) - .catch((e) => { - if (e.statusCode === 429) { - that.serverless.cli.log("'Too many requests' received, sleeping 5 seconds"); - setTimeout(doCall, 5000); + // We're resembling if/else logic, therefore single `then` instead of `then`/`catch` pair + .then(resolve, e => { + if (numTry < MAX_TRIES && + ((e.providerError && e.providerError.retryable) || e.statusCode === 429)) { + that.serverless.cli.log( + _.join([ + `Recoverable error occurred (${e.message}), sleeping for 5 seconds.`, + `Try ${numTry + 1} of ${MAX_TRIES}`, + ], ' ') + ); + setTimeout(doCall, 5000, numTry + 1); } else { reject(e); } }); }; - return doCall(); + return doCall(0); }); // Emit a warning for misuses of the old signature including stage and region @@ -250,21 +256,29 @@ class AwsProvider { const errorMessage = [ 'AWS provider credentials not found.', ' Learn how to set up AWS provider credentials', - ` in our docs here: ${chalk.green('http://bit.ly/aws-creds-setup')}.`, + ` in our docs here: <${chalk.green('http://bit.ly/aws-creds-setup')}>.`, ].join(''); message = errorMessage; userStats.track('user_awsCredentialsNotFound'); + // We do not want to trigger the retry mechanism for credential errors + return BbPromise.reject(Object.assign( + new this.serverless.classes.Error(message, err.statusCode), + { providerError: _.assign({}, err, { retryable: false }) } + )); } - return BbPromise.reject(new this.serverless.classes.Error(message, err.statusCode)); + return BbPromise.reject(Object.assign( + new this.serverless.classes.Error(message, err.statusCode), + { providerError: err } + )); }); }) - .then(data => { - const result = BbPromise.resolve(data); - if (shouldCache) { - _.set(this.requestCache, `${service}.${method}.${paramsHash}`, result); - } - return result; - })); + .then(data => { + const result = BbPromise.resolve(data); + if (shouldCache) { + _.set(this.requestCache, `${service}.${method}.${paramsHash}`, result); + } + return result; + })); if (shouldCache) { _.set(this.requestCache, `${service}.${method}.${paramsHash}`, request); @@ -309,8 +323,17 @@ class AwsProvider { canUseS3TransferAcceleration(service, method) { // TODO enable more S3 APIs? return service === 'S3' - && ['upload', 'putObject'].indexOf(method) !== -1 - && this.isS3TransferAccelerationEnabled(); + && ['upload', 'putObject'].indexOf(method) !== -1 + && this.isS3TransferAccelerationEnabled(); + } + + // This function will be used to block the addition of transfer acceleration options + // to the cloudformation template for regions where acceleration is not supported (ie, govcloud) + isS3TransferAccelerationSupported() { + // Only enable s3 transfer acceleration for standard regions (non govcloud/china) + // since those regions do not yet support it + const endpoint = getS3EndpointForRegion(this.getRegion()); + return endpoint === 's3.amazonaws.com'; } isS3TransferAccelerationEnabled() { @@ -330,13 +353,28 @@ class AwsProvider { credentials.useAccelerateEndpoint = true; // eslint-disable-line no-param-reassign } + getValues(source, paths) { + return paths.map(path => ({ + path, + value: _.get(source, path.join('.')), + })); + } + firstValue(values) { + return values.reduce((result, current) => (result.value ? result : current), {}); + } + + getRegionSourceValue() { + const values = this.getValues(this, [ + ['options', 'region'], + ['serverless', 'config', 'region'], + ['serverless', 'service', 'provider', 'region'], + ]); + return this.firstValue(values); + } getRegion() { const defaultRegion = 'us-east-1'; - - return _.get(this, 'options.region') - || _.get(this, 'serverless.config.region') - || _.get(this, 'serverless.service.provider.region') - || defaultRegion; + const regionSourceValue = this.getRegionSourceValue(); + return regionSourceValue.value || defaultRegion; } getServerlessDeploymentBucketName() { @@ -352,18 +390,38 @@ class AwsProvider { ).then((result) => result.StackResourceDetail.PhysicalResourceId); } + getStageSourceValue() { + const values = this.getValues(this, [ + ['options', 'stage'], + ['serverless', 'config', 'stage'], + ['serverless', 'service', 'provider', 'stage'], + ]); + return this.firstValue(values); + } getStage() { const defaultStage = 'dev'; - - return _.get(this, 'options.stage') - || _.get(this, 'serverless.config.stage') - || _.get(this, 'serverless.service.provider.stage') - || defaultStage; + const stageSourceValue = this.getStageSourceValue(); + return stageSourceValue.value || defaultStage; } getAccountId() { + return this.getAccountInfo() + .then((result) => result.accountId); + } + + getAccountInfo() { return this.request('STS', 'getCallerIdentity', {}) - .then((result) => result.Account); + .then((result) => { + const arn = result.Arn; + const accountId = result.Account; + const partition = _.nth(_.split(arn, ':'), 1); // ex: arn:aws:iam:acctId:user/xyz + return { + accountId, + partition, + arn: result.Arn, + userId: result.UserId, + }; + }); } /** @@ -383,7 +441,7 @@ class AwsProvider { */ getApiGatewayRestApiRootResourceId() { if (this.serverless.service.provider.apiGateway - && this.serverless.service.provider.apiGateway.restApiRootResourceId) { + && this.serverless.service.provider.apiGateway.restApiRootResourceId) { return this.serverless.service.provider.apiGateway.restApiRootResourceId; } return { 'Fn::GetAtt': [this.naming.getRestApiLogicalId(), 'RootResourceId'] }; @@ -394,7 +452,7 @@ class AwsProvider { */ getApiGatewayPredefinedResources() { if (!this.serverless.service.provider.apiGateway - || !this.serverless.service.provider.apiGateway.restApiResources) { + || !this.serverless.service.provider.apiGateway.restApiResources) { return []; } diff --git a/lib/plugins/aws/provider/awsProvider.test.js b/lib/plugins/aws/provider/awsProvider.test.js index 297b298b670..b518cc28dc7 100644 --- a/lib/plugins/aws/provider/awsProvider.test.js +++ b/lib/plugins/aws/provider/awsProvider.test.js @@ -1,5 +1,7 @@ 'use strict'; +/* eslint-disable no-unused-expressions */ + const _ = require('lodash'); const BbPromise = require('bluebird'); const chai = require('chai'); @@ -72,118 +74,118 @@ describe('AwsProvider', () => { // clear env delete process.env.AWS_CLIENT_TIMEOUT; }); - }); - describe('#constructor() certificate authority - environment variable', () => { - afterEach('Environment Variable Cleanup', () => { - // clear env - delete process.env.ca; - }); - it('should set AWS ca single', () => { - process.env.ca = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; - const newAwsProvider = new AwsProvider(serverless, options); + describe('certificate authority - environment variable', () => { + afterEach('Environment Variable Cleanup', () => { + // clear env + delete process.env.ca; + }); + it('should set AWS ca single', () => { + process.env.ca = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; + const newAwsProvider = new AwsProvider(serverless, options); - expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); - }); + expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); + }); - it('should set AWS ca multiple', () => { - const certContents = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; - process.env.ca = `${certContents},${certContents}`; - const newAwsProvider = new AwsProvider(serverless, options); + it('should set AWS ca multiple', () => { + const certContents = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; + process.env.ca = `${certContents},${certContents}`; + const newAwsProvider = new AwsProvider(serverless, options); - expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); + expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); + }); }); - }); - describe('#constructor() certificate authority - file', () => { - const certContents = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; - const tmpdir = os.tmpdir(); - let file1 = null; - let file2 = null; - beforeEach('Create CA Files and env vars', () => { - file1 = path.join(tmpdir, 'ca1.txt'); - file2 = path.join(tmpdir, 'ca2.txt'); - fs.writeFileSync(file1, certContents); - fs.writeFileSync(file2, certContents); - }); - - afterEach('CA File Cleanup', () => { - // delete files - fs.unlinkSync(file1); - fs.unlinkSync(file2); - // clear env - delete process.env.ca; - delete process.env.cafile; - }); + describe('certificate authority - file', () => { + const certContents = '-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----'; + const tmpdir = os.tmpdir(); + let file1 = null; + let file2 = null; + beforeEach('Create CA Files and env vars', () => { + file1 = path.join(tmpdir, 'ca1.txt'); + file2 = path.join(tmpdir, 'ca2.txt'); + fs.writeFileSync(file1, certContents); + fs.writeFileSync(file2, certContents); + }); - it('should set AWS cafile single', () => { - process.env.cafile = file1; - const newAwsProvider = new AwsProvider(serverless, options); + afterEach('CA File Cleanup', () => { + // delete files + fs.unlinkSync(file1); + fs.unlinkSync(file2); + // clear env + delete process.env.ca; + delete process.env.cafile; + }); - expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); - }); + it('should set AWS cafile single', () => { + process.env.cafile = file1; + const newAwsProvider = new AwsProvider(serverless, options); - it('should set AWS cafile multiple', () => { - process.env.cafile = `${file1},${file2}`; - const newAwsProvider = new AwsProvider(serverless, options); + expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); + }); - expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); - }); + it('should set AWS cafile multiple', () => { + process.env.cafile = `${file1},${file2}`; + const newAwsProvider = new AwsProvider(serverless, options); - it('should set AWS ca and cafile', () => { - process.env.ca = certContents; - process.env.cafile = file1; - const newAwsProvider = new AwsProvider(serverless, options); + expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); + }); - expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); + it('should set AWS ca and cafile', () => { + process.env.ca = certContents; + process.env.cafile = file1; + const newAwsProvider = new AwsProvider(serverless, options); + + expect(typeof newAwsProvider.sdk.config.httpOptions.agent).to.not.equal('undefined'); + }); }); - }); - describe('when checking for the deploymentBucket config', () => { - it('should do nothing if the deploymentBucket config is not used', () => { - serverless.service.provider.deploymentBucket = undefined; + describe('deploymentBucket configuration', () => { + it('should do nothing if not defined', () => { + serverless.service.provider.deploymentBucket = undefined; - const newAwsProvider = new AwsProvider(serverless, options); + const newAwsProvider = new AwsProvider(serverless, options); - expect(newAwsProvider.serverless.service.provider.deploymentBucket).to.equal(undefined); - }); + expect(newAwsProvider.serverless.service.provider.deploymentBucket).to.equal(undefined); + }); - it('should do nothing if the deploymentBucket config is a string', () => { - serverless.service.provider.deploymentBucket = 'my.deployment.bucket'; + it('should do nothing if the value is a string', () => { + serverless.service.provider.deploymentBucket = 'my.deployment.bucket'; - const newAwsProvider = new AwsProvider(serverless, options); + const newAwsProvider = new AwsProvider(serverless, options); - expect(newAwsProvider.serverless.service.provider.deploymentBucket) - .to.equal('my.deployment.bucket'); - }); + expect(newAwsProvider.serverless.service.provider.deploymentBucket) + .to.equal('my.deployment.bucket'); + }); - it('should save the object and use the name for the deploymentBucket if provided', () => { - const deploymentBucketObject = { - name: 'my.deployment.bucket', - serverSideEncryption: 'AES256', - }; - serverless.service.provider.deploymentBucket = deploymentBucketObject; + it('should save a given object and use name from it', () => { + const deploymentBucketObject = { + name: 'my.deployment.bucket', + serverSideEncryption: 'AES256', + }; + serverless.service.provider.deploymentBucket = deploymentBucketObject; - const newAwsProvider = new AwsProvider(serverless, options); + const newAwsProvider = new AwsProvider(serverless, options); - expect(newAwsProvider.serverless.service.provider.deploymentBucket) - .to.equal('my.deployment.bucket'); - expect(newAwsProvider.serverless.service.provider.deploymentBucketObject) - .to.deep.equal(deploymentBucketObject); - }); + expect(newAwsProvider.serverless.service.provider.deploymentBucket) + .to.equal('my.deployment.bucket'); + expect(newAwsProvider.serverless.service.provider.deploymentBucketObject) + .to.deep.equal(deploymentBucketObject); + }); - it('should save the object and nullify the name if it is not provided', () => { - const deploymentBucketObject = { - serverSideEncryption: 'AES256', - }; - serverless.service.provider.deploymentBucket = deploymentBucketObject; + it('should save a given object and nullify the name if one is not provided', () => { + const deploymentBucketObject = { + serverSideEncryption: 'AES256', + }; + serverless.service.provider.deploymentBucket = deploymentBucketObject; - const newAwsProvider = new AwsProvider(serverless, options); + const newAwsProvider = new AwsProvider(serverless, options); - expect(newAwsProvider.serverless.service.provider.deploymentBucket) - .to.equal(null); - expect(newAwsProvider.serverless.service.provider.deploymentBucketObject) - .to.deep.equal(deploymentBucketObject); + expect(newAwsProvider.serverless.service.provider.deploymentBucket) + .to.equal(null); + expect(newAwsProvider.serverless.service.provider.deploymentBucketObject) + .to.deep.equal(deploymentBucketObject); + }); }); }); @@ -230,27 +232,23 @@ describe('AwsProvider', () => { }); it('should retry if error code is 429', (done) => { - let first = true; const error = { statusCode: 429, + retryable: true, message: 'Testing retry', }; + const sendFake = { + send: sinon.stub(), + }; + sendFake.send.onFirstCall().yields(error); + sendFake.send.yields(undefined, {}); class FakeS3 { constructor(credentials) { this.credentials = credentials; } error() { - return { - send(cb) { - if (first) { - first = false; - cb(error); - } else { - cb(undefined, {}); - } - }, - }; + return sendFake; } } awsProvider.sdk = { @@ -258,8 +256,40 @@ describe('AwsProvider', () => { }; awsProvider.request('S3', 'error', {}) .then(data => { - expect(data).to.exist; // eslint-disable-line no-unused-expressions - expect(first).to.be.false; // eslint-disable-line no-unused-expressions + expect(data).to.exist; + expect(sendFake.send).to.have.been.calledTwice; + done(); + }) + .catch(done); + }); + + it('should retry if error code is 429 and retryable is set to false', (done) => { + const error = { + statusCode: 429, + retryable: false, + message: 'Testing retry', + }; + const sendFake = { + send: sinon.stub(), + }; + sendFake.send.onFirstCall().yields(error); + sendFake.send.yields(undefined, {}); + class FakeS3 { + constructor(credentials) { + this.credentials = credentials; + } + + error() { + return sendFake; + } + } + awsProvider.sdk = { + S3: FakeS3, + }; + awsProvider.request('S3', 'error', {}) + .then(data => { + expect(data).to.exist; + expect(sendFake.send).to.have.been.calledTwice; done(); }) .catch(done); @@ -321,6 +351,36 @@ describe('AwsProvider', () => { .catch(done); }); + it('should not retry for missing credentials', (done) => { + const error = { + statusCode: 403, + message: 'Missing credentials in config', + }; + const sendFake = { + send: sinon.stub().yields(error), + }; + class FakeS3 { + constructor(credentials) { + this.credentials = credentials; + } + + error() { + return sendFake; + } + } + awsProvider.sdk = { + S3: FakeS3, + }; + awsProvider.request('S3', 'error', {}) + .then(() => done('Should not succeed')) + .catch((err) => { + expect(sendFake.send).to.have.been.calledOnce; + expect(err.message).to.contain('in our docs here:'); + done(); + }) + .catch(done); + }); + it('should enable S3 acceleration if CLI option is provided', () => { // mocking S3 for testing class FakeS3 { @@ -393,9 +453,9 @@ describe('AwsProvider', () => { {}, { useCache: true } ) - .then(data => { - expect(data.called).to.equal(true); - }); + .then(data => { + expect(data.called).to.equal(true); + }); }); it('should resolve to the same response with mutiple parallel requests', () => { @@ -442,19 +502,19 @@ describe('AwsProvider', () => { } return BbPromise.all(requests) - .then(results => { - expect(_.size(results, numTests)); - _.forEach(results, result => { - expect(result).to.deep.equal(expectedResult); + .then(results => { + expect(_.size(results, numTests)); + _.forEach(results, result => { + expect(result).to.deep.equal(expectedResult); + }); + return BbPromise.join( + expect(sendStub).to.have.been.calledOnce, + expect(requestSpy).to.have.callCount(numTests) + ); + }) + .finally(() => { + requestSpy.restore(); }); - return BbPromise.join( - expect(sendStub).to.have.been.calledOnce, - expect(requestSpy).to.have.callCount(numTests) - ); - }) - .finally(() => { - requestSpy.restore(); - }); }); }); }); @@ -693,6 +753,69 @@ describe('AwsProvider', () => { }); }); + describe('values', () => { + const obj = { + a: 'b', + c: { + d: 'e', + f: { + g: 'h', + }, + }, + }; + const paths = [ + ['a'], + ['c', 'd'], + ['c', 'f', 'g'], + ]; + const getExpected = [ + { path: paths[0], value: obj.a }, + { path: paths[1], value: obj.c.d }, + { path: paths[2], value: obj.c.f.g }, + ]; + describe('#getValues', () => { + it('should return an array of values given paths to them', () => { + expect(awsProvider.getValues(obj, paths)).to.eql(getExpected); + }); + }); + describe('#firstValue', () => { + it('should ignore entries without a \'value\' attribute', () => { + const input = _.cloneDeep(getExpected); + delete input[0].value; + delete input[2].value; + expect(awsProvider.firstValue(input)).to.eql(getExpected[1]); + }); + it('should ignore entries with an undefined \'value\' attribute', () => { + const input = _.cloneDeep(getExpected); + input[0].value = undefined; + input[2].value = undefined; + expect(awsProvider.firstValue(input)).to.eql(getExpected[1]); + }); + it('should return the first value', () => { + expect(awsProvider.firstValue(getExpected)).to.equal(getExpected[0]); + }); + it('should return the middle value', () => { + const input = _.cloneDeep(getExpected); + delete input[0].value; + delete input[2].value; + expect(awsProvider.firstValue(input)).to.equal(input[1]); + }); + it('should return the last value', () => { + const input = _.cloneDeep(getExpected); + delete input[0].value; + delete input[1].value; + expect(awsProvider.firstValue(input)).to.equal(input[2]); + }); + it('should return the last object if none have valid values', () => { + const input = _.cloneDeep(getExpected); + delete input[0].value; + delete input[1].value; + delete input[2].value; + expect(awsProvider.firstValue(input)).to.equal(input[2]); + }); + }); + }); + describe('#getRegion()', () => { let newAwsProvider; @@ -835,6 +958,30 @@ describe('AwsProvider', () => { }); }); + describe('#getAccountInfo()', () => { + it('should return the AWS account id and partition', () => { + const accountId = '12345678'; + const partition = 'aws'; + + const stsGetCallerIdentityStub = sinon + .stub(awsProvider, 'request') + .resolves({ + ResponseMetadata: { RequestId: '12345678-1234-1234-1234-123456789012' }, + UserId: 'ABCDEFGHIJKLMNOPQRSTU:VWXYZ', + Account: accountId, + Arn: 'arn:aws:sts::123456789012:assumed-role/ROLE-NAME/VWXYZ', + }); + + return awsProvider.getAccountInfo() + .then((result) => { + expect(stsGetCallerIdentityStub.calledOnce).to.equal(true); + expect(result.accountId).to.equal(accountId); + expect(result.partition).to.equal(partition); + awsProvider.request.restore(); + }); + }); + }); + describe('#getAccountId()', () => { it('should return the AWS account id', () => { const accountId = '12345678'; diff --git a/lib/plugins/aws/utils/arnRegularExpressions.js b/lib/plugins/aws/utils/arnRegularExpressions.js new file mode 100644 index 00000000000..3f4f9467f78 --- /dev/null +++ b/lib/plugins/aws/utils/arnRegularExpressions.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = { + cognitoIdpArnExpr: /^arn:[a-zA-Z-]*:cognito-idp/, + lambdaArnExpr: /arn:[a-zA-Z-]*:lambda/, +}; diff --git a/lib/plugins/aws/utils/formatLambdaLogEvent.js b/lib/plugins/aws/utils/formatLambdaLogEvent.js index 2c2286e9b98..62efe8f644c 100644 --- a/lib/plugins/aws/utils/formatLambdaLogEvent.js +++ b/lib/plugins/aws/utils/formatLambdaLogEvent.js @@ -34,7 +34,7 @@ module.exports = (msgParam) => { } else if (!isNaN((new Date(splitted[1])).getTime())) { date = splitted[1]; reqId = splitted[2]; - level = `${chalk.white(splitted[0])}\t`; + level = `${splitted[0]}\t`; } else { return msg; } diff --git a/lib/plugins/aws/utils/formatLambdaLogEvent.test.js b/lib/plugins/aws/utils/formatLambdaLogEvent.test.js index b1ae167f2fa..a5d4639dfc4 100644 --- a/lib/plugins/aws/utils/formatLambdaLogEvent.test.js +++ b/lib/plugins/aws/utils/formatLambdaLogEvent.test.js @@ -37,7 +37,7 @@ describe('#formatLambdaLogEvent()', () => { const momentDate = moment('2016-01-01T12:00:00Z').format('YYYY-MM-DD HH:mm:ss.SSS (Z)'); expectedLogMessage += `${chalk.green(momentDate)}\t`; expectedLogMessage += `${chalk.yellow('99c30000-b01a-11e5-93f7-b8e85631a00e')}\t`; - expectedLogMessage += `${chalk.white('[INFO]')}\t`; + expectedLogMessage += `${'[INFO]'}\t`; expectedLogMessage += 'test'; expect(formatLambdaLogEvent(pythonLoggerLine)).to.equal(expectedLogMessage); diff --git a/lib/plugins/aws/utils/getS3EndpointForRegion.js b/lib/plugins/aws/utils/getS3EndpointForRegion.js new file mode 100644 index 00000000000..39392ff1099 --- /dev/null +++ b/lib/plugins/aws/utils/getS3EndpointForRegion.js @@ -0,0 +1,11 @@ +'use strict'; + +module.exports = function getS3EndpointForRegion(region) { + const strRegion = region.toLowerCase(); + // look for govcloud - currently s3-us-gov-west-1.amazonaws.com + if (strRegion.match(/us-gov/)) return `s3-${strRegion}.amazonaws.com`; + // look for china - currently s3.cn-north-1.amazonaws.com.cn + if (strRegion.match(/cn-/)) return `s3.${strRegion}.amazonaws.com.cn`; + // default s3 endpoint for other regions + return 's3.amazonaws.com'; +}; diff --git a/lib/plugins/aws/utils/getS3EndpointForRegion.test.js b/lib/plugins/aws/utils/getS3EndpointForRegion.test.js new file mode 100644 index 00000000000..e543fb59110 --- /dev/null +++ b/lib/plugins/aws/utils/getS3EndpointForRegion.test.js @@ -0,0 +1,21 @@ +'use strict'; +const expect = require('chai').expect; +const getS3EndpointForRegion = require('./getS3EndpointForRegion'); + +describe('getS3EndpointForRegion', () => { + it('should return standard endpoint for us-east-1', () => { + const expected = 's3.amazonaws.com'; + const actual = getS3EndpointForRegion('us-east-1'); + expect(actual).to.equal(expected); + }); + it('should return govcloud endpoint for us-gov-west-1', () => { + const expected = 's3-us-gov-west-1.amazonaws.com'; + const actual = getS3EndpointForRegion('us-gov-west-1'); + expect(actual).to.equal(expected); + }); + it('should return china endpoint for cn-north-1', () => { + const expected = 's3.cn-north-1.amazonaws.com.cn'; + const actual = getS3EndpointForRegion('cn-north-1'); + expect(actual).to.equal(expected); + }); +}); diff --git a/lib/plugins/create/create.js b/lib/plugins/create/create.js index 84368002a70..d1aeb1cbe2d 100644 --- a/lib/plugins/create/create.js +++ b/lib/plugins/create/create.js @@ -31,9 +31,12 @@ const validTemplates = [ 'aws-go', 'aws-go-dep', 'azure-nodejs', + 'fn-nodejs', + 'fn-go', 'google-nodejs', 'kubeless-python', 'kubeless-nodejs', + 'openwhisk-java-maven', 'openwhisk-nodejs', 'openwhisk-php', 'openwhisk-python', diff --git a/lib/plugins/create/create.test.js b/lib/plugins/create/create.test.js index c0bf9d726da..a6e10f2a1ca 100644 --- a/lib/plugins/create/create.test.js +++ b/lib/plugins/create/create.test.js @@ -143,7 +143,6 @@ describe('Create', () => { expect(dirContent).to.include('aws-csharp.csproj'); expect(dirContent).to.include('build.cmd'); expect(dirContent).to.include('build.sh'); - expect(dirContent).to.include('global.json'); }); }); @@ -159,7 +158,7 @@ describe('Create', () => { expect(dirContent).to.include('build.sh'); expect(dirContent).to.include('build.cmd'); expect(dirContent).to.include('aws-fsharp.fsproj'); - expect(dirContent).to.include('global.json'); + expect(dirContent).to.not.include('global.json'); }); }); @@ -197,7 +196,7 @@ describe('Create', () => { expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('pom.xml'); - expect(dirContent).to.include(path.join('src', 'main', 'resources', 'log4j.properties')); + expect(dirContent).to.include(path.join('src', 'main', 'resources', 'log4j2.xml')); expect(dirContent).to.include(path.join('src', 'main', 'java', 'com', 'serverless', 'Handler.java')); expect(dirContent).to.include(path.join('src', 'main', 'java', 'com', 'serverless', @@ -264,9 +263,9 @@ describe('Create', () => { expect(dirContent).to.include('gradlew.bat'); expect(dirContent).to.include('package.json'); expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.jar')); + 'gradle-wrapper.jar')); expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.properties')); + 'gradle-wrapper.properties')); expect(dirContent).to.include(path.join('src', 'main', 'kotlin', 'com', 'serverless', 'Handler.kt')); expect(dirContent).to.include(path.join('src', 'main', 'kotlin', 'com', 'serverless', @@ -291,17 +290,17 @@ describe('Create', () => { expect(dirContent).to.include('gradlew'); expect(dirContent).to.include('gradlew.bat'); expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.jar')); + 'gradle-wrapper.jar')); expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.properties')); + 'gradle-wrapper.properties')); expect(dirContent).to.include(path.join('src', 'main', 'resources', - 'log4j.properties')); + 'log4j.properties')); expect(dirContent).to.include(path.join('src', 'main', 'java', - 'com', 'serverless', 'Handler.java')); + 'com', 'serverless', 'Handler.java')); expect(dirContent).to.include(path.join('src', 'main', 'java', - 'com', 'serverless', 'ApiGatewayResponse.java')); + 'com', 'serverless', 'ApiGatewayResponse.java')); expect(dirContent).to.include(path.join('src', 'main', 'java', - 'com', 'serverless', 'Response.java')); + 'com', 'serverless', 'Response.java')); expect(dirContent).to.include(path.join('.gitignore')); }); }); @@ -319,17 +318,17 @@ describe('Create', () => { expect(dirContent).to.include('gradlew'); expect(dirContent).to.include('gradlew.bat'); expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.jar')); + 'gradle-wrapper.jar')); expect(dirContent).to.include(path.join('gradle', 'wrapper', - 'gradle-wrapper.properties')); + 'gradle-wrapper.properties')); expect(dirContent).to.include(path.join('src', 'main', 'resources', - 'log4j.properties')); + 'log4j.properties')); expect(dirContent).to.include(path.join('src', 'main', 'groovy', - 'com', 'serverless', 'Handler.groovy')); + 'com', 'serverless', 'Handler.groovy')); expect(dirContent).to.include(path.join('src', 'main', 'groovy', - 'com', 'serverless', 'ApiGatewayResponse.groovy')); + 'com', 'serverless', 'ApiGatewayResponse.groovy')); expect(dirContent).to.include(path.join('src', 'main', 'groovy', - 'com', 'serverless', 'Response.groovy')); + 'com', 'serverless', 'Response.groovy')); expect(dirContent).to.include('.gitignore'); }); }); @@ -354,6 +353,23 @@ describe('Create', () => { }); }); + it('should generate scaffolding for "openwhisk-java-maven" template', () => { + process.chdir(tmpDir); + create.options.template = 'openwhisk-java-maven'; + + return create.create().then(() => { + const dirContent = walkDirSync(tmpDir) + .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + expect(dirContent).to.include('pom.xml'); + expect(dirContent).to.include(path.join('src', 'main', 'java', + 'com', 'example', 'FunctionApp.java')); + expect(dirContent).to.include(path.join('src', 'test', 'java', + 'com', 'example', 'FunctionAppTest.java')); + expect(dirContent).to.include('.gitignore'); + expect(dirContent).to.include('serverless.yml'); + }); + }); + it('should generate scaffolding for "openwhisk-nodejs" template', () => { process.chdir(tmpDir); create.options.template = 'openwhisk-nodejs'; @@ -509,7 +525,7 @@ describe('Create', () => { expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include('pom.xml'); expect(dirContent).to.include(path.join('src', 'main', 'java', - 'com', 'serverless', 'Handler.java')); + 'com', 'serverless', 'Handler.java')); expect(dirContent).to.include('.gitignore'); }); }); @@ -527,6 +543,38 @@ describe('Create', () => { }); }); + it('should generate scaffolding for "fn-nodejs" template', () => { + process.chdir(tmpDir); + create.options.template = 'fn-nodejs'; + + return create.create().then(() => { + const dirContent = walkDirSync(tmpDir) + .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + expect(dirContent).to.include('package.json'); + expect(dirContent).to.include('serverless.yml'); + expect(dirContent).to.include('.gitignore'); + expect(dirContent).to.include(path.join('hello', 'func.js')); + expect(dirContent).to.include(path.join('hello', 'package.json')); + expect(dirContent).to.include(path.join('hello', 'test.json')); + }); + }); + + it('should generate scaffolding for "fn-go" template', () => { + process.chdir(tmpDir); + create.options.template = 'fn-go'; + + return create.create().then(() => { + const dirContent = walkDirSync(tmpDir) + .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + expect(dirContent).to.include('package.json'); + expect(dirContent).to.include('serverless.yml'); + expect(dirContent).to.include('.gitignore'); + expect(dirContent).to.include(path.join('hello', 'func.go')); + expect(dirContent).to.include(path.join('hello', 'Gopkg.toml')); + expect(dirContent).to.include(path.join('hello', 'test.json')); + }); + }); + it('should generate scaffolding for "plugin" template', () => { process.chdir(tmpDir); create.options.template = 'plugin'; @@ -608,7 +656,7 @@ describe('Create', () => { }); it('should create a custom renamed service in the directory if using ' + - 'the "path" and "name" option', () => { + 'the "path" and "name" option', () => { process.chdir(tmpDir); create.options.path = 'my-new-service'; @@ -640,7 +688,7 @@ describe('Create', () => { create.options.template = 'aws-nodejs'; create.options.path = ''; create.serverless.utils.copyDirContentsSync(path.join(create.serverless.config.serverlessPath, - 'plugins', 'create', 'templates', create.options.template), tmpDir); + 'plugins', 'create', 'templates', create.options.template), tmpDir); const dirContent = fs.readdirSync(tmpDir); @@ -749,13 +797,12 @@ describe('Create', () => { return create.create().then(() => { const dirContent = walkDirSync(tmpDir) - .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); + .map(elem => elem.replace(path.join(tmpDir, path.sep), '')); expect(dirContent).to.include('serverless.yml'); expect(dirContent).to.include(path.join('hello', 'main.go')); expect(dirContent).to.include(path.join('world', 'main.go')); expect(dirContent).to.include('Gopkg.toml'); - expect(dirContent).to.include('Gopkg.lock'); expect(dirContent).to.include('Makefile'); expect(dirContent).to.include('.gitignore'); }); diff --git a/lib/plugins/create/templates/aws-csharp/.vs/aws-csharp/v15/.suo b/lib/plugins/create/templates/aws-csharp/.vs/aws-csharp/v15/.suo deleted file mode 100644 index 9c228fb45c0..00000000000 Binary files a/lib/plugins/create/templates/aws-csharp/.vs/aws-csharp/v15/.suo and /dev/null differ diff --git a/lib/plugins/create/templates/aws-csharp/aws-csharp.csproj b/lib/plugins/create/templates/aws-csharp/aws-csharp.csproj index efa84bbf57f..9cedd48b6b0 100644 --- a/lib/plugins/create/templates/aws-csharp/aws-csharp.csproj +++ b/lib/plugins/create/templates/aws-csharp/aws-csharp.csproj @@ -1,7 +1,8 @@  - netcoreapp1.0 + netcoreapp2.0 + true CsharpHandlers aws-csharp @@ -12,7 +13,7 @@ - + diff --git a/lib/plugins/create/templates/aws-csharp/build.cmd b/lib/plugins/create/templates/aws-csharp/build.cmd index f33ae0254e9..9bd8efb21a6 100644 --- a/lib/plugins/create/templates/aws-csharp/build.cmd +++ b/lib/plugins/create/templates/aws-csharp/build.cmd @@ -1,2 +1,2 @@ dotnet restore -dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip \ No newline at end of file +dotnet lambda package --configuration release --framework netcoreapp2.0 --output-package bin/release/netcoreapp2.0/deploy-package.zip diff --git a/lib/plugins/create/templates/aws-csharp/build.sh b/lib/plugins/create/templates/aws-csharp/build.sh index 892b0f28986..f05233ca576 100755 --- a/lib/plugins/create/templates/aws-csharp/build.sh +++ b/lib/plugins/create/templates/aws-csharp/build.sh @@ -1,10 +1,11 @@ #!/bin/bash -#install zip -apt-get -qq update -apt-get -qq -y install zip +#install zip on debian OS, since microsoft/dotnet container doesn't have zip by default +if [ -f /etc/debian_version ] +then + apt -qq update + apt -qq -y install zip +fi dotnet restore - -#create deployment package -dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip +dotnet lambda package --configuration release --framework netcoreapp2.0 --output-package bin/release/netcoreapp2.0/deploy-package.zip diff --git a/lib/plugins/create/templates/aws-csharp/global.json b/lib/plugins/create/templates/aws-csharp/global.json deleted file mode 100644 index 8af244a4642..00000000000 --- a/lib/plugins/create/templates/aws-csharp/global.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "sdk": { - "version": "1.0.4" - } -} diff --git a/lib/plugins/create/templates/aws-csharp/serverless.yml b/lib/plugins/create/templates/aws-csharp/serverless.yml index fb55c7aacee..8f2c5f37033 100644 --- a/lib/plugins/create/templates/aws-csharp/serverless.yml +++ b/lib/plugins/create/templates/aws-csharp/serverless.yml @@ -19,7 +19,7 @@ service: aws-csharp # NOTE: update this with your service name provider: name: aws - runtime: dotnetcore1.0 + runtime: dotnetcore2.0 # you can overwrite defaults here # stage: dev @@ -47,7 +47,7 @@ provider: # you can add packaging information here package: - artifact: bin/release/netcoreapp1.0/deploy-package.zip + artifact: bin/release/netcoreapp2.0/deploy-package.zip # exclude: # - exclude-me.js # - exclude-me-dir/** @@ -67,7 +67,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-fsharp/aws-fsharp.fsproj b/lib/plugins/create/templates/aws-fsharp/aws-fsharp.fsproj index f3a1aafd44b..cb30c3303b2 100644 --- a/lib/plugins/create/templates/aws-fsharp/aws-fsharp.fsproj +++ b/lib/plugins/create/templates/aws-fsharp/aws-fsharp.fsproj @@ -1,7 +1,7 @@ - + - netcoreapp1.0 + netcoreapp2.0 FsharpHandlers aws-fsharp @@ -14,11 +14,10 @@ - - + diff --git a/lib/plugins/create/templates/aws-fsharp/build.cmd b/lib/plugins/create/templates/aws-fsharp/build.cmd index 468f8503abc..9bd8efb21a6 100644 --- a/lib/plugins/create/templates/aws-fsharp/build.cmd +++ b/lib/plugins/create/templates/aws-fsharp/build.cmd @@ -1,2 +1,2 @@ dotnet restore -dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip +dotnet lambda package --configuration release --framework netcoreapp2.0 --output-package bin/release/netcoreapp2.0/deploy-package.zip diff --git a/lib/plugins/create/templates/aws-fsharp/build.sh b/lib/plugins/create/templates/aws-fsharp/build.sh index f57aebe45af..f05233ca576 100644 --- a/lib/plugins/create/templates/aws-fsharp/build.sh +++ b/lib/plugins/create/templates/aws-fsharp/build.sh @@ -1,15 +1,11 @@ #!/bin/bash -isMacOs=`uname -a | grep Darwin` - -#install zip -if [ -z "$isMacOs" ] +#install zip on debian OS, since microsoft/dotnet container doesn't have zip by default +if [ -f /etc/debian_version ] then - apt-get -qq update - apt-get -qq -y install zip + apt -qq update + apt -qq -y install zip fi dotnet restore - -#create deployment package -dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip +dotnet lambda package --configuration release --framework netcoreapp2.0 --output-package bin/release/netcoreapp2.0/deploy-package.zip diff --git a/lib/plugins/create/templates/aws-fsharp/global.json b/lib/plugins/create/templates/aws-fsharp/global.json deleted file mode 100644 index 8af244a4642..00000000000 --- a/lib/plugins/create/templates/aws-fsharp/global.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "sdk": { - "version": "1.0.4" - } -} diff --git a/lib/plugins/create/templates/aws-fsharp/serverless.yml b/lib/plugins/create/templates/aws-fsharp/serverless.yml index 21dd15dd37d..e12d4231b3f 100644 --- a/lib/plugins/create/templates/aws-fsharp/serverless.yml +++ b/lib/plugins/create/templates/aws-fsharp/serverless.yml @@ -19,7 +19,7 @@ service: aws-fsharp # NOTE: update this with your service name provider: name: aws - runtime: dotnetcore1.0 + runtime: dotnetcore2.0 # you can overwrite defaults here # stage: dev @@ -47,7 +47,7 @@ provider: # you can add packaging information here package: - artifact: bin/release/netcoreapp1.0/deploy-package.zip + artifact: bin/release/netcoreapp2.0/deploy-package.zip # exclude: # - exclude-me.js # - exclude-me-dir/** @@ -67,7 +67,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-go-dep/Gopkg.lock b/lib/plugins/create/templates/aws-go-dep/Gopkg.lock deleted file mode 100644 index f93855f76bc..00000000000 --- a/lib/plugins/create/templates/aws-go-dep/Gopkg.lock +++ /dev/null @@ -1,19 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - name = "github.com/aws/aws-lambda-go" - packages = [ - "lambda", - "lambda/messages", - "lambdacontext" - ] - revision = "6e2e37798efbb1dfd8e9c6681702e683a6046517" - version = "v1.0.1" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - inputs-digest = "85fa166cc59d0fa113a1517ffbb5dee0f1fa4a6795239936afb18c64364af759" - solver-name = "gps-cdcl" - solver-version = 1 \ No newline at end of file diff --git a/lib/plugins/create/templates/aws-go-dep/Gopkg.toml b/lib/plugins/create/templates/aws-go-dep/Gopkg.toml index 8fce8929ead..a844086a94a 100644 --- a/lib/plugins/create/templates/aws-go-dep/Gopkg.toml +++ b/lib/plugins/create/templates/aws-go-dep/Gopkg.toml @@ -22,4 +22,4 @@ [[constraint]] name = "github.com/aws/aws-lambda-go" - version = "^1.0.1" + version = "1.x" diff --git a/lib/plugins/create/templates/aws-go-dep/serverless.yml b/lib/plugins/create/templates/aws-go-dep/serverless.yml index 67f92c77f2e..76819414366 100644 --- a/lib/plugins/create/templates/aws-go-dep/serverless.yml +++ b/lib/plugins/create/templates/aws-go-dep/serverless.yml @@ -69,7 +69,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-go/serverless.yml b/lib/plugins/create/templates/aws-go/serverless.yml index c3a1e2c95d4..47fd7378729 100644 --- a/lib/plugins/create/templates/aws-go/serverless.yml +++ b/lib/plugins/create/templates/aws-go/serverless.yml @@ -69,7 +69,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml index a1adaaf4d92..e7956282923 100644 --- a/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-groovy-gradle/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-java-gradle/serverless.yml b/lib/plugins/create/templates/aws-java-gradle/serverless.yml index e8811732e9e..c1079296fda 100644 --- a/lib/plugins/create/templates/aws-java-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-java-gradle/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-java-maven/pom.xml b/lib/plugins/create/templates/aws-java-maven/pom.xml index 4bafb38b4da..6c3af73ed4a 100644 --- a/lib/plugins/create/templates/aws-java-maven/pom.xml +++ b/lib/plugins/create/templates/aws-java-maven/pom.xml @@ -6,7 +6,7 @@ jar dev hello - + 1.8 1.8 @@ -16,13 +16,18 @@ com.amazonaws - aws-lambda-java-core + aws-lambda-java-log4j2 1.1.0 - com.amazonaws - aws-lambda-java-log4j - 1.0.0 + org.apache.logging.log4j + log4j-core + 2.8.2 + + + org.apache.logging.log4j + log4j-api + 2.8.2 com.fasterxml.jackson.core @@ -65,8 +70,22 @@ shade + + + + + + + + + com.github.edwgiz + maven-shade-plugin.log4j2-cachefile-transformer + 2.8.1 + + diff --git a/lib/plugins/create/templates/aws-java-maven/serverless.yml b/lib/plugins/create/templates/aws-java-maven/serverless.yml index 106bab8aaca..7f94d0131c5 100644 --- a/lib/plugins/create/templates/aws-java-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-java-maven/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/ApiGatewayResponse.java b/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/ApiGatewayResponse.java index cb25c5deaa1..083529604b2 100644 --- a/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/ApiGatewayResponse.java +++ b/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/ApiGatewayResponse.java @@ -5,7 +5,8 @@ import java.util.Collections; import java.util.Map; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -47,7 +48,7 @@ public static Builder builder() { public static class Builder { - private static final Logger LOG = Logger.getLogger(ApiGatewayResponse.Builder.class); + private static final Logger LOG = LogManager.getLogger(ApiGatewayResponse.Builder.class); private static final ObjectMapper objectMapper = new ObjectMapper(); diff --git a/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/Handler.java b/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/Handler.java index 6d6c670e5bd..fa7403ca05a 100644 --- a/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/Handler.java +++ b/lib/plugins/create/templates/aws-java-maven/src/main/java/com/serverless/Handler.java @@ -3,21 +3,19 @@ import java.util.Collections; import java.util.Map; -import org.apache.log4j.BasicConfigurator; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.RequestHandler; public class Handler implements RequestHandler, ApiGatewayResponse> { - private static final Logger LOG = Logger.getLogger(Handler.class); + private static final Logger LOG = LogManager.getLogger(Handler.class); @Override public ApiGatewayResponse handleRequest(Map input, Context context) { - BasicConfigurator.configure(); - - LOG.info("received: " + input); + LOG.info("received: {}", input); Response responseBody = new Response("Go Serverless v1.x! Your function executed successfully!", input); return ApiGatewayResponse.builder() .setStatusCode(200) diff --git a/lib/plugins/create/templates/aws-java-maven/src/main/resources/log4j.properties b/lib/plugins/create/templates/aws-java-maven/src/main/resources/log4j.properties deleted file mode 100644 index 63904cf8dc3..00000000000 --- a/lib/plugins/create/templates/aws-java-maven/src/main/resources/log4j.properties +++ /dev/null @@ -1,6 +0,0 @@ -log = . -log4j.rootLogger = DEBUG, LAMBDA - -log4j.appender.LAMBDA=com.amazonaws.services.lambda.runtime.log4j.LambdaAppender -log4j.appender.LAMBDA.layout=org.apache.log4j.PatternLayout -log4j.appender.LAMBDA.layout.conversionPattern=%d{yyyy-MM-dd HH:mm:ss} <%X{AWSRequestId}> %-5p %c:%L - %m%n diff --git a/lib/plugins/create/templates/aws-java-maven/src/main/resources/log4j2.xml b/lib/plugins/create/templates/aws-java-maven/src/main/resources/log4j2.xml new file mode 100644 index 00000000000..0970e0bd0c7 --- /dev/null +++ b/lib/plugins/create/templates/aws-java-maven/src/main/resources/log4j2.xml @@ -0,0 +1,15 @@ + + + + + + %d{yyyy-MM-dd HH:mm:ss} %X{AWSRequestId} %-5p %c{1}:%L - %m%n + + + + + + + + + diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml index fff1b55109b..434eb3d8155 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-gradle/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml index cb892140f01..1387dfc8a84 100644 --- a/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-jvm-maven/serverless.yml @@ -64,7 +64,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml index 5fd69aab122..bbe5cbed097 100644 --- a/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml +++ b/lib/plugins/create/templates/aws-kotlin-nodejs-gradle/serverless.yml @@ -60,7 +60,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/package.json b/lib/plugins/create/templates/aws-nodejs-typescript/package.json index 24d137ae93a..f6175222120 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/package.json +++ b/lib/plugins/create/templates/aws-nodejs-typescript/package.json @@ -6,14 +6,18 @@ "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, + "dependencies": { + "source-map-support": "^0.5.0" + }, "devDependencies": { - "@types/aws-lambda": "0.0.22", + "@types/aws-lambda": "8.10.1", "@types/node": "^8.0.57", - "serverless-webpack": "^4.0.0", - "ts-loader": "^2.3.7", - "typescript": "^2.5.2", - "webpack": "^3.6.0" + "serverless-webpack": "^5.1.1", + "ts-loader": "^4.2.0", + "typescript": "^2.8.1", + "webpack": "^4.5.0" }, - "author": "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", + "author": + "The serverless webpack authors (https://github.com/elastic-coders/serverless-webpack)", "license": "MIT" -} \ No newline at end of file +} diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml b/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml index bb8a2f56693..ef04a44b725 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs-typescript/serverless.yml @@ -7,7 +7,7 @@ plugins: provider: name: aws - runtime: nodejs6.10 + runtime: nodejs8.10 functions: hello: diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/source-map-install.js b/lib/plugins/create/templates/aws-nodejs-typescript/source-map-install.js new file mode 100644 index 00000000000..ef7457f72e7 --- /dev/null +++ b/lib/plugins/create/templates/aws-nodejs-typescript/source-map-install.js @@ -0,0 +1 @@ +require('source-map-support').install(); diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/tsconfig.json b/lib/plugins/create/templates/aws-nodejs-typescript/tsconfig.json index a98943cf604..20be6a1c6f9 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/tsconfig.json +++ b/lib/plugins/create/templates/aws-nodejs-typescript/tsconfig.json @@ -1,9 +1,13 @@ { "compilerOptions": { "sourceMap": true, + "target": "es6", "lib": [ - "es5", - "es2015.promise" - ] - } + "esnext" + ], + "moduleResolution": "node" + }, + "exclude": [ + "node_modules" + ] } diff --git a/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js b/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js index 41c1876cd1f..482b984316e 100644 --- a/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js +++ b/lib/plugins/create/templates/aws-nodejs-typescript/webpack.config.js @@ -1,17 +1,18 @@ const path = require('path'); const slsw = require('serverless-webpack'); +const entries = {}; + +Object.keys(slsw.lib.entries).forEach( + key => (entries[key] = ['./source-map-install.js', slsw.lib.entries[key]]) +); + module.exports = { - entry: slsw.lib.entries, + mode: slsw.lib.webpack.isLocal ? 'development' : 'production', + entry: entries, devtool: 'source-map', resolve: { - extensions: [ - '.js', - '.jsx', - '.json', - '.ts', - '.tsx' - ] + extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], }, output: { libraryTarget: 'commonjs', @@ -20,8 +21,9 @@ module.exports = { }, target: 'node', module: { - loaders: [ - { test: /\.ts(x?)$/, loader: 'ts-loader' }, + rules: [ + // all files with a `.ts` or `.tsx` extension will be handled by `ts-loader` + { test: /\.tsx?$/, loader: 'ts-loader' }, ], }, }; diff --git a/lib/plugins/create/templates/aws-nodejs/serverless.yml b/lib/plugins/create/templates/aws-nodejs/serverless.yml index 1cce386a131..471855a41df 100644 --- a/lib/plugins/create/templates/aws-nodejs/serverless.yml +++ b/lib/plugins/create/templates/aws-nodejs/serverless.yml @@ -69,7 +69,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-python/gitignore b/lib/plugins/create/templates/aws-python/gitignore index 84c61a91b7c..9e767198c29 100644 --- a/lib/plugins/create/templates/aws-python/gitignore +++ b/lib/plugins/create/templates/aws-python/gitignore @@ -1,5 +1,6 @@ # Distribution / packaging .Python +*.pyc env/ build/ develop-eggs/ @@ -17,4 +18,4 @@ var/ *.egg # Serverless directories -.serverless \ No newline at end of file +.serverless diff --git a/lib/plugins/create/templates/aws-python/serverless.yml b/lib/plugins/create/templates/aws-python/serverless.yml index f651608e047..2486ba648b2 100644 --- a/lib/plugins/create/templates/aws-python/serverless.yml +++ b/lib/plugins/create/templates/aws-python/serverless.yml @@ -69,7 +69,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-python3/serverless.yml b/lib/plugins/create/templates/aws-python3/serverless.yml index 1d69fcb9da4..3d77f19acd3 100644 --- a/lib/plugins/create/templates/aws-python3/serverless.yml +++ b/lib/plugins/create/templates/aws-python3/serverless.yml @@ -69,7 +69,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml index 7eb89a39da7..ee0e94d284d 100644 --- a/lib/plugins/create/templates/aws-scala-sbt/serverless.yml +++ b/lib/plugins/create/templates/aws-scala-sbt/serverless.yml @@ -66,7 +66,7 @@ functions: # - schedule: rate(10 minutes) # - sns: greeter-topic # - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000 -# - alexaSkill +# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx # - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx # - iot: # sql: "SELECT * FROM 'some_topic'" diff --git a/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Handler.scala b/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Handler.scala index c30742a9ab9..a4d9fbbc2eb 100644 --- a/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Handler.scala +++ b/lib/plugins/create/templates/aws-scala-sbt/src/main/scala/hello/Handler.scala @@ -5,9 +5,9 @@ import scala.collection.JavaConverters class Handler extends RequestHandler[Request, Response] { - def handleRequest(input: Request, context: Context): Response = { - Response("Go Serverless v1.0! Your function executed successfully!", input) - } + def handleRequest(input: Request, context: Context): Response = { + Response("Go Serverless v1.0! Your function executed successfully!", input) + } } class ApiGatewayHandler extends RequestHandler[Request, ApiGatewayResponse] { diff --git a/lib/plugins/create/templates/fn-go/.gitignore b/lib/plugins/create/templates/fn-go/.gitignore new file mode 100644 index 00000000000..f5b4c36adc3 --- /dev/null +++ b/lib/plugins/create/templates/fn-go/.gitignore @@ -0,0 +1,5 @@ +# Serverless directories +.serverless + +# golang output binary directory +bin \ No newline at end of file diff --git a/lib/plugins/create/templates/fn-go/hello/Gopkg.toml b/lib/plugins/create/templates/fn-go/hello/Gopkg.toml new file mode 100644 index 00000000000..c330ebe9435 --- /dev/null +++ b/lib/plugins/create/templates/fn-go/hello/Gopkg.toml @@ -0,0 +1,8 @@ + +[[constraint]] + branch = "master" + name = "github.com/fnproject/fdk-go" + +[prune] + go-tests = true + unused-packages = true diff --git a/lib/plugins/create/templates/fn-go/hello/func.go b/lib/plugins/create/templates/fn-go/hello/func.go new file mode 100644 index 00000000000..6cea350951f --- /dev/null +++ b/lib/plugins/create/templates/fn-go/hello/func.go @@ -0,0 +1,29 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "io" + + fdk "github.com/fnproject/fdk-go" +) + +func main() { + fdk.Handle(fdk.HandlerFunc(myHandler)) +} + +type Person struct { + Name string `json:"name"` +} + +func myHandler(ctx context.Context, in io.Reader, out io.Writer) { + p := &Person{Name: "World"} + json.NewDecoder(in).Decode(p) + msg := struct { + Msg string `json:"message"` + }{ + Msg: fmt.Sprintf("Hello %s", p.Name), + } + json.NewEncoder(out).Encode(&msg) +} diff --git a/lib/plugins/create/templates/fn-go/hello/test.json b/lib/plugins/create/templates/fn-go/hello/test.json new file mode 100644 index 00000000000..391d9b42fc0 --- /dev/null +++ b/lib/plugins/create/templates/fn-go/hello/test.json @@ -0,0 +1,26 @@ +{ + "tests": [ + { + "input": { + "body": { + "name": "Johnny" + } + }, + "output": { + "body": { + "message": "Hello Johnny" + } + } + }, + { + "input": { + "body": "" + }, + "output": { + "body": { + "message": "Hello World" + } + } + } + ] +} diff --git a/lib/plugins/create/templates/fn-go/package.json b/lib/plugins/create/templates/fn-go/package.json new file mode 100644 index 00000000000..a30c8806c06 --- /dev/null +++ b/lib/plugins/create/templates/fn-go/package.json @@ -0,0 +1,14 @@ +{ + "name": "fn-go", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "devDependencies": { + "serverless-fn": "0.0.1" + } +} diff --git a/lib/plugins/create/templates/fn-go/serverless.yml b/lib/plugins/create/templates/fn-go/serverless.yml new file mode 100644 index 00000000000..b338b2a6911 --- /dev/null +++ b/lib/plugins/create/templates/fn-go/serverless.yml @@ -0,0 +1,29 @@ +# Welcome to serverless. Read the fn provider docs +# https://serverless.com/framework/docs/providers/fn/ + +# Serverless.yml is the configuration the CLI +# uses to deploy your code to your provider of choice + +# The `service` block is the name of the service +service: + name: hello-world +# config: +# some: 'val' + +# The `provider` block defines where your service will be deployed +provider: + name: fn + +plugins: + - serverless-fn + +# The `functions` block defines what code to deploy +functions: + hello: # <- hello references the ./hello folder and the func.go file inside + name: hello + version: 0.0.1 + format: json + runtime: go + events: + - http: + path: /hellogo diff --git a/lib/plugins/create/templates/fn-nodejs/.gitignore b/lib/plugins/create/templates/fn-nodejs/.gitignore new file mode 100644 index 00000000000..f5b4c36adc3 --- /dev/null +++ b/lib/plugins/create/templates/fn-nodejs/.gitignore @@ -0,0 +1,5 @@ +# Serverless directories +.serverless + +# golang output binary directory +bin \ No newline at end of file diff --git a/lib/plugins/create/templates/fn-nodejs/hello/func.js b/lib/plugins/create/templates/fn-nodejs/hello/func.js new file mode 100644 index 00000000000..3d7d26bd771 --- /dev/null +++ b/lib/plugins/create/templates/fn-nodejs/hello/func.js @@ -0,0 +1,11 @@ +const fdk = require('@fnproject/fdk'); + +fdk.handle((input) => { + let name = 'World'; + if (input.name) { + name = input.name; + } + const response = { message: `Hello ${name}` }; + console.error(`I show up in the logs name was: ${name}`); + return response; +}); diff --git a/lib/plugins/create/templates/fn-nodejs/hello/package.json b/lib/plugins/create/templates/fn-nodejs/hello/package.json new file mode 100644 index 00000000000..cfd79b5388c --- /dev/null +++ b/lib/plugins/create/templates/fn-nodejs/hello/package.json @@ -0,0 +1,11 @@ +{ + "name": "hellofn", + "version": "1.0.0", + "description": "example function", + "main": "func.js", + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@fnproject/fdk": "0.x" + } +} diff --git a/lib/plugins/create/templates/fn-nodejs/hello/test.json b/lib/plugins/create/templates/fn-nodejs/hello/test.json new file mode 100644 index 00000000000..391d9b42fc0 --- /dev/null +++ b/lib/plugins/create/templates/fn-nodejs/hello/test.json @@ -0,0 +1,26 @@ +{ + "tests": [ + { + "input": { + "body": { + "name": "Johnny" + } + }, + "output": { + "body": { + "message": "Hello Johnny" + } + } + }, + { + "input": { + "body": "" + }, + "output": { + "body": { + "message": "Hello World" + } + } + } + ] +} diff --git a/lib/plugins/create/templates/fn-nodejs/package.json b/lib/plugins/create/templates/fn-nodejs/package.json new file mode 100644 index 00000000000..b3386411534 --- /dev/null +++ b/lib/plugins/create/templates/fn-nodejs/package.json @@ -0,0 +1,14 @@ +{ + "name": "fn-node", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "devDependencies": { + "serverless-fn": "0.0.1" + } +} diff --git a/lib/plugins/create/templates/fn-nodejs/serverless.yml b/lib/plugins/create/templates/fn-nodejs/serverless.yml new file mode 100644 index 00000000000..bd51faf0403 --- /dev/null +++ b/lib/plugins/create/templates/fn-nodejs/serverless.yml @@ -0,0 +1,33 @@ +# Welcome to serverless. Read the fn provider docs +# https://serverless.com/framework/docs/providers/fn/ + +# Serverless.yml is the configuration the CLI +# uses to deploy your code to your provider of choice + +# The `service` block is the name of the service +service: + name: hello-world +# config: +# some: 'val' + +# The `provider` block defines where your service will be deployed +provider: + name: fn + +plugins: + - serverless-fn + +# The `functions` block defines what code to deploy +functions: + hello: # <- hello references the ./hello folder and the func.js file inside + name: hello + version: 0.0.1 + idletimeout: 45 + format: json + memory: 256 +# config: +# another: value + runtime: node + events: + - http: + path: /hello diff --git a/lib/plugins/create/templates/kubeless-nodejs/handler.js b/lib/plugins/create/templates/kubeless-nodejs/handler.js index 1ed9667acc2..25977df7122 100644 --- a/lib/plugins/create/templates/kubeless-nodejs/handler.js +++ b/lib/plugins/create/templates/kubeless-nodejs/handler.js @@ -3,15 +3,7 @@ const _ = require('lodash'); module.exports = { - capitalize(req, res) { - let body = []; - req.on('error', (err) => { - console.error(err); - }).on('data', (chunk) => { - body.push(chunk); - }).on('end', () => { - body = Buffer.concat(body).toString(); - res.end(_.capitalize(body)); - }); + capitalize(event, context) { + return _.capitalize(event.data); }, }; diff --git a/lib/plugins/create/templates/kubeless-nodejs/package.json b/lib/plugins/create/templates/kubeless-nodejs/package.json index 039292997e2..b7c28e1b15e 100644 --- a/lib/plugins/create/templates/kubeless-nodejs/package.json +++ b/lib/plugins/create/templates/kubeless-nodejs/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Example function for serverless kubeless", "dependencies": { - "serverless-kubeless": "^0.3.1", + "serverless-kubeless": "^0.4.0", "lodash": "^4.1.0" }, "devDependencies": {}, diff --git a/lib/plugins/create/templates/kubeless-python/handler.py b/lib/plugins/create/templates/kubeless-python/handler.py index 84c7fd5254a..128656cc124 100644 --- a/lib/plugins/create/templates/kubeless-python/handler.py +++ b/lib/plugins/create/templates/kubeless-python/handler.py @@ -1,10 +1,10 @@ import json -def hello(request): +def hello(event, context): body = { "message": "Go Serverless v1.0! Your function executed successfully!", - "input": request.json + "input": event['data'] } response = { diff --git a/lib/plugins/create/templates/kubeless-python/package.json b/lib/plugins/create/templates/kubeless-python/package.json index 2eacb76727a..0d713104852 100644 --- a/lib/plugins/create/templates/kubeless-python/package.json +++ b/lib/plugins/create/templates/kubeless-python/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "Sample Kubeless Python serverless framework service.", "dependencies": { - "serverless-kubeless": "^0.3.1" + "serverless-kubeless": "^0.4.0" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" diff --git a/lib/plugins/create/templates/openwhisk-java-maven/.gitignore b/lib/plugins/create/templates/openwhisk-java-maven/.gitignore new file mode 100644 index 00000000000..186e57ceb43 --- /dev/null +++ b/lib/plugins/create/templates/openwhisk-java-maven/.gitignore @@ -0,0 +1,8 @@ +target +.vscode +.sts4-cache +.project +.classpath +.settings +*.iml +.idea diff --git a/lib/plugins/create/templates/openwhisk-java-maven/pom.xml b/lib/plugins/create/templates/openwhisk-java-maven/pom.xml new file mode 100644 index 00000000000..95c0ac0ff46 --- /dev/null +++ b/lib/plugins/create/templates/openwhisk-java-maven/pom.xml @@ -0,0 +1,47 @@ + + 4.0.0 + com.example + demo-function + 1.0-SNAPSHOT + https://openwhisk.apache.org/ + + UTF-8 + 1.8 + 1.8 + 2.8.2 + + + + com.google.code.gson + gson + ${gson.version} + + + junit + junit + 4.12 + test + + + + demo-function + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + package + + shade + + + + + + + diff --git a/lib/plugins/create/templates/openwhisk-java-maven/serverless.yml b/lib/plugins/create/templates/openwhisk-java-maven/serverless.yml new file mode 100644 index 00000000000..d3541cdf8c8 --- /dev/null +++ b/lib/plugins/create/templates/openwhisk-java-maven/serverless.yml @@ -0,0 +1,49 @@ +# Welcome to Serverless! +# +# This file is the main config file for your service. +# It's very minimal at this point and uses default values. +# You can always add more config options for more control. +# We've included some commented out config examples here. +# Just uncomment any of them to get that config option. +# +# For full config options, check the docs: +# docs.serverless.com +# +# Happy Coding! + +service: openwhisk-java-maven # NOTE: update this with your service name + +# Please ensure the serverless-openwhisk provider plugin is installed globally. +# $ npm install -g serverless-openwhisk +# ...before installing project dependencies to register this provider. +# $ npm install +provider: + name: openwhisk + runtime: java + +# you can add packaging information here +package: + artifact: target/demo-function.jar + +functions: + demo: + handler: com.example.FunctionApp + +# extend the framework using plugins listed here: +# https://github.com/serverless/plugins +plugins: + - "serverless-openwhisk" + +# you can define custom triggers and trigger feeds using the resources section. +# +#resources: +# triggers: +# my_trigger: +# parameters: +# hello: world +# alarm_trigger: +# parameters: +# hello: world +# feed: /whisk.system/alarms/alarm +# feed_parameters: +# cron: '*/8 * * * * *' diff --git a/lib/plugins/create/templates/openwhisk-java-maven/src/main/java/com/example/FunctionApp.java b/lib/plugins/create/templates/openwhisk-java-maven/src/main/java/com/example/FunctionApp.java new file mode 100644 index 00000000000..f216f79c9cd --- /dev/null +++ b/lib/plugins/create/templates/openwhisk-java-maven/src/main/java/com/example/FunctionApp.java @@ -0,0 +1,31 @@ +package com.example; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import com.google.gson.JsonObject; + +/** + * Hello FunctionApp + */ +public class FunctionApp { + public static JsonObject main(JsonObject args) { + JsonObject response = new JsonObject(); + response.addProperty("greetings", "Hello! Welcome to OpenWhisk"); + return response; + } +} diff --git a/lib/plugins/create/templates/openwhisk-java-maven/src/test/java/com/example/FunctionAppTest.java b/lib/plugins/create/templates/openwhisk-java-maven/src/test/java/com/example/FunctionAppTest.java new file mode 100644 index 00000000000..20229769ed1 --- /dev/null +++ b/lib/plugins/create/templates/openwhisk-java-maven/src/test/java/com/example/FunctionAppTest.java @@ -0,0 +1,39 @@ +package com.example; + +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import static org.junit.Assert.*; + +import com.google.gson.JsonObject; + +import org.junit.Test; + +/** + * Unit test for simple function. + */ +public class FunctionAppTest { + @Test + public void testFunction() { + JsonObject args = new JsonObject(); + JsonObject response = FunctionApp.main(args); + assertNotNull(response); + String greetings = response.getAsJsonPrimitive("greetings").getAsString(); + assertNotNull(greetings); + assertEquals("Hello! Welcome to OpenWhisk", greetings); + } +} diff --git a/lib/plugins/create/templates/openwhisk-nodejs/README.md b/lib/plugins/create/templates/openwhisk-nodejs/README.md index 2b0ef67a82d..6d3525c4d20 100644 --- a/lib/plugins/create/templates/openwhisk-nodejs/README.md +++ b/lib/plugins/create/templates/openwhisk-nodejs/README.md @@ -20,33 +20,14 @@ Account credentials for OpenWhisk can be provided through a configuration file o - *OW_APIHOST* - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` - *OW_AUTH* - Authentication key, e.g. `xxxxxx:yyyyy +### Have you installed the provider plugin? - -### Have you installed and setup the provider plugin? - -Using the framework with the OpenWhisk platform needs you to install the provider plugin and link this to your service. - -#### Install the provider plugin - -``` -$ npm install --global serverless-openwhisk -``` - -*Due to an [outstanding issue](https://github.com/serverless/serverless/issues/2895) with provider plugins, the [OpenWhisk provider](https://github.com/serverless/serverless-openwhisk) must be installed as a global module.* - - -#### Link provider plugin to service directory - -Using `npm link` will import the provider plugin into the service directory. Running `npm install` will automatically perform this using a `post install` script. +Install project dependencies which includes the OpenWhisk provider plugin. ``` -$ npm link serverless-openwhisk -or $ npm install ``` - - **_…and that's it!_** ### Deploy Service @@ -63,4 +44,4 @@ serverless deploy If you have any issues, comments or want to see new features, please file an issue in the project repository: -https://github.com/serverless/serverless-openwhisk \ No newline at end of file +https://github.com/serverless/serverless-openwhisk diff --git a/lib/plugins/create/templates/openwhisk-nodejs/package.json b/lib/plugins/create/templates/openwhisk-nodejs/package.json index 36e0bb93eea..efd8bebd841 100644 --- a/lib/plugins/create/templates/openwhisk-nodejs/package.json +++ b/lib/plugins/create/templates/openwhisk-nodejs/package.json @@ -3,11 +3,11 @@ "version": "1.0.0", "description": "Sample OpenWhisk NodeJS serverless framework service.", "main": "handler.js", - "scripts": { - "postinstall": "npm link serverless-openwhisk" - }, "keywords": [ "serverless", "openwhisk" - ] + ], + "devDependencies": { + "serverless-openwhisk": ">=0.13.0" + } } diff --git a/lib/plugins/create/templates/openwhisk-nodejs/serverless.yml b/lib/plugins/create/templates/openwhisk-nodejs/serverless.yml index 5c6a998c201..bf9f167aea5 100644 --- a/lib/plugins/create/templates/openwhisk-nodejs/serverless.yml +++ b/lib/plugins/create/templates/openwhisk-nodejs/serverless.yml @@ -13,55 +13,16 @@ service: openwhisk-nodejs # NOTE: update this with your service name -# Please ensure the serverless-openwhisk provider plugin is installed globally. -# $ npm install -g serverless-openwhisk -# ...before installing project dependencies to register this provider. +# Remember to install project dependencies to register the provider plugin. # $ npm install provider: name: openwhisk -# you can add packaging information here -#package: -# include: -# - include-me.js -# - include-me-dir/** -# exclude: -# - exclude-me.js -# - exclude-me-dir/** - functions: hello: handler: handler.hello -# Functions can be defined using sequences rather than referring -# to a handler. -# sequence: -# - parse_input -# - do_some_algorithm -# - construct_output - -# The following are a few example events you can configure -# Check the event documentation for details -# events: -# - http: GET /api/users/create -# - trigger: trigger_name - - # extend the framework using plugins listed here: # https://github.com/serverless/plugins plugins: - serverless-openwhisk - -# you can define custom triggers and trigger feeds using the resources section. -# -#resources: -# triggers: -# my_trigger: -# parameters: -# hello: world -# alarm_trigger: -# parameters: -# hello: world -# feed: /whisk.system/alarms/alarm -# feed_parameters: -# cron: '*/8 * * * * *' diff --git a/lib/plugins/create/templates/openwhisk-php/README.md b/lib/plugins/create/templates/openwhisk-php/README.md index 136e130f99f..853d4159028 100644 --- a/lib/plugins/create/templates/openwhisk-php/README.md +++ b/lib/plugins/create/templates/openwhisk-php/README.md @@ -20,33 +20,14 @@ Account credentials for OpenWhisk can be provided through a configuration file o - *OW_APIHOST* - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` - *OW_AUTH* - Authentication key, e.g. `xxxxxx:yyyyy +### Have you installed the provider plugin? - -### Have you installed and setup the provider plugin? - -Using the framework with the OpenWhisk platform needs you to install the provider plugin and link this to your service. - -#### Install the provider plugin - -``` -$ npm install --global serverless-openwhisk -``` - -*Due to an [outstanding issue](https://github.com/serverless/serverless/issues/2895) with provider plugins, the [OpenWhisk provider](https://github.com/serverless/serverless-openwhisk) must be installed as a global module.* - - -#### Link provider plugin to service directory - -Using `npm link` will import the provider plugin into the service directory. Running `npm install` will automatically perform this using a `post install` script. +Install project dependencies which includes the OpenWhisk provider plugin. ``` -$ npm link serverless-openwhisk -or $ npm install ``` - - **_…and that's it!_** ### Deploy Service diff --git a/lib/plugins/create/templates/openwhisk-php/package.json b/lib/plugins/create/templates/openwhisk-php/package.json index d2bd3aebfa7..a1376ec02fd 100644 --- a/lib/plugins/create/templates/openwhisk-php/package.json +++ b/lib/plugins/create/templates/openwhisk-php/package.json @@ -3,11 +3,11 @@ "version": "1.0.0", "description": "Sample OpenWhisk PHP serverless framework service.", "main": "handler.js", - "scripts": { - "postinstall": "npm link serverless-openwhisk" - }, "keywords": [ "serverless", "openwhisk" - ] + ], + "devDependencies": { + "serverless-openwhisk": ">=0.13.0" + } } diff --git a/lib/plugins/create/templates/openwhisk-php/serverless.yml b/lib/plugins/create/templates/openwhisk-php/serverless.yml index 544462bacfb..d830e3b28d8 100644 --- a/lib/plugins/create/templates/openwhisk-php/serverless.yml +++ b/lib/plugins/create/templates/openwhisk-php/serverless.yml @@ -13,56 +13,17 @@ service: openwhisk-php # NOTE: update this with your service name -# Please ensure the serverless-openwhisk provider plugin is installed globally. -# $ npm install -g serverless-openwhisk -# ...before installing project dependencies to register this provider. +# Remember to install project dependencies to register the provider plugin. # $ npm install provider: name: openwhisk runtime: php -# you can add packaging information here -#package: -# include: -# - include-me.js -# - include-me-dir/** -# exclude: -# - exclude-me.js -# - exclude-me-dir/** - functions: hello: handler: handler.hello -# Functions can be defined using sequences rather than referring -# to a handler. -# sequence: -# - parse_input -# - do_some_algorithm -# - construct_output - -# The following are a few example events you can configure -# Check the event documentation for details -# events: -# - http: GET /api/users/create -# - trigger: trigger_name - - # extend the framework using plugins listed here: # https://github.com/serverless/plugins plugins: - serverless-openwhisk - -# you can define custom triggers and trigger feeds using the resources section. -# -#resources: -# triggers: -# my_trigger: -# parameters: -# hello: world -# alarm_trigger: -# parameters: -# hello: world -# feed: /whisk.system/alarms/alarm -# feed_parameters: -# cron: '*/8 * * * * *' diff --git a/lib/plugins/create/templates/openwhisk-python/README.md b/lib/plugins/create/templates/openwhisk-python/README.md index 79627461e60..1b521d66183 100644 --- a/lib/plugins/create/templates/openwhisk-python/README.md +++ b/lib/plugins/create/templates/openwhisk-python/README.md @@ -20,33 +20,14 @@ Account credentials for OpenWhisk can be provided through a configuration file o - *OW_APIHOST* - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` - *OW_AUTH* - Authentication key, e.g. `xxxxxx:yyyyy +### Have you installed the provider plugin? - -### Have you installed and setup the provider plugin? - -Using the framework with the OpenWhisk platform needs you to install the provider plugin and link this to your service. - -#### Install the provider plugin - -``` -$ npm install --global serverless-openwhisk -``` - -*Due to an [outstanding issue](https://github.com/serverless/serverless/issues/2895) with provider plugins, the [OpenWhisk provider](https://github.com/serverless/serverless-openwhisk) must be installed as a global module.* - - -#### Link provider plugin to service directory - -Using `npm link` will import the provider plugin into the service directory. Running `npm install` will automatically perform this using a `post install` script. +Install project dependencies which includes the OpenWhisk provider plugin. ``` -$ npm link serverless-openwhisk -or $ npm install ``` - - **_…and that's it!_** ### Deploy Service diff --git a/lib/plugins/create/templates/openwhisk-python/package.json b/lib/plugins/create/templates/openwhisk-python/package.json index 2857776e39b..dc96b5be676 100644 --- a/lib/plugins/create/templates/openwhisk-python/package.json +++ b/lib/plugins/create/templates/openwhisk-python/package.json @@ -3,11 +3,11 @@ "version": "1.0.0", "description": "Sample OpenWhisk Python serverless framework service.", "main": "handler.js", - "scripts": { - "postinstall": "npm link serverless-openwhisk" - }, "keywords": [ "serverless", "openwhisk" - ] + ], + "devDependencies": { + "serverless-openwhisk": ">=0.13.0" + } } diff --git a/lib/plugins/create/templates/openwhisk-python/serverless.yml b/lib/plugins/create/templates/openwhisk-python/serverless.yml index c61e99eed7c..6124ffba322 100644 --- a/lib/plugins/create/templates/openwhisk-python/serverless.yml +++ b/lib/plugins/create/templates/openwhisk-python/serverless.yml @@ -13,56 +13,17 @@ service: openwhisk-python # NOTE: update this with your service name -# Please ensure the serverless-openwhisk provider plugin is installed globally. -# $ npm install -g serverless-openwhisk -# ...before installing project dependencies to register this provider. +# Remember to install project dependencies to register the provider plugin. # $ npm install provider: name: openwhisk runtime: python -# you can add packaging information here -#package: -# include: -# - include-me.js -# - include-me-dir/** -# exclude: -# - exclude-me.js -# - exclude-me-dir/** - functions: hello: handler: handler.hello -# Functions can be defined using sequences rather than referring -# to a handler. -# sequence: -# - parse_input -# - do_some_algorithm -# - construct_output - -# The following are a few example events you can configure -# Check the event documentation for details -# events: -# - http: GET /api/users/create -# - trigger: trigger_name - - # extend the framework using plugins listed here: # https://github.com/serverless/plugins plugins: - serverless-openwhisk - -# you can define custom triggers and trigger feeds using the resources section. -# -#resources: -# triggers: -# my_trigger: -# parameters: -# hello: world -# alarm_trigger: -# parameters: -# hello: world -# feed: /whisk.system/alarms/alarm -# feed_parameters: -# cron: '*/8 * * * * *' diff --git a/lib/plugins/create/templates/openwhisk-swift/README.md b/lib/plugins/create/templates/openwhisk-swift/README.md index ecc91088c04..ab7f9328b8d 100644 --- a/lib/plugins/create/templates/openwhisk-swift/README.md +++ b/lib/plugins/create/templates/openwhisk-swift/README.md @@ -20,33 +20,14 @@ Account credentials for OpenWhisk can be provided through a configuration file o - *OW_APIHOST* - Platform endpoint, e.g. `openwhisk.ng.bluemix.net` - *OW_AUTH* - Authentication key, e.g. `xxxxxx:yyyyy +### Have you installed the provider plugin? - -### Have you installed and setup the provider plugin? - -Using the framework with the OpenWhisk platform needs you to install the provider plugin and link this to your service. - -#### Install the provider plugin - -``` -$ npm install --global serverless-openwhisk -``` - -*Due to an [outstanding issue](https://github.com/serverless/serverless/issues/2895) with provider plugins, the [OpenWhisk provider](https://github.com/serverless/serverless-openwhisk) must be installed as a global module.* - - -#### Link provider plugin to service directory - -Using `npm link` will import the provider plugin into the service directory. Running `npm install` will automatically perform this using a `post install` script. +Install project dependencies which includes the OpenWhisk provider plugin. ``` -$ npm link serverless-openwhisk -or $ npm install ``` - - **_…and that's it!_** ### Deploy Service diff --git a/lib/plugins/create/templates/openwhisk-swift/package.json b/lib/plugins/create/templates/openwhisk-swift/package.json index d2cd8a50007..f20c557cdd5 100644 --- a/lib/plugins/create/templates/openwhisk-swift/package.json +++ b/lib/plugins/create/templates/openwhisk-swift/package.json @@ -3,11 +3,11 @@ "version": "1.0.0", "description": "Sample OpenWhisk Swift serverless framework service.", "main": "handler.js", - "scripts": { - "postinstall": "npm link serverless-openwhisk" - }, "keywords": [ "serverless", "openwhisk" - ] + ], + "devDependencies": { + "serverless-openwhisk": ">=0.13.0" + } } diff --git a/lib/plugins/create/templates/openwhisk-swift/serverless.yml b/lib/plugins/create/templates/openwhisk-swift/serverless.yml index 9f6f70f6972..2c1cad7fb6d 100644 --- a/lib/plugins/create/templates/openwhisk-swift/serverless.yml +++ b/lib/plugins/create/templates/openwhisk-swift/serverless.yml @@ -13,56 +13,17 @@ service: openwhisk-swift # NOTE: update this with your service name -# Please ensure the serverless-openwhisk provider plugin is installed globally. -# $ npm install -g serverless-openwhisk -# ...before installing project dependencies to register this provider. +# Remember to install project dependencies to register the provider plugin. # $ npm install provider: name: openwhisk runtime: swift -# you can add packaging information here -#package: -# include: -# - include-me.js -# - include-me-dir/** -# exclude: -# - exclude-me.js -# - exclude-me-dir/** - functions: hello: handler: ping.main -# Functions can be defined using sequences rather than referring -# to a handler. -# sequence: -# - parse_input -# - do_some_algorithm -# - construct_output - -# The following are a few example events you can configure -# Check the event documentation for details -# events: -# - http: GET /api/users/create -# - trigger: trigger_name - - # extend the framework using plugins listed here: # https://github.com/serverless/plugins plugins: - serverless-openwhisk - -# you can define custom triggers and trigger feeds using the resources section. -# -#resources: -# triggers: -# my_trigger: -# parameters: -# hello: world -# alarm_trigger: -# parameters: -# hello: world -# feed: /whisk.system/alarms/alarm -# feed_parameters: -# cron: '*/8 * * * * *' diff --git a/lib/plugins/create/templates/spotinst-java8/serverless.yml b/lib/plugins/create/templates/spotinst-java8/serverless.yml index 088814abc58..d6281ff8b85 100644 --- a/lib/plugins/create/templates/spotinst-java8/serverless.yml +++ b/lib/plugins/create/templates/spotinst-java8/serverless.yml @@ -15,9 +15,9 @@ service: spotinst-java8 provider: name: spotinst - #stage: #Optional setting. By default it is set to 'dev' + #stage: #Optional. Defaults to 'dev', see https://help.spotinst.com/hc/en-us/articles/115005893409 spotinst: - #environment: # NOTE: Remember to add the environment ID + environment: # Required. functions: hello: @@ -26,12 +26,21 @@ functions: memory: 128 timeout: 30 access: private -# cron: # Setup scheduled trigger with cron expression +# iamRoleConfig: +# roleId: # role-id +# activeVersions: +# - "version": "$LATEST" +# "percentage": 100.0 +# cors: +# enabled: # false by default +# origin: # '*' by default +# headers: # 'Content-Type,Authorization' by default +# methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default +# cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' -# environmentVariables: { -# Key: "Value", -# } +# environmentVariables: +# key: Value # extend the framework using plugins listed here: # https://github.com/serverless/plugins diff --git a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml index b0079fcde84..257f12de063 100644 --- a/lib/plugins/create/templates/spotinst-nodejs/serverless.yml +++ b/lib/plugins/create/templates/spotinst-nodejs/serverless.yml @@ -15,9 +15,9 @@ service: spotinst-nodejs # NOTE: update this with your service name provider: name: spotinst - #stage: #Optional setting. By default it is set to 'dev' + #stage: #Optional. Defaults to 'dev', see https://help.spotinst.com/hc/en-us/articles/115005893409 spotinst: - #environment: # NOTE: Remember to add the environment ID + environment: # Required. functions: hello: @@ -26,12 +26,21 @@ functions: memory: 128 timeout: 30 access: private -# cron: # Setup scheduled trigger with cron expression +# iamRoleConfig: +# roleId: # role-id +# activeVersions: +# - "version": "$LATEST" +# "percentage": 100.0 +# cors: +# enabled: # false by default +# origin: # '*' by default +# headers: # 'Content-Type,Authorization' by default +# methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default +# cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' -# environmentVariables: { -# Key: "Value", -# } +# environmentVariables: +# key: Value # extend the framework using plugins listed here: # https://github.com/serverless/plugins diff --git a/lib/plugins/create/templates/spotinst-python/serverless.yml b/lib/plugins/create/templates/spotinst-python/serverless.yml index a78c753b7ef..94ce1ee491c 100644 --- a/lib/plugins/create/templates/spotinst-python/serverless.yml +++ b/lib/plugins/create/templates/spotinst-python/serverless.yml @@ -15,9 +15,9 @@ service: spotinst-python # NOTE: update this with your service name provider: name: spotinst - #stage: #Optional setting. By default it is set to 'dev' + #stage: #Optional. Defaults to 'dev', see https://help.spotinst.com/hc/en-us/articles/115005893409 spotinst: - #environment: # NOTE: Remember to add the environment ID + environment: # Required. functions: hello: @@ -26,12 +26,21 @@ functions: memory: 128 timeout: 30 access: private -# cron: # Setup scheduled trigger with cron expression +# iamRoleConfig: +# roleId: # role-id +# activeVersions: +# - "version": "$LATEST" +# "percentage": 100.0 +# cors: +# enabled: # false by default +# origin: # '*' by default +# headers: # 'Content-Type,Authorization' by default +# methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default +# cron: # Setup scheduled trigger with cron expression # active: true # value: '* * * * *' -# environmentVariables: { -# Key: "Value", -# } +# environmentVariables: +# key: Value # extend the framework using plugins listed here: # https://github.com/serverless/plugins diff --git a/lib/plugins/create/templates/spotinst-ruby/serverless.yml b/lib/plugins/create/templates/spotinst-ruby/serverless.yml index 0a1f291023f..bd44647155d 100644 --- a/lib/plugins/create/templates/spotinst-ruby/serverless.yml +++ b/lib/plugins/create/templates/spotinst-ruby/serverless.yml @@ -15,9 +15,9 @@ service: spotinst-ruby # NOTE: update this with your service name provider: name: spotinst - #stage: #Optional setting. By default it is set to 'dev' + #stage: #Optional. Defaults to 'dev', see https://help.spotinst.com/hc/en-us/articles/115005893409 spotinst: - #environment: # NOTE: Remember to add the environment ID + environment: # Required. functions: hello: @@ -26,12 +26,21 @@ functions: memory: 128 timeout: 30 access: private +# iamRoleConfig: +# roleId: # role-id +# activeVersions: +# - "version": "$LATEST" +# "percentage": 100.0 +# cors: +# enabled: # false by default +# origin: # '*' by default +# headers: # 'Content-Type,Authorization' by default +# methods: # 'DELETE,GET,HEAD,OPTIONS,PATCH,POST,PUT' by default # cron: # Setup scheduled trigger with cron expression -# active: true -# value: '* * * * *' -# environmentVariables: { -# Key: "Value", -# } +# active: true +# value: '* * * * *' +# environmentVariables: +# key: Value # extend the framework using plugins listed here: # https://github.com/serverless/plugins diff --git a/lib/plugins/emit/index.js b/lib/plugins/emit/index.js index 89745a2414c..d5b846c966c 100644 --- a/lib/plugins/emit/index.js +++ b/lib/plugins/emit/index.js @@ -5,7 +5,7 @@ const BbPromise = require('bluebird'); const fdk = require('@serverless/fdk'); const path = require('path'); const stdin = require('get-stdin'); -const getAuthToken = require('../../utils/getAuthToken'); +const isLoggedIn = require('../../utils/isLoggedIn'); const userStats = require('../../utils/userStats'); const chalk = require('chalk'); @@ -120,8 +120,7 @@ class Emit { } emitEvent() { - const authToken = getAuthToken(); - if (!authToken) { + if (!isLoggedIn()) { return BbPromise.reject(new this.serverless.classes .Error('Must be logged in to use this command. Please run "serverless login".')); } diff --git a/lib/plugins/emit/index.test.js b/lib/plugins/emit/index.test.js index dd859cde396..92476f9f30e 100644 --- a/lib/plugins/emit/index.test.js +++ b/lib/plugins/emit/index.test.js @@ -162,7 +162,7 @@ describe('Emit', () => { emit: emitEventStub, }), }, - '../../utils/getAuthToken': () => 'abc123', + '../../utils/isLoggedIn': () => true, }); emit = new Emit(serverless); logStub = sinon.stub(emit.serverless.cli, 'consoleLog'); @@ -221,7 +221,7 @@ describe('Emit', () => { emit: emitEventStub, }), }, - '../../utils/getAuthToken': () => null, + '../../utils/isLoggedIn': () => false, }); emit = new Emit(serverless); }); diff --git a/lib/plugins/login/lib/decryptToken.js b/lib/plugins/login/lib/decryptToken.js deleted file mode 100644 index 17e0b00e59d..00000000000 --- a/lib/plugins/login/lib/decryptToken.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -const forge = require('node-forge'); - -module.exports = (encryptedToken, key, iv) => { - const decipherToken = forge.cipher.createDecipher('AES-CBC', key); - decipherToken.start({ iv }); - decipherToken.update(forge.util.createBuffer(forge.util.decode64(encryptedToken))); - const result = decipherToken.finish(); // check 'result' for true/false - if (!result) { - throw new Error(`Couldn't decrypt token: ${encryptedToken}`); - } - return decipherToken.output.toString(); -}; diff --git a/lib/plugins/login/lib/decryptToken.test.js b/lib/plugins/login/lib/decryptToken.test.js deleted file mode 100644 index a9b71cde1bc..00000000000 --- a/lib/plugins/login/lib/decryptToken.test.js +++ /dev/null @@ -1,23 +0,0 @@ -'use strict'; - -const expect = require('chai').expect; -const forge = require('node-forge'); -const decryptToken = require('./decryptToken'); - -const encryptAuthToken = (token, key, iv) => { - const cipher = forge.cipher.createCipher('AES-CBC', key); - cipher.start({ iv }); - cipher.update(forge.util.createBuffer(token)); - cipher.finish(); - return forge.util.encode64(cipher.output.data); -}; - -describe('#decryptToken()', () => { - it('should encrypt a token with AWS CBC', () => { - const token = 'f3120811-8306-4d67-98e5-13fde2e490e4'; - const key = forge.random.getBytesSync(16); - const iv = forge.random.getBytesSync(16); - const encryptedToken = encryptAuthToken(token, key, iv); - expect(decryptToken(encryptedToken, key, iv)).to.equal(token); - }); -}); diff --git a/lib/plugins/login/lib/getCliLoginById.js b/lib/plugins/login/lib/getCliLoginById.js deleted file mode 100644 index ae0104ba4b5..00000000000 --- a/lib/plugins/login/lib/getCliLoginById.js +++ /dev/null @@ -1,20 +0,0 @@ -'use strict'; - -const gql = require('graphql-tag'); - -module.exports = (id, apolloQueryFn) => - apolloQueryFn({ - fetchPolicy: 'network-only', - query: gql` - query cliLoginById($id: String!) { - cliLoginById(id: $id) { - encryptedAccessToken - encryptedIdToken - encryptedRefreshToken - encryptedKey - encryptedIv - } - } - `, - variables: { id }, - }).then(response => response.data); diff --git a/lib/plugins/login/lib/getCliLoginById.test.js b/lib/plugins/login/lib/getCliLoginById.test.js deleted file mode 100644 index 3c739bcf8f9..00000000000 --- a/lib/plugins/login/lib/getCliLoginById.test.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -const sinon = require('sinon'); -const expect = require('chai').expect; -const gql = require('graphql-tag'); -const getCliLoginById = require('./getCliLoginById'); - -describe('#getCliLoginById()', () => { - it('should query for the cliLoginById', () => { - const expectedParams = { - fetchPolicy: 'network-only', - query: gql` - query cliLoginById($id: String!) { - cliLoginById(id: $id) { - encryptedAccessToken - encryptedIdToken - encryptedRefreshToken - encryptedKey - encryptedIv - } - } - `, - variables: { id: 'abcd' }, - }; - - const query = sinon.stub().resolves({ data: { cliLoginId: 'abcd' } }); - return getCliLoginById('abcd', query).then(data => { - expect(data).to.deep.equal({ cliLoginId: 'abcd' }); - expect(query.getCall(0).args[0]).to.deep.equal(expectedParams); - }); - }); -}); diff --git a/lib/plugins/login/login.js b/lib/plugins/login/login.js index d1e6c1f2f23..4d1b98967d1 100644 --- a/lib/plugins/login/login.js +++ b/lib/plugins/login/login.js @@ -2,26 +2,9 @@ const BbPromise = require('bluebird'); const jwtDecode = require('jwt-decode'); -const chalk = require('chalk'); -const uuid = require('uuid'); -const has = require('lodash/has'); -const forge = require('node-forge'); -const querystring = require('querystring'); -const openBrowser = require('../../utils/openBrowser'); +const platform = require('@serverless/platform-sdk'); const configUtils = require('../../utils/config'); -const clearConsole = require('../../utils/clearConsole'); const userStats = require('../../utils/userStats'); -const setConfig = require('../../utils/config').set; -const createApolloClient = require('../../utils/createApolloClient'); -const decryptToken = require('./lib/decryptToken'); -const getCliLoginById = require('./lib/getCliLoginById'); - -const config = { - PLATFORM_FRONTEND_BASE_URL: 'https://platform.serverless.com/', - GRAPHQL_ENDPOINT_URL: 'https://graphql.serverless.com/graphql', -}; - -const client = createApolloClient(config.GRAPHQL_ENDPOINT_URL); class Login { constructor(serverless, options) { @@ -41,136 +24,52 @@ class Login { }; } login() { - clearConsole(); this.serverless.cli.log('The Serverless login will open in your default browser...'); const configuration = configUtils.getConfig(); const frameworkId = configuration.frameworkId; - const cliLoginId = uuid.v4(); - let fetchTries = 0; - - forge.pki.rsa.generateKeyPair({ bits: 2048 }, (generateKeyErr, keypair) => { - if (generateKeyErr) { - this.serverless.cli.log( - 'Sorry, failed initiating the authentication key. ', - 'Please contact the Serverless team at hello@serverless.com' - ); - throw generateKeyErr; - } - const publicKeyPem = forge.pki.publicKeyToPem(keypair.publicKey); - const encodedPublicKeyPem = forge.util.encode64(publicKeyPem); - - this.serverless.cli.log('Opening browser...'); - - // Avoid camel casing since queries is going into to be part of the URL - const queries = querystring.stringify({ - cli: 'v1', - 'login-id': cliLoginId, - 'public-key': encodedPublicKeyPem, - }); - // open default browser - openBrowser(`${config.PLATFORM_FRONTEND_BASE_URL}login?${queries}`); - const fetchCliLogin = () => { - fetchTries += 1; - // indicating the user after a while that the CLI is still waiting - if (fetchTries >= 60) { - this.serverless.cli.log('Waiting for a successful authentication in the browser.'); - fetchTries = 0; - } - getCliLoginById(cliLoginId, client.query) - .then(response => { - const hasAllToken = - has(response, ['cliLoginById', 'encryptedAccessToken']) && - has(response, ['cliLoginById', 'encryptedIdToken']) && - has(response, ['cliLoginById', 'encryptedRefreshToken']) && - has(response, ['cliLoginById', 'encryptedKey']) && - has(response, ['cliLoginById', 'encryptedIv']) && - response.cliLoginById.encryptedAccessToken !== null && - response.cliLoginById.encryptedIdToken !== null && - response.cliLoginById.encryptedRefreshToken !== null && - response.cliLoginById.encryptedKey !== null && - response.cliLoginById.encryptedIv !== null; - if (!hasAllToken) { - // delay the requests for a bit - setTimeout(() => fetchCliLogin(), 500); - } else { - const key = keypair.privateKey.decrypt( - forge.util.decode64(response.cliLoginById.encryptedKey), - 'RSA-OAEP' - ); - const iv = keypair.privateKey.decrypt( - forge.util.decode64(response.cliLoginById.encryptedIv), - 'RSA-OAEP' - ); - - const idToken = decryptToken(response.cliLoginById.encryptedIdToken, key, iv); - const accessToken = decryptToken(response.cliLoginById.encryptedAccessToken, key, iv); - const refreshToken = decryptToken( - response.cliLoginById.encryptedRefreshToken, - key, - iv - ); - - const decoded = jwtDecode(idToken); - this.serverless.cli.log('You are now logged in'); - // because platform only support github - const id = decoded.tracking_id || decoded.sub; - const userConfig = { - userId: id, - frameworkId, - users: {}, - }; - // set user auth in global .serverlessrc file - userConfig.users[id] = { - userId: id, - name: decoded.name, - email: decoded.email, - auth: { - id_token: idToken, - access_token: accessToken, - refresh_token: refreshToken, - }, - }; - - // update .serverlessrc - setConfig(userConfig); - - // identify user for better onboarding - userStats - .identify({ - id, - frameworkId, - email: decoded.email, - // unix timestamp - created_at: Math.round(+new Date(decoded.createdAt) / 1000), - trackingDisabled: configuration.trackingDisabled, - force: true, - }) - .then(() => { - userStats - .track('user_loggedIn', { - id, - email: decoded.email, - force: true, - }) - .then(() => { - // then exit process - process.exit(0); - }); - }); - } - }) - .catch(() => { - this.serverless.cli.consoleLog( - chalk.red('Sorry, something went wrong. Please run "serverless login" again.') - ); - throw new this.serverless.classes.Error( - 'Failed to login due to an error. Please try again or contact hello@serverless.com' - ); - }); + return platform.login().then(data => { + const decoded = jwtDecode(data.idToken); + // because platform only support github + const id = decoded.tracking_id || decoded.sub; + const userConfig = { + userId: id, + frameworkId, + users: {}, + }; + // set user auth in global .serverlessrc file + userConfig.users[id] = { + userId: id, + name: decoded.name, + email: decoded.email, + username: data.username, + dashboard: data, }; - fetchCliLogin(); + // update .serverlessrc + configUtils.set(userConfig); + + // identify user for better onboarding + userStats + .identify({ + id, + frameworkId, + email: decoded.email, + // unix timestamp + created_at: Math.round(+new Date(decoded.createdAt) / 1000), + trackingDisabled: configuration.trackingDisabled, + force: true, + }) + .then(() => { + userStats + .track('user_loggedIn', { + id, + email: decoded.email, + force: true, + }); + }); + this.serverless.cli.log('You are now logged in'); + process.exit(0); }); } } diff --git a/lib/plugins/logout/logout.js b/lib/plugins/logout/logout.js index 38066869477..e483d512ee8 100644 --- a/lib/plugins/logout/logout.js +++ b/lib/plugins/logout/logout.js @@ -30,9 +30,12 @@ class Logout { // that invalidate a refresh token in Auth0 (using the Auth0 Management API). if (globalConfig && globalConfig.users && globalConfig.users[currentId]) { - if (globalConfig.users[currentId].auth) { + if (globalConfig.users[currentId].dashboard + && globalConfig.users[currentId].dashboard.idToken) { // remove auth tokens from user - configUtils.set(`users.${currentId}.auth`, null); + configUtils.set(`users.${currentId}.dashboard.accessToken`, null); + configUtils.set(`users.${currentId}.dashboard.idToken`, null); + configUtils.set(`users.${currentId}.dashboard.expiresAt`, null); // log stat userStats.track('user_loggedOut').then(() => { this.serverless.cli.consoleLog('Successfully logged out.'); diff --git a/lib/plugins/package/lib/packageService.js b/lib/plugins/package/lib/packageService.js index 67a73208aca..b7008b88cde 100644 --- a/lib/plugins/package/lib/packageService.js +++ b/lib/plugins/package/lib/packageService.js @@ -26,9 +26,13 @@ module.exports = { getExcludes(exclude) { const packageExcludes = this.serverless.service.package.exclude || []; + // add local service plugins Path + const pluginsLocalPath = this.serverless.pluginManager + .parsePluginsObject(this.serverless.service.plugins).localPath; + const localPathExcludes = pluginsLocalPath ? [pluginsLocalPath] : []; // add defaults for exclude - return _.union(this.defaultExcludes, packageExcludes, exclude); + return _.union(this.defaultExcludes, localPathExcludes, packageExcludes, exclude); }, packageService() { diff --git a/lib/plugins/package/lib/packageService.test.js b/lib/plugins/package/lib/packageService.test.js index a625e0f3099..4420a1dcb59 100644 --- a/lib/plugins/package/lib/packageService.test.js +++ b/lib/plugins/package/lib/packageService.test.js @@ -1,5 +1,6 @@ 'use strict'; +const _ = require('lodash'); const BbPromise = require('bluebird'); const path = require('path'); const chai = require('chai'); @@ -71,41 +72,61 @@ describe('#packageService()', () => { expect(exclude).to.deep.equal(packagePlugin.defaultExcludes); }); - it('should merge defaults with excludes', () => { + it('should exclude plugins localPath defaults', () => { + const localPath = './myplugins'; + serverless.service.plugins = { localPath }; + + const exclude = packagePlugin.getExcludes(); + expect(exclude).to.deep.equal(_.union(packagePlugin.defaultExcludes, [localPath])); + }); + + it('should not exclude plugins localPath if it is empty', () => { + const localPath = ''; + serverless.service.plugins = { localPath }; + + const exclude = packagePlugin.getExcludes(); + expect(exclude).to.deep.equal(_.union(packagePlugin.defaultExcludes)); + }); + + it('should not exclude plugins localPath if it is not a string', () => { + const localPath = {}; + serverless.service.plugins = { localPath }; + + const exclude = packagePlugin.getExcludes(); + expect(exclude).to.deep.equal(_.union(packagePlugin.defaultExcludes)); + }); + + it('should merge defaults with plugin localPath and excludes', () => { + const localPath = './myplugins'; + serverless.service.plugins = { localPath }; + const packageExcludes = [ 'dir', 'file.js', ]; - serverless.service.package.exclude = packageExcludes; + const exclude = packagePlugin.getExcludes(); - expect(exclude).to.deep.equal([ - '.git/**', '.gitignore', '.DS_Store', - 'npm-debug.log', 'serverless.yml', - 'serverless.yaml', 'serverless.json', 'serverless.js', - '.serverless/**', '.serverless_plugins/**', - 'dir', 'file.js', - ]); + expect(exclude).to.deep.equal(_.union(packagePlugin.defaultExcludes, + [localPath], packageExcludes)); }); - it('should merge defaults with package and func excludes', () => { - const funcExcludes = [ - 'lib', 'other.js', - ]; + it('should merge defaults with plugin localPath package and func excludes', () => { + const localPath = './myplugins'; + serverless.service.plugins = { localPath }; + const packageExcludes = [ 'dir', 'file.js', ]; - serverless.service.package.exclude = packageExcludes; + const funcExcludes = [ + 'lib', 'other.js', + ]; + const exclude = packagePlugin.getExcludes(funcExcludes); - expect(exclude).to.deep.equal([ - '.git/**', '.gitignore', '.DS_Store', - 'npm-debug.log', 'serverless.yml', - 'serverless.yaml', 'serverless.json', 'serverless.js', - '.serverless/**', '.serverless_plugins/**', - 'dir', 'file.js', 'lib', 'other.js', - ]); + expect(exclude).to.deep.equal(_.union(packagePlugin.defaultExcludes, + [localPath], packageExcludes, funcExcludes)); }); }); diff --git a/lib/plugins/platform/platform.js b/lib/plugins/platform/platform.js index 9710a8afaaf..85ba651be68 100644 --- a/lib/plugins/platform/platform.js +++ b/lib/plugins/platform/platform.js @@ -2,208 +2,244 @@ /* eslint-disable no-console */ +const BbPromise = require('bluebird'); const path = require('path'); const fs = require('fs'); -const gql = require('graphql-tag'); -const jwtDecode = require('jwt-decode'); -const BbPromise = require('bluebird'); const fsExtra = require('../../utils/fs/fse'); -const fetch = require('node-fetch'); -const chalk = require('chalk'); -const functionInfoUtils = require('../../utils/functionInfoUtils'); -const createApolloClient = require('../../utils/createApolloClient'); -const getAuthToken = require('../../utils/getAuthToken'); -const selectServicePublish = require('../../utils/selectors/selectServicePublish'); - -// NOTE Needed for apollo to work -global.fetch = fetch; - -const config = { - PLATFORM_FRONTEND_BASE_URL: 'https://platform.serverless.com/', - GRAPHQL_ENDPOINT_URL: 'https://graphql.serverless.com/graphql', -}; - -function addReadme(attributes, readmePath) { - if (fs.existsSync(readmePath)) { - const readmeContent = fsExtra.readFileSync(readmePath).toString('utf8'); - // eslint-disable-next-line no-param-reassign - attributes.readme = readmeContent; - } - return attributes; -} - -function fetchEndpoint(provider) { - return provider - .request( - 'CloudFormation', - 'describeStacks', - { StackName: provider.naming.getStackName() }, - { useCache: true } // Use request cache - ) - .then(result => { - let endpoint = null; - - if (result) { - result.Stacks[0].Outputs - .filter(x => x.OutputKey.match(provider.naming.getServiceEndpointRegex())) - .forEach(x => { - endpoint = x.OutputValue; - }); - } - - return endpoint; - }); -} +const crypto = require('crypto'); +const platform = require('@serverless/platform-sdk'); +const getAccessKey = require('../../utils/getAccessKey'); +const isLoggedIn = require('../../utils/isLoggedIn'); class Platform { constructor(serverless, options) { this.serverless = serverless; this.options = options; this.provider = this.serverless.getProvider('aws'); + this.config = { + app: this.serverless.service.app, + tenant: this.serverless.service.tenant, + }; + // NOTE for the time being we only track services published to AWS if (this.provider) { this.hooks = { - 'after:deploy:deploy': this.publishService.bind(this), + 'after:deploy:finalize': this.publishService.bind(this), 'after:remove:remove': this.archiveService.bind(this), }; } } - archiveServiceRequest(name, client) { - return client.mutate({ - mutation: gql` - mutation archiveService($name: String!) { - archiveService(name: $name) { - archived - } - } - `, - variables: { name }, - }); - } - - archiveService() { - const authToken = this.getAuthToken(); - const publishFlag = selectServicePublish(this.serverless.service); - if (!authToken || !publishFlag) { - // NOTE archiveService is an opt-in feature and no warning is needed + publishService() { + if (!isLoggedIn()) { return BbPromise.resolve(); } - const clientWithAuth = createApolloClient(config.GRAPHQL_ENDPOINT_URL, authToken); - return this.archiveServiceRequest(this.serverless.service.service, clientWithAuth) - .then(response => { - this.serverless.cli.log('Successfully archived your service on the Serverless Platform'); - return response.data; - }) - .catch(err => { - this.serverless.cli.log('Failed to archived your service on the Serverless Platform'); - throw new this.serverless.classes.Error(err.message); - }); - } + return getAccessKey(this.config.tenant).then(accessKey => { + if (!accessKey || !this.config.app || !this.config.tenant) { + return BbPromise.resolve(); + } + this.serverless.cli.log('Publishing service to Serverless Platform...'); + + return this.provider.getAccountId().then(accountId => { + const service = this.serverless.service; + + const data = { + app: this.config.app, + tenant: this.config.tenant, + accessKey, + version: '0.1.0', + service: this.getServiceData(), + functions: [], + subscriptions: [], + }; - publishServiceRequest(service, client) { - return client - .mutate({ - mutation: gql` - mutation publishService($service: ServicePublishInputType!) { - publishService(service: $service) { - name + data.service.provider.accountId = accountId; + + Object.keys(service.functions).forEach(fn => { + const fnData = { + functionId: this.serverless.service.getFunction(fn).name, + details: { + runtime: service.functions[fn].runtime, + memory: service.functions[fn].memory, + timeout: service.functions[fn].timeout, + }, + package: { + handler: service.functions[fn].handler, + name: service.functions[fn].name, + arn: `arn:aws:lambda:${data.service.provider.region}:${ + data.service.provider.accountId}:function:${ + this.serverless.service.getFunction(fn).name}`, + }, + }; + data.functions.push(fnData); + this.serverless.service.getAllEventsInFunction(fn).forEach(event => { + const subscription = { + functionId: this.serverless.service.getFunction(fn).name, + }; + + if (Object.keys(event)[0] === 'eventgateway') { + if (event.eventgateway.event === 'http') { + subscription.type = 'com.serverless.http'; + } else { + subscription.type = event.eventgateway.event; + } + subscription.event = event.eventgateway; + } else if (Object.keys(event)[0] === 'http') { + subscription.type = 'aws.apigateway.http'; + subscription.event = event.http; + } else if (Object.keys(event)[0] === 'stream') { + if (typeof event.stream === 'string') { + const streamType = event.stream.split(':')[2]; + subscription.type = `aws.${streamType}`; + } else if (typeof event.stream === 'object') { + if (event.stream.type === 'dynamodb') { + subscription.type = 'aws.dynamodb'; + } else if (event.stream.type === 'kinesis') { + subscription.type = 'aws.kinesis'; + } + } + subscription.event = event.stream; + } else if (Object.keys(event)[0] === 's3') { + if (typeof event.s3 === 'string') { + subscription.type = 'aws.s3.ObjectCreated'; + } else if (typeof event.s3 === 'object') { + subscription.type = this.getS3Type(event.s3.event); + } + subscription.event = event.s3; + } else if (Object.keys(event)[0] === 'schedule') { + subscription.type = 'aws.cloudwatch.scheduled'; + subscription.event = event.schedule; + } else if (Object.keys(event)[0] === 'sns') { + subscription.type = 'aws.sns'; + subscription.event = event.sns; + } else if (Object.keys(event)[0] === 'alexaSkill') { + subscription.type = 'aws.alexa.skill'; + subscription.event = event.alexaSkill; + } else if (Object.keys(event)[0] === 'iot') { + subscription.type = 'aws.iot'; + subscription.event = event.iot; + } else if (Object.keys(event)[0] === 'cloudwatchEvent') { + subscription.type = 'aws.cloudwatch'; + subscription.event = event.cloudwatchEvent; + } else if (Object.keys(event)[0] === 'cloudwatchLog') { + subscription.type = 'aws.cloudwatch.log'; + subscription.event = event.cloudwatchLog; + } else if (Object.keys(event)[0] === 'cognitoUserPool') { + subscription.type = 'aws.cognito'; + subscription.event = event.cognitoUserPool; + } else if (Object.keys(event)[0] === 'alexaSmartHome') { + subscription.type = 'aws.alexa.home'; + subscription.event = event.alexaSmartHome; } - } - `, - variables: { service }, - }) - .then(response => response.data); + + subscription.subscriptionId = crypto.createHash('md5') + .update(JSON.stringify(subscription)).digest('hex'); + data.subscriptions.push(subscription); + }); + }); + + return platform.publishService(data) + .then((serviceUrl) => { + this.serverless.cli + .log('Successfully published your service on the Serverless Platform'); + this.serverless.cli.log(`Service URL: ${serviceUrl}`); + }) + .catch(() => { + this.serverless.cli.log('Failed to publish your service on the Serverless Platform'); + }); + }); + }); } - getAuthToken() { - return getAuthToken(); + getReadme() { + const readmePath = path.join(this.serverless.config.servicePath, 'README.md'); + if (fs.existsSync(readmePath)) { + return fsExtra.readFileSync(readmePath).toString('utf8'); + } + return null; } - publishService() { - const authToken = this.getAuthToken(); - const publishFlag = selectServicePublish(this.serverless.service); - if (!authToken || !publishFlag) { - // NOTE publishService is an opt-in feature and no warning is needed - return BbPromise.resolve(); + getS3Type(s3Event) { + const splittedS3Event = s3Event.split(':'); + if (splittedS3Event[1] === 'ReducedRedundancyLostObject') { + return 'aws.s3.ReducedRedundancyLostObject'; + } else if (splittedS3Event[1] === 'ObjectCreated' || splittedS3Event[1] === 'ObjectRemoved') { + if (splittedS3Event[2] === '*') { + return `aws.s3.${splittedS3Event[1]}`; + } else if (typeof splittedS3Event[2] === 'string') { + return `aws.s3.${splittedS3Event[1]}.${splittedS3Event[2]}`; + } } + return `aws.s3.${splittedS3Event[1]}`; + } - this.serverless.cli.log('Publish service to Serverless Platform...'); + getServiceData() { + const serviceData = { + name: this.serverless.service.service, + stage: this.serverless.processedInput.options.stage + || this.serverless.service.provider.stage, + provider: { + name: this.serverless.service.provider.name, + region: this.serverless.service.provider.region, + accountId: this.serverless.service.provider.accountId, + }, + pluginsData: this.serverless.service.pluginsData, + readme: this.getReadme(), + }; + if (this.serverless.service.serviceObject.description) { + serviceData.description = this.serverless.service.serviceObject.description; + } + if (this.serverless.service.serviceObject.license) { + serviceData.license = this.serverless.service.serviceObject.license; + } + if (this.serverless.service.serviceObject.bugs) { + serviceData.bugs = this.serverless.service.serviceObject.bugs; + } + if (this.serverless.service.serviceObject.repository) { + serviceData.repository = this.serverless.service.serviceObject.repository; + } + if (this.serverless.service.serviceObject.homepage) { + serviceData.homepage = this.serverless.service.serviceObject.homepage; + } + return serviceData; + } - const clientWithAuth = createApolloClient(config.GRAPHQL_ENDPOINT_URL, authToken); + archiveService() { + if (!isLoggedIn()) { + return BbPromise.resolve(); + } - const region = this.provider.getRegion(); + if (!this.config.tenant && !this.config.app) { + this.serverless.cli.log('WARNING: Missing "tenant" and "app" properties in serverless.yml'); + } else if (this.config.tenant && !this.config.app) { + const errorMessage = ['Missing "app" property in serverless.yml'].join(''); + throw new this.serverless.classes.Error(errorMessage); + } else if (!this.config.tenant && this.config.app) { + const errorMessage = ['Missing "tenant" property in serverless.yml'].join(''); + throw new this.serverless.classes.Error(errorMessage); + } - return this.provider.getAccountId().then(accountId => - fetchEndpoint(this.provider).then(endpoint => { - const funcs = this.serverless.service.getAllFunctions().map(key => { - const arnName = functionInfoUtils.aws.getArnName(key, this.serverless); - let funcAttributes = { - name: key, - runtime: functionInfoUtils.aws.getRuntime(key, this.serverless), - memory: functionInfoUtils.aws.getMemorySize(key, this.serverless), - timeout: functionInfoUtils.aws.getTimeout(key, this.serverless), - provider: this.serverless.service.provider.name, - originId: `arn:aws:lambda:${region}:${accountId}:function:${arnName}`, - endpoints: functionInfoUtils.aws.getEndpoints(key, this.serverless, endpoint), - }; - if (this.serverless.service.functions[key].readme) { - funcAttributes = addReadme( - funcAttributes, - this.serverless.service.functions[key].readme - ); - } - if (this.serverless.service.functions[key].description) { - funcAttributes.description = this.serverless.service.functions[key].description; - } - return funcAttributes; - }); - const serviceData = { - name: this.serverless.service.service, - stage: this.serverless.processedInput.options.stage, - functions: funcs, - }; - if (this.serverless.service.serviceObject.description) { - serviceData.description = this.serverless.service.serviceObject.description; - } - if (this.serverless.service.serviceObject.license) { - serviceData.license = this.serverless.service.serviceObject.license; - } - if (this.serverless.service.serviceObject.bugs) { - serviceData.bugs = this.serverless.service.serviceObject.bugs; - } - if (this.serverless.service.serviceObject.repository) { - serviceData.repository = this.serverless.service.serviceObject.repository; - } - if (this.serverless.service.serviceObject.homepage) { - serviceData.homepage = this.serverless.service.serviceObject.homepage; - } - - // NOTE can be improved by making sure it captures multiple variations - // of readme file name e.g. readme.md, readme.txt, Readme.md - const readmePath = path.join(this.serverless.config.servicePath, 'README.md'); - const serviceDataWithReadme = addReadme(serviceData, readmePath); - - return new BbPromise((resolve, reject) => { - this.publishServiceRequest(serviceDataWithReadme, clientWithAuth) - .then(() => { - const username = jwtDecode(authToken).nickname; - const serviceName = this.serverless.service.service; - const url = `${config.PLATFORM_FRONTEND_BASE_URL}services/${username}/${serviceName}`; - console.log('Service successfully published! Your service details are available at:'); - console.log(chalk.green(url)); - resolve(); - }) - .catch(error => { - this.serverless.cli.log( - "Couldn't publish this deploy information to the Serverless Platform." - ); - reject(error); - }); + return getAccessKey(this.config.tenant).then(accessKey => { + if (!accessKey || !this.config.app || !this.config.tenant) { + return BbPromise.resolve(); + } + const data = { + name: this.serverless.service.service, + tenant: this.config.tenant, + app: this.config.app, + accessKey, + }; + return platform.archiveService(data) + .then(() => { + this.serverless.cli.log('Successfully archived your service on the Serverless Platform'); + // process.exit(0); + }) + .catch(err => { + this.serverless.cli.log('Failed to archived your service on the Serverless Platform'); + throw new this.serverless.classes.Error(err.message); }); - }) - ); + }); } } diff --git a/lib/plugins/platform/platform.test.js b/lib/plugins/platform/platform.test.js deleted file mode 100644 index 41cadaa9dec..00000000000 --- a/lib/plugins/platform/platform.test.js +++ /dev/null @@ -1,235 +0,0 @@ -'use strict'; - -/* eslint-disable no-console */ - -const expect = require('chai').expect; -const sinon = require('sinon'); -const chalk = require('chalk'); -const Platform = require('./platform'); -const Serverless = require('../../Serverless'); -const AwsProvider = require('../aws/provider/awsProvider'); - -describe('Platform', () => { - let serverless; - let platform; - - beforeEach(() => { - serverless = new Serverless(); - serverless.init(); - serverless.setProvider('aws', new AwsProvider(serverless)); - platform = new Platform(serverless); - }); - - describe('#constructor()', () => { - it('should have access to the serverless instance', () => { - expect(platform.serverless).to.deep.equal(serverless); - }); - - it('should have a hook after the deploy', () => { - expect(platform.hooks).to.have.property('after:deploy:deploy'); - }); - - it('should set the provider variable to an instance of AwsProvider', () => { - expect(platform.provider).to.be.instanceof(AwsProvider); - }); - }); - - describe('#archiveService()', () => { - let getAuthTokenStub; - let archiveServiceRequestStub; - let logStub; - - beforeEach(() => { - archiveServiceRequestStub = sinon - .stub(platform, 'archiveServiceRequest') - .resolves({ data: {} }); - getAuthTokenStub = sinon.stub(platform, 'getAuthToken'); - logStub = sinon.stub(platform.serverless.cli, 'log'); - }); - - afterEach(() => { - platform.archiveServiceRequest.restore(); - platform.getAuthToken.restore(); - }); - - it('should skip archiving if user opted out via "publish" config', () => { - getAuthTokenStub.returns( - // eslint-disable-next-line max-len - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0OTc4ODMwMzMsImV4cCI6MTUyOTQxOTAzMywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIm5pY2tuYW1lIjoiam9obmRvZSJ9.GD6sqQR3qLirnrvLKKrmOc7vgsHpqZ3TPwyG8ZI69ig' - ); - platform.serverless.service.service = 'new-service-3'; - platform.serverless.service.serviceObject = { - name: 'new-service-3', - publish: false, - }; - - return platform.archiveService().then(() => { - expect(getAuthTokenStub.calledOnce).to.be.equal(true); - expect(archiveServiceRequestStub.calledOnce).to.be.equal(false); - }); - }); - - it('should skip archiving if user is not logged in', () => { - getAuthTokenStub.returns(undefined); - - platform.serverless.service.service = 'new-service-3'; - platform.serverless.service.serviceObject = { - name: 'new-service-3', - }; - - return platform.archiveService().then(() => { - expect(getAuthTokenStub.calledOnce).to.be.equal(true); - expect(archiveServiceRequestStub.calledOnce).to.be.equal(false); - }); - }); - - it('should archiving', () => { - getAuthTokenStub.returns( - // eslint-disable-next-line max-len - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0OTc4ODMwMzMsImV4cCI6MTUyOTQxOTAzMywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIm5pY2tuYW1lIjoiam9obmRvZSJ9.GD6sqQR3qLirnrvLKKrmOc7vgsHpqZ3TPwyG8ZI69ig' - ); - platform.serverless.service.service = 'new-service-3'; - platform.serverless.service.serviceObject = { - name: 'new-service-3', - }; - - return platform.archiveService().then(() => { - expect(getAuthTokenStub.calledOnce).to.be.equal(true); - expect(archiveServiceRequestStub.calledOnce).to.be.equal(true); - expect( - logStub.calledWithExactly('Successfully archived your service on the Serverless Platform') - ).to.be.equal(true); - }); - }); - }); - - describe('#publishService()', () => { - let getAuthTokenStub; - let getAccountIdStub; - let endpointsRequestStub; - let publishServiceRequestStub; - - beforeEach(() => { - getAuthTokenStub = sinon.stub(platform, 'getAuthToken').returns( - // eslint-disable-next-line max-len - 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE0OTc4ODMwMzMsImV4cCI6MTUyOTQxOTAzMywiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIm5pY2tuYW1lIjoiam9obmRvZSJ9.GD6sqQR3qLirnrvLKKrmOc7vgsHpqZ3TPwyG8ZI69ig' - ); - getAccountIdStub = sinon.stub(platform.provider, 'getAccountId').resolves('acountId123'); - endpointsRequestStub = sinon.stub(platform.provider, 'request').resolves({ - Stacks: [ - { - Outputs: [{ OutputKey: 'ServiceEndpoint', OutputValue: 'http://service-endpoint' }], - }, - ], - }); - publishServiceRequestStub = sinon.stub(platform, 'publishServiceRequest').resolves(); - sinon.spy(console, 'log'); - }); - - afterEach(() => { - platform.getAuthToken.restore(); - platform.provider.getAccountId.restore(); - platform.provider.request.restore(); - platform.publishServiceRequest.restore(); - console.log.restore(); - }); - - it('should send a minimal service request to the platform', () => { - platform.serverless.service.service = 'new-service-2'; - platform.serverless.service.serviceObject = { - name: 'new-service-2', - }; - platform.serverless.config.servicePath = '/path/to/service'; - - return platform.publishService().then(() => { - expect(getAuthTokenStub.calledOnce).to.be.equal(true); - expect(getAccountIdStub.calledOnce).to.be.equal(true); - expect(endpointsRequestStub.calledOnce).to.be.equal(true); - expect(publishServiceRequestStub.calledOnce).to.be.equal(true); - const expected = { name: 'new-service-2', stage: undefined, functions: [] }; - expect(publishServiceRequestStub.getCall(0).args[0]).to.deep.equal(expected); - const url = chalk.green('https://platform.serverless.com/services/johndoe/new-service-2'); - const successLog = 'Service successfully published! Your service details are available at:'; - expect(console.log.calledWithExactly(successLog)).to.be.equal(true); - expect(console.log.calledWithExactly(url)).to.be.equal(true); - }); - }); - - it('should send a full service request to the platform', () => { - platform.serverless.service.service = 'new-service-2'; - platform.serverless.service.serviceObject = { - name: 'new-service-2', - description: 'test description', - repository: 'https://example.com/repo', - homepage: 'https://example.com', - bugs: 'https://example.com/bugs', - license: 'MIT', - }; - platform.serverless.config.servicePath = '/path/to/service'; - platform.serverless.service.provider.name = 'aws'; - platform.serverless.service.functions = { - hello: { - handler: 'handler.hello', - description: 'test desc', - events: [{ http: { path: 'users/create', method: 'get', integration: 'AWS_PROXY' } }], - name: 'test-service2-dev-hello', - package: {}, - vpc: {}, - }, - }; - - return platform.publishService().then(() => { - expect(getAuthTokenStub.calledOnce).to.be.equal(true); - expect(getAccountIdStub.calledOnce).to.be.equal(true); - expect(endpointsRequestStub.calledOnce).to.be.equal(true); - expect(publishServiceRequestStub.calledOnce).to.be.equal(true); - const expected = { - name: 'new-service-2', - stage: undefined, - repository: 'https://example.com/repo', - bugs: 'https://example.com/bugs', - description: 'test description', - functions: [ - { - description: 'test desc', - endpoints: [ - { - method: 'GET', - url: 'http://service-endpoint/users/create', - }, - ], - memory: 1024, - name: 'hello', - originId: 'arn:aws:lambda:us-east-1:acountId123:function:test-service2-dev-hello', - provider: 'aws', - runtime: 'nodejs4.3', - timeout: 6, - }, - ], - homepage: 'https://example.com', - license: 'MIT', - }; - expect(publishServiceRequestStub.getCall(0).args[0]).to.deep.equal(expected); - const url = chalk.green('https://platform.serverless.com/services/johndoe/new-service-2'); - const successLog = 'Service successfully published! Your service details are available at:'; - expect(console.log.calledWithExactly(successLog)).to.be.equal(true); - expect(console.log.calledWithExactly(url)).to.be.equal(true); - }); - }); - - it('should skip publishing if user opted out via "publish" config', () => { - platform.serverless.service.service = 'new-service-2'; - platform.serverless.service.serviceObject = { - name: 'new-service-2', - publish: false, - }; - - return platform.publishService().then(() => { - expect(getAuthTokenStub.calledOnce).to.be.equal(true); - expect(getAccountIdStub.calledOnce).to.be.equal(false); - expect(endpointsRequestStub.calledOnce).to.be.equal(false); - expect(publishServiceRequestStub.calledOnce).to.be.equal(false); - }); - }); - }); -}); diff --git a/lib/plugins/plugin/install/install.js b/lib/plugins/plugin/install/install.js index c688128bba8..65116941149 100644 --- a/lib/plugins/plugin/install/install.js +++ b/lib/plugins/plugin/install/install.js @@ -117,19 +117,40 @@ class PluginInstall { return BbPromise.resolve(); } + const checkIsArrayPluginsObject = (pluginsObject) => + _.isNil(pluginsObject) || _.isArray(pluginsObject); + // pluginsObject type determined based on the value loaded during the serverless init. if (_.last(_.split(serverlessFilePath, '.')) === 'json') { return fse.readJsonAsync(serverlessFilePath).then(serverlessFileObj => { const newServerlessFileObj = serverlessFileObj; - if (newServerlessFileObj.plugins) { - newServerlessFileObj.plugins.push(this.options.pluginName); + const isArrayPluginsObject = checkIsArrayPluginsObject(newServerlessFileObj.plugins); + // null modules property is not supported + let plugins = isArrayPluginsObject ? newServerlessFileObj.plugins || [] : + newServerlessFileObj.plugins.modules; + + if (_.isNil(plugins)) { + throw new Error('plugins modules property must be present'); + } + + plugins.push(this.options.pluginName); + plugins = _.sortedUniq(plugins); + + if (isArrayPluginsObject) { + newServerlessFileObj.plugins = plugins; } else { - newServerlessFileObj.plugins = [this.options.pluginName]; + newServerlessFileObj.plugins.modules = plugins; } - newServerlessFileObj.plugins = _.sortedUniq(newServerlessFileObj.plugins); + return fse.writeJsonAsync(serverlessFilePath, newServerlessFileObj); }); } - return yamlAstParser.addNewArrayItem(serverlessFilePath, 'plugins', this.options.pluginName); + + return this.serverless.yamlParser + .parse(serverlessFilePath) + .then((serverlessFileObj) => + yamlAstParser.addNewArrayItem(serverlessFilePath, + checkIsArrayPluginsObject(serverlessFileObj.plugins) ? + 'plugins' : 'plugins.modules', this.options.pluginName)); }); } diff --git a/lib/plugins/plugin/install/install.test.js b/lib/plugins/plugin/install/install.test.js index 396dd603a16..7bdf1d17b3d 100644 --- a/lib/plugins/plugin/install/install.test.js +++ b/lib/plugins/plugin/install/install.test.js @@ -14,13 +14,13 @@ const CLI = require('../../../classes/CLI'); const testUtils = require('../../../../tests/utils'); chai.use(require('chai-as-promised')); const expect = require('chai').expect; +const _ = require('lodash'); describe('PluginInstall', () => { let pluginInstall; let serverless; let consoleLogStub; let serverlessErrorStub; - const plugins = [ { name: 'serverless-plugin-1', @@ -253,14 +253,16 @@ describe('PluginInstall', () => { serverless.utils .writeFileSync(packageJsonFilePath, packageJson); - return expect(pluginInstall.pluginInstall()).to.be.fulfilled.then(() => { - expect(consoleLogStub.called).to.equal(true); - expect(npmInstallStub.calledWithExactly( - 'npm install --save-dev serverless-plugin-1@latest', - { stdio: 'ignore' } - )).to.equal(true); - expect(serverlessErrorStub.calledOnce).to.equal(false); - }); + return expect(pluginInstall.pluginInstall()).to.be.fulfilled.then(() => + Promise.all([ + expect(consoleLogStub.called).to.equal(true), + expect(npmInstallStub.calledWithExactly( + 'npm install --save-dev serverless-plugin-1@latest', + { stdio: 'ignore' } + )).to.equal(true), + expect(serverlessErrorStub.calledOnce).to.equal(false), + ]) + ); }); it('should generate a package.json file in the service directory if not present', @@ -298,8 +300,10 @@ describe('PluginInstall', () => { pluginInstall.options.pluginName = 'serverless-plugin-1'; return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8').plugins) - .to.deep.equal(['serverless-plugin-1']); + expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8')) + .to.deep.equal(_.assign({}, serverlessYml, { + plugins: ['serverless-plugin-1'], + })); }); }); @@ -316,8 +320,10 @@ describe('PluginInstall', () => { pluginInstall.options.pluginName = 'serverless-plugin-1'; return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8').plugins) - .to.deep.equal(['serverless-existing-plugin', 'serverless-plugin-1']); + expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8')) + .to.deep.equal(_.assign({}, serverlessYml, { + plugins: ['serverless-existing-plugin', 'serverless-plugin-1'], + })); }); }); @@ -331,8 +337,8 @@ describe('PluginInstall', () => { .writeFileSync(serverlessYamlFilePath, YAML.dump(serverlessYml)); pluginInstall.options.pluginName = 'serverless-plugin-1'; return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessYamlFilePath, 'utf8').plugins) - .to.deep.equal(['serverless-plugin-1']); + expect(serverless.utils.readFileSync(serverlessYamlFilePath, 'utf8')) + .to.deep.equal(_.assign({}, serverlessYml, { plugins: ['serverless-plugin-1'] })); }); }); @@ -346,16 +352,19 @@ describe('PluginInstall', () => { .writeFileSync(serverlessJsonFilePath, serverlessJson); pluginInstall.options.pluginName = 'serverless-plugin-1'; return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8').plugins) - .to.deep.equal(['serverless-plugin-1']); + expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')) + .to.deep.equal(_.assign({}, serverlessJson, { plugins: ['serverless-plugin-1'] })); }) - .then(() => { - pluginInstall.options.pluginName = 'serverless-plugin-2'; - return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { - expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8').plugins) - .to.deep.equal(['serverless-plugin-1', 'serverless-plugin-2']); + .then(() => { + pluginInstall.options.pluginName = 'serverless-plugin-2'; + return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { + expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')) + .to.deep.equal(_.assign({}, serverlessJson, + { + plugins: ['serverless-plugin-1', 'serverless-plugin-2'], + })); + }); }); - }); }); it('should not modify serverless .js file', () => { @@ -375,6 +384,70 @@ describe('PluginInstall', () => { .to.be.deep.equal([]); }); }); + + describe('if plugins object is not array', () => { + it('should add the plugin to the service file', () => { + // serverless.yml + const serverlessYml = { + service: 'plugin-service', + provider: 'aws', + plugins: { + localPath: 'test', + modules: [], + }, + }; + serverless.utils + .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + + pluginInstall.options.pluginName = 'serverless-plugin-1'; + + return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { + expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8')) + .to.deep.equal(_.assign({}, serverlessYml, { + plugins: { + localPath: 'test', + modules: [pluginInstall.options.pluginName], + }, + })); + }); + }); + + it('should add the plugin to serverless file path for a .json file', () => { + const serverlessJsonFilePath = path.join(servicePath, 'serverless.json'); + const serverlessJson = { + service: 'plugin-service', + provider: 'aws', + plugins: { + localPath: 'test', + modules: [], + }, + }; + serverless.utils + .writeFileSync(serverlessJsonFilePath, serverlessJson); + pluginInstall.options.pluginName = 'serverless-plugin-1'; + return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { + expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')) + .to.deep.equal(_.assign({}, serverlessJson, { + plugins: { + localPath: 'test', + modules: [pluginInstall.options.pluginName], + }, + })); + }) + .then(() => { + pluginInstall.options.pluginName = 'serverless-plugin-2'; + return expect(pluginInstall.addPluginToServerlessFile()).to.be.fulfilled.then(() => { + expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')) + .to.deep.equal(_.assign({}, serverlessJson, { + plugins: { + localPath: 'test', + modules: ['serverless-plugin-1', 'serverless-plugin-2'], + }, + })); + }); + }); + }); + }); }); describe('#installPeerDependencies()', () => { diff --git a/lib/plugins/plugin/uninstall/uninstall.js b/lib/plugins/plugin/uninstall/uninstall.js index 05580a98ab8..4d241d2e99f 100644 --- a/lib/plugins/plugin/uninstall/uninstall.js +++ b/lib/plugins/plugin/uninstall/uninstall.js @@ -88,17 +88,32 @@ class PluginUninstall { if (_.last(_.split(serverlessFilePath, '.')) === 'json') { return fse.readJsonAsync(serverlessFilePath).then(serverlessFileObj => { - if (serverlessFileObj.plugins) { - _.pull(serverlessFileObj.plugins, this.options.pluginName); - if (_.isEmpty(serverlessFileObj.plugins)) { - _.unset(serverlessFileObj, 'plugins'); + const isArrayPluginsObject = _.isArray(serverlessFileObj.plugins); + const plugins = isArrayPluginsObject ? + serverlessFileObj.plugins : + serverlessFileObj.plugins && serverlessFileObj.plugins.modules; + + if (plugins) { + _.pull(plugins, this.options.pluginName); + if (_.isEmpty(plugins)) { + if (isArrayPluginsObject) { + _.unset(serverlessFileObj, 'plugins'); + } else { + _.unset(serverlessFileObj.plugins, 'modules'); + } } + return fse.writeJsonAsync(serverlessFilePath, serverlessFileObj); } - return fse.writeJsonAsync(serverlessFilePath, serverlessFileObj); + return BbPromise.resolve(); }); } - return yamlAstParser.removeExistingArrayItem( - serverlessFilePath, 'plugins', this.options.pluginName); + + return this.serverless.yamlParser + .parse(serverlessFilePath) + .then((serverlessFileObj) => + yamlAstParser.removeExistingArrayItem( + serverlessFilePath, _.isArray(serverlessFileObj.plugins) ? + 'plugins' : 'plugins.modules', this.options.pluginName)); }); } diff --git a/lib/plugins/plugin/uninstall/uninstall.test.js b/lib/plugins/plugin/uninstall/uninstall.test.js index a119fbbc0ea..8f75cd91ce7 100644 --- a/lib/plugins/plugin/uninstall/uninstall.test.js +++ b/lib/plugins/plugin/uninstall/uninstall.test.js @@ -85,9 +85,9 @@ describe('PluginUninstall', () => { it('should run promise chain in order for "plugin:uninstall:uninstall" hook', () => expect(pluginUninstall.hooks['plugin:uninstall:uninstall']()) - .to.be.fulfilled.then(() => { - expect(uninstallStub.calledOnce).to.equal(true); - }) + .to.be.fulfilled.then(() => { + expect(uninstallStub.calledOnce).to.equal(true); + }) ); }); @@ -205,7 +205,7 @@ describe('PluginUninstall', () => { fse.ensureDirSync(servicePath); packageJsonFilePath = path.join(servicePath, 'package.json'); npmUninstallStub = sinon.stub(childProcess, 'execAsync') - .returns(BbPromise.resolve()); + .returns(BbPromise.resolve()); // save the cwd so that we can restore it later savedCwd = process.cwd(); process.chdir(servicePath); @@ -328,16 +328,16 @@ describe('PluginUninstall', () => { .to.deep.equal(['serverless-plugin-2']); pluginUninstall.options.pluginName = 'serverless-plugin-2'; }) - .then(() => expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then( - () => { - expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')) - .to.not.have.property('plugins'); - })) - .then(() => expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then( - () => { - expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')) - .to.not.have.property('plugins'); - })); + .then(() => expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then( + () => { + expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')) + .to.not.have.property('plugins'); + })) + .then(() => expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then( + () => { + expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8')) + .to.not.have.property('plugins'); + })); }); it('should not modify serverless .js file', () => { @@ -364,6 +364,103 @@ describe('PluginUninstall', () => { }); }); }); + + describe('if plugins object is not array', () => { + it('should only remove the given plugin from the service', () => { + // serverless.yml + const serverlessYml = { + service: 'plugin-service', + provider: 'aws', + plugins: { + localPath: 'test', + modules: [ + 'serverless-existing-plugin', + 'serverless-plugin-1', + ], + }, + }; + serverless.utils + .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + + pluginUninstall.options.pluginName = 'serverless-plugin-1'; + + return expect(pluginUninstall.removePluginFromServerlessFile()) + .to.be.fulfilled.then(() => { + expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8').plugins) + .to.deep.equal({ + localPath: 'test', + modules: ['serverless-existing-plugin'], + }); + }); + }); + + it('should remove the plugin from the service if it is the only one', () => { + // serverless.yml + const serverlessYml = { + service: 'plugin-service', + provider: 'aws', + plugins: { + localPath: 'test', + modules: [ + 'serverless-plugin-1', + ], + }, + }; + serverless.utils + .writeFileSync(serverlessYmlFilePath, YAML.dump(serverlessYml)); + + pluginUninstall.options.pluginName = 'serverless-plugin-1'; + + return expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then(() => { + expect(serverless.utils.readFileSync(serverlessYmlFilePath, 'utf8').plugins) + .to.deep.equal({ + localPath: 'test', + }); + }); + }); + + it('should remove the plugin from serverless file path for a .json file', () => { + const serverlessJsonFilePath = path.join(servicePath, 'serverless.json'); + const serverlessJson = { + service: 'plugin-service', + provider: 'aws', + plugins: { + localPath: 'test', + modules: [ + 'serverless-plugin-1', + 'serverless-plugin-2', + ], + }, + }; + serverless.utils + .writeFileSync(serverlessJsonFilePath, serverlessJson); + pluginUninstall.options.pluginName = 'serverless-plugin-1'; + return expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then(() => { + expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8').plugins) + .to.deep.equal({ + localPath: 'test', + modules: [ + 'serverless-plugin-2', + ], + }); + pluginUninstall.options.pluginName = 'serverless-plugin-2'; + }) + .then(() => expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then( + () => { + expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8').plugins) + .to.deep.equal({ + localPath: 'test', + }); + })) + .then(() => expect(pluginUninstall.removePluginFromServerlessFile()).to.be.fulfilled.then( + () => { + expect(serverless.utils.readFileSync(serverlessJsonFilePath, 'utf8').plugins) + .to.deep.equal({ + localPath: 'test', + }); + })); + }); + }); }); describe('#uninstallPeerDependencies()', () => { diff --git a/lib/plugins/print/print.js b/lib/plugins/print/print.js index b48f26e36f8..9c146bdbc18 100644 --- a/lib/plugins/print/print.js +++ b/lib/plugins/print/print.js @@ -1,13 +1,17 @@ 'use strict'; +const os = require('os'); +const _ = require('lodash'); const BbPromise = require('bluebird'); const getServerlessConfigFile = require('../../utils/getServerlessConfigFile'); const jc = require('json-cycle'); const YAML = require('js-yaml'); class Print { - constructor(serverless) { + constructor(serverless, options) { this.serverless = serverless; + this.options = options || {}; + this.cache = {}; this.commands = { print: { @@ -15,6 +19,17 @@ class Print { lifecycleEvents: [ 'print', ], + options: { + format: { + usage: 'Print configuration in given format ("yaml", "json", "text"). Default: yaml', + }, + path: { + usage: 'Optional period-separated path to print a sub-value (eg: "provider.name")', + }, + transform: { + usage: 'Optional transform-function to apply to the value ("keys")', + }, + }, }, }; this.hooks = { @@ -23,29 +38,129 @@ class Print { }; } + adorn(svc) { + const service = svc; + // service object treatment + this.cache.serviceService = service.service; + if (_.isObject(this.cache.serviceService)) { + service.service = service.service.name; + service.serviceObject = this.cache.serviceService; + } + // provider treatment and defaults + this.cache.serviceProvider = service.provider; + if (_.isString(this.cache.serviceProvider)) { + service.provider = { name: this.cache.serviceProvider }; + } + service.provider = _.merge({ + stage: 'dev', + region: 'us-east-1', + variableSyntax: '\\${([ ~:a-zA-Z0-9._\'",\\-\\/\\(\\)]+?)}', + }, service.provider); + } + strip(svc) { + const service = svc; + if (_.isObject(this.cache.serviceService)) { + service.service = this.cache.serviceService; + delete service.serviceObject; + } + if (_.isString(this.cache.serviceProvider)) { + service.provider = this.cache.serviceProvider; + } else { // is object + if (!this.cache.serviceProvider.stage) { + delete service.provider.stage; + } + if (!this.cache.serviceProvider.region) { + delete service.provider.region; + } + if (this.cache.serviceProvider.variableSyntax) { + service.provider.variableSyntax = this.cache.serviceProvider.variableSyntax; + } + } + } print() { - let variableSyntax; - this.serverless.variables.options = this.serverless.processedInput.options; - this.serverless.variables.loadVariableSyntax(); + // ##################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/classes/Variables.js ## + // ## there, see `populateService` ## + // ## here, see below ## + // ##################################################################### + // ################################################################### + // ## KEEP SYNCHRONIZED WITH EQUIVALENT IN ~/lib/classes/Service.js ## + // ## there, see `constructor` and `loadServiceFileParam` ## + // ## here, see `strip` and `adorn` ## + // ################################################################### + // The *already loaded* Service (i.e. serverless.yml) is adorned with extras for use by the + // framework and throughout its codebase. We could try using the populated service but that + // would require playing whack-a-mole on issues as changes are made to the service anywhere in + // the codebase. Avoiding that, this method must read the serverless.yml file itself, adorn it + // as the Service class would and then populate it, reversing the adornments thereafter in + // preparation for printing the service for the user. return getServerlessConfigFile(process.cwd()) - .then((data) => { - const conf = data; - // Need to delete variableSyntax to avoid potential matching errors - if (conf.provider.variableSyntax) { - variableSyntax = conf.provider.variableSyntax; - delete conf.provider.variableSyntax; - } - this.serverless.variables.service = conf; - this.serverless.variables.cache = {}; - return this.serverless.variables.populateObject(conf); - }) - .then((data) => { - const conf = data; - if (variableSyntax !== undefined) { - conf.provider.variableSyntax = variableSyntax; - } - const out = JSON.parse(jc.stringify(conf)); - this.serverless.cli.consoleLog(YAML.dump(out, { noRefs: true })); + .then((svc) => { + const service = svc; + this.adorn(service); + // Need to delete variableSyntax to avoid self-matching errors + this.serverless.variables.loadVariableSyntax(); + delete service.provider.variableSyntax; // cached by adorn, restored by strip + this.serverless.variables.service = service; + return this.serverless.variables.populateObject(service) + .then((populated) => { + let conf = populated; + this.strip(conf); + + // dig into the object + if (this.options.path) { + const steps = this.options.path.split('.'); + for (const step of steps) { + conf = conf[step]; + + if (conf === undefined) { + return BbPromise.reject( + new this.serverless.classes.Error(`Path "${this.options.path}" not found`) + ); + } + } + } + + // apply an optional filter + if (this.options.transform) { + if (this.options.transform === 'keys') { + conf = Object.keys(conf); + } else { + return BbPromise.reject( + new this.serverless.classes.Error('Transform can only be "keys"') + ); + } + } + + // print configuration in the specified format + const format = this.options.format || 'yaml'; + let out; + + if (format === 'text') { + if (_.isArray(conf)) { + out = conf.join(os.EOL); + } else { + if (_.isObject(conf)) { + return BbPromise.reject( + new this.serverless.classes.Error('Cannot print an object as "text"') + ); + } + + out = String(conf); + } + } else if (format === 'json') { + out = jc.stringify(conf, null, ' '); + } else if (format === 'yaml') { + out = YAML.dump(JSON.parse(jc.stringify(conf)), { noRefs: true }); + } else { + return BbPromise.reject( + new this.serverless.classes.Error('Format must be "yaml", "json" or "text"') + ); + } + + this.serverless.cli.consoleLog(out); + return BbPromise.resolve(); + }); }); } diff --git a/lib/plugins/print/print.test.js b/lib/plugins/print/print.test.js index ec4dc1950e0..ccd2cd25f12 100644 --- a/lib/plugins/print/print.test.js +++ b/lib/plugins/print/print.test.js @@ -1,5 +1,6 @@ 'use strict'; +const os = require('os'); const expect = require('chai').expect; const sinon = require('sinon'); const proxyquire = require('proxyquire'); @@ -19,9 +20,9 @@ describe('Print', () => { '../../utils/getServerlessConfigFile': getServerlessConfigFileStub, }); serverless = new Serverless(); - serverless.processedInput = { - commands: ['print'], - options: { stage: undefined, region: undefined }, + serverless.variables.options = { + stage: 'dev', + region: 'us-east-1', }; serverless.cli = new CLI(serverless); print = new PrintPlugin(serverless); @@ -48,7 +49,172 @@ describe('Print', () => { expect(getServerlessConfigFileStub.calledOnce).to.equal(true); expect(print.serverless.cli.consoleLog.called).to.be.equal(true); - expect(message).to.have.string(YAML.dump(conf)); + expect(YAML.load(message)).to.eql(conf); + }); + }); + + it('should print standard config in JSON', () => { + const conf = { + service: 'my-service', + provider: { + name: 'aws', + }, + }; + getServerlessConfigFileStub.resolves(conf); + + print.options = { format: 'json' }; + return print.print().then(() => { + const message = print.serverless.cli.consoleLog.args.join(); + + expect(getServerlessConfigFileStub.calledOnce).to.equal(true); + expect(print.serverless.cli.consoleLog.called).to.be.equal(true); + expect(JSON.parse(message)).to.eql(conf); + }); + }); + + it('should apply paths to standard config in JSON', () => { + const conf = { + service: 'my-service', + provider: { + name: 'aws', + }, + }; + getServerlessConfigFileStub.resolves(conf); + + print.options = { format: 'json', path: 'service' }; + return print.print().then(() => { + const message = print.serverless.cli.consoleLog.args.join(); + + expect(getServerlessConfigFileStub.calledOnce).to.equal(true); + expect(print.serverless.cli.consoleLog.called).to.be.equal(true); + expect(JSON.parse(message)).to.eql(conf.service); + }); + }); + + it('should apply paths to standard config in text', () => { + const conf = { + service: 'my-service', + provider: { + name: 'aws', + }, + }; + getServerlessConfigFileStub.resolves(conf); + + print.options = { path: 'provider.name', format: 'text' }; + return print.print().then(() => { + const message = print.serverless.cli.consoleLog.args.join(); + + expect(getServerlessConfigFileStub.calledOnce).to.equal(true); + expect(print.serverless.cli.consoleLog.called).to.be.equal(true); + expect(message).to.eql(conf.provider.name); + }); + }); + + it('should print arrays in text', () => { + const conf = { + service: 'my-service', + provider: { + name: 'aws', + }, + }; + getServerlessConfigFileStub.resolves(conf); + + print.options = { transform: 'keys', format: 'text' }; + return print.print().then(() => { + const message = print.serverless.cli.consoleLog.args.join(); + + expect(getServerlessConfigFileStub.calledOnce).to.equal(true); + expect(print.serverless.cli.consoleLog.called).to.be.equal(true); + expect(message).to.eql(`service${os.EOL}provider`); + }); + }); + + it('should apply a keys-transform to standard config in JSON', () => { + const conf = { + service: 'my-service', + provider: { + name: 'aws', + }, + }; + getServerlessConfigFileStub.resolves(conf); + + print.options = { format: 'json', transform: 'keys' }; + return print.print().then(() => { + const message = print.serverless.cli.consoleLog.args.join(); + + expect(getServerlessConfigFileStub.calledOnce).to.equal(true); + expect(print.serverless.cli.consoleLog.called).to.be.equal(true); + expect(JSON.parse(message)).to.eql(Object.keys(conf)); + }); + }); + + it('should not allow a non-existing path', () => { + const conf = { + service: 'my-service', + provider: { + name: 'aws', + }, + }; + getServerlessConfigFileStub.resolves(conf); + + print.options = { path: 'provider.foobar' }; + return expect(print.print()).to.be.rejected; + }); + + it('should not allow an object as "text"', () => { + const conf = { + service: 'my-service', + provider: { + name: 'aws', + }, + }; + getServerlessConfigFileStub.resolves(conf); + + print.options = { format: 'text' }; + return expect(print.print()).to.be.rejected; + }); + + it('should not allow an unknown transform', () => { + const conf = { + service: 'my-service', + provider: { + name: 'aws', + }, + }; + getServerlessConfigFileStub.resolves(conf); + + print.options = { transform: 'foobar' }; + return expect(print.print()).to.be.rejected; + }); + + it('should not allow an unknown format', () => { + const conf = { + service: 'my-service', + provider: { + name: 'aws', + }, + }; + getServerlessConfigFileStub.resolves(conf); + + print.options = { format: 'foobar' }; + return expect(print.print()).to.be.rejected; + }); + + it('should print special service object and provider string configs', () => { + const conf = { + service: { + name: 'my-service', + }, + provider: 'aws', + }; + getServerlessConfigFileStub.resolves(conf); + + return print.print().then(() => { + const message = print.serverless.cli.consoleLog.args.join(); + + expect(getServerlessConfigFileStub.calledOnce).to.equal(true); + expect(print.serverless.cli.consoleLog.called).to.be.equal(true); + expect(YAML.load(message)).to.eql(conf); }); }); @@ -80,7 +246,7 @@ describe('Print', () => { expect(getServerlessConfigFileStub.calledOnce).to.equal(true); expect(print.serverless.cli.consoleLog.called).to.be.equal(true); - expect(message).to.equal(YAML.dump(expected)); + expect(YAML.load(message)).to.eql(expected); }); }); @@ -115,7 +281,7 @@ describe('Print', () => { expect(getServerlessConfigFileStub.calledOnce).to.equal(true); expect(print.serverless.cli.consoleLog.called).to.be.equal(true); - expect(message).to.equal(YAML.dump(expected)); + expect(YAML.load(message)).to.eql(expected); }); }); @@ -154,7 +320,7 @@ describe('Print', () => { expect(getServerlessConfigFileStub.calledOnce).to.equal(true); expect(print.serverless.cli.consoleLog.called).to.be.equal(true); - expect(message).to.equal(YAML.dump(expected)); + expect(YAML.load(message)).to.eql(expected); }); }); @@ -186,7 +352,7 @@ describe('Print', () => { expect(getServerlessConfigFileStub.calledOnce).to.equal(true); expect(print.serverless.cli.consoleLog.called).to.be.equal(true); - expect(message).to.equal(YAML.dump(expected)); + expect(YAML.load(message)).to.eql(expected); }); }); }); diff --git a/lib/plugins/run/index.js b/lib/plugins/run/index.js index b6fad0a2b00..0f6b457d4f8 100644 --- a/lib/plugins/run/index.js +++ b/lib/plugins/run/index.js @@ -20,7 +20,7 @@ const registerFunctionsToEventGateway = require('./utils/registerFunctionsToEven const manageLocalEmulator = require('./utils/manageLocalEmulator'); const manageEventGateway = require('./utils/manageEventGateway'); -const getAuthToken = require('../../utils/getAuthToken'); +const isLoggedIn = require('../../utils/isLoggedIn'); class Run { constructor(serverless, options) { @@ -67,8 +67,7 @@ class Run { const EVENT_GATEWAY_VERSION = '0.5.15'; const LOCAL_EMULATOR_VERSION = '0.1.18'; - const authToken = getAuthToken(); - if (!authToken) { + if (!isLoggedIn()) { throw new this.serverless.classes .Error('Must be logged in to use this command. Please run "serverless login".'); } diff --git a/lib/utils/clearConsole.js b/lib/utils/clearConsole.js deleted file mode 100644 index aeb5b9f2220..00000000000 --- a/lib/utils/clearConsole.js +++ /dev/null @@ -1,8 +0,0 @@ -'use strict'; - -/* Clear terminal output */ -const clearConsole = function () { - process.stdout.write(process.platform !== 'win32' ? '\x1B[2J\x1B[3J\x1B[H' : '\x1Bc'); -}; - -module.exports = clearConsole; diff --git a/lib/utils/createApolloClient.js b/lib/utils/createApolloClient.js deleted file mode 100644 index fa66b72614c..00000000000 --- a/lib/utils/createApolloClient.js +++ /dev/null @@ -1,24 +0,0 @@ -const apollo = require('apollo-client'); - -module.exports = (endpoint, auth0IdToken) => { - const networkInterface = apollo.createNetworkInterface({ uri: endpoint }); - - if (auth0IdToken) { - networkInterface.use([{ - applyMiddleware(req, next) { - if (!req.options.headers) { - // eslint-disable-next-line no-param-reassign - req.options.headers = {}; - } - const token = auth0IdToken; - // eslint-disable-next-line no-param-reassign - req.options.headers.authorization = token ? `Bearer ${token}` : null; - next(); - }, - }]); - } - - return new apollo.ApolloClient({ - networkInterface, - }); -}; diff --git a/lib/utils/fs/writeFile.js b/lib/utils/fs/writeFile.js index 625bdbc285c..abeef07e0e7 100644 --- a/lib/utils/fs/writeFile.js +++ b/lib/utils/fs/writeFile.js @@ -5,13 +5,17 @@ const path = require('path'); const jc = require('json-cycle'); const YAML = require('js-yaml'); -function writeFile(filePath, conts) { +function writeFile(filePath, conts, cycles) { let contents = conts || ''; return fse.mkdirsAsync(path.dirname(filePath)) .then(() => { if (filePath.indexOf('.json') !== -1 && typeof contents !== 'string') { - contents = jc.stringify(contents, null, 2); + if (cycles) { + contents = jc.stringify(contents, null, 2); + } else { + contents = JSON.stringify(contents, null, 2); + } } const yamlFileExists = (filePath.indexOf('.yaml') !== -1); diff --git a/lib/utils/fs/writeFile.test.js b/lib/utils/fs/writeFile.test.js index 972669f40c4..966c928d43c 100644 --- a/lib/utils/fs/writeFile.test.js +++ b/lib/utils/fs/writeFile.test.js @@ -49,7 +49,7 @@ describe('#writeFile()', function () { bar.foo = bar; const expected = '{\n "foo": {\n "$ref": "$"\n }\n}'; - return writeFile(tmpFilePath, bar) + return writeFile(tmpFilePath, bar, true) .then(() => expect(fse.readFileAsync(tmpFilePath, 'utf8')).to.eventually.equal(expected) ); diff --git a/lib/utils/fs/writeFileSync.js b/lib/utils/fs/writeFileSync.js index ab5fff093ee..6e58263c41b 100644 --- a/lib/utils/fs/writeFileSync.js +++ b/lib/utils/fs/writeFileSync.js @@ -5,13 +5,17 @@ const path = require('path'); const jc = require('json-cycle'); const YAML = require('js-yaml'); -function writeFileSync(filePath, conts) { +function writeFileSync(filePath, conts, cycles) { let contents = conts || ''; fse.mkdirsSync(path.dirname(filePath)); if (filePath.indexOf('.json') !== -1 && typeof contents !== 'string') { - contents = jc.stringify(contents, null, 2); + if (cycles) { + contents = jc.stringify(contents, null, 2); + } else { + contents = JSON.stringify(contents, null, 2); + } } const yamlFileExists = (filePath.indexOf('.yaml') !== -1); diff --git a/lib/utils/fs/writeFileSync.test.js b/lib/utils/fs/writeFileSync.test.js index 64cc604b931..87ec760a57e 100644 --- a/lib/utils/fs/writeFileSync.test.js +++ b/lib/utils/fs/writeFileSync.test.js @@ -53,7 +53,7 @@ describe('#writeFileSync()', () => { bar.foo = bar; const expected = '{\n "foo": {\n "$ref": "$"\n }\n}'; - writeFileSync(tmpFilePath, bar); + writeFileSync(tmpFilePath, bar, true); return fse.readFileAsync(tmpFilePath, 'utf8').then((contents) => { expect(contents).to.equal(expected); diff --git a/lib/utils/getAccessKey.js b/lib/utils/getAccessKey.js new file mode 100644 index 00000000000..06f1aba2b31 --- /dev/null +++ b/lib/utils/getAccessKey.js @@ -0,0 +1,38 @@ +'use strict'; + +const configUtils = require('./config'); +const platform = require('@serverless/platform-sdk'); +const BbPromise = require('bluebird'); + +function getAccessKey(tenant) { + if (process.env.SERVERLESS_ACCESS_KEY) { + return BbPromise.resolve(process.env.SERVERLESS_ACCESS_KEY); + } + if (!tenant) { + return BbPromise.resolve(null); + } + const userConfig = configUtils.getConfig(); + const currentId = userConfig.userId; + const globalConfig = configUtils.getGlobalConfig(); + if (globalConfig.users && globalConfig.users[currentId] && + globalConfig.users[currentId].dashboard) { + if (globalConfig.users[currentId].dashboard.accessKey) { + return BbPromise.resolve(globalConfig.users[currentId].dashboard.accessKey); + } else if (globalConfig.users[currentId].dashboard.idToken) { + const data = { + tenant, + username: globalConfig.users[currentId].username, + idToken: globalConfig.users[currentId].dashboard.idToken, + title: 'Framework', + }; + return platform.createAccessKey(data).then(accessKey => { + globalConfig.users[currentId].dashboard.accessKey = accessKey; + configUtils.set(globalConfig); + return accessKey; + }); + } + } + return BbPromise.resolve(null); +} + +module.exports = getAccessKey; diff --git a/lib/utils/getAuthToken.js b/lib/utils/getAuthToken.js deleted file mode 100644 index 71081335fb6..00000000000 --- a/lib/utils/getAuthToken.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -const configUtils = require('./config'); - -function getAuthToken() { - if (process.env.SERVERLESS_TOKEN) { - return process.env.SERVERLESS_TOKEN; - } - - const userConfig = configUtils.getConfig(); - const currentId = userConfig.userId; - const globalConfig = configUtils.getGlobalConfig(); - if (globalConfig.users && globalConfig.users[currentId] && globalConfig.users[currentId].auth) { - return globalConfig.users[currentId].auth.id_token; - } - return null; -} - -module.exports = getAuthToken; diff --git a/lib/utils/isLoggedIn.js b/lib/utils/isLoggedIn.js new file mode 100644 index 00000000000..5ad732a2914 --- /dev/null +++ b/lib/utils/isLoggedIn.js @@ -0,0 +1,19 @@ +'use strict'; + +const configUtils = require('./config'); + +function isLoggedIn() { + const config = configUtils.getConfig(); + const currentId = config.userId; + const globalConfig = configUtils.getGlobalConfig(); + if (globalConfig + && globalConfig.users + && globalConfig.users[currentId] + && globalConfig.users[currentId].dashboard + && globalConfig.users[currentId].dashboard.idToken) { + return true; + } + return false; +} + +module.exports = isLoggedIn; diff --git a/lib/utils/openBrowser.js b/lib/utils/openBrowser.js deleted file mode 100644 index 553e2327f46..00000000000 --- a/lib/utils/openBrowser.js +++ /dev/null @@ -1,31 +0,0 @@ -'use strict'; - -/* eslint-disable no-console */ - -const opn = require('opn'); -const chalk = require('chalk'); -const isDockerContainer = require('is-docker'); - -function displayManualOpenMessage(url, err) { - // https://github.com/sindresorhus/log-symbols - console.log('---------------------------'); - const errMsg = err ? `\nError: ${err.message}` : ''; - const msg = `Unable to open browser automatically${errMsg}`; - console.log(`🙈 ${chalk.red(msg)}`); - console.log(chalk.green('Please open your browser & open the URL below to login:')); - console.log(chalk.yellow(url)); - console.log('---------------------------'); - return false; -} - -module.exports = function openBrowser(url) { - let browser = process.env.BROWSER; - if (browser === 'none' || isDockerContainer()) { - return displayManualOpenMessage(url); - } - if (process.platform === 'darwin' && browser === 'open') { - browser = undefined; - } - const options = { app: browser }; - return opn(url, options).catch(err => displayManualOpenMessage(url, err)); -}; diff --git a/lib/utils/openBrowser.test.js b/lib/utils/openBrowser.test.js deleted file mode 100644 index dfe4ea9b390..00000000000 --- a/lib/utils/openBrowser.test.js +++ /dev/null @@ -1,37 +0,0 @@ -'use strict'; - -const proxyquire = require('proxyquire'); -const sinon = require('sinon'); -const expect = require('chai').expect; - -describe('#openBrowser', () => { - let openBrowser; - let opnStub; - let isDockerStub; - - beforeEach(() => { - opnStub = sinon.stub().resolves({}); - opnStub = sinon.stub().resolves({}); - isDockerStub = sinon.stub().returns(false); - - openBrowser = proxyquire('./openBrowser', { - opn: opnStub, - 'is-docker': isDockerStub, - }); - }); - - it('should open the browser with the provided url', () => { - openBrowser('http://www.example.com'); - expect(opnStub.getCall(0).args[0]).to.equal('http://www.example.com'); - }); - - it('should open the browser with the provided url', () => { - isDockerStub = sinon.stub().returns(true); - openBrowser = proxyquire('./openBrowser', { - opn: opnStub, - 'is-docker': isDockerStub, - }); - openBrowser('http://www.example.com'); - expect(opnStub.notCalled).to.equal(true); - }); -}); diff --git a/lib/utils/selectors/selectServicePublish.js b/lib/utils/selectors/selectServicePublish.js deleted file mode 100644 index 02c3fd37962..00000000000 --- a/lib/utils/selectors/selectServicePublish.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -const _ = require('lodash'); - -const selectServicePublish = (service) => _.get(service, 'serviceObject.publish', true); - -module.exports = selectServicePublish; diff --git a/lib/utils/selectors/selectServicePublish.test.js b/lib/utils/selectors/selectServicePublish.test.js deleted file mode 100644 index b762eaeade8..00000000000 --- a/lib/utils/selectors/selectServicePublish.test.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -const expect = require('chai').expect; -const selectServicePublish = require('./selectServicePublish'); - -describe('#selectServicePublish()', () => { - it('should return the publish value of the service object', () => { - const service = { - serviceObject: { - publish: true, - }, - }; - - const result = selectServicePublish(service); - - expect(result).to.equal(true); - }); -}); diff --git a/lib/utils/yamlAstParser.js b/lib/utils/yamlAstParser.js index 7d91f55f986..324b543dc44 100644 --- a/lib/utils/yamlAstParser.js +++ b/lib/utils/yamlAstParser.js @@ -58,60 +58,113 @@ const parseAST = (ymlAstContent, astObject) => { return newAstObject; }; +const constructPlainObject = (ymlAstContent, branchObject) => { + const newbranchObject = branchObject || {}; + if (ymlAstContent.mappings && _.isArray(ymlAstContent.mappings)) { + _.forEach(ymlAstContent.mappings, (v) => { + if (!v.value) { + // no need to log twice, parseAST will log errors + return; + } + + if (v.key.kind === 0 && v.value.kind === 0) { + newbranchObject[v.key.value] = v.value.value; + } else if (v.key.kind === 0 && v.value.kind === 2) { + newbranchObject[v.key.value] = constructPlainObject(v.value, {}); + } else if (v.key.kind === 0 && v.value.kind === 3) { + const plainArray = []; + _.forEach(v.value.items, (c) => { + plainArray.push(c.value); + }); + newbranchObject[v.key.value] = plainArray; + } + }); + } + + return newbranchObject; +}; + const addNewArrayItem = (ymlFile, pathInYml, newValue) => fs.readFileAsync(ymlFile, 'utf8').then(yamlContent => { - const astObject = parseAST(yaml.load(yamlContent)); + const rawAstObject = yaml.load(yamlContent); + const astObject = parseAST(rawAstObject); + const plainObject = constructPlainObject(rawAstObject); + const pathInYmlArray = pathInYml.split('.'); - if (astObject[pathInYml] && astObject[pathInYml].items) { - let newArray = []; - _.forEach(astObject[pathInYml].items, (v) => { - newArray.push(v.value); - }); - newArray = _.union(newArray, [newValue]); + let currentNode = plainObject; + for (let i = 0; i < pathInYmlArray.length - 1; i++) { + const propertyName = pathInYmlArray[i]; + const property = currentNode[propertyName]; + if (_.isUndefined(property) || _.isObject(property)) { + currentNode[propertyName] = property || {}; + currentNode = currentNode[propertyName]; + } else { + throw new Error(`${property} can only be undefined or an object!`); + } + } - const pathInYmlArrray = pathInYml.split('.'); - let newObject = JSON.stringify(newArray); - _.forEach(pathInYmlArrray.reverse(), (v) => { - newObject = `{"${v}":${newObject}}`; - }); + const arrayPropertyName = _.last(pathInYmlArray); + let arrayProperty = currentNode[arrayPropertyName]; + if (_.isUndefined(arrayProperty) || _.isArray(arrayProperty)) { + arrayProperty = arrayProperty || []; + } else { + throw new Error(`${arrayProperty} can only be undefined or an array!`); + } + currentNode[arrayPropertyName] = _.union(arrayProperty, [newValue]); - const beginning = yamlContent.substring(0, astObject[_.head(_.split(pathInYml, '.'))] - .parent.key.startPosition); - const end = yamlContent.substring(astObject[pathInYml].endPosition, yamlContent.length); - return fs.writeFileAsync(ymlFile, `${beginning}${yaml.dump(JSON.parse(newObject))}${end}`); + const branchToReplaceName = _.head(pathInYmlArray); + const newObject = {}; + newObject[branchToReplaceName] = plainObject[branchToReplaceName]; + const newText = yaml.dump(newObject); + if (astObject[branchToReplaceName]) { + const beginning = yamlContent + .substring(0, astObject[branchToReplaceName].parent.key.startPosition); + const end = yamlContent + .substring(astObject[branchToReplaceName].endPosition, yamlContent.length); + return fs.writeFileAsync(ymlFile, `${beginning}${newText}${end}`); } - const pathInYmlArrray = pathInYml.split('.'); - let newObject = JSON.stringify([newValue]); - _.forEach(pathInYmlArrray.reverse(), (v) => { - newObject = `{"${v}":${newObject}}`; - }); - const beginning = yamlContent.substring(0, yamlContent.length); - return fs.writeFileAsync(ymlFile, `${beginning}${os.EOL}${yaml.dump(JSON.parse(newObject))}`); + return fs.writeFileAsync(ymlFile, `${yamlContent}${os.EOL}${newText}`); }); const removeExistingArrayItem = (ymlFile, pathInYml, removeValue) => fs.readFileAsync(ymlFile, 'utf8').then(yamlContent => { - const astObject = parseAST(yaml.load(yamlContent)); + const rawAstObject = yaml.load(yamlContent); + const astObject = parseAST(rawAstObject); if (astObject[pathInYml] && astObject[pathInYml].items) { - const newArray = []; - let newText = ''; - _.forEach(astObject[pathInYml].items, (v) => { - newArray.push(v.value); - }); - _.pull(newArray, removeValue); - - if (!_.isEmpty(newArray)) { - const pathInYmlArrray = pathInYml.split('.'); - let newObject = JSON.stringify(newArray); - _.forEach(pathInYmlArrray.reverse(), (v) => { - newObject = `{"${v}":${newObject}}`; - }); - newText = yaml.dump(JSON.parse(newObject)); + const plainObject = constructPlainObject(rawAstObject); + const pathInYmlArray = pathInYml.split('.'); + + let currentNode = plainObject; + const pathInObjectTree = []; + for (let i = 0; i < pathInYmlArray.length - 1; i++) { + pathInObjectTree.push(currentNode); + currentNode = currentNode[pathInYmlArray[i]]; + } + const arrayPropertyName = _.last(pathInYmlArray); + const arrayProperty = currentNode[arrayPropertyName]; + _.pull(arrayProperty, removeValue); + + if (_.isEmpty(arrayProperty)) { + _.unset(currentNode, arrayPropertyName); + pathInObjectTree.push(currentNode); + for (let i = pathInObjectTree.length - 1; i > 0; i--) { + if (_.keys(pathInObjectTree[i]).length > 0) { + break; + } + _.unset(pathInObjectTree[i - 1], pathInYmlArray[i - 1]); + } } - const beginning = yamlContent.substring(0, astObject[_.head(_.split(pathInYml, '.'))] - .parent.key.startPosition); + const headObjectPath = _.head(pathInYmlArray); + let newText = ''; + + if (plainObject[headObjectPath]) { + const newObject = {}; + newObject[headObjectPath] = plainObject[headObjectPath]; + newText = yaml.dump(newObject); + } + const beginning = yamlContent.substring(0, astObject[headObjectPath].parent.key.startPosition); const end = yamlContent.substring(astObject[pathInYml].endPosition, yamlContent.length); return fs.writeFileAsync(ymlFile, `${beginning}${newText}${end}`); } diff --git a/lib/utils/yamlAstParser.test.js b/lib/utils/yamlAstParser.test.js index 9e04b4a0ec9..3dbbb23612f 100644 --- a/lib/utils/yamlAstParser.test.js +++ b/lib/utils/yamlAstParser.test.js @@ -6,7 +6,10 @@ const testUtils = require('../../tests/utils'); const writeFileSync = require('./fs/writeFileSync'); const readFileSync = require('./fs/readFileSync'); const yamlAstParser = require('./yamlAstParser'); -chai.use(require('chai-as-promised')); +const _ = require('lodash'); +const chaiAsPromised = require('chai-as-promised'); + +chai.use(chaiAsPromised); const expect = require('chai').expect; describe('#yamlAstParser', () => { @@ -17,177 +20,273 @@ describe('#yamlAstParser', () => { }); describe('#addNewArrayItem()', () => { - it('should add a top level object and item into the yaml file', () => { + const addNewArrayItemAndVerifyResult = (yamlContent, pathInYaml, newItem, expectedResult) => { const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); - - writeFileSync(yamlFilePath, 'service: test-service'); - return expect(yamlAstParser.addNewArrayItem(yamlFilePath, 'toplevel', 'foo')) + writeFileSync(yamlFilePath, yamlContent); + return expect(yamlAstParser.addNewArrayItem(yamlFilePath, pathInYaml, newItem)) .to.be.fulfilled.then(() => { const yaml = readFileSync(yamlFilePath, 'utf8'); - expect(yaml).to.have.property('toplevel'); - expect(yaml.toplevel).to.be.deep.equal(['foo']); + expect(yaml).to.be.deep.equal(expectedResult); }); + }; + + it('should add a top level object and item into the yaml file', () => { + const yamlContent = { service: 'test-service' }; + const expectedResult = _.assign({}, yamlContent, { + toplevel: ['foo'], + }); + return addNewArrayItemAndVerifyResult(yamlContent, 'toplevel', 'foo', expectedResult); }); it('should add an item under the existing object which you specify', () => { - const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); - - writeFileSync(yamlFilePath, { toplevel: ['foo'] }); - return expect(yamlAstParser.addNewArrayItem(yamlFilePath, 'toplevel', 'bar')) - .to.be.fulfilled.then(() => { - const yaml = readFileSync(yamlFilePath, 'utf8'); - expect(yaml).to.have.property('toplevel'); - expect(yaml.toplevel).to.be.deep.equal(['foo', 'bar']); - }); + const yamlContent = { toplevel: ['foo'] }; + const expectedResult = { toplevel: ['foo', 'bar'] }; + return addNewArrayItemAndVerifyResult(yamlContent, 'toplevel', 'bar', expectedResult); }); it('should add a multiple level object and item into the yaml file', () => { - const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); - - writeFileSync(yamlFilePath, 'service: test-service'); - return expect(yamlAstParser.addNewArrayItem(yamlFilePath, 'toplevel.second.third', 'foo')) - .to.be.fulfilled.then(() => { - const yaml = readFileSync(yamlFilePath, 'utf8'); - expect(yaml).to.have.property('toplevel'); - expect(yaml.toplevel).to.be.deep.equal({ - second: { - third: ['foo'], - }, - }); - }); + const yamlContent = { service: 'test-service' }; + const expectedResult = _.assign({}, yamlContent, { + toplevel: { + second: { + third: ['foo'], + }, + }, + }); + return addNewArrayItemAndVerifyResult(yamlContent, + 'toplevel.second.third', 'foo', expectedResult); }); it('should add an item under the existing multiple level object which you specify', () => { - const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); - - writeFileSync(yamlFilePath, { + const yamlContent = { toplevel: { second: { third: ['foo'], }, }, - }); - return expect(yamlAstParser.addNewArrayItem(yamlFilePath, 'toplevel.second.third', 'bar')) - .to.be.fulfilled.then(() => { - const yaml = readFileSync(yamlFilePath, 'utf8'); - expect(yaml).to.have.property('toplevel'); - expect(yaml.toplevel).to.be.deep.equal({ - second: { - third: ['foo', 'bar'], - }, - }); - }); + }; + const expectedResult = { + toplevel: { + second: { + third: ['foo', 'bar'], + }, + }, + }; + return addNewArrayItemAndVerifyResult(yamlContent, + 'toplevel.second.third', 'bar', expectedResult); }); - it('should do nothing when adding the existing item', () => { - const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); + it('should add an item under partially existing multiple level object', () => { + const yamlContent = { + toplevel: { + first: 'foo', + second: { + }, + }, + }; + const expectedResult = { + toplevel: { + first: 'foo', + second: { + third: ['bar'], + }, + }, + }; + return addNewArrayItemAndVerifyResult(yamlContent, + 'toplevel.second.third', 'bar', expectedResult); + }); - writeFileSync(yamlFilePath, { toplevel: ['foo'] }); - return expect(yamlAstParser.addNewArrayItem(yamlFilePath, 'toplevel', 'foo')) - .to.be.fulfilled.then(() => { - const yaml = readFileSync(yamlFilePath, 'utf8'); - expect(yaml).to.have.property('toplevel'); - expect(yaml.toplevel).to.be.deep.equal(['foo']); - }); + it('should add an item in the middle branch', () => { + const yamlContent = { + initiallevel: 'bar', + toplevel: { + first: 'foo', + }, + bottomlevel: 'bar', + }; + const expectedResult = { + initiallevel: 'bar', + toplevel: { + first: 'foo', + second: ['bar'], + }, + bottomlevel: 'bar', + }; + return addNewArrayItemAndVerifyResult(yamlContent, + 'toplevel.second', 'bar', expectedResult); }); - it('should survive with invalid yaml', () => { - const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); + it('should add an item with multiple top level entries', () => { + const yamlContent = { + toplevel: { + first: 'foo', + second: { + }, + }, + nexttoplevel: { + first: 'bar', + }, + }; + const expectedResult = { + toplevel: { + first: 'foo', + second: { + third: ['bar'], + }, + }, + nexttoplevel: { + first: 'bar', + }, + }; + return addNewArrayItemAndVerifyResult(yamlContent, + 'toplevel.second.third', 'bar', expectedResult); + }); - writeFileSync(yamlFilePath, 'service:'); - return expect(yamlAstParser.addNewArrayItem(yamlFilePath, 'toplevel', 'foo')) - .to.be.fulfilled.then(() => { - const yaml = readFileSync(yamlFilePath, 'utf8'); - expect(yaml).to.have.property('toplevel'); - expect(yaml.toplevel).to.be.deep.equal(['foo']); - }); + it('should do nothing when adding the existing item', () => { + const yamlContent = { toplevel: ['foo'] }; + const expectedResult = { toplevel: ['foo'] }; + return addNewArrayItemAndVerifyResult(yamlContent, 'toplevel', 'foo', expectedResult); + }); + + it('should survive with invalid yaml', () => { + const yamlContent = 'service:'; + const expectedResult = { service: null, toplevel: ['foo'] }; + return addNewArrayItemAndVerifyResult(yamlContent, 'toplevel', 'foo', expectedResult); }); }); describe('#removeExistingArrayItem()', () => { - it('should remove the existing top level object and item from the yaml file', () => { - const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); + const removeExistingArrayItemAndVerifyResult = + (yamlContent, pathInYaml, removeItem, expectedResult) => { + const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); + writeFileSync(yamlFilePath, yamlContent); + return expect(yamlAstParser.removeExistingArrayItem(yamlFilePath, pathInYaml, removeItem)) + .to.be.fulfilled.then(() => { + const yaml = readFileSync(yamlFilePath, 'utf8'); + expect(yaml).to.be.deep.equal(expectedResult); + }); + }; - writeFileSync(yamlFilePath, { - serveice: 'test-service', + it('should remove the existing top level object and item from the yaml file', () => { + const yamlContent = { + service: 'test-service', toplevel: ['foo'], - }); - return expect(yamlAstParser.removeExistingArrayItem(yamlFilePath, 'toplevel', 'foo')) - .to.be.fulfilled.then(() => { - const yaml = readFileSync(yamlFilePath, 'utf8'); - expect(yaml).to.have.not.property('toplevel'); - }); + }; + const expectedResult = { service: 'test-service' }; + return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel', + 'foo', expectedResult); }); it('should remove the existing item under the object which you specify', () => { - const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); - - writeFileSync(yamlFilePath, { - serveice: 'test-service', + const yamlContent = { + service: 'test-service', toplevel: ['foo', 'bar'], - }); - return expect(yamlAstParser.removeExistingArrayItem(yamlFilePath, 'toplevel', 'bar')) - .to.be.fulfilled.then(() => { - const yaml = readFileSync(yamlFilePath, 'utf8'); - expect(yaml).to.have.property('toplevel'); - expect(yaml.toplevel).to.be.deep.equal(['foo']); - }); + }; + const expectedResult = { + service: 'test-service', + toplevel: ['foo'], + }; + return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel', + 'bar', expectedResult); }); it('should remove the multiple level object and item from the yaml file', () => { - const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); - - writeFileSync(yamlFilePath, { - serveice: 'test-service', + const yamlContent = { + service: 'test-service', toplevel: { second: { third: ['foo'], }, }, - }); - return expect(yamlAstParser.removeExistingArrayItem( - yamlFilePath, 'toplevel.second.third', 'foo')).to.be.fulfilled.then(() => { - const yaml = readFileSync(yamlFilePath, 'utf8'); - expect(yaml).to.have.not.property('toplevel'); - }); + }; + const expectedResult = { service: 'test-service' }; + return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel.second.third', + 'foo', expectedResult); }); it('should remove the existing item under the multiple level object which you specify', () => { - const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); + const yamlContent = { + service: 'test-service', + toplevel: { + second: { + third: ['foo', 'bar'], + }, + }, + }; + const expectedResult = { + service: 'test-service', + toplevel: { + second: { + third: ['foo'], + }, + }, + }; + return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel.second.third', + 'bar', expectedResult); + }); - writeFileSync(yamlFilePath, { - serveice: 'test-service', + it('should remove multilevel object from the middle branch', () => { + const yamlContent = { + service: 'test-service', + toplevel: { + second: { + third: ['foo'], + }, + }, + end: 'end', + }; + const expectedResult = { + service: 'test-service', + end: 'end', + }; + return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel.second.third', + 'foo', expectedResult); + }); + + it('should remove item from multilevel object from the middle branch', () => { + const yamlContent = { + service: 'test-service', toplevel: { second: { third: ['foo', 'bar'], }, }, - }); - return expect(yamlAstParser.removeExistingArrayItem( - yamlFilePath, 'toplevel.second.third', 'bar')).to.be.fulfilled.then(() => { - const yaml = readFileSync(yamlFilePath, 'utf8'); - expect(yaml).to.have.property('toplevel'); - expect(yaml.toplevel).to.be.deep.equal({ - second: { - third: ['foo'], - }, - }); - }); + end: 'end', + }; + const expectedResult = { + service: 'test-service', + toplevel: { + second: { + third: ['bar'], + }, + }, + end: 'end', + }; + return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel.second.third', + 'foo', expectedResult); }); it('should do nothing when you can not find the object which you specify', () => { - const yamlFilePath = path.join(tmpDirPath, 'test.yaml'); - - writeFileSync(yamlFilePath, { + const yamlContent = { serveice: 'test-service', toplevel: ['foo', 'bar'], - }); - return expect(yamlAstParser.removeExistingArrayItem(yamlFilePath, 'toplevel', 'foo2')) - .to.be.fulfilled.then(() => { - const yaml = readFileSync(yamlFilePath, 'utf8'); - expect(yaml).to.have.property('toplevel'); - expect(yaml.toplevel).to.be.deep.equal(['foo', 'bar']); - }); + }; + + return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel', + 'foo2', yamlContent); + }); + + it('should remove when with inline declaration of the array', () => { + const yamlContent = + 'toplevel:\n' + + ' second: ["foo2", "bar"]'; + const expectedResult = { + toplevel: { + second: ['foo2'], + }, + }; + return removeExistingArrayItemAndVerifyResult(yamlContent, 'toplevel.second', + 'bar', expectedResult); }); }); }); diff --git a/package-lock.json b/package-lock.json index a238c631184..f68fd1bb46a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,24 +1,71 @@ { "name": "serverless", - "version": "1.26.0", + "version": "1.28.0", "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.51.tgz", + "integrity": "sha1-vXHZsZKvl435FYKdOdQJRFZDmgw=", + "dev": true, + "requires": { + "@babel/highlight": "7.0.0-beta.51" + } + }, + "@babel/highlight": { + "version": "7.0.0-beta.51", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.51.tgz", + "integrity": "sha1-6IRK4loVlcz9QriWI7Q3bKBtIl0=", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^3.0.0" + } + }, "@serverless/fdk": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@serverless/fdk/-/fdk-0.5.1.tgz", "integrity": "sha512-Z/+5R0AohLwDT1E+9BTeUA7NozlyIoTh0iEt6x8x+ZZhIBK5HBMBN6v2LfkI4wmmOOyceTvsN0l8nWfGp4Oh5g==", "requires": { - "isomorphic-fetch": "2.2.1", - "ramda": "0.24.1", - "url-parse": "1.2.0" + "isomorphic-fetch": "^2.2.1", + "ramda": "^0.24.1", + "url-parse": "^1.1.9" } }, - "@types/graphql": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@types/graphql/-/graphql-0.10.2.tgz", - "integrity": "sha512-Ayw0w+kr8vYd8DToiMXjcHxXv1ljWbqX2mnLwXDxkBgog3vywGriC0JZ+npsuohKs3+E88M8OOtobo4g0X3SIA==", - "optional": true + "@serverless/platform-sdk": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@serverless/platform-sdk/-/platform-sdk-0.1.5.tgz", + "integrity": "sha512-d61v7iSAqvNlT+9TBZnsJwmSmVtu6MERwa+IUTHj46RwICPYsup3dB0U4jy3mhTAk1XtQ+EWuUOSTZdck2aD8w==", + "requires": { + "babel-polyfill": "^6.26.0", + "bluebird": "^3.5.1", + "body-parser": "^1.18.3", + "chalk": "^2.4.1", + "cors": "^2.8.4", + "express": "^4.16.3", + "is-docker": "^1.1.0", + "is-wsl": "^1.1.0", + "isomorphic-fetch": "^2.2.1", + "node-fetch": "^2.1.2", + "opn": "^5.3.0", + "querystring": "^0.2.0", + "ramda": "^0.25.0", + "source-map-support": "^0.5.5" + }, + "dependencies": { + "node-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", + "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" + }, + "ramda": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.25.0.tgz", + "integrity": "sha512-GXpfrYVPwx3K7RQ6aYT8KPS8XViSXUVJT1ONhoKPE9VAleW42YE+U+8VEyGWt41EnEQW7gwecYJriTI0pKoecQ==" + } + } }, "abab": { "version": "1.0.4", @@ -32,27 +79,28 @@ "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=", "dev": true }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, "acorn": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", - "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", + "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==", "dev": true }, "acorn-globals": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", - "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.1.0.tgz", + "integrity": "sha512-KjZwU26uG3u6eZcfGbTULzFcsoz6pegNKtHPksZPOUsiKo5bUmiBPa38FuHZ/Eun+XYh/JCCkS9AS3Lu4McQOQ==", "dev": true, "requires": { - "acorn": "4.0.13" - }, - "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", - "dev": true - } + "acorn": "^5.0.0" } }, "acorn-jsx": { @@ -61,7 +109,7 @@ "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", "dev": true, "requires": { - "acorn": "3.3.0" + "acorn": "^3.0.4" }, "dependencies": { "acorn": { @@ -73,29 +121,23 @@ } }, "agent-base": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz", - "integrity": "sha1-1t4Q1a9hMtW9aSQn1G/FOFOQlMc=", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", + "integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==", "requires": { - "extend": "3.0.1", - "semver": "5.0.3" - }, - "dependencies": { - "semver": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", - "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=" - } + "es6-promisify": "^5.0.0" } }, "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", "dev": true, "requires": { - "co": "4.6.0", - "json-stable-stringify": "1.0.1" + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" } }, "ajv-keywords": { @@ -110,9 +152,9 @@ "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "dev": true, "requires": { - "kind-of": "3.2.2", - "longest": "1.0.1", - "repeat-string": "1.6.1" + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" } }, "amdefine": { @@ -131,7 +173,7 @@ "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", "requires": { - "string-width": "2.1.1" + "string-width": "^2.0.0" }, "dependencies": { "ansi-regex": { @@ -149,8 +191,8 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" } }, "strip-ansi": { @@ -158,7 +200,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } @@ -183,11 +225,11 @@ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", - "integrity": "sha512-NnSOmMEYtVR2JVMIGTzynRkkaxtiq1xnFBcdQD/DnNCYPoEPsVJhM98BDyaoNOQIi7p4okdi3E27eN7GQbsUug==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { - "color-convert": "1.9.1" + "color-convert": "^1.9.0" } }, "ansi-wrap": { @@ -196,44 +238,23 @@ "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", "dev": true }, - "ansicolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansicolors/-/ansicolors-0.2.1.tgz", - "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=", - "dev": true - }, - "apollo-client": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-1.9.3.tgz", - "integrity": "sha512-JABKKbqvcw8DJm3YUkEmyx1SK74i+/DesEtAtyocJi10LLmeMQYQFpg8W3BG1tZsYEQ3owEmPbsdNGTly+VOQg==", - "requires": { - "@types/graphql": "0.10.2", - "apollo-link-core": "0.5.4", - "graphql": "0.10.5", - "graphql-anywhere": "3.1.0", - "graphql-tag": "2.6.1", - "redux": "3.7.2", - "symbol-observable": "1.2.0", - "whatwg-fetch": "2.0.3" - } - }, - "apollo-link-core": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/apollo-link-core/-/apollo-link-core-0.5.4.tgz", - "integrity": "sha512-OxL0Kjizb0eS2ObldDqJEs/tFN9xI9RZuTJcaszgGy+xudoPXhIMCHMr7hGZhy0mK+U+BbBULZJw4YQU4J0ODQ==", + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, "requires": { - "graphql": "0.10.5", - "graphql-tag": "2.6.1", - "zen-observable-ts": "0.4.4" + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" } }, "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", "dev": true, "requires": { - "default-require-extensions": "1.0.0" + "default-require-extensions": "^2.0.0" } }, "archiver": { @@ -241,23 +262,23 @@ "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.3.0.tgz", "integrity": "sha1-TyGU1tj5nfP1MeaIHxTxXVX6ryI=", "requires": { - "archiver-utils": "1.3.0", - "async": "2.6.0", - "buffer-crc32": "0.2.13", - "glob": "7.1.2", - "lodash": "4.17.4", - "readable-stream": "2.3.3", - "tar-stream": "1.5.5", - "walkdir": "0.0.11", - "zip-stream": "1.2.0" + "archiver-utils": "^1.3.0", + "async": "^2.0.0", + "buffer-crc32": "^0.2.1", + "glob": "^7.0.0", + "lodash": "^4.8.0", + "readable-stream": "^2.0.0", + "tar-stream": "^1.5.0", + "walkdir": "^0.0.11", + "zip-stream": "^1.1.0" }, "dependencies": { "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", "requires": { - "lodash": "4.17.4" + "lodash": "^4.17.10" } } } @@ -267,39 +288,36 @@ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz", "integrity": "sha1-5QtMCccL89aA4y/xt5lOn52JUXQ=", "requires": { - "glob": "7.1.2", - "graceful-fs": "4.1.11", - "lazystream": "1.0.0", - "lodash": "4.17.4", - "normalize-path": "2.1.1", - "readable-stream": "2.3.3" + "glob": "^7.0.0", + "graceful-fs": "^4.1.0", + "lazystream": "^1.0.0", + "lodash": "^4.8.0", + "normalize-path": "^2.0.0", + "readable-stream": "^2.0.0" } }, "are-we-there-yet": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz", - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "requires": { - "delegates": "1.0.0", - "readable-stream": "2.3.3" + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" } }, "argparse": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", - "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "requires": { - "sprintf-js": "1.0.3" + "sprintf-js": "~1.0.2" } }, "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "1.1.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true }, "arr-flatten": { "version": "1.1.0", @@ -307,18 +325,29 @@ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, "array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "requires": { - "array-uniq": "1.0.3" + "array-uniq": "^1.0.1" } }, "array-uniq": { @@ -327,9 +356,9 @@ "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" }, "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, "array.prototype.find": { @@ -338,8 +367,8 @@ "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=", "dev": true, "requires": { - "define-properties": "1.1.2", - "es-abstract": "1.10.0" + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" } }, "arrify": { @@ -361,9 +390,9 @@ "dev": true }, "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, "assertion-error": { @@ -372,16 +401,40 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", + "dev": true + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "atob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", + "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", + "dev": true + }, "autolinker": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.15.3.tgz", @@ -389,19 +442,19 @@ "dev": true }, "aws-sdk": { - "version": "2.188.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.188.0.tgz", - "integrity": "sha1-kGKrx9umOTRZ+i80I89dKU8ARhE=", + "version": "2.268.1", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.268.1.tgz", + "integrity": "sha512-X/CNn6ZErrS2ugV6z4rDj7nGmYNbpwJsnSnFc87hr7jo5PTmQoOQQObfBE2YzSoSK5EeXlCaE9j/R6SusUCNkA==", "requires": { "buffer": "4.9.1", "events": "1.1.1", + "ieee754": "1.1.8", "jmespath": "0.15.0", "querystring": "0.2.0", "sax": "1.2.1", "url": "0.10.3", "uuid": "3.1.0", - "xml2js": "0.4.17", - "xmlbuilder": "4.2.1" + "xml2js": "0.4.17" }, "dependencies": { "uuid": { @@ -412,15 +465,15 @@ } }, "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", "dev": true }, "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", + "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", "dev": true }, "babel-code-frame": { @@ -429,9 +482,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "1.1.3", - "esutils": "2.0.2", - "js-tokens": "3.0.2" + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" }, "dependencies": { "ansi-styles": { @@ -446,11 +499,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "supports-color": { @@ -462,30 +515,30 @@ } }, "babel-core": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", - "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", - "dev": true, - "requires": { - "babel-code-frame": "6.26.0", - "babel-generator": "6.26.0", - "babel-helpers": "6.24.1", - "babel-messages": "6.23.0", - "babel-register": "6.26.0", - "babel-runtime": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "convert-source-map": "1.5.1", - "debug": "2.6.9", - "json5": "0.5.1", - "lodash": "4.17.4", - "minimatch": "3.0.4", - "path-is-absolute": "1.0.1", - "private": "0.1.8", - "slash": "1.0.0", - "source-map": "0.5.7" + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" }, "dependencies": { "source-map": { @@ -497,19 +550,19 @@ } }, "babel-generator": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.0.tgz", - "integrity": "sha1-rBriAHC3n248odMmlhMFN3TyDcU=", - "dev": true, - "requires": { - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "detect-indent": "4.0.0", - "jsesc": "1.3.0", - "lodash": "4.17.4", - "source-map": "0.5.7", - "trim-right": "1.0.1" + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" }, "dependencies": { "source-map": { @@ -526,19 +579,18 @@ "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-template": "6.26.0" + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" } }, "babel-jest": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-18.0.0.tgz", - "integrity": "sha1-F+u6jLMoXJBthZ6HB+Tnl5X7ZeM=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.2.0.tgz", + "integrity": "sha1-FKnWo/QSLf6mBp03CFrfJqU6Tbo=", "dev": true, "requires": { - "babel-core": "6.26.0", - "babel-plugin-istanbul": "3.1.2", - "babel-preset-jest": "18.0.0" + "babel-plugin-istanbul": "^4.1.6", + "babel-preset-jest": "^23.2.0" } }, "babel-messages": { @@ -547,34 +599,62 @@ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", "dev": true, "requires": { - "babel-runtime": "6.26.0" + "babel-runtime": "^6.22.0" } }, "babel-plugin-istanbul": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-3.1.2.tgz", - "integrity": "sha1-EdWr3hhCXsJLXWSMfgtdJc01SiI=", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", + "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", "dev": true, "requires": { - "find-up": "1.1.2", - "istanbul-lib-instrument": "1.9.1", - "object-assign": "4.1.1", - "test-exclude": "3.3.0" + "babel-plugin-syntax-object-rest-spread": "^6.13.0", + "find-up": "^2.1.0", + "istanbul-lib-instrument": "^1.10.1", + "test-exclude": "^4.2.1" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + } } }, "babel-plugin-jest-hoist": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-18.0.0.tgz", - "integrity": "sha1-QVDnDsq1YObnNErchJSYBy004So=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz", + "integrity": "sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=", + "dev": true + }, + "babel-plugin-syntax-object-rest-spread": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", + "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", "dev": true }, + "babel-polyfill": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", + "requires": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + } + }, "babel-preset-jest": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-18.0.0.tgz", - "integrity": "sha1-hPr4yj7GWrp9Xj9Zu67ZNaskBJ4=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", + "integrity": "sha1-jsegOhOPABoaj7HoETZSvxpV2kY=", "dev": true, "requires": { - "babel-plugin-jest-hoist": "18.0.0" + "babel-plugin-jest-hoist": "^23.2.0", + "babel-plugin-syntax-object-rest-spread": "^6.13.0" } }, "babel-register": { @@ -583,23 +663,46 @@ "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", "dev": true, "requires": { - "babel-core": "6.26.0", - "babel-runtime": "6.26.0", - "core-js": "2.5.3", - "home-or-tmp": "2.0.0", - "lodash": "4.17.4", - "mkdirp": "0.5.1", - "source-map-support": "0.4.18" + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "dev": true, + "requires": { + "source-map": "^0.5.6" + } + } } }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { - "core-js": "2.5.3", - "regenerator-runtime": "0.11.1" + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" + } } }, "babel-template": { @@ -608,11 +711,11 @@ "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "lodash": "4.17.4" + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" } }, "babel-traverse": { @@ -621,15 +724,15 @@ "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "babel-messages": "6.23.0", - "babel-runtime": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "debug": "2.6.9", - "globals": "9.18.0", - "invariant": "2.2.2", - "lodash": "4.17.4" + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" } }, "babel-types": { @@ -638,10 +741,10 @@ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "dev": true, "requires": { - "babel-runtime": "6.26.0", - "esutils": "2.0.2", - "lodash": "4.17.4", - "to-fast-properties": "1.0.3" + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" } }, "babylon": { @@ -655,27 +758,89 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, "base64-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", - "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, "optional": true, "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, "bl": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.1.tgz", - "integrity": "sha1-ysMo977kVzDUBLaSID/LWQ4XLV4=", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "requires": { - "readable-stream": "2.3.3" + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" } }, "bluebird": { @@ -683,13 +848,21 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "dev": true, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", "requires": { - "hoek": "2.16.3" + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" } }, "boxen": { @@ -697,13 +870,13 @@ "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", "requires": { - "ansi-align": "2.0.0", - "camelcase": "4.1.0", - "chalk": "2.3.0", - "cli-boxes": "1.0.0", - "string-width": "2.1.1", - "term-size": "1.2.0", - "widest-line": "2.0.0" + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" }, "dependencies": { "ansi-regex": { @@ -721,8 +894,8 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" } }, "strip-ansi": { @@ -730,35 +903,59 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } }, "brace-expansion": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { - "balanced-match": "1.0.0", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "1.8.2", - "preserve": "0.2.0", - "repeat-element": "1.1.2" + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, + "browser-process-hrtime": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz", + "integrity": "sha1-Ql1opY00R/AqBKqJQYf86K+Le44=", + "dev": true + }, "browser-resolve": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", - "integrity": "sha1-j/CbCixCFxihBRwmCzLkj0QpOM4=", + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", "dev": true, "requires": { "resolve": "1.1.7" @@ -773,18 +970,18 @@ } }, "browser-stdout": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, "bser": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bser/-/bser-1.0.2.tgz", - "integrity": "sha1-OBEWlwsqbe6lZG3RXdcnhES1YWk=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", + "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", "dev": true, "requires": { - "node-int64": "0.4.0" + "node-int64": "^0.4.0" } }, "buffer": { @@ -792,29 +989,75 @@ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "requires": { - "base64-js": "1.2.1", - "ieee754": "1.1.8", - "isarray": "1.0.0" + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "requires": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" } }, + "buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==" + }, "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" }, + "buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=" + }, + "buffer-from": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", + "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==" + }, "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, "caller-id": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-id/-/caller-id-0.1.0.tgz", "integrity": "sha1-Wb2sCJPRLDhxQIJ5Ix+XRYNk8Hs=", "dev": true, "requires": { - "stack-trace": "0.0.9" + "stack-trace": "~0.0.7" } }, "caller-path": { @@ -823,7 +1066,7 @@ "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", "dev": true, "requires": { - "callsites": "0.2.0" + "callsites": "^0.2.0" } }, "callsites": { @@ -837,25 +1080,24 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" }, + "capture-exit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz", + "integrity": "sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28=", + "dev": true, + "requires": { + "rsvp": "^3.3.3" + } + }, "capture-stack-trace": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=" }, - "cardinal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-1.0.0.tgz", - "integrity": "sha1-UOIcGwqjdyn5N33vGWtanOyTLuk=", - "dev": true, - "requires": { - "ansicolors": "0.2.1", - "redeyed": "1.0.1" - } - }, "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, "caw": { @@ -863,10 +1105,10 @@ "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", "requires": { - "get-proxy": "2.1.0", - "isurl": "1.0.0", - "tunnel-agent": "0.6.0", - "url-to-options": "1.0.1" + "get-proxy": "^2.0.0", + "isurl": "^1.0.0-alpha5", + "tunnel-agent": "^0.6.0", + "url-to-options": "^1.0.1" } }, "center-align": { @@ -876,8 +1118,8 @@ "dev": true, "optional": true, "requires": { - "align-text": "0.1.4", - "lazy-cache": "1.0.4" + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" } }, "chai": { @@ -886,9 +1128,9 @@ "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", "dev": true, "requires": { - "assertion-error": "1.1.0", - "deep-eql": "0.1.3", - "type-detect": "1.0.0" + "assertion-error": "^1.0.1", + "deep-eql": "^0.1.3", + "type-detect": "^1.0.0" } }, "chai-as-promised": { @@ -897,17 +1139,17 @@ "integrity": "sha1-GgKkM6byTa+sY7nJb6FoTbGqjaY=", "dev": true, "requires": { - "check-error": "1.0.2" + "check-error": "^1.0.2" } }, "chalk": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", - "integrity": "sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "requires": { - "ansi-styles": "3.2.0", - "escape-string-regexp": "1.0.5", - "supports-color": "4.5.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, "check-error": { @@ -917,9 +1159,9 @@ "dev": true }, "ci-info": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.2.tgz", - "integrity": "sha512-uTGIPNx/nSpBdsF6xnseRXLLtfr9VLqkz8ZqHXr3Y7b6SftyRxBGjwMtJj1OhNbmlc1wZzLNAlAcvyIiE8a6ZA==" + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz", + "integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==" }, "circular-json": { "version": "0.3.3", @@ -927,6 +1169,29 @@ "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", "dev": true }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, "cli-boxes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", @@ -937,26 +1202,7 @@ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", "requires": { - "restore-cursor": "1.0.1" - } - }, - "cli-table": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", - "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", - "dev": true, - "requires": { - "colors": "1.0.3" - } - }, - "cli-usage": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/cli-usage/-/cli-usage-0.1.7.tgz", - "integrity": "sha512-x/Q52iLSZsRrRb2ePmTsVYXrGcrPQ8G4yRAY7QpMlumxAfPVrnDOH2X6Z5s8qsAX7AA7YuIi8AXFrvH0wWEesA==", - "dev": true, - "requires": { - "marked": "0.3.12", - "marked-terminal": "2.0.0" + "restore-cursor": "^1.0.1" } }, "cli-width": { @@ -971,8 +1217,8 @@ "dev": true, "optional": true, "requires": { - "center-align": "0.1.3", - "right-align": "0.1.3", + "center-align": "^0.1.1", + "right-align": "^0.1.1", "wordwrap": "0.0.2" }, "dependencies": { @@ -985,6 +1231,12 @@ } } }, + "clorox": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clorox/-/clorox-1.0.3.tgz", + "integrity": "sha512-w3gKAUKMJYmmaJyc+p+iDrDtLvsFasrx/y6/zWo2U1TZfsz3y4Vl4T9PHCZrOwk1eMTOSRI6xHdpDR4PhTdy8Q==", + "dev": true + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -1002,31 +1254,35 @@ "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", "dev": true }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.2.tgz", + "integrity": "sha512-3NUJZdhMhcdPn8vJ9v2UQJoH0qqoGUkYTgFEPZaPjEtwmmKUfNV46zZmgB2M5M4DCEQHMaCfWHCxiBflLm04Tg==", "requires": { - "color-name": "1.1.3" + "color-name": "1.1.1" } }, "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha1-SxQVMEz1ACjqgWQ2Q72C6gWANok=" }, "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", + "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "commander": { @@ -1034,9 +1290,15 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", "requires": { - "graceful-readlink": "1.0.1" + "graceful-readlink": ">= 1.0.0" } }, + "compare-versions": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.3.0.tgz", + "integrity": "sha512-MAAAIOdi2s4Gl6rZ76PNcUa9IOYB+5ICdT41o5uMRf09aEu/F9RK+qhe8RjXNPwcTjGV7KU7h2P/fljThFVqyQ==", + "dev": true + }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", @@ -1047,10 +1309,10 @@ "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-1.2.2.tgz", "integrity": "sha1-UkqfEJA/OoEzibAiXSfEi7dRiQ8=", "requires": { - "buffer-crc32": "0.2.13", - "crc32-stream": "2.0.0", - "normalize-path": "2.1.1", - "readable-stream": "2.3.3" + "buffer-crc32": "^0.2.1", + "crc32-stream": "^2.0.0", + "normalize-path": "^2.0.0", + "readable-stream": "^2.0.0" } }, "concat-map": { @@ -1059,13 +1321,14 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "requires": { - "inherits": "2.0.3", - "readable-stream": "2.3.3", - "typedarray": "0.0.6" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" } }, "config-chain": { @@ -1073,21 +1336,21 @@ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", "requires": { - "ini": "1.3.5", - "proto-list": "1.2.4" + "ini": "^1.3.4", + "proto-list": "~1.2.1" } }, "configstore": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.1.tgz", - "integrity": "sha512-5oNkD/L++l0O6xGXxb1EWS7SivtjfGQlRyxJsYgE0Z495/L81e2h4/d3r969hoPXuFItzNOKMtsXgYG4c7dYvw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", "requires": { - "dot-prop": "4.2.0", - "graceful-fs": "4.1.11", - "make-dir": "1.1.0", - "unique-string": "1.0.0", - "write-file-atomic": "2.3.0", - "xdg-basedir": "3.0.0" + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" } }, "contains-path": { @@ -1096,11 +1359,15 @@ "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", "dev": true }, - "content-type-parser": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/content-type-parser/-/content-type-parser-1.0.2.tgz", - "integrity": "sha512-lM4l4CnMEwOLHAHr/P6MEZwZFPJFtAAKgL6pogbXmVZggIqXhdB6RbBtPOTsw2FcXwYhehRGERJmRrjOiIB8pQ==", - "dev": true + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "convert-source-map": { "version": "1.5.1", @@ -1113,51 +1380,53 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" }, - "cookiejar": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz", - "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=" + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, - "core-js": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", - "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=", + "cookiejar": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz", + "integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==" + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", + "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "coveralls": { - "version": "2.13.3", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-2.13.3.tgz", - "integrity": "sha512-iiAmn+l1XqRwNLXhW8Rs5qHZRFMYp9ZIPjEOVRpC/c4so6Y/f4/lFi0FfR5B9cCqgyhkJ5cZmbvcVRfP8MHchw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.2.tgz", + "integrity": "sha512-Tv0LKe/MkBOilH2v7WBiTBdudg2ChfGbdXafc/s330djpF3zKOmuehTeRwjXWc7pzfj9FrDUTA7tEx6Div8NFw==", "dev": true, "requires": { - "js-yaml": "3.6.1", - "lcov-parse": "0.0.10", - "log-driver": "1.2.5", - "minimist": "1.2.0", - "request": "2.79.0" - }, - "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "js-yaml": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz", - "integrity": "sha1-bl/mfYsgXOTSL60Ft3geja3MSzA=", - "dev": true, - "requires": { - "argparse": "1.0.9", - "esprima": "2.7.3" - } - } + "growl": "~> 1.10.0", + "js-yaml": "^3.11.0", + "lcov-parse": "^0.0.10", + "log-driver": "^1.2.7", + "minimist": "^1.2.0", + "request": "^2.85.0" } }, "crc": { @@ -1170,8 +1439,8 @@ "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-2.0.0.tgz", "integrity": "sha1-483TtN8xaN10494/u8t7KX/pCPQ=", "requires": { - "crc": "3.5.0", - "readable-stream": "2.3.3" + "crc": "^3.4.4", + "readable-stream": "^2.0.0" } }, "create-error-class": { @@ -1179,7 +1448,7 @@ "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "requires": { - "capture-stack-trace": "1.0.0" + "capture-stack-trace": "^1.0.0" } }, "cross-spawn": { @@ -1187,18 +1456,9 @@ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { - "lru-cache": "4.1.1", - "shebang-command": "1.2.0", - "which": "1.3.0" - } - }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "dev": true, - "requires": { - "boom": "2.10.1" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "crypto-random-string": { @@ -1207,18 +1467,18 @@ "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" }, "cssom": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz", - "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz", + "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==", "dev": true }, "cssstyle": { - "version": "0.2.37", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", - "integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.3.1.tgz", + "integrity": "sha512-tNvaxM5blOnxanyxI6panOsnfiyLRj3HV4qjqqS45WPNS1usdYWRUQjqTEEELK73lpeP/1KoIGYUwrBn/VcECA==", "dev": true, "requires": { - "cssom": "0.3.2" + "cssom": "0.3.x" } }, "d": { @@ -1227,7 +1487,7 @@ "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "dev": true, "requires": { - "es5-ext": "0.10.38" + "es5-ext": "^0.10.9" } }, "damerau-levenshtein": { @@ -1242,15 +1502,18 @@ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.0.0.tgz", + "integrity": "sha512-ai40PPQR0Fn1lD2PPie79CibnlMN2AYiDhwFX/rZHVsxbs5kNJSjegqXIprhouGXlRdEnfybva7kqRGnB6mypA==", + "dev": true, + "requires": { + "abab": "^1.0.4", + "whatwg-mimetype": "^2.0.0", + "whatwg-url": "^6.4.0" } }, "debug": { @@ -1267,19 +1530,25 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true + }, "decompress": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.0.tgz", "integrity": "sha1-eu3YVCflqS2s/lVnSnxQXpbQH50=", "requires": { - "decompress-tar": "4.1.1", - "decompress-tarbz2": "4.1.1", - "decompress-targz": "4.1.1", - "decompress-unzip": "4.0.1", - "graceful-fs": "4.1.11", - "make-dir": "1.1.0", - "pify": "2.3.0", - "strip-dirs": "2.1.0" + "decompress-tar": "^4.0.0", + "decompress-tarbz2": "^4.0.0", + "decompress-targz": "^4.0.0", + "decompress-unzip": "^4.0.1", + "graceful-fs": "^4.1.10", + "make-dir": "^1.0.0", + "pify": "^2.3.0", + "strip-dirs": "^2.0.0" } }, "decompress-tar": { @@ -1287,9 +1556,9 @@ "resolved": "https://registry.npmjs.org/decompress-tar/-/decompress-tar-4.1.1.tgz", "integrity": "sha512-JdJMaCrGpB5fESVyxwpCx4Jdj2AagLmv3y58Qy4GE6HMVjWz1FeVQk1Ct4Kye7PftcdOo/7U7UKzYBJgqnGeUQ==", "requires": { - "file-type": "5.2.0", - "is-stream": "1.1.0", - "tar-stream": "1.5.5" + "file-type": "^5.2.0", + "is-stream": "^1.1.0", + "tar-stream": "^1.5.2" } }, "decompress-tarbz2": { @@ -1297,11 +1566,11 @@ "resolved": "https://registry.npmjs.org/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz", "integrity": "sha512-s88xLzf1r81ICXLAVQVzaN6ZmX4A6U4z2nMbOwobxkLoIIfjVMBg7TeguTUXkKeXni795B6y5rnvDw7rxhAq9A==", "requires": { - "decompress-tar": "4.1.1", - "file-type": "6.2.0", - "is-stream": "1.1.0", - "seek-bzip": "1.0.5", - "unbzip2-stream": "1.2.5" + "decompress-tar": "^4.1.0", + "file-type": "^6.1.0", + "is-stream": "^1.1.0", + "seek-bzip": "^1.0.5", + "unbzip2-stream": "^1.0.9" }, "dependencies": { "file-type": { @@ -1316,9 +1585,9 @@ "resolved": "https://registry.npmjs.org/decompress-targz/-/decompress-targz-4.1.1.tgz", "integrity": "sha512-4z81Znfr6chWnRDNfFNqLwPvm4db3WuZkqV+UgXQzSngG3CEKdBkw5jrv3axjjL96glyiiKjsxJG3X6WBZwX3w==", "requires": { - "decompress-tar": "4.1.1", - "file-type": "5.2.0", - "is-stream": "1.1.0" + "decompress-tar": "^4.1.1", + "file-type": "^5.2.0", + "is-stream": "^1.1.0" } }, "decompress-unzip": { @@ -1326,10 +1595,10 @@ "resolved": "https://registry.npmjs.org/decompress-unzip/-/decompress-unzip-4.0.1.tgz", "integrity": "sha1-3qrM39FK6vhVePczroIQ+bSEj2k=", "requires": { - "file-type": "3.9.0", - "get-stream": "2.3.1", - "pify": "2.3.0", - "yauzl": "2.9.1" + "file-type": "^3.8.0", + "get-stream": "^2.2.0", + "pify": "^2.3.0", + "yauzl": "^2.4.2" }, "dependencies": { "file-type": { @@ -1342,8 +1611,8 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-2.3.1.tgz", "integrity": "sha1-Xzj5PzRgCWZu4BUKBUFn+Rvdld4=", "requires": { - "object-assign": "4.1.1", - "pinkie-promise": "2.0.1" + "object-assign": "^4.0.1", + "pinkie-promise": "^2.0.0" } } } @@ -1366,9 +1635,9 @@ } }, "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "deep-is": { "version": "0.1.3", @@ -1383,23 +1652,12 @@ "dev": true }, "default-require-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", - "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", "dev": true, "requires": { - "strip-bom": "2.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "0.2.1" - } - } + "strip-bom": "^3.0.0" } }, "define-properties": { @@ -1408,8 +1666,55 @@ "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", "dev": true, "requires": { - "foreach": "2.0.5", - "object-keys": "1.0.11" + "foreach": "^2.0.5", + "object-keys": "^1.0.8" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } } }, "del": { @@ -1418,13 +1723,13 @@ "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", "dev": true, "requires": { - "globby": "5.0.0", - "is-path-cwd": "1.0.0", - "is-path-in-cwd": "1.0.0", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "rimraf": "2.6.2" + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" }, "dependencies": { "globby": { @@ -1433,12 +1738,12 @@ "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", "dev": true, "requires": { - "array-union": "1.0.2", - "arrify": "1.0.1", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } } } @@ -1453,15 +1758,31 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, "detect-indent": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", "dev": true, "requires": { - "repeating": "2.0.1" + "repeating": "^2.0.0" } }, + "detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", + "dev": true + }, "diacritics-map": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/diacritics-map/-/diacritics-map-0.1.0.tgz", @@ -1469,9 +1790,9 @@ "dev": true }, "diff": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.4.0.tgz", - "integrity": "sha512-QpVuMTEoJMF7cKzi6bvWhRulU1fZqZnvyVQgNhPaxxuTYwyjn/j1v9falseQ/uXWwPnO56RBfwtg4h/EQXmucA==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, "doctrine": { @@ -1480,7 +1801,16 @@ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "requires": { - "esutils": "2.0.2" + "esutils": "^2.0.2" + } + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" } }, "dot-prop": { @@ -1488,7 +1818,7 @@ "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", "requires": { - "is-obj": "1.0.1" + "is-obj": "^1.0.0" } }, "download": { @@ -1496,13 +1826,13 @@ "resolved": "https://registry.npmjs.org/download/-/download-5.0.3.tgz", "integrity": "sha1-Y1N/l3+ZJmow64oqL70fILgAD3o=", "requires": { - "caw": "2.0.1", - "decompress": "4.2.0", - "filenamify": "2.0.0", - "get-stream": "3.0.0", - "got": "6.7.1", - "mkdirp": "0.5.1", - "pify": "2.3.0" + "caw": "^2.0.0", + "decompress": "^4.0.0", + "filenamify": "^2.0.0", + "get-stream": "^3.0.0", + "got": "^6.3.0", + "mkdirp": "^0.5.1", + "pify": "^2.3.0" } }, "duplexer3": { @@ -1517,15 +1847,25 @@ "dev": true, "optional": true, "requires": { - "jsbn": "0.1.1" + "jsbn": "~0.1.0" } }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, "encoding": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", "requires": { - "iconv-lite": "0.4.19" + "iconv-lite": "~0.4.13" } }, "end-of-stream": { @@ -1533,38 +1873,29 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "requires": { - "once": "1.4.0" - } - }, - "errno": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.6.tgz", - "integrity": "sha512-IsORQDpaaSwcDP4ZZnHxgE85werpo34VYn1Ud3mq+eUsF593faR8oCZNXrROVkpFu2TsbrNhHin0aUrTsQ9vNw==", - "dev": true, - "requires": { - "prr": "1.0.1" + "once": "^1.4.0" } }, "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, "requires": { - "is-arrayish": "0.2.1" + "is-arrayish": "^0.2.1" } }, "es-abstract": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.10.0.tgz", - "integrity": "sha512-/uh/DhdqIOSkAWifU+8nG78vlQxdLckUdI/sPgy0VhuXi2qJ7T8czBmqIYtLQVpCIFYafChnsRsB5pyb1JdmCQ==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", + "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", "dev": true, "requires": { - "es-to-primitive": "1.1.1", - "function-bind": "1.1.1", - "has": "1.0.1", - "is-callable": "1.1.3", - "is-regex": "1.0.4" + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" } }, "es-to-primitive": { @@ -1573,19 +1904,20 @@ "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", "dev": true, "requires": { - "is-callable": "1.1.3", - "is-date-object": "1.0.1", - "is-symbol": "1.0.1" + "is-callable": "^1.1.1", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.1" } }, "es5-ext": { - "version": "0.10.38", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.38.tgz", - "integrity": "sha512-jCMyePo7AXbUESwbl8Qi01VSH2piY9s/a3rSU/5w/MlTIx8HPL1xn2InGN8ejt/xulcJgnTO7vqNtOAxzYd2Kg==", + "version": "0.10.45", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.45.tgz", + "integrity": "sha512-FkfM6Vxxfmztilbxxz5UKSD4ICMf5tSpRFtDNtkAhOxZ0EKtX6qwmXNyH/sFyIbX2P/nU5AMiA9jilWsUGJzCQ==", "dev": true, "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.1", + "next-tick": "1" } }, "es6-iterator": { @@ -1594,9 +1926,9 @@ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.38", - "es6-symbol": "3.1.1" + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" } }, "es6-map": { @@ -1605,19 +1937,26 @@ "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.38", - "es6-iterator": "2.0.3", - "es6-set": "0.1.5", - "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" } }, "es6-promise": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", - "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=", - "dev": true + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", + "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + } }, "es6-set": { "version": "0.1.5", @@ -1625,11 +1964,11 @@ "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.38", - "es6-iterator": "2.0.3", + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", "es6-symbol": "3.1.1", - "event-emitter": "0.3.5" + "event-emitter": "~0.3.5" } }, "es6-symbol": { @@ -1638,8 +1977,8 @@ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.38" + "d": "1", + "es5-ext": "~0.10.14" } }, "es6-weak-map": { @@ -1648,12 +1987,17 @@ "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.38", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" + "d": "1", + "es5-ext": "^0.10.14", + "es6-iterator": "^2.0.1", + "es6-symbol": "^3.1.1" } }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", @@ -1665,11 +2009,11 @@ "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", "dev": true, "requires": { - "esprima": "2.7.3", - "estraverse": "1.9.3", - "esutils": "2.0.2", - "optionator": "0.8.2", - "source-map": "0.2.0" + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" }, "dependencies": { "esprima": { @@ -1683,6 +2027,16 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", "dev": true + }, + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } } } }, @@ -1692,10 +2046,10 @@ "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", "dev": true, "requires": { - "es6-map": "0.1.5", - "es6-weak-map": "2.0.2", - "esrecurse": "4.2.0", - "estraverse": "4.2.0" + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" } }, "eslint": { @@ -1704,41 +2058,41 @@ "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", "dev": true, "requires": { - "babel-code-frame": "6.26.0", - "chalk": "1.1.3", - "concat-stream": "1.6.0", - "debug": "2.6.9", - "doctrine": "2.1.0", - "escope": "3.6.0", - "espree": "3.5.2", - "esquery": "1.0.0", - "estraverse": "4.2.0", - "esutils": "2.0.2", - "file-entry-cache": "2.0.0", - "glob": "7.1.2", - "globals": "9.18.0", - "ignore": "3.3.7", - "imurmurhash": "0.1.4", - "inquirer": "0.12.0", - "is-my-json-valid": "2.17.1", - "is-resolvable": "1.1.0", - "js-yaml": "3.10.0", - "json-stable-stringify": "1.0.1", - "levn": "0.3.0", - "lodash": "4.17.4", - "mkdirp": "0.5.1", - "natural-compare": "1.4.0", - "optionator": "0.8.2", - "path-is-inside": "1.0.2", - "pluralize": "1.2.1", - "progress": "1.1.8", - "require-uncached": "1.0.3", - "shelljs": "0.7.8", - "strip-bom": "3.0.0", - "strip-json-comments": "2.0.1", - "table": "3.8.3", - "text-table": "0.2.0", - "user-home": "2.0.0" + "babel-code-frame": "^6.16.0", + "chalk": "^1.1.3", + "concat-stream": "^1.5.2", + "debug": "^2.1.1", + "doctrine": "^2.0.0", + "escope": "^3.6.0", + "espree": "^3.4.0", + "esquery": "^1.0.0", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "glob": "^7.0.3", + "globals": "^9.14.0", + "ignore": "^3.2.0", + "imurmurhash": "^0.1.4", + "inquirer": "^0.12.0", + "is-my-json-valid": "^2.10.0", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.5.1", + "json-stable-stringify": "^1.0.0", + "levn": "^0.3.0", + "lodash": "^4.0.0", + "mkdirp": "^0.5.0", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.1", + "pluralize": "^1.2.1", + "progress": "^1.1.8", + "require-uncached": "^1.0.2", + "shelljs": "^0.7.5", + "strip-bom": "^3.0.0", + "strip-json-comments": "~2.0.1", + "table": "^3.7.8", + "text-table": "~0.2.0", + "user-home": "^2.0.0" }, "dependencies": { "ansi-styles": { @@ -1753,11 +2107,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "inquirer": { @@ -1766,19 +2120,19 @@ "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", "dev": true, "requires": { - "ansi-escapes": "1.4.0", - "ansi-regex": "2.1.1", - "chalk": "1.1.3", - "cli-cursor": "1.0.2", - "cli-width": "2.2.0", - "figures": "1.7.0", - "lodash": "4.17.4", - "readline2": "1.0.1", - "run-async": "0.1.0", - "rx-lite": "3.1.2", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "through": "2.3.8" + "ansi-escapes": "^1.1.0", + "ansi-regex": "^2.0.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^2.0.0", + "figures": "^1.3.5", + "lodash": "^4.3.0", + "readline2": "^1.0.1", + "run-async": "^0.1.0", + "rx-lite": "^3.1.2", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" } }, "run-async": { @@ -1787,7 +2141,7 @@ "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", "dev": true, "requires": { - "once": "1.4.0" + "once": "^1.3.0" } }, "supports-color": { @@ -1804,7 +2158,7 @@ "integrity": "sha1-pHAQhkbWxF4fY5oD8R1QShqkrtw=", "dev": true, "requires": { - "eslint-config-airbnb-base": "5.0.3" + "eslint-config-airbnb-base": "^5.0.2" } }, "eslint-config-airbnb-base": { @@ -1819,9 +2173,9 @@ "integrity": "sha1-Wt2BBujJKNssuiMrzZ76hG49oWw=", "dev": true, "requires": { - "debug": "2.6.9", - "object-assign": "4.1.1", - "resolve": "1.5.0" + "debug": "^2.2.0", + "object-assign": "^4.0.1", + "resolve": "^1.1.6" } }, "eslint-plugin-import": { @@ -1830,22 +2184,22 @@ "integrity": "sha1-svoH68xTUE0PKkR3WC7Iv/GHG58=", "dev": true, "requires": { - "builtin-modules": "1.1.1", - "contains-path": "0.1.0", - "debug": "2.6.9", - "doctrine": "1.3.0", - "es6-map": "0.1.5", - "es6-set": "0.1.5", - "eslint-import-resolver-node": "0.2.3", - "has": "1.0.1", - "lodash.cond": "4.5.2", - "lodash.endswith": "4.2.1", - "lodash.find": "4.6.0", - "lodash.findindex": "4.6.0", - "minimatch": "3.0.4", - "object-assign": "4.1.1", - "pkg-dir": "1.0.0", - "pkg-up": "1.0.0" + "builtin-modules": "^1.1.1", + "contains-path": "^0.1.0", + "debug": "^2.2.0", + "doctrine": "1.3.x", + "es6-map": "^0.1.3", + "es6-set": "^0.1.4", + "eslint-import-resolver-node": "^0.2.0", + "has": "^1.0.1", + "lodash.cond": "^4.3.0", + "lodash.endswith": "^4.0.1", + "lodash.find": "^4.3.0", + "lodash.findindex": "^4.3.0", + "minimatch": "^3.0.3", + "object-assign": "^4.0.1", + "pkg-dir": "^1.0.0", + "pkg-up": "^1.0.0" }, "dependencies": { "doctrine": { @@ -1854,8 +2208,8 @@ "integrity": "sha1-E+dWgrVVGEJCdvfBc3g0Vu+RPSY=", "dev": true, "requires": { - "esutils": "2.0.2", - "isarray": "1.0.0" + "esutils": "^2.0.2", + "isarray": "^1.0.0" } } } @@ -1866,9 +2220,9 @@ "integrity": "sha1-TjXLcbin23AqxBXIBuuOjZ6mxl0=", "dev": true, "requires": { - "damerau-levenshtein": "1.0.4", - "jsx-ast-utils": "1.4.1", - "object-assign": "4.1.1" + "damerau-levenshtein": "^1.0.0", + "jsx-ast-utils": "^1.0.0", + "object-assign": "^4.0.1" } }, "eslint-plugin-react": { @@ -1877,11 +2231,11 @@ "integrity": "sha1-xUNb6wZ3ThLH2y9qut3L+QDNP3g=", "dev": true, "requires": { - "array.prototype.find": "2.0.4", - "doctrine": "1.5.0", - "has": "1.0.1", - "jsx-ast-utils": "1.4.1", - "object.assign": "4.1.0" + "array.prototype.find": "^2.0.1", + "doctrine": "^1.2.2", + "has": "^1.0.1", + "jsx-ast-utils": "^1.3.4", + "object.assign": "^4.0.4" }, "dependencies": { "doctrine": { @@ -1890,20 +2244,20 @@ "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", "dev": true, "requires": { - "esutils": "2.0.2", - "isarray": "1.0.0" + "esutils": "^2.0.2", + "isarray": "^1.0.0" } } } }, "espree": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.2.tgz", - "integrity": "sha512-sadKeYwaR/aJ3stC2CdvgXu1T16TdYN+qwCpcWbMnGJ8s0zNWemzrvb2GbD4OhmJ/fwpJjudThAlLobGbWZbCQ==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", "dev": true, "requires": { - "acorn": "5.3.0", - "acorn-jsx": "3.0.1" + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" } }, "esprima": { @@ -1912,22 +2266,21 @@ "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" }, "esquery": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", - "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", + "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", "dev": true, "requires": { - "estraverse": "4.2.0" + "estraverse": "^4.0.0" } }, "esrecurse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", - "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", "dev": true, "requires": { - "estraverse": "4.2.0", - "object-assign": "4.1.1" + "estraverse": "^4.1.0" } }, "estraverse": { @@ -1942,14 +2295,19 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, "event-emitter": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", "dev": true, "requires": { - "d": "1.0.0", - "es5-ext": "0.10.38" + "d": "1", + "es5-ext": "~0.10.14" } }, "events": { @@ -1958,12 +2316,12 @@ "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" }, "exec-sh": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.1.tgz", - "integrity": "sha512-aLt95pexaugVtQerpmE51+4QfWrNc304uez7jvj6fWnN8GeEHpttB8F36n8N7uVhUMbH/1enbxQ9HImZ4w/9qg==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", + "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", "dev": true, "requires": { - "merge": "1.2.0" + "merge": "^1.2.0" } }, "execa": { @@ -1971,27 +2329,59 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" } }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, "exit-hook": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=" }, "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "0.1.1" + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, "expand-range": { @@ -2000,7 +2390,159 @@ "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", "dev": true, "requires": { - "fill-range": "2.2.3" + "fill-range": "^2.1.0" + }, + "dependencies": { + "fill-range": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "dev": true, + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "expect": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-23.2.0.tgz", + "integrity": "sha1-U6fhNeNv4n51hnsReP8IqqzCsN0=", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "jest-diff": "^23.2.0", + "jest-get-type": "^22.1.0", + "jest-matcher-utils": "^23.2.0", + "jest-message-util": "^23.2.0", + "jest-regex-util": "^23.0.0" + } + }, + "express": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", + "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.2", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.3", + "qs": "6.5.1", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.1", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", + "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.1", + "http-errors": "~1.6.2", + "iconv-lite": "0.4.19", + "on-finished": "~2.3.0", + "qs": "6.5.1", + "raw-body": "2.3.2", + "type-is": "~1.6.15" + } + }, + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + }, + "raw-body": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", + "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.2", + "iconv-lite": "0.4.19", + "unpipe": "1.0.0" + }, + "dependencies": { + "depd": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", + "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" + }, + "http-errors": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", + "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + } + }, + "setprototypeof": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + } + } + }, + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } } }, "extend": { @@ -2009,12 +2551,24 @@ "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "is-extendable": "0.1.1" + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } } }, "external-editor": { @@ -2022,18 +2576,80 @@ "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-1.1.1.tgz", "integrity": "sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=", "requires": { - "extend": "3.0.1", - "spawn-sync": "1.0.15", - "tmp": "0.0.29" + "extend": "^3.0.0", + "spawn-sync": "^1.0.15", + "tmp": "^0.0.29" } }, "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } } }, "extsprintf": { @@ -2042,26 +2658,38 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, "fb-watchman": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-1.9.2.tgz", - "integrity": "sha1-okz0eCf4LTj7Waaa1wt247auc4M=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", + "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", "dev": true, "requires": { - "bser": "1.0.2" + "bser": "^2.0.0" } }, "fd-slicer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", - "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "requires": { - "pend": "1.2.0" + "pend": "~1.2.0" } }, "figures": { @@ -2069,8 +2697,8 @@ "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", "requires": { - "escape-string-regexp": "1.0.5", - "object-assign": "4.1.1" + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" } }, "file-entry-cache": { @@ -2079,8 +2707,8 @@ "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", "dev": true, "requires": { - "flat-cache": "1.3.0", - "object-assign": "4.1.1" + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" } }, "file-type": { @@ -2088,25 +2716,19 @@ "resolved": "https://registry.npmjs.org/file-type/-/file-type-5.2.0.tgz", "integrity": "sha1-LdvqfHP/42No365J3DOMBYwritY=" }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, "filename-reserved-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=" }, "filenamify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.0.0.tgz", - "integrity": "sha1-vRYiYsC26Uv7zc8Zo7uzdk94VpU=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-2.1.0.tgz", + "integrity": "sha512-ICw7NTT6RsDp2rnYKVd8Fu4cr6ITzGy3+u4vUujPkabyaz+03F24NWEX7fs5fp+kBonlaqPH8fAO2NM+SXt/JA==", "requires": { - "filename-reserved-regex": "2.0.0", - "strip-outer": "1.0.0", - "trim-repeated": "1.0.0" + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.0", + "trim-repeated": "^1.0.0" } }, "fileset": { @@ -2115,14 +2737,14 @@ "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", "dev": true, "requires": { - "glob": "7.1.2", - "minimatch": "3.0.4" + "glob": "^7.0.3", + "minimatch": "^3.0.3" } }, "filesize": { - "version": "3.5.11", - "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.5.11.tgz", - "integrity": "sha512-ZH7loueKBoDb7yG9esn1U+fgq7BzlzW6NRi5/rMdxIZ05dj7GFD/Xc5rq2CDt5Yq86CyfSYVyx4242QQNZbx1g==" + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz", + "integrity": "sha512-7KjR1vv6qnicaPMi1iiTcI85CyYwRO/PSFCu6SvqL8jN2Wjt/NIYQTFtFs7fSDCYOstUkEWIQGFUg5YZQfjlcg==" }, "fill-keys": { "version": "1.0.2", @@ -2130,21 +2752,52 @@ "integrity": "sha1-mo+jb06K1jTjv2tPPIiCVRRS6yA=", "dev": true, "requires": { - "is-object": "1.0.1", - "merge-descriptors": "1.0.1" + "is-object": "~1.0.1", + "merge-descriptors": "~1.0.0" } }, "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "is-number": "2.1.0", - "isobject": "2.1.0", - "randomatic": "1.1.7", - "repeat-element": "1.1.2", - "repeat-string": "1.6.1" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } } }, "find-up": { @@ -2153,8 +2806,8 @@ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "dev": true, "requires": { - "path-exists": "2.1.0", - "pinkie-promise": "2.0.1" + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "flat-cache": { @@ -2163,10 +2816,10 @@ "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", "dev": true, "requires": { - "circular-json": "0.3.3", - "del": "2.2.2", - "graceful-fs": "4.1.11", - "write": "0.2.1" + "circular-json": "^0.3.1", + "del": "^2.0.2", + "graceful-fs": "^4.1.2", + "write": "^0.2.1" } }, "for-in": { @@ -2175,15 +2828,6 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "1.0.2" - } - }, "foreach": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", @@ -2197,13 +2841,13 @@ "dev": true }, "form-data": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.1.tgz", - "integrity": "sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", + "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" } }, "formatio": { @@ -2212,24 +2856,48 @@ "integrity": "sha1-XtPM1jZVEJc4NGXZlhmRAOhhYek=", "dev": true, "requires": { - "samsam": "1.1.2" + "samsam": "~1.1" } }, "formidable": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz", - "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak=" + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", + "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, "fs-extra": { "version": "0.26.7", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "2.4.0", - "klaw": "1.3.1", - "path-is-absolute": "1.0.1", - "rimraf": "2.6.2" + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0", + "path-is-absolute": "^1.0.0", + "rimraf": "^2.2.8" } }, "fs.realpath": { @@ -2237,6 +2905,535 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "fsevents": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.21", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "^2.1.0" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.1.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true + } + } + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -2248,11 +3445,11 @@ "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.7.tgz", "integrity": "sha1-6c7FSD09TuDvRLYKfZnkk14TbZM=", "requires": { - "ansi": "0.3.1", - "has-unicode": "2.0.1", - "lodash.pad": "4.5.1", - "lodash.padend": "4.6.1", - "lodash.padstart": "4.6.1" + "ansi": "^0.3.0", + "has-unicode": "^2.0.0", + "lodash.pad": "^4.1.0", + "lodash.padend": "^4.1.0", + "lodash.padstart": "^4.1.0" } }, "generate-function": { @@ -2267,7 +3464,7 @@ "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", "dev": true, "requires": { - "is-property": "1.0.2" + "is-property": "^1.0.0" } }, "get-caller-file": { @@ -2281,7 +3478,7 @@ "resolved": "https://registry.npmjs.org/get-proxy/-/get-proxy-2.1.0.tgz", "integrity": "sha512-zmZIaQTWnNQb4R4fJUEp/FC51eZsc6EkErspy3xtIYStaq8EB/hDIWipxsal+E8rz0qD7f2sL/NA9Xee4RInJw==", "requires": { - "npm-conf": "1.1.3" + "npm-conf": "^1.1.0" } }, "get-stdin": { @@ -2294,21 +3491,19 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, "requires": { - "assert-plus": "1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } + "assert-plus": "^1.0.0" } }, "glob": { @@ -2316,31 +3511,12 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "2.0.0", - "is-glob": "2.0.1" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "2.0.1" + "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" } }, "global-dirs": { @@ -2348,7 +3524,7 @@ "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "requires": { - "ini": "1.3.5" + "ini": "^1.3.4" } }, "globals": { @@ -2362,11 +3538,11 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "requires": { - "array-union": "1.0.2", - "glob": "7.1.2", - "object-assign": "4.1.1", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "got": { @@ -2374,17 +3550,17 @@ "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "requires": { - "create-error-class": "3.0.2", - "duplexer3": "0.1.4", - "get-stream": "3.0.0", - "is-redirect": "1.0.0", - "is-retry-allowed": "1.1.0", - "is-stream": "1.1.0", - "lowercase-keys": "1.0.0", - "safe-buffer": "5.1.1", - "timed-out": "4.0.1", - "unzip-response": "2.0.1", - "url-parse-lax": "1.0.0" + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" } }, "graceful-fs": { @@ -2402,44 +3578,37 @@ "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.5.tgz", "integrity": "sha512-XvtbqCcw+EM5SqQrIetIKKD+uZVNQtDPD1goIg7K73RuRZtVI5rYMdcCVSHm/AS1sCBZ7vt0p5WgXouucHQaOA==", "requires": { - "lodash": "4.17.4" - } - }, - "graphql": { - "version": "0.10.5", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.10.5.tgz", - "integrity": "sha512-Q7cx22DiLhwHsEfUnUip1Ww/Vfx7FS0w6+iHItNuN61+XpegHSa3k5U0+6M5BcpavQImBwFiy0z3uYwY7cXMLQ==", - "requires": { - "iterall": "1.1.4" + "lodash": "^4.11.1" } }, - "graphql-anywhere": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/graphql-anywhere/-/graphql-anywhere-3.1.0.tgz", - "integrity": "sha1-PqDY6GRrXO5oA1AWqadVfBXCHpY=" - }, - "graphql-tag": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.6.1.tgz", - "integrity": "sha1-R4jVCfbilgfZR/xHpAxOGPc200o=" - }, "gray-matter": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-2.1.1.tgz", "integrity": "sha1-MELZrewqHe1qdwep7SOA+KF6Qw4=", "dev": true, "requires": { - "ansi-red": "0.1.1", - "coffee-script": "1.12.7", - "extend-shallow": "2.0.1", - "js-yaml": "3.10.0", - "toml": "2.3.3" + "ansi-red": "^0.1.1", + "coffee-script": "^1.12.4", + "extend-shallow": "^2.0.1", + "js-yaml": "^3.8.1", + "toml": "^2.3.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, "growl": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", - "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, "growly": { @@ -2454,10 +3623,10 @@ "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", "dev": true, "requires": { - "async": "1.5.2", - "optimist": "0.6.1", - "source-map": "0.4.4", - "uglify-js": "2.8.29" + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" }, "dependencies": { "source-map": { @@ -2466,63 +3635,34 @@ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "dev": true, "requires": { - "amdefine": "1.0.1" + "amdefine": ">=0.0.4" } } } }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true + }, "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", + "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", "dev": true, "requires": { - "chalk": "1.1.3", - "commander": "2.13.0", - "is-my-json-valid": "2.17.1", - "pinkie-promise": "2.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", - "dev": true - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "ajv": "^5.1.0", + "har-schema": "^2.0.0" } }, "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "requires": { - "function-bind": "1.1.1" + "function-bind": "^1.1.1" } }, "has-ansi": { @@ -2530,18 +3670,18 @@ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "has-flag": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbol-support-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.1.tgz", - "integrity": "sha512-JkaetveU7hFbqnAC1EV1sF4rlojU2D4Usc5CmS69l6NfmPDnpnFUegzFg33eDkkpNCxZ0mQp65HwUDrNFS/8MA==" + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==" }, "has-symbols": { "version": "1.0.0", @@ -2554,7 +3694,7 @@ "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", "requires": { - "has-symbol-support-x": "1.4.1" + "has-symbol-support-x": "^1.4.1" } }, "has-unicode": { @@ -2562,16 +3702,36 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { - "boom": "2.10.1", - "cryptiles": "2.0.5", - "hoek": "2.16.3", - "sntp": "1.0.9" + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "he": { @@ -2580,26 +3740,20 @@ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", - "dev": true - }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", "dev": true, "requires": { - "os-homedir": "1.0.2", - "os-tmpdir": "1.0.2" + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" } }, "hosted-git-info": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.1.tgz", + "integrity": "sha512-Ba4+0M4YvIDUUsprMjhVTU1yN9F2/LJSAl69ZpzaLT4l4j5mwTS6jqqW9Ojvj6lKz/veqPzpJBqGbXspOb533A==", "dev": true }, "html-encoding-sniffer": { @@ -2608,7 +3762,7 @@ "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", "dev": true, "requires": { - "whatwg-encoding": "1.0.3" + "whatwg-encoding": "^1.0.1" } }, "http-basic": { @@ -2617,9 +3771,28 @@ "integrity": "sha1-jORHvbW2xXf4pj4/p4BW7Eu02/s=", "dev": true, "requires": { - "caseless": "0.11.0", - "concat-stream": "1.6.0", - "http-response-object": "1.1.0" + "caseless": "~0.11.0", + "concat-stream": "^1.4.6", + "http-response-object": "^1.0.0" + }, + "dependencies": { + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", + "dev": true + } + } + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" } }, "http-response-object": { @@ -2629,30 +3802,42 @@ "dev": true }, "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { - "assert-plus": "0.2.0", - "jsprim": "1.4.1", - "sshpk": "1.13.1" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "https-proxy-agent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", - "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", + "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", "requires": { - "agent-base": "2.1.1", - "debug": "2.6.9", - "extend": "3.0.1" + "agent-base": "^4.1.0", + "debug": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } } }, "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "ieee754": { "version": "1.1.8", @@ -2660,9 +3845,9 @@ "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" }, "ignore": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", - "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", "dev": true }, "immediate": { @@ -2676,6 +3861,36 @@ "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" }, + "import-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", + "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", + "dev": true, + "requires": { + "pkg-dir": "^2.0.0", + "resolve-cwd": "^2.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + } + } + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -2686,8 +3901,8 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" + "once": "^1.3.0", + "wrappy": "1" } }, "inherits": { @@ -2705,20 +3920,20 @@ "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-1.2.3.tgz", "integrity": "sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=", "requires": { - "ansi-escapes": "1.4.0", - "chalk": "1.1.3", - "cli-cursor": "1.0.2", - "cli-width": "2.2.0", - "external-editor": "1.1.1", - "figures": "1.7.0", - "lodash": "4.17.4", + "ansi-escapes": "^1.1.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^2.0.0", + "external-editor": "^1.1.0", + "figures": "^1.3.5", + "lodash": "^4.3.0", "mute-stream": "0.0.6", - "pinkie-promise": "2.0.1", - "run-async": "2.3.0", - "rx": "4.1.0", - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "through": "2.3.8" + "pinkie-promise": "^2.0.0", + "run-async": "^2.2.0", + "rx": "^4.1.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" }, "dependencies": { "ansi-styles": { @@ -2731,11 +3946,11 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "supports-color": { @@ -2752,12 +3967,12 @@ "dev": true }, "invariant": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", - "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", "dev": true, "requires": { - "loose-envify": "1.3.1" + "loose-envify": "^1.0.0" } }, "invert-kv": { @@ -2766,6 +3981,20 @@ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "dev": true }, + "ipaddr.js": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", + "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -2784,22 +4013,30 @@ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { - "builtin-modules": "1.1.1" + "builtin-modules": "^1.0.0" } }, "is-callable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", "dev": true }, "is-ci": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", + "requires": { + "ci-info": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "ci-info": "1.1.2" + "kind-of": "^3.0.2" } }, "is-date-object": { @@ -2808,45 +4045,43 @@ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", "dev": true }, - "is-docker": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-1.1.0.tgz", - "integrity": "sha1-8EN01O7lMQ6ajhE78UlUEeRhdqE=" - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-primitive": "2.0.0" + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } } }, + "is-docker": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-1.1.0.tgz", + "integrity": "sha1-8EN01O7lMQ6ajhE78UlUEeRhdqE=" + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, "is-finite": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", "dev": true, "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, "is-fullwidth-code-point": { @@ -2854,25 +4089,22 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { - "number-is-nan": "1.0.1" + "number-is-nan": "^1.0.0" } }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "1.0.0" - } + "is-generator-fn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", + "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=", + "dev": true }, "is-installed-globally": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", "requires": { - "global-dirs": "0.1.1", - "is-path-inside": "1.0.1" + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" } }, "is-local-path": { @@ -2881,16 +4113,23 @@ "integrity": "sha1-gV0USxTVac7L6tTVaTCX8Aqb9sU=", "dev": true }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, "is-my-json-valid": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz", - "integrity": "sha512-Q2khNw+oBlWuaYvEEHtKSw/pCxD2L5Rc1C+UQme9X6JdRDh7m5D7HkozA0qa3DUkQ6VzCnEm8mVIQPyIRkI5sQ==", + "version": "2.17.2", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", + "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", "dev": true, "requires": { - "generate-function": "2.0.0", - "generate-object-property": "1.2.0", - "jsonpointer": "4.0.1", - "xtend": "4.0.1" + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^4.0.0", + "xtend": "^4.0.0" } }, "is-natural-number": { @@ -2904,12 +4143,12 @@ "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" }, "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" } }, "is-obj": { @@ -2929,12 +4168,12 @@ "dev": true }, "is-path-in-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", - "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", "dev": true, "requires": { - "is-path-inside": "1.0.1" + "is-path-inside": "^1.0.0" } }, "is-path-inside": { @@ -2942,7 +4181,7 @@ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "requires": { - "path-is-inside": "1.0.2" + "path-is-inside": "^1.0.1" } }, "is-plain-object": { @@ -2951,29 +4190,9 @@ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } + "isobject": "^3.0.1" } }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -2996,7 +4215,7 @@ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", "dev": true, "requires": { - "has": "1.0.1" + "has": "^1.0.1" } }, "is-resolvable": { @@ -3033,6 +4252,12 @@ "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", "dev": true }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, "is-wsl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", @@ -3049,21 +4274,18 @@ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true }, "isomorphic-fetch": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", "requires": { - "node-fetch": "1.7.3", - "whatwg-fetch": "2.0.3" + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" } }, "isstream": { @@ -3078,20 +4300,20 @@ "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=", "dev": true, "requires": { - "abbrev": "1.0.9", - "async": "1.5.2", - "escodegen": "1.8.1", - "esprima": "2.7.3", - "glob": "5.0.15", - "handlebars": "4.0.11", - "js-yaml": "3.10.0", - "mkdirp": "0.5.1", - "nopt": "3.0.6", - "once": "1.4.0", - "resolve": "1.1.7", - "supports-color": "3.2.3", - "which": "1.3.0", - "wordwrap": "1.0.0" + "abbrev": "1.0.x", + "async": "1.x", + "escodegen": "1.8.x", + "esprima": "2.7.x", + "glob": "^5.0.15", + "handlebars": "^4.0.1", + "js-yaml": "3.x", + "mkdirp": "0.5.x", + "nopt": "3.x", + "once": "1.x", + "resolve": "1.1.x", + "supports-color": "^3.1.0", + "which": "^1.1.1", + "wordwrap": "^1.0.0" }, "dependencies": { "esprima": { @@ -3106,11 +4328,11 @@ "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" } }, "has-flag": { @@ -3131,81 +4353,82 @@ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { - "has-flag": "1.0.0" + "has-flag": "^1.0.0" } } } }, "istanbul-api": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.2.1.tgz", - "integrity": "sha512-oFCwXvd65amgaPCzqrR+a2XjanS1MvpXN6l/MlMUTv6uiA1NOgGX+I0uyq8Lg3GDxsxPsaP1049krz3hIJ5+KA==", - "dev": true, - "requires": { - "async": "2.6.0", - "fileset": "2.0.3", - "istanbul-lib-coverage": "1.1.1", - "istanbul-lib-hook": "1.1.0", - "istanbul-lib-instrument": "1.9.1", - "istanbul-lib-report": "1.1.2", - "istanbul-lib-source-maps": "1.2.2", - "istanbul-reports": "1.1.3", - "js-yaml": "3.10.0", - "mkdirp": "0.5.1", - "once": "1.4.0" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.1.tgz", + "integrity": "sha512-duj6AlLcsWNwUpfyfHt0nWIeRiZpuShnP40YTxOGQgtaN8fd6JYSxsvxUphTDy8V5MfDXo4s/xVCIIvVCO808g==", + "dev": true, + "requires": { + "async": "^2.1.4", + "compare-versions": "^3.1.0", + "fileset": "^2.0.2", + "istanbul-lib-coverage": "^1.2.0", + "istanbul-lib-hook": "^1.2.0", + "istanbul-lib-instrument": "^1.10.1", + "istanbul-lib-report": "^1.1.4", + "istanbul-lib-source-maps": "^1.2.4", + "istanbul-reports": "^1.3.0", + "js-yaml": "^3.7.0", + "mkdirp": "^0.5.1", + "once": "^1.4.0" }, "dependencies": { "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", + "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", "dev": true, "requires": { - "lodash": "4.17.4" + "lodash": "^4.17.10" } } } }, "istanbul-lib-coverage": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz", - "integrity": "sha512-0+1vDkmzxqJIn5rcoEqapSB4DmPxE31EtI2dF2aCkV5esN9EWHxZ0dwgDClivMXJqE7zaYQxq30hj5L0nlTN5Q==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz", + "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==", "dev": true }, "istanbul-lib-hook": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz", - "integrity": "sha512-U3qEgwVDUerZ0bt8cfl3dSP3S6opBoOtk3ROO5f2EfBr/SRiD9FQqzwaZBqFORu8W7O0EXpai+k7kxHK13beRg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.1.tgz", + "integrity": "sha512-eLAMkPG9FU0v5L02lIkcj/2/Zlz9OuluaXikdr5iStk8FDbSwAixTK9TkYxbF0eNnzAJTwM2fkV2A1tpsIp4Jg==", "dev": true, "requires": { - "append-transform": "0.4.0" + "append-transform": "^1.0.0" } }, "istanbul-lib-instrument": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz", - "integrity": "sha512-RQmXeQ7sphar7k7O1wTNzVczF9igKpaeGQAG9qR2L+BS4DCJNTI9nytRmIVYevwO0bbq+2CXvJmYDuz0gMrywA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz", + "integrity": "sha512-1dYuzkOCbuR5GRJqySuZdsmsNKPL3PTuyPevQfoCXJePT9C8y1ga75neU+Tuy9+yS3G/dgx8wgOmp2KLpgdoeQ==", "dev": true, "requires": { - "babel-generator": "6.26.0", - "babel-template": "6.26.0", - "babel-traverse": "6.26.0", - "babel-types": "6.26.0", - "babylon": "6.18.0", - "istanbul-lib-coverage": "1.1.1", - "semver": "5.5.0" + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.0", + "semver": "^5.3.0" } }, "istanbul-lib-report": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.2.tgz", - "integrity": "sha512-UTv4VGx+HZivJQwAo1wnRwe1KTvFpfi/NYwN7DcsrdzMXwpRT/Yb6r4SBPoHWj4VuQPakR32g4PUUeyKkdDkBA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.4.tgz", + "integrity": "sha512-Azqvq5tT0U09nrncK3q82e/Zjkxa4tkFZv7E6VcqP0QCPn6oNljDPfrZEC/umNXds2t7b8sRJfs6Kmpzt8m2kA==", "dev": true, "requires": { - "istanbul-lib-coverage": "1.1.1", - "mkdirp": "0.5.1", - "path-parse": "1.0.5", - "supports-color": "3.2.3" + "istanbul-lib-coverage": "^1.2.0", + "mkdirp": "^0.5.1", + "path-parse": "^1.0.5", + "supports-color": "^3.1.2" }, "dependencies": { "has-flag": { @@ -3220,22 +4443,22 @@ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", "dev": true, "requires": { - "has-flag": "1.0.0" + "has-flag": "^1.0.0" } } } }, "istanbul-lib-source-maps": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.2.tgz", - "integrity": "sha512-8BfdqSfEdtip7/wo1RnrvLpHVEd8zMZEDmOFEnpC6dg0vXflHt9nvoAyQUzig2uMSXfF2OBEYBV3CVjIL9JvaQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.5.tgz", + "integrity": "sha512-8O2T/3VhrQHn0XcJbP1/GN7kXMiRAlPi+fj3uEHrjBD8Oz7Py0prSC25C09NuAZS6bgW1NNKAvCSHZXB0irSGA==", "dev": true, "requires": { - "debug": "3.1.0", - "istanbul-lib-coverage": "1.1.1", - "mkdirp": "0.5.1", - "rimraf": "2.6.2", - "source-map": "0.5.7" + "debug": "^3.1.0", + "istanbul-lib-coverage": "^1.2.0", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.1", + "source-map": "^0.5.3" }, "dependencies": { "debug": { @@ -3256,12 +4479,12 @@ } }, "istanbul-reports": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.1.3.tgz", - "integrity": "sha512-ZEelkHh8hrZNI5xDaKwPMFwDsUf5wIEI2bXAFGp1e6deR2mnEKBPhLJEgr4ZBt8Gi6Mj38E/C8kcy9XLggVO2Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.3.0.tgz", + "integrity": "sha512-y2Z2IMqE1gefWUaVjrBm0mSKvUkaBy9Vqz8iwr/r40Y9hBbIteH5wqHG/9DLTfJ9xUnUT2j7A3+VVJ6EaYBllA==", "dev": true, "requires": { - "handlebars": "4.0.11" + "handlebars": "^4.0.3" } }, "isurl": { @@ -3269,483 +4492,528 @@ "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", "requires": { - "has-to-string-tag-x": "1.4.1", - "is-object": "1.0.1" + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" } }, - "iterall": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.4.tgz", - "integrity": "sha512-eaDsM/PY8D/X5mYQhecVc5/9xvSHED7yPON+ffQroBeTuqUVm7dfphMkK8NksXuImqZlVRoKtrNfMIVCYIqaUQ==" - }, "jest-changed-files": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-17.0.2.tgz", - "integrity": "sha1-9WV3WHNplvWQpRuH5ck2nZBLp7c=", - "dev": true + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-23.2.0.tgz", + "integrity": "sha1-oUWm5LZtASn8fJnO4TTck3pkPZw=", + "dev": true, + "requires": { + "throat": "^4.0.0" + } }, "jest-cli": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-18.1.0.tgz", - "integrity": "sha1-Xq027K1CCBfCybqiqnV09jJXs9Y=", - "dev": true, - "requires": { - "ansi-escapes": "1.4.0", - "callsites": "2.0.0", - "chalk": "1.1.3", - "graceful-fs": "4.1.11", - "is-ci": "1.1.0", - "istanbul-api": "1.2.1", - "istanbul-lib-coverage": "1.1.1", - "istanbul-lib-instrument": "1.9.1", - "jest-changed-files": "17.0.2", - "jest-config": "18.1.0", - "jest-environment-jsdom": "18.1.0", - "jest-file-exists": "17.0.0", - "jest-haste-map": "18.1.0", - "jest-jasmine2": "18.1.0", - "jest-mock": "18.0.0", - "jest-resolve": "18.1.0", - "jest-resolve-dependencies": "18.1.0", - "jest-runtime": "18.1.0", - "jest-snapshot": "18.1.0", - "jest-util": "18.1.0", - "json-stable-stringify": "1.0.1", - "node-notifier": "4.6.1", - "sane": "1.4.1", - "strip-ansi": "3.0.1", - "throat": "3.2.0", - "which": "1.3.0", - "worker-farm": "1.5.2", - "yargs": "6.6.0" + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-23.2.0.tgz", + "integrity": "sha1-O1Q6PaUUXdiTeTEBcoI3n8aWxFs=", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "import-local": "^1.0.0", + "is-ci": "^1.0.10", + "istanbul-api": "^1.3.1", + "istanbul-lib-coverage": "^1.2.0", + "istanbul-lib-instrument": "^1.10.1", + "istanbul-lib-source-maps": "^1.2.4", + "jest-changed-files": "^23.2.0", + "jest-config": "^23.2.0", + "jest-environment-jsdom": "^23.2.0", + "jest-get-type": "^22.1.0", + "jest-haste-map": "^23.2.0", + "jest-message-util": "^23.2.0", + "jest-regex-util": "^23.0.0", + "jest-resolve-dependencies": "^23.2.0", + "jest-runner": "^23.2.0", + "jest-runtime": "^23.2.0", + "jest-snapshot": "^23.2.0", + "jest-util": "^23.2.0", + "jest-validate": "^23.2.0", + "jest-watcher": "^23.2.0", + "jest-worker": "^23.2.0", + "micromatch": "^3.1.10", + "node-notifier": "^5.2.1", + "prompts": "^0.1.9", + "realpath-native": "^1.0.0", + "rimraf": "^2.5.4", + "slash": "^1.0.0", + "string-length": "^2.0.0", + "strip-ansi": "^4.0.0", + "which": "^1.2.12", + "yargs": "^11.0.0" }, "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true }, - "camelcase": { + "ansi-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" } }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" + "locate-path": "^2.0.0" } }, - "supports-color": { + "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, - "yargs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", - "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "camelcase": "3.0.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "1.4.0", - "read-pkg-up": "1.0.1", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "1.0.2", - "which-module": "1.0.0", - "y18n": "3.2.1", - "yargs-parser": "4.2.1" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" } - } - } - }, - "jest-config": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-18.1.0.tgz", - "integrity": "sha1-YRF0Cm1Iqrhv9anmqwuYvZk7b/Q=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "jest-environment-jsdom": "18.1.0", - "jest-environment-node": "18.1.0", - "jest-jasmine2": "18.1.0", - "jest-mock": "18.0.0", - "jest-resolve": "18.1.0", - "jest-util": "18.1.0", - "json-stable-stringify": "1.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-regex": "^3.0.0" } }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "yargs": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", + "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + } } } }, + "jest-config": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-23.2.0.tgz", + "integrity": "sha1-0vtVb9WioZw561bROdzKXa0qHIg=", + "dev": true, + "requires": { + "babel-core": "^6.0.0", + "babel-jest": "^23.2.0", + "chalk": "^2.0.1", + "glob": "^7.1.1", + "jest-environment-jsdom": "^23.2.0", + "jest-environment-node": "^23.2.0", + "jest-get-type": "^22.1.0", + "jest-jasmine2": "^23.2.0", + "jest-regex-util": "^23.0.0", + "jest-resolve": "^23.2.0", + "jest-util": "^23.2.0", + "jest-validate": "^23.2.0", + "pretty-format": "^23.2.0" + } + }, "jest-diff": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-18.1.0.tgz", - "integrity": "sha1-T/eedN2YjBORlbNl3GXYf2BvSAM=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.2.0.tgz", + "integrity": "sha1-nyz0tR4Sx5FVAgCrwWtHEwrxBio=", "dev": true, "requires": { - "chalk": "1.1.3", - "diff": "3.4.0", - "jest-matcher-utils": "18.1.0", - "pretty-format": "18.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.0.1", + "diff": "^3.2.0", + "jest-get-type": "^22.1.0", + "pretty-format": "^23.2.0" + } + }, + "jest-docblock": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-23.2.0.tgz", + "integrity": "sha1-8IXh8YVI2Z/dabICB+b9VdkTg6c=", + "dev": true, + "requires": { + "detect-newline": "^2.1.0" + } + }, + "jest-each": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-23.2.0.tgz", + "integrity": "sha1-pAD4HIVwg/UMT1M5mxCfEgI/sZ0=", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "pretty-format": "^23.2.0" } }, "jest-environment-jsdom": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-18.1.0.tgz", - "integrity": "sha1-GLQvDE6iuunzbKs2ObHo+MOE4k4=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-23.2.0.tgz", + "integrity": "sha1-NjRgOgipdbDKimWDIPVqVKjgRVg=", "dev": true, "requires": { - "jest-mock": "18.0.0", - "jest-util": "18.1.0", - "jsdom": "9.12.0" + "jest-mock": "^23.2.0", + "jest-util": "^23.2.0", + "jsdom": "^11.5.1" } }, "jest-environment-node": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-18.1.0.tgz", - "integrity": "sha1-TWeXVyyN2pms9frmlutilFVHx3k=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-23.2.0.tgz", + "integrity": "sha1-tv5BNy44IJO7bz2b32wcTsClDxg=", "dev": true, "requires": { - "jest-mock": "18.0.0", - "jest-util": "18.1.0" + "jest-mock": "^23.2.0", + "jest-util": "^23.2.0" } }, - "jest-file-exists": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/jest-file-exists/-/jest-file-exists-17.0.0.tgz", - "integrity": "sha1-f2Prc6HEOhP0Yb4mF2i0WvLN0Wk=", + "jest-get-type": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", + "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", "dev": true }, "jest-haste-map": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-18.1.0.tgz", - "integrity": "sha1-BoOcdLdwpAwaEGlohR340oHAg3U=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-23.2.0.tgz", + "integrity": "sha1-0Qy6wAfGlZSMjvGCGisu0tTy1Ng=", "dev": true, "requires": { - "fb-watchman": "1.9.2", - "graceful-fs": "4.1.11", - "micromatch": "2.3.11", - "sane": "1.4.1", - "worker-farm": "1.5.2" + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.1.11", + "jest-docblock": "^23.2.0", + "jest-serializer": "^23.0.1", + "jest-worker": "^23.2.0", + "micromatch": "^3.1.10", + "sane": "^2.0.0" } }, "jest-jasmine2": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-18.1.0.tgz", - "integrity": "sha1-CU4QTCwYlwh2bHcmO7Kuy1hgqAs=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-23.2.0.tgz", + "integrity": "sha1-qmcM2x5NX47HdMlN2l4QX+M9i7Q=", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "co": "^4.6.0", + "expect": "^23.2.0", + "is-generator-fn": "^1.0.0", + "jest-diff": "^23.2.0", + "jest-each": "^23.2.0", + "jest-matcher-utils": "^23.2.0", + "jest-message-util": "^23.2.0", + "jest-snapshot": "^23.2.0", + "jest-util": "^23.2.0", + "pretty-format": "^23.2.0" + } + }, + "jest-leak-detector": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-23.2.0.tgz", + "integrity": "sha1-wonZYdxjjxQ1fU75bgQx7MGqN30=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "jest-matcher-utils": "18.1.0", - "jest-matchers": "18.1.0", - "jest-snapshot": "18.1.0", - "jest-util": "18.1.0" + "pretty-format": "^23.2.0" } }, "jest-matcher-utils": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-18.1.0.tgz", - "integrity": "sha1-GsRlGVXuKmDO8ef8yYzf13PA+TI=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.2.0.tgz", + "integrity": "sha1-TUmB8jIT6Tnjzt8j3DTHR7WuGRM=", "dev": true, "requires": { - "chalk": "1.1.3", - "pretty-format": "18.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.0.1", + "jest-get-type": "^22.1.0", + "pretty-format": "^23.2.0" } }, - "jest-matchers": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/jest-matchers/-/jest-matchers-18.1.0.tgz", - "integrity": "sha1-A0FIS/h6H9C6wKTSyJnit3o/Hq0=", + "jest-message-util": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.2.0.tgz", + "integrity": "sha1-WR6BSP/2nPibBBSAnHIXVuvv50Q=", "dev": true, "requires": { - "jest-diff": "18.1.0", - "jest-matcher-utils": "18.1.0", - "jest-util": "18.1.0", - "pretty-format": "18.1.0" + "@babel/code-frame": "^7.0.0-beta.35", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^1.0.0", + "stack-utils": "^1.0.1" } }, "jest-mock": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-18.0.0.tgz", - "integrity": "sha1-XCSIRuoz+lWLUm9TEqtKZ2XkibM=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-23.2.0.tgz", + "integrity": "sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=", + "dev": true + }, + "jest-regex-util": { + "version": "23.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.0.0.tgz", + "integrity": "sha1-3Vwf3gxG9DcTFM8Q96dRoj9Oj3Y=", "dev": true }, "jest-resolve": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-18.1.0.tgz", - "integrity": "sha1-aACsy1NmWMkGzV4p3kErGrmsJJs=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-23.2.0.tgz", + "integrity": "sha1-oHkK1aO5kAKrTb/L+Nni1qabPZk=", "dev": true, "requires": { - "browser-resolve": "1.11.2", - "jest-file-exists": "17.0.0", - "jest-haste-map": "18.1.0", - "resolve": "1.5.0" + "browser-resolve": "^1.11.3", + "chalk": "^2.0.1", + "realpath-native": "^1.0.0" } }, "jest-resolve-dependencies": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-18.1.0.tgz", - "integrity": "sha1-gTT7XK9Zye2EL+AVKrAcUnEfG7s=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-23.2.0.tgz", + "integrity": "sha1-bfjVcJxkBmOc0H9Uv/B04BtcBFg=", + "dev": true, + "requires": { + "jest-regex-util": "^23.0.0", + "jest-snapshot": "^23.2.0" + } + }, + "jest-runner": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-23.2.0.tgz", + "integrity": "sha1-DZGWfqgvcrDHBZEJJghtIFXOda8=", "dev": true, "requires": { - "jest-file-exists": "17.0.0", - "jest-resolve": "18.1.0" + "exit": "^0.1.2", + "graceful-fs": "^4.1.11", + "jest-config": "^23.2.0", + "jest-docblock": "^23.2.0", + "jest-haste-map": "^23.2.0", + "jest-jasmine2": "^23.2.0", + "jest-leak-detector": "^23.2.0", + "jest-message-util": "^23.2.0", + "jest-runtime": "^23.2.0", + "jest-util": "^23.2.0", + "jest-worker": "^23.2.0", + "source-map-support": "^0.5.6", + "throat": "^4.0.0" } }, "jest-runtime": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-18.1.0.tgz", - "integrity": "sha1-Or/WhxdbIfw7haK4BkOZ6ZeFmSI=", - "dev": true, - "requires": { - "babel-core": "6.26.0", - "babel-jest": "18.0.0", - "babel-plugin-istanbul": "3.1.2", - "chalk": "1.1.3", - "graceful-fs": "4.1.11", - "jest-config": "18.1.0", - "jest-file-exists": "17.0.0", - "jest-haste-map": "18.1.0", - "jest-mock": "18.0.0", - "jest-resolve": "18.1.0", - "jest-snapshot": "18.1.0", - "jest-util": "18.1.0", - "json-stable-stringify": "1.0.1", - "micromatch": "2.3.11", - "yargs": "6.6.0" + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-23.2.0.tgz", + "integrity": "sha1-YtywF2ahxMZGltwJAgnnbOGq3Lw=", + "dev": true, + "requires": { + "babel-core": "^6.0.0", + "babel-plugin-istanbul": "^4.1.6", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "exit": "^0.1.2", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.11", + "jest-config": "^23.2.0", + "jest-haste-map": "^23.2.0", + "jest-message-util": "^23.2.0", + "jest-regex-util": "^23.0.0", + "jest-resolve": "^23.2.0", + "jest-snapshot": "^23.2.0", + "jest-util": "^23.2.0", + "jest-validate": "^23.2.0", + "micromatch": "^3.1.10", + "realpath-native": "^1.0.0", + "slash": "^1.0.0", + "strip-bom": "3.0.0", + "write-file-atomic": "^2.1.0", + "yargs": "^11.0.0" }, "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "camelcase": { + "ansi-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" } }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" + "locate-path": "^2.0.0" } }, - "supports-color": { + "is-fullwidth-code-point": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, "yargs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", - "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", + "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", "dev": true, "requires": { - "camelcase": "3.0.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "1.4.0", - "read-pkg-up": "1.0.1", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "1.0.2", - "which-module": "1.0.0", - "y18n": "3.2.1", - "yargs-parser": "4.2.1" + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" } } } }, + "jest-serializer": { + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-23.0.1.tgz", + "integrity": "sha1-o3dq6zEekP6D+rnlM+hRAr0WQWU=", + "dev": true + }, "jest-snapshot": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-18.1.0.tgz", - "integrity": "sha1-VbltLuY5ybznb4fyo/1Atxx6WRY=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-23.2.0.tgz", + "integrity": "sha1-x6PQFxd7utYMillYac+QqHguan4=", "dev": true, "requires": { - "jest-diff": "18.1.0", - "jest-file-exists": "17.0.0", - "jest-matcher-utils": "18.1.0", - "jest-util": "18.1.0", - "natural-compare": "1.4.0", - "pretty-format": "18.1.0" + "chalk": "^2.0.1", + "jest-diff": "^23.2.0", + "jest-matcher-utils": "^23.2.0", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^23.2.0" } }, "jest-util": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-18.1.0.tgz", - "integrity": "sha1-OpnDIRSrF/hL4JQ4JScAbm1L/Go=", - "dev": true, - "requires": { - "chalk": "1.1.3", - "diff": "3.4.0", - "graceful-fs": "4.1.11", - "jest-file-exists": "17.0.0", - "jest-mock": "18.0.0", - "mkdirp": "0.5.1" + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.2.0.tgz", + "integrity": "sha1-YrdwdXaW2W4JSgS48cNzylClqy4=", + "dev": true, + "requires": { + "callsites": "^2.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.11", + "is-ci": "^1.0.10", + "jest-message-util": "^23.2.0", + "mkdirp": "^0.5.1", + "slash": "^1.0.0", + "source-map": "^0.6.0" }, "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", + "dev": true + } + } + }, + "jest-validate": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.2.0.tgz", + "integrity": "sha1-Z8i5CeEa8XAXZSOIlMZ6wykbGV4=", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-get-type": "^22.1.0", + "leven": "^2.1.0", + "pretty-format": "^23.2.0" + } + }, + "jest-watcher": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-23.2.0.tgz", + "integrity": "sha1-Z46FKJbpGenZoOtLi68a4nliDqk=", + "dev": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "string-length": "^2.0.0" + }, + "dependencies": { + "ansi-escapes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", + "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", "dev": true } } }, + "jest-worker": { + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-23.2.0.tgz", + "integrity": "sha1-+vcGqNo2+uYOsmlXJX+ntdjqArk=", + "dev": true, + "requires": { + "merge-stream": "^1.0.1" + } + }, "jmespath": { "version": "0.15.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", @@ -3754,15 +5022,16 @@ "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true }, "js-yaml": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", - "integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", + "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", "requires": { - "argparse": "1.0.9", - "esprima": "4.0.0" + "argparse": "^1.0.7", + "esprima": "^4.0.0" } }, "jsbn": { @@ -3773,36 +5042,62 @@ "optional": true }, "jsdom": { - "version": "9.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-9.12.0.tgz", - "integrity": "sha1-6MVG//ywbADUgzyoRBD+1/igl9Q=", - "dev": true, - "requires": { - "abab": "1.0.4", - "acorn": "4.0.13", - "acorn-globals": "3.1.0", - "array-equal": "1.0.0", - "content-type-parser": "1.0.2", - "cssom": "0.3.2", - "cssstyle": "0.2.37", - "escodegen": "1.8.1", - "html-encoding-sniffer": "1.0.2", - "nwmatcher": "1.4.3", - "parse5": "1.5.1", - "request": "2.79.0", - "sax": "1.2.1", - "symbol-tree": "3.2.2", - "tough-cookie": "2.3.3", - "webidl-conversions": "4.0.2", - "whatwg-encoding": "1.0.3", - "whatwg-url": "4.8.0", - "xml-name-validator": "2.0.1" + "version": "11.11.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.11.0.tgz", + "integrity": "sha512-ou1VyfjwsSuWkudGxb03FotDajxAto6USAlmMZjE2lc0jCznt7sBWkhfRBRaWwbnmDqdMSTKTLT5d9sBFkkM7A==", + "dev": true, + "requires": { + "abab": "^1.0.4", + "acorn": "^5.3.0", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": ">= 0.3.1 < 0.4.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.0", + "escodegen": "^1.9.0", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.2.0", + "nwsapi": "^2.0.0", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.83.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.3", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^4.0.0", + "xml-name-validator": "^3.0.0" }, "dependencies": { - "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "escodegen": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.10.0.tgz", + "integrity": "sha512-fjUOf8johsv23WuIKdNQU4P9t9jhQ4Qzx6pC2uW890OloK3Zs1ZAoCNpg/2larNF501jLl3UNy0kIRcF6VI22g==", + "dev": true, + "requires": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true } } @@ -3823,19 +5118,19 @@ "resolved": "https://registry.npmjs.org/json-refs/-/json-refs-2.1.7.tgz", "integrity": "sha1-uesB/in16j6Sh48VrqEK04taz4k=", "requires": { - "commander": "2.13.0", - "graphlib": "2.1.5", - "js-yaml": "3.10.0", - "native-promise-only": "0.8.1", - "path-loader": "1.0.4", - "slash": "1.0.0", - "uri-js": "3.0.2" + "commander": "^2.9.0", + "graphlib": "^2.1.1", + "js-yaml": "^3.8.3", + "native-promise-only": "^0.8.1", + "path-loader": "^1.0.2", + "slash": "^1.0.0", + "uri-js": "^3.0.2" }, "dependencies": { "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==" + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", + "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==" } } }, @@ -3845,13 +5140,19 @@ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, "json-stable-stringify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", "dev": true, "requires": { - "jsonify": "0.0.0" + "jsonify": "~0.0.0" } }, "json-stringify-safe": { @@ -3859,12 +5160,6 @@ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, - "json3": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz", - "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=", - "dev": true - }, "json5": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", @@ -3876,7 +5171,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.6" } }, "jsonify": { @@ -3901,14 +5196,6 @@ "extsprintf": "1.3.0", "json-schema": "0.2.3", "verror": "1.10.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } } }, "jsx-ast-utils": { @@ -3923,11 +5210,11 @@ "integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==", "dev": true, "requires": { - "core-js": "2.3.0", - "es6-promise": "3.0.2", - "lie": "3.1.1", - "pako": "1.0.6", - "readable-stream": "2.0.6" + "core-js": "~2.3.0", + "es6-promise": "~3.0.2", + "lie": "~3.1.0", + "pako": "~1.0.2", + "readable-stream": "~2.0.6" }, "dependencies": { "core-js": { @@ -3936,18 +5223,30 @@ "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=", "dev": true }, + "es6-promise": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz", + "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=", + "dev": true + }, + "process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, "readable-stream": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", "dev": true, "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "0.10.31", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" } }, "string_decoder": { @@ -3969,7 +5268,7 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "1.1.6" + "is-buffer": "^1.1.5" } }, "klaw": { @@ -3977,7 +5276,7 @@ "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", "requires": { - "graceful-fs": "4.1.11" + "graceful-fs": "^4.1.9" } }, "latest-version": { @@ -3985,7 +5284,7 @@ "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", "requires": { - "package-json": "4.0.1" + "package-json": "^4.0.0" } }, "lazy-cache": { @@ -4000,7 +5299,7 @@ "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "requires": { - "readable-stream": "2.3.3" + "readable-stream": "^2.0.5" } }, "lcid": { @@ -4009,7 +5308,7 @@ "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "dev": true, "requires": { - "invert-kv": "1.0.0" + "invert-kv": "^1.0.0" } }, "lcov-parse": { @@ -4018,14 +5317,26 @@ "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", "dev": true }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "dev": true + }, + "leven": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", + "dev": true + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { - "prelude-ls": "1.1.2", - "type-check": "0.3.2" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" } }, "lie": { @@ -4034,7 +5345,7 @@ "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", "dev": true, "requires": { - "immediate": "3.0.6" + "immediate": "~3.0.5" } }, "list-item": { @@ -4043,10 +5354,30 @@ "integrity": "sha1-DGXQDih8tmPMs8s4Sad+iewmilY=", "dev": true, "requires": { - "expand-range": "1.8.2", - "extend-shallow": "2.0.1", - "is-number": "2.1.0", - "repeat-string": "1.6.1" + "expand-range": "^1.8.1", + "extend-shallow": "^2.0.1", + "is-number": "^2.1.0", + "repeat-string": "^1.5.2" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } } }, "load-json-file": { @@ -4055,11 +5386,11 @@ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "pinkie-promise": "2.0.1", - "strip-bom": "2.0.0" + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" }, "dependencies": { "strip-bom": { @@ -4068,7 +5399,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "0.2.1" + "is-utf8": "^0.2.0" } } } @@ -4079,8 +5410,8 @@ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" }, "dependencies": { "path-exists": { @@ -4092,102 +5423,9 @@ } }, "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" - }, - "lodash-es": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.4.tgz", - "integrity": "sha1-3MHXVS4VCgZABzupyzHXDwMpUOc=" - }, - "lodash._arraycopy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz", - "integrity": "sha1-due3wfH7klRzdIeKVi7Qaj5Q9uE=", - "dev": true - }, - "lodash._arrayeach": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz", - "integrity": "sha1-urFWsqkNPxu9XGU0AzSeXlkz754=", - "dev": true - }, - "lodash._baseassign": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", - "integrity": "sha1-jDigmVAPIVrQnlnxci/QxSv+Ck4=", - "dev": true, - "requires": { - "lodash._basecopy": "3.0.1", - "lodash.keys": "3.1.2" - } - }, - "lodash._baseclone": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz", - "integrity": "sha1-MDUZv2OT/n5C802LYw73eU41Qrc=", - "dev": true, - "requires": { - "lodash._arraycopy": "3.0.0", - "lodash._arrayeach": "3.0.0", - "lodash._baseassign": "3.2.0", - "lodash._basefor": "3.0.3", - "lodash.isarray": "3.0.4", - "lodash.keys": "3.1.2" - } - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basecreate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz", - "integrity": "sha1-G8ZhYU2qf8MRt9A78WgGoCE8+CE=", - "dev": true - }, - "lodash._basefor": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.3.tgz", - "integrity": "sha1-dVC06SGO8J+tJDQ7YSAhx5tMIMI=", - "dev": true - }, - "lodash._bindcallback": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz", - "integrity": "sha1-5THCdkTPi1epnhftlbNcdIeJOS4=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash.assign": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", - "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", - "dev": true - }, - "lodash.clonedeep": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-3.0.2.tgz", - "integrity": "sha1-oKHkDYKl6on/WxR7hETtY9koJ9s=", - "dev": true, - "requires": { - "lodash._baseclone": "3.3.0", - "lodash._bindcallback": "3.0.1" - } + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, "lodash.cond": { "version": "4.5.2", @@ -4195,17 +5433,6 @@ "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", "dev": true }, - "lodash.create": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.create/-/lodash.create-3.1.1.tgz", - "integrity": "sha1-1/KEnw29p+BGgruM1yqwIkYd6+c=", - "dev": true, - "requires": { - "lodash._baseassign": "3.2.0", - "lodash._basecreate": "3.0.3", - "lodash._isiterateecall": "3.0.9" - } - }, "lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", @@ -4229,29 +5456,6 @@ "integrity": "sha1-oyRd7mH7m24GJLU1ElYku2nBEQY=", "dev": true }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "3.9.1", - "lodash.isarguments": "3.1.0", - "lodash.isarray": "3.0.4" - } - }, "lodash.pad": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-4.5.1.tgz", @@ -4267,10 +5471,10 @@ "resolved": "https://registry.npmjs.org/lodash.padstart/-/lodash.padstart-4.6.1.tgz", "integrity": "sha1-0uPuv/DZ05rVD1y9G1KnvOa7YRs=" }, - "lodash.toarray": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.toarray/-/lodash.toarray-4.4.0.tgz", - "integrity": "sha1-JMS/zWsvuji/0FlNsRedjptlZWE=", + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, "lodash.uniq": { @@ -4279,9 +5483,9 @@ "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=" }, "log-driver": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.5.tgz", - "integrity": "sha1-euTsJXMC/XkNVXyxDJcQDYV7AFY=", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", "dev": true }, "lolex": { @@ -4300,22 +5504,23 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "dev": true, "requires": { - "js-tokens": "3.0.2" + "js-tokens": "^3.0.0" } }, "lowercase-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", - "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" }, "lru-cache": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", - "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", + "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" } }, "lsmod": { @@ -4324,11 +5529,11 @@ "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" }, "make-dir": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.1.0.tgz", - "integrity": "sha512-0Pkui4wLJ7rxvmfUvs87skoEaxmu0hCUApF8nonzpl7q//FWp9zu8W61Scz4sd/kUiqDxvUhtoam2efDyiBzcA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", "requires": { - "pify": "3.0.0" + "pify": "^3.0.0" }, "dependencies": { "pify": { @@ -4344,7 +5549,22 @@ "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", "dev": true, "requires": { - "tmpl": "1.0.4" + "tmpl": "1.0.x" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" } }, "markdown-link": { @@ -4354,25 +5574,25 @@ "dev": true }, "markdown-magic": { - "version": "0.1.20", - "resolved": "https://registry.npmjs.org/markdown-magic/-/markdown-magic-0.1.20.tgz", - "integrity": "sha1-Xw73k0L6G0O7pCr+Y9MCMobj7sM=", - "dev": true, - "requires": { - "commander": "2.13.0", - "deepmerge": "1.5.2", - "find-up": "2.1.0", - "fs-extra": "1.0.0", - "globby": "6.1.0", - "is-local-path": "0.1.6", - "markdown-toc": "1.2.0", - "sync-request": "3.0.1" + "version": "0.1.23", + "resolved": "https://registry.npmjs.org/markdown-magic/-/markdown-magic-0.1.23.tgz", + "integrity": "sha512-umNBEP0GOq8AcysPBIOxmYpuMYg3686GwI4poO/oxIh319J1nHUL+G+SJFcMWbmATRq2P8OiGRva2aoVKEN3jA==", + "dev": true, + "requires": { + "commander": "^2.9.0", + "deepmerge": "^1.3.0", + "find-up": "^2.1.0", + "fs-extra": "^1.0.0", + "globby": "^6.1.0", + "is-local-path": "^0.1.6", + "markdown-toc": "^1.0.2", + "sync-request": "^3.0.1" }, "dependencies": { "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.16.0.tgz", + "integrity": "sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew==", "dev": true }, "find-up": { @@ -4381,7 +5601,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "2.0.0" + "locate-path": "^2.0.0" } }, "fs-extra": { @@ -4390,17 +5610,17 @@ "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "2.4.0", - "klaw": "1.3.1" + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" } } } }, "markdown-table": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.1.tgz", - "integrity": "sha1-Sz3ToTPRUYuO8NvHCb8qG0gkvIw=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.2.tgz", + "integrity": "sha512-NcWuJFHDA8V3wkDgR/j4+gZx+YQwstPgfQDV8ndUeWWzta3dnDTBxpVzqS9lkmJAuV5YX35lmyojl6HO5JXAgw==", "dev": true }, "markdown-toc": { @@ -4409,18 +5629,18 @@ "integrity": "sha512-eOsq7EGd3asV0oBfmyqngeEIhrbkc7XVP63OwcJBIhH2EpG2PzFcbZdhy1jutXSlRBBVMNXHvMtSr5LAxSUvUg==", "dev": true, "requires": { - "concat-stream": "1.6.0", - "diacritics-map": "0.1.0", - "gray-matter": "2.1.1", - "lazy-cache": "2.0.2", - "list-item": "1.1.1", - "markdown-link": "0.1.1", - "minimist": "1.2.0", - "mixin-deep": "1.3.0", - "object.pick": "1.3.0", - "remarkable": "1.7.1", - "repeat-string": "1.6.1", - "strip-color": "0.1.0" + "concat-stream": "^1.5.2", + "diacritics-map": "^0.1.0", + "gray-matter": "^2.1.0", + "lazy-cache": "^2.0.2", + "list-item": "^1.1.1", + "markdown-link": "^0.1.1", + "minimist": "^1.2.0", + "mixin-deep": "^1.1.3", + "object.pick": "^1.2.0", + "remarkable": "^1.7.1", + "repeat-string": "^1.6.1", + "strip-color": "^0.1.0" }, "dependencies": { "lazy-cache": { @@ -4429,55 +5649,29 @@ "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", "dev": true, "requires": { - "set-getter": "0.1.0" + "set-getter": "^0.1.0" } } } }, - "marked": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.12.tgz", - "integrity": "sha512-k4NaW+vS7ytQn6MgJn3fYpQt20/mOgYM5Ft9BYMfQJDz2QT6yEeS9XJ8k2Nw8JTeWK/znPPW2n3UJGzyYEiMoA==", + "math-random": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", "dev": true }, - "marked-terminal": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-2.0.0.tgz", - "integrity": "sha1-Xq9Wi+ZvaGVBr6UqVYKAMQox3i0=", + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "dev": true, "requires": { - "cardinal": "1.0.0", - "chalk": "1.1.3", - "cli-table": "0.3.1", - "lodash.assign": "4.2.0", - "node-emoji": "1.8.1" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "mimic-fn": "^1.0.0" } }, "merge": { @@ -4489,8 +5683,16 @@ "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } }, "methods": { "version": "1.1.2", @@ -4498,50 +5700,64 @@ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "2.0.0", - "array-unique": "0.2.1", - "braces": "1.8.5", - "expand-brackets": "0.1.5", - "extglob": "0.3.2", - "filename-regex": "2.0.1", - "is-extglob": "1.0.0", - "is-glob": "2.0.1", - "kind-of": "3.2.2", - "normalize-path": "2.1.1", - "object.omit": "2.0.1", - "parse-glob": "3.0.4", - "regex-cache": "0.4.4" + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } } }, "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" }, "mime-db": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.30.0.tgz", - "integrity": "sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=" + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" }, "mime-types": { - "version": "2.1.17", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.17.tgz", - "integrity": "sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=", + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", "requires": { - "mime-db": "1.30.0" + "mime-db": "~1.33.0" } }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "1.1.8" + "brace-expansion": "^1.1.7" } }, "minimist": { @@ -4550,13 +5766,13 @@ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "mixin-deep": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.0.tgz", - "integrity": "sha512-dgaCvoh6i1nosAUBKb0l0pfJ78K8+S9fluyIR2YvAeUD/QuMahnFnF3xYty5eYXMjhGSsB0DsW6A0uAZyetoAg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "dev": true, "requires": { - "for-in": "1.0.2", - "is-extendable": "1.0.1" + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" }, "dependencies": { "is-extendable": { @@ -4565,7 +5781,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "2.0.4" + "is-plain-object": "^2.0.4" } } } @@ -4586,77 +5802,38 @@ } }, "mocha": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.5.3.tgz", - "integrity": "sha512-/6na001MJWEtYxHOV1WLfsmR4YIynkUEhBwzsb+fk2qmQ3iqsi258l/Q2MWHJMImAcNpZ8DEdYAK72NHoIQ9Eg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", "dev": true, "requires": { - "browser-stdout": "1.3.0", - "commander": "2.9.0", - "debug": "2.6.8", - "diff": "3.2.0", + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", "escape-string-regexp": "1.0.5", - "glob": "7.1.1", - "growl": "1.9.2", + "glob": "7.1.2", + "growl": "1.10.5", "he": "1.1.1", - "json3": "3.3.2", - "lodash.create": "3.1.1", + "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "3.1.2" + "supports-color": "5.4.0" }, "dependencies": { "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": "1.0.1" - } + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true }, "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", "dev": true, "requires": { "ms": "2.0.0" } - }, - "diff": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.2.0.tgz", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", - "dev": true - }, - "glob": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", - "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", - "dev": true, - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "supports-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.2.tgz", - "integrity": "sha1-cqJiiU2dQIuVbKBf83su2KbiotU=", - "dev": true, - "requires": { - "has-flag": "1.0.0" - } } } }, @@ -4672,7 +5849,7 @@ "integrity": "sha1-gmFElS5QR2L45pJKqPY5Rl0deiQ=", "dev": true, "requires": { - "caller-id": "0.1.0" + "caller-id": "^0.1.0" } }, "module-not-found-error": { @@ -4682,9 +5859,9 @@ "dev": true }, "moment": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz", - "integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg==" + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", + "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" }, "ms": { "version": "2.0.0", @@ -4696,6 +5873,40 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz", "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=" }, + "nan": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", + "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", + "dev": true, + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, "native-promise-only": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", @@ -4707,29 +5918,26 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, - "node-emoji": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.8.1.tgz", - "integrity": "sha512-+ktMAh1Jwas+TnGodfCfjUbJKoANqPaJFN0z0iqh41eqD8dvguNzcitVSBSVK1pidz0AqGbLKcoVuVLRVZ/aVg==", - "dev": true, - "requires": { - "lodash.toarray": "4.4.0" - } + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true }, "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", "requires": { - "encoding": "0.1.12", - "is-stream": "1.1.0" + "encoding": "^0.1.11", + "is-stream": "^1.0.1" } }, - "node-forge": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.1.tgz", - "integrity": "sha1-naYR6giYL0uUIGs760zJZl8gwwA=" - }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -4737,18 +5945,15 @@ "dev": true }, "node-notifier": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-4.6.1.tgz", - "integrity": "sha1-BW0UJE89zBzq3+aK+c/wxUc6M/M=", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.2.1.tgz", + "integrity": "sha512-MIBs+AAd6dJ2SklbbE8RUDRlIVhU8MaNLh1A9SUZDUHPiZkWLFde6UNwG41yQHZEToHgJMXqyVZ9UcS/ReOVTg==", "dev": true, "requires": { - "cli-usage": "0.1.7", - "growly": "1.3.0", - "lodash.clonedeep": "3.0.2", - "minimist": "1.2.0", - "semver": "5.5.0", - "shellwords": "0.1.1", - "which": "1.3.0" + "growly": "^1.3.0", + "semver": "^5.4.1", + "shellwords": "^0.1.1", + "which": "^1.3.0" } }, "nopt": { @@ -4757,7 +5962,7 @@ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "dev": true, "requires": { - "abbrev": "1.0.9" + "abbrev": "1" } }, "normalize-package-data": { @@ -4766,10 +5971,10 @@ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", "dev": true, "requires": { - "hosted-git-info": "2.5.0", - "is-builtin-module": "1.0.0", - "semver": "5.5.0", - "validate-npm-package-license": "3.0.1" + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" } }, "normalize-path": { @@ -4777,7 +5982,7 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "requires": { - "remove-trailing-separator": "1.1.0" + "remove-trailing-separator": "^1.0.1" } }, "npm-conf": { @@ -4785,8 +5990,8 @@ "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", "requires": { - "config-chain": "1.1.11", - "pify": "3.0.0" + "config-chain": "^1.1.11", + "pify": "^3.0.0" }, "dependencies": { "pify": { @@ -4801,7 +6006,7 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "requires": { - "path-key": "2.0.1" + "path-key": "^2.0.0" } }, "npmlog": { @@ -4809,9 +6014,9 @@ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-2.0.4.tgz", "integrity": "sha1-mLUlMPJRTKkNCexbIsiEZyI3VpI=", "requires": { - "ansi": "0.3.1", - "are-we-there-yet": "1.1.4", - "gauge": "1.2.7" + "ansi": "~0.3.1", + "are-we-there-yet": "~1.1.2", + "gauge": "~1.2.5" } }, "number-is-nan": { @@ -4819,10 +6024,10 @@ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, - "nwmatcher": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.3.tgz", - "integrity": "sha512-IKdSTiDWCarf2JTS5e9e2+5tPZGdkRJ79XjYV0pzK8Q9BpsFyBq1RGKxzs7Q8UBushGw7m6TzVKz6fcY99iSWw==", + "nwsapi": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.4.tgz", + "integrity": "sha512-Zt6HRR6RcJkuj5/N9zeE7FN6YitRW//hK2wTOwX274IBphbY3Zf5+yn5mZ9v/SzAOTMjQNxZf9KkmPLWn0cV4g==", "dev": true }, "oauth-sign": { @@ -4836,37 +6041,68 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, "object-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.2.0.tgz", - "integrity": "sha512-smRWXzkvxw72VquyZ0wggySl7PFUtoDhvhpdwgESXxUrH7vVhhp9asfup1+rVLrhsl7L45Ee1Q/l5R2Ul4MwUg==" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.0.tgz", + "integrity": "sha512-05KzQ70lSeGSrZJQXE5wNDiTkBJDlUT/myi6RX9dVIvz7a7Qh4oH93BQdiPMn27nldYvVQCKMUaM83AfizZlsQ==" }, "object-keys": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", "dev": true }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, "object.assign": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { - "define-properties": "1.1.2", - "function-bind": "1.1.1", - "has-symbols": "1.0.0", - "object-keys": "1.0.11" + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" } }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", "dev": true, "requires": { - "for-own": "0.1.5", - "is-extendable": "0.1.1" + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" } }, "object.pick": { @@ -4875,15 +6111,15 @@ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "isobject": "3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } + "isobject": "^3.0.1" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" } }, "once": { @@ -4891,20 +6127,20 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1.0.2" + "wrappy": "1" } }, "onetime": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=" }, "opn": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.2.0.tgz", - "integrity": "sha512-Jd/GpzPyHF4P2/aNOVmS3lfMSWV9J7cOhCG1s08XCEAsPkB7lp6ddiU0J7XzyQRDUh8BqJ7PchfINjR8jyofRQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", "requires": { - "is-wsl": "1.1.0" + "is-wsl": "^1.1.0" } }, "optimist": { @@ -4913,8 +6149,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "0.0.10", - "wordwrap": "0.0.3" + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" }, "dependencies": { "minimist": { @@ -4937,12 +6173,12 @@ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "dev": true, "requires": { - "deep-is": "0.1.3", - "fast-levenshtein": "2.0.6", - "levn": "0.3.0", - "prelude-ls": "1.1.2", - "type-check": "0.3.2", - "wordwrap": "1.0.0" + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" } }, "os-homedir": { @@ -4952,12 +6188,14 @@ "dev": true }, "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", "dev": true, "requires": { - "lcid": "1.0.0" + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" } }, "os-shim": { @@ -4976,12 +6214,12 @@ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "p-try": "1.0.0" + "p-try": "^1.0.0" } }, "p-locate": { @@ -4990,7 +6228,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "1.2.0" + "p-limit": "^1.1.0" } }, "p-try": { @@ -5004,10 +6242,10 @@ "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", "requires": { - "got": "6.7.1", - "registry-auth-token": "3.3.2", - "registry-url": "3.1.0", - "semver": "5.5.0" + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" } }, "pako": { @@ -5022,31 +6260,30 @@ "integrity": "sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==", "dev": true }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "0.3.0", - "is-dotfile": "1.0.3", - "is-extglob": "1.0.0", - "is-glob": "2.0.1" - } - }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { - "error-ex": "1.3.1" + "error-ex": "^1.2.0" } }, "parse5": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", - "integrity": "sha1-m387DeMr543CQBsXVzzK8Pb1nZQ=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", "dev": true }, "path-exists": { @@ -5055,7 +6292,7 @@ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "dev": true, "requires": { - "pinkie-promise": "2.0.1" + "pinkie-promise": "^2.0.0" } }, "path-is-absolute": { @@ -5078,8 +6315,8 @@ "resolved": "https://registry.npmjs.org/path-loader/-/path-loader-1.0.4.tgz", "integrity": "sha512-k/IPo9OWyofATP5gwIehHHQoFShS37zsSIsejKe6fjI+tqK+FnRpiSg4ZfWUpxb0g2PfCreWPqBD4ayjqjqkdQ==", "requires": { - "native-promise-only": "0.8.1", - "superagent": "3.8.2" + "native-promise-only": "^0.8.1", + "superagent": "^3.6.3" } }, "path-parse": { @@ -5088,15 +6325,20 @@ "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", "dev": true }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "dev": true, "requires": { - "graceful-fs": "4.1.11", - "pify": "2.3.0", - "pinkie-promise": "2.0.1" + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" } }, "pend": { @@ -5104,6 +6346,12 @@ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -5119,7 +6367,7 @@ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "requires": { - "pinkie": "2.0.4" + "pinkie": "^2.0.0" } }, "pkg-dir": { @@ -5128,7 +6376,7 @@ "integrity": "sha1-ektQio1bstYp1EcFb/TpyTFM89Q=", "dev": true, "requires": { - "find-up": "1.1.2" + "find-up": "^1.0.0" } }, "pkg-up": { @@ -5137,7 +6385,7 @@ "integrity": "sha1-Pgj7RhUlxEIWJKM7n35tCvWwWiY=", "dev": true, "requires": { - "find-up": "1.1.2" + "find-up": "^1.0.0" } }, "pluralize": { @@ -5146,6 +6394,18 @@ "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", "dev": true }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -5157,25 +6417,20 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, "pretty-format": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-18.1.0.tgz", - "integrity": "sha1-+2Wob3p/kZSWPu6RhlwbzxA54oQ=", + "version": "23.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.2.0.tgz", + "integrity": "sha1-OwqqY8AYpTWDNzwcs6XZbMXoMBc=", "dev": true, "requires": { - "ansi-styles": "2.2.1" + "ansi-regex": "^3.0.0", + "ansi-styles": "^3.2.0" }, "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true } } @@ -5187,9 +6442,9 @@ "dev": true }, "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, "progress": { "version": "1.1.8", @@ -5203,7 +6458,7 @@ "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "dev": true, "requires": { - "asap": "2.0.6" + "asap": "~2.0.3" } }, "promise-queue": { @@ -5211,20 +6466,39 @@ "resolved": "https://registry.npmjs.org/promise-queue/-/promise-queue-2.2.5.tgz", "integrity": "sha1-L29ffA9tCBCelnZZx5uIqe1ek7Q=" }, + "prompts": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-0.1.10.tgz", + "integrity": "sha512-/MPwms6+g/m6fvXZlQyOL4m4ziDim2+Wc6CdWVjp+nVCkzEkK2N4rR74m/bbGf+dkta+/SBpo1FfES8Wgrk/Fw==", + "dev": true, + "requires": { + "clorox": "^1.0.3", + "sisteransi": "^0.1.1" + } + }, "proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" }, + "proxy-addr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", + "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.6.0" + } + }, "proxyquire": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/proxyquire/-/proxyquire-1.8.0.tgz", "integrity": "sha1-AtUUpb7ZhvBMuyCTrxZ0FTX3ntw=", "dev": true, "requires": { - "fill-keys": "1.0.2", - "module-not-found-error": "1.0.1", - "resolve": "1.1.7" + "fill-keys": "^1.0.2", + "module-not-found-error": "^1.0.0", + "resolve": "~1.1.7" }, "dependencies": { "resolve": { @@ -5235,12 +6509,6 @@ } } }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -5252,9 +6520,9 @@ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "querystring": { "version": "0.2.0", @@ -5262,9 +6530,9 @@ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" }, "querystringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-1.0.0.tgz", - "integrity": "sha1-YoYkIRLFtxL6ZU5SZlK/ahP/Bcs=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", + "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==" }, "ramda": { "version": "0.24.1", @@ -5272,46 +6540,35 @@ "integrity": "sha1-w7d1UZfzW43DUCIoJixMkd22uFc=" }, "randomatic": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", + "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", "dev": true, "requires": { - "is-number": "3.0.0", - "kind-of": "4.0.0" + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" }, "dependencies": { "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } - } - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true }, "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "1.1.6" - } + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true } } }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, "raven": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/raven/-/raven-1.2.1.tgz", @@ -5331,15 +6588,26 @@ } } }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + } + }, "rc": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.4.tgz", - "integrity": "sha1-oPYGyq4qO4YrvQ74VILAElsxX6M=", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "requires": { - "deep-extend": "0.4.2", - "ini": "1.3.5", - "minimist": "1.2.0", - "strip-json-comments": "2.0.1" + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" } }, "read-pkg": { @@ -5348,9 +6616,9 @@ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "dev": true, "requires": { - "load-json-file": "1.1.0", - "normalize-package-data": "2.4.0", - "path-type": "1.1.0" + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" } }, "read-pkg-up": { @@ -5359,22 +6627,22 @@ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "dev": true, "requires": { - "find-up": "1.1.2", - "read-pkg": "1.1.0" + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" } }, "readable-stream": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "safe-buffer": "5.1.1", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, "readline2": { @@ -5383,8 +6651,8 @@ "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", "dev": true, "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", "mute-stream": "0.0.5" }, "dependencies": { @@ -5396,56 +6664,37 @@ } } }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "realpath-native": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.0.0.tgz", + "integrity": "sha512-XJtlRJ9jf0E1H1SLeJyQ9PGzQD7S65h1pRXEcAeK48doKOnKxcgPeNohJvD5u/2sI9J1oke6E8bZHS/fmW1UiQ==", "dev": true, "requires": { - "resolve": "1.5.0" + "util.promisify": "^1.0.0" } }, - "redeyed": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/redeyed/-/redeyed-1.0.1.tgz", - "integrity": "sha1-6WwZO0DAgWsArshCaY5hGF5VSYo=", + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "dev": true, "requires": { - "esprima": "3.0.0" - }, - "dependencies": { - "esprima": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.0.0.tgz", - "integrity": "sha1-U88kes2ncxPlUcOqLnM0LT+099k=", - "dev": true - } - } - }, - "redux": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", - "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", - "requires": { - "lodash": "4.17.4", - "lodash-es": "4.17.4", - "loose-envify": "1.3.1", - "symbol-observable": "1.2.0" + "resolve": "^1.1.6" } }, "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true + "version": "0.10.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz", + "integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=" }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { - "is-equal-shallow": "0.1.3" + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" } }, "registry-auth-token": { @@ -5453,8 +6702,8 @@ "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", "requires": { - "rc": "1.2.4", - "safe-buffer": "5.1.1" + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" } }, "registry-url": { @@ -5462,7 +6711,7 @@ "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "requires": { - "rc": "1.2.4" + "rc": "^1.0.1" } }, "remarkable": { @@ -5471,8 +6720,8 @@ "integrity": "sha1-qspJchALZqZCpjoQIcpLrBvjv/Y=", "dev": true, "requires": { - "argparse": "0.1.16", - "autolinker": "0.15.3" + "argparse": "~0.1.15", + "autolinker": "~0.15.0" }, "dependencies": { "argparse": { @@ -5481,8 +6730,8 @@ "integrity": "sha1-z9AeD7uj1srtBJ+9dY1A9lGW9Xw=", "dev": true, "requires": { - "underscore": "1.7.0", - "underscore.string": "2.4.0" + "underscore": "~1.7.0", + "underscore.string": "~2.4.0" } } } @@ -5510,7 +6759,7 @@ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "dev": true, "requires": { - "is-finite": "1.0.2" + "is-finite": "^1.0.0" } }, "replaceall": { @@ -5519,64 +6768,61 @@ "integrity": "sha1-gdgax663LX9cSUKt8ml6MiBojY4=" }, "request": { - "version": "2.79.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", - "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", - "dev": true, - "requires": { - "aws-sign2": "0.6.0", - "aws4": "1.6.0", - "caseless": "0.11.0", - "combined-stream": "1.0.5", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.1.4", - "har-validator": "2.0.6", - "hawk": "3.1.3", - "http-signature": "1.1.1", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.17", - "oauth-sign": "0.8.2", - "qs": "6.3.2", - "stringstream": "0.0.5", - "tough-cookie": "2.3.3", - "tunnel-agent": "0.4.3", - "uuid": "3.2.1" + "version": "2.87.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", + "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", + "dev": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.6.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.1", + "forever-agent": "~0.6.1", + "form-data": "~2.3.1", + "har-validator": "~5.0.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.17", + "oauth-sign": "~0.8.2", + "performance-now": "^2.1.0", + "qs": "~6.5.1", + "safe-buffer": "^5.1.1", + "tough-cookie": "~2.3.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.1.0" }, "dependencies": { - "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "dev": true, - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.5", - "mime-types": "2.1.17" - } - }, - "qs": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", - "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=", - "dev": true - }, - "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true - }, "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", "dev": true } } }, + "request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "dev": true, + "requires": { + "lodash": "^4.13.1" + } + }, + "request-promise-native": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", + "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", + "dev": true, + "requires": { + "request-promise-core": "1.1.1", + "stealthy-require": "^1.1.0", + "tough-cookie": ">=2.3.3" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5595,8 +6841,8 @@ "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", "dev": true, "requires": { - "caller-path": "0.1.0", - "resolve-from": "1.0.1" + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" } }, "requires-port": { @@ -5605,12 +6851,29 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, "resolve": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", - "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "dev": true, + "requires": { + "path-parse": "^1.0.5" + } + }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", "dev": true, "requires": { - "path-parse": "1.0.5" + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } } }, "resolve-from": { @@ -5619,15 +6882,27 @@ "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", "dev": true }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, "restore-cursor": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", "requires": { - "exit-hook": "1.1.1", - "onetime": "1.1.0" + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" } }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", @@ -5635,7 +6910,7 @@ "dev": true, "optional": true, "requires": { - "align-text": "0.1.4" + "align-text": "^0.1.1" } }, "rimraf": { @@ -5643,15 +6918,21 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "requires": { - "glob": "7.1.2" + "glob": "^7.0.5" } }, + "rsvp": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", + "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", + "dev": true + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", "requires": { - "is-promise": "2.1.0" + "is-promise": "^2.1.0" } }, "rx": { @@ -5670,6 +6951,20 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "samsam": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.1.2.tgz", @@ -5677,17 +6972,20 @@ "dev": true }, "sane": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/sane/-/sane-1.4.1.tgz", - "integrity": "sha1-iPdj10BA9fDCVrYWPbOZvxEKxxU=", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/sane/-/sane-2.5.2.tgz", + "integrity": "sha1-tNwYYcIbQn6SlQej51HiosuKs/o=", "dev": true, "requires": { - "exec-sh": "0.2.1", - "fb-watchman": "1.9.2", - "minimatch": "3.0.4", - "minimist": "1.2.0", - "walker": "1.0.7", - "watch": "0.10.0" + "anymatch": "^2.0.0", + "capture-exit": "^1.2.0", + "exec-sh": "^0.2.0", + "fb-watchman": "^2.0.0", + "fsevents": "^1.2.3", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5", + "watch": "~0.18.0" } }, "sax": { @@ -5700,7 +6998,7 @@ "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-1.0.5.tgz", "integrity": "sha1-z+kXyz0nS8/6x5J1ivUxc+sfq9w=", "requires": { - "commander": "2.8.1" + "commander": "~2.8.1" } }, "semver": { @@ -5713,7 +7011,7 @@ "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "requires": { - "semver": "5.5.0" + "semver": "^5.0.3" } }, "semver-regex": { @@ -5721,6 +7019,44 @@ "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-1.0.0.tgz", "integrity": "sha1-kqSWkGX5xwxpR1PVUkj8aPj2Usk=" }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -5733,15 +7069,43 @@ "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", "dev": true, "requires": { - "to-object-path": "0.3.0" + "to-object-path": "^0.3.0" } }, + "set-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "requires": { - "shebang-regex": "1.0.0" + "shebang-regex": "^1.0.0" } }, "shebang-regex": { @@ -5755,9 +7119,9 @@ "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", "dev": true, "requires": { - "glob": "7.1.2", - "interpret": "1.1.0", - "rechoir": "0.6.2" + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" } }, "shellwords": { @@ -5780,7 +7144,7 @@ "formatio": "1.1.1", "lolex": "1.3.2", "samsam": "1.1.2", - "util": "0.10.3" + "util": ">=0.10.3 <1" } }, "sinon-bluebird": { @@ -5795,6 +7159,12 @@ "integrity": "sha512-9stIF1utB0ywNHNT7RgiXbdmen8QDCRsrTjw+G9TgKt1Yexjiv8TOWZ6WHsTPz57Yky3DIswZvEqX8fpuHNDtQ==", "dev": true }, + "sisteransi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-0.1.1.tgz", + "integrity": "sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g==", + "dev": true + }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -5806,99 +7176,217 @@ "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", "dev": true }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { - "hoek": "2.16.3" + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } } }, - "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, - "optional": true, "requires": { - "amdefine": "1.0.1" + "kind-of": "^3.2.0" } }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-resolve": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", + "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", "dev": true, "requires": { - "source-map": "0.5.7" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "atob": "^2.1.1", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" } }, + "source-map-support": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz", + "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, "spawn-sync": { "version": "1.0.15", "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", "requires": { - "concat-stream": "1.6.0", - "os-shim": "0.1.3" + "concat-stream": "^1.4.7", + "os-shim": "^0.1.2" } }, "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", "dev": true, "requires": { - "spdx-license-ids": "1.2.2" + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" } }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "spdx-exceptions": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", "dev": true }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", "dev": true }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", + "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", "dev": true, "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" } }, "stack-trace": { @@ -5906,36 +7394,95 @@ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=" }, + "stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha1-1PM6tU6OOHeLDKXP07OvsS22hiA=", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, + "string-length": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", + "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", + "dev": true, + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" } }, "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "~5.1.0" } }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", - "dev": true - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { - "ansi-regex": "2.1.1" + "ansi-regex": "^2.0.0" } }, "strip-bom": { @@ -5955,7 +7502,7 @@ "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-2.1.0.tgz", "integrity": "sha512-JOCxOeKLm2CAS73y/U4ZeZPTkE+gNVCzKt7Eox84Iej1LT/2pTWYpZKJuxwQpvX1LiZb1xokNR7RLfuBAa7T3g==", "requires": { - "is-natural-number": "4.0.1" + "is-natural-number": "^4.0.1" } }, "strip-eof": { @@ -5969,28 +7516,28 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "strip-outer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.0.tgz", - "integrity": "sha1-qsC6YNLpDF1PJ1/Yhp/ZotMQ/7g=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", "requires": { - "escape-string-regexp": "1.0.5" + "escape-string-regexp": "^1.0.2" } }, "superagent": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", - "integrity": "sha512-gVH4QfYHcY3P0f/BZzavLreHW3T1v7hG9B+hpMQotGQqurOvhv87GcMCd6LWySmBuf+BDR44TQd0aISjVHLeNQ==", - "requires": { - "component-emitter": "1.2.1", - "cookiejar": "2.1.1", - "debug": "3.1.0", - "extend": "3.0.1", - "form-data": "2.3.1", - "formidable": "1.1.1", - "methods": "1.1.2", - "mime": "1.6.0", - "qs": "6.5.1", - "readable-stream": "2.3.3" + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz", + "integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==", + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.2.0", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.3.5" }, "dependencies": { "debug": { @@ -6004,18 +7551,13 @@ } }, "supports-color": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", - "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "requires": { - "has-flag": "2.0.0" + "has-flag": "^3.0.0" } }, - "symbol-observable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", - "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" - }, "symbol-tree": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", @@ -6028,9 +7570,9 @@ "integrity": "sha1-yqEjWq+Im6UBB2oYNMQ2gwqC+3M=", "dev": true, "requires": { - "concat-stream": "1.6.0", - "http-response-object": "1.1.0", - "then-request": "2.2.0" + "concat-stream": "^1.4.7", + "http-response-object": "^1.0.1", + "then-request": "^2.0.1" } }, "table": { @@ -6039,14 +7581,24 @@ "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", "dev": true, "requires": { - "ajv": "4.11.8", - "ajv-keywords": "1.5.1", - "chalk": "1.1.3", - "lodash": "4.17.4", + "ajv": "^4.7.0", + "ajv-keywords": "^1.0.0", + "chalk": "^1.1.1", + "lodash": "^4.0.0", "slice-ansi": "0.0.4", - "string-width": "2.1.1" + "string-width": "^2.0.0" }, "dependencies": { + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", @@ -6065,11 +7617,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "2.2.1", - "escape-string-regexp": "1.0.5", - "has-ansi": "2.0.0", - "strip-ansi": "3.0.1", - "supports-color": "2.0.0" + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" } }, "is-fullwidth-code-point": { @@ -6084,8 +7636,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" }, "dependencies": { "strip-ansi": { @@ -6094,7 +7646,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } @@ -6112,25 +7664,28 @@ "resolved": "https://registry.npmjs.org/tabtab/-/tabtab-2.2.2.tgz", "integrity": "sha1-egR/FDsBC0y9MfhX6ClhUSy/ThQ=", "requires": { - "debug": "2.6.9", - "inquirer": "1.2.3", - "lodash.difference": "4.5.0", - "lodash.uniq": "4.5.0", - "minimist": "1.2.0", - "mkdirp": "0.5.1", - "npmlog": "2.0.4", - "object-assign": "4.1.1" + "debug": "^2.2.0", + "inquirer": "^1.0.2", + "lodash.difference": "^4.5.0", + "lodash.uniq": "^4.5.0", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "npmlog": "^2.0.3", + "object-assign": "^4.1.0" } }, "tar-stream": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.5.5.tgz", - "integrity": "sha512-mQdgLPc/Vjfr3VWqWbfxW8yQNiJCbAZ+Gf6GDu1Cy0bdb33ofyiNGBtAY96jHFhDuivCwgW1H9DgTON+INiXgg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.1.tgz", + "integrity": "sha512-IFLM5wp3QrJODQFPm6/to3LJZrONdBY/otxcvDIQzu217zKye6yVR3hhi9lAjrC2Z+m/j5oDxMPb1qcd8cIvpA==", "requires": { - "bl": "1.2.1", - "end-of-stream": "1.4.1", - "readable-stream": "2.3.3", - "xtend": "4.0.1" + "bl": "^1.0.0", + "buffer-alloc": "^1.1.0", + "end-of-stream": "^1.0.0", + "fs-constants": "^1.0.0", + "readable-stream": "^2.3.0", + "to-buffer": "^1.1.0", + "xtend": "^4.0.0" } }, "term-size": { @@ -6138,20 +7693,20 @@ "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", "requires": { - "execa": "0.7.0" + "execa": "^0.7.0" } }, "test-exclude": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-3.3.0.tgz", - "integrity": "sha1-ehfKEjmYjJg2ewYhRW27fUvDiXc=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.1.tgz", + "integrity": "sha512-qpqlP/8Zl+sosLxBcVKl9vYy26T9NPalxSzzCP/OY6K7j938ui2oKgo+kRZYfxAeIpLqpbVnsHq1tyV70E4lWQ==", "dev": true, "requires": { - "arrify": "1.0.1", - "micromatch": "2.3.11", - "object-assign": "4.1.1", - "read-pkg-up": "1.0.1", - "require-main-filename": "1.0.1" + "arrify": "^1.0.1", + "micromatch": "^3.1.8", + "object-assign": "^4.1.0", + "read-pkg-up": "^1.0.1", + "require-main-filename": "^1.0.1" } }, "text-table": { @@ -6166,18 +7721,26 @@ "integrity": "sha1-ZnizL6DKIY/laZgbvYhxtZQGDYE=", "dev": true, "requires": { - "caseless": "0.11.0", - "concat-stream": "1.6.0", - "http-basic": "2.5.1", - "http-response-object": "1.1.0", - "promise": "7.3.1", - "qs": "6.5.1" + "caseless": "~0.11.0", + "concat-stream": "^1.4.7", + "http-basic": "^2.5.1", + "http-response-object": "^1.1.0", + "promise": "^7.1.1", + "qs": "^6.1.0" + }, + "dependencies": { + "caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", + "dev": true + } } }, "throat": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-3.2.0.tgz", - "integrity": "sha512-/EY8VpvlqJ+sFtLPeOgc8Pl7kQVOWv0woD87KTXVHPIAE842FGT+rokxIhe8xIUP1cfgrkt0as0vDLjDiMtr8w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", + "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", "dev": true }, "through": { @@ -6195,7 +7758,7 @@ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", "requires": { - "os-tmpdir": "1.0.2" + "os-tmpdir": "~1.0.1" } }, "tmpl": { @@ -6204,6 +7767,11 @@ "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", "dev": true }, + "to-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", + "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" + }, "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", @@ -6216,7 +7784,29 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "kind-of": "3.2.2" + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" } }, "toml": { @@ -6226,12 +7816,12 @@ "dev": true }, "tough-cookie": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz", - "integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", + "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "dev": true, "requires": { - "punycode": "1.4.1" + "punycode": "^1.4.1" }, "dependencies": { "punycode": { @@ -6243,17 +7833,28 @@ } }, "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", - "dev": true + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } }, "trim-repeated": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", "requires": { - "escape-string-regexp": "1.0.5" + "escape-string-regexp": "^1.0.2" } }, "trim-right": { @@ -6267,7 +7868,7 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { @@ -6283,7 +7884,7 @@ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", "dev": true, "requires": { - "prelude-ls": "1.1.2" + "prelude-ls": "~1.1.2" } }, "type-detect": { @@ -6292,6 +7893,15 @@ "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", "dev": true }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -6304,9 +7914,9 @@ "dev": true, "optional": true, "requires": { - "source-map": "0.5.7", - "uglify-to-browserify": "1.0.2", - "yargs": "3.10.0" + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" }, "dependencies": { "source-map": { @@ -6330,8 +7940,8 @@ "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz", "integrity": "sha512-izD3jxT8xkzwtXRUZjtmRwKnZoeECrfZ8ra/ketwOcusbZEp4mjULMnJOCfTDZBgGQAAY1AJ/IgxcwkavcX9Og==", "requires": { - "buffer": "3.6.0", - "through": "2.3.8" + "buffer": "^3.0.1", + "through": "^2.3.6" }, "dependencies": { "base64-js": { @@ -6345,8 +7955,8 @@ "integrity": "sha1-pyyTb3e5a/UvX357RnGAYoVR3vs=", "requires": { "base64-js": "0.0.8", - "ieee754": "1.1.8", - "isarray": "1.0.0" + "ieee754": "^1.1.4", + "isarray": "^1.0.0" } } } @@ -6363,12 +7973,92 @@ "integrity": "sha1-jN2PusTi0uoefi6Al8QvRCKA+Fs=", "dev": true }, + "union-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", + "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", + "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, "unique-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", "requires": { - "crypto-random-string": "1.0.0" + "crypto-random-string": "^1.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } } }, "unzip-response": { @@ -6377,19 +8067,20 @@ "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" }, "update-notifier": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.3.0.tgz", - "integrity": "sha1-TognpruRUUCrCTVZ1wFOPruDdFE=", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", "requires": { - "boxen": "1.3.0", - "chalk": "2.3.0", - "configstore": "3.1.1", - "import-lazy": "2.1.0", - "is-installed-globally": "0.1.0", - "is-npm": "1.0.0", - "latest-version": "3.1.0", - "semver-diff": "2.1.0", - "xdg-basedir": "3.0.0" + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" } }, "uri-js": { @@ -6397,16 +8088,22 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz", "integrity": "sha1-+QuFhQf4HepNz7s8TD2/orVX+qo=", "requires": { - "punycode": "2.1.0" + "punycode": "^2.1.0" }, "dependencies": { "punycode": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", - "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" } } }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, "url": { "version": "0.10.3", "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", @@ -6417,12 +8114,12 @@ } }, "url-parse": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.2.0.tgz", - "integrity": "sha512-DT1XbYAfmQP65M/mE6OALxmXzZ/z1+e5zk2TcSKe/KiYbNGZxgtttzC0mR/sjopbpOXcbniq7eIKmocJnUWlEw==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.1.tgz", + "integrity": "sha512-x95Td74QcvICAA0+qERaVkRpTGKyBHHYdwL2LXZm5t/gBtCB9KQSO/0zQgSTYEV1p0WcvSg79TLNPSvd5IDJMQ==", "requires": { - "querystringify": "1.0.0", - "requires-port": "1.0.0" + "querystringify": "^2.0.0", + "requires-port": "^1.0.0" } }, "url-parse-lax": { @@ -6430,7 +8127,7 @@ "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", "requires": { - "prepend-http": "1.0.4" + "prepend-http": "^1.0.1" } }, "url-to-options": { @@ -6438,30 +8135,39 @@ "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=" }, + "use": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", + "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true + } + } + }, "user-home": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", "dev": true, "requires": { - "os-homedir": "1.0.2" + "os-homedir": "^1.0.0" } }, "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.0.tgz", + "integrity": "sha512-5n12uMzKCjvB2HPFHnbQSjaqAa98L5iIXmHrZCLavuZVe0qe/SJGbDGWlpaHk5lnBkWRDO+dRu1/PgmUYKPPTw==", "dev": true, "requires": { - "inherits": "2.0.1" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - } + "inherits": "2.0.3" } }, "util-deprecate": { @@ -6469,38 +8175,59 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, "uuid": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=" }, "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", + "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", "dev": true, "requires": { - "spdx-correct": "1.0.2", - "spdx-expression-parse": "1.0.4" + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" } }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - } + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "requires": { + "browser-process-hrtime": "^0.1.2" } }, "walkdir": { @@ -6514,14 +8241,18 @@ "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", "dev": true, "requires": { - "makeerror": "1.0.11" + "makeerror": "1.0.x" } }, "watch": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/watch/-/watch-0.10.0.tgz", - "integrity": "sha1-d3mLLaD5kQ1ZXxrOWwwiWFIfIdw=", - "dev": true + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz", + "integrity": "sha1-KAlUdsbffJDJYxOJkMClQj60uYY=", + "dev": true, + "requires": { + "exec-sh": "^0.2.0", + "minimist": "^1.2.0" + } }, "webidl-conversions": { "version": "4.0.2", @@ -6536,43 +8267,50 @@ "dev": true, "requires": { "iconv-lite": "0.4.19" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", + "dev": true + } } }, "whatwg-fetch": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", - "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" + }, + "whatwg-mimetype": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz", + "integrity": "sha512-FKxhYLytBQiUKjkYteN71fAUA3g6KpNXoho1isLiLSB3N1G4F35Q5vUxWfKFhBwi5IWF27VE6WxhrnnC+m0Mew==", + "dev": true }, "whatwg-url": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-4.8.0.tgz", - "integrity": "sha1-0pgaqRSMHgCkHFphMRZqtGg7vMA=", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", "dev": true, "requires": { - "tr46": "0.0.3", - "webidl-conversions": "3.0.1" - }, - "dependencies": { - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", - "dev": true - } + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" } }, "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { - "isexe": "2.0.0" + "isexe": "^2.0.0" } }, "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, "widest-line": { @@ -6580,7 +8318,7 @@ "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz", "integrity": "sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM=", "requires": { - "string-width": "2.1.1" + "string-width": "^2.1.1" }, "dependencies": { "ansi-regex": { @@ -6598,8 +8336,8 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" } }, "strip-ansi": { @@ -6607,7 +8345,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "requires": { - "ansi-regex": "3.0.0" + "ansi-regex": "^3.0.0" } } } @@ -6625,24 +8363,14 @@ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", "dev": true }, - "worker-farm": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.5.2.tgz", - "integrity": "sha512-XxiQ9kZN5n6mmnW+mFJ+wXjNNI/Nx4DIdaAKLX1Bn6LYBWlN/zaBhu34DQYPZ1AJobQuu67S2OfDdNSVULvXkQ==", - "dev": true, - "requires": { - "errno": "0.1.6", - "xtend": "4.0.1" - } - }, "wrap-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" } }, "wrappy": { @@ -6656,7 +8384,7 @@ "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", "dev": true, "requires": { - "mkdirp": "0.5.1" + "mkdirp": "^0.5.1" } }, "write-file-atomic": { @@ -6664,9 +8392,19 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", "requires": { - "graceful-fs": "4.1.11", - "imurmurhash": "0.1.4", - "signal-exit": "3.0.2" + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "ws": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz", + "integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0" } }, "xdg-basedir": { @@ -6675,9 +8413,9 @@ "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" }, "xml-name-validator": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", - "integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, "xml2js": { @@ -6685,8 +8423,8 @@ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.17.tgz", "integrity": "sha1-F76T6q4/O3eTWceVtBlwWogX6Gg=", "requires": { - "sax": "1.2.1", - "xmlbuilder": "4.2.1" + "sax": ">=0.6.0", + "xmlbuilder": "^4.1.0" } }, "xmlbuilder": { @@ -6694,7 +8432,7 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-4.2.1.tgz", "integrity": "sha1-qlijBBoGb5DqoWwvU4n/GfP0YaU=", "requires": { - "lodash": "4.17.4" + "lodash": "^4.0.0" } }, "xtend": { @@ -6725,9 +8463,9 @@ "dev": true, "optional": true, "requires": { - "camelcase": "1.2.1", - "cliui": "2.1.0", - "decamelize": "1.2.0", + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", "window-size": "0.1.0" }, "dependencies": { @@ -6741,45 +8479,32 @@ } }, "yargs-parser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", - "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", + "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", "dev": true, "requires": { - "camelcase": "3.0.0" - }, - "dependencies": { - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - } + "camelcase": "^4.1.0" } }, "yauzl": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.9.1.tgz", - "integrity": "sha1-qBmB6nCleUYTOIPwKcWCGok1mn8=", + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "requires": { - "buffer-crc32": "0.2.13", - "fd-slicer": "1.0.1" + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" } }, - "zen-observable-ts": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.4.4.tgz", - "integrity": "sha512-SNVY1sWWhoe7FwFmHpD9ERi+7Mhhj3+JdS0BGy2UxLIg7cY+3zQbyZauQCI6DN6YK4uoKNaIm3S7Qkqi1Lr+Fw==" - }, "zip-stream": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-1.2.0.tgz", "integrity": "sha1-qLxF9MG0lpnGuQGYuqyqzbzUugQ=", "requires": { - "archiver-utils": "1.3.0", - "compress-commons": "1.2.2", - "lodash": "4.17.4", - "readable-stream": "2.3.3" + "archiver-utils": "^1.3.0", + "compress-commons": "^1.2.0", + "lodash": "^4.8.0", + "readable-stream": "^2.0.0" } } } diff --git a/package.json b/package.json index ec049036b40..37597bebc82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "serverless", - "version": "1.26.0", + "version": "1.28.0", "engines": { "node": ">=4.0" }, @@ -68,7 +68,7 @@ "devDependencies": { "chai": "^3.5.0", "chai-as-promised": "^6.0.0", - "coveralls": "^2.12.0", + "coveralls": "^3.0.1", "eslint": "^3.3.1", "eslint-config-airbnb": "^10.0.1", "eslint-config-airbnb-base": "^5.0.2", @@ -76,12 +76,12 @@ "eslint-plugin-jsx-a11y": "^2.1.0", "eslint-plugin-react": "^6.1.1", "istanbul": "^0.4.4", - "jest-cli": "^18.0.0", + "jest-cli": "^23.1.0", "jszip": "^3.1.2", "markdown-link": "^0.1.1", "markdown-magic": "^0.1.19", "markdown-table": "^1.1.1", - "mocha": "^3.0.2", + "mocha": "^5.2.0", "mocha-lcov-reporter": "^1.2.0", "mock-require": "^1.3.0", "parse-github-url": "^1.0.1", @@ -92,10 +92,10 @@ }, "dependencies": { "@serverless/fdk": "^0.5.1", - "apollo-client": "^1.9.2", + "@serverless/platform-sdk": "^0.1.5", "archiver": "^1.1.0", "async": "^1.5.2", - "aws-sdk": "^2.75.0", + "aws-sdk": "^2.228.0", "bluebird": "^3.5.0", "chalk": "^2.0.0", "ci-info": "^1.1.1", @@ -106,9 +106,7 @@ "get-stdin": "^5.0.1", "globby": "^6.1.0", "graceful-fs": "^4.1.11", - "graphql": "^0.10.1", - "graphql-tag": "^2.4.0", - "https-proxy-agent": "^1.0.0", + "https-proxy-agent": "^2.2.1", "is-docker": "^1.1.0", "js-yaml": "^3.6.1", "json-cycle": "^1.3.0", @@ -118,9 +116,7 @@ "minimist": "^1.2.0", "moment": "^2.13.0", "node-fetch": "^1.6.0", - "node-forge": "^0.7.1", "object-hash": "^1.2.0", - "opn": "^5.0.0", "promise-queue": "^2.2.3", "raven": "^1.2.1", "rc": "^1.1.6", diff --git a/tests/templates/test_all_templates b/tests/templates/test_all_templates index fd88105abf8..d207639bc35 100755 --- a/tests/templates/test_all_templates +++ b/tests/templates/test_all_templates @@ -8,8 +8,8 @@ function integration-test { $DIR/integration-test-template $@ } -integration-test aws-csharp 'apt-get -qq update && apt-get -qq -y install zip && dotnet restore && dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip' -integration-test aws-fsharp 'apt-get -qq update && apt-get -qq -y install zip && dotnet restore && dotnet lambda package --configuration release --framework netcoreapp1.0 --output-package bin/release/netcoreapp1.0/deploy-package.zip' +integration-test aws-csharp './build.sh' +integration-test aws-fsharp './build.sh' integration-test aws-go 'cd /go/src/app && make build' integration-test aws-go-dep 'cd /go/src/app && make build' integration-test aws-groovy-gradle ./gradlew build