├── .eslintrc ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── example ├── .gitignore ├── README.md ├── deploy.sh ├── download-swagger-json.sh ├── extract-rest-api-id.js ├── handler.js ├── package.json ├── remove.sh ├── serverless.yml └── yarn.lock ├── package.json ├── src ├── documentation.js ├── documentation.spec.js ├── downloadDocumentation.js ├── downloadDocumentation.spec.js ├── functionDocumentationParts.json ├── globalDocumentationParts.json ├── index.js ├── index.spec.js ├── models.js ├── models.spec.js └── swagger.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "ecmaFeatures": { 4 | "modules": true 5 | }, 6 | "rules": { 7 | "no-console": [ 8 | "error", 9 | { "allow": ["warn", "error", "info"] } 10 | ], 11 | "no-underscore-dangle": 0, 12 | "no-restricted-syntax": ["error", "WithStatement"], 13 | "no-param-reassign": ["error", { 14 | "props": false 15 | }], 16 | "func-names": 0, 17 | "prefer-arrow-callback": 0, 18 | "quotes": ["error", "single", { "allowTemplateLiterals": true }] 19 | }, 20 | "env": { 21 | "browser": true, 22 | "node": true, 23 | "es6": true 24 | }, 25 | "globals": { 26 | "afterEach": false, 27 | "beforeEach": false, 28 | "describe": false, 29 | "expect": false, 30 | "it": false, 31 | "jasmine": false, 32 | "pending": false, 33 | "spyOn": false, 34 | "waits": false, 35 | "waitsFor": false, 36 | "xdescribe": false, 37 | "xit": false, 38 | "beforeAll": false, 39 | "afterAll": false, 40 | "runs": false 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: node_js 4 | node_js: 5 | - "6" 6 | cache: 7 | bundler: true 8 | directories: 9 | - node_modules 10 | after_success: 11 | - npm run codecov 12 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright © 2016 9Cookies GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the “Software”), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![serverless](http://public.serverless.com/badges/v3.svg)](http://www.serverless.com) [![Build Status](https://travis-ci.org/9cookies/serverless-aws-documentation.svg?branch=master)](https://travis-ci.org/9cookies/serverless-aws-documentation) [![codecov](https://codecov.io/gh/9cookies/serverless-aws-documentation/branch/master/graph/badge.svg)](https://codecov.io/gh/9cookies/serverless-aws-documentation) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/hyperium/hyper/master/LICENSE) 2 | 3 | # Serverless AWS Documentation 4 | 5 | This is a [Serverless](http://www.serverless.com) v1 plugin that adds support for AWS API Gateway 6 | documentation and models (e.g. to export a Swagger JSON file with input/output definitions and full text 7 | documentation for API documentation). 8 | 9 | ## Important notice 10 | This plugin is no longer maintained and therefore archived. 11 | If you want to continue this project feel free to fork it and publish it under a new name. 12 | Unfortunately we can't transfer ownership of this project. 13 | 14 | ## What is AWS API Gateway documentation? 15 | 16 | Amazon introduced a new documentation feature for it's API Gateway on AWS at the end of 2016. With this you can add manually written documentation to all parts of API Gateway such as resources, requests, responses or single path or query parameters. When exporting Swagger from API Gateway these documentation is added to the other information to create a more human understandable documentation. 17 | 18 | In addition to this documentation this plugin also adds support to add models to API Gateway and use it with the serverless functions. Models are JSON Schemas that define the structure of request or response bodies. This includes property structure, their types and their validation. More about this you'll find here: https://spacetelescope.github.io/understanding-json-schema/ 19 | 20 | ## Install 21 | 22 | This plugin only works for Serverless 1.0 and up. For a plugin that supports 0.5 look at 23 | [this plugin](https://github.com/HyperBrain/serverless-models-plugin). 24 | 25 | To install this plugin, add `serverless-aws-documentation` to your package.json: 26 | 27 | ``` 28 | npm install serverless-aws-documentation --save-dev 29 | ``` 30 | 31 | Next, add the `serverless-aws-documentation` plugin in to serverless.yml file: 32 | If you don't already have a plugins section, create one that looks like this: 33 | 34 | ```YAML 35 | plugins: 36 | - serverless-aws-documentation 37 | ``` 38 | 39 | To verify that the plugin was added successfully, run this in your command line: 40 | ``` 41 | serverless 42 | ``` 43 | 44 | The plugin should show up in the "Plugins" section of the output as "ServerlessAWSDocumentation" 45 | 46 | ## Example serverless.yml 47 | 48 | You can find a fully functioning serverless project with examples of documentation in the `./example/` directory. See the [README.md](./example/README.md) in there for more details. 49 | 50 | ## Usage 51 | 52 | There are two places you need to touch in the `serverless.yml`: *custom variables* to define your 53 | general documentation descriptions and models, and the *http* events in your `functions` section to 54 | add these models to your requests and responses and add description to function relevant parts. 55 | 56 | ### Define descriptions for your documentation 57 | 58 | For manual full text descriptions for the parts of your API you need to describe it's structure. 59 | In the general part you can describe your API in general, authorizers, models and resources. 60 | If you want to find out more about models, you can skip to the next section. 61 | 62 | ------ 63 | #### Gotcha with 'version' and 'title' on the API 64 | 65 | Currently (August 2017) you'll have trouble with the `title` and `version` fields for you API description. If you define them as below, they'll be correctly created in API Gateway (you can see it in the web console) but when you export the Swagger document from API Gateway, your title and version will be ignored and replaced with something like: 66 | 67 | version: "2017-08-23T07:59:29Z" 68 | title: dev-your-api-serverless 69 | 70 | ------ 71 | Your general documentation has to be nested in the custom variables section and looks like this: 72 | 73 | ```YAML 74 | custom: 75 | documentation: 76 | api: 77 | info: 78 | version: "2" # see note above about this being ignored 79 | title: "Name of your API" # see note above about this being ignored 80 | description: "This is the best API ever" 81 | termsOfService: "http://www.example.com/terms-of-service" 82 | contact: 83 | name: "John Smith" 84 | url: "http://www.example.com/me" 85 | email: "js@example.com" 86 | license: 87 | name: "Licensing" 88 | url: "http://www.example.com/licensing" 89 | tags: 90 | - 91 | name: "Data Creation" 92 | description: "Services to create things" 93 | - 94 | name: "Some other tag" 95 | description: "A tag for other things" 96 | authorizers: 97 | - 98 | name: "MyCustomAuthorizer" 99 | description: "This is an error" 100 | resources: 101 | - 102 | path: "some/path" 103 | description: "This is the description for some/path" 104 | - 105 | path: "some/other/path" 106 | description: "This is the description for some/other/path" 107 | ``` 108 | 109 | Your documentation has to be nested in the `documentation` custom variable. You describe your 110 | documentation parts with the `description` and `summary` (or `title` for the API itself) properties. The summary is some sort of 111 | title and the description is for further explanation. You can see the expected format in the [Swagger v2 specification for the info object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#info-object). 112 | 113 | On the upper level, under the `documentation/api` section, you describe your API in the `info` object. 114 | In there you also can manually describe the version (needs to be a string). If you don't define the 115 | version, the version that API Gateway needs will automatically be generated. This auto version is a 116 | hash of the documentation you defined, so if you don't change your documentation, the documentation 117 | in API Gateway won't be touched. 118 | 119 | Underneath you can define `tags`, `authorizers`, `resources` and `models` which are all lists of descriptions. 120 | In addition to the description and the summary, Authorizers need the name of the authorizer, resources 121 | need the path of the described resource and models need the name of the model. Tags provides the description for tags that are used on `METHOD`s (HTTP events), [more info here](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#tag-object). 122 | 123 | 124 | ### Define the models 125 | 126 | Models have additional information you have to define. Besides the model name, the description and 127 | the summary, you need to define the *content type* for this model in addition to the *schema* that 128 | describes the model: 129 | 130 | * `contentType`: the content type of the described request/response (like `"application/json"` or 131 | `"application/xml"`). This is mandatory. 132 | * `schema`: The JSON Schema that describes the model. In the examples below external files are 133 | imported, but you can also define the schema inline using YAML format. 134 | 135 | Your models definition could look like this: 136 | 137 | ```YAML 138 | custom: 139 | documentation: 140 | models: 141 | - 142 | name: "ErrorResponse" 143 | description: "This is an error" 144 | contentType: "application/json" 145 | schema: ${file(models/error.json)} 146 | - 147 | name: "CreateRequest" 148 | description: "Model for creating something" 149 | contentType: "application/json" 150 | schema: ${file(models/create_request.json)} 151 | ``` 152 | 153 | Within the schema, you can reference and nest any of your models with the `$ref` keyword, its value should be something like `{{model: YourModelName}}`. For example: 154 | 155 | ```YAML 156 | custom: 157 | documentation: 158 | models: 159 | - 160 | name: "Address" 161 | description: "This is an address" 162 | contentType: "application/json" 163 | schema: 164 | type: "object" 165 | properties: 166 | street: 167 | type: "string" 168 | - 169 | name: "Customer" 170 | description: "This is a customer" 171 | contentType: "application/json" 172 | schema: 173 | type: "object" 174 | properties: 175 | name: 176 | type: "string" 177 | address: 178 | $ref: "{{model: Address}}" 179 | ``` 180 | 181 | ### Function specific documentation 182 | 183 | When you want to describe the parts inside a `RESOURCE` you need to do this in the functions 184 | described in your `serverless.yml`. Inside the `http` event of your functions you need to add the 185 | `documentation` property which can hold the following parts: 186 | 187 | * The method description which is described directly inside the `documentation` property 188 | * `requestBody`: The body of your HTTP request 189 | * `requestHeaders`: A list of headers for your HTTP request (needs `name` of the header) 190 | * `queryParams`: A list of query parameters (needs `name` of the parameter) 191 | * `pathParams`: A list of path parameters (needs `name` of the parameter) 192 | * `methodResponses`: A list of method responses (needs the `statusCode` of the response) 193 | * `tags`: A list of tags apply to the `METHOD`, which is the HTTP event in serverless. Used in [Swagger-UI](https://swagger.io/swagger-ui/) 194 | 195 | The methodResponses itself can have the following parts: 196 | 197 | * `responseBody`: The body of the HTTP request 198 | * `responseHeaders`: A list of headers for your HTTP response (needs `name` of the header) 199 | 200 | With this your function definition could look like this: 201 | 202 | ```YAML 203 | createItem: 204 | handler: handler.create 205 | events: 206 | - http: 207 | path: create 208 | method: post 209 | documentation: 210 | summary: "Create something" 211 | description: "Creates the thing you need" 212 | tags: 213 | - "Data Creation" 214 | - "Some other tag" 215 | requestBody: 216 | description: "Request body description" 217 | requestHeaders: 218 | - 219 | name: "x-header" 220 | description: "Header description" 221 | - 222 | name: "Authorization" 223 | description: "Auth Header description" 224 | queryParams: 225 | - 226 | name: "sid" 227 | description: "Session ID" 228 | - 229 | name: "theme" 230 | description: "Theme for for the website" 231 | pathParams: 232 | - 233 | name: "id" 234 | description: "ID of the thing you want to create" 235 | requestModels: 236 | "application/json": "CreateRequest" 237 | "application/xml": "CreateRequestXml" 238 | methodResponses: 239 | - 240 | statusCode: "200" 241 | responseBody: 242 | description: "Response body description" 243 | responseHeaders: 244 | - 245 | name: "x-superheader" 246 | description: "this is a super header" 247 | responseModels: 248 | "application/json": "CreateResponse" 249 | - 250 | statusCode: "400" 251 | responseModels: 252 | "application/json": "ErrorResponse" 253 | ``` 254 | 255 | To add your defined models to the function you also need the following properties. 256 | 257 | #### requestModels 258 | 259 | In the `requestModels` property you can add models for the HTTP request of the function. You can have 260 | multiple models for different `ContentType`s. Inside the `requestModels` property you define the 261 | content type as the key and the model name defined in the models section above as the value. 262 | Here's short example: 263 | 264 | ```YAML 265 | requestModels: 266 | "application/json": "CreateRequest" 267 | "application/xml": "CreateRequestXml" 268 | ``` 269 | 270 | #### methodResponses.responseModels 271 | 272 | In the `methodResponses` property you can define multiple response models for this function. 273 | The response models are described in the `ResponseModels` property which contains the models for the 274 | different content types. These response models are described like the `requestModels` above. 275 | 276 | ```YAML 277 | methodResponses: 278 | - 279 | statusCode: "200" 280 | responseModels: 281 | "application/json": "CreateResponse" 282 | "application/xml": "CreateResponseXml" 283 | - 284 | statusCode: "400" 285 | responseModels: 286 | "application/json": "ErrorResponse" 287 | ``` 288 | 289 | In the full example above you also can see the definition of the `requestModels` and `responseModels` 290 | in a the context of the documentation. 291 | 292 | ### Deploy the documentation 293 | 294 | To deploy the models you described above you just need to use `serverless deploy` as you are used to. 295 | 296 | If you've defined `requestHeaders` in your documentation this will add those request headers to the CloudFormation being deployed, if you haven't already defined those request parameters yourself. If you don't want this, add the option `--doc-safe-mode` when deploying. If you use that option you need to define the request parameters manually to have them included in the documentation, e.g. 297 | 298 | ```YAML 299 | ApiGatewayMethod{normalizedPath}{normalizedMethod}: 300 | Properties: 301 | RequestParameters: 302 | method.request.header.{header-name}: true|false 303 | ``` 304 | 305 | See the Serverless documentation for more information on [resource naming](https://serverless.com/framework/docs/providers/aws/guide/resources/), and the AWS documentation for more information on [request parameters](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-apitgateway-method-integration.html#cfn-apigateway-method-integration-requestparameters). 306 | 307 | ### Download documentation from AWS API Gateway 308 | 309 | To download the deployed documentation you just need to use `serverless downloadDocumentation --outputFileName=filename.ext`. 310 | For `yml` or `yaml` extensions application/yaml content will be downloaded from AWS. In any other case - application/json. 311 | Optional argument --extensions ['integrations', 'apigateway', 'authorizers', 'postman']. Defaults to 'integrations'. 312 | 313 | ## Contribution 314 | 315 | When you think something is missing or found some bug, please add an issue to this repo. If you want 316 | to contribute code, just fork this repo and create a PR when you are finished. Pull Requests are only 317 | accepted when there are unit tests covering your code. 318 | 319 | ## License 320 | 321 | MIT 322 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # package directories 2 | node_modules 3 | jspm_packages 4 | 5 | # Serverless directories 6 | .serverless 7 | 8 | serverless-aws-documentation-example-.json 9 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # serverless-aws-documentation-example 2 | 3 | This project demonstrates how to document your Serverless API running on AWS. 4 | 5 | You can use this by project by doing 6 | 7 | ./deploy.sh 8 | ./download-swagger-json.sh 9 | # grab the downloaded file and copy-paste it into http://editor.swagger.io/ 10 | 11 | Once you're done, you can remove the API with: 12 | 13 | ./remove.sh 14 | -------------------------------------------------------------------------------- /example/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Deploys the API 3 | cd `dirname "$0"` 4 | set -e 5 | 6 | sls deploy 7 | 8 | echo "Now run $(tput setaf 2)./download-swagger-json.sh$(tput sgr0) to get your Swagger document" 9 | -------------------------------------------------------------------------------- /example/download-swagger-json.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Downloads the JSON Swagger document 3 | cd `dirname $0` 4 | set -e 5 | stage=dev 6 | region=us-east-1 # must match serverless.yml:provider.region 7 | apiId=`bash -c "aws apigateway get-rest-apis --output=json --region=$region | /usr/bin/env node ./extract-rest-api-id.js $stage"` 8 | fileType=json 9 | outputFileName=serverless-aws-documentation-example-$STAGE.$fileType 10 | printf "Downloading Swagger definition to ./$outputFileName 11 | API ID: $apiId 12 | Stage: $stage 13 | Accept: $fileType\n\n" 14 | 15 | aws apigateway get-export \ 16 | --rest-api-id=$apiId \ 17 | --stage-name=$stage \ 18 | --export-type=swagger \ 19 | --accept=application/$fileType \ 20 | --region=$region \ 21 | $outputFileName 22 | 23 | printf " 24 | $(tput setaf 2)Done, your swagger document is: ./$outputFileName$(tput sgr0) 25 | Go to http://editor.swagger.io/ and paste the contents of your swagger document to see it in Swagger UI" 26 | -------------------------------------------------------------------------------- /example/extract-rest-api-id.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict' 3 | let stdin = process.stdin, 4 | stdout = process.stdout, 5 | inputChunks = [] 6 | 7 | const stage = process.argv[2] 8 | if (typeof stage === 'undefined') { 9 | const msg = "[ERROR] mandatory parameter 'stage' is not present" 10 | console.error(msg) 11 | console.error(`usage: aws apigateway get-rest-apis --output json | ${process.argv[1]} `) 12 | console.error(` eg: aws apigateway get-rest-apis --output json | ${process.argv[1]} dev`) 13 | throw new Error(msg) 14 | } 15 | const targetRestApiName = stage + '-serverless-aws-documentation-example' 16 | 17 | stdin.resume() 18 | stdin.setEncoding('utf8') 19 | 20 | stdin.on('data', function (chunk) { 21 | inputChunks.push(chunk); 22 | }) 23 | 24 | stdin.on('end', function () { 25 | let inputJSON = inputChunks.join('') 26 | let parsedData = JSON.parse(inputJSON) 27 | parsedData.items.forEach(function (curr) { 28 | if (curr.name === targetRestApiName) { 29 | stdout.write(curr.id) 30 | stdout.write('\n') 31 | } 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /example/handler.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const responseHeadersJson = { 4 | 'Access-Control-Allow-Origin': '*', // Required for CORS support to work 5 | 'Access-Control-Allow-Credentials': true, 6 | 'Access-Control-Expose-Headers': 'link', 7 | 'Content-Type': "'application/json'" 8 | } 9 | 10 | module.exports.router = (event, context, callback) => { 11 | const routes = { 12 | '/example/message': exampleMessageGet, 13 | '/example/do-something': exampleDoSomethingPost 14 | } 15 | let handler = routes[event.path] 16 | const response = handler(event, context, callback) 17 | callback(null, response) 18 | } 19 | 20 | function exampleMessageGet (event, context, callback) { 21 | let headers = responseHeadersJson 22 | let scheme = event.headers['X-Forwarded-Proto'] 23 | let host = event.headers.Host 24 | let path = event.requestContext.path.replace('example/message','example/do-something') 25 | headers.link = `<${scheme}://${host}${path}>; rel="related"` 26 | return { 27 | statusCode: 200, 28 | headers: headers, 29 | body: JSON.stringify({ 30 | message: 'Hello, World!', 31 | }) 32 | } 33 | } 34 | 35 | function exampleDoSomethingPost (event, context, callback) { 36 | let body = JSON.parse(event.body) 37 | if (body.constructor !== Array) { 38 | return { 39 | statusCode: 400, 40 | headers: responseHeadersJson, 41 | body: JSON.stringify({ 42 | message: 'The supplied request body must be a JSON array', 43 | statusCode: '400' 44 | }) 45 | } 46 | } 47 | let itemCount = body.length 48 | return { 49 | statusCode: 200, 50 | headers: responseHeadersJson, 51 | body: JSON.stringify({ 52 | result: 'Thanks for sending that data', 53 | submittedItems: itemCount 54 | }) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-aws-documentation-example", 3 | "version": "1.0.0", 4 | "description": "Example project showing available options", 5 | "license": "MIT", 6 | "dependencies": { 7 | "serverless-aws-documentation": "file:.." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/remove.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Removes the API 3 | cd `dirname "$0"` 4 | set -e 5 | 6 | sls remove 7 | 8 | echo 'Done, all cleaned up' 9 | -------------------------------------------------------------------------------- /example/serverless.yml: -------------------------------------------------------------------------------- 1 | service: serverless-aws-documentation-example 2 | 3 | provider: 4 | name: aws 5 | runtime: nodejs6.10 6 | region: us-east-1 7 | 8 | # Remember to reference the plugin 9 | plugins: 10 | - serverless-aws-documentation 11 | 12 | # Let's keep this deployment lightweight. Probably not something you should copy 13 | package: 14 | exclude: 15 | - ./** 16 | include: 17 | - handler.js 18 | 19 | custom: 20 | # You must have the documentation object 21 | documentation: 22 | # this is general info about the API 23 | api: 24 | info: 25 | version: '2' 26 | title: Example API 27 | description: Some example API 28 | termsOfService: https://www.google.com 29 | contact: 30 | name: The contact person 31 | url: https://www.serverless.com/framework 32 | email: some-fake@email.com 33 | license: 34 | name: The license 35 | url: https://www.github.com 36 | tags: 37 | - 38 | name: Tag1 39 | description: The first tag 40 | - 41 | name: Tag2 42 | description: That other tag that we all love 43 | # Now we describe all the models that we use 44 | models: 45 | - 46 | name: MessageResponse 47 | contentType: "application/json" 48 | schema: 49 | type: object 50 | properties: 51 | message: 52 | type: string 53 | - 54 | name: DoSomethingRequest 55 | contentType: "application/json" 56 | schema: 57 | type: array 58 | items: 59 | type: string 60 | - 61 | name: DoSomethingResponse 62 | contentType: "application/json" 63 | schema: 64 | type: object 65 | properties: 66 | result: 67 | type: string 68 | submittedItems: 69 | type: number 70 | - 71 | name: 400JsonResponse 72 | contentType: "application/json" 73 | schema: 74 | type: object 75 | properties: 76 | message: 77 | type: string 78 | statusCode: 79 | type: number 80 | commonModelSchemaFragments: 81 | # defining common fragments means you can reference them with a single line 82 | MethodResponse400Json: 83 | statusCode: '400' 84 | responseModels: 85 | "application/json": 400JsonResponse 86 | 87 | functions: 88 | theRouter: 89 | handler: handler.router 90 | events: 91 | - http: 92 | path: example/message 93 | method: get 94 | cors: true 95 | documentation: 96 | summary: Gets a simple message 97 | tags: 98 | - Tag1 99 | description: > 100 | Demonstrates a GET method. You can see query string parameters, 101 | request headers, response body and response headers. 102 | queryParams: 103 | - 104 | name: firstParam 105 | description: The first param that we want, you MUST pass it 106 | required: true 107 | - 108 | name: secondParam 109 | description: The second param. This one is optional 110 | methodResponses: 111 | - 112 | statusCode: '200' 113 | responseModels: 114 | "application/json": MessageResponse 115 | responseHeaders: 116 | - 117 | name: link 118 | description: describes other actions that can be taken 119 | type: string 120 | - http: 121 | path: example/do-something 122 | method: post 123 | cors: true 124 | documentation: 125 | summary: Takes a request body 126 | tags: 127 | - Tag2 128 | description: Demonstrates a POST method. We show a request body here and have multiple response models. 129 | requestModels: 130 | "application/json": DoSomethingRequest 131 | methodResponses: 132 | - 133 | statusCode: '200' 134 | responseModels: 135 | "application/json": DoSomethingResponse 136 | - ${self:custom.commonModelSchemaFragments.MethodResponse400Json} 137 | -------------------------------------------------------------------------------- /example/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | object-hash@^1.1.7: 6 | version "1.1.8" 7 | resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.1.8.tgz#28a659cf987d96a4dabe7860289f3b5326c4a03c" 8 | 9 | "serverless-aws-documentation@file:..": 10 | version "0.8.0" 11 | dependencies: 12 | object-hash "^1.1.7" 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serverless-aws-documentation", 3 | "version": "1.1.0", 4 | "description": "Serverless 1.0 plugin to add documentation and models to the serverless generated API Gateway", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "codecov": "cat coverage/*/lcov.info | codecov", 8 | "test": "istanbul cover -x \"src/index.spec.js\" jasmine ./src/index.spec.js ./src/documentation.spec.js ./src/models.spec.js ./src/downloadDocumentation.spec.js", 9 | "test:nocoverage": "jasmine ./src/index.spec.js" 10 | }, 11 | "repository": "9cookies/serverless-aws-documentation", 12 | "author": "Simon Jentsch (https://twitter.com/tchockie)", 13 | "license": "MIT", 14 | "devDependencies": { 15 | "codecov": "^1.0.1", 16 | "eslint": "^3.11.1", 17 | "istanbul": "^0.4.5", 18 | "jasmine": "^2.5.2", 19 | "jasmine-diff": "^0.1.2" 20 | }, 21 | "dependencies": { 22 | "object-hash": "^1.1.7" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/documentation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const objectHash = require('object-hash'); 4 | 5 | const globalDocumentationParts = require('./globalDocumentationParts.json'); 6 | const functionDocumentationParts = require('./functionDocumentationParts.json'); 7 | 8 | function getDocumentationProperties(def, propertiesToGet) { 9 | const docProperties = new Map(); 10 | propertiesToGet.forEach((key) => { 11 | if (def[key]) { 12 | docProperties.set(key, def[key]); 13 | } 14 | }); 15 | return docProperties; 16 | } 17 | 18 | function _mapToObj(map) { 19 | const returnObj = {}; 20 | map.forEach((val, key) => { 21 | returnObj[key] = val; 22 | }); 23 | 24 | return returnObj; 25 | } 26 | 27 | /* 28 | * Different types support different extra properties beyond 29 | * the basic ones, so we need to make sure we only look for 30 | * the appropriate properties. 31 | */ 32 | function determinePropertiesToGet (type) { 33 | const defaultProperties = ['description', 'summary'] 34 | let result = defaultProperties 35 | switch (type) { 36 | case 'API': 37 | result.push('tags', 'info') 38 | break 39 | case 'METHOD': 40 | result.push('tags') 41 | break 42 | } 43 | return result 44 | 45 | } 46 | 47 | var autoVersion; 48 | 49 | module.exports = function() { 50 | return { 51 | _createDocumentationPart: function _createDocumentationPart(part, def, knownLocation) { 52 | const location = part.locationProps.reduce((loc, property) => { 53 | loc[property] = knownLocation[property] || def[property]; 54 | return loc; 55 | }, {}); 56 | location.type = part.type; 57 | const propertiesToGet = determinePropertiesToGet(location.type) 58 | 59 | const props = getDocumentationProperties(def, propertiesToGet); 60 | if (props.size > 0) { 61 | this.documentationParts.push({ 62 | location, 63 | properties: _mapToObj(props), 64 | restApiId: this.restApiId, 65 | }); 66 | } 67 | 68 | if (part.children) { 69 | this.createDocumentationParts(part.children, def, location); 70 | } 71 | }, 72 | 73 | createDocumentationPart: function createDocumentationPart(part, def, knownLocation) { 74 | if (part.isList) { 75 | if (!(def instanceof Array)) { 76 | const msg = `definition for type "${part.type}" is not an array`; 77 | console.info('-------------------'); 78 | console.info(msg); 79 | throw new Error(msg); 80 | } 81 | 82 | def.forEach((singleDef) => this._createDocumentationPart(part, singleDef, knownLocation)); 83 | } else { 84 | this._createDocumentationPart(part, def, knownLocation); 85 | } 86 | }, 87 | 88 | createDocumentationParts: function createDocumentationParts(parts, def, knownLocation) { 89 | Object.keys(parts).forEach((part) => { 90 | if (def[part]) { 91 | this.createDocumentationPart(parts[part], def[part], knownLocation); 92 | } 93 | }); 94 | }, 95 | 96 | _updateDocumentation: function _updateDocumentation() { 97 | const aws = this.serverless.providers.aws; 98 | return aws.request('APIGateway', 'getDocumentationVersion', { 99 | restApiId: this.restApiId, 100 | documentationVersion: this.getDocumentationVersion(), 101 | }).then(() => { 102 | const msg = 'documentation version already exists, skipping upload'; 103 | console.info('-------------------'); 104 | console.info(msg); 105 | return Promise.reject(msg); 106 | }, err => { 107 | if (err.message === 'Invalid Documentation version specified') { 108 | return Promise.resolve(); 109 | } 110 | 111 | return Promise.reject(err); 112 | }) 113 | .then(() => 114 | aws.request('APIGateway', 'getDocumentationParts', { 115 | restApiId: this.restApiId, 116 | limit: 9999, 117 | }) 118 | ) 119 | .then(results => results.items.map( 120 | part => aws.request('APIGateway', 'deleteDocumentationPart', { 121 | documentationPartId: part.id, 122 | restApiId: this.restApiId, 123 | }) 124 | )) 125 | .then(promises => Promise.all(promises)) 126 | .then(() => this.documentationParts.reduce((promise, part) => { 127 | return promise.then(() => { 128 | part.properties = JSON.stringify(part.properties); 129 | return aws.request('APIGateway', 'createDocumentationPart', part); 130 | }); 131 | }, Promise.resolve())) 132 | .then(() => aws.request('APIGateway', 'createDocumentationVersion', { 133 | restApiId: this.restApiId, 134 | documentationVersion: this.getDocumentationVersion(), 135 | stageName: this.options.stage, 136 | })); 137 | }, 138 | 139 | getGlobalDocumentationParts: function getGlobalDocumentationParts() { 140 | const globalDocumentation = this.customVars.documentation; 141 | this.createDocumentationParts(globalDocumentationParts, globalDocumentation, {}); 142 | }, 143 | 144 | getFunctionDocumentationParts: function getFunctionDocumentationParts() { 145 | const httpEvents = this._getHttpEvents(); 146 | Object.keys(httpEvents).forEach(funcNameAndPath => { 147 | const httpEvent = httpEvents[funcNameAndPath]; 148 | const path = httpEvent.path; 149 | const method = httpEvent.method.toUpperCase(); 150 | this.createDocumentationParts(functionDocumentationParts, httpEvent, { path, method }); 151 | }); 152 | }, 153 | 154 | _getHttpEvents: function _getHttpEvents() { 155 | return this.serverless.service.getAllFunctions().reduce((documentationObj, functionName) => { 156 | const func = this.serverless.service.getFunction(functionName); 157 | func.events 158 | .filter((eventTypes) => eventTypes.http && eventTypes.http.documentation) 159 | .map((eventTypes) => eventTypes.http) 160 | .forEach(currEvent => { 161 | let key = functionName + currEvent.method + currEvent.path; 162 | documentationObj[key] = currEvent; 163 | }); 164 | return documentationObj; 165 | }, {}); 166 | }, 167 | 168 | generateAutoDocumentationVersion: function generateAutoDocumentationVersion() { 169 | const versionObject = { 170 | globalDocs: this.customVars.documentation, 171 | functionDocs: {}, 172 | } 173 | 174 | const httpEvents = this._getHttpEvents(); 175 | Object.keys(httpEvents).forEach(funcName => { 176 | versionObject.functionDocs[funcName] = httpEvents[funcName].documentation; 177 | }); 178 | 179 | autoVersion = objectHash(versionObject); 180 | 181 | return autoVersion; 182 | }, 183 | 184 | getDocumentationVersion: function getDocumentationVersion() { 185 | return this.customVars.documentation.version || autoVersion || this.generateAutoDocumentationVersion(); 186 | }, 187 | 188 | _buildDocumentation: function _buildDocumentation(result) { 189 | this.restApiId = result.Stacks[0].Outputs 190 | .filter(output => output.OutputKey === 'AwsDocApiId') 191 | .map(output => output.OutputValue)[0]; 192 | 193 | this.getGlobalDocumentationParts(); 194 | this.getFunctionDocumentationParts(); 195 | 196 | if (this.options.noDeploy) { 197 | console.info('-------------------'); 198 | console.info('documentation parts:'); 199 | console.info(this.documentationParts); 200 | return; 201 | } 202 | 203 | return this._updateDocumentation(); 204 | }, 205 | 206 | addDocumentationToApiGateway: function addDocumentationToApiGateway(resource, documentationPart, mapPath) { 207 | if (documentationPart && Object.keys(documentationPart).length > 0) { 208 | if (!resource.Properties.RequestParameters) { 209 | resource.Properties.RequestParameters = {}; 210 | } 211 | 212 | documentationPart.forEach(function(qp) { 213 | const source = `method.request.${mapPath}.${qp.name}`; 214 | if (resource.Properties.RequestParameters.hasOwnProperty(source)) return; // don't mess with existing config 215 | resource.Properties.RequestParameters[source] = qp.required || false; 216 | }); 217 | } 218 | }, 219 | 220 | updateCfTemplateFromHttp: function updateCfTemplateFromHttp(eventTypes) { 221 | if (eventTypes.http && eventTypes.http.documentation) { 222 | const resourceName = this.normalizePath(eventTypes.http.path); 223 | const methodLogicalId = this.getMethodLogicalId(resourceName, eventTypes.http.method); 224 | const resource = this.cfTemplate.Resources[methodLogicalId]; 225 | 226 | resource.DependsOn = new Set(); 227 | this.addMethodResponses(resource, eventTypes.http.documentation); 228 | this.addRequestModels(resource, eventTypes.http.documentation); 229 | if (!this.options['doc-safe-mode']) { 230 | this.addDocumentationToApiGateway( 231 | resource, 232 | eventTypes.http.documentation.requestHeaders, 233 | 'header' 234 | ); 235 | this.addDocumentationToApiGateway( 236 | resource, 237 | eventTypes.http.documentation.queryParams, 238 | 'querystring' 239 | ); 240 | this.addDocumentationToApiGateway( 241 | resource, 242 | eventTypes.http.documentation.pathParams, 243 | 'path' 244 | ); 245 | } 246 | resource.DependsOn = Array.from(resource.DependsOn); 247 | if (resource.DependsOn.length === 0) { 248 | delete resource.DependsOn; 249 | } 250 | } 251 | }, 252 | 253 | _getDocumentationProperties: getDocumentationProperties 254 | }; 255 | }; 256 | -------------------------------------------------------------------------------- /src/documentation.spec.js: -------------------------------------------------------------------------------- 1 | describe('ServerlessAWSDocumentation', function () { 2 | 3 | const objectUnderTest = require('./documentation.js')() 4 | 5 | beforeEach(() => { 6 | objectUnderTest.documentationParts = [] 7 | objectUnderTest.restApiId = 'testApiId' 8 | }) 9 | 10 | describe('_createDocumentationPart', () => { 11 | it('should include the tags property for an API part', () => { 12 | let part = { 13 | type: 'API', 14 | isList: false, 15 | locationProps: [] 16 | } 17 | let def = { 18 | info: { 19 | title: 'the title', 20 | description: 'the desc', 21 | version: 123, 22 | termsOfService: 'http://www.example.com/terms-of-service', 23 | contact: { 24 | name: 'John Smith', 25 | url: 'http://www.example.com/me', 26 | email: 'js@example.com' 27 | }, 28 | license: { 29 | name: 'Licensing', 30 | url: 'http://www.example.com/licensing' 31 | } 32 | }, 33 | tags: ['tag1'] 34 | } 35 | let knownLocation = {} 36 | objectUnderTest._createDocumentationPart(part, def, knownLocation) 37 | let result = objectUnderTest.documentationParts 38 | expect(result.length).toBe(1) 39 | expect(result).toEqual([ 40 | { 41 | location: { 42 | type: 'API' 43 | }, 44 | properties: { 45 | info: { 46 | title: 'the title', 47 | description: 'the desc', 48 | version: 123, 49 | termsOfService: 'http://www.example.com/terms-of-service', 50 | contact: { 51 | name: 'John Smith', 52 | url: 'http://www.example.com/me', 53 | email: 'js@example.com' 54 | }, 55 | license: { 56 | name: 'Licensing', 57 | url: 'http://www.example.com/licensing' 58 | } 59 | }, 60 | tags: ['tag1'] 61 | }, 62 | restApiId: 'testApiId' 63 | } 64 | ]) 65 | }) 66 | 67 | it('should include the tags property for a METHOD part', () => { 68 | let part = { 69 | type: 'METHOD', 70 | isList: false, 71 | locationProps: ['path', 'method'], 72 | children: {} 73 | } 74 | let def = { 75 | description: 'the desc', 76 | summary: 'the summary', 77 | tags: ['tag1'] 78 | } 79 | let knownLocation = { 80 | path: '/some/path', 81 | method: 'GET' 82 | } 83 | objectUnderTest._createDocumentationPart(part, def, knownLocation) 84 | let result = objectUnderTest.documentationParts 85 | expect(result.length).toBe(1) 86 | expect(result).toEqual([ 87 | { 88 | location: { 89 | type: 'METHOD', 90 | path: '/some/path', 91 | method: 'GET' 92 | }, 93 | properties: { 94 | description: 'the desc', 95 | summary: 'the summary', 96 | tags: ['tag1'] 97 | }, 98 | restApiId: 'testApiId' 99 | } 100 | ]) 101 | }) 102 | 103 | it('should not include the tags property for a REQUEST_BODY part (actually anything other than API or METHOD)', () => { 104 | let part = { 105 | type: 'QUERY_PARAMETER', 106 | isList: true, 107 | locationProps: ['path', 'method', 'name'] 108 | } 109 | let def = { 110 | description: 'the desc', 111 | summary: 'the summary', 112 | tags: ['tag1'] // should be ignored 113 | } 114 | let knownLocation = { 115 | path: '/some/path', 116 | method: 'GET', 117 | name: 'someParam' 118 | } 119 | objectUnderTest._createDocumentationPart(part, def, knownLocation) 120 | let result = objectUnderTest.documentationParts 121 | expect(result.length).toBe(1) 122 | expect(result).toEqual([ 123 | { 124 | location: { 125 | type: 'QUERY_PARAMETER', 126 | path: '/some/path', 127 | method: 'GET', 128 | name: 'someParam' 129 | }, 130 | properties: { 131 | description: 'the desc', 132 | summary: 'the summary' 133 | }, 134 | restApiId: 'testApiId' 135 | } 136 | ]) 137 | }) 138 | }) 139 | 140 | describe('getDocumentationProperties', () => { 141 | it('should include the tags property when we indicate to include it', () => { 142 | let def = { 143 | description: 'the desc', 144 | summary: 'the summary', 145 | tags: ['tag1'] 146 | } 147 | let propertiesToGet = ['description', 'summary', 'tags'] 148 | let result = objectUnderTest._getDocumentationProperties(def, propertiesToGet) 149 | expect(result.size).toBe(3) 150 | expect(result.get('description')).toBe('the desc') 151 | expect(result.get('summary')).toBe('the summary') 152 | expect(result.get('tags')).toEqual(['tag1']) 153 | }) 154 | 155 | it('should ignore a defined tag when we indicate to not include it', () => { 156 | let def = { 157 | description: 'the desc', 158 | summary: 'the summary', 159 | tags: ['tag1'] 160 | } 161 | let propertiesToGet = ['description', 'summary'] // no 'tags' 162 | let result = objectUnderTest._getDocumentationProperties(def, propertiesToGet) 163 | expect(result.size).toBe(2) 164 | expect(result.get('description')).toBe('the desc') 165 | expect(result.get('summary')).toBe('the summary') 166 | expect(result.get('tags')).toBeUndefined() 167 | }) 168 | }) 169 | }) 170 | -------------------------------------------------------------------------------- /src/downloadDocumentation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | downloadDocumentation: function () { 5 | const aws = this.serverless.providers.aws; 6 | const stackName = aws.naming.getStackName(aws.getStage()); 7 | return this._getRestApiId(stackName).then((restApiId) => { 8 | return aws.request('APIGateway', 'getExport', { 9 | stageName: aws.getStage(), 10 | restApiId: restApiId, 11 | exportType: 'swagger', 12 | parameters: { 13 | extensions: extensionType(this.options.extensions), 14 | }, 15 | accepts: createAWSContentType(this.options.outputFileName), 16 | }); 17 | }).then((response) => { 18 | this.fs.writeFileSync(this.options.outputFileName, response.body); 19 | }); 20 | }, 21 | 22 | _getRestApiId: function (stackName) { 23 | const aws = this.serverless.providers.aws; 24 | return aws.request('CloudFormation', 'describeStacks', {StackName: stackName}, 25 | aws.getStage(), 26 | aws.getRegion() 27 | ).then((result) => { 28 | return result.Stacks[0].Outputs 29 | .filter(output => output.OutputKey === 'AwsDocApiId') 30 | .map(output => output.OutputValue)[0]; 31 | }); 32 | }, 33 | }; 34 | 35 | function getFileExtension(filename) { 36 | const path = require('path'); 37 | let ext = path.extname(filename || '').split('.'); 38 | 39 | return ext[ext.length - 1]; 40 | } 41 | 42 | function createAWSContentType(outputFileName) { 43 | const fileExtension = getFileExtension(outputFileName); 44 | let awsContentType = 'application/json'; 45 | if (fileExtension === 'yml' || fileExtension === 'yaml') { 46 | awsContentType = 'application/yaml'; 47 | } 48 | 49 | return awsContentType; 50 | } 51 | 52 | function extensionType(extensionArg) { 53 | const possibleExtensions = ['integrations', 'apigateway', 'authorizers', 'postman']; 54 | 55 | if (possibleExtensions.includes(extensionArg)) { 56 | return extensionArg; 57 | } else { 58 | return 'integrations'; 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /src/downloadDocumentation.spec.js: -------------------------------------------------------------------------------- 1 | describe('ServerlessAWSDocumentation', function () { 2 | let objectUnderTest; 3 | 4 | beforeEach(() => { 5 | objectUnderTest = require('./downloadDocumentation.js'); 6 | objectUnderTest.fs = { 7 | writeFileSync: jasmine.createSpy('fs') 8 | }; 9 | objectUnderTest.serverless = { 10 | providers: { 11 | aws: { 12 | naming: { 13 | getStackName: () => { 14 | return 'testStackName'; 15 | } 16 | }, 17 | request: jasmine.createSpy('aws request'), 18 | getStage: () => 'testStage', 19 | getRegion: () => 'testRegion' 20 | } 21 | }, 22 | service: { 23 | provider: { 24 | stage: 'testStage', 25 | region: 'testRegion', 26 | } 27 | } 28 | }; 29 | }); 30 | 31 | afterEach(() => { 32 | delete require.cache[require.resolve('./downloadDocumentation.js')]; 33 | }); 34 | 35 | describe('downloadDocumentation', () => { 36 | it('should successfully download documentation, unknown file extension', (done) => { 37 | objectUnderTest.options = { 38 | outputFileName: 'test.txt', 39 | }; 40 | objectUnderTest._getRestApiId = () => { 41 | return Promise.resolve('testRestApiId') 42 | }; 43 | 44 | objectUnderTest.serverless.providers.aws.request.and.returnValue(Promise.resolve({ 45 | body: 'some body', 46 | })); 47 | return objectUnderTest.downloadDocumentation().then(() => { 48 | expect(objectUnderTest.serverless.providers.aws.request).toHaveBeenCalledWith('APIGateway', 'getExport', { 49 | stageName: 'testStage', 50 | restApiId: 'testRestApiId', 51 | exportType: 'swagger', 52 | parameters: { 53 | extensions: 'integrations', 54 | }, 55 | accepts: 'application/json', 56 | }); 57 | expect(objectUnderTest.fs.writeFileSync).toHaveBeenCalledWith('test.txt', 'some body'); 58 | 59 | done(); 60 | }); 61 | }); 62 | 63 | it('should successfully download documentation, yaml extension', (done) => { 64 | objectUnderTest.options = { 65 | outputFileName: 'test.yml', 66 | }; 67 | objectUnderTest._getRestApiId = () => { 68 | return Promise.resolve('testRestApiId') 69 | }; 70 | 71 | objectUnderTest.serverless.providers.aws.request.and.returnValue(Promise.resolve({ 72 | body: 'some body', 73 | })); 74 | return objectUnderTest.downloadDocumentation().then(() => { 75 | expect(objectUnderTest.serverless.providers.aws.request).toHaveBeenCalledWith('APIGateway', 'getExport', { 76 | stageName: 'testStage', 77 | restApiId: 'testRestApiId', 78 | exportType: 'swagger', 79 | parameters: { 80 | extensions: 'integrations', 81 | }, 82 | accepts: 'application/yaml', 83 | }); 84 | expect(objectUnderTest.fs.writeFileSync).toHaveBeenCalledWith('test.yml', 'some body'); 85 | 86 | done(); 87 | }); 88 | }); 89 | 90 | it('should successfully download documentation, yaml extension, using an extensions argument', (done) => { 91 | objectUnderTest.options = { 92 | outputFileName: 'test.yml', 93 | extensions: 'apigateway', 94 | }; 95 | objectUnderTest._getRestApiId = () => { 96 | return Promise.resolve('testRestApiId') 97 | }; 98 | 99 | objectUnderTest.serverless.providers.aws.request.and.returnValue(Promise.resolve({ 100 | body: 'some body', 101 | })); 102 | return objectUnderTest.downloadDocumentation().then(() => { 103 | expect(objectUnderTest.serverless.providers.aws.request).toHaveBeenCalledWith('APIGateway', 'getExport', { 104 | stageName: 'testStage', 105 | restApiId: 'testRestApiId', 106 | exportType: 'swagger', 107 | parameters: { 108 | extensions: 'apigateway', 109 | }, 110 | accepts: 'application/yaml', 111 | }); 112 | expect(objectUnderTest.fs.writeFileSync).toHaveBeenCalledWith('test.yml', 'some body'); 113 | 114 | done(); 115 | }); 116 | }); 117 | 118 | it('should throw an error', (done) => { 119 | objectUnderTest.options = { 120 | outputFileName: 'test.json', 121 | }; 122 | objectUnderTest._getRestApiId = () => { 123 | return Promise.resolve('testRestApiId'); 124 | }; 125 | objectUnderTest.serverless.providers.aws.request.and.returnValue(Promise.reject('reason')); 126 | return objectUnderTest.downloadDocumentation().catch(() => { 127 | done(); 128 | }); 129 | }); 130 | 131 | it('should get rest api id', (done) => { 132 | objectUnderTest.serverless.providers.aws.request.and.returnValue(Promise.resolve({ 133 | Stacks: [{ 134 | Outputs: [{ 135 | OutputKey: 'some-key-1', 136 | OutputValue: 'some-value-1', 137 | }, { 138 | OutputKey: 'AwsDocApiId', 139 | OutputValue: 'testRestApiId', 140 | }, { 141 | OutputKey: 'some-key-2', 142 | OutputValue: 'some-value-2', 143 | }] 144 | }] 145 | })); 146 | 147 | return objectUnderTest._getRestApiId('testStackName').then((restApiId) => { 148 | expect(restApiId).toBe('testRestApiId'); 149 | expect(objectUnderTest.serverless.providers.aws.request).toHaveBeenCalledWith( 150 | 'CloudFormation', 151 | 'describeStacks', 152 | jasmine.objectContaining({StackName: 'testStackName'}), 'testStage', 'testRegion' 153 | ); 154 | 155 | done(); 156 | }); 157 | }); 158 | }); 159 | }); 160 | -------------------------------------------------------------------------------- /src/functionDocumentationParts.json: -------------------------------------------------------------------------------- 1 | { 2 | "documentation": { 3 | "type": "METHOD", 4 | "isList": false, 5 | "locationProps": ["path", "method"], 6 | "children": { 7 | "requestBody": { 8 | "type": "REQUEST_BODY", 9 | "isList": false, 10 | "locationProps": ["path", "method"] 11 | }, 12 | "requestHeaders": { 13 | "type": "REQUEST_HEADER", 14 | "isList": true, 15 | "locationProps": ["path", "method", "name"] 16 | }, 17 | "queryParams": { 18 | "type": "QUERY_PARAMETER", 19 | "isList": true, 20 | "locationProps": ["path", "method", "name"] 21 | }, 22 | "pathParams": { 23 | "type": "PATH_PARAMETER", 24 | "isList": true, 25 | "locationProps": ["path", "method", "name"] 26 | }, 27 | "methodResponses": { 28 | "type": "RESPONSE", 29 | "isList": true, 30 | "locationProps": ["path", "method", "statusCode"], 31 | "children": { 32 | "responseHeaders": { 33 | "type": "RESPONSE_HEADER", 34 | "isList": true, 35 | "locationProps": ["path", "method", "name", "statusCode"] 36 | }, 37 | "responseBody": { 38 | "type": "RESPONSE_BODY", 39 | "isList": false, 40 | "locationProps": ["path", "method", "statusCode"] 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/globalDocumentationParts.json: -------------------------------------------------------------------------------- 1 | { 2 | "api": { 3 | "type": "API", 4 | "isList": false, 5 | "locationProps": [] 6 | }, 7 | "authorizers": { 8 | "type": "AUTHORIZER", 9 | "isList": true, 10 | "locationProps": ["name"] 11 | }, 12 | "models": { 13 | "type": "MODEL", 14 | "isList": true, 15 | "locationProps": ["name"] 16 | }, 17 | "resources": { 18 | "type": "RESOURCE", 19 | "isList": true, 20 | "locationProps": ["path"] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const documentation = require('./documentation'); 3 | const models = require('./models'); 4 | const swagger = require('./swagger'); 5 | const fs = require('fs'); 6 | const downloadDocumentation = require('./downloadDocumentation'); 7 | 8 | class ServerlessAWSDocumentation { 9 | constructor(serverless, options) { 10 | this.serverless = serverless; 11 | this.options = options; 12 | this.provider = 'aws'; 13 | this.fs = fs; 14 | 15 | Object.assign(this, models); 16 | Object.assign(this, swagger); 17 | Object.assign(this, documentation()); 18 | Object.assign(this, downloadDocumentation); 19 | 20 | this.customVars = this.serverless.variables.service.custom; 21 | const naming = this.serverless.providers.aws.naming; 22 | this.getMethodLogicalId = naming.getMethodLogicalId.bind(naming); 23 | this.normalizePath = naming.normalizePath.bind(naming); 24 | 25 | this._beforeDeploy = this.beforeDeploy.bind(this) 26 | this._afterDeploy = this.afterDeploy.bind(this) 27 | this._download = downloadDocumentation.downloadDocumentation.bind(this) 28 | 29 | this.hooks = { 30 | 'before:package:finalize': this._beforeDeploy, 31 | 'after:deploy:deploy': this._afterDeploy, 32 | 'downloadDocumentation:downloadDocumentation': this._download 33 | }; 34 | 35 | this.documentationParts = []; 36 | 37 | this.commands = { 38 | downloadDocumentation: { 39 | usage: 'Download API Gateway documentation from AWS', 40 | lifecycleEvents: [ 41 | 'downloadDocumentation', 42 | ], 43 | options: { 44 | outputFileName: { 45 | required: true, 46 | }, 47 | extensions: { 48 | required: false, 49 | }, 50 | }, 51 | } 52 | }; 53 | } 54 | 55 | beforeDeploy() { 56 | this.customVars = this.serverless.variables.service.custom; 57 | if (!(this.customVars && this.customVars.documentation)) return; 58 | 59 | if (this.customVars.documentation.swagger) { 60 | // Handle references to models 61 | this.replaceSwaggerDefinitions(this.customVars.documentation.definitions) 62 | //Map swagger into documentation models 63 | const swaggerDefs = this.customVars.documentation.definitions 64 | if (swaggerDefs) { 65 | const swaggerModels = Object.keys(swaggerDefs).map(definitionName => { 66 | return { 67 | name: definitionName, 68 | description: swaggerDefs[definitionName].description, 69 | contentType: 'application/json', 70 | schema: swaggerDefs[definitionName] 71 | } 72 | }) 73 | this.customVars.documentation.models = swaggerModels 74 | } else { 75 | this.customVars.documentation.models = [] 76 | } 77 | 78 | //Find http events and map the swagger across 79 | this.serverless.service.getAllFunctions().forEach(functionName => { 80 | const func = this.serverless.service.getFunction(functionName) 81 | if (func.events) { 82 | func.events.forEach(event => { 83 | if (event.http) { 84 | // look up the path in the swagger 85 | const path = this.customVars.documentation.paths['/' + event.http.path] 86 | if (path) { 87 | const method = path[event.http.method] 88 | const methodDoc = {'requestHeaders': [], 'pathParams': [], 'queryParams': [], 89 | 'requestModels': {}} 90 | if ( method.parameters ) { 91 | method.parameters.forEach(param => { 92 | if (param.in === 'header') { 93 | methodDoc['requestHeaders'].push({ 94 | name: param.name, 95 | description: param.description, 96 | required: param.required 97 | }) 98 | } else if (param.in === 'path') { 99 | methodDoc['pathParams'].push({ 100 | name: param.name, 101 | description: param.description, 102 | required: param.required 103 | }) 104 | } else if (param.in === 'query') { 105 | methodDoc['queryParams'].push({ 106 | name: param.name, 107 | description: param.description, 108 | required: param.required 109 | }) 110 | } else if (param.in === 'body') { 111 | methodDoc['requestModels']['application/json'] = 112 | this.extractModel(param, this.customVars.documentation.models); 113 | } 114 | }) 115 | } 116 | 117 | if ( method.responses ) { 118 | methodDoc['methodResponses'] = [] 119 | Object.keys(method.responses).map(statusCode => { 120 | const response = method.responses[statusCode]; 121 | const methodResponse = { 122 | statusCode: ""+statusCode, 123 | }; 124 | 125 | if ( response.schema ) { 126 | const responseModels = {}; 127 | responseModels['application/json'] = 128 | this.extractModel(response, this.customVars.documentation.models); 129 | methodResponse['responseModels'] = responseModels; 130 | } 131 | methodDoc['methodResponses'].push(methodResponse); 132 | }); 133 | } 134 | 135 | event.http.documentation = methodDoc 136 | } 137 | } 138 | }) 139 | } 140 | }) 141 | } 142 | 143 | this.cfTemplate = this.serverless.service.provider.compiledCloudFormationTemplate; 144 | 145 | // The default rest API reference 146 | let restApiId = { 147 | Ref: 'ApiGatewayRestApi', 148 | }; 149 | 150 | // Use the provider API gateway if one has been provided. 151 | if (this.serverless.service.provider.apiGateway && this.serverless.service.provider.apiGateway.restApiId) { 152 | restApiId = this.serverless.service.provider.apiGateway.restApiId 153 | } 154 | 155 | if (this.customVars.documentation.models) { 156 | const cfModelCreator = this.createCfModel(restApiId); 157 | 158 | // Add model resources 159 | const models = this.customVars.documentation.models.map(cfModelCreator) 160 | .reduce((modelObj, model) => { 161 | modelObj[`${model.Properties.Name}Model`] = model; 162 | return modelObj; 163 | }, {}); 164 | Object.assign(this.cfTemplate.Resources, models); 165 | } 166 | 167 | // Add models to method resources 168 | this.serverless.service.getAllFunctions().forEach(functionName => { 169 | const func = this.serverless.service.getFunction(functionName); 170 | func.events.forEach(this.updateCfTemplateFromHttp.bind(this)); 171 | }); 172 | 173 | // Add models 174 | this.cfTemplate.Outputs.AwsDocApiId = { 175 | Description: 'API ID', 176 | Value: restApiId, 177 | }; 178 | } 179 | 180 | afterDeploy() { 181 | if (!this.customVars.documentation) return; 182 | const stackName = this.serverless.providers.aws.naming.getStackName(this.options.stage); 183 | return this.serverless.providers.aws.request('CloudFormation', 'describeStacks', { StackName: stackName }, 184 | this.options.stage, 185 | this.options.region 186 | ).then(this._buildDocumentation.bind(this)) 187 | .catch(err => { 188 | if (err === 'documentation version already exists, skipping upload') { 189 | return Promise.resolve(); 190 | } 191 | 192 | return Promise.reject(err); 193 | }); 194 | } 195 | 196 | } 197 | 198 | module.exports = ServerlessAWSDocumentation; 199 | -------------------------------------------------------------------------------- /src/models.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function replaceModelRefs(restApiId, cfModel) { 4 | if (!cfModel.Properties || !cfModel.Properties.Schema || Object.keys(cfModel.Properties.Schema).length == 0) { 5 | return cfModel; 6 | } 7 | 8 | function replaceRefs(obj) { 9 | for (let key of Object.keys(obj)) { 10 | if (key === '$ref') { 11 | let match; 12 | if (match = /{{model:\s*([\-\w]+)}}/.exec(obj[key])) { 13 | obj[key] = { 14 | 'Fn::Join': [ 15 | '/', 16 | [ 17 | 'https://apigateway.amazonaws.com/restapis', 18 | restApiId, 19 | 'models', 20 | match[1] 21 | ] 22 | ] 23 | }; 24 | if (!cfModel.DependsOn) { 25 | cfModel.DependsOn = new Set(); 26 | } 27 | cfModel.DependsOn.add(match[1]+'Model'); 28 | } 29 | } else if (typeof obj[key] === 'object' && obj[key] !== null) { 30 | replaceRefs(obj[key]); 31 | } 32 | } 33 | } 34 | 35 | replaceRefs(cfModel.Properties.Schema); 36 | if (cfModel.DependsOn) { 37 | cfModel.DependsOn = Array.from(cfModel.DependsOn); 38 | } 39 | return cfModel; 40 | } 41 | 42 | module.exports = { 43 | createCfModel: function createCfModel(restApiId) { 44 | return function(model) { 45 | 46 | let cfModel = { 47 | Type: 'AWS::ApiGateway::Model', 48 | Properties: { 49 | RestApiId: restApiId, 50 | ContentType: model.contentType, 51 | Name: model.name, 52 | Schema: model.schema || {}, 53 | }, 54 | } 55 | 56 | if (model.description) { 57 | cfModel.Properties.Description = model.description 58 | } 59 | 60 | return replaceModelRefs(restApiId, cfModel) 61 | } 62 | }, 63 | 64 | addModelDependencies: function addModelDependencies(models, resource) { 65 | Object.keys(models).forEach(contentType => { 66 | resource.DependsOn.add(`${models[contentType]}Model`); 67 | }); 68 | }, 69 | 70 | addMethodResponses: function addMethodResponses(resource, documentation) { 71 | if (documentation.methodResponses) { 72 | if (!resource.Properties.MethodResponses) { 73 | resource.Properties.MethodResponses = []; 74 | } 75 | 76 | documentation.methodResponses.forEach(response => { 77 | const statusCode = response.statusCode.toString(); 78 | let _response = resource.Properties.MethodResponses 79 | .find(originalResponse => originalResponse.StatusCode.toString() === statusCode); 80 | 81 | if (!_response) { 82 | _response = { 83 | StatusCode: statusCode, 84 | }; 85 | 86 | if (response.responseHeaders) { 87 | const methodResponseHeaders = {}; 88 | response.responseHeaders.forEach(header => { 89 | methodResponseHeaders[`method.response.header.${header.name}`] = true 90 | }); 91 | _response.ResponseParameters = methodResponseHeaders; 92 | } 93 | 94 | resource.Properties.MethodResponses.push(_response); 95 | } 96 | 97 | if (response.responseModels) { 98 | _response.ResponseModels = response.responseModels; 99 | this.addModelDependencies(_response.ResponseModels, resource); 100 | } 101 | }); 102 | } 103 | }, 104 | 105 | addRequestModels: function addRequestModels(resource, documentation) { 106 | if (documentation.requestModels && Object.keys(documentation.requestModels).length > 0) { 107 | this.addModelDependencies(documentation.requestModels, resource); 108 | resource.Properties.RequestModels = documentation.requestModels; 109 | } 110 | } 111 | 112 | }; 113 | -------------------------------------------------------------------------------- /src/models.spec.js: -------------------------------------------------------------------------------- 1 | describe('ServerlessAWSDocumentation', function() { 2 | const objectUnderTest = require('./models.js') 3 | 4 | describe('createCfModel', () => { 5 | it('should replace model ref with valid URI', () => { 6 | let modelInput = { 7 | contentType: 'application/json', 8 | name: 'TestModel', 9 | schema: { 10 | type: 'object', 11 | properties: { 12 | prop: { 13 | '$ref': '{{model: OtherModelName}}' 14 | } 15 | } 16 | } 17 | }; 18 | 19 | let modelOutput = objectUnderTest.createCfModel({ 20 | Ref: 'ApiGatewayRestApi', 21 | })(modelInput); 22 | expect(modelOutput).toEqual({ 23 | Type: 'AWS::ApiGateway::Model', 24 | Properties: { 25 | RestApiId: { 26 | Ref: 'ApiGatewayRestApi', 27 | }, 28 | ContentType: 'application/json', 29 | Name: 'TestModel', 30 | Schema: { 31 | type: 'object', 32 | properties: { 33 | prop: { 34 | '$ref': { 35 | 'Fn::Join': [ 36 | '/', 37 | [ 38 | 'https://apigateway.amazonaws.com/restapis', 39 | { 40 | 'Ref': 'ApiGatewayRestApi' 41 | }, 42 | 'models', 43 | 'OtherModelName' 44 | ] 45 | ] 46 | } 47 | } 48 | } 49 | } 50 | }, 51 | DependsOn: [ 52 | 'OtherModelNameModel' 53 | ] 54 | }); 55 | }); 56 | 57 | it('should use provided rest api setting', () => { 58 | let modelInput = { 59 | contentType: 'application/json', 60 | name: 'TestModel', 61 | description: 'Test description', 62 | schema: { 63 | type: 'object', 64 | properties: { 65 | prop: { 66 | '$ref': '{{model: OtherModelName}}' 67 | } 68 | } 69 | } 70 | }; 71 | 72 | let modelOutput = objectUnderTest.createCfModel({ 73 | 'Fn::ImportValue': 'PublicApiGatewayRestApi', 74 | })(modelInput); 75 | expect(modelOutput).toEqual({ 76 | Type: 'AWS::ApiGateway::Model', 77 | Properties: { 78 | RestApiId: { 79 | 'Fn::ImportValue': 'PublicApiGatewayRestApi', 80 | }, 81 | ContentType: 'application/json', 82 | Name: 'TestModel', 83 | Schema: { 84 | type: 'object', 85 | properties: { 86 | prop: { 87 | '$ref': { 88 | 'Fn::Join': [ 89 | '/', 90 | [ 91 | 'https://apigateway.amazonaws.com/restapis', 92 | { 93 | 'Fn::ImportValue': 'PublicApiGatewayRestApi' 94 | }, 95 | 'models', 96 | 'OtherModelName' 97 | ] 98 | ] 99 | } 100 | } 101 | } 102 | }, 103 | Description: 'Test description', 104 | }, 105 | DependsOn: [ 106 | 'OtherModelNameModel' 107 | ] 108 | }); 109 | }); 110 | 111 | it('should not mess with non-ref model definitions', () => { 112 | let modelInput = { 113 | contentType: 'application/json', 114 | name: 'TestModel', 115 | schema: { 116 | type: 'object', 117 | properties: { 118 | prop: { 119 | type: 'string' 120 | } 121 | } 122 | } 123 | }; 124 | 125 | let modelOutput = objectUnderTest.createCfModel({ 126 | Ref: 'ApiGatewayRestApi', 127 | })(modelInput); 128 | expect(modelOutput).toEqual({ 129 | Type: 'AWS::ApiGateway::Model', 130 | Properties: { 131 | RestApiId: { 132 | Ref: 'ApiGatewayRestApi', 133 | }, 134 | ContentType: 'application/json', 135 | Name: 'TestModel', 136 | Schema: { 137 | type: 'object', 138 | properties: { 139 | prop: { 140 | type: 'string' 141 | } 142 | } 143 | } 144 | } 145 | }); 146 | }); 147 | 148 | it('should not crash with null values', () => { 149 | let modelInput = { 150 | contentType: 'application/json', 151 | name: 'TestModel', 152 | schema: { 153 | type: 'object', 154 | properties: { 155 | prop: { 156 | enum: ['test', null], 157 | default: null 158 | } 159 | } 160 | } 161 | }; 162 | 163 | let modelExecution = function() { 164 | objectUnderTest.createCfModel({ 165 | Ref: 'ApiGatewayRestApi', 166 | })(modelInput); 167 | } 168 | expect(modelExecution).not.toThrow(); 169 | }); 170 | }); 171 | }) 172 | -------------------------------------------------------------------------------- /src/swagger.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function replaceSwaggerRefs (swagger) { 4 | function replaceRefs (obj) { 5 | if (!obj) { 6 | return 7 | } 8 | for (let key of Object.keys(obj)) { 9 | if (key === '$ref') { 10 | let match 11 | if (match = /#\/definitions\/([\-\w]+)/.exec(obj[key])) { 12 | obj[key] = '{{model: ' + match[1] + '}}' 13 | } 14 | } else if (typeof obj[key] === 'object') { 15 | replaceRefs(obj[key]) 16 | } 17 | } 18 | } 19 | 20 | replaceRefs(swagger) 21 | } 22 | 23 | function extractModelDefinition(param, models) { 24 | // if the schema is just a $ref, set it to that value 25 | // otherwise create a model to handle this response 26 | if (param.schema['$ref']) { 27 | let match 28 | if (match = /#\/definitions\/([\-\w]+)/.exec(param.schema['$ref'])) { 29 | return match[1]; 30 | } 31 | } else { 32 | replaceSwaggerRefs(param.schema) 33 | models.push({ 34 | name: param.name, 35 | description: param.description, 36 | contentType: 'application/json', 37 | schema: param.schema 38 | }) 39 | return param.name; 40 | } 41 | } 42 | 43 | module.exports = { 44 | replaceSwaggerDefinitions: function replaceSwaggerDefinitions (swagger) { 45 | return replaceSwaggerRefs(swagger) 46 | }, 47 | extractModel: function extractModel(param, models) { 48 | return extractModelDefinition(param, models) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | abbrev@1, abbrev@1.0.x: 6 | version "1.0.9" 7 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.0.9.tgz#91b4792588a7738c25f35dd6f63752a2f8776135" 8 | 9 | acorn-jsx@^3.0.0: 10 | version "3.0.1" 11 | resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" 12 | dependencies: 13 | acorn "^3.0.4" 14 | 15 | acorn@^3.0.4: 16 | version "3.3.0" 17 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" 18 | 19 | acorn@^4.0.1: 20 | version "4.0.3" 21 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.3.tgz#1a3e850b428e73ba6b09d1cc527f5aaad4d03ef1" 22 | 23 | ajv-keywords@^1.0.0: 24 | version "1.2.0" 25 | resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.2.0.tgz#676c4f087bfe1e8b12dca6fda2f3c74f417b099c" 26 | 27 | ajv@^4.7.0: 28 | version "4.9.1" 29 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.9.1.tgz#08e1b0a5fddc8b844d28ca7b03510e78812ee3a0" 30 | dependencies: 31 | co "^4.6.0" 32 | json-stable-stringify "^1.0.1" 33 | 34 | amdefine@>=0.0.4: 35 | version "1.0.1" 36 | resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" 37 | 38 | ansi-escapes@^1.1.0: 39 | version "1.4.0" 40 | resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" 41 | 42 | ansi-regex@^2.0.0: 43 | version "2.0.0" 44 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.0.0.tgz#c5061b6e0ef8a81775e50f5d66151bf6bf371107" 45 | 46 | ansi-styles@^2.2.1: 47 | version "2.2.1" 48 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 49 | 50 | argparse@^1.0.7: 51 | version "1.0.10" 52 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 53 | dependencies: 54 | sprintf-js "~1.0.2" 55 | 56 | argv@>=0.0.2: 57 | version "0.0.2" 58 | resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab" 59 | 60 | array-union@^1.0.1: 61 | version "1.0.2" 62 | resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" 63 | dependencies: 64 | array-uniq "^1.0.1" 65 | 66 | array-uniq@^1.0.1: 67 | version "1.0.3" 68 | resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" 69 | 70 | arrify@^1.0.0: 71 | version "1.0.1" 72 | resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" 73 | 74 | asn1@~0.2.3: 75 | version "0.2.4" 76 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" 77 | dependencies: 78 | safer-buffer "~2.1.0" 79 | 80 | assert-plus@^0.2.0: 81 | version "0.2.0" 82 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" 83 | 84 | assert-plus@^1.0.0: 85 | version "1.0.0" 86 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" 87 | 88 | async@1.x: 89 | version "1.5.2" 90 | resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" 91 | 92 | asynckit@^0.4.0: 93 | version "0.4.0" 94 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 95 | 96 | aws-sign2@~0.6.0: 97 | version "0.6.0" 98 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" 99 | 100 | aws4@^1.2.1: 101 | version "1.5.0" 102 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.5.0.tgz#0a29ffb79c31c9e712eeb087e8e7a64b4a56d755" 103 | 104 | babel-code-frame@^6.16.0: 105 | version "6.16.0" 106 | resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.16.0.tgz#f90e60da0862909d3ce098733b5d3987c97cb8de" 107 | dependencies: 108 | chalk "^1.1.0" 109 | esutils "^2.0.2" 110 | js-tokens "^2.0.0" 111 | 112 | balanced-match@^0.4.1: 113 | version "0.4.2" 114 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" 115 | 116 | bcrypt-pbkdf@^1.0.0: 117 | version "1.0.2" 118 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" 119 | dependencies: 120 | tweetnacl "^0.14.3" 121 | 122 | boom@2.x.x: 123 | version "2.10.1" 124 | resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" 125 | dependencies: 126 | hoek "2.x.x" 127 | 128 | brace-expansion@^1.0.0: 129 | version "1.1.6" 130 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.6.tgz#7197d7eaa9b87e648390ea61fc66c84427420df9" 131 | dependencies: 132 | balanced-match "^0.4.1" 133 | concat-map "0.0.1" 134 | 135 | caller-path@^0.1.0: 136 | version "0.1.0" 137 | resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" 138 | dependencies: 139 | callsites "^0.2.0" 140 | 141 | callsites@^0.2.0: 142 | version "0.2.0" 143 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" 144 | 145 | caseless@~0.11.0: 146 | version "0.11.0" 147 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" 148 | 149 | chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: 150 | version "1.1.3" 151 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 152 | dependencies: 153 | ansi-styles "^2.2.1" 154 | escape-string-regexp "^1.0.2" 155 | has-ansi "^2.0.0" 156 | strip-ansi "^3.0.0" 157 | supports-color "^2.0.0" 158 | 159 | circular-json@^0.3.0: 160 | version "0.3.1" 161 | resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" 162 | 163 | cli-cursor@^1.0.1: 164 | version "1.0.2" 165 | resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" 166 | dependencies: 167 | restore-cursor "^1.0.1" 168 | 169 | cli-width@^2.0.0: 170 | version "2.1.0" 171 | resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" 172 | 173 | co@^4.6.0: 174 | version "4.6.0" 175 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" 176 | 177 | code-point-at@^1.0.0: 178 | version "1.1.0" 179 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" 180 | 181 | codecov@^1.0.1: 182 | version "1.0.1" 183 | resolved "https://registry.yarnpkg.com/codecov/-/codecov-1.0.1.tgz#97260ceac0e96b8eda8d562006558a53a139dffd" 184 | dependencies: 185 | argv ">=0.0.2" 186 | execSync "1.0.2" 187 | request ">=2.42.0" 188 | urlgrey ">=0.4.0" 189 | 190 | combined-stream@^1.0.5, combined-stream@~1.0.5: 191 | version "1.0.5" 192 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" 193 | dependencies: 194 | delayed-stream "~1.0.0" 195 | 196 | commander@^2.9.0: 197 | version "2.9.0" 198 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" 199 | dependencies: 200 | graceful-readlink ">= 1.0.0" 201 | 202 | commander@~2.20.3: 203 | version "2.20.3" 204 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" 205 | 206 | concat-map@0.0.1: 207 | version "0.0.1" 208 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 209 | 210 | concat-stream@^1.4.6: 211 | version "1.5.2" 212 | resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266" 213 | dependencies: 214 | inherits "~2.0.1" 215 | readable-stream "~2.0.0" 216 | typedarray "~0.0.5" 217 | 218 | core-util-is@~1.0.0: 219 | version "1.0.2" 220 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 221 | 222 | cryptiles@2.x.x: 223 | version "2.0.5" 224 | resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" 225 | dependencies: 226 | boom "2.x.x" 227 | 228 | d@^0.1.1, d@~0.1.1: 229 | version "0.1.1" 230 | resolved "https://registry.yarnpkg.com/d/-/d-0.1.1.tgz#da184c535d18d8ee7ba2aa229b914009fae11309" 231 | dependencies: 232 | es5-ext "~0.10.2" 233 | 234 | dashdash@^1.12.0: 235 | version "1.14.1" 236 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" 237 | dependencies: 238 | assert-plus "^1.0.0" 239 | 240 | debug@^2.1.1: 241 | version "2.6.9" 242 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 243 | dependencies: 244 | ms "2.0.0" 245 | 246 | deep-is@~0.1.3: 247 | version "0.1.3" 248 | resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" 249 | 250 | del@^2.0.2: 251 | version "2.2.2" 252 | resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" 253 | dependencies: 254 | globby "^5.0.0" 255 | is-path-cwd "^1.0.0" 256 | is-path-in-cwd "^1.0.0" 257 | object-assign "^4.0.1" 258 | pify "^2.0.0" 259 | pinkie-promise "^2.0.0" 260 | rimraf "^2.2.8" 261 | 262 | delayed-stream@~1.0.0: 263 | version "1.0.0" 264 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 265 | 266 | diff@^3.2.0: 267 | version "3.3.0" 268 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.0.tgz#056695150d7aa93237ca7e378ac3b1682b7963b9" 269 | 270 | doctrine@^1.2.2: 271 | version "1.5.0" 272 | resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" 273 | dependencies: 274 | esutils "^2.0.2" 275 | isarray "^1.0.0" 276 | 277 | ecc-jsbn@~0.1.1: 278 | version "0.1.2" 279 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" 280 | dependencies: 281 | jsbn "~0.1.0" 282 | safer-buffer "^2.1.0" 283 | 284 | es5-ext@^0.10.7, es5-ext@^0.10.8, es5-ext@~0.10.11, es5-ext@~0.10.2, es5-ext@~0.10.7: 285 | version "0.10.12" 286 | resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.12.tgz#aa84641d4db76b62abba5e45fd805ecbab140047" 287 | dependencies: 288 | es6-iterator "2" 289 | es6-symbol "~3.1" 290 | 291 | es6-iterator@2: 292 | version "2.0.0" 293 | resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.0.tgz#bd968567d61635e33c0b80727613c9cb4b096bac" 294 | dependencies: 295 | d "^0.1.1" 296 | es5-ext "^0.10.7" 297 | es6-symbol "3" 298 | 299 | es6-map@^0.1.3: 300 | version "0.1.4" 301 | resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.4.tgz#a34b147be224773a4d7da8072794cefa3632b897" 302 | dependencies: 303 | d "~0.1.1" 304 | es5-ext "~0.10.11" 305 | es6-iterator "2" 306 | es6-set "~0.1.3" 307 | es6-symbol "~3.1.0" 308 | event-emitter "~0.3.4" 309 | 310 | es6-set@~0.1.3: 311 | version "0.1.4" 312 | resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.4.tgz#9516b6761c2964b92ff479456233a247dc707ce8" 313 | dependencies: 314 | d "~0.1.1" 315 | es5-ext "~0.10.11" 316 | es6-iterator "2" 317 | es6-symbol "3" 318 | event-emitter "~0.3.4" 319 | 320 | es6-symbol@3, es6-symbol@~3.1, es6-symbol@~3.1.0: 321 | version "3.1.0" 322 | resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.0.tgz#94481c655e7a7cad82eba832d97d5433496d7ffa" 323 | dependencies: 324 | d "~0.1.1" 325 | es5-ext "~0.10.11" 326 | 327 | es6-weak-map@^2.0.1: 328 | version "2.0.1" 329 | resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.1.tgz#0d2bbd8827eb5fb4ba8f97fbfea50d43db21ea81" 330 | dependencies: 331 | d "^0.1.1" 332 | es5-ext "^0.10.8" 333 | es6-iterator "2" 334 | es6-symbol "3" 335 | 336 | escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: 337 | version "1.0.5" 338 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 339 | 340 | escodegen@1.8.x: 341 | version "1.8.1" 342 | resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" 343 | dependencies: 344 | esprima "^2.7.1" 345 | estraverse "^1.9.1" 346 | esutils "^2.0.2" 347 | optionator "^0.8.1" 348 | optionalDependencies: 349 | source-map "~0.2.0" 350 | 351 | escope@^3.6.0: 352 | version "3.6.0" 353 | resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" 354 | dependencies: 355 | es6-map "^0.1.3" 356 | es6-weak-map "^2.0.1" 357 | esrecurse "^4.1.0" 358 | estraverse "^4.1.1" 359 | 360 | eslint@^3.11.1: 361 | version "3.11.1" 362 | resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.11.1.tgz#408be581041385cba947cd8d1cd2227782b55dbf" 363 | dependencies: 364 | babel-code-frame "^6.16.0" 365 | chalk "^1.1.3" 366 | concat-stream "^1.4.6" 367 | debug "^2.1.1" 368 | doctrine "^1.2.2" 369 | escope "^3.6.0" 370 | espree "^3.3.1" 371 | estraverse "^4.2.0" 372 | esutils "^2.0.2" 373 | file-entry-cache "^2.0.0" 374 | glob "^7.0.3" 375 | globals "^9.2.0" 376 | ignore "^3.2.0" 377 | imurmurhash "^0.1.4" 378 | inquirer "^0.12.0" 379 | is-my-json-valid "^2.10.0" 380 | is-resolvable "^1.0.0" 381 | js-yaml "^3.5.1" 382 | json-stable-stringify "^1.0.0" 383 | levn "^0.3.0" 384 | lodash "^4.0.0" 385 | mkdirp "^0.5.0" 386 | natural-compare "^1.4.0" 387 | optionator "^0.8.2" 388 | path-is-inside "^1.0.1" 389 | pluralize "^1.2.1" 390 | progress "^1.1.8" 391 | require-uncached "^1.0.2" 392 | shelljs "^0.7.5" 393 | strip-bom "^3.0.0" 394 | strip-json-comments "~1.0.1" 395 | table "^3.7.8" 396 | text-table "~0.2.0" 397 | user-home "^2.0.0" 398 | 399 | espree@^3.3.1: 400 | version "3.3.2" 401 | resolved "https://registry.yarnpkg.com/espree/-/espree-3.3.2.tgz#dbf3fadeb4ecb4d4778303e50103b3d36c88b89c" 402 | dependencies: 403 | acorn "^4.0.1" 404 | acorn-jsx "^3.0.0" 405 | 406 | esprima@2.7.x, esprima@^2.7.1: 407 | version "2.7.3" 408 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" 409 | 410 | esprima@^4.0.0: 411 | version "4.0.1" 412 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 413 | 414 | esrecurse@^4.1.0: 415 | version "4.1.0" 416 | resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220" 417 | dependencies: 418 | estraverse "~4.1.0" 419 | object-assign "^4.0.1" 420 | 421 | estraverse@^1.9.1: 422 | version "1.9.3" 423 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" 424 | 425 | estraverse@^4.1.1, estraverse@^4.2.0: 426 | version "4.2.0" 427 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" 428 | 429 | estraverse@~4.1.0: 430 | version "4.1.1" 431 | resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2" 432 | 433 | esutils@^2.0.2: 434 | version "2.0.2" 435 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 436 | 437 | event-emitter@~0.3.4: 438 | version "0.3.4" 439 | resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.4.tgz#8d63ddfb4cfe1fae3b32ca265c4c720222080bb5" 440 | dependencies: 441 | d "~0.1.1" 442 | es5-ext "~0.10.7" 443 | 444 | execSync@1.0.2: 445 | version "1.0.2" 446 | resolved "https://registry.yarnpkg.com/execSync/-/execSync-1.0.2.tgz#1f42eda582225180053224ecdd3fd1960fdb3139" 447 | dependencies: 448 | temp "~0.5.1" 449 | 450 | exit-hook@^1.0.0: 451 | version "1.1.1" 452 | resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" 453 | 454 | exit@^0.1.2: 455 | version "0.1.2" 456 | resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" 457 | 458 | extend@~3.0.0: 459 | version "3.0.2" 460 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 461 | 462 | extsprintf@1.0.2: 463 | version "1.0.2" 464 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" 465 | 466 | fast-levenshtein@~2.0.4: 467 | version "2.0.5" 468 | resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.5.tgz#bd33145744519ab1c36c3ee9f31f08e9079b67f2" 469 | 470 | figures@^1.3.5: 471 | version "1.7.0" 472 | resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" 473 | dependencies: 474 | escape-string-regexp "^1.0.5" 475 | object-assign "^4.1.0" 476 | 477 | file-entry-cache@^2.0.0: 478 | version "2.0.0" 479 | resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" 480 | dependencies: 481 | flat-cache "^1.2.1" 482 | object-assign "^4.0.1" 483 | 484 | flat-cache@^1.2.1: 485 | version "1.2.1" 486 | resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.1.tgz#6c837d6225a7de5659323740b36d5361f71691ff" 487 | dependencies: 488 | circular-json "^0.3.0" 489 | del "^2.0.2" 490 | graceful-fs "^4.1.2" 491 | write "^0.2.1" 492 | 493 | forever-agent@~0.6.1: 494 | version "0.6.1" 495 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" 496 | 497 | form-data@~2.1.1: 498 | version "2.1.2" 499 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.2.tgz#89c3534008b97eada4cbb157d58f6f5df025eae4" 500 | dependencies: 501 | asynckit "^0.4.0" 502 | combined-stream "^1.0.5" 503 | mime-types "^2.1.12" 504 | 505 | fs.realpath@^1.0.0: 506 | version "1.0.0" 507 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 508 | 509 | generate-function@^2.0.0: 510 | version "2.0.0" 511 | resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" 512 | 513 | generate-object-property@^1.1.0: 514 | version "1.2.0" 515 | resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" 516 | dependencies: 517 | is-property "^1.0.0" 518 | 519 | getpass@^0.1.1: 520 | version "0.1.7" 521 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" 522 | dependencies: 523 | assert-plus "^1.0.0" 524 | 525 | glob@^5.0.15: 526 | version "5.0.15" 527 | resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" 528 | dependencies: 529 | inflight "^1.0.4" 530 | inherits "2" 531 | minimatch "2 || 3" 532 | once "^1.3.0" 533 | path-is-absolute "^1.0.0" 534 | 535 | glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6: 536 | version "7.1.1" 537 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" 538 | dependencies: 539 | fs.realpath "^1.0.0" 540 | inflight "^1.0.4" 541 | inherits "2" 542 | minimatch "^3.0.2" 543 | once "^1.3.0" 544 | path-is-absolute "^1.0.0" 545 | 546 | globals@^9.2.0: 547 | version "9.14.0" 548 | resolved "https://registry.yarnpkg.com/globals/-/globals-9.14.0.tgz#8859936af0038741263053b39d0e76ca241e4034" 549 | 550 | globby@^5.0.0: 551 | version "5.0.0" 552 | resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" 553 | dependencies: 554 | array-union "^1.0.1" 555 | arrify "^1.0.0" 556 | glob "^7.0.3" 557 | object-assign "^4.0.1" 558 | pify "^2.0.0" 559 | pinkie-promise "^2.0.0" 560 | 561 | graceful-fs@^4.1.2: 562 | version "4.1.11" 563 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" 564 | 565 | graceful-fs@~1: 566 | version "1.2.3" 567 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" 568 | 569 | "graceful-readlink@>= 1.0.0": 570 | version "1.0.1" 571 | resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" 572 | 573 | handlebars@^4.0.1: 574 | version "4.5.1" 575 | resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.1.tgz#8a01c382c180272260d07f2d1aa3ae745715c7ba" 576 | dependencies: 577 | neo-async "^2.6.0" 578 | optimist "^0.6.1" 579 | source-map "^0.6.1" 580 | optionalDependencies: 581 | uglify-js "^3.1.4" 582 | 583 | har-validator@~2.0.6: 584 | version "2.0.6" 585 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" 586 | dependencies: 587 | chalk "^1.1.1" 588 | commander "^2.9.0" 589 | is-my-json-valid "^2.12.4" 590 | pinkie-promise "^2.0.0" 591 | 592 | has-ansi@^2.0.0: 593 | version "2.0.0" 594 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 595 | dependencies: 596 | ansi-regex "^2.0.0" 597 | 598 | has-flag@^1.0.0: 599 | version "1.0.0" 600 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" 601 | 602 | hawk@~3.1.3: 603 | version "3.1.3" 604 | resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" 605 | dependencies: 606 | boom "2.x.x" 607 | cryptiles "2.x.x" 608 | hoek "2.x.x" 609 | sntp "1.x.x" 610 | 611 | hoek@2.x.x: 612 | version "2.16.3" 613 | resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" 614 | 615 | http-signature@~1.1.0: 616 | version "1.1.1" 617 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" 618 | dependencies: 619 | assert-plus "^0.2.0" 620 | jsprim "^1.2.2" 621 | sshpk "^1.7.0" 622 | 623 | ignore@^3.2.0: 624 | version "3.2.0" 625 | resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.0.tgz#8d88f03c3002a0ac52114db25d2c673b0bf1e435" 626 | 627 | imurmurhash@^0.1.4: 628 | version "0.1.4" 629 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" 630 | 631 | inflight@^1.0.4: 632 | version "1.0.6" 633 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 634 | dependencies: 635 | once "^1.3.0" 636 | wrappy "1" 637 | 638 | inherits@2, inherits@~2.0.1: 639 | version "2.0.3" 640 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 641 | 642 | inquirer@^0.12.0: 643 | version "0.12.0" 644 | resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" 645 | dependencies: 646 | ansi-escapes "^1.1.0" 647 | ansi-regex "^2.0.0" 648 | chalk "^1.0.0" 649 | cli-cursor "^1.0.1" 650 | cli-width "^2.0.0" 651 | figures "^1.3.5" 652 | lodash "^4.3.0" 653 | readline2 "^1.0.1" 654 | run-async "^0.1.0" 655 | rx-lite "^3.1.2" 656 | string-width "^1.0.1" 657 | strip-ansi "^3.0.0" 658 | through "^2.3.6" 659 | 660 | interpret@^1.0.0: 661 | version "1.0.1" 662 | resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.1.tgz#d579fb7f693b858004947af39fa0db49f795602c" 663 | 664 | is-fullwidth-code-point@^1.0.0: 665 | version "1.0.0" 666 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" 667 | dependencies: 668 | number-is-nan "^1.0.0" 669 | 670 | is-fullwidth-code-point@^2.0.0: 671 | version "2.0.0" 672 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 673 | 674 | is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4: 675 | version "2.15.0" 676 | resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz#936edda3ca3c211fd98f3b2d3e08da43f7b2915b" 677 | dependencies: 678 | generate-function "^2.0.0" 679 | generate-object-property "^1.1.0" 680 | jsonpointer "^4.0.0" 681 | xtend "^4.0.0" 682 | 683 | is-path-cwd@^1.0.0: 684 | version "1.0.0" 685 | resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" 686 | 687 | is-path-in-cwd@^1.0.0: 688 | version "1.0.0" 689 | resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" 690 | dependencies: 691 | is-path-inside "^1.0.0" 692 | 693 | is-path-inside@^1.0.0: 694 | version "1.0.0" 695 | resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" 696 | dependencies: 697 | path-is-inside "^1.0.1" 698 | 699 | is-property@^1.0.0: 700 | version "1.0.2" 701 | resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" 702 | 703 | is-resolvable@^1.0.0: 704 | version "1.0.0" 705 | resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" 706 | dependencies: 707 | tryit "^1.0.1" 708 | 709 | is-typedarray@~1.0.0: 710 | version "1.0.0" 711 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 712 | 713 | isarray@^1.0.0, isarray@~1.0.0: 714 | version "1.0.0" 715 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 716 | 717 | isexe@^1.1.1: 718 | version "1.1.2" 719 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-1.1.2.tgz#36f3e22e60750920f5e7241a476a8c6a42275ad0" 720 | 721 | isstream@~0.1.2: 722 | version "0.1.2" 723 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 724 | 725 | istanbul@^0.4.5: 726 | version "0.4.5" 727 | resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" 728 | dependencies: 729 | abbrev "1.0.x" 730 | async "1.x" 731 | escodegen "1.8.x" 732 | esprima "2.7.x" 733 | glob "^5.0.15" 734 | handlebars "^4.0.1" 735 | js-yaml "3.x" 736 | mkdirp "0.5.x" 737 | nopt "3.x" 738 | once "1.x" 739 | resolve "1.1.x" 740 | supports-color "^3.1.0" 741 | which "^1.1.1" 742 | wordwrap "^1.0.0" 743 | 744 | jasmine-core@~2.5.2: 745 | version "2.5.2" 746 | resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.5.2.tgz#6f61bd79061e27f43e6f9355e44b3c6cab6ff297" 747 | 748 | jasmine-diff@^0.1.2: 749 | version "0.1.2" 750 | resolved "https://registry.yarnpkg.com/jasmine-diff/-/jasmine-diff-0.1.2.tgz#6925a8a6fcccff820bb8b2cf4abaec162b813925" 751 | dependencies: 752 | diff "^3.2.0" 753 | 754 | jasmine@^2.5.2: 755 | version "2.5.2" 756 | resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.5.2.tgz#6283cef7085c095cc25d651e954df004f7e3e421" 757 | dependencies: 758 | exit "^0.1.2" 759 | glob "^7.0.6" 760 | jasmine-core "~2.5.2" 761 | 762 | js-tokens@^2.0.0: 763 | version "2.0.0" 764 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-2.0.0.tgz#79903f5563ee778cc1162e6dcf1a0027c97f9cb5" 765 | 766 | js-yaml@3.x, js-yaml@^3.5.1: 767 | version "3.13.1" 768 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" 769 | dependencies: 770 | argparse "^1.0.7" 771 | esprima "^4.0.0" 772 | 773 | jsbn@~0.1.0: 774 | version "0.1.1" 775 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" 776 | 777 | json-schema@0.2.3: 778 | version "0.2.3" 779 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" 780 | 781 | json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: 782 | version "1.0.1" 783 | resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" 784 | dependencies: 785 | jsonify "~0.0.0" 786 | 787 | json-stringify-safe@~5.0.1: 788 | version "5.0.1" 789 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 790 | 791 | jsonify@~0.0.0: 792 | version "0.0.0" 793 | resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" 794 | 795 | jsonpointer@^4.0.0: 796 | version "4.0.0" 797 | resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.0.tgz#6661e161d2fc445f19f98430231343722e1fcbd5" 798 | 799 | jsprim@^1.2.2: 800 | version "1.3.1" 801 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.3.1.tgz#2a7256f70412a29ee3670aaca625994c4dcff252" 802 | dependencies: 803 | extsprintf "1.0.2" 804 | json-schema "0.2.3" 805 | verror "1.3.6" 806 | 807 | levn@^0.3.0, levn@~0.3.0: 808 | version "0.3.0" 809 | resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" 810 | dependencies: 811 | prelude-ls "~1.1.2" 812 | type-check "~0.3.2" 813 | 814 | lodash@^4.0.0, lodash@^4.3.0: 815 | version "4.17.15" 816 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" 817 | 818 | mime-db@~1.25.0: 819 | version "1.25.0" 820 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.25.0.tgz#c18dbd7c73a5dbf6f44a024dc0d165a1e7b1c392" 821 | 822 | mime-types@^2.1.12, mime-types@~2.1.7: 823 | version "2.1.13" 824 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.13.tgz#e07aaa9c6c6b9a7ca3012c69003ad25a39e92a88" 825 | dependencies: 826 | mime-db "~1.25.0" 827 | 828 | "minimatch@2 || 3", minimatch@^3.0.2: 829 | version "3.0.3" 830 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" 831 | dependencies: 832 | brace-expansion "^1.0.0" 833 | 834 | minimist@0.0.8: 835 | version "0.0.8" 836 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 837 | 838 | minimist@~0.0.1: 839 | version "0.0.10" 840 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" 841 | 842 | mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1: 843 | version "0.5.1" 844 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 845 | dependencies: 846 | minimist "0.0.8" 847 | 848 | ms@2.0.0: 849 | version "2.0.0" 850 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 851 | 852 | mute-stream@0.0.5: 853 | version "0.0.5" 854 | resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" 855 | 856 | natural-compare@^1.4.0: 857 | version "1.4.0" 858 | resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" 859 | 860 | neo-async@^2.6.0: 861 | version "2.6.1" 862 | resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" 863 | 864 | nopt@3.x: 865 | version "3.0.6" 866 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" 867 | dependencies: 868 | abbrev "1" 869 | 870 | number-is-nan@^1.0.0: 871 | version "1.0.1" 872 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 873 | 874 | oauth-sign@~0.8.1: 875 | version "0.8.2" 876 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" 877 | 878 | object-assign@^4.0.1, object-assign@^4.1.0: 879 | version "4.1.0" 880 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" 881 | 882 | object-hash@^1.1.7: 883 | version "1.1.7" 884 | resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.1.7.tgz#a8d83fdf5d4583a4e2e7ffc18e8915e08482ef52" 885 | 886 | once@1.x, once@^1.3.0: 887 | version "1.4.0" 888 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 889 | dependencies: 890 | wrappy "1" 891 | 892 | onetime@^1.0.0: 893 | version "1.1.0" 894 | resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" 895 | 896 | optimist@^0.6.1: 897 | version "0.6.1" 898 | resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" 899 | dependencies: 900 | minimist "~0.0.1" 901 | wordwrap "~0.0.2" 902 | 903 | optionator@^0.8.1, optionator@^0.8.2: 904 | version "0.8.2" 905 | resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" 906 | dependencies: 907 | deep-is "~0.1.3" 908 | fast-levenshtein "~2.0.4" 909 | levn "~0.3.0" 910 | prelude-ls "~1.1.2" 911 | type-check "~0.3.2" 912 | wordwrap "~1.0.0" 913 | 914 | os-homedir@^1.0.0: 915 | version "1.0.2" 916 | resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" 917 | 918 | path-is-absolute@^1.0.0: 919 | version "1.0.1" 920 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 921 | 922 | path-is-inside@^1.0.1: 923 | version "1.0.2" 924 | resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" 925 | 926 | pify@^2.0.0: 927 | version "2.3.0" 928 | resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" 929 | 930 | pinkie-promise@^2.0.0: 931 | version "2.0.1" 932 | resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" 933 | dependencies: 934 | pinkie "^2.0.0" 935 | 936 | pinkie@^2.0.0: 937 | version "2.0.4" 938 | resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" 939 | 940 | pluralize@^1.2.1: 941 | version "1.2.1" 942 | resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" 943 | 944 | prelude-ls@~1.1.2: 945 | version "1.1.2" 946 | resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" 947 | 948 | process-nextick-args@~1.0.6: 949 | version "1.0.7" 950 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" 951 | 952 | progress@^1.1.8: 953 | version "1.1.8" 954 | resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" 955 | 956 | punycode@^1.4.1: 957 | version "1.4.1" 958 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" 959 | 960 | qs@~6.3.0: 961 | version "6.3.0" 962 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.0.tgz#f403b264f23bc01228c74131b407f18d5ea5d442" 963 | 964 | readable-stream@~2.0.0: 965 | version "2.0.6" 966 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" 967 | dependencies: 968 | core-util-is "~1.0.0" 969 | inherits "~2.0.1" 970 | isarray "~1.0.0" 971 | process-nextick-args "~1.0.6" 972 | string_decoder "~0.10.x" 973 | util-deprecate "~1.0.1" 974 | 975 | readline2@^1.0.1: 976 | version "1.0.1" 977 | resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" 978 | dependencies: 979 | code-point-at "^1.0.0" 980 | is-fullwidth-code-point "^1.0.0" 981 | mute-stream "0.0.5" 982 | 983 | rechoir@^0.6.2: 984 | version "0.6.2" 985 | resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" 986 | dependencies: 987 | resolve "^1.1.6" 988 | 989 | request@>=2.42.0: 990 | version "2.79.0" 991 | resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" 992 | dependencies: 993 | aws-sign2 "~0.6.0" 994 | aws4 "^1.2.1" 995 | caseless "~0.11.0" 996 | combined-stream "~1.0.5" 997 | extend "~3.0.0" 998 | forever-agent "~0.6.1" 999 | form-data "~2.1.1" 1000 | har-validator "~2.0.6" 1001 | hawk "~3.1.3" 1002 | http-signature "~1.1.0" 1003 | is-typedarray "~1.0.0" 1004 | isstream "~0.1.2" 1005 | json-stringify-safe "~5.0.1" 1006 | mime-types "~2.1.7" 1007 | oauth-sign "~0.8.1" 1008 | qs "~6.3.0" 1009 | stringstream "~0.0.4" 1010 | tough-cookie "~2.3.0" 1011 | tunnel-agent "~0.4.1" 1012 | uuid "^3.0.0" 1013 | 1014 | require-uncached@^1.0.2: 1015 | version "1.0.3" 1016 | resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" 1017 | dependencies: 1018 | caller-path "^0.1.0" 1019 | resolve-from "^1.0.0" 1020 | 1021 | resolve-from@^1.0.0: 1022 | version "1.0.1" 1023 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" 1024 | 1025 | resolve@1.1.x, resolve@^1.1.6: 1026 | version "1.1.7" 1027 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" 1028 | 1029 | restore-cursor@^1.0.1: 1030 | version "1.0.1" 1031 | resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" 1032 | dependencies: 1033 | exit-hook "^1.0.0" 1034 | onetime "^1.0.0" 1035 | 1036 | rimraf@^2.2.8: 1037 | version "2.5.4" 1038 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.5.4.tgz#96800093cbf1a0c86bd95b4625467535c29dfa04" 1039 | dependencies: 1040 | glob "^7.0.5" 1041 | 1042 | rimraf@~2.1.4: 1043 | version "2.1.4" 1044 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.1.4.tgz#5a6eb62eeda068f51ede50f29b3e5cd22f3d9bb2" 1045 | optionalDependencies: 1046 | graceful-fs "~1" 1047 | 1048 | run-async@^0.1.0: 1049 | version "0.1.0" 1050 | resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" 1051 | dependencies: 1052 | once "^1.3.0" 1053 | 1054 | rx-lite@^3.1.2: 1055 | version "3.1.2" 1056 | resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" 1057 | 1058 | safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: 1059 | version "2.1.2" 1060 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 1061 | 1062 | shelljs@^0.7.5: 1063 | version "0.7.5" 1064 | resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.5.tgz#2eef7a50a21e1ccf37da00df767ec69e30ad0675" 1065 | dependencies: 1066 | glob "^7.0.0" 1067 | interpret "^1.0.0" 1068 | rechoir "^0.6.2" 1069 | 1070 | slice-ansi@0.0.4: 1071 | version "0.0.4" 1072 | resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" 1073 | 1074 | sntp@1.x.x: 1075 | version "1.0.9" 1076 | resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" 1077 | dependencies: 1078 | hoek "2.x.x" 1079 | 1080 | source-map@^0.6.1, source-map@~0.6.1: 1081 | version "0.6.1" 1082 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 1083 | 1084 | source-map@~0.2.0: 1085 | version "0.2.0" 1086 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" 1087 | dependencies: 1088 | amdefine ">=0.0.4" 1089 | 1090 | sprintf-js@~1.0.2: 1091 | version "1.0.3" 1092 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 1093 | 1094 | sshpk@^1.7.0: 1095 | version "1.16.1" 1096 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" 1097 | dependencies: 1098 | asn1 "~0.2.3" 1099 | assert-plus "^1.0.0" 1100 | bcrypt-pbkdf "^1.0.0" 1101 | dashdash "^1.12.0" 1102 | ecc-jsbn "~0.1.1" 1103 | getpass "^0.1.1" 1104 | jsbn "~0.1.0" 1105 | safer-buffer "^2.0.2" 1106 | tweetnacl "~0.14.0" 1107 | 1108 | string-width@^1.0.1: 1109 | version "1.0.2" 1110 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" 1111 | dependencies: 1112 | code-point-at "^1.0.0" 1113 | is-fullwidth-code-point "^1.0.0" 1114 | strip-ansi "^3.0.0" 1115 | 1116 | string-width@^2.0.0: 1117 | version "2.0.0" 1118 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e" 1119 | dependencies: 1120 | is-fullwidth-code-point "^2.0.0" 1121 | strip-ansi "^3.0.0" 1122 | 1123 | string_decoder@~0.10.x: 1124 | version "0.10.31" 1125 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" 1126 | 1127 | stringstream@~0.0.4: 1128 | version "0.0.5" 1129 | resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" 1130 | 1131 | strip-ansi@^3.0.0: 1132 | version "3.0.1" 1133 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 1134 | dependencies: 1135 | ansi-regex "^2.0.0" 1136 | 1137 | strip-bom@^3.0.0: 1138 | version "3.0.0" 1139 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" 1140 | 1141 | strip-json-comments@~1.0.1: 1142 | version "1.0.4" 1143 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" 1144 | 1145 | supports-color@^2.0.0: 1146 | version "2.0.0" 1147 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 1148 | 1149 | supports-color@^3.1.0: 1150 | version "3.1.2" 1151 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" 1152 | dependencies: 1153 | has-flag "^1.0.0" 1154 | 1155 | table@^3.7.8: 1156 | version "3.8.3" 1157 | resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" 1158 | dependencies: 1159 | ajv "^4.7.0" 1160 | ajv-keywords "^1.0.0" 1161 | chalk "^1.1.1" 1162 | lodash "^4.0.0" 1163 | slice-ansi "0.0.4" 1164 | string-width "^2.0.0" 1165 | 1166 | temp@~0.5.1: 1167 | version "0.5.1" 1168 | resolved "https://registry.yarnpkg.com/temp/-/temp-0.5.1.tgz#77ab19c79aa7b593cbe4fac2441768cad987b8df" 1169 | dependencies: 1170 | rimraf "~2.1.4" 1171 | 1172 | text-table@~0.2.0: 1173 | version "0.2.0" 1174 | resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" 1175 | 1176 | through@^2.3.6: 1177 | version "2.3.8" 1178 | resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" 1179 | 1180 | tough-cookie@~2.3.0: 1181 | version "2.3.4" 1182 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" 1183 | dependencies: 1184 | punycode "^1.4.1" 1185 | 1186 | tryit@^1.0.1: 1187 | version "1.0.3" 1188 | resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" 1189 | 1190 | tunnel-agent@~0.4.1: 1191 | version "0.4.3" 1192 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" 1193 | 1194 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: 1195 | version "0.14.5" 1196 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" 1197 | 1198 | type-check@~0.3.2: 1199 | version "0.3.2" 1200 | resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" 1201 | dependencies: 1202 | prelude-ls "~1.1.2" 1203 | 1204 | typedarray@~0.0.5: 1205 | version "0.0.6" 1206 | resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" 1207 | 1208 | uglify-js@^3.1.4: 1209 | version "3.6.8" 1210 | resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.6.8.tgz#5edcbcf9d49cbb0403dc49f856fe81530d65145e" 1211 | dependencies: 1212 | commander "~2.20.3" 1213 | source-map "~0.6.1" 1214 | 1215 | urlgrey@>=0.4.0: 1216 | version "0.4.4" 1217 | resolved "https://registry.yarnpkg.com/urlgrey/-/urlgrey-0.4.4.tgz#892fe95960805e85519f1cd4389f2cb4cbb7652f" 1218 | 1219 | user-home@^2.0.0: 1220 | version "2.0.0" 1221 | resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" 1222 | dependencies: 1223 | os-homedir "^1.0.0" 1224 | 1225 | util-deprecate@~1.0.1: 1226 | version "1.0.2" 1227 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 1228 | 1229 | uuid@^3.0.0: 1230 | version "3.0.0" 1231 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.0.tgz#6728fc0459c450d796a99c31837569bdf672d728" 1232 | 1233 | verror@1.3.6: 1234 | version "1.3.6" 1235 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" 1236 | dependencies: 1237 | extsprintf "1.0.2" 1238 | 1239 | which@^1.1.1: 1240 | version "1.2.12" 1241 | resolved "https://registry.yarnpkg.com/which/-/which-1.2.12.tgz#de67b5e450269f194909ef23ece4ebe416fa1192" 1242 | dependencies: 1243 | isexe "^1.1.1" 1244 | 1245 | wordwrap@^1.0.0, wordwrap@~1.0.0: 1246 | version "1.0.0" 1247 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" 1248 | 1249 | wordwrap@~0.0.2: 1250 | version "0.0.3" 1251 | resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" 1252 | 1253 | wrappy@1: 1254 | version "1.0.2" 1255 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1256 | 1257 | write@^0.2.1: 1258 | version "0.2.1" 1259 | resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" 1260 | dependencies: 1261 | mkdirp "^0.5.1" 1262 | 1263 | xtend@^4.0.0: 1264 | version "4.0.1" 1265 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" 1266 | --------------------------------------------------------------------------------