From 4c5a0a0050d723e44e9a227a1a12b52a3fe19699 Mon Sep 17 00:00:00 2001 From: Guillermo Rauch Date: Thu, 13 Jul 2017 20:49:15 +0800 Subject: [PATCH] preview --- .flowconfig | 9 + .gitignore | 2 + LICENSE | 191 + Readme.md | 297 ++ examples/nodejs/1-basic-server/server.js | 5 + examples/nodejs/2-microservice/package.json | 9 + examples/nodejs/2-microservice/server.js | 8 + examples/static/index.html | 17 + examples/static/style.css | 21 + package.json | 112 + src/describe-project.js | 107 + src/get-default-auth-cfg.js | 7 + src/get-default-cfg.js | 6 + src/get-help.js | 46 + src/get-now-dir.js | 8 + src/get-welcome.js | 40 + src/now.js | 397 ++ src/providers/aws/deploy.js | 362 ++ src/providers/aws/get-aws.js | 18 + src/providers/aws/get-lambda-handler.js | 61 + src/providers/aws/help.js | 5 + src/providers/aws/index.js | 13 + src/providers/aws/login.js | 162 + .../aws/util/external-credentials.js | 64 + src/providers/gcp/deploy.js | 285 ++ src/providers/gcp/help.js | 5 + src/providers/gcp/index.js | 18 + src/providers/gcp/list-projects.js | 19 + src/providers/gcp/login.js | 276 ++ src/providers/gcp/util/fetch.js | 24 + src/providers/gcp/util/get-access-token.js | 121 + .../gcp/util/get-function-handler.js | 64 + src/providers/gcp/util/save-credentials.js | 31 + src/providers/index.js | 6 + src/providers/sh/deploy.js | 853 ++++ src/providers/sh/index.js | 13 + src/providers/sh/legacy/agent.js | 83 + src/providers/sh/legacy/build-logger.js | 138 + src/providers/sh/legacy/check-path.js | 49 + src/providers/sh/legacy/error.js | 91 + src/providers/sh/legacy/get-files.js | 385 ++ src/providers/sh/legacy/git.js | 221 + src/providers/sh/legacy/hash.js | 44 + src/providers/sh/legacy/ignored.js | 17 + src/providers/sh/legacy/logs.js | 14 + src/providers/sh/legacy/now.js | 1031 ++++ src/providers/sh/legacy/plans.js | 57 + src/providers/sh/legacy/prompt-options.js | 39 + src/providers/sh/legacy/read-metadata.js | 195 + src/providers/sh/legacy/to-host.js | 20 + src/providers/sh/login.js | 290 ++ src/providers/sh/util/get-user.js | 54 + src/providers/sh/util/pkg.js | 11 + src/providers/sh/util/ua.js | 7 + src/resolve.js | 28 + src/resolvers/fs.js | 13 + src/resolvers/github.js | 100 + src/resolvers/index.js | 4 + src/serverless/README.md | 4 + src/serverless/build.js | 7 + src/serverless/builders/index.js | 11 + src/serverless/builders/nodejs.js | 92 + src/serverless/get-handler.js | 29 + src/serverless/handler.js | 110 + src/util/config-files.js | 43 + src/util/copy-to-clipboard.js | 31 + src/util/humanize-path.js | 15 + src/util/input/list.js | 82 + src/util/input/patch-inquirer.js | 18 + src/util/input/prompt-bool.js | 68 + src/util/input/text.js | 103 + src/util/output/aborted.js | 5 + src/util/output/chars.js | 7 + src/util/output/cmd.js | 5 + src/util/output/effect.js | 5 + src/util/output/erase-lines.js | 5 + src/util/output/error.js | 7 + src/util/output/highlight.js | 5 + src/util/output/info.js | 7 + src/util/output/link.js | 5 + src/util/output/list-item.js | 17 + src/util/output/logo.js | 3 + src/util/output/note.js | 5 + src/util/output/ok.js | 6 + src/util/output/param.js | 5 + src/util/output/ready.js | 5 + src/util/output/success.js | 5 + src/util/output/wait.js | 16 + src/util/sleep.js | 7 + test.js | 7 + yarn.lock | 4255 +++++++++++++++++ 91 files changed, 11568 insertions(+) create mode 100644 .flowconfig create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Readme.md create mode 100644 examples/nodejs/1-basic-server/server.js create mode 100644 examples/nodejs/2-microservice/package.json create mode 100644 examples/nodejs/2-microservice/server.js create mode 100644 examples/static/index.html create mode 100644 examples/static/style.css create mode 100644 package.json create mode 100644 src/describe-project.js create mode 100644 src/get-default-auth-cfg.js create mode 100644 src/get-default-cfg.js create mode 100644 src/get-help.js create mode 100644 src/get-now-dir.js create mode 100644 src/get-welcome.js create mode 100755 src/now.js create mode 100644 src/providers/aws/deploy.js create mode 100644 src/providers/aws/get-aws.js create mode 100644 src/providers/aws/get-lambda-handler.js create mode 100644 src/providers/aws/help.js create mode 100644 src/providers/aws/index.js create mode 100644 src/providers/aws/login.js create mode 100644 src/providers/aws/util/external-credentials.js create mode 100644 src/providers/gcp/deploy.js create mode 100644 src/providers/gcp/help.js create mode 100644 src/providers/gcp/index.js create mode 100644 src/providers/gcp/list-projects.js create mode 100644 src/providers/gcp/login.js create mode 100644 src/providers/gcp/util/fetch.js create mode 100644 src/providers/gcp/util/get-access-token.js create mode 100644 src/providers/gcp/util/get-function-handler.js create mode 100644 src/providers/gcp/util/save-credentials.js create mode 100644 src/providers/index.js create mode 100644 src/providers/sh/deploy.js create mode 100644 src/providers/sh/index.js create mode 100644 src/providers/sh/legacy/agent.js create mode 100644 src/providers/sh/legacy/build-logger.js create mode 100644 src/providers/sh/legacy/check-path.js create mode 100644 src/providers/sh/legacy/error.js create mode 100644 src/providers/sh/legacy/get-files.js create mode 100644 src/providers/sh/legacy/git.js create mode 100644 src/providers/sh/legacy/hash.js create mode 100644 src/providers/sh/legacy/ignored.js create mode 100644 src/providers/sh/legacy/logs.js create mode 100644 src/providers/sh/legacy/now.js create mode 100644 src/providers/sh/legacy/plans.js create mode 100644 src/providers/sh/legacy/prompt-options.js create mode 100644 src/providers/sh/legacy/read-metadata.js create mode 100644 src/providers/sh/legacy/to-host.js create mode 100644 src/providers/sh/login.js create mode 100644 src/providers/sh/util/get-user.js create mode 100644 src/providers/sh/util/pkg.js create mode 100644 src/providers/sh/util/ua.js create mode 100644 src/resolve.js create mode 100644 src/resolvers/fs.js create mode 100644 src/resolvers/github.js create mode 100644 src/resolvers/index.js create mode 100644 src/serverless/README.md create mode 100644 src/serverless/build.js create mode 100644 src/serverless/builders/index.js create mode 100644 src/serverless/builders/nodejs.js create mode 100644 src/serverless/get-handler.js create mode 100644 src/serverless/handler.js create mode 100644 src/util/config-files.js create mode 100644 src/util/copy-to-clipboard.js create mode 100644 src/util/humanize-path.js create mode 100644 src/util/input/list.js create mode 100644 src/util/input/patch-inquirer.js create mode 100644 src/util/input/prompt-bool.js create mode 100644 src/util/input/text.js create mode 100644 src/util/output/aborted.js create mode 100644 src/util/output/chars.js create mode 100644 src/util/output/cmd.js create mode 100644 src/util/output/effect.js create mode 100644 src/util/output/erase-lines.js create mode 100644 src/util/output/error.js create mode 100644 src/util/output/highlight.js create mode 100644 src/util/output/info.js create mode 100644 src/util/output/link.js create mode 100644 src/util/output/list-item.js create mode 100644 src/util/output/logo.js create mode 100644 src/util/output/note.js create mode 100644 src/util/output/ok.js create mode 100644 src/util/output/param.js create mode 100644 src/util/output/ready.js create mode 100644 src/util/output/success.js create mode 100644 src/util/output/wait.js create mode 100644 src/util/sleep.js create mode 100644 test.js create mode 100644 yarn.lock diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..60e7050 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,9 @@ +[ignore] + +[include] + +[libs] + +[options] + +[lints] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9209ef5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +out diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..23ec093 --- /dev/null +++ b/LICENSE @@ -0,0 +1,191 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +Copyright 2017 ZEIT, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..cbb8be9 --- /dev/null +++ b/Readme.md @@ -0,0 +1,297 @@ + +![now](https://github.com/zeit/art/blob/master/now-hosted/now-header.png?raw=true) + +## How it works + +Now enables instant immutable deployments to **any cloud provider** +with a simple API that's scalable, intuitive and optimized for collaboration. + +This is all it takes to deploy, for example, a Node.js project: + +``` +cd my-project +cat server.js +``` + +```js +require('http').createServer((req, res) => { + res.end('▲ Hello World') +}).listen(process.env.PORT) +``` + +and deploy! + +``` +now +``` + +The output of the `now` command will be a unique url to the deployment. No need for git. + +## Features + +- **Single command deployment**: `now`. +- **100% OSS** and licensed under Apache 2.0 +- **Serverless**. Worry about code, not servers. +- **Immutable**. Every time you write `now`, a new deployment is provisioned. +- **Pluggable**. Client can query any public and private cloud provider APIs +- **Flexible**. Interact with multiple clouds at once: `now gcp deploy && now aws deploy` +- **Single command setup**: `now [provider] login` +- **Secure**. All deployments are served over SSL +- **Dynamic and Static**. Deploy popular runtimes or static websites +- **Remote fs support**. Deploy any github project with `now project/repo`, gitlab with `gitlab://`. [PRs welcome](https://github.com/zeit/now/pulls)! + +## Installation + +To install the latest version: + +``` +npm install -g now@preview +``` + +Note: while the API has been in production for over a year, the different +providers are still under heavy development + +Optionally, you can clone this repo and run `npm run build` to +produce the [pkg](https://github.com/zeit/pkg) binaries. + +## Setup + +Configuration of one or more provides is necessary via `login` commands is necessary. If no logins are active and `now` + +Global configuration is stored as `~/.now/config.json`. + +Your default provider will be the first one you log in to. If you are logged into multiple providers and want to set + +``` +now config set provider gcp +``` + +### Now.sh + +``` +now login +``` + +To skip the configuration steps and deploy to `https://now.sh` +execute `now login` without any parameters, defaulting to the `sh` provider (equivalent to: `now sh login`). + +[Now.sh](https://zeit.co/now) is _**free** for open-source projects and static deployments_. It supports `Dockerfile`, `package.json` and static sites out of the box. All builds are reproducible and executed in the cloud. + +### AWS Lambda (`aws`) + +Run: + +``` +now aws login +``` + +If you have already run `aws configure` before, you will be offered +to synchronize your credentials. + +Serverless deployments are provisioned by using: + +- Lambda functions λ + - A proxy is automatically used to bridge the API between + HTTP and lambda functions and retain a consistent interface +- Certificate Manager +- API Gateway + +### Google Cloud Platform (`gcp`) + +``` +$ now gcp login +``` + +and follow the instructions! + +### Microsoft Azure (`az`) + +``` +$ now az login +``` + +and follow the instructions! + +## Project Configuration + + + +
ℹ️We welcome feedback from the community!
+ +The v1 release of `now.json` includes the following specification: + +- `name` (optional, recommended) `String` +- `description` (optional, recommended) `String` +- `type` (optional, recommended). One of: + - `String` an unique identifier for the project type. The following + are recommended choices to be supported by every provider: + - `docker` + - `nodejs` + - `static` + - `Object` + when it's necessary to specify a version or multiple interacting runtimes. It's a dictionary of runtime identifier and [SemVer-compatible]() version. For example: + ``` + { "type": { "docker": "1.x.x" } } + ``` + - `provider` (optional) indicates affinity to a certain provider +- `target` (optional) `String` + - specifies a directory or file to deploy. If relative, it's resolved + to the project directory. This is useful when a certain + deployment type (like `static`) has an output target, like an `out` + or `dist` directory. +- `env` (optional). One of + - `Object` a dictionary mapping the name of the environmental variable + to expose to the deployment and its value. + If the value begins with `@`, it's considered a + - `Array` a list of suggested environmental variables that the project + _might_ require to be deployed and function correctly +- `regions` - `Array` of `String` + - specifies one or more regition identifiers to deploy to. A wildcard + can be used to signify deployment to all supported regions by the + provider +- `files` - `Array` of `String` + - specifies a whitelist of what files have to be deployed + +To supply provider-specific configuration, you can include an arbitrary `Object` and use the provider identifier as the key. + +## Global Configuration + +The client will initialize a `.now` directory in the user's home +directory upon first running. + +There, two files can be found: + +- `config.json` +- `credentials.json` + +## Implementation notes + +Now is directly modeled after UNIX. It's useful to think of the primary subcommands `deploy`, `alias` and `rm` as being the "cloud equivalents" of `cp`, `ln` and `rm`. + +The minimal set of commands that providers must supply are: + + + + + + + + + + +
[] / deploythe default command to launch a deployment
remove / rmremove a deployment identified by its unique URL
+ +Recommended, but not required, commands are: + + + + + + + + + + + + + + + + + + + + + + +
logs | lnassociates a URL with a permanent domain name
secrets ls rm addassociates a URL with a permanent domain name
domains ls / add / rmmanage domains
dns ls / add / rmmanage dns records
certs ls / add / rmmanage certificates
+ +The `build` step for serverless deployments is implemented locally and is compatible with projects configured with the `type`: + + - `nodejs` + - `go` + - `static` + +## Philosophy + +### Immutability + +Each time you write `now` a new deployment is provisioned. Whenever +possible, providers should strive to make deployments idempotent in the +absence of changes to: + +- Originating source code +- Configuration +- Environment variables + +### Standards compliance + +All projects expose a HTTP/1.1-compatible interface. A port is provided +via the standard `process.env.PORT`. + +### Secure + +Whenever possible, deployments are strongly encouraged to be served over SSL. The process of provisioning certificates should be transparent to the user. + +### Projects should require minimal JSON configuration + +Whenever possible, projects should be deployable with minimal or no configuration. + +### Avoid manifest duplication + +If the configuration or conventions imposed by a programming language +or framework are present, attempt to provide sane defaults. + +Examples of this is the presence of `Dockerfile` or `package.json`. When +publishing a project it's recommended that the [`type`](#type) is strictly +configured in [`now.json`](#now-json) to avoid + +## Contributions and Roadmap + +#### Community + +All feedback and suggestions are welcome! + +- 💬 Chat: Join us on [zeit.chat](https://zeit.chat) `#now-client`. +- 📣 Stay up to date on new features and announcments on [@zeithq](https://twitter.com/zeithq). +- 🔐 Subscribe to our [security](http://zeit.us12.list-manage1.com/subscribe?u=3c9e9e13d7e6dae8faf375bed&id=110e586914) mailing list to stay up-to-date on urgent security disclosures. + +Please note: we adhere to the [contributor coventant](http://contributor-covenant.org/) for +all interactions in our community. + +#### Contributions + +To get started contributing, make sure you're running `node` `8.x.x`. Clone this repository: + +``` +git clone https://github.com/zeit/now +``` + +To test the [`pkg`](https://github.com/zeit/pkg) binary distribution, run: + +``` +npm run build +``` + +#### Ongoing development + +- Support for `now `, with support for: + - Binaries as a first-class deployment type + - Static deployments as a fallback +- We are working on built-in support for provisioning [Kubernetes](https://kubernetes.io/) + replication controllers and pods, in a similar vein as the [Draft](https://github.com/azure/draft) project. +- A simple API to register custom providers and pluggable build systems externally, such as Travis, Circle CI, etc. +- A companion desktop app [Now Desktop](https://github.com/zeit/now-desktop) + is available, released under the MIT license. + Work is ongoing for pluggable providers to enable: + - Team collaboration + - One-click context switch + - Drag and drop deployments +- Adding interoperabity between objects that live in different providers +- Providing a Next.js and React powered dashboard that can be deployed anywhere + +## License + +Now is licensed under the Apache License, Version 2.0. +See LICENSE for the full license text. + diff --git a/examples/nodejs/1-basic-server/server.js b/examples/nodejs/1-basic-server/server.js new file mode 100644 index 0000000..15d48e7 --- /dev/null +++ b/examples/nodejs/1-basic-server/server.js @@ -0,0 +1,5 @@ +require('http') + .createServer((req, res) => { + res.end('Hello world!') + }) + .listen(process.env.PORT) diff --git a/examples/nodejs/2-microservice/package.json b/examples/nodejs/2-microservice/package.json new file mode 100644 index 0000000..1237f02 --- /dev/null +++ b/examples/nodejs/2-microservice/package.json @@ -0,0 +1,9 @@ +{ + "name": "micro-example", + "dependencies": { + "micro": "latest" + }, + "scripts": { + "start": "micro server" + } +} diff --git a/examples/nodejs/2-microservice/server.js b/examples/nodejs/2-microservice/server.js new file mode 100644 index 0000000..017f472 --- /dev/null +++ b/examples/nodejs/2-microservice/server.js @@ -0,0 +1,8 @@ +module.exports = () => { + return { + project: { + type: 'microservice', + poweredBy: '▲' + } + } +} diff --git a/examples/static/index.html b/examples/static/index.html new file mode 100644 index 0000000..7fa4df7 --- /dev/null +++ b/examples/static/index.html @@ -0,0 +1,17 @@ + + + + My first now deployment + + + + + +
+

+ ▲ +

+
+ + + diff --git a/examples/static/style.css b/examples/static/style.css new file mode 100644 index 0000000..1c2083f --- /dev/null +++ b/examples/static/style.css @@ -0,0 +1,21 @@ +body { + background: #000; + color: #fff; +} + +div { + display: flex; + align-items: center; + height: 100%; + width: 100%; + position: absolute; +} + +p { + font-size: 200px; + margin: 0; + padding: 0; + width: 100%; + text-align: center; +} + diff --git a/package.json b/package.json new file mode 100644 index 0000000..674f243 --- /dev/null +++ b/package.json @@ -0,0 +1,112 @@ +{ + "name": "now", + "version": "8.0.0-beta.1", + "dependencies": { + "ansi-escapes": "^2.0.0", + "archiver": "^2.0.0", + "array-unique": "^0.3.2", + "async-retry": "^1.1.3", + "aws-sdk": "^2.82.0", + "bytes": "^2.5.0", + "chalk": "^2.0.1", + "clipboardy": "^1.1.4", + "convert-stream": "^1.0.2", + "debug": "^2.6.8", + "deployment-type": "^1.0.1", + "docker-file-parser": "^1.0.2", + "dotenv": "^4.0.0", + "download": "^6.2.5", + "email-prompt": "^0.3.1", + "email-validator": "^1.0.7", + "fs-extra": "^3.0.1", + "fs.promised": "^3.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.3", + "ini": "^1.3.4", + "inquirer": "^3.2.0", + "is-url": "^1.2.2", + "minimist": "^1.2.0", + "mkdirp-promise": "^5.0.1", + "ms": "^2.0.0", + "node-fetch": "^1.7.1", + "opn": "^5.1.0", + "ora": "^1.3.0", + "pipe-streams-to-promise": "^0.2.0", + "resumer": "^0.0.0", + "socket.io-client": "^2.0.3", + "split-array": "^1.0.1", + "strip-ansi": "^4.0.0", + "tar-fs": "^1.15.3", + "then-sleep": "^1.0.1", + "tmp-promise": "^1.0.3", + "uid-promise": "^1.0.0" + }, + "main": "./out/now.js", + "files": [ + "out" + ], + "bin": { + "now": "./out/now.js" + }, + "devDependencies": { + "ava": "^0.20.0", + "babel-cli": "^6.24.1", + "babel-eslint": "^7.2.3", + "babel-preset-flow": "^6.23.0", + "babel-register": "^6.24.1", + "eslint": "^4.1.1", + "flow-bin": "^0.49.1", + "flow-remove-types": "^1.2.1", + "lint-staged": "^4.0.1", + "pre-commit": "^1.2.2", + "prettier": "^1.5.2" + }, + "scripts": { + "build": "babel src/ -d out/ && chmod +x out/now.js", + "test": "eslint . && flow", + "prepublish": "yarn run test && yarn run build", + "lint:staged": "lint-staged", + "dev": "yarn run build && ./out/now.js" + }, + "pre-commit": "lint:staged", + "lint-staged": { + "*.js": [ + "eslint", + "prettier --write --single-quote --no-semi", + "git add" + ] + }, + "eslintConfig": { + "parserOptions": { + "ecmaVersion": 8, + "sourceType": "script" + }, + "parser": "babel-eslint", + "extends": [ + "eslint:recommended" + ], + "env": { + "es6": true, + "node": true + }, + "rules": { + "func-names": [ + "error", + "as-needed" + ], + "no-console": 0, + "no-shadow": "error", + "no-extra-semi": 0 + } + }, + "babel": { + "presets": [ + "flow" + ] + }, + "ava": { + "require": [ + "babel-register" + ] + } +} diff --git a/src/describe-project.js b/src/describe-project.js new file mode 100644 index 0000000..44909c2 --- /dev/null +++ b/src/describe-project.js @@ -0,0 +1,107 @@ +const { join, basename } = require('path') +const { exists, stat, readFile } = require('fs.promised') + +const describeProject = async path => { + let nowJSON = null + let packageJSON = null + + const s = await stat(path) + if (s.isFile()) { + throw new Error( + 'Deploying files directly is coming! Please supply a directory' + ) + } + + const nowJSONPath = join(path, 'now.json') + + if (await exists(nowJSONPath)) { + nowJSON = JSON.parse(await readFile(nowJSONPath)) + } + + const packageJSONPath = join(path, 'package.json') + + if (await exists(packageJSONPath)) { + packageJSON = JSON.parse(await readFile(packageJSONPath)) + } + + if (packageJSON && packageJSON.now && nowJSON) { + const err = new Error( + 'Ambigous config: package.json (with `now` field) and now.json' + ) + err.code = 'AMBIGOUS_CONFIG' + err.files = ['package.json', 'now.json'] + throw err + } + + if (nowJSON && (nowJSON.type === 'npm' || nowJSON.type === 'node')) { + console.log( + 'DEPRECATED: `npm` and `node` types should be `nodejs` in `now.json`' + ) + nowJSON.type = 'nodejs' + } + + // npm has a convention that `npm start`, if not defined, + // will invoke `node server.js` + const hasServerJSFile = await exists(join(path, 'server.js')) + + // we support explicit definition of nodejs as a type, or we + // guess it based on `package.json` or + if ( + (nowJSON && nowJSON.type === 'nodejs') || + ((!nowJSON || !nowJSON.type) && (packageJSON || hasServerJSFile)) + ) { + return { + name: getName(path, nowJSON, packageJSON), + description: getDescription(nowJSON, packageJSON), + type: 'nodejs', + nowJSON, + packageJSON, + hasServerJSFile + } + } + + if (nowJSON && nowJSON.type) { + return { + name: getName(path, nowJSON), + description: getDescription(nowJSON), + type: nowJSON.type, + nowJSON + } + } else if (await exists(join(path, 'main.go'))) { + return { + name: getName(path, nowJSON), + description: getDescription(nowJSON), + type: 'go' + } + } else { + return { + type: 'static' + } + } +} + +const getName = (path, nowJSON = null, packageJSON = null) => { + if (nowJSON && nowJSON.name != null) { + return nowJSON.name.toLowerCase() + } + + if (packageJSON && packageJSON.name != null) { + return packageJSON.name.toLowerCase() + } + + return basename(path).replace(/[^\w]+/g, '-').toLowerCase() +} + +const getDescription = (nowJSON = null, packageJSON = null) => { + if (nowJSON && nowJSON.description != null) { + return nowJSON.description + } + + if (packageJSON && packageJSON.name != null) { + return packageJSON.description + } + + return null +} + +module.exports = describeProject diff --git a/src/get-default-auth-cfg.js b/src/get-default-auth-cfg.js new file mode 100644 index 0000000..cf1a98b --- /dev/null +++ b/src/get-default-auth-cfg.js @@ -0,0 +1,7 @@ +const getDefaultAuthCfg = () => ({ + _: + 'This is your now credentials file. DONT SHARE! More: https://git.io/now-global-config', + credentials: [] +}) + +module.exports = getDefaultAuthCfg diff --git a/src/get-default-cfg.js b/src/get-default-cfg.js new file mode 100644 index 0000000..7a3d012 --- /dev/null +++ b/src/get-default-cfg.js @@ -0,0 +1,6 @@ +const getDefaultCfg = () => ({ + _: + 'This is your now credentials file. DONT SHARE! More: https://git.io/now-global-config' +}) + +module.exports = getDefaultCfg diff --git a/src/get-help.js b/src/get-help.js new file mode 100644 index 0000000..3094234 --- /dev/null +++ b/src/get-help.js @@ -0,0 +1,46 @@ +const cmd = require('./util/output/cmd') +const li = require('./util/output/list-item') +const link = require('./util/output/link') +const { gray, bold } = require('chalk') + +// prettier-disable +const getHelp = (currentProvider, providers) => + ` + ${bold('Now')}: universal serverless deployments. + + To deploy, run in any directory of your choosing: + + ${cmd('now')} + + The deployment backend provider is fully configurable. + The following are supported: + + ${Object.keys(providers) + .map(name => + li( + `${bold(name)}\t ${providers[name] + .title}\t\t\t\t\t${currentProvider === name + ? gray('(default)') + : ' '}` + ) + ) + .join('\n ')} + + For example, to setup AWS Lambda functions run: + + ${cmd('now aws login')} + + Some useful subcommands: + + ${li(cmd('now ls'))} + ${li(cmd('now rm'))} + ${li(cmd('now alias'))} + + To read more in-depth documentation, run: + + ${cmd('now [provider] [subcommand] help')} + + For more information: ${link('https://github.com/zeit/now')}. +` + +module.exports = getHelp diff --git a/src/get-now-dir.js b/src/get-now-dir.js new file mode 100644 index 0000000..ea4da51 --- /dev/null +++ b/src/get-now-dir.js @@ -0,0 +1,8 @@ +const { homedir } = require('os') +const { join } = require('path') + +const getNowDir = () => { + return process.env.NOW_HOME || join(homedir(), '.now') +} + +module.exports = getNowDir diff --git a/src/get-welcome.js b/src/get-welcome.js new file mode 100644 index 0000000..2ffc00b --- /dev/null +++ b/src/get-welcome.js @@ -0,0 +1,40 @@ +const cmd = require('./util/output/cmd') +const li = require('./util/output/list-item') +const link = require('./util/output/link') +const { gray, bold } = require('chalk') + +// prettier-disable +const getWelcome = (currentProvider, providers) => + ` + Welcome to ${bold('Now')}! + + Our tool makes serverless deployment universal and instant, + with just one command: ${cmd('now')}. + + To setup deployments with ${link('https://now.sh')} run: + + ${cmd('now login')} + + The following providers are also supported + + ${Object.keys(providers) + .map(name => + li( + `${bold(name)}\t ${providers[name] + .title}\t\t\t\t\t${currentProvider === name + ? gray('(default)') + : ' '}` + ) + ) + .join('\n ')} + + To set up AWS, for example, run ${cmd('now aws login')}. + Many can be configured simultaneously! + + Hope you enjoy Now! Check out these other resources: + + ${li(`Run ${cmd('now help')} for more info and examples`)} + ${li(link('https://github.com/zeit/now'))} +` + +module.exports = getWelcome diff --git a/src/now.js b/src/now.js new file mode 100755 index 0000000..e784343 --- /dev/null +++ b/src/now.js @@ -0,0 +1,397 @@ +#!/usr/bin/env node +//@flow +const start = Date.now() + +// theirs +const debug = require('debug')('now:main') +const { exists } = require('fs.promised') +const { join } = require('path') +const mkdirp = require('mkdirp-promise') +const minimist = require('minimist') + +// ours +const error = require('./util/output/error') +const effect = require('./util/output/effect') +const param = require('./util/output/param') +const getHelp = require('./get-help') +const getWelcome = require('./get-welcome') +const getNowDir = require('./get-now-dir') +const getDefaultCfg = require('./get-default-cfg') +const getDefaultAuthCfg = require('./get-default-auth-cfg') +const hp = require('./util/humanize-path') +const providers = require('./providers') +const configFiles = require('./util/config-files') + +const NOW_DIR = getNowDir() +const NOW_CONFIG_PATH = configFiles.getConfigFilePath() +const NOW_AUTH_CONFIG_PATH = configFiles.getAuthConfigFilePath() + +const GLOBAL_COMMANDS = new Set(['help']) + +const exit = code => { + debug('finished in', Date.now() - start) + process.exit(code) +} + +const main = async argv_ => { + const argv = minimist(argv_, { + boolean: ['help', 'version'], + alias: { + help: 'h', + version: 'v' + } + }) + + // the second argument to the command can be a path + // (as in: `now path/`) or a subcommand / provider + // (as in: `now ls` or `now aws help`) + let targetOrSubcommand: ?string = argv._[2] + + // we want to handle version or help directly only + if (!targetOrSubcommand) { + if (argv.version) { + console.log(require('../package').version) + return 0 + } + } + + let nowDirExists + + try { + nowDirExists = await exists(NOW_DIR) + } catch (err) { + console.error( + error( + 'An unexpected error occurred while trying to find the ' + + 'now global directory: ' + + err.message + ) + ) + return 1 + } + + if (!nowDirExists) { + try { + await mkdirp(NOW_DIR) + } catch (err) { + error( + 'An unexpected error occurred while trying to create the ' + + `now global directory "${hp(NOW_DIR)}" ` + + err.message + ) + } + } + + let initConfig = false + let initAuthConfig = false + let configExists + + try { + configExists = await exists(NOW_CONFIG_PATH) + } catch (err) { + console.error( + error( + 'An unexpected error occurred while trying to find the ' + + `now config file "${hp(NOW_CONFIG_PATH)}" ` + + err.message + ) + ) + return 1 + } + + let config + + if (configExists) { + try { + config = configFiles.readConfigFile() + } catch (err) { + console.error( + error( + 'An unexpected error occurred while trying to read the ' + + `now config in "${hp(NOW_CONFIG_PATH)}" ` + + err.message + ) + ) + return 1 + } + + try { + config = JSON.parse(config) + } catch (err) { + console.error( + error( + `An error occurred while trying to parse "${hp(NOW_CONFIG_PATH)}": ` + + err.message + ) + ) + return 1 + } + } else { + config = getDefaultCfg() + try { + configFiles.writeToConfigFile(config) + initConfig = true + } catch (err) { + console.error( + error( + 'An unexpected error occurred while trying to write the ' + + `default now config to "${hp(NOW_CONFIG_PATH)}" ` + + err.message + ) + ) + return 1 + } + } + + let authConfigExists + + try { + authConfigExists = await exists(NOW_AUTH_CONFIG_PATH) + } catch (err) { + console.error( + error( + 'An unexpected error occurred while trying to find the ' + + `now auth file "${hp(NOW_AUTH_CONFIG_PATH)}" ` + + err.message + ) + ) + return 1 + } + + let authConfig = null + + if (authConfigExists) { + try { + authConfig = configFiles.readAuthConfigFile() + } catch (err) { + console.error( + error( + 'An unexpected error occurred while trying to read the ' + + `now auth config in "${hp(NOW_AUTH_CONFIG_PATH)}" ` + + err.message + ) + ) + return 1 + } + + try { + authConfig = JSON.parse(authConfig) + + if (!Array.isArray(authConfig.credentials)) { + console.error( + error( + `The content of "${hp(NOW_AUTH_CONFIG_PATH)}" is invalid. ` + + 'No `credentials` list found inside' + ) + ) + return 1 + } + + for (const [i, { provider }] of authConfig.credentials.entries()) { + if (null == provider) { + console.error( + error( + `Invalid credential found in "${hp(NOW_AUTH_CONFIG_PATH)}". ` + + `Missing \`provider\` key in entry with index ${i}` + ) + ) + return 1 + } + + if (!(provider in providers)) { + console.error( + error( + `Invalid credential found in "${hp(NOW_AUTH_CONFIG_PATH)}". ` + + `Unknown provider "${provider}"` + ) + ) + return 1 + } + } + } catch (err) { + console.error( + error( + `An error occurred while trying to parse "${hp( + NOW_AUTH_CONFIG_PATH + )}": ` + err.message + ) + ) + return 1 + } + } else { + authConfig = getDefaultAuthCfg() + try { + configFiles.writeToAuthConfigFile(authConfig) + initAuthConfig = true + } catch (err) { + console.error( + error( + 'An unexpected error occurred while trying to write the ' + + `default now config to "${hp(NOW_CONFIG_PATH)}" ` + + err.message + ) + ) + return 1 + } + } + + if (initConfig || initAuthConfig) { + console.log( + effect( + `Initialized default config in "${initConfig && initAuthConfig + ? hp(NOW_DIR) + : hp(initConfig ? NOW_CONFIG_PATH : NOW_AUTH_CONFIG_PATH)}"` + ) + ) + } + + let suppliedProvider = null + + // if the target is something like `aws` + if (targetOrSubcommand in providers) { + debug('user supplied a known provider') + const targetPath = join(process.cwd(), targetOrSubcommand) + const targetPathExists = await exists(targetPath) + + if (targetPathExists) { + console.error( + error( + `The supplied argument ${param(targetOrSubcommand)} is ambiguous. ` + + 'Both a directory and a provider are known' + ) + ) + return 1 + } + + suppliedProvider = targetOrSubcommand + targetOrSubcommand = argv._[3] + } + + let { defaultProvider = null }: { defaultProvider: ?string } = config + + if (null === suppliedProvider) { + if (null === defaultProvider) { + // the first provider the user ever logged in to is + // the default provider + if (authConfig && authConfig.credentials.length) { + debug('using first credential as default provider') + defaultProvider = authConfig.credentials[0].provider + } else { + debug(`fallbacking to default now provider 'sh'`) + defaultProvider = 'sh' + } + } else { + debug('using provider supplied by user', defaultProvider) + if (!(defaultProvider in providers)) { + console.error( + error( + `The \`defaultProvider\` "${defaultProvider}" supplied in ` + + `"${NOW_CONFIG_PATH}" is not a valid provider` + ) + ) + return 1 + } + } + } + + // we special case help because it's a generic command with + // information about all providers + if (!suppliedProvider && argv.help) { + console.log(getHelp(defaultProvider, providers)) + return 0 + } + + const provider: Object = providers[suppliedProvider || defaultProvider] + + // the context object to supply to the providers + const ctx = { + config, + authConfig, + argv: argv_ + } + + let subcommand + + // we check if we are deploying something + if (targetOrSubcommand) { + const targetPath = join(process.cwd(), targetOrSubcommand) + const targetPathExists = await exists(targetPath) + + const subcommandExists = + GLOBAL_COMMANDS.has(targetOrSubcommand) || + provider.subcommands.has(targetOrSubcommand) + + if (targetPathExists && subcommandExists) { + console.error( + error( + `The supplied argument ${param(targetOrSubcommand)} is ambiguous. ` + + 'Both a directory and a subcommand are known' + ) + ) + return 1 + } + + if (subcommandExists) { + debug('user supplied known subcommand', targetOrSubcommand) + subcommand = targetOrSubcommand + } else { + debug('user supplied a possible target for deployment') + // our default command is deployment + // at this point we're + subcommand = 'deploy' + } + } else { + debug('user supplied no target, defaulting to deploy') + subcommand = 'deploy' + } + + if (subcommand === 'deploy' && !authConfig.credentials.length) { + debug('subcommand is deploy, but user has no credentials') + console.log(getWelcome(provider, providers)) + return 0 + } + + if (subcommand === 'help') { + console.log(getHelp(defaultProvider, providers)) + return 0 + } + + try { + return provider[subcommand](ctx) + } catch (err) { + console.error( + error( + `An unexpected error occurred in provider ${subcommand}: ${err.stack}` + ) + ) + } +} + +debug('start') + +const handleRejection = err => { + debug('handling rejection') + if (err) { + if (err instanceof Error) { + handleUnexpected(err) + } else { + console.error(error(`An unexpected rejection occurred\n ${err}`)) + } + } else { + console.error(error('An unexpected empty rejection occurred')) + } + return 1 +} + +const handleUnexpected = err => { + debug('handling unexpected error') + console.error( + error(`An unexpected error occurred!\n ${err.stack} ${err.stack}`) + ) + return 1 +} + +process.on('uncaughtRejection', handleRejection) +process.on('uncaughtException', handleUnexpected) + +main(process.argv).then((code: number) => exit(code)).catch(handleUnexpected) diff --git a/src/providers/aws/deploy.js b/src/providers/aws/deploy.js new file mode 100644 index 0000000..c297657 --- /dev/null +++ b/src/providers/aws/deploy.js @@ -0,0 +1,362 @@ +// @flow + +// theirs +const ms = require('ms') +const minimist = require('minimist') +const { gray, bold } = require('chalk') +const bytes = require('bytes') +const uid = require('uid-promise') +const debug = require('debug')('now:aws:deploy') + +// ours +const resolve = require('../../resolve') +const ok = require('../../util/output/ok') +const wait = require('../../util/output/wait') +const info = require('../../util/output/info') +const error = require('../../util/output/error') +const link = require('../../util/output/link') +const success = require('../../util/output/success') +const param = require('../../util/output/param') +const humanPath = require('../../util/humanize-path') +const build = require('../../serverless/build') +const getLambdaHandler = require('./get-lambda-handler') +const getAWS = require('./get-aws') +const describeProject = require('../../describe-project') +const copyToClipboard = require('../../util/copy-to-clipboard') + +const NOW_DEFAULT_IAM_ROLE = 'now-default-role' +const IAM_POLICY_DOCUMENT = { + Version: '2012-10-17', + Statement: [ + { + Sid: '', + Effect: 'Allow', + Principal: { + Service: 'lambda.amazonaws.com' + }, + Action: 'sts:AssumeRole' + } + ] +} + +const deploy = async ({ config, authConfig, argv: argv_ }) => { + const argv = minimist(argv_, { + boolean: ['help'], + alias: { + help: 'h' + } + }) + + // `now [provider] [deploy] [target]` + const [cmdOrTarget = null, target_ = null] = argv._.slice(2).slice(-2) + + let target + + if (cmdOrTarget === 'aws' || cmdOrTarget === 'deploy') { + target = target_ === null ? process.cwd() : target_ + } else { + if (target_) { + console.error(error('Unexpected number of arguments for deploy command')) + return 1 + } else { + target = cmdOrTarget === null ? process.cwd() : cmdOrTarget + } + } + + const start = Date.now() + const resolved = await resolve(target) + + if (resolved === null) { + console.error(error(`Could not resolve deployment target ${param(target)}`)) + return 1 + } + + let desc = null + + try { + desc = await describeProject(resolved) + } catch (err) { + if (err.code === 'AMBIGOUS_CONFIG') { + console.error( + error(`There is more than one source of \`now\` config: ${err.files}`) + ) + return 1 + } else { + throw err + } + } + + // a set of files that we personalize for this build + const overrides = { + '__now_handler.js': getLambdaHandler(desc) + } + + // initialize aws client + const aws = getAWS(authConfig) + const region = aws.config.region || 'us-west-1' + + console.log( + info( + `Deploying ${param(humanPath(resolved))} ${gray('(aws)')} ${gray( + `(${region})` + )}` + ) + ) + const buildStart = Date.now() + const stopBuildSpinner = wait('Building and bundling your app…') + const zipFile = await build(resolved, desc, { overrides }) + stopBuildSpinner() + + // lambda limits to 50mb + if (zipFile.length > 50 * 1024 * 1024) { + console.error(error('The build exceeds the 50mb AWS Lambda limit')) + return 1 + } + + console.log( + ok( + `Build generated a ${bold(bytes(zipFile.length))} zip ${gray( + `[${ms(Date.now() - buildStart)}]` + )}` + ) + ) + + const iam = new aws.IAM({ apiVersion: '2010-05-08' }) + + const gateway = new aws.APIGateway({ + apiVersion: '2015-07-09', + region + }) + + const lambda = new aws.Lambda({ + apiVersion: '2015-03-31', + region + }) + + let role + + try { + role = await getRole(iam, { RoleName: NOW_DEFAULT_IAM_ROLE }) + } catch (err) { + if ('NoSuchEntity' === err.code) { + const iamStart = Date.now() + role = await createRole(iam, { + AssumeRolePolicyDocument: JSON.stringify(IAM_POLICY_DOCUMENT), + RoleName: NOW_DEFAULT_IAM_ROLE + }) + console.log( + ok( + `Initialized IAM role ${param(NOW_DEFAULT_IAM_ROLE)} ${gray( + `[${ms(iamStart - Date.now())}]` + )}` + ) + ) + } else { + throw err + } + } + + const deploymentId = 'now-' + desc.name + '-' + (await uid(10)) + + const resourcesStart = Date.now() + const stopResourcesSpinner = wait('Creating API resources') + + debug('initializing lambda function') + const λ = await createFunction(lambda, { + Code: { + ZipFile: zipFile + }, + Runtime: 'nodejs6.10', + Description: desc.description, + FunctionName: deploymentId, + Handler: '__now_handler.handler', + Role: role.Role.Arn, + Timeout: 15, + MemorySize: 512 + }) + + debug('initializing api gateway') + const api = await createAPI(gateway, { + name: deploymentId, + description: desc.description + }) + + debug('retrieving root resource id') + const resources = await getResources(gateway, { + restApiId: api.id + }) + const rootResourceId = resources.items[0].id + + debug('initializing gateway method for /') + await putMethod(gateway, { + restApiId: api.id, + authorizationType: 'NONE', + httpMethod: 'ANY', + resourceId: rootResourceId + }) + + debug('initializing gateway integration for /') + await putIntegration(gateway, { + restApiId: api.id, + resourceId: rootResourceId, + httpMethod: 'ANY', + type: 'AWS_PROXY', + integrationHttpMethod: 'POST', + uri: `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${λ.FunctionArn}/invocations` + }) + + debug('initializing gateway resource') + const resource = await createResource(gateway, { + restApiId: api.id, + parentId: rootResourceId, + pathPart: '{proxy+}' + }) + + debug('initializing gateway method for {proxy+}') + await putMethod(gateway, { + restApiId: api.id, + authorizationType: 'NONE', + httpMethod: 'ANY', + resourceId: resource.id + }) + + debug('initializing gateway integration for {proxy+}') + await putIntegration(gateway, { + restApiId: api.id, + resourceId: resource.id, + httpMethod: 'ANY', + type: 'AWS_PROXY', + integrationHttpMethod: 'POST', + uri: `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${λ.FunctionArn}/invocations` + }) + + debug('creating deployment') + await createDeployment(gateway, { + restApiId: api.id, + stageName: 'now' + }) + + const [, accountId] = role.Role.Arn.match(/^arn:aws:iam::(\d+):/) + + await addPermission(lambda, { + FunctionName: deploymentId, + StatementId: deploymentId, + Action: 'lambda:InvokeFunction', + Principal: 'apigateway.amazonaws.com', + SourceArn: `arn:aws:execute-api:${region}:${accountId}:${api.id}/now/ANY/*` + }) + + stopResourcesSpinner() + console.log( + ok( + `API resources created (id: ${param(deploymentId)}) ${gray( + `[${ms(Date.now() - resourcesStart)}]` + )}` + ) + ) + + const url = `https://${api.id}.execute-api.${region}.amazonaws.com/now` + const copied = copyToClipboard(url, config.copyToClipboard) + + console.log( + success( + `${link(url)} ${copied ? gray('(in clipboard)') : ''} ${gray( + `[${ms(Date.now() - start)}]` + )}` + ) + ) + + return 0 +} + +const getRole = (iam, params) => { + return new Promise((res, reject) => { + iam.getRole(params, (err, data) => { + if (err) return reject(err) + res(data) + }) + }) +} + +const createRole = (iam, params) => { + return new Promise((res, reject) => { + iam.createRole(params, (err, data) => { + if (err) return reject(err) + res(data) + }) + }) +} + +const createFunction = (lambda, params) => { + return new Promise((res, reject) => { + lambda.createFunction(params, (err, data) => { + if (err) return reject(err) + res(data) + }) + }) +} + +const addPermission = (lambda, params) => { + return new Promise((res, reject) => { + lambda.addPermission(params, (err, data) => { + if (err) return reject(err) + res(data) + }) + }) +} + +const createAPI = (gateway, params) => { + return new Promise((res, reject) => { + gateway.createRestApi(params, (err, data) => { + if (err) return reject(err) + res(data) + }) + }) +} + +const getResources = (gateway, params) => { + return new Promise((res, reject) => { + gateway.getResources(params, (err, data) => { + if (err) return reject(err) + res(data) + }) + }) +} + +const createResource = (gateway, params) => { + return new Promise((res, reject) => { + gateway.createResource(params, (err, data) => { + if (err) return reject(err) + res(data) + }) + }) +} + +const putMethod = (gateway, params) => { + return new Promise((res, reject) => { + gateway.putMethod(params, (err, data) => { + if (err) return reject(err) + res(data) + }) + }) +} + +const putIntegration = (gateway, params) => { + return new Promise((res, reject) => { + gateway.putIntegration(params, (err, data) => { + if (err) return reject(err) + res(data) + }) + }) +} + +const createDeployment = (gateway, params) => { + return new Promise((res, reject) => { + gateway.createDeployment(params, (err, data) => { + if (err) return reject(err) + res(data) + }) + }) +} + +module.exports = deploy diff --git a/src/providers/aws/get-aws.js b/src/providers/aws/get-aws.js new file mode 100644 index 0000000..5fc581f --- /dev/null +++ b/src/providers/aws/get-aws.js @@ -0,0 +1,18 @@ +const aws = require('aws-sdk') + +const getAWS = authConfig => { + const { credentials } = authConfig + const awsCredentials: Object = credentials.find(c => c.provider === 'aws') + + if (awsCredentials.useVendorConfig) { + aws.config.credentials = new aws.SharedIniFileCredentials() + } else { + aws.config = new aws.Config() + aws.config.accessKeyId = awsCredentials.accessKeyId + aws.config.secretAccessKey = awsCredentials.secretAccessKey + } + + return aws +} + +module.exports = getAWS diff --git a/src/providers/aws/get-lambda-handler.js b/src/providers/aws/get-lambda-handler.js new file mode 100644 index 0000000..f1f5b2b --- /dev/null +++ b/src/providers/aws/get-lambda-handler.js @@ -0,0 +1,61 @@ +const getHandler = require('../../serverless/get-handler') + +// generate the handler that we'll use as the ƛ function +const getLambdaHandler = desc => { + // the command that our handler will invoke to fire up + // the user-suppled HTTP server + let cmd = null + let script = null + + if (desc.packageJSON) { + if (desc.packageJSON.scripts && desc.packageJSON.scripts.start) { + cmd = desc.packageJSON.scripts.start + } else { + // `node .` will use `main` or fallback to `index.js` + script = './' + } + } else { + if (desc.hasServerJSFile) { + script = 'server.js' + } else { + script = 'index.js' + } + } + + return getHandler({ script, cmd }, (makeRequest, getPort, req, ctx, fn) => { + const url = + req.path + + '?' + + require('querystring').stringify(req.queryStringParameters) + const proxy = makeRequest( + { + port: getPort(), + hostname: '127.0.0.1', + path: url, + method: req.httpMethod, + headers: req.headers + }, + proxyRes => { + let body = '' + proxyRes.on('data', data => { + body += data + }) + proxyRes.on('error', err => { + fn(err) + body = '' + }) + proxyRes.on('end', () => { + fn(null, { + statusCode: proxyRes.statusCode, + headers: proxyRes.headers, + body + }) + }) + } + ) + proxy.on('error', fn) + proxy.end(req.body) + }) +} + +module.exports = getLambdaHandler diff --git a/src/providers/aws/help.js b/src/providers/aws/help.js new file mode 100644 index 0000000..0fc8e93 --- /dev/null +++ b/src/providers/aws/help.js @@ -0,0 +1,5 @@ +const help = () => { + console.log('halp') +} + +module.exports = help diff --git a/src/providers/aws/index.js b/src/providers/aws/index.js new file mode 100644 index 0000000..75e0289 --- /dev/null +++ b/src/providers/aws/index.js @@ -0,0 +1,13 @@ +module.exports = { + title: 'AWS Lambda', + subcommands: new Set(['help', 'login', 'deploy', 'ls']), + get deploy() { + return require('./deploy') + }, + get help() { + return require('./help') + }, + get login() { + return require('./login') + } +} diff --git a/src/providers/aws/login.js b/src/providers/aws/login.js new file mode 100644 index 0000000..fcc5620 --- /dev/null +++ b/src/providers/aws/login.js @@ -0,0 +1,162 @@ +// @flow +// theirs +const { green, italic } = require('chalk') + +// ours +const info = require('../../util/output/info') +const note = require('../../util/output/note') +const aborted = require('../../util/output/aborted') +const cmd = require('../../util/output/cmd') +const param = require('../../util/output/param') +const ready = require('../../util/output/ready') +const highlight = require('../../util/output/highlight') +const listItem = require('../../util/output/list-item') +const link = require('../../util/output/link') +const textInput = require('../../util/input/text') +const eraseLines = require('../../util/output/erase-lines') +const chars = require('../../util/output/chars') +const { + hasExternalCredentials, + getExternalCredentials, + AWS_CREDENTIALS_FILE_PATH +} = require('./util/external-credentials') +const promptBool = require('../../util/input/prompt-bool') +const { + writeToAuthConfigFile, + getAuthConfigFilePath +} = require('../../util/config-files') +const humanize = require('../../util/humanize-path') + +const accessKeyIdLabel = 'Access Key ID ' +const secretAccessKeyLabel = 'Secret Access Key ' + +function saveCredentials({ + ctx, + accessKeyId, + secretAccessKey, + useExternal, + credentialsIndex +}) { + const obj = { + provider: 'aws' + } + + if (useExternal) { + obj.useVendorConfig = true + } else { + obj.accessKeyId = accessKeyId + obj.secretAccessKey = secretAccessKey + } + + if (credentialsIndex === -1) { + // the user is not logged in + ctx.authConfig.credentials.push(obj) + } else { + // the user is already logged in - let's replace the credentials we have + ctx.authConfig.credentials[credentialsIndex] = obj + } + + writeToAuthConfigFile(ctx.authConfig) + + return ctx +} + +async function login(ctx) { + const credentialsIndex = ctx.authConfig.credentials.findIndex( + cred => cred.provider === 'aws' + ) + + if (credentialsIndex !== -1) { + // the user is already logged in on aws + console.log( + note(`You already have AWS credentials – this will replace them.`) + ) + } + + if (await hasExternalCredentials()) { + // if the user has ~/.aws/credentials, let's ask if they want to use them + const credentials = await getExternalCredentials() + + if (credentials.accessKeyId && credentials.secretAccessKey) { + let yes + try { + yes = await promptBool( + info( + `AWS credentials found in ${param(AWS_CREDENTIALS_FILE_PATH)}.`, + ` Would you like to use them?` + ), + { + defaultValue: true + } + ) + } catch (err) { + if (err.code === 'USER_ABORT') { + console.log(aborted('No changes made.')) + return 1 + } + throw err + } + + if (yes) { + ctx = saveCredentials({ ctx, useExternal: true, credentialsIndex }) + console.log( + ready(`Credentials will be read from your AWS config when needed`) + ) + return + } else { + console.log(info(`Ignoring ${param(AWS_CREDENTIALS_FILE_PATH)}`)) + } + } + } + + // prettier-ignore + console.log(info( + `We'll need your ${highlight('AWS credentials')} in order to comunicate with their API.`, + ` To provision a dedicated set of tokens for ${cmd('now')}, do the following:`, + ``, + ` ${listItem(1, `Go to ${link('https://console.aws.amazon.com/iam')}`)}`, + ` ${listItem(2, `Click on ${param('Users')} in the left menubar`)}`, + ` ${listItem(3, `Click on ${param('Add user')}`)}`, + ` ${listItem(4, `Give your user a name and select ${param('Programmatic access')}`)}`, + ` ${listItem(5, `In the ${param('Permissions')} step, select\n` + + ` ${param('Attach existing policies directly')}\n` + + ` and then\n` + + ` ${param('AdministratorAccess')}`)} ${italic('(or pick your own)')}`, + ` ${listItem(6, `After the ${param('Review')} step, grab your keys and paste them below:`)}`, + `` + )) + + try { + const accessKeyId = await textInput({ label: listItem(accessKeyIdLabel) }) + console.log( + `${eraseLines(1)}${green(chars.tick)} ${accessKeyIdLabel}${accessKeyId}` + ) + + const secretAccessKey = await textInput({ + label: listItem(secretAccessKeyLabel) + }) + console.log( + `${eraseLines(1)}${green( + chars.tick + )} ${secretAccessKeyLabel}${secretAccessKey}` + ) + + ctx = saveCredentials({ + ctx, + accessKeyId, + secretAccessKey, + credentialsIndex + }) + console.log( + ready(`Credentials saved in ${param(humanize(getAuthConfigFilePath()))}`) + ) + } catch (err) { + if (err.code === 'USER_ABORT') { + console.log(aborted('No changes made.')) + return 1 + } + throw err + } +} + +module.exports = login diff --git a/src/providers/aws/util/external-credentials.js b/src/providers/aws/util/external-credentials.js new file mode 100644 index 0000000..606640f --- /dev/null +++ b/src/providers/aws/util/external-credentials.js @@ -0,0 +1,64 @@ +// node +const { join: joinPath } = require('path') +const { homedir } = require('os') + +// theirs +const { readFile, exists: fileExists } = require('fs.promised') +const debug = require('debug')('now:aws:util:external-credentials') + +const AWS_CREDENTIALS_FILE_PATH = joinPath(homedir(), '.aws', 'credentials') +// matches `aws_access_key_id=aaaaa` +// and `aws_access_key_id = aaaaa` with any number of spaces +const ACCESS_KEY_ID_REGEX = /^aws_access_key_id(\s+)?=(\s+)?(.*)$/m +const SECRET_ACCESS_KEY_REGEX = /^aws_secret_access_key(\s+)?=(\s+)?(.*)$/m + +// checks if there's a ~/.aws/credentials +async function hasExternalCredentials() { + let found = false + try { + found = await fileExists(AWS_CREDENTIALS_FILE_PATH) + } catch (err) { + // if this happens, we're fine: + // 1. if the user is trying to login, let's just fallback to the manual + // steps + // 2. if it's the Nth time the user is using `now aws`, we know we depend + // on this file and we'll let him know that we couldn't find the file + // anymore upon `hasExternalCredentials() === false` + debug(`Couldn't read ${AWS_CREDENTIALS_FILE_PATH} because of ${err}`) + } + + return found +} + +// gets the two aws tokens from ~/.aws/credentials +// assumes the file exist – `hasExternalCredentials` should always be called +// first +async function getExternalCredentials() { + let contents + try { + contents = await readFile(AWS_CREDENTIALS_FILE_PATH, 'utf8') + } catch (err) { + // Here we should error because the file is there but we can't read it + throw new Error( + `Couldn't read ${AWS_CREDENTIALS_FILE_PATH} beause of ${err.message}` + ) + } + + const matchesAccessKeyId = ACCESS_KEY_ID_REGEX.exec(contents) + const matchesSecretAccessKey = SECRET_ACCESS_KEY_REGEX.exec(contents) + + return { + accessKeyId: (matchesAccessKeyId && matchesAccessKeyId[3]) || undefined, + secretAccessKey: + (matchesSecretAccessKey && matchesSecretAccessKey[3]) || undefined + } +} + +module.exports = { + hasExternalCredentials, + getExternalCredentials, + AWS_CREDENTIALS_FILE_PATH: + process.platform === 'win32' + ? AWS_CREDENTIALS_FILE_PATH + : AWS_CREDENTIALS_FILE_PATH.replace(homedir(), '~') +} diff --git a/src/providers/gcp/deploy.js b/src/providers/gcp/deploy.js new file mode 100644 index 0000000..a545215 --- /dev/null +++ b/src/providers/gcp/deploy.js @@ -0,0 +1,285 @@ +// @flow + +// theirs +const ms = require('ms') +const fetch = require('node-fetch') +const minimist = require('minimist') +const { gray, bold } = require('chalk') +const uid = require('uid-promise') +const bytes = require('bytes') +const sleep = require('then-sleep') +const debug = require('debug')('now:gcp:deploy') + +// ours +const ok = require('../../util/output/ok') +const info = require('../../util/output/info') +const wait = require('../../util/output/wait') +const link = require('../../util/output/link') +const success = require('../../util/output/success') +const humanPath = require('../../util/humanize-path') +const resolve = require('../../resolve') +const error = require('../../util/output/error') +const param = require('../../util/output/param') +const build = require('../../serverless/build') +const getToken = require('./util/get-access-token') +const describeProject = require('../../describe-project') +const copyToClipboard = require('../../util/copy-to-clipboard') +const getFunctionHandler = require('./util/get-function-handler') + +const BUCKET_NAME = 'now-deployments' + +const deploy = async ctx => { + const { argv: argv_ } = ctx + const argv = minimist(argv_, { + boolean: ['help'], + alias: { + help: 'h' + } + }) + + const token = await getToken(ctx) + + // `now [provider] [deploy] [target]` + const [cmdOrTarget = null, target_ = null] = argv._.slice(2).slice(-2) + + let target + + if (cmdOrTarget === 'gcp' || cmdOrTarget === 'deploy') { + target = target_ === null ? process.cwd() : target_ + } else { + if (target_) { + console.error(error('Unexpected number of arguments for deploy command')) + return 1 + } else { + target = cmdOrTarget === null ? process.cwd() : cmdOrTarget + } + } + + const start = Date.now() + const resolved = await resolve(target) + + if (resolved === null) { + console.error(error(`Could not resolve deployment target ${param(target)}`)) + return 1 + } + + let desc = null + + try { + desc = await describeProject(resolved) + } catch (err) { + if (err.code === 'AMBIGOUS_CONFIG') { + console.error( + error(`There is more than one source of \`now\` config: ${err.files}`) + ) + return 1 + } else { + throw err + } + } + + const overrides = { + 'function.js': getFunctionHandler(desc) + } + + const region = 'us-central1' + + console.log( + info( + `Deploying ${param(humanPath(resolved))} ${gray('(gcp)')} ${gray( + `(${region})` + )}` + ) + ) + + const buildStart = Date.now() + const stopBuildSpinner = wait('Building and bundling your app…') + const zipFile = await build(resolved, desc, { overrides }) + stopBuildSpinner() + + if (zipFile.length > 100 * 1024 * 1024) { + console.error(error('The build exceeds the 100mb GCP Functions limit')) + return 1 + } + + console.log( + ok( + `Build generated a ${bold(bytes(zipFile.length))} zip ${gray( + `[${ms(Date.now() - buildStart)}]` + )}` + ) + ) + + const deploymentId = 'now-' + desc.name + '-' + (await uid(10)) + const zipFileName = `${deploymentId}.zip` + + const { project } = ctx.authConfig.credentials.find(p => p.provider === 'gcp') + + const resourcesStart = Date.now() + const stopResourcesSpinner = wait('Creating API resources') + + debug('creating gcp storage bucket') + const bucketRes = await fetch( + `https://www.googleapis.com/storage/v1/b?project=${project.id}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + name: BUCKET_NAME + }) + } + ) + + if ( + bucketRes.status !== 200 && + bucketRes.status !== 409 /* already exists */ + ) { + console.error( + error( + `Error while creating GCP Storage bucket: ${await bucketRes.text()}` + ) + ) + return 1 + } + + debug('creating gcp storage file') + const fileRes = await fetch( + `https://www.googleapis.com/upload/storage/v1/b/${BUCKET_NAME}/o?uploadType=media&name=${encodeURIComponent( + zipFileName + )}&project=${encodeURIComponent(project.id)}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/zip', + 'Content-Length': zipFile.length, + Authorization: `Bearer ${token}` + }, + body: zipFile + } + ) + + try { + await assertSuccessfulResponse(fileRes) + } catch (err) { + console.error(error(err.message)) + return 1 + } + + debug('creating gcp function create') + const fnCreateRes = await fetch( + `https://cloudfunctions.googleapis.com/v1beta2/projects/${project.id}/locations/${region}/functions`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${token}` + }, + body: JSON.stringify({ + name: `projects/${project.id}/locations/${region}/functions/${deploymentId}`, + timeout: '15s', + availableMemoryMb: 512, + sourceArchiveUrl: `gs://${BUCKET_NAME}/${zipFileName}`, + entryPoint: 'handler', + httpsTrigger: { + url: null + } + }) + } + ) + + if (403 === fnCreateRes.status) { + const url = `https://console.cloud.google.com/apis/api/cloudfunctions.googleapis.com/overview?project=${project.id}` + console.error( + error( + 'GCP Permission Denied error. Make sure the "Google Cloud Functions API" ' + + `is enabled in the API Manager\n ${bold('API Manager URL')}: ${link( + url + )}` + ) + ) + return 1 + } + + try { + await assertSuccessfulResponse(fnCreateRes) + } catch (err) { + console.error(error(err.message)) + return 1 + } + + let retriesLeft = 10 + let status + let url + + do { + if (!--retriesLeft) { + console.error( + error('Could not determine status of the deployment: ' + url) + ) + return 1 + } else { + await sleep(5000) + } + + const fnRes = await fetch( + `https://cloudfunctions.googleapis.com/v1beta2/projects/${project.id}/locations/${region}/functions/${deploymentId}`, + { + method: 'GET', + headers: { + Authorization: `Bearer ${token}` + } + } + ) + + try { + await assertSuccessfulResponse(fnRes) + } catch (err) { + console.error(error(err.message)) + return 1 + } + + ;({ status, httpsTrigger: { url } } = await fnRes.json()) + } while (status !== 'READY') + + stopResourcesSpinner() + console.log( + ok( + `API resources created (id: ${param(deploymentId)}) ${gray( + `[${ms(Date.now() - resourcesStart)}]` + )}` + ) + ) + + const copied = copyToClipboard(url, ctx.config.copyToClipboard) + + console.log( + success( + `${link(url)} ${copied ? gray('(in clipboard)') : ''} ${gray( + `[${ms(Date.now() - start)}]` + )}` + ) + ) + + return 0 +} + +const assertSuccessfulResponse = async res => { + if (!res.ok) { + let msg + let body + + try { + body = await res.json() + } catch (err) { + msg = `An API error was returned (${res.status}), but the error code could not be diagnosed` + } + + msg = body.error.message + throw new Error(msg) + } +} + +module.exports = deploy diff --git a/src/providers/gcp/help.js b/src/providers/gcp/help.js new file mode 100644 index 0000000..35d4c91 --- /dev/null +++ b/src/providers/gcp/help.js @@ -0,0 +1,5 @@ +const help = () => { + console.log('gcp help') +} + +module.exports = help diff --git a/src/providers/gcp/index.js b/src/providers/gcp/index.js new file mode 100644 index 0000000..659f98a --- /dev/null +++ b/src/providers/gcp/index.js @@ -0,0 +1,18 @@ +module.exports = { + title: 'Google Cloud Platform', + subcommands: new Set(['help', 'login', 'deploy', 'ls']), + + // we use getters for commands to lazily get code + // and not bog down initialization + get help() { + return require('./help') + }, + + get deploy() { + return require('./deploy') + }, + + get login() { + return require('./login') + } +} diff --git a/src/providers/gcp/list-projects.js b/src/providers/gcp/list-projects.js new file mode 100644 index 0000000..e797611 --- /dev/null +++ b/src/providers/gcp/list-projects.js @@ -0,0 +1,19 @@ +// ours +const fetch = require('./util/fetch') +const getToken = require('./util/get-access-token') + +const URL = 'https://cloudresourcemanager.googleapis.com/v1/projects' + +const projectsLs = async ctx => { + const token = await getToken(ctx) + + if (!token) { + return 1 + } + + const { projects } = await fetch({ url: URL, token }) + + return projects +} + +module.exports = projectsLs diff --git a/src/providers/gcp/login.js b/src/providers/gcp/login.js new file mode 100644 index 0000000..5e65a7c --- /dev/null +++ b/src/providers/gcp/login.js @@ -0,0 +1,276 @@ +// node +const { parse: parseUrl } = require('url') +const { encode: encodeQuery, stringify: formUrlEncode } = require('querystring') +const { createServer } = require('http') + +// theirs +const opn = require('opn') +const fetch = require('node-fetch') +const debug = require('debug')('now:gcp:login') + +// ours +const error = require('../../util/output/error') +const aborted = require('../../util/output/aborted') +const info = require('../../util/output/info') +const ready = require('../../util/output/ready') +const param = require('../../util/output/param') +const promptBool = require('../../util/input/prompt-bool') +const getNowDir = require('../../get-now-dir') +const humanize = require('../../util/humanize-path') +const saveCredentials = require('./util/save-credentials') +const promptList = require('../../util/input/list') +const listProjects = require('./list-projects') +const { writeToAuthConfigFile } = require('../../util/config-files') + +// ports that are authorized in the GCP app +const PORTS = [8085, 8086, 8087, 8088] +const CLIENT_ID = + '258013614557-0qulvq65vqk8pi9akn7igqsquejjffil.apps.googleusercontent.com' +const CLIENT_SECRET = 'SvmeeRFmKQkIe_ZQHSe1UJ-O' +// instructs gcp to send the response in the query string +const RESPONSE_TYPE = 'code' +const SCOPES = [ + 'https://www.googleapis.com/auth/cloud-platform', + 'https://www.googleapis.com/auth/appengine.admin', + 'https://www.googleapis.com/auth/compute', + 'https://www.googleapis.com/auth/accounts.reauth' +] +// instructs gcp to return a `refresh_token` that we'll use to seamlessly +// get a new auth token every time the current one expires +const ACCESS_TYPE = 'offline' +// url we'll send the user to +const USER_URL = 'https://accounts.google.com/o/oauth2/v2/auth' +// url we'll get the access tokens from and refresh the token when needed +const TOKEN_URL = 'https://www.googleapis.com/oauth2/v4/token' +// required by oauth2's spec +const GRANT_TYPE = 'authorization_code' +// this ensures google *always* asks the user for permission +// we enfore this to make sure we *always* receive a `refresh_token` (if the +// is already authorized by the user, a `refresh_token` will *not* +// be returned) since we need it +const PROMPT_CONSENT = 'consent' + +const serverListen = ({ server, port }) => { + return new Promise((resolve, reject) => { + server.on('error', reject) // will happen if the port is already in use + server.listen(port, resolve) + }) +} + +function login(ctx) { + return new Promise(async resolve => { + let credentialsIndex = ctx.authConfig.credentials.findIndex( + cred => cred.provider === 'gcp' + ) + + if (credentialsIndex !== -1) { + // the user is already logged into gcp + let yes + try { + yes = await promptBool( + info( + `You already have GCP credentials – this will replace them.`, + ` Do you want to continue?` + ) + ) + } catch (err) { + // promptBool only `reject`s upon user abort + // let's set it to false just to make it clear + yes = false + } + + if (!yes) { + console.log(aborted('No changes made.')) + resolve(0) + } + } + + const ports = [...PORTS] + const server = createServer(async function handleRequest(req, res) { + const { query: { error: _error, code } } = parseUrl(req.url, true) + + if (!_error && !code) { + // the browser requesting the favicon etc + res.end('') + return + } + + res.setHeader('content-type', 'text/html') + res.end( + `` + + `

That's it – you can now return to your terminal!

` + ) + + if (_error) { + // the user didn't give us permission + console.log(aborted(`No changes made.`)) + return resolve(1) + } + + if (code) { + // that's right after the user gave us permission + // let's exchange the authorization code for an access + refresh codes + + const body = formUrlEncode({ + code, + client_id: CLIENT_ID, + client_secret: CLIENT_SECRET, + redirect_uri: `http://${req.headers.host}`, + grant_type: GRANT_TYPE + }) + + const opts = { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'content-length': body.length // just in case + }, + body: body + } + + let accessToken + let expiresIn + let refreshToken + let response + + try { + response = await fetch(TOKEN_URL, opts) + if (response.status !== 200) { + debug( + `HTTP ${response.status} when trying to exchange the authorization code`, + await response.text() + ) + console.log( + error( + `Got unexpected status code from Google: ${response.status}` + ) + ) + return resolve(1) + } + } catch (err) { + debug( + 'unexpected error occurred while making the request to exchange the authorization code', + err.message + ) + console.log( + error( + 'Unexpected error occurred while authenthing with Google', + err.stack + ) + ) + return resolve(1) + } + + try { + const json = await response.json() + accessToken = json.access_token + expiresIn = json.expires_in + refreshToken = json.refresh_token + } catch (err) { + debug( + 'unexpected error occurred while parsing the JSON from the exchange request', + err.stack, + 'got', + await response.text() + ) + console.log( + error( + 'Unexpected error occurred while parsing the JSON response from Google', + err.message + ) + ) + resolve(1) + } + + const now = new Date() + // `expires_in` is 3600 seconds + const expiresAt = now.setSeconds(now.getSeconds() + expiresIn) + ctx = saveCredentials({ + ctx, + accessToken, + expiresAt, + refreshToken, + credentialsIndex + }) + + const projects = await listProjects(ctx) + const message = 'Select a project:' + const choices = projects.map(project => { + return { + name: `${project.name} (${project.projectId})`, + value: project.projectId, + short: project.name + } + }) + + const projectId = await promptList({ + message, + choices, + separator: false + }) + + const { projectId: id, name } = projects.find( + p => p.projectId === projectId + ) + + credentialsIndex = ctx.authConfig.credentials.findIndex( + cred => cred.provider === 'gcp' + ) + ctx.authConfig.credentials[credentialsIndex].project = { + id, + name + } + + writeToAuthConfigFile(ctx.authConfig) + + console.log( + ready( + `Credentials and project saved in ${param(humanize(getNowDir()))}.` + ) + ) + resolve(1) + } + }) + + let shouldRetry = true + let portToTry = ports.shift() + + while (shouldRetry) { + try { + await serverListen({ server, port: portToTry }) + shouldRetry = false // done, listening + } catch (err) { + if (ports.length) { + // let's try again + portToTry = ports.shift() + } else { + // we're out of ports to try + shouldRetry = false + } + } + } + + if (!server.listening) { + console.log( + error( + `Make sure you have one of the following TCP ports available:`, + ` ${PORTS.join(', ').replace()}` + ) + ) + return resolve(1) + } + + const query = { + client_id: CLIENT_ID, + redirect_uri: `http://localhost:${portToTry}`, + response_type: RESPONSE_TYPE, + scope: SCOPES.join(' '), + access_type: ACCESS_TYPE, + prompt: PROMPT_CONSENT + } + + opn(USER_URL + '?' + encodeQuery(query)) + }) +} + +module.exports = login diff --git a/src/providers/gcp/util/fetch.js b/src/providers/gcp/util/fetch.js new file mode 100644 index 0000000..1d3820c --- /dev/null +++ b/src/providers/gcp/util/fetch.js @@ -0,0 +1,24 @@ +// node +const { encode: encodeQuery } = require('querystring') + +// theirs +const _fetch = require('node-fetch') + +const fetch = async ({ url, method = 'GET', token, query }) => { + url = query ? url + '?' + encodeQuery(query) : url + const headers = { + Accept: 'application/json', + Authorization: `Bearer ${token}` + } + + const res = await _fetch(url, { + method, + headers + }) + + const json = await res.json() + + return json +} + +module.exports = fetch diff --git a/src/providers/gcp/util/get-access-token.js b/src/providers/gcp/util/get-access-token.js new file mode 100644 index 0000000..758cfee --- /dev/null +++ b/src/providers/gcp/util/get-access-token.js @@ -0,0 +1,121 @@ +// node +const { stringify: formUrlEncode } = require('querystring') + +// theirs +const fetch = require('node-fetch') +const debug = require('debug')('now:gcp:get_token') + +// ours +const saveCredentials = require('./save-credentials') +const error = require('../../../util/output/error') +const cmd = require('../../../util/output/cmd') + +const CLIENT_ID = + '258013614557-0qulvq65vqk8pi9akn7igqsquejjffil.apps.googleusercontent.com' +const CLIENT_SECRET = 'SvmeeRFmKQkIe_ZQHSe1UJ-O' +// required by oauth2's spec +const GRANT_TYPE = 'refresh_token' +const URL = 'https://www.googleapis.com/oauth2/v4/token' + +// note that this function treats the errors it can produce, printing them +// to the user and then returns `undefined` +const getAccessToken = async ctx => { + const credentialsIndex = ctx.authConfig.credentials.findIndex( + c => c.provider === 'gcp' + ) + + if (credentialsIndex === -1) { + console.log(error(`You're not logged in! Run ${cmd('now gcp login')}.`)) + return + } + + const { accessToken, expiresAt, refreshToken } = ctx.authConfig.credentials[ + credentialsIndex + ] + + if (Date.now() < expiresAt) { + // the token is still valid + return accessToken + } + // we need to refresh the token + const body = formUrlEncode({ + refresh_token: refreshToken, + client_id: CLIENT_ID, + client_secret: CLIENT_SECRET, + grant_type: GRANT_TYPE + }) + + const opts = { + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded', + 'content-length': body.length // just in case + }, + body: body + } + + let newAccessToken + let newExpiresIn + let response + + try { + response = await fetch(URL, opts) + if (response.status !== 200) { + debug( + `HTTP ${response.status} when trying to exchange the authorization code`, + await response.text() + ) + console.log( + error(`Got unexpected status code from Google: ${response.status}`) + ) + return + } + } catch (err) { + debug( + 'unexpected error occurred while making the request to exchange the authorization code', + err.message + ) + console.log( + error( + 'Unexpected error occurred while authenthing with Google', + err.stack + ) + ) + return + } + + try { + const json = await response.json() + newAccessToken = json.access_token + newExpiresIn = json.expires_in + } catch (err) { + debug( + 'unexpected error occurred while parsing the JSON from the exchange request', + err.stack, + 'got', + await response.text() + ) + console.log( + error( + 'Unexpected error occurred while parsing the JSON response from Google', + err.message + ) + ) + return + } + + const now = new Date() + // `expires_in` is 3600 seconds + const newExpiresAt = now.setSeconds(now.getSeconds() + newExpiresIn) + saveCredentials({ + ctx, + accessToken: newAccessToken, + expiresAt: newExpiresAt, + refreshToken, + credentialsIndex + }) + + return newAccessToken +} + +module.exports = getAccessToken diff --git a/src/providers/gcp/util/get-function-handler.js b/src/providers/gcp/util/get-function-handler.js new file mode 100644 index 0000000..5548e9e --- /dev/null +++ b/src/providers/gcp/util/get-function-handler.js @@ -0,0 +1,64 @@ +const getHandler = require('../../../serverless/get-handler') + +const getFunctionHandler = desc => { + // the command that our handler will invoke to fire up + // the user-suppled HTTP server + let cmd = null + let script = null + + if (desc.packageJSON) { + if (desc.packageJSON.scripts && desc.packageJSON.scripts.start) { + cmd = desc.packageJSON.scripts.start + } else { + // `node .` will use `main` or fallback to `index.js` + script = './' + } + } else { + if (desc.hasServerJSFile) { + script = 'server.js' + } else { + script = 'index.js' + } + } + + return getHandler({ cmd, script }, (makeRequest, getPort, req, res) => { + let body + + if ('object' === typeof req.body && !(body instanceof Buffer)) { + body = JSON.stringify(req.body) + } else { + body = req.body + } + + console.log('got request', req.url, req.method, req.headers) + const proxyRequest = makeRequest( + { + port: getPort(), + hostname: '127.0.0.1', + // TODO: figure out how to get the path? + path: req.url, + method: req.method, + headers: req.headers + }, + proxyRes => { + proxyRes.on('data', data => { + res.write(data) + }) + proxyRes.on('error', err => { + console.error(err) + res.status(500).end() + }) + proxyRes.on('end', () => { + res.end() + }) + } + ) + proxyRequest.on('error', err => { + console.error(err) + res.status(500).end() + }) + proxyRequest.end(body) + }) +} + +module.exports = getFunctionHandler diff --git a/src/providers/gcp/util/save-credentials.js b/src/providers/gcp/util/save-credentials.js new file mode 100644 index 0000000..4aeba9a --- /dev/null +++ b/src/providers/gcp/util/save-credentials.js @@ -0,0 +1,31 @@ +const { writeToAuthConfigFile } = require('../../../util/config-files') + +const saveCredentials = ({ + ctx, + accessToken, + expiresAt, + refreshToken, + credentialsIndex +}) => { + const current = ctx.authConfig.credentials[credentialsIndex] || {} + const obj = Object.assign({}, current, { + provider: 'gcp', + accessToken, + expiresAt, + refreshToken + }) + + if (credentialsIndex === -1) { + // the user is not logged in + ctx.authConfig.credentials.push(obj) + } else { + // the user is already logged in - let's replace the credentials we have + ctx.authConfig.credentials[credentialsIndex] = obj + } + + writeToAuthConfigFile(ctx.authConfig) + + return ctx +} + +module.exports = saveCredentials diff --git a/src/providers/index.js b/src/providers/index.js new file mode 100644 index 0000000..811b11d --- /dev/null +++ b/src/providers/index.js @@ -0,0 +1,6 @@ +// @flow +module.exports = { + sh: require('./sh'), + aws: require('./aws'), + gcp: require('./gcp') +} diff --git a/src/providers/sh/deploy.js b/src/providers/sh/deploy.js new file mode 100644 index 0000000..1400b12 --- /dev/null +++ b/src/providers/sh/deploy.js @@ -0,0 +1,853 @@ +#!/usr/bin/env node + +// Native +const { resolve, basename } = require('path') + +// Packages +const Progress = require('progress') +const fs = require('fs-extra') +const bytes = require('bytes') +const chalk = require('chalk') +const minimist = require('minimist') +const ms = require('ms') +const dotenv = require('dotenv') +const { eraseLines } = require('ansi-escapes') +const { write: copy } = require('clipboardy') +const inquirer = require('inquirer') + +// Ours +const Logger = require('./legacy/build-logger') +const Now = require('./legacy/now.js') +const toHumanPath = require('../../util/humanize-path') +const { handleError, error } = require('./legacy/error') +const { fromGit, isRepoPath, gitPathParts } = require('./legacy/git') +const readMetaData = require('./legacy/read-metadata') +const checkPath = require('./legacy/check-path') +const logo = require('../../util/output/logo') +const cmd = require('../../util/output/cmd') +const info = require('../../util/output/info') +const wait = require('../../util/output/wait') +const NowPlans = require('./legacy/plans') +const promptBool = require('../../util/input/prompt-bool') +const promptOptions = require('./legacy/prompt-options') +const note = require('../../util/output/note') + +const minimistOpts = { + string: ['config', 'token', 'name', 'alias', 'session-affinity'], + boolean: [ + 'help', + 'version', + 'debug', + 'force', + 'links', + 'login', + 'no-clipboard', + 'forward-npm', + 'docker', + 'npm', + 'static' + ], + alias: { + env: 'e', + dotenv: 'E', + help: 'h', + config: 'c', + debug: 'd', + version: 'v', + force: 'f', + token: 't', + forceSync: 'F', + links: 'l', + login: 'L', + public: 'p', + 'no-clipboard': 'C', + 'forward-npm': 'N', + 'session-affinity': 'S', + name: 'n', + alias: 'a' + } +} + +const help = () => { + console.log(` + ${chalk.bold(`${logo()} now`)} [options] + + ${chalk.dim('Commands:')} + + ${chalk.dim('Cloud')} + + deploy [path] Performs a deployment ${chalk.bold( + '(default)' + )} + ls | list [app] List deployments + rm | remove [id] Remove a deployment + ln | alias [id] [url] Configures aliases for deployments + domains [name] Manages your domain names + certs [cmd] Manages your SSL certificates + secrets [name] Manages your secret environment variables + dns [name] Manages your DNS records + logs [url] Displays the logs for a deployment + scale [args] Scales the instance count of a deployment + help [cmd] Displays complete help for [cmd] + + ${chalk.dim('Administrative')} + + billing | cc [cmd] Manages your credit cards and billing methods + upgrade | downgrade [plan] Upgrades or downgrades your plan + teams [team] Manages your teams + switch Switches between teams and your account + login Login into your account or creates a new one + logout Logout from your account + + ${chalk.dim('Options:')} + + -h, --help Output usage information + -v, --version Output the version number + -n, --name Set the name of the deployment + -c ${chalk.underline('FILE')}, --config=${chalk.underline( + 'FILE' + )} Config file + -d, --debug Debug mode [off] + -f, --force Force a new deployment even if nothing has changed + -t ${chalk.underline('TOKEN')}, --token=${chalk.underline( + 'TOKEN' + )} Login token + -L, --login Configure login + -l, --links Copy symlinks without resolving their target + -p, --public Deployment is public (${chalk.dim( + '`/_src`' + )} is exposed) [on for oss, off for premium] + -e, --env Include an env var (e.g.: ${chalk.dim( + '`-e KEY=value`' + )}). Can appear many times. + -E ${chalk.underline('FILE')}, --dotenv=${chalk.underline( + 'FILE' + )} Include env vars from .env file. Defaults to '.env' + -C, --no-clipboard Do not attempt to copy URL to clipboard + -N, --forward-npm Forward login information to install private npm modules + --session-affinity Session affinity, \`ip\` (default) or \`random\` to control session affinity. + + ${chalk.dim( + 'Enforcable Types (when both package.json and Dockerfile exist):' + )} + + --npm Node.js application + --docker Docker container + --static Static file hosting + + ${chalk.dim('Examples:')} + + ${chalk.gray('–')} Deploys the current directory + + ${chalk.cyan('$ now')} + + ${chalk.gray('–')} Deploys a custom path ${chalk.dim('`/usr/src/project`')} + + ${chalk.cyan('$ now /usr/src/project')} + + ${chalk.gray('–')} Deploys a GitHub repository + + ${chalk.cyan('$ now user/repo#ref')} + + ${chalk.gray('–')} Deploys a GitHub, GitLab or Bitbucket repo using its URL + + ${chalk.cyan('$ now https://gitlab.com/user/repo')} + + ${chalk.gray('–')} Deploys with ENV vars + + ${chalk.cyan( + '$ now -e NODE_ENV=production -e MYSQL_PASSWORD=@mysql-password' + )} + + ${chalk.gray('–')} Displays comprehensive help for the subcommand ${chalk.dim( + '`list`' + )} + + ${chalk.cyan('$ now help list')} +`) +} + +let argv +let path + +// Options +let forceNew +let deploymentName +let sessionAffinity +let debug +let clipboard +let forwardNpm +let forceSync +let followSymlinks +let wantsPublic +let apiUrl +let isTTY +let quiet +let alwaysForwardNpm + +// If the current deployment is a repo +const gitRepo = {} + +const stopDeployment = msg => { + handleError(msg) + process.exit(1) +} + +const envFields = async list => { + const questions = [] + + for (const field of list) { + questions.push({ + name: field, + message: field + }) + } + + // eslint-disable-next-line import/no-unassigned-import + require('../../lib/util/input/patch-inquirer') + + console.log( + info('Please enter the values for the following environment variables:') + ) + const answers = await inquirer.prompt(questions) + + for (const answer in answers) { + if (!{}.hasOwnProperty.call(answers, answer)) { + continue + } + + const content = answers[answer] + + if (content === '') { + stopDeployment(`Enter a value for ${answer}`) + } + } + + return answers +} + +async function main(ctx) { + argv = minimist(ctx.argv.slice(2), minimistOpts) + + // very ugly hack – this (now-cli's code) expects that `argv._[0]` is the path + // we should fix this ASAP + if (argv._[0] === 'sh') { + argv._.shift() + } + if (argv._[0] === 'deploy') { + argv._.shift() + } + + if (path) { + // If path is relative: resolve + // if path is absolute: clear up strange `/` etc + path = resolve(process.cwd(), path) + } else { + path = process.cwd() + } + + // Options + forceNew = argv.force + deploymentName = argv.name + sessionAffinity = argv['session-affinity'] + debug = argv.debug + clipboard = !argv['no-clipboard'] + forwardNpm = argv['forward-npm'] + forceSync = argv.forceSync + followSymlinks = !argv.links + wantsPublic = argv.public + apiUrl = argv.url || 'https://api.zeit.co' + isTTY = process.stdout.isTTY + quiet = !isTTY + if (argv.h || argv.help) { + help() + return 0 + } + + let { token } = + ctx.authConfig.credentials.find(c => c.provider === 'sh') || {} + + if (!token) { + // node file sh [...] + const sh = argv[2] === 'sh' + const _cmd = `now ${sh ? 'sh ' : ''}login` + console.log(error(`You're not logged in! Please run ${cmd(_cmd)}`)) + return 1 + } + + const config = ctx.config.sh + + alwaysForwardNpm = config.forwardNpm + + if (argv.config) { + // TODO enable this + // cfg.setConfigFile(argv.config) + } + + try { + return sync({ token, config }) + } catch (err) { + return stopDeployment(err) + } +} + +async function sync({ token, config: { currentTeam, user } }) { + return new Promise(async (_resolve, reject) => { + const start = Date.now() + const rawPath = argv._[0] + + const planPromise = new NowPlans({ + apiUrl, + token, + debug, + currentTeam + }).getCurrent() + + try { + await fs.stat(path) + } catch (err) { + let repo + let isValidRepo = false + try { + isValidRepo = isRepoPath(rawPath) + } catch (_err) { + if (err.code === 'INVALID_URL') { + stopDeployment(_err) + } else { + reject(_err) + } + } + + if (isValidRepo) { + const gitParts = gitPathParts(rawPath) + Object.assign(gitRepo, gitParts) + + const searchMessage = setTimeout(() => { + console.log( + `> Didn't find directory. Searching on ${gitRepo.type}...` + ) + }, 500) + + try { + repo = await fromGit(rawPath, debug) + } catch (_err) { + // why is this ignored? + } + + clearTimeout(searchMessage) + } + + if (repo) { + // Tell now which directory to deploy + path = repo.path + + // Set global variable for deleting tmp dir later + // once the deployment has finished + Object.assign(gitRepo, repo) + } else if (isValidRepo) { + const gitRef = gitRepo.ref ? `with "${chalk.bold(gitRepo.ref)}" ` : '' + stopDeployment( + `There's no repository named "${chalk.bold( + gitRepo.main + )}" ${gitRef}on ${gitRepo.type}` + ) + } else { + error(`The specified directory "${basename(path)}" doesn't exist.`) + process.exit(1) + } + } + + // Make sure that directory is deployable + try { + await checkPath(path) + } catch (err) { + error(err) + return + } + + if (!quiet) { + if (gitRepo.main) { + const gitRef = gitRepo.ref ? ` at "${chalk.bold(gitRepo.ref)}" ` : '' + console.log( + `> Deploying ${gitRepo.type} repository "${chalk.bold( + gitRepo.main + )}" ${gitRef} under ${chalk.bold( + (currentTeam && currentTeam.slug) || user.username || user.email + )}` + ) + } else { + console.log( + `> Deploying ${chalk.bold(toHumanPath(path))} under ${chalk.bold( + (currentTeam && currentTeam.slug) || user.username || user.email + )}` + ) + } + } + + let deploymentType + + // CLI deployment type explicit overrides + if (argv.docker) { + if (debug) { + console.log(`> [debug] Forcing \`deploymentType\` = \`docker\``) + } + + deploymentType = 'docker' + } else if (argv.npm) { + if (debug) { + console.log(`> [debug] Forcing \`deploymentType\` = \`npm\``) + } + + deploymentType = 'npm' + } else if (argv.static) { + if (debug) { + console.log(`> [debug] Forcing \`deploymentType\` = \`static\``) + } + + deploymentType = 'static' + } + + let meta + ;({ + meta, + deploymentName, + deploymentType, + sessionAffinity + } = await readMeta(path, deploymentName, deploymentType, sessionAffinity)) + const nowConfig = meta.nowConfig + + const now = new Now({ apiUrl, token, debug, currentTeam }) + + let dotenvConfig + let dotenvOption + + if (argv.dotenv) { + dotenvOption = argv.dotenv + } else if (nowConfig && nowConfig.dotenv) { + dotenvOption = nowConfig.dotenv + } + + if (dotenvOption) { + const dotenvFileName = + typeof dotenvOption === 'string' ? dotenvOption : '.env' + + if (!fs.existsSync(dotenvFileName)) { + error(`--dotenv flag is set but ${dotenvFileName} file is missing`) + return process.exit(1) + } + + const dotenvFile = await fs.readFile(dotenvFileName) + dotenvConfig = dotenv.parse(dotenvFile) + } + + let pkgEnv = nowConfig && nowConfig.env + const argEnv = [].concat(argv.env || []) + + if (pkgEnv && Array.isArray(nowConfig.env)) { + const defined = argEnv.join() + const askFor = nowConfig.env.filter(item => !defined.includes(`${item}=`)) + + pkgEnv = await envFields(askFor) + } + + // Merge `now.env` from package.json with `-e` arguments + const envs = [ + ...Object.keys(dotenvConfig || {}).map(k => `${k}=${dotenvConfig[k]}`), + ...Object.keys(pkgEnv || {}).map(k => `${k}=${pkgEnv[k]}`), + ...argEnv + ] + + let secrets + const findSecret = async uidOrName => { + if (!secrets) { + secrets = await now.listSecrets() + } + + return secrets.filter(secret => { + return secret.name === uidOrName || secret.uid === uidOrName + }) + } + + const env_ = await Promise.all( + envs.map(async kv => { + if (typeof kv !== 'string') { + error('Env key and value missing') + return process.exit(1) + } + + const [key, ...rest] = kv.split('=') + let val + + if (rest.length > 0) { + val = rest.join('=') + } + + if (/[^A-z0-9_]/i.test(key)) { + error( + `Invalid ${chalk.dim('-e')} key ${chalk.bold( + `"${chalk.bold(key)}"` + )}. Only letters, digits and underscores are allowed.` + ) + return process.exit(1) + } + + if (!key) { + error(`Invalid env option ${chalk.bold(`"${kv}"`)}`) + return process.exit(1) + } + + if (val === undefined) { + if (key in process.env) { + console.log( + `> Reading ${chalk.bold( + `"${chalk.bold(key)}"` + )} from your env (as no value was specified)` + ) + // Escape value if it begins with @ + val = process.env[key].replace(/^@/, '\\@') + } else { + error( + `No value specified for env ${chalk.bold( + `"${chalk.bold(key)}"` + )} and it was not found in your env.` + ) + return process.exit(1) + } + } + + if (val[0] === '@') { + const uidOrName = val.substr(1) + const _secrets = await findSecret(uidOrName) + if (_secrets.length === 0) { + if (uidOrName === '') { + error( + `Empty reference provided for env key ${chalk.bold( + `"${chalk.bold(key)}"` + )}` + ) + } else { + error( + `No secret found by uid or name ${chalk.bold(`"${uidOrName}"`)}` + ) + } + return process.exit(1) + } else if (_secrets.length > 1) { + error( + `Ambiguous secret ${chalk.bold( + `"${uidOrName}"` + )} (matches ${chalk.bold(_secrets.length)} secrets)` + ) + return process.exit(1) + } + + val = { uid: _secrets[0].uid } + } + + return [key, typeof val === 'string' ? val.replace(/^\\@/, '@') : val] + }) + ) + + const env = {} + env_.filter(v => Boolean(v)).forEach(([key, val]) => { + if (key in env) { + console.log( + note(`Overriding duplicate env key ${chalk.bold(`"${key}"`)}`) + ) + } + + env[key] = val + }) + + try { + await now.create( + path, + Object.assign( + { + env, + followSymlinks, + forceNew, + forceSync, + forwardNpm: alwaysForwardNpm || forwardNpm, + quiet, + wantsPublic, + sessionAffinity + }, + meta + ) + ) + } catch (err) { + if (debug) { + console.log(`> [debug] error: ${err}\n${err.stack}`) + } + + return stopDeployment(err) + } + + const { url } = now + const elapsed = ms(new Date() - start) + + if (isTTY) { + if (clipboard) { + try { + await copy(url) + console.log( + `${chalk.cyan('> Ready!')} ${chalk.bold( + url + )} (copied to clipboard) [${elapsed}]` + ) + } catch (err) { + console.log( + `${chalk.cyan('> Ready!')} ${chalk.bold(url)} [${elapsed}]` + ) + } + } else { + console.log(`> ${url} [${elapsed}]`) + } + } else { + process.stdout.write(url) + } + + const startU = new Date() + + const complete = ({ syncCount }) => { + if (!quiet) { + const elapsedU = ms(new Date() - startU) + console.log( + `> Synced ${syncCount} (${bytes(now.syncAmount)}) [${elapsedU}] ` + ) + console.log('> Initializing…') + } + + // Close http2 agent + now.close() + + // Show build logs + if (!quiet) { + if (deploymentType === 'static') { + console.log(`${chalk.cyan('> Deployment complete!')}`) + } else { + printLogs(now.host, token, currentTeam, user) + } + } + } + + const plan = await planPromise + + if (plan.id === 'oss' && !wantsPublic) { + if (isTTY) { + console.log( + info( + `${chalk.bold( + (currentTeam && `${currentTeam.slug} is`) || + `You (${user.username || user.email}) are` + )} on the OSS plan. Your code and logs will be made ${chalk.bold( + 'public' + )}.` + ) + ) + + const proceed = await promptBool( + 'Are you sure you want to proceed with the deployment?', + { trailing: eraseLines(1) } + ) + + if (proceed) { + console.log( + note(`You can use ${cmd('now --public')} to skip this prompt`) + ) + } else { + const stopSpinner = wait('Canceling deployment') + await now.remove(now.id, { hard: true }) + stopSpinner() + console.log( + info( + 'Deployment aborted. No files were synced.', + ` You can upgrade by running ${cmd('now upgrade')}.` + ) + ) + return 0 + } + } else if (!wantsPublic) { + const msg = + '\nYou are on the OSS plan. Your code and logs will be made public.' + + ' If you agree with that, please run again with --public.' + return stopDeployment(msg) + } + } + + if (now.syncAmount) { + if (debug && now.syncFileCount !== now.fileCount) { + console.log( + `> [debug] total files ${now.fileCount}, ${now.syncFileCount} changed. ` + ) + } + const size = bytes(now.syncAmount) + const syncCount = `${now.syncFileCount} file${now.syncFileCount > 1 + ? 's' + : ''}` + const bar = new Progress( + `> Upload [:bar] :percent :etas (${size}) [${syncCount}]`, + { + width: 20, + complete: '=', + incomplete: '', + total: now.syncAmount, + clear: true + } + ) + + now.upload() + + now.on('upload', ({ names, data }) => { + const amount = data.length + if (debug) { + console.log( + `> [debug] Uploaded: ${names.join(' ')} (${bytes(data.length)})` + ) + } + bar.tick(amount) + }) + + now.on('complete', () => complete({ syncCount })) + + now.on('error', err => { + error('Upload failed') + return stopDeployment(err) + }) + } else { + if (!quiet) { + console.log(`> Initializing…`) + } + + // Close http2 agent + now.close() + + // Show build logs + if (!quiet) { + if (deploymentType === 'static') { + console.log(`${chalk.cyan('> Deployment complete!')}`) + } else { + printLogs(now.host, token, currentTeam, user) + } + } + } + }) +} + +async function readMeta( + _path, + _deploymentName, + deploymentType, + _sessionAffinity +) { + try { + const meta = await readMetaData(_path, { + deploymentType, + deploymentName: _deploymentName, + quiet: true, + sessionAffinity: _sessionAffinity + }) + + if (!deploymentType) { + deploymentType = meta.type + + if (debug) { + console.log( + `> [debug] Detected \`deploymentType\` = \`${deploymentType}\`` + ) + } + } + + if (!_deploymentName) { + _deploymentName = meta.name + + if (debug) { + console.log( + `> [debug] Detected \`deploymentName\` = "${_deploymentName}"` + ) + } + } + + return { + meta, + deploymentName: _deploymentName, + deploymentType, + sessionAffinity: _sessionAffinity + } + } catch (err) { + if (isTTY && err.code === 'MULTIPLE_MANIFESTS') { + if (debug) { + console.log('> [debug] Multiple manifests found, disambiguating') + } + + console.log( + `> Two manifests found. Press [${chalk.bold( + 'n' + )}] to deploy or re-run with --flag` + ) + + deploymentType = await promptOptions([ + ['npm', `${chalk.bold('package.json')}\t${chalk.gray(' --npm')} `], + ['docker', `${chalk.bold('Dockerfile')}\t${chalk.gray('--docker')} `] + ]) + + if (debug) { + console.log( + `> [debug] Selected \`deploymentType\` = "${deploymentType}"` + ) + } + + return readMeta(_path, _deploymentName, deploymentType) + } + throw err + } +} + +function printLogs(host, token) { + // Log build + const logger = new Logger(host, token, { debug, quiet }) + + logger.on('error', async err => { + if (!quiet) { + if (err && err.type === 'BUILD_ERROR') { + error( + `The build step of your project failed. To retry, run ${cmd( + 'now --force' + )}.` + ) + } else { + error('Deployment failed') + } + } + + if (gitRepo && gitRepo.cleanup) { + // Delete temporary directory that contains repository + gitRepo.cleanup() + + if (debug) { + console.log(`> [debug] Removed temporary repo directory`) + } + } + + process.exit(1) + }) + + logger.on('close', async () => { + if (!quiet) { + console.log(`${chalk.cyan('> Deployment complete!')}`) + } + + if (gitRepo && gitRepo.cleanup) { + // Delete temporary directory that contains repository + gitRepo.cleanup() + + if (debug) { + console.log(`> [debug] Removed temporary repo directory`) + } + } + + process.exit(0) + }) +} + +module.exports = main diff --git a/src/providers/sh/index.js b/src/providers/sh/index.js new file mode 100644 index 0000000..20815a1 --- /dev/null +++ b/src/providers/sh/index.js @@ -0,0 +1,13 @@ +module.exports = { + title: 'now.sh', + subcommands: new Set(['help', 'login', 'deploy', 'ls']), + get deploy() { + return require('./deploy') + }, + get help() { + return require('./help') + }, + get login() { + return require('./login') + } +} diff --git a/src/providers/sh/legacy/agent.js b/src/providers/sh/legacy/agent.js new file mode 100644 index 0000000..06b31af --- /dev/null +++ b/src/providers/sh/legacy/agent.js @@ -0,0 +1,83 @@ +// Native +const { parse } = require('url') +const http = require('http') +const https = require('https') + +// Packages +const fetch = require('node-fetch') + +/** + * Returns a `fetch` version with a similar + * API to the browser's configured with a + * HTTP2 agent. + * + * It encodes `body` automatically as JSON. + * + * @param {String} host + * @return {Function} fetch + */ + +module.exports = class Agent { + constructor(url, { tls = true, debug } = {}) { + this._url = url + const parsed = parse(url) + this._protocol = parsed.protocol + this._debug = debug + if (tls) { + this._initAgent() + } + } + + _initAgent() { + const module = this._protocol === 'https:' ? https : http + this._agent = new module.Agent({ + keepAlive: true, + keepAliveMsecs: 10000, + maxSockets: 8 + }).on('error', err => this._onError(err, this._agent)) + } + + _onError(err, agent) { + if (this._debug) { + console.log(`> [debug] agent connection error ${err}\n${err.stack}`) + } + if (this._agent === agent) { + this._agent = null + } + } + + fetch(path, opts = {}) { + if (!this._agent) { + if (this._debug) { + console.log('> [debug] re-initializing agent') + } + this._initAgent() + } + + const { body } = opts + if (this._agent) { + opts.agent = this._agent + } + + if (body && typeof body === 'object' && typeof body.pipe !== 'function') { + opts.headers['Content-Type'] = 'application/json' + opts.body = JSON.stringify(body) + } + + if (opts.body && typeof body.pipe !== 'function') { + opts.headers['Content-Length'] = Buffer.byteLength(opts.body) + } + + return fetch(this._url + path, opts) + } + + close() { + if (this._debug) { + console.log('> [debug] closing agent') + } + + if (this._agent) { + this._agent.destroy() + } + } +} diff --git a/src/providers/sh/legacy/build-logger.js b/src/providers/sh/legacy/build-logger.js new file mode 100644 index 0000000..8479f08 --- /dev/null +++ b/src/providers/sh/legacy/build-logger.js @@ -0,0 +1,138 @@ +// Native +const EventEmitter = require('events') + +// Packages +const io = require('socket.io-client') +const chalk = require('chalk') + +const { compare, deserialize } = require('./logs') + +module.exports = class Logger extends EventEmitter { + constructor(host, token, { debug = false, quiet = false } = {}) { + super() + this.host = host + this.token = token + this.debug = debug + this.quiet = quiet + + // ReadyState + this.building = false + + this.socket = io(`https://io.now.sh/states?host=${host}&v=2`) + this.socket.once('error', this.onSocketError.bind(this)) + this.socket.on('auth', this.onAuth.bind(this)) + this.socket.on('state', this.onState.bind(this)) + this.socket.on('logs', this.onLog.bind(this)) + this.socket.on('backend', this.onComplete.bind(this)) + + // Log buffer + this.buf = [] + this.printed = new Set() + } + + onAuth(callback) { + if (this.debug) { + console.log('> [debug] authenticate') + } + callback(this.token) + } + + onState(state) { + // Console.log(state) + if (!state.id) { + console.error('> Deployment not found') + this.emit('error') + return + } + + if (state.error) { + this.emit('error', state) + return + } + + if (state.backend) { + this.onComplete() + return + } + + if (state.logs) { + state.logs.forEach(this.onLog, this) + } + } + + onLog(log) { + if (!this.building) { + if (!this.quiet) { + console.log('> Building') + } + this.building = true + } + + if (this.quiet) { + return + } + + log = deserialize(log) + + const timer = setTimeout(() => { + this.buf.sort((a, b) => compare(a.log, b.log)) + const idx = this.buf.findIndex(b => b.log.id === log.id) + 1 + for (const b of this.buf.slice(0, idx)) { + clearTimeout(b.timer) + this.printLog(b.log) + } + this.buf = this.buf.slice(idx) + }, 500) + + this.buf.push({ log, timer }) + } + + onComplete() { + this.socket.disconnect() + + if (this.building) { + this.building = false + } + + this.buf.sort((a, b) => compare(a.log, b.log)) + + // Flush all buffer + for (const b of this.buf) { + clearTimeout(b.timer) + this.printLog(b.log) + } + this.buf = [] + + this.emit('close') + } + + onSocketError(err) { + if (this.debug) { + console.log(`> [debug] Socket error ${err}\n${err.stack}`) + } + } + + printLog(log) { + if (this.printed.has(log.id)) return + + this.printed.add(log.id) + + const data = log.object ? JSON.stringify(log.object) : log.text + + if (log.type === 'command') { + console.log(`${chalk.gray('>')} ▲ ${data}`) + } else if (log.type === 'stderr') { + data.split('\n').forEach(v => { + if (v.length > 0) { + console.error(chalk.gray(`> ${v}`)) + } + }) + } else if (log.type === 'stdout') { + data.split('\n').forEach(v => { + if (v.length > 0) { + console.log(`${chalk.gray('>')} ${v}`) + } + }) + } + } +} diff --git a/src/providers/sh/legacy/check-path.js b/src/providers/sh/legacy/check-path.js new file mode 100644 index 0000000..8d71267 --- /dev/null +++ b/src/providers/sh/legacy/check-path.js @@ -0,0 +1,49 @@ +// Native +const os = require('os') +const path = require('path') + +const checkPath = async dir => { + if (!dir) { + return + } + + const home = os.homedir() + let location + + const paths = { + home, + desktop: path.join(home, 'Desktop'), + downloads: path.join(home, 'Downloads') + } + + for (const locationPath in paths) { + if (!{}.hasOwnProperty.call(paths, locationPath)) { + continue + } + + if (dir === paths[locationPath]) { + location = locationPath + } + } + + if (!location) { + return + } + + let locationName + + switch (location) { + case 'home': + locationName = 'user directory' + break + case 'downloads': + locationName = 'downloads directory' + break + default: + locationName = location + } + + throw new Error(`You're trying to deploy your ${locationName}.`) +} + +module.exports = checkPath diff --git a/src/providers/sh/legacy/error.js b/src/providers/sh/legacy/error.js new file mode 100644 index 0000000..98cf0aa --- /dev/null +++ b/src/providers/sh/legacy/error.js @@ -0,0 +1,91 @@ +// Packages +const ms = require('ms') +const chalk = require('chalk') + +const error = require('../../../util/output/error') +const info = require('../../../util/output/info') + +function handleError(err, { debug = false } = {}) { + // Coerce Strings to Error instances + if (typeof err === 'string') { + err = new Error(err) + } + + if (debug) { + console.log(`> [debug] handling error: ${err.stack}`) + } + + if (err.status === 403) { + console.log( + error( + 'Authentication error. Run `now -L` or `now --login` to log-in again.' + ) + ) + } else if (err.status === 429) { + if (err.retryAfter === 'never') { + console.log(error(err.message)) + } else if (err.retryAfter === null) { + console.log(error('Rate limit exceeded error. Please try later.')) + } else { + console.log( + error( + 'Rate limit exceeded error. Try again in ' + + ms(err.retryAfter * 1000, { long: true }) + + ', or upgrade your account by running ' + + `${chalk.gray('`')}${chalk.cyan('now upgrade')}${chalk.gray('`')}` + ) + ) + } + } else if (err.userError) { + console.log(error(err.message)) + } else if (err.status === 500) { + console.log(error('Unexpected server error. Please retry.')) + } else if (err.code === 'USER_ABORT') { + console.log(info('Aborted')) + } else { + console.log( + error(`Unexpected error. Please try again later. (${err.message})`) + ) + } +} + +async function responseError(res) { + let message + let userError + + if (res.status >= 400 && res.status < 500) { + let body + + try { + body = await res.json() + } catch (err) { + body = {} + } + + // Some APIs wrongly return `err` instead of `error` + message = (body.error || body.err || {}).message + userError = true + } else { + userError = false + } + + const err = new Error(message || 'Response error') + err.status = res.status + err.userError = userError + + if (res.status === 429) { + const retryAfter = res.headers.get('Retry-After') + + if (retryAfter) { + err.retryAfter = parseInt(retryAfter, 10) + } + } + + return err +} + +module.exports = { + handleError, + responseError, + error +} diff --git a/src/providers/sh/legacy/get-files.js b/src/providers/sh/legacy/get-files.js new file mode 100644 index 0000000..0147889 --- /dev/null +++ b/src/providers/sh/legacy/get-files.js @@ -0,0 +1,385 @@ +// Native +const { resolve } = require('path') + +// Packages +const flatten = require('arr-flatten') +const unique = require('array-unique') +const ignore = require('ignore') +const _glob = require('glob') +const { stat, readdir, readFile } = require('fs-extra') + +// Ours +const IGNORED = require('./ignored') + +const glob = async function(pattern, options) { + return new Promise((resolve, reject) => { + _glob(pattern, options, (error, files) => { + if (error) { + reject(error) + } else { + resolve(files) + } + }) + }) +} + +/** + * Remove leading `./` from the beginning of ignores + * because our parser doesn't like them :| + */ + +const clearRelative = function(str) { + return str.replace(/(\n|^)\.\//g, '$1') +} + +/** + * Returns the contents of a file if it exists. + * + * @return {String} results or `''` + */ + +const maybeRead = async function(path, default_ = '') { + try { + return await readFile(path, 'utf8') + } catch (err) { + return default_ + } +} + +/** + * Transform relative paths into absolutes, + * and maintains absolutes as such. + * + * @param {String} maybe relative path + * @param {String} parent full path + */ + +const asAbsolute = function(path, parent) { + if (path[0] === '/') { + return path + } + + return resolve(parent, path) +} + +/** + * Returns a list of files in the given + * directory that are subject to be + * synchronized for static deployments. + * + * @param {String} full path to directory + * @param {Object} options: + * - `limit` {Number|null} byte limit + * - `debug` {Boolean} warn upon ignore + * @return {Array} comprehensive list of paths to sync + */ + +async function staticFiles( + path, + nowConfig = {}, + { limit = null, hasNowJson = false, debug = false } = {} +) { + const whitelist = nowConfig.files + + // The package.json `files` whitelist still + // honors ignores: https://docs.npmjs.com/files/package.json#files + const search_ = whitelist || ['.'] + // Convert all filenames into absolute paths + const search = Array.prototype.concat.apply( + [], + await Promise.all( + search_.map(file => glob(file, { cwd: path, absolute: true, dot: true })) + ) + ) + + // Compile list of ignored patterns and files + const gitIgnore = await maybeRead(resolve(path, '.gitignore')) + + const filter = ignore() + .add(IGNORED + '\n' + clearRelative(gitIgnore)) + .createFilter() + + const prefixLength = path.length + 1 + + // The package.json `files` whitelist still + // honors npmignores: https://docs.npmjs.com/files/package.json#files + // but we don't ignore if the user is explicitly listing files + // under the now namespace, or using files in combination with gitignore + const accepts = file => { + const relativePath = file.substr(prefixLength) + + if (relativePath === '') { + return true + } + + const accepted = filter(relativePath) + if (!accepted && debug) { + console.log('> [debug] ignoring "%s"', file) + } + return accepted + } + + // Locate files + if (debug) { + console.time(`> [debug] locating files ${path}`) + } + + const files = await explode(search, { + accepts, + limit, + debug + }) + + if (debug) { + console.timeEnd(`> [debug] locating files ${path}`) + } + + if (hasNowJson) { + files.push(asAbsolute('now.json', path)) + } + + // Get files + return unique(files) +} + +/** + * Returns a list of files in the given + * directory that are subject to be + * synchronized for npm. + * + * @param {String} full path to directory + * @param {String} contents of `package.json` to avoid lookup + * @param {Object} options: + * - `limit` {Number|null} byte limit + * - `debug` {Boolean} warn upon ignore + * @return {Array} comprehensive list of paths to sync + */ + +async function npm( + path, + pkg = {}, + nowConfig = {}, + { limit = null, hasNowJson = false, debug = false } = {} +) { + const whitelist = nowConfig.files || pkg.files || (pkg.now && pkg.now.files) + + // The package.json `files` whitelist still + // honors ignores: https://docs.npmjs.com/files/package.json#files + const search_ = whitelist || ['.'] + // Convert all filenames into absolute paths + const search = Array.prototype.concat.apply( + [], + await Promise.all( + search_.map(file => glob(file, { cwd: path, absolute: true, dot: true })) + ) + ) + + // Compile list of ignored patterns and files + const npmIgnore = await maybeRead(resolve(path, '.npmignore'), null) + const gitIgnore = + npmIgnore === null ? await maybeRead(resolve(path, '.gitignore')) : null + + const filter = ignore() + .add( + IGNORED + '\n' + clearRelative(npmIgnore === null ? gitIgnore : npmIgnore) + ) + .createFilter() + + const prefixLength = path.length + 1 + + // The package.json `files` whitelist still + // honors npmignores: https://docs.npmjs.com/files/package.json#files + // but we don't ignore if the user is explicitly listing files + // under the now namespace, or using files in combination with gitignore + const overrideIgnores = + (pkg.now && pkg.now.files) || + nowConfig.files || + (gitIgnore !== null && pkg.files) + const accepts = overrideIgnores + ? () => true + : file => { + const relativePath = file.substr(prefixLength) + + if (relativePath === '') { + return true + } + + const accepted = filter(relativePath) + if (!accepted && debug) { + console.log('> [debug] ignoring "%s"', file) + } + return accepted + } + + // Locate files + if (debug) { + console.time(`> [debug] locating files ${path}`) + } + + const files = await explode(search, { + accepts, + limit, + debug + }) + + if (debug) { + console.timeEnd(`> [debug] locating files ${path}`) + } + + // Always include manifest as npm does not allow ignoring it + // source: https://docs.npmjs.com/files/package.json#files + files.push(asAbsolute('package.json', path)) + + if (hasNowJson) { + files.push(asAbsolute('now.json', path)) + } + + // Get files + return unique(files) +} + +/** + * Returns a list of files in the given + * directory that are subject to be + * sent to docker as build context. + * + * @param {String} full path to directory + * @param {String} contents of `Dockerfile` + * @param {Object} options: + * - `limit` {Number|null} byte limit + * - `debug` {Boolean} warn upon ignore + * @return {Array} comprehensive list of paths to sync + */ + +async function docker( + path, + nowConfig = {}, + { limit = null, hasNowJson = false, debug = false } = {} +) { + const whitelist = nowConfig.files + + // Base search path + // the now.json `files` whitelist still + // honors ignores: https://docs.npmjs.com/files/package.json#files + const search_ = whitelist || ['.'] + + // Convert all filenames into absolute paths + const search = search_.map(file => asAbsolute(file, path)) + + // Compile list of ignored patterns and files + const dockerIgnore = await maybeRead(resolve(path, '.dockerignore'), null) + + const filter = ignore() + .add( + IGNORED + + '\n' + + clearRelative( + dockerIgnore === null + ? await maybeRead(resolve(path, '.gitignore')) + : dockerIgnore + ) + ) + .createFilter() + + const prefixLength = path.length + 1 + const accepts = function(file) { + const relativePath = file.substr(prefixLength) + + if (relativePath === '') { + return true + } + + const accepted = filter(relativePath) + if (!accepted && debug) { + console.log('> [debug] ignoring "%s"', file) + } + return accepted + } + + // Locate files + if (debug) { + console.time(`> [debug] locating files ${path}`) + } + + const files = await explode(search, { accepts, limit, debug }) + + if (debug) { + console.timeEnd(`> [debug] locating files ${path}`) + } + + // Always include manifest as npm does not allow ignoring it + // source: https://docs.npmjs.com/files/package.json#files + files.push(asAbsolute('Dockerfile', path)) + + if (hasNowJson) { + files.push(asAbsolute('now.json', path)) + } + + // Get files + return unique(files) +} + +/** + * Explodes directories into a full list of files. + * Eg: + * in: ['/a.js', '/b'] + * out: ['/a.js', '/b/c.js', '/b/d.js'] + * + * @param {Array} of {String}s representing paths + * @param {Array} of ignored {String}s. + * @param {Object} options: + * - `limit` {Number|null} byte limit + * - `debug` {Boolean} warn upon ignore + * @return {Array} of {String}s of full paths + */ + +async function explode(paths, { accepts, debug }) { + const list = async file => { + let path = file + let s + + if (!accepts(file)) { + return null + } + + try { + s = await stat(path) + } catch (e) { + // In case the file comes from `files` + // and it wasn't specified with `.js` by the user + path = file + '.js' + + try { + s = await stat(path) + } catch (e2) { + if (debug) { + console.log('> [debug] ignoring invalid file "%s"', file) + } + return null + } + } + + if (s.isDirectory()) { + const all = await readdir(file) + /* eslint-disable no-use-before-define */ + return many(all.map(subdir => asAbsolute(subdir, file))) + /* eslint-enable no-use-before-define */ + } else if (!s.isFile()) { + if (debug) { + console.log('> [debug] ignoring special file "%s"', file) + } + return null + } + + return path + } + + const many = all => Promise.all(all.map(file => list(file))) + return flatten(await many(paths)).filter(v => v !== null) +} + +module.exports = { + npm, + docker, + staticFiles +} diff --git a/src/providers/sh/legacy/git.js b/src/providers/sh/legacy/git.js new file mode 100644 index 0000000..2624569 --- /dev/null +++ b/src/providers/sh/legacy/git.js @@ -0,0 +1,221 @@ +// Native +const path = require('path') +const url = require('url') +const childProcess = require('child_process') + +// Packages +const fs = require('fs-extra') +const download = require('download') +const tmp = require('tmp-promise') +const isURL = require('is-url') + +const cloneRepo = (parts, tmpDir, { ssh }) => + new Promise((resolve, reject) => { + let host + + switch (parts.type) { + case 'GitLab': + host = `gitlab.com` + break + case 'Bitbucket': + host = `bitbucket.org` + break + default: + host = `github.com` + } + + const url = ssh + ? `git@${host}:${parts.main}` + : `https://${host}/${parts.main}` + + const ref = parts.ref || (parts.type === 'Bitbucket' ? 'default' : 'master') + const cmd = `git clone ${url} --single-branch ${ref}` + + childProcess.exec(cmd, { cwd: tmpDir.path }, (err, stdout) => { + if (err) { + reject(err) + } + + resolve(stdout) + }) + }) + +const renameRepoDir = async (pathParts, tmpDir) => { + const tmpContents = await fs.readdir(tmpDir.path) + + const oldTemp = path.join(tmpDir.path, tmpContents[0]) + const newTemp = path.join(tmpDir.path, pathParts.main.replace('/', '-')) + + await fs.rename(oldTemp, newTemp) + tmpDir.path = newTemp + + return tmpDir +} + +const capitalizePlatform = name => { + const names = { + github: 'GitHub', + gitlab: 'GitLab', + bitbucket: 'Bitbucket' + } + + return names[name] +} + +const splittedURL = fullURL => { + const parsedURL = url.parse(fullURL) + const pathParts = parsedURL.path.split('/') + + pathParts.shift() + + // Set path to repo... + const main = pathParts[0] + '/' + pathParts[1] + + // ...and then remove it from the parts + pathParts.splice(0, 2) + + // Assign Git reference + let ref = pathParts.length >= 2 ? pathParts[1] : '' + + // Firstly be sure that we haven know the ref type + if (pathParts[0]) { + // Then shorten the SHA of the commit + if (pathParts[0] === 'commit' || pathParts[0] === 'commits') { + ref = ref.substring(0, 7) + } + } + + // We're deploying master by default, + // so there's no need to indicate it explicitly + if (ref === 'master') { + ref = '' + } + + return { + main, + ref, + type: capitalizePlatform(parsedURL.host.split('.')[0]) + } +} + +const gitPathParts = main => { + let ref = '' + + if (isURL(main)) { + return splittedURL(main) + } + + if (main.split('/')[1].includes('#')) { + const parts = main.split('#') + + ref = parts[1] + main = parts[0] + } + + return { + main, + ref, + type: capitalizePlatform('github') + } +} + +const downloadRepo = async repoPath => { + const pathParts = gitPathParts(repoPath) + + const tmpDir = await tmp.dir({ + // We'll remove it manually once deployment is done + keep: true, + // Recursively remove directory when calling respective method + unsafeCleanup: true + }) + + let gitInstalled = true + + try { + await cloneRepo(pathParts, tmpDir) + } catch (err) { + try { + await cloneRepo(pathParts, tmpDir, { ssh: true }) + } catch (err) { + gitInstalled = false + } + } + + if (gitInstalled) { + const renaming = await renameRepoDir(pathParts, tmpDir) + return renaming + } + + let url + + switch (pathParts.type) { + case 'GitLab': { + const ref = pathParts.ref ? `?ref=${pathParts.ref}` : '' + url = `https://gitlab.com/${pathParts.main}/repository/archive.tar` + ref + break + } + case 'Bitbucket': + url = `https://bitbucket.org/${pathParts.main}/get/${pathParts.ref || + 'default'}.zip` + break + default: + url = `https://api.github.com/repos/${pathParts.main}/tarball/${pathParts.ref}` + } + + try { + await download(url, tmpDir.path, { + extract: true + }) + } catch (err) { + tmpDir.cleanup() + return false + } + + const renaming = await renameRepoDir(pathParts, tmpDir) + return renaming +} + +const isRepoPath = path => { + if (!path) { + return false + } + + const allowedHosts = ['github.com', 'gitlab.com', 'bitbucket.org'] + + if (isURL(path)) { + const urlParts = url.parse(path) + const slashSplitted = urlParts.path.split('/').filter(n => n) + const notBare = slashSplitted.length >= 2 + + if (allowedHosts.includes(urlParts.host) && notBare) { + return true + } + + const err = new Error(`Host "${urlParts.host}" is unsupported.`) + err.code = 'INVALID_URL' + err.userError = true + throw err + } + + return /[^\s\\]\/[^\s\\]/g.test(path) +} + +const fromGit = async (path, debug) => { + let tmpDir = false + + try { + tmpDir = await downloadRepo(path) + } catch (err) { + if (debug) { + console.log(`Could not download "${path}" repo from GitHub`) + } + } + + return tmpDir +} + +module.exports = { + gitPathParts, + isRepoPath, + fromGit +} diff --git a/src/providers/sh/legacy/hash.js b/src/providers/sh/legacy/hash.js new file mode 100644 index 0000000..b5a7d63 --- /dev/null +++ b/src/providers/sh/legacy/hash.js @@ -0,0 +1,44 @@ +// Native +const { createHash } = require('crypto') + +// Packages +const { readFile } = require('fs-extra') + +/** + * Computes hashes for the contents of each file given. + * + * @param {Array} of {String} full paths + * @return {Map} + */ + +async function hashes(files) { + const map = new Map() + + await Promise.all( + files.map(async name => { + const data = await readFile(name) + + const h = hash(data) + const entry = map.get(h) + if (entry) { + entry.names.push(name) + } else { + map.set(hash(data), { names: [name], data }) + } + }) + ) + return map +} + +/** + * Computes a hash for the given buf. + * + * @param {Buffer} file data + * @return {String} hex digest + */ + +function hash(buf) { + return createHash('sha1').update(buf).digest('hex') +} + +module.exports = hashes diff --git a/src/providers/sh/legacy/ignored.js b/src/providers/sh/legacy/ignored.js new file mode 100644 index 0000000..e71a28f --- /dev/null +++ b/src/providers/sh/legacy/ignored.js @@ -0,0 +1,17 @@ +// Base `.gitignore` to which we add entries +// supplied by the user +module.exports = `.hg +.git +.gitmodules +.svn +.npmignore +.dockerignore +.gitignore +.*.swp +.DS_Store +.wafpicke-* +.lock-wscript +npm-debug.log +config.gypi +node_modules +CVS` diff --git a/src/providers/sh/legacy/logs.js b/src/providers/sh/legacy/logs.js new file mode 100644 index 0000000..a636231 --- /dev/null +++ b/src/providers/sh/legacy/logs.js @@ -0,0 +1,14 @@ +exports.compare = function(a, b) { + return ( + a.serial.localeCompare(b.serial) || + // For the case serials are a same value on old logs + a.created.getTime() - b.created.getTime() + ) +} + +exports.deserialize = function(log) { + return Object.assign({}, log, { + date: new Date(log.date), + created: new Date(log.created) + }) +} diff --git a/src/providers/sh/legacy/now.js b/src/providers/sh/legacy/now.js new file mode 100644 index 0000000..8f91d6f --- /dev/null +++ b/src/providers/sh/legacy/now.js @@ -0,0 +1,1031 @@ +// Native +const { homedir } = require('os') +const { resolve: resolvePath } = require('path') +const EventEmitter = require('events') +const qs = require('querystring') +const { parse: parseUrl } = require('url') + +// Packages +const fetch = require('node-fetch') +const bytes = require('bytes') +const chalk = require('chalk') +const resumer = require('resumer') +const retry = require('async-retry') +const splitArray = require('split-array') +const { parse: parseIni } = require('ini') +const { readFile, stat, lstat } = require('fs-extra') +const ms = require('ms') + +// Utilities +const { + staticFiles: getFiles, + npm: getNpmFiles, + docker: getDockerFiles +} = require('./get-files') +const ua = require('../util/ua') +const hash = require('./hash') +const Agent = require('./agent') +const toHost = require('./to-host') +const { responseError } = require('./error') + +// How many concurrent HTTP/2 stream uploads +const MAX_CONCURRENT = 10 + +// Check if running windows +const IS_WIN = process.platform.startsWith('win') +const SEP = IS_WIN ? '\\' : '/' + +module.exports = class Now extends EventEmitter { + constructor({ apiUrl, token, currentTeam, forceNew = false, debug = false }) { + super() + this._token = token + this._debug = debug + this._forceNew = forceNew + this._agent = new Agent(apiUrl, { debug }) + this._onRetry = this._onRetry.bind(this) + this.currentTeam = currentTeam + } + + async create( + path, + { + wantsPublic, + quiet = false, + env = {}, + followSymlinks = true, + forceNew = false, + forceSync = false, + forwardNpm = false, + + // From readMetaData + name, + description, + type = 'npm', + pkg = {}, + nowConfig = {}, + hasNowJson = false, + sessionAffinity = 'ip' + } + ) { + this._path = path + + let files + let engines + + if (this._debug) { + console.time('> [debug] Getting files') + } + + const opts = { debug: this._debug, hasNowJson } + if (type === 'npm') { + files = await getNpmFiles(path, pkg, nowConfig, opts) + + // A `start` or `now-start` npm script, or a `server.js` file + // in the root directory of the deployment are required + if (!hasNpmStart(pkg) && !hasFile(path, files, 'server.js')) { + const err = new Error( + 'Missing `start` (or `now-start`) script in `package.json`. ' + + 'See: https://docs.npmjs.com/cli/start.' + ) + err.userError = true + throw err + } + + engines = nowConfig.engines || pkg.engines + forwardNpm = forwardNpm || nowConfig.forwardNpm + } else if (type === 'static') { + files = await getFiles(path, nowConfig, opts) + } else if (type === 'docker') { + files = await getDockerFiles(path, nowConfig, opts) + } + + if (this._debug) { + console.timeEnd('> [debug] Getting files') + } + + // Read `registry.npmjs.org` authToken from .npmrc + let authToken + if (type === 'npm' && forwardNpm) { + authToken = + (await readAuthToken(path)) || (await readAuthToken(homedir())) + } + + if (this._debug) { + console.time('> [debug] Computing hashes') + } + + const pkgDetails = Object.assign({ name }, pkg) + const hashes = await hash(files, pkgDetails) + + if (this._debug) { + console.timeEnd('> [debug] Computing hashes') + } + + this._files = hashes + + const deployment = await this.retry(async bail => { + if (this._debug) { + console.time('> [debug] /now/create') + } + + // Flatten the array to contain files to sync where each nested input + // array has a group of files with the same sha but different path + const files = await Promise.all( + Array.prototype.concat.apply( + [], + await Promise.all( + Array.from(this._files).map(async ([sha, { data, names }]) => { + const statFn = followSymlinks ? stat : lstat + + return names.map(async name => { + const getMode = async () => { + const st = await statFn(name) + return st.mode + } + + const mode = await getMode() + + return { + sha, + size: data.length, + file: toRelative(name, this._path), + mode + } + }) + }) + ) + ) + ) + + const res = await this._fetch('/now/create', { + method: 'POST', + body: { + env, + public: wantsPublic || nowConfig.public, + forceNew, + forceSync, + name, + description, + deploymentType: type, + registryAuthToken: authToken, + files, + engines, + sessionAffinity + } + }) + + if (this._debug) { + console.timeEnd('> [debug] /now/create') + } + + // No retry on 4xx + let body + try { + body = await res.json() + } catch (err) { + throw new Error('Unexpected response') + } + + if (res.status === 429) { + let msg = `You reached your 20 deployments limit in the OSS plan.\n` + msg += `${chalk.gray('>')} Please run ${chalk.gray('`')}${chalk.cyan( + 'now upgrade' + )}${chalk.gray('`')} to proceed` + const err = new Error(msg) + err.status = res.status + err.retryAfter = 'never' + return bail(err) + } else if (res.status >= 400 && res.status < 500) { + const err = new Error(body.error.message) + err.userError = true + return bail(err) + } else if (res.status !== 200) { + throw new Error(body.error.message) + } + + return body + }) + + // We report about files whose sizes are too big + let missingVersion = false + if (deployment.warnings) { + let sizeExceeded = 0 + deployment.warnings.forEach(warning => { + if (warning.reason === 'size_limit_exceeded') { + const { sha, limit } = warning + const n = hashes.get(sha).names.pop() + console.error( + '> \u001B[31mWarning!\u001B[39m Skipping file %s (size exceeded %s)', + n, + bytes(limit) + ) + hashes.get(sha).names.unshift(n) // Move name (hack, if duplicate matches we report them in order) + sizeExceeded++ + } else if (warning.reason === 'node_version_not_found') { + const { wanted, used } = warning + console.error( + '> \u001B[31mWarning!\u001B[39m Requested node version %s is not available', + wanted, + used + ) + missingVersion = true + } + }) + + if (sizeExceeded) { + console.error( + `> \u001B[31mWarning!\u001B[39m ${sizeExceeded} of the files ` + + 'exceeded the limit for your plan.\n' + + `> Please run ${chalk.gray('`')}${chalk.cyan( + 'now upgrade' + )}${chalk.gray('`')} to upgrade.` + ) + } + } + + if (!quiet && type === 'npm' && deployment.nodeVersion) { + if (engines && engines.node) { + if (missingVersion) { + console.log( + `> Using Node.js ${chalk.bold(deployment.nodeVersion)} (default)` + ) + } else { + console.log( + `> Using Node.js ${chalk.bold( + deployment.nodeVersion + )} (requested: ${chalk.dim(`\`${engines.node}\``)})` + ) + } + } else { + console.log( + `> Using Node.js ${chalk.bold(deployment.nodeVersion)} (default)` + ) + } + } + + this._id = deployment.deploymentId + this._host = deployment.url + this._missing = deployment.missing || [] + this._fileCount = files.length + + return this._url + } + + upload() { + const parts = splitArray(this._missing, MAX_CONCURRENT) + + if (this._debug) { + console.log( + '> [debug] Will upload ' + + `${this._missing.length} files in ${parts.length} ` + + `steps of ${MAX_CONCURRENT} uploads.` + ) + } + + const uploadChunk = () => { + Promise.all( + parts.shift().map(sha => + retry( + async (bail, attempt) => { + const file = this._files.get(sha) + const { data, names } = file + + if (this._debug) { + console.time(`> [debug] /sync #${attempt} ${names.join(' ')}`) + } + + const stream = resumer().queue(data).end() + + const res = await this._fetch('/now/sync', { + method: 'POST', + headers: { + 'Content-Type': 'application/octet-stream', + 'Content-Length': data.length, + 'x-now-deployment-id': this._id, + 'x-now-sha': sha, + 'x-now-file': names + .map(name => { + return toRelative(encodeURIComponent(name), this._path) + }) + .join(','), + 'x-now-size': data.length + }, + body: stream + }) + + if (this._debug) { + console.timeEnd( + `> [debug] /sync #${attempt} ${names.join(' ')}` + ) + } + + // No retry on 4xx + if ( + res.status !== 200 && + (res.status >= 400 || res.status < 500) + ) { + if (this._debug) { + console.log( + '> [debug] bailing on creating due to %s', + res.status + ) + } + + return bail(await responseError(res)) + } + + this.emit('upload', file) + }, + { retries: 3, randomize: true, onRetry: this._onRetry } + ) + ) + ) + .then(() => (parts.length ? uploadChunk() : this.emit('complete'))) + .catch(err => this.emit('error', err)) + } + + uploadChunk() + } + + async listSecrets() { + return this.retry(async (bail, attempt) => { + if (this._debug) { + console.time(`> [debug] #${attempt} GET /secrets`) + } + + const res = await this._fetch('/now/secrets') + + if (this._debug) { + console.timeEnd(`> [debug] #${attempt} GET /secrets`) + } + + const body = await res.json() + return body.secrets + }) + } + + async list(app) { + const query = app ? `?app=${encodeURIComponent(app)}` : '' + + const { deployments } = await this.retry( + async bail => { + if (this._debug) { + console.time('> [debug] /list') + } + + const res = await this._fetch('/now/list' + query) + + if (this._debug) { + console.timeEnd('> [debug] /list') + } + + // No retry on 4xx + if (res.status >= 400 && res.status < 500) { + if (this._debug) { + console.log('> [debug] bailing on listing due to %s', res.status) + } + return bail(await responseError(res)) + } + + if (res.status !== 200) { + throw new Error('Fetching deployment url failed') + } + + return res.json() + }, + { retries: 3, minTimeout: 2500, onRetry: this._onRetry } + ) + + return deployments + } + + async listInstances(deploymentId) { + const { instances } = await this.retry( + async bail => { + if (this._debug) { + console.time(`> [debug] /deployments/${deploymentId}/instances`) + } + + const res = await this._fetch( + `/now/deployments/${deploymentId}/instances` + ) + + if (this._debug) { + console.timeEnd(`> [debug] /deployments/${deploymentId}/instances`) + } + + // No retry on 4xx + if (res.status >= 400 && res.status < 500) { + if (this._debug) { + console.log('> [debug] bailing on listing due to %s', res.status) + } + return bail(await responseError(res)) + } + + if (res.status !== 200) { + throw new Error('Fetching instances list failed') + } + + return res.json() + }, + { retries: 3, minTimeout: 2500, onRetry: this._onRetry } + ) + + return instances + } + + async findDeployment(deployment) { + const list = await this.list() + + let key + let val + + if (/\./.test(deployment)) { + val = toHost(deployment) + key = 'url' + } else { + val = deployment + key = 'uid' + } + + const depl = list.find(d => { + if (d[key] === val) { + if (this._debug) { + console.log(`> [debug] matched deployment ${d.uid} by ${key} ${val}`) + } + + return true + } + + // Match prefix + if (`${val}.now.sh` === d.url) { + if (this._debug) { + console.log(`> [debug] matched deployment ${d.uid} by url ${d.url}`) + } + + return true + } + + return false + }) + + return depl + } + + async logs( + deploymentIdOrURL, + { instanceId, types, limit, query, since, until } = {} + ) { + const q = qs.stringify({ + instanceId, + types: types.join(','), + limit, + q: query, + since, + until + }) + + const { logs } = await this.retry( + async bail => { + if (this._debug) { + console.time('> [debug] /logs') + } + + const url = `/now/deployments/${encodeURIComponent( + deploymentIdOrURL + )}/logs?${q}` + const res = await this._fetch(url) + + if (this._debug) { + console.timeEnd('> [debug] /logs') + } + + // No retry on 4xx + if (res.status >= 400 && res.status < 500) { + if (this._debug) { + console.log( + '> [debug] bailing on printing logs due to %s', + res.status + ) + } + + return bail(await responseError(res)) + } + + if (res.status !== 200) { + throw new Error('Fetching deployment logs failed') + } + + return res.json() + }, + { + retries: 3, + minTimeout: 2500, + onRetry: this._onRetry + } + ) + + return logs + } + + async listAliases(deploymentId) { + return this.retry(async bail => { + const res = await this._fetch( + deploymentId + ? `/now/deployments/${deploymentId}/aliases` + : '/now/aliases' + ) + + if (res.status >= 400 && res.status < 500) { + if (this._debug) { + console.log('> [debug] bailing on get domain due to %s', res.status) + } + return bail(await responseError(res)) + } + + if (res.status !== 200) { + throw new Error('API error getting aliases') + } + + const body = await res.json() + return body.aliases + }) + } + + async last(app) { + const deployments = await this.list(app) + + const last = deployments + .sort((a, b) => { + return b.created - a.created + }) + .shift() + + if (!last) { + const e = Error(`No deployments found for "${app}"`) + e.userError = true + throw e + } + + return last + } + + async listDomains() { + return this.retry(async (bail, attempt) => { + if (this._debug) { + console.time(`> [debug] #${attempt} GET /domains`) + } + + const res = await this._fetch('/domains') + + if (this._debug) { + console.timeEnd(`> [debug] #${attempt} GET /domains`) + } + + if (res.status >= 400 && res.status < 500) { + if (this._debug) { + console.log('> [debug] bailing on get domain due to %s', res.status) + } + return bail(await responseError(res)) + } + + if (res.status !== 200) { + console.log(res.status, await res.json()) + throw new Error('API error getting domains') + } + + const body = await res.json() + return body.domains + }) + } + + async getDomain(domain) { + return this.retry(async (bail, attempt) => { + if (this._debug) { + console.time(`> [debug] #${attempt} GET /domains/${domain}`) + } + + const res = await this._fetch(`/domains/${domain}`) + + if (res.status >= 400 && res.status < 500) { + if (this._debug) { + console.log('> [debug] bailing on get domain due to %s', res.status) + } + return bail(await responseError(res)) + } + + if (res.status !== 200) { + throw new Error('API error getting domain name') + } + + if (this._debug) { + console.timeEnd(`> [debug] #${attempt} GET /domains/${domain}`) + } + + return res.json() + }) + } + + getNameservers(domain) { + return new Promise((resolve, reject) => { + let fallback = false + + this.retry(async (bail, attempt) => { + if (this._debug) { + console.time( + `> [debug] #${attempt} GET /whois-ns${fallback ? '-fallback' : ''}` + ) + } + + const res = await this._fetch( + `/whois-ns${fallback ? '-fallback' : ''}?domain=${encodeURIComponent( + domain + )}` + ) + + if (this._debug) { + console.timeEnd( + `> [debug] #${attempt} GET /whois-ns${fallback ? '-fallback' : ''}` + ) + } + + const body = await res.json() + + if (res.status === 200) { + if ( + (!body.nameservers || body.nameservers.length === 0) && + !fallback + ) { + // If the nameservers are `null` it's likely + // that our whois service failed to parse it + fallback = true + throw new Error('Invalid whois response') + } + + return body + } + + if (attempt > 1) { + fallback = true + } + + throw new Error(`Whois error (${res.status}): ${body.error.message}`) + }) + .then(body => { + body.nameservers = body.nameservers.filter(ns => { + // Temporary hack: + // sometimes we get a response that looks like: + // ['ns', 'ns', '', ''] + // so we filter the empty ones + return ns.length + }) + resolve(body) + }) + .catch(err => { + reject(err) + }) + }) + } + + // _ensures_ the domain is setup (idempotent) + setupDomain(name, { isExternal } = {}) { + return this.retry(async (bail, attempt) => { + if (this._debug) { + console.time(`> [debug] #${attempt} POST /domains`) + } + + const res = await this._fetch('/domains', { + method: 'POST', + body: { name, isExternal: Boolean(isExternal) } + }) + + if (this._debug) { + console.timeEnd(`> [debug] #${attempt} POST /domains`) + } + + const body = await res.json() + + if (res.status === 403) { + const code = body.error.code + let err + + if (code === 'custom_domain_needs_upgrade') { + err = new Error( + `Custom domains are only enabled for premium accounts. Please upgrade at ${chalk.underline( + 'https://zeit.co/account' + )}.` + ) + } else { + err = new Error(`Not authorized to access domain ${name}`) + } + + err.userError = true + return bail(err) + } else if (res.status === 409) { + // Domain already exists + if (this._debug) { + console.log('> [debug] Domain already exists (noop)') + } + + return { uid: body.error.uid, code: body.error.code } + } else if ( + res.status === 401 && + body.error && + body.error.code === 'verification_failed' + ) { + throw new Error(body.error.message) + } else if (res.status !== 200) { + throw new Error(body.error.message) + } + + return body + }) + } + + createCert(domain, { renew } = {}) { + return this.retry( + async (bail, attempt) => { + if (this._debug) { + console.time(`> [debug] /now/certs #${attempt}`) + } + + const res = await this._fetch('/now/certs', { + method: 'POST', + body: { + domains: [domain], + renew + } + }) + + if (res.status === 304) { + console.log('> Certificate already issued.') + return + } + + const body = await res.json() + + if (this._debug) { + console.timeEnd(`> [debug] /now/certs #${attempt}`) + } + + if (body.error) { + const { code } = body.error + + if (code === 'verification_failed') { + const err = new Error( + 'The certificate issuer failed to verify ownership of the domain. ' + + 'This likely has to do with DNS propagation and caching issues. Please retry later!' + ) + err.userError = true + // Retry + throw err + } else if (code === 'rate_limited') { + const err = new Error(body.error.message) + err.userError = true + // Dont retry + return bail(err) + } + + throw new Error(body.error.message) + } + + if (res.status !== 200 && res.status !== 304) { + throw new Error('Unhandled error') + } + return body + }, + { retries: 3, minTimeout: 30000, maxTimeout: 90000 } + ) + } + + deleteCert(domain) { + return this.retry( + async (bail, attempt) => { + if (this._debug) { + console.time(`> [debug] /now/certs #${attempt}`) + } + + const res = await this._fetch(`/now/certs/${domain}`, { + method: 'DELETE' + }) + + if (res.status !== 200) { + const err = new Error(res.body.error.message) + err.userError = false + + if (res.status === 400 || res.status === 404) { + return bail(err) + } + + throw err + } + }, + { retries: 3 } + ) + } + + async remove(deploymentId, { hard }) { + const data = { deploymentId, hard } + + await this.retry(async bail => { + if (this._debug) { + console.time('> [debug] /remove') + } + + const res = await this._fetch('/now/remove', { + method: 'DELETE', + body: data + }) + + if (this._debug) { + console.timeEnd('> [debug] /remove') + } + + // No retry on 4xx + if (res.status >= 400 && res.status < 500) { + if (this._debug) { + console.log('> [debug] bailing on removal due to %s', res.status) + } + return bail(await responseError(res)) + } + + if (res.status !== 200) { + throw new Error('Removing deployment failed') + } + }) + + return true + } + + retry(fn, { retries = 3, maxTimeout = Infinity } = {}) { + return retry(fn, { + retries, + maxTimeout, + onRetry: this._onRetry + }) + } + + _onRetry(err) { + if (this._debug) { + console.log(`> [debug] Retrying: ${err}\n${err.stack}`) + } + } + + close() { + this._agent.close() + } + + get id() { + return this._id + } + + get url() { + return `https://${this._host}` + } + + get fileCount() { + return this._fileCount + } + + get host() { + return this._host + } + + get syncAmount() { + if (!this._syncAmount) { + this._syncAmount = this._missing + .map(sha => this._files.get(sha).data.length) + .reduce((a, b) => a + b, 0) + } + return this._syncAmount + } + + get syncFileCount() { + return this._missing.length + } + + _fetch(_url, opts = {}) { + if (opts.useCurrentTeam !== false && this.currentTeam) { + const parsedUrl = parseUrl(_url, true) + const query = parsedUrl.query + + query.teamId = this.currentTeam.id + _url = `${parsedUrl.pathname}?${qs.encode(query)}` + delete opts.useCurrentTeam + } + + opts.headers = opts.headers || {} + opts.headers.authorization = `Bearer ${this._token}` + opts.headers['user-agent'] = ua + return this._agent.fetch(_url, opts) + } + + setScale(nameOrId, scale) { + return this.retry( + async (bail, attempt) => { + if (this._debug) { + console.time( + `> [debug] #${attempt} POST /deployments/${nameOrId}/instances` + ) + } + + const res = await this._fetch( + `/now/deployments/${nameOrId}/instances`, + { + method: 'POST', + body: scale + } + ) + + if (this._debug) { + console.timeEnd( + `> [debug] #${attempt} POST /deployments/${nameOrId}/instances` + ) + } + + if (res.status === 403) { + return bail(new Error('Unauthorized')) + } + + const body = await res.json() + + if (res.status !== 200) { + if (res.status === 404 || res.status === 400) { + if ( + body && + body.error && + body.error.code && + body.error.code === 'not_snapshotted' + ) { + throw new Error(body.error.message) + } + const err = new Error(body.error.message) + err.userError = true + return bail(err) + } + + if (body.error && body.error.message) { + const err = new Error(body.error.message) + err.userError = true + return bail(err) + } + throw new Error( + `Error occurred while scaling. Please try again later` + ) + } + + return body + }, + { + retries: 300, + maxTimeout: ms('5s'), + factor: 1.1 + } + ) + } + + async unfreeze(depl) { + return this.retry(async bail => { + const res = await fetch(`https://${depl.url}`) + + if ([500, 502, 503].includes(res.status)) { + const err = new Error('Unfreeze failed. Try again later.') + bail(err) + } + }) + } + + async getPlanMax() { + return 10 + } +} + +function toRelative(path, base) { + const fullBase = base.endsWith(SEP) ? base : base + SEP + let relative = path.substr(fullBase.length) + + if (relative.startsWith(SEP)) { + relative = relative.substr(1) + } + + return relative.replace(/\\/g, '/') +} + +function hasNpmStart(pkg) { + return pkg.scripts && (pkg.scripts.start || pkg.scripts['now-start']) +} + +function hasFile(base, files, name) { + const relative = files.map(file => toRelative(file, base)) + return relative.indexOf(name) !== -1 +} + +async function readAuthToken(path, name = '.npmrc') { + try { + const contents = await readFile(resolvePath(path, name), 'utf8') + const npmrc = parseIni(contents) + return npmrc['//registry.npmjs.org/:_authToken'] + } catch (err) { + // Do nothing + } +} diff --git a/src/providers/sh/legacy/plans.js b/src/providers/sh/legacy/plans.js new file mode 100644 index 0000000..1f3664d --- /dev/null +++ b/src/providers/sh/legacy/plans.js @@ -0,0 +1,57 @@ +const ms = require('ms') + +const Now = require('./now') + +async function parsePlan(json) { + const { subscription } = json + let id + let until + let name + + if (subscription) { + const planItems = subscription.items.data + const mainPlan = planItems.find(d => d.plan.metadata.is_main_plan === '1') + + if (mainPlan) { + id = mainPlan.plan.id + name = mainPlan.plan.name + if (subscription.cancel_at_period_end) { + until = ms( + new Date(subscription.current_period_end * 1000) - new Date(), + { long: true } + ) + } + } else { + id = 'oss' + } + } else { + id = 'oss' + } + + return { id, name, until } +} + +module.exports = class Plans extends Now { + async getCurrent() { + const res = await this._fetch('/plan') + const json = await res.json() + return parsePlan(json) + } + + async set(plan) { + const res = await this._fetch('/plan', { + method: 'PUT', + body: { plan } + }) + + const json = await res.json() + + if (res.ok) { + return parsePlan(json) + } + + const err = new Error(json.error.message) + err.code = json.error.code + throw err + } +} diff --git a/src/providers/sh/legacy/prompt-options.js b/src/providers/sh/legacy/prompt-options.js new file mode 100644 index 0000000..a698a99 --- /dev/null +++ b/src/providers/sh/legacy/prompt-options.js @@ -0,0 +1,39 @@ +// Packages +const chalk = require('chalk') + +module.exports = promptOptions + +function promptOptions(opts) { + return new Promise((resolve, reject) => { + opts.forEach(([, text], i) => { + console.log(`${chalk.gray('>')} [${chalk.bold(i + 1)}] ${text}`) + }) + + const ondata = v => { + const s = v.toString() + + const cleanup = () => { + process.stdin.setRawMode(false) + process.stdin.removeListener('data', ondata) + } + + // Ctrl + C + if (s === '\u0003') { + cleanup() + const err = new Error('Aborted') + err.code = 'USER_ABORT' + return reject(err) + } + + const n = Number(s) + if (opts[n - 1]) { + cleanup() + resolve(opts[n - 1][0]) + } + } + + process.stdin.setRawMode(true) + process.stdin.resume() + process.stdin.on('data', ondata) + }) +} diff --git a/src/providers/sh/legacy/read-metadata.js b/src/providers/sh/legacy/read-metadata.js new file mode 100644 index 0000000..261399a --- /dev/null +++ b/src/providers/sh/legacy/read-metadata.js @@ -0,0 +1,195 @@ +// Native +const { basename, resolve: resolvePath } = require('path') + +// Packages +const chalk = require('chalk') +const { readFile } = require('fs-extra') +const { parse: parseDockerfile } = require('docker-file-parser') +const determineType = require('deployment-type') + +module.exports = readMetaData + +async function readMetaData( + path, + { + deploymentType, + deploymentName, + sessionAffinity, + quiet = false, + strict = true + } +) { + let description + let type = deploymentType + let name = deploymentName + let affinity = sessionAffinity + + const pkg = await readJSON(path, 'package.json') + let nowConfig = await readJSON(path, 'now.json') + const dockerfile = await readDockerfile(path) + + const hasNowJson = Boolean(nowConfig) + + if (pkg && pkg.now) { + // If the project has both a `now.json` and `now` Object in the `package.json` + // file, then fail hard and let the user know that they need to pick one or the + // other + if (nowConfig) { + const err = new Error( + 'You have a `now` configuration field inside `package.json` ' + + 'but configuration is also present in `now.json`! ' + + "Please ensure there's a single source of configuration by removing one." + ) + err.userError = true + throw err + } else { + nowConfig = pkg.now + } + } + + // We can remove this once the prompt for choosing `--npm` or `--docker` is gone + if (pkg && pkg.now && pkg.now.type) { + type = nowConfig.type + } + + // The same goes for this + if (nowConfig && nowConfig.type) { + type = nowConfig.type + } + + if (!type) { + type = await determineType(path) + + // Both `package.json` and `Dockerfile` exist! Prompt the user to pick one. + // We can remove this soon (details are internal) - also read the comment paragraph above + if (type === 'docker' && (pkg && dockerfile)) { + const err = new Error( + 'Ambiguous deployment (`package.json` and `Dockerfile` found). ' + + 'Please supply `--npm` or `--docker` to disambiguate.' + ) + + err.userError = true + err.code = 'MULTIPLE_MANIFESTS' + + throw err + } + } + + if (!name && nowConfig) { + name = nowConfig.name + } + + if (!affinity && nowConfig) { + affinity = nowConfig.sessionAffinity + } + + if (type === 'npm') { + if (pkg) { + if (!name && pkg.now && pkg.now.name) { + name = String(pkg.now.name) + } + + if (!name && pkg.name) { + name = String(pkg.name) + } + + description = pkg.description + } + } else if (type === 'docker') { + if (strict && dockerfile.length <= 0) { + const err = new Error('No commands found in `Dockerfile`') + err.userError = true + + throw err + } + + const labels = {} + + dockerfile.filter(cmd => cmd.name === 'LABEL').forEach(({ args }) => { + for (const key in args) { + if (!{}.hasOwnProperty.call(args, key)) { + continue + } + + // Unescape and convert into string + try { + labels[key] = args[key] + } catch (err) { + const e = new Error( + `Error parsing value for LABEL ${key} in \`Dockerfile\`` + ) + + e.userError = true + throw e + } + } + }) + + if (!name) { + name = labels.name + } + + description = labels.description + } else if (type === 'static') { + // Do nothing + } else { + throw new TypeError(`Unsupported "deploymentType": ${type}`) + } + + // No name in `package.json` / `now.json`, or "name" label in Dockerfile. + // Default to the basename of the root dir + if (!name) { + name = basename(path) + + if (!quiet && type !== 'static') { + if (type === 'docker') { + console.log( + `> No \`name\` LABEL in \`Dockerfile\`, using ${chalk.bold(name)}` + ) + } else { + console.log( + `> No \`name\` in \`package.json\`, using ${chalk.bold(name)}` + ) + } + } + } + + return { + name, + description, + type, + pkg, + nowConfig, + hasNowJson, + + // XXX: legacy + deploymentType: type, + sessionAffinity: affinity + } +} + +async function readJSON(path, name) { + try { + const contents = await readFile(resolvePath(path, name), 'utf8') + return JSON.parse(contents) + } catch (err) { + // If the file doesn't exist then that's fine; any other error bubbles up + if (err.code !== 'ENOENT') { + err.userError = true + throw err + } + } +} + +async function readDockerfile(path, name = 'Dockerfile') { + try { + const contents = await readFile(resolvePath(path, name), 'utf8') + return parseDockerfile(contents, { includeComments: true }) + } catch (err) { + // If the file doesn't exist then that's fine; any other error bubbles up + if (err.code !== 'ENOENT') { + err.userError = true + throw err + } + } +} diff --git a/src/providers/sh/legacy/to-host.js b/src/providers/sh/legacy/to-host.js new file mode 100644 index 0000000..f0eaf78 --- /dev/null +++ b/src/providers/sh/legacy/to-host.js @@ -0,0 +1,20 @@ +// Native +const { parse } = require('url') + +/** + * Converts a valid deployment lookup parameter to a hostname. + * `http://google.com` => google.com + * google.com => google.com + */ + +function toHost(url) { + if (/^https?:\/\//.test(url)) { + return parse(url).host + } + + // Remove any path if present + // `a.b.c/` => `a.b.c` + return url.replace(/(\/\/)?([^/]+)(.*)/, '$2') +} + +module.exports = toHost diff --git a/src/providers/sh/login.js b/src/providers/sh/login.js new file mode 100644 index 0000000..29acf6b --- /dev/null +++ b/src/providers/sh/login.js @@ -0,0 +1,290 @@ +// node +const { stringify: stringifyQuery } = require('querystring') +const { platform, arch, hostname } = require('os') + +// theirs +const fetch = require('node-fetch') +const debug = require('debug')('now:sh:login') +const promptEmail = require('email-prompt') +const ms = require('ms') +const { validate: validateEmail } = require('email-validator') + +// ours +const { version } = require('./util/pkg') +const ua = require('./util/ua') +const error = require('../../util/output/error') +const aborted = require('../../util/output/aborted') +const wait = require('../../util/output/wait') +const highlight = require('../../util/output/highlight') +const info = require('../../util/output/info') +const ok = require('../../util/output/ok') +const cmd = require('../../util/output/cmd') +const ready = require('../../util/output/ready') +const param = require('../../util/output/param') +const eraseLines = require('../../util/output/erase-lines') +const sleep = require('../../util/sleep') +const getUser = require('./util/get-user') +const { + writeToAuthConfigFile, + writeToConfigFile +} = require('../../util/config-files') +const getNowDir = require('../../get-now-dir') +const hp = require('../../util/humanize-path') + +// POSTs to /now/registration – either creates an account or performs a login +// returns {token, securityCode} +// token: should be used to verify the status of the login process +// securityCode: will be sent to the user in the email body +const getVerificationData = async ({ apiUrl, email }) => { + const tokenName = `Now CLI ${version} – ${platform()}-${arch()} (${hostname()})` + const data = JSON.stringify({ email, tokenName }) + + debug('POST /now/registration') + let res + try { + res = await fetch(`${apiUrl}/now/registration`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(data), + 'User-Agent': ua + }, + body: data + }) + } catch (err) { + debug('error fetching /now/registration: %O', err.stack) + throw new Error( + error( + `An unexpected error occurred while trying to login: ${err.message}` + ) + ) + } + + debug('parsing response from POST /now/registration') + + let body + try { + body = await res.json() + } catch (err) { + debug( + `error parsing the response from /now/registration as JSON – got %O`, + err.stack + ) + throw new Error( + error( + `An unexpected error occurred while trying to log in: ${err.message}` + ) + ) + } + + return body +} + +const verify = async ({ apiUrl, email, verificationToken }) => { + const query = { + email, + token: verificationToken + } + + debug('GET /now/registration/verify') + + let res + try { + res = await fetch( + `${apiUrl}/now/registration/verify?${stringifyQuery(query)}`, + { + headers: { 'User-Agent': ua } + } + ) + } catch (err) { + debug(`error fetching /now/registration/verify: $O`, err.stack) + throw new Error( + error( + `An unexpected error occurred while trying to verify your login: ${err.message}` + ) + ) + } + + debug('parsing response from GET /now/registration/verify') + + let body + try { + body = await res.json() + } catch (err) { + debug( + `error parsing the response from /now/registration/verify: $O`, + err.stack + ) + throw new Error( + error( + `An unexpected error occurred while trying to verify your login: ${err.message}` + ) + ) + } + + return body.token +} + +const readEmail = async () => { + let email + try { + email = await promptEmail({ start: info('Enter your email: ') }) + } catch (err) { + console.log() // \n + if (err.message === 'User abort') { + throw new Error(aborted('No changes made.')) + } + if (err.message === 'stdin lacks setRawMode support') { + throw new Error( + error( + `Interactive mode not supported – please run ${cmd( + 'now login you@domain.com' + )}` + ) + ) + } + } + console.log() // \n + return email +} + +// TODO open issues: .co, error messages + +const login = async ctx => { + const { argv } = ctx + const apiUrl = + (ctx.config.sh && ctx.config.sh.apiUrl) || 'https://api.zeit.co' + let email + let emailIsValid = false + let stopSpinner + + // node file sh login [email|help] + const argvHasSh = argv[2] === 'sh' + const allowedNumberOfArgs = argvHasSh ? 5 : 4 + if (argv.length > allowedNumberOfArgs) { + const _cmd = argvHasSh ? 'now sh login' : 'now login' + console.log(error(`Invalid number of arguments for ${cmd(_cmd)}`)) + console.log(info(`See ${cmd(_cmd + ' help')}`)) + return 1 + } + + const maybeEmail = argv[argv.length - 1] + + // if the last arg is not the command itself, then maybe it's an email + if (maybeEmail !== 'login') { + if (!validateEmail(maybeEmail)) { + // if it's not a valid email, let's just error + console.log(error(`Invalid email: ${param(maybeEmail)}.`)) + return 1 + } + // valid email, no need to prompt the user + email = maybeEmail + } else { + do { + try { + email = await readEmail() + } catch (err) { + let erase = '' + if (err.message.includes('Aborted')) { + // no need to keep the prompt if the user `ctrl+c`ed + erase = eraseLines(2) + } + console.log(erase + err.message) + return 1 + } + emailIsValid = validateEmail(email) + if (!emailIsValid) { + // let's erase the `> Enter email [...]` + // we can't use `console.log()` because it appends a `\n` + // we need this check because `email-prompt` doesn't print + // anything if there's no TTY + process.stdout.write(eraseLines(2)) + } + } while (!emailIsValid) + } + + let verificationToken + let securityCode + stopSpinner = wait('Sending you an email') + try { + const data = await getVerificationData({ apiUrl, email }) + verificationToken = data.token + securityCode = data.securityCode + } catch (err) { + stopSpinner() + console.log(err.message) + return 1 + } + + stopSpinner() + + // prettier-ignore + console.log(info( + `We sent an email to ${highlight(email)}. Please follow the steps provided`, + ` in it and make sure the security code matches ${highlight(securityCode)}.` + )) + + stopSpinner = wait('Waiting for your confirmation') + + let token + + while (!token) { + try { + await sleep(ms('1s')) + token = await verify({ apiUrl, email, verificationToken }) + } catch (err) { + if (/invalid json response body/.test(err.message)) { + // /now/registraton is currently returning plain text in that case + // we just wait for the user to click on the link + } else { + stopSpinner() + console.log(err.message) + return 1 + } + } + } + + stopSpinner() + console.log(ok('Email confirmed.')) + + stopSpinner = wait('Feching your personal details') + let user + try { + user = await getUser({ apiUrl, token }) + } catch (err) { + stopSpinner() + console.log(err) + return 1 + } + + const index = ctx.authConfig.credentials.findIndex(c => c.provider === 'sh') + const obj = { provider: 'sh', token } + if (index === -1) { + // wasn't logged in before + ctx.authConfig.credentials.push(obj) + } else { + // let's just replace the existing object + ctx.authConfig.credentials[index] = obj + } + + // NOTE: this will override any existing config for `sh` + ctx.config.sh = { user } + + writeToAuthConfigFile(ctx.authConfig) + writeToConfigFile(ctx.config) + + stopSpinner() + console.log(ok('Fetched your personal details.')) + + console.log( + ready( + `Authentication token and personal details saved in ${param( + hp(getNowDir()) + )}` + ) + ) + + return ctx +} + +module.exports = login diff --git a/src/providers/sh/util/get-user.js b/src/providers/sh/util/get-user.js new file mode 100644 index 0000000..47340d3 --- /dev/null +++ b/src/providers/sh/util/get-user.js @@ -0,0 +1,54 @@ +// theirs +const fetch = require('node-fetch') +const debug = require('debug')('now:sh:get-user') + +// ours +const error = require('../../../util/output/error') + +const getUser = async ({ apiUrl, token }) => { + debug('start') + const url = apiUrl + '/www/user' + const headers = { + Authorization: `Bearer ${token}` + } + + debug('GET /www/user') + + let res + try { + res = await fetch(url, { headers }) + } catch (err) { + debug(`error fetching /www/user: $O`, err.stack) + throw new Error( + error( + `An unexpected error occurred while trying to fetch your personal details: ${err.message}` + ) + ) + } + + debug('parsing response from GET /www/user') + + let body + try { + body = await res.json() + } catch (err) { + debug( + `error parsing the response from /www/user as JSON – got %O`, + err.stack + ) + throw new Error( + error( + `An unexpected error occurred while trying to fetch your personal details: ${err.message}` + ) + ) + } + + const { user } = body + + // this is pretty much useless + delete user.billingChecked + + return user +} + +module.exports = getUser diff --git a/src/providers/sh/util/pkg.js b/src/providers/sh/util/pkg.js new file mode 100644 index 0000000..261bb4c --- /dev/null +++ b/src/providers/sh/util/pkg.js @@ -0,0 +1,11 @@ +const path = require('path') +const pkg = require('../../../../package.json') + +try { + const distDir = path.dirname(process.execPath) + pkg._npmPkg = require(path.join(distDir, '../../package.json')) +} catch (err) { + pkg._npmPkg = null +} + +module.exports = pkg diff --git a/src/providers/sh/util/ua.js b/src/providers/sh/util/ua.js new file mode 100644 index 0000000..be3995c --- /dev/null +++ b/src/providers/sh/util/ua.js @@ -0,0 +1,7 @@ +// node +const os = require('os') + +// ours +const { version } = require('./pkg') + +module.exports = `now ${version} node-${process.version} ${os.platform()} (${os.arch()})` diff --git a/src/resolve.js b/src/resolve.js new file mode 100644 index 0000000..2cf0625 --- /dev/null +++ b/src/resolve.js @@ -0,0 +1,28 @@ +const resolvers = require('./resolvers') +const resolverNames = Object.keys(resolvers) + +const resolve = async (param, opts) => { + for (const name of resolverNames) { + const resolver = resolvers[name] + let resolved + + // give the caller the ability to create + // nicer errors by attaching the resolver name + try { + resolved = await resolver(param, opts) + } catch (err) { + err.resolverName = name + throw err + } + + if (resolved !== null) { + return resolved + } + // otherwise continue onto the next resolver + // note: if a resolver throws, we consider that + // unexpected. a resolver should return `null` + // when the parameter is unresolvable instead + } + return null +} +module.exports = resolve diff --git a/src/resolvers/fs.js b/src/resolvers/fs.js new file mode 100644 index 0000000..57d9a6e --- /dev/null +++ b/src/resolvers/fs.js @@ -0,0 +1,13 @@ +const { exists } = require('fs.promised') +const { resolve } = require('path') + +const fsResolver = async (param, { cwd = process.cwd() } = {}) => { + const resolved = resolve(cwd, param) + if (await exists(resolved)) { + return resolved + } else { + return null + } +} + +module.exports = fsResolver diff --git a/src/resolvers/github.js b/src/resolvers/github.js new file mode 100644 index 0000000..b34dbe8 --- /dev/null +++ b/src/resolvers/github.js @@ -0,0 +1,100 @@ +//@flow +const { tmpdir } = require('os') +const { parse, format } = require('url') +const fetch = require('node-fetch') +const tar = require('tar-fs') +const pipeStreams = require('pipe-streams-to-promise') +const { mkdir } = require('fs.promised') +const uid = require('uid-promise') +const { createGunzip } = require('zlib') +const { join } = require('path') +const debug = require('debug')('now:resolvers:github') + +// matches a parameter that can be `now`d like zeit/now#master +const DEPLOY_PARAM_REGEX = /^([\w-]+)\/([\w-]+)(#\w+)?$/ + +// matches whether the parameter could be a github url +const GITHUB_TEST_REGEX = /^(https?:\/\/)(www\.)?github\.com/ + +// matches a github url pathname like: zeit/now/tree/master +const URL_PATHNAME_REGEX = /^\/([\w-]+)\/([\w-]+)(\/tree\/(\w+))?$/ + +const resolveGitHub = param => { + // support simple `user/repo` syntax + const match = param.match(DEPLOY_PARAM_REGEX) + if (match) { + const [, user, repo, tree = 'master'] = match + return resolveGitHubByURL(`https://github.com/${user}/${repo}/tree/${tree}`) + } else if (GITHUB_TEST_REGEX.test(param)) { + return resolveGitHubByURL(param) + } else { + return null + } +} + +const resolveGitHubByURL = async (url: string) => { + debug('resolving %s by github url', url) + if (/^https?/.test(url)) { + const parsed = parse(url) + if (parsed.hostname === 'github.com') { + const httpsUrl = + 'https:' === parsed.protocol ? url : format(Object.assign({}, parsed)) + const res = await fetch(httpsUrl) + if (res.ok) { + debug('attempting github clone') + const { pathname } = parsed + const match = pathname.match(URL_PATHNAME_REGEX) + if (match) { + const [, user, repo, , tree] = match + const downloadURL = format({ + protocol: 'https:', + hostname: 'codeload.github.com', + pathname: `/${user}/${repo}/tar.gz/${tree}` + }) + debug('fetching download url', downloadURL) + const downloadRes = await fetch(downloadURL, { compress: false }) + if (downloadRes.ok) { + const tmpDir = join(tmpdir(), `now-gh-${await uid(20)}`) + debug('creating tmp dir to extract', tmpDir) + try { + await mkdir(tmpDir) + } catch (err) { + throw new Error( + 'Error occurred while trying to extract ' + + `GH tarball to tmp directory ${tmpDir}: ${err.stack}` + ) + } + debug('unzipping and untarring stream') + await pipeStreams([ + downloadRes.body, + createGunzip(), + tar.extract(tmpDir) + ]) + // instead of stripping a directory upon untar, + // we return the full path to the extracted project, + // so that now can take advantage of the name + return join(tmpDir, `${repo}-${tree}`) + } else { + throw new Error( + 'An HTTP error ${res.status} was returned ' + + `by "${downloadURL}"` + ) + } + } else { + debug('invalid github project url') + return null + } + } else { + debug('non-200 from github (%d)', res.status) + return null + } + } else { + debug('skipping non-github hostname') + return null + } + } else { + return null + } +} + +module.exports = resolveGitHub diff --git a/src/resolvers/index.js b/src/resolvers/index.js new file mode 100644 index 0000000..ff4fc81 --- /dev/null +++ b/src/resolvers/index.js @@ -0,0 +1,4 @@ +module.exports = { + fs: require('./fs'), + github: require('./github') +} diff --git a/src/serverless/README.md b/src/serverless/README.md new file mode 100644 index 0000000..4117d3a --- /dev/null +++ b/src/serverless/README.md @@ -0,0 +1,4 @@ +# serverless utilities + +This directory contains a utilities that are useful and reusable +across different FaaS providers. diff --git a/src/serverless/build.js b/src/serverless/build.js new file mode 100644 index 0000000..55389a0 --- /dev/null +++ b/src/serverless/build.js @@ -0,0 +1,7 @@ +const builders = require('./builders') + +const build = (dir, desc, opts) => { + return builders[desc.type](dir, desc, opts) +} + +module.exports = build diff --git a/src/serverless/builders/index.js b/src/serverless/builders/index.js new file mode 100644 index 0000000..223ffe7 --- /dev/null +++ b/src/serverless/builders/index.js @@ -0,0 +1,11 @@ +module.exports = { + get nodejs() { + return require('./nodejs') + }, + get static() { + return require('./static') + }, + get go() { + return require('./go') + } +} diff --git a/src/serverless/builders/nodejs.js b/src/serverless/builders/nodejs.js new file mode 100644 index 0000000..36cad7d --- /dev/null +++ b/src/serverless/builders/nodejs.js @@ -0,0 +1,92 @@ +const { tmpdir } = require('os') +const { join } = require('path') +const { mkdir, stat, link, exists, readdir } = require('fs.promised') +const uid = require('uid-promise') +const { exec: exec_ } = require('child_process') +const { toBuffer } = require('convert-stream') +const archiver = require('archiver') +const debug = require('debug')('now:serverless:builders:nodejs') +const exec = require('util').promisify(exec_) + +const nodejsBuilder = async (dir, desc, { overrides = {} } = {}) => { + const files = await readdir(dir) + const tmpDirName = `now-nodejs-build-${await uid(20)}` + const targetPath = join(tmpdir(), tmpDirName) + + debug('init nodejs project build stage in', targetPath) + await mkdir(targetPath) + + // produce hard links of the source files in the target dir + await Promise.all( + files + .filter(name => name !== 'node_modules' && !(name in overrides)) + .map(file => { + debug('making hard link for %s', file) + return link(join(dir, file), join(targetPath, file)) + }) + ) + + const archive = archiver('zip') + + // trigger an install if needed + if (desc.packageJSON) { + let buildCommand = '' + + if (await exists(join(targetPath, 'package-lock.json'))) { + buildCommand = 'npm install' + } else if (await exists(join(targetPath, 'yarn.lock'))) { + buildCommand = 'yarn install' + } else { + buildCommand = 'npm install' + } + + try { + debug('executing %s in %s', buildCommand, targetPath) + await exec(buildCommand, { + cwd: targetPath, + env: Object.assign({}, process.env, { + // we set this so that we make the installers ignore + // dev dependencies. in the future, we can add a flag + // to ignore this behavior, or set different envs + NODE_ENV: 'production' + }) + }) + } catch (err) { + throw new Error( + `The build command ${buildCommand} failed for ${dir}: ${err.message}` + ) + } + } else { + debug('ignoring build step, no manifests found') + } + + const buffer = toBuffer(archive) + + archive.on('warning', err => { + console.error('Warning while creating zip file', err) + }) + + for (const name in overrides) { + archive.append(overrides[name], { name }) + } + + // we read again to get the results of the build process + const filesToZip = await readdir(targetPath) + await Promise.all( + filesToZip.map(async file => { + const path = join(targetPath, file) + const stats = await stat(path) + debug('adding', path) + return stats.isDirectory() + ? archive.directory(path, file, { stats }) + : archive.file(path, { name: file, stats }) + }) + ) + + archive.finalize() + + // buffer promise + return buffer +} + +module.exports = nodejsBuilder diff --git a/src/serverless/get-handler.js b/src/serverless/get-handler.js new file mode 100644 index 0000000..e330617 --- /dev/null +++ b/src/serverless/get-handler.js @@ -0,0 +1,29 @@ +// @flow +const { readFileSync } = require('fs') +const { join } = require('path') +const handler = readFileSync(join(__dirname, 'handler.js')).toString() + +// symbols to replace in the meta-source +const CMD_SYMBOL = '/*NOW_CMD*/' +const SCRIPT_SYMBOL = '/*NOW_SCRIPT*/' +const REQ_HANDLER_SYMBOL = '/*PROXY_REQUEST_SOURCE*/' + +if (handler.indexOf(CMD_SYMBOL) < 0) { + throw new Error('Missing symbol in `handler.js`: ' + CMD_SYMBOL) +} + +if (handler.indexOf(SCRIPT_SYMBOL) < 0) { + throw new Error('Missing symbol in `handler.js`: ' + SCRIPT_SYMBOL) +} + +if (handler.indexOf(REQ_HANDLER_SYMBOL) < 0) { + throw new Error('Missing symbol in `handler.js`: ' + REQ_HANDLER_SYMBOL) +} + +const getHandler = ({ cmd, script }, fn: Function) => + handler + .replace(CMD_SYMBOL, JSON.stringify(cmd)) + .replace(SCRIPT_SYMBOL, JSON.stringify(script)) + .replace(REQ_HANDLER_SYMBOL, fn.toString()) + +module.exports = getHandler diff --git a/src/serverless/handler.js b/src/serverless/handler.js new file mode 100644 index 0000000..c9ed719 --- /dev/null +++ b/src/serverless/handler.js @@ -0,0 +1,110 @@ +// @flow +const start = new Date() + +const { createServer } = require('http') +const { createConnection } = require('net') +const { spawn } = require('child_process') +const request = require('http').request + +let spawned = false +let PORT = null +let retriesLeft = 20 +let buffer = [] + +const flushBuffer = () => { + buffer.forEach(args => { + proxyRequest.apply(null, args) + }) + buffer = null +} + +const findFreePort = () => + new Promise((resolve, reject) => { + const srv = createServer(() => {}).listen(err => { + if (err) return reject(err) + const { port } = srv.address() + srv.close() + resolve(port) + }) + }) + +findFreePort().then( + port => { + PORT = port + + const env = Object.assign({}, process.env, { + // we need to add `/nodejs/bin` for GCP functions to + // work correctly + PATH: `/nodejs/bin:/usr/local/bin:/usr/bin`, + PORT + }) + + const NOW_CMD = [ + /*NOW_CMD*/ + ][0] + + const NOW_SCRIPT = [ + /*NOW_SCRIPT*/ + ][0] + + if (NOW_CMD) { + const cmd = spawn('/usr/bin/env', ['sh', '-c', NOW_CMD], { env: env }) + cmd.on('error', err => { + throw err + }) + } else { + process.env.PORT = PORT + require(`./${NOW_SCRIPT}`) + } + + const attemptConnect = () => { + const socket = createConnection(PORT) + socket.setTimeout(1000) + socket.on('error', retry) + socket.on('connect', () => { + socket.end() + spawned = true + flushBuffer() + console.log('spawn took', new Date() - start) + }) + socket.on('timeout', () => { + socket.end() + retry() + }) + } + + const retry = () => { + if (--retriesLeft < 0) { + throw new Error('Could not establish a connection to the http server') + } + // this is close to the bootup time of the most minimal + // node server that could be created + setTimeout(attemptConnect, 80) + } + + retry() + }, + err => { + throw err + } +) + +exports.handler = (...args) => { + // hack for lambda. we will refactor the handler injection + // per-provider later + if (args[1] && args[1].callbackWaitsForEmptyEventLoop) { + args[1].callbackWaitsForEmptyEventLoop = false + } + + if (spawned) { + proxyRequest.apply(null, args) + } else { + buffer.push(args) + } +} + +// we will replace the comment with the function with the logic +// to proxy the request for every provider +const proxyRequest = [ + /*PROXY_REQUEST_SOURCE*/ +][0].bind(null, request, () => PORT) diff --git a/src/util/config-files.js b/src/util/config-files.js new file mode 100644 index 0000000..785c904 --- /dev/null +++ b/src/util/config-files.js @@ -0,0 +1,43 @@ +// node +const { readFileSync, writeFileSync } = require('fs') +const { join: joinPath } = require('path') + +// ours +const getNowDir = require('../get-now-dir') + +const NOW_DIR = getNowDir() +const CONFIG_FILE_PATH = joinPath(NOW_DIR, 'config.json') +const AUTH_CONFIG_FILE_PATH = joinPath(NOW_DIR, 'auth.json') + +const prettify = obj => JSON.stringify(obj, null, 2) + +// reads `CONFIG_FILE_PATH` atomically +const readConfigFile = () => readFileSync(CONFIG_FILE_PATH, 'utf8') + +// writes whatever's in `stuff` to `CONFIG_FILE_PATH`, atomically +const writeToConfigFile = stuff => + writeFileSync(CONFIG_FILE_PATH, prettify(stuff)) + +// reads `AUTH_CONFIG_FILE_PATH` atomically +const readAuthConfigFile = () => readFileSync(AUTH_CONFIG_FILE_PATH, 'utf8') + +// writes whatever's in `stuff` to `AUTH_CONFIG_FILE_PATH`, atomically +const writeToAuthConfigFile = stuff => + writeFileSync(AUTH_CONFIG_FILE_PATH, prettify(stuff)) + +function getConfigFilePath() { + return CONFIG_FILE_PATH +} + +function getAuthConfigFilePath() { + return AUTH_CONFIG_FILE_PATH +} + +module.exports = { + readConfigFile, + writeToConfigFile, + readAuthConfigFile, + writeToAuthConfigFile, + getConfigFilePath, + getAuthConfigFilePath +} diff --git a/src/util/copy-to-clipboard.js b/src/util/copy-to-clipboard.js new file mode 100644 index 0000000..e780fbf --- /dev/null +++ b/src/util/copy-to-clipboard.js @@ -0,0 +1,31 @@ +const { write } = require('clipboardy') + +const copyToClipboard = async ( + str: string, + shouldCopy = 'auto', + isTTY = process.stdout.isTTY +): boolean => { + if (shouldCopy === false) { + return false + } + + if (shouldCopy === 'auto') { + if (isTTY) { + await write(str) + return true + } else { + return false + } + } + + if (shouldCopy === true) { + await write(str) + return true + } + + throw new TypeError( + 'The `copyToClipbard` value in now config has an invalid type' + ) +} + +module.exports = copyToClipboard diff --git a/src/util/humanize-path.js b/src/util/humanize-path.js new file mode 100644 index 0000000..6f62529 --- /dev/null +++ b/src/util/humanize-path.js @@ -0,0 +1,15 @@ +// @flow +const { homedir } = require('os') +const { resolve } = require('path') + +const humanizePath = (path: string) => { + const resolved: string = resolve(path) + const _homedir = homedir() + if (resolved.indexOf(_homedir) === 0) { + return `~` + resolved.substr(_homedir.length) + } else { + return resolved + } +} + +module.exports = humanizePath diff --git a/src/util/input/list.js b/src/util/input/list.js new file mode 100644 index 0000000..3236cc1 --- /dev/null +++ b/src/util/input/list.js @@ -0,0 +1,82 @@ +const inquirer = require('inquirer') +const stripAnsi = require('strip-ansi') + +// eslint-disable-next-line import/no-unassigned-import +require('./patch-inquirer') + +function getLength(string) { + let biggestLength = 0 + string.split('\n').map(str => { + str = stripAnsi(str) + if (str.length > biggestLength) { + biggestLength = str.length + } + return undefined + }) + return biggestLength +} + +module.exports = async function promptList({ + message = 'the question', + // eslint-disable-line no-unused-vars + choices = [ + { + name: 'something\ndescription\ndetails\netc', + value: 'something unique', + short: 'generally the first line of `name`' + } + ], + pageSize = 15, // Show 15 lines without scrolling (~4 credit cards) + separator = true, // Puts a blank separator between each choice + // Wether the `abort` option will be at the `start` or the `end` + // can be `false` + abort = 'end' +}) { + let biggestLength = 0 + + choices = choices.map(choice => { + if (choice.name) { + const length = getLength(choice.name) + if (length > biggestLength) { + biggestLength = length + } + return choice + } + throw new Error('Invalid choice') + }) + + if (separator === true) { + choices = choices.reduce( + (prev, curr) => prev.concat(new inquirer.Separator(' '), curr), + [] + ) + } + + if (abort) { + const abortSeparator = new inquirer.Separator('─'.repeat(biggestLength)) + const _abort = { + name: 'Abort', + value: undefined + } + if (abort === 'start') { + const blankSep = choices.shift() + choices.unshift(abortSeparator) + choices.unshift(_abort) + choices.unshift(blankSep) + } else { + choices.push(abortSeparator) + choices.push(_abort) + } + } + + const nonce = Date.now() + const answer = await inquirer.prompt({ + name: nonce, + type: 'list', + message, + choices, + pageSize + }) + + return answer[nonce] +} diff --git a/src/util/input/patch-inquirer.js b/src/util/input/patch-inquirer.js new file mode 100644 index 0000000..0d02352 --- /dev/null +++ b/src/util/input/patch-inquirer.js @@ -0,0 +1,18 @@ +const inquirer = require('inquirer') +const chalk = require('chalk') + +// Here we patch inquirer to use a `>` instead of the ugly green `?` +const getQuestion = function() { + var message = chalk.bold('> ' + this.opt.message) + ' ' + + // Append the default if available, and if question isn't answered + if (this.opt.default != null && this.status !== 'answered') { + message += chalk.dim('(' + this.opt.default + ') ') + } + + return message +} +/* eslint-enable */ + +inquirer.prompt.prompts.input.prototype.getQuestion = getQuestion +inquirer.prompt.prompts.list.prototype.getQuestion = getQuestion diff --git a/src/util/input/prompt-bool.js b/src/util/input/prompt-bool.js new file mode 100644 index 0000000..07ada1e --- /dev/null +++ b/src/util/input/prompt-bool.js @@ -0,0 +1,68 @@ +// theirs +const chalk = require('chalk') + +// ours +const eraseLines = require('../output/erase-lines') + +module.exports = ( + label, + { + defaultValue = false, + abortSequences = new Set(['\u0003', '\u001b']), // ctrl+c, esc + resolveChars = new Set(['\r']), // enter + yesChar = 'y', + noChar = 'n', + stdin = process.stdin, + stdout = process.stdout, + // if `true`, `eraseLines(1)` will be `stdout.write`d before + // `resolve`ing or `reject`ing + clearWhenDone = true + } = {} +) => { + return new Promise((resolve, reject) => { + const isRaw = stdin.isRaw + + stdin.setRawMode(true) + stdin.resume() + + function restore() { + if (clearWhenDone) { + stdout.write(eraseLines(1)) + } + stdin.setRawMode(isRaw) + stdin.pause() + stdin.removeListener('data', onData) + } + + function onData(buffer) { + const data = buffer.toString() + + if (data[0].toLowerCase() === yesChar) { + restore() + resolve(true) + } else if (data[0].toLowerCase() === noChar) { + restore() + resolve(false) + } else if (abortSequences.has(data)) { + restore() + const e = new Error('User abort') + e.code = 'USER_ABORT' + reject(e) + } else if (resolveChars.has(data[0])) { + restore() + resolve(defaultValue) + } else { + // ignore extraneous input + } + } + + const defaultText = + defaultValue === null + ? `[${yesChar}|${noChar}]` + : defaultValue + ? `[${chalk.bold(yesChar.toUpperCase())}|${noChar}]` + : `[${yesChar}|${chalk.bold(noChar.toUpperCase())}]` + stdout.write(`${label} ${chalk.gray(defaultText)} `) + stdin.on('data', onData) + }) +} diff --git a/src/util/input/text.js b/src/util/input/text.js new file mode 100644 index 0000000..509c26c --- /dev/null +++ b/src/util/input/text.js @@ -0,0 +1,103 @@ +// inspired by https://github.com/zeit/email-prompt + +// theirs +const ansiEscapes = require('ansi-escapes') +const stripAnsi = require('strip-ansi') + +// ours +const eraseLines = require('../output/erase-lines') + +const ESCAPES = { + LEFT: '\u001B[D', + RIGHT: '\u001B[C', + CTRL_C: '\x03', + BACKSPACE: '\u0008', + CTRL_H: '\u007F', + CARRIAGE: '\r' +} + +const textInput = ({ + label = 'Enter some text: ', + resolveChars = new Set([ESCAPES.CARRIAGE]), + abortChars = new Set([ESCAPES.CTRL_C]), + // if `true`, `eraseLines(1)` will be `stdout.write`d before + // `resolve`ing or `reject`ing + clearWhenDone = true +}) => { + return new Promise((resolve, reject) => { + if (!process.stdin.setRawMode) { + // Some environments (e.g., cygwin) don't provide a tty + const e = new Error('stdin lacks setRawMode support') + e.userError = true + restore() + reject(e) + } + + const isRaw = process.stdin.isRaw + + process.stdin.setRawMode(true) + process.stdin.resume() + process.stdout.write(label) + + let input = '' // Whatever the user types + let caretOffset = 0 // Left/right keys + + const onData = buffer => { + let data = buffer.toString() + + if (abortChars.has(data)) { + const e = new Error('User abort') + e.code = 'USER_ABORT' + restore() + return reject(e) + } + + if (data === ESCAPES.LEFT) { + if (input.length > Math.abs(caretOffset)) { + caretOffset-- + } + } else if (data === ESCAPES.RIGHT) { + if (caretOffset < 0) { + caretOffset++ + } + } else if (data === '\x08' || data === '\x7f') { + // Delete key needs splicing according to caret position + input = + input.substr(0, input.length + caretOffset - 1) + + input.substr(input.length + caretOffset) + } else { + if (resolveChars.has(data)) { + restore() + resolve(input) + } + + if (stripAnsi(data).length !== data.length) { + data = '' + } + + input = + input.substr(0, input.length + caretOffset) + + stripAnsi(data) + + input.substr(input.length + caretOffset) + } + + process.stdout.write(eraseLines(1) + label + input) + if (caretOffset) { + process.stdout.write(ansiEscapes.cursorBackward(Math.abs(caretOffset))) + } + } + + const restore = () => { + if (clearWhenDone) { + process.stdout.write(eraseLines(1)) + } + process.stdin.setRawMode(isRaw) + process.stdin.pause() + process.stdin.removeListener('data', onData) + } + + process.stdin.on('data', onData) + }) +} + +module.exports = textInput diff --git a/src/util/output/aborted.js b/src/util/output/aborted.js new file mode 100644 index 0000000..35a1700 --- /dev/null +++ b/src/util/output/aborted.js @@ -0,0 +1,5 @@ +const { red } = require('chalk') + +const error = msg => `${red('> Aborted!')} ${msg}` + +module.exports = error diff --git a/src/util/output/chars.js b/src/util/output/chars.js new file mode 100644 index 0000000..b8f4b24 --- /dev/null +++ b/src/util/output/chars.js @@ -0,0 +1,7 @@ +const chars = { + // in some setups now.exe crashes if we use + // the normal tick unicode character :| + tick: process.platform === 'win32' ? '√' : '✔' +} + +module.exports = chars diff --git a/src/util/output/cmd.js b/src/util/output/cmd.js new file mode 100644 index 0000000..bc16419 --- /dev/null +++ b/src/util/output/cmd.js @@ -0,0 +1,5 @@ +const { gray, cyan } = require('chalk') + +const cmd = text => `${gray('`')}${cyan(text)}${gray('`')}` + +module.exports = cmd diff --git a/src/util/output/effect.js b/src/util/output/effect.js new file mode 100644 index 0000000..4e3654a --- /dev/null +++ b/src/util/output/effect.js @@ -0,0 +1,5 @@ +const { gray } = require('chalk') + +const effect = msg => `${gray(`+ ${msg}`)}` + +module.exports = effect diff --git a/src/util/output/erase-lines.js b/src/util/output/erase-lines.js new file mode 100644 index 0000000..54017cd --- /dev/null +++ b/src/util/output/erase-lines.js @@ -0,0 +1,5 @@ +const ansiEscapes = require('ansi-escapes') + +const eraseLines = n => ansiEscapes.eraseLines(n) + +module.exports = eraseLines diff --git a/src/util/output/error.js b/src/util/output/error.js new file mode 100644 index 0000000..3ee725d --- /dev/null +++ b/src/util/output/error.js @@ -0,0 +1,7 @@ +const { red } = require('chalk') + +// error('woot') === '> woot' +// error('woot', 'yay') === 'woot\nyay' +const error = (...msgs) => `${red('> Error!')} ${msgs.join('\n')}` + +module.exports = error diff --git a/src/util/output/highlight.js b/src/util/output/highlight.js new file mode 100644 index 0000000..277bebf --- /dev/null +++ b/src/util/output/highlight.js @@ -0,0 +1,5 @@ +const { bold } = require('chalk') + +const highlight = text => bold.underline(text) + +module.exports = highlight diff --git a/src/util/output/info.js b/src/util/output/info.js new file mode 100644 index 0000000..a80a137 --- /dev/null +++ b/src/util/output/info.js @@ -0,0 +1,7 @@ +const { gray } = require('chalk') + +// info('woot') === '> woot' +// info('woot', 'yay') === 'woot\nyay' +const info = (...msgs) => `${gray('>')} ${msgs.join('\n')}` + +module.exports = info diff --git a/src/util/output/link.js b/src/util/output/link.js new file mode 100644 index 0000000..d7336ba --- /dev/null +++ b/src/util/output/link.js @@ -0,0 +1,5 @@ +const { underline } = require('chalk') + +const highlight = text => underline(text) + +module.exports = highlight diff --git a/src/util/output/list-item.js b/src/util/output/list-item.js new file mode 100644 index 0000000..741718f --- /dev/null +++ b/src/util/output/list-item.js @@ -0,0 +1,17 @@ +const { gray } = require('chalk') + +// listItem('woot') === '- woot' +// listItem('->', 'woot') === '-> woot' +// listItem(1, 'woot') === '1. woot' +const listItem = (n, msg) => { + if (!msg) { + msg = n + n = '-' + } + if (!isNaN(n)) { + n += '.' + } + return `${gray(n)} ${msg}` +} + +module.exports = listItem diff --git a/src/util/output/logo.js b/src/util/output/logo.js new file mode 100644 index 0000000..88f9b0c --- /dev/null +++ b/src/util/output/logo.js @@ -0,0 +1,3 @@ +const logo = () => (process.platform === 'win32' ? 'Δ' : '𝚫') + +module.exports = logo diff --git a/src/util/output/note.js b/src/util/output/note.js new file mode 100644 index 0000000..0eeecdc --- /dev/null +++ b/src/util/output/note.js @@ -0,0 +1,5 @@ +const { yellow } = require('chalk') + +const note = msg => `${yellow('> NOTE:')} ${msg}` + +module.exports = note diff --git a/src/util/output/ok.js b/src/util/output/ok.js new file mode 100644 index 0000000..2ff5301 --- /dev/null +++ b/src/util/output/ok.js @@ -0,0 +1,6 @@ +const { cyan } = require('chalk') +const { tick } = require('./chars') + +const ok = msg => `${cyan(tick)} ${msg}` + +module.exports = ok diff --git a/src/util/output/param.js b/src/util/output/param.js new file mode 100644 index 0000000..c2741c6 --- /dev/null +++ b/src/util/output/param.js @@ -0,0 +1,5 @@ +const { gray, bold } = require('chalk') + +const param = text => `${gray('"')}${bold(text)}${gray('"')}` + +module.exports = param diff --git a/src/util/output/ready.js b/src/util/output/ready.js new file mode 100644 index 0000000..6fdd1dd --- /dev/null +++ b/src/util/output/ready.js @@ -0,0 +1,5 @@ +const { cyan } = require('chalk') + +const ready = msg => `${cyan('> Ready!')} ${msg}` + +module.exports = ready diff --git a/src/util/output/success.js b/src/util/output/success.js new file mode 100644 index 0000000..a70b232 --- /dev/null +++ b/src/util/output/success.js @@ -0,0 +1,5 @@ +const { cyan } = require('chalk') + +const success = msg => `${cyan('> Success!')} ${msg}` + +module.exports = success diff --git a/src/util/output/wait.js b/src/util/output/wait.js new file mode 100644 index 0000000..0479e2f --- /dev/null +++ b/src/util/output/wait.js @@ -0,0 +1,16 @@ +const ora = require('ora') +const { gray } = require('chalk') +const eraseLines = require('./erase-lines') + +const wait = msg => { + const spinner = ora(gray(msg)) + spinner.color = 'gray' + spinner.start() + + return () => { + spinner.stop() + process.stdout.write(eraseLines(1)) + } +} + +module.exports = wait diff --git a/src/util/sleep.js b/src/util/sleep.js new file mode 100644 index 0000000..f2d0307 --- /dev/null +++ b/src/util/sleep.js @@ -0,0 +1,7 @@ +const sleep = ms => { + return new Promise(resolve => { + setTimeout(resolve, ms) + }) +} + +module.exports = sleep diff --git a/test.js b/test.js new file mode 100644 index 0000000..3762e9e --- /dev/null +++ b/test.js @@ -0,0 +1,7 @@ +const resolve = require('./src/resolve') + +resolve('now-examples/wordpress') + .then(dir => { + console.log(dir) + }) + .catch(console.error) diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..a007f50 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,4255 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ava/babel-plugin-throws-helper@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@ava/babel-plugin-throws-helper/-/babel-plugin-throws-helper-2.0.0.tgz#2fc1fe3c211a71071a4eca7b8f7af5842cd1ae7c" + +"@ava/babel-preset-stage-4@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@ava/babel-preset-stage-4/-/babel-preset-stage-4-1.1.0.tgz#ae60be881a0babf7d35f52aba770d1f6194f76bd" + dependencies: + babel-plugin-check-es2015-constants "^6.8.0" + babel-plugin-syntax-trailing-function-commas "^6.20.0" + babel-plugin-transform-async-to-generator "^6.16.0" + babel-plugin-transform-es2015-destructuring "^6.19.0" + babel-plugin-transform-es2015-function-name "^6.9.0" + babel-plugin-transform-es2015-modules-commonjs "^6.18.0" + babel-plugin-transform-es2015-parameters "^6.21.0" + babel-plugin-transform-es2015-spread "^6.8.0" + babel-plugin-transform-es2015-sticky-regex "^6.8.0" + babel-plugin-transform-es2015-unicode-regex "^6.11.0" + babel-plugin-transform-exponentiation-operator "^6.8.0" + package-hash "^1.2.0" + +"@ava/babel-preset-transform-test-files@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@ava/babel-preset-transform-test-files/-/babel-preset-transform-test-files-3.0.0.tgz#cded1196a8d8d9381a509240ab92e91a5ec069f7" + dependencies: + "@ava/babel-plugin-throws-helper" "^2.0.0" + babel-plugin-espower "^2.3.2" + +"@ava/write-file-atomic@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ava/write-file-atomic/-/write-file-atomic-2.2.0.tgz#d625046f3495f1f5e372135f473909684b429247" + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + slide "^1.1.5" + +"@concordance/react@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@concordance/react/-/react-1.0.0.tgz#fcf3cad020e5121bfd1c61d05bc3516aac25f734" + dependencies: + arrify "^1.0.1" + +abbrev@1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" + +acorn-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + dependencies: + acorn "^3.0.4" + +acorn@^3.0.4: + version "3.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + +acorn@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75" + +after@0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" + +ajv-keywords@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" + +ajv@^4.7.0, ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + +ajv@^5.2.0: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.2.tgz#47c68d69e86f5d953103b0074a9430dc63da5e39" + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + json-schema-traverse "^0.3.0" + json-stable-stringify "^1.0.1" + +ansi-align@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" + dependencies: + string-width "^2.0.0" + +ansi-escapes@2.0.0, ansi-escapes@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b" + +ansi-escapes@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +ansi-styles@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.1.0.tgz#09c202d5c917ec23188caa5c9cb9179cd9547750" + dependencies: + color-convert "^1.0.0" + +ansi-styles@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178" + +any-promise@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + +anymatch@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" + dependencies: + arrify "^1.0.0" + micromatch "^2.1.5" + +app-root-path@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.0.1.tgz#cd62dcf8e4fd5a417efc664d2e5b10653c651b46" + +aproba@^1.0.3: + version "1.1.2" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1" + +archiver-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-1.3.0.tgz#e50b4c09c70bf3d680e32ff1b7994e9f9d895174" + dependencies: + glob "^7.0.0" + graceful-fs "^4.1.0" + lazystream "^1.0.0" + lodash "^4.8.0" + normalize-path "^2.0.0" + readable-stream "^2.0.0" + +archiver@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-2.0.0.tgz#ffb73ecccd8dd65b0019e1180f78092a053d43c4" + dependencies: + archiver-utils "^1.3.0" + async "^2.0.0" + buffer-crc32 "^0.2.1" + glob "^7.0.0" + lodash "^4.8.0" + readable-stream "^2.0.0" + tar-stream "^1.5.0" + walkdir "^0.0.11" + zip-stream "^1.2.0" + +are-we-there-yet@~1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +argparse@^1.0.7: + version "1.0.9" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + dependencies: + arr-flatten "^1.0.1" + +arr-exclude@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/arr-exclude/-/arr-exclude-1.0.0.tgz#dfc7c2e552a270723ccda04cf3128c8cbfe5c631" + +arr-flatten@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + +array-differ@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" + +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1, array-uniq@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + +arraybuffer.slice@0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca" + +arrify@^1.0.0, arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + +asn1@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + +async-each@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + +async-retry@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.1.3.tgz#1be664783337a0614999d543009a3b6e0de3609d" + dependencies: + retry "0.10.1" + +async@^2.0.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" + dependencies: + lodash "^4.14.0" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +auto-bind@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/auto-bind/-/auto-bind-1.1.0.tgz#93b864dc7ee01a326281775d5c75ca0a751e5961" + +ava-init@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/ava-init/-/ava-init-0.2.1.tgz#75ac4c8553326290d2866e63b62fa7035684bd58" + dependencies: + arr-exclude "^1.0.0" + execa "^0.7.0" + has-yarn "^1.0.0" + read-pkg-up "^2.0.0" + write-pkg "^3.1.0" + +ava@^0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/ava/-/ava-0.20.0.tgz#bdc0dd36453d7255e9f733305ab370c248381e41" + dependencies: + "@ava/babel-preset-stage-4" "^1.1.0" + "@ava/babel-preset-transform-test-files" "^3.0.0" + "@ava/write-file-atomic" "^2.2.0" + "@concordance/react" "^1.0.0" + ansi-escapes "^2.0.0" + ansi-styles "^3.1.0" + arr-flatten "^1.0.1" + array-union "^1.0.1" + array-uniq "^1.0.2" + arrify "^1.0.0" + auto-bind "^1.1.0" + ava-init "^0.2.0" + babel-core "^6.17.0" + bluebird "^3.0.0" + caching-transform "^1.0.0" + chalk "^1.0.0" + chokidar "^1.4.2" + clean-stack "^1.1.1" + clean-yaml-object "^0.1.0" + cli-cursor "^2.1.0" + cli-spinners "^1.0.0" + cli-truncate "^1.0.0" + co-with-promise "^4.6.0" + code-excerpt "^2.1.0" + common-path-prefix "^1.0.0" + concordance "^2.0.0" + convert-source-map "^1.2.0" + core-assert "^0.2.0" + currently-unhandled "^0.4.1" + debug "^2.2.0" + dot-prop "^4.1.0" + empower-core "^0.6.1" + equal-length "^1.0.0" + figures "^2.0.0" + find-cache-dir "^0.1.1" + fn-name "^2.0.0" + get-port "^3.0.0" + globby "^6.0.0" + has-flag "^2.0.0" + hullabaloo-config-manager "^1.1.0" + ignore-by-default "^1.0.0" + import-local "^0.1.1" + indent-string "^3.0.0" + is-ci "^1.0.7" + is-generator-fn "^1.0.0" + is-obj "^1.0.0" + is-observable "^0.2.0" + is-promise "^2.1.0" + js-yaml "^3.8.2" + last-line-stream "^1.0.0" + lodash.clonedeepwith "^4.5.0" + lodash.debounce "^4.0.3" + lodash.difference "^4.3.0" + lodash.flatten "^4.2.0" + loud-rejection "^1.2.0" + make-dir "^1.0.0" + matcher "^0.1.1" + md5-hex "^2.0.0" + meow "^3.7.0" + ms "^1.0.0" + multimatch "^2.1.0" + observable-to-promise "^0.5.0" + option-chain "^0.1.0" + package-hash "^2.0.0" + pkg-conf "^2.0.0" + plur "^2.0.0" + pretty-ms "^2.0.0" + require-precompiled "^0.1.0" + resolve-cwd "^1.0.0" + slash "^1.0.0" + source-map-support "^0.4.0" + stack-utils "^1.0.0" + strip-ansi "^3.0.1" + strip-bom-buf "^1.0.0" + supports-color "^3.2.3" + time-require "^0.1.2" + trim-off-newlines "^1.0.1" + unique-temp-dir "^1.0.0" + update-notifier "^2.1.0" + +aws-sdk@^2.82.0: + version "2.83.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.83.0.tgz#f0107183576d2e6093636ab89ccd9b546a531be7" + dependencies: + buffer "4.9.1" + crypto-browserify "1.0.9" + events "^1.1.1" + jmespath "0.15.0" + querystring "0.2.0" + sax "1.2.1" + url "0.10.3" + uuid "3.0.1" + xml2js "0.4.17" + xmlbuilder "4.2.1" + +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + +aws4@^1.2.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + +babel-cli@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-cli/-/babel-cli-6.24.1.tgz#207cd705bba61489b2ea41b5312341cf6aca2283" + dependencies: + babel-core "^6.24.1" + babel-polyfill "^6.23.0" + babel-register "^6.24.1" + babel-runtime "^6.22.0" + commander "^2.8.1" + convert-source-map "^1.1.0" + fs-readdir-recursive "^1.0.0" + glob "^7.0.0" + lodash "^4.2.0" + output-file-sync "^1.1.0" + path-is-absolute "^1.0.0" + slash "^1.0.0" + source-map "^0.5.0" + v8flags "^2.0.10" + optionalDependencies: + chokidar "^1.6.1" + +babel-code-frame@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" + dependencies: + chalk "^1.1.0" + esutils "^2.0.2" + js-tokens "^3.0.0" + +babel-core@^6.17.0, babel-core@^6.24.1: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.25.0.tgz#7dd42b0463c742e9d5296deb3ec67a9322dad729" + dependencies: + babel-code-frame "^6.22.0" + babel-generator "^6.25.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.25.0" + babel-traverse "^6.25.0" + babel-types "^6.25.0" + babylon "^6.17.2" + convert-source-map "^1.1.0" + debug "^2.1.1" + json5 "^0.5.0" + lodash "^4.2.0" + minimatch "^3.0.2" + path-is-absolute "^1.0.0" + private "^0.1.6" + slash "^1.0.0" + source-map "^0.5.0" + +babel-eslint@^7.2.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.3.tgz#b2fe2d80126470f5c19442dc757253a897710827" + dependencies: + babel-code-frame "^6.22.0" + babel-traverse "^6.23.1" + babel-types "^6.23.0" + babylon "^6.17.0" + +babel-generator@^6.1.0, babel-generator@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.25.0.tgz#33a1af70d5f2890aeb465a4a7793c1df6a9ea9fc" + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-types "^6.25.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.2.0" + source-map "^0.5.0" + trim-right "^1.0.1" + +babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" + dependencies: + babel-helper-explode-assignable-expression "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-call-delegate@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-explode-assignable-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-function-name@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" + dependencies: + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-get-function-arity@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-hoist-variables@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-regex@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz#d36e22fab1008d79d88648e32116868128456ce8" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + lodash "^4.2.0" + +babel-helper-remap-async-to-generator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helpers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-check-es2015-constants@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-espower@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/babel-plugin-espower/-/babel-plugin-espower-2.3.2.tgz#5516b8fcdb26c9f0e1d8160749f6e4c65e71271e" + dependencies: + babel-generator "^6.1.0" + babylon "^6.1.0" + call-matcher "^1.0.0" + core-js "^2.0.0" + espower-location-detector "^1.0.0" + espurify "^1.6.0" + estraverse "^4.1.1" + +babel-plugin-syntax-async-functions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + +babel-plugin-syntax-exponentiation-operator@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" + +babel-plugin-syntax-flow@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" + +babel-plugin-syntax-trailing-function-commas@^6.20.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" + +babel-plugin-transform-async-to-generator@^6.16.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" + dependencies: + babel-helper-remap-async-to-generator "^6.24.1" + babel-plugin-syntax-async-functions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-destructuring@^6.19.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-function-name@^6.9.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-modules-commonjs@^6.18.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz#d3e310b40ef664a36622200097c6d440298f2bfe" + dependencies: + babel-plugin-transform-strict-mode "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-parameters@^6.21.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" + dependencies: + babel-helper-call-delegate "^6.24.1" + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-spread@^6.8.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-sticky-regex@^6.8.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-unicode-regex@^6.11.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + regexpu-core "^2.0.0" + +babel-plugin-transform-exponentiation-operator@^6.8.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" + dependencies: + babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" + babel-plugin-syntax-exponentiation-operator "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-flow-strip-types@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" + dependencies: + babel-plugin-syntax-flow "^6.18.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-strict-mode@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-polyfill@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d" + dependencies: + babel-runtime "^6.22.0" + core-js "^2.4.0" + regenerator-runtime "^0.10.0" + +babel-preset-flow@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d" + dependencies: + babel-plugin-transform-flow-strip-types "^6.22.0" + +babel-register@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.24.1.tgz#7e10e13a2f71065bdfad5a1787ba45bca6ded75f" + dependencies: + babel-core "^6.24.1" + babel-runtime "^6.22.0" + core-js "^2.4.0" + home-or-tmp "^2.0.0" + lodash "^4.2.0" + mkdirp "^0.5.1" + source-map-support "^0.4.2" + +babel-runtime@^6.22.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.10.0" + +babel-template@^6.24.1, babel-template@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.25.0" + babel-types "^6.25.0" + babylon "^6.17.2" + lodash "^4.2.0" + +babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.25.0.tgz#2257497e2fcd19b89edc13c4c91381f9512496f1" + dependencies: + babel-code-frame "^6.22.0" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-types "^6.25.0" + babylon "^6.17.2" + debug "^2.2.0" + globals "^9.0.0" + invariant "^2.2.0" + lodash "^4.2.0" + +babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.25.0.tgz#70afb248d5660e5d18f811d91c8303b54134a18e" + dependencies: + babel-runtime "^6.22.0" + esutils "^2.0.2" + lodash "^4.2.0" + to-fast-properties "^1.0.1" + +babylon@^6.1.0, babylon@^6.15.0, babylon@^6.17.0, babylon@^6.17.2: + version "6.17.4" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.4.tgz#3e8b7402b88d22c3423e137a1577883b15ff869a" + +backo2@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +base64-arraybuffer@0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" + +base64-js@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" + +base64-js@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" + +bcrypt-pbkdf@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + dependencies: + tweetnacl "^0.14.3" + +better-assert@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" + dependencies: + callsite "1.0.0" + +binary-extensions@^1.0.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774" + +bl@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.1.tgz#cac328f7bee45730d404b692203fcb590e172d5e" + dependencies: + readable-stream "^2.0.5" + +blob@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + dependencies: + inherits "~2.0.0" + +bluebird@^2.9.13: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" + +bluebird@^3.0.0, bluebird@^3.3.1, bluebird@^3.4.1: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" + +bluebird@~3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.0.6.tgz#f2488f325782f66d174842f481992e2faba56f38" + +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + dependencies: + hoek "2.x.x" + +boxen@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.2.0.tgz#03478d84be7fe02189b80904d81d6a80384368f1" + dependencies: + ansi-align "^2.0.0" + camelcase "^4.0.0" + chalk "^2.0.1" + cli-boxes "^1.0.0" + string-width "^2.0.0" + term-size "^1.2.0" + widest-line "^1.0.0" + +brace-expansion@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" + +buf-compare@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buf-compare/-/buf-compare-1.0.1.tgz#fef28da8b8113a0a0db4430b0b6467b69730b34a" + +buffer-crc32@^0.2.1, buffer-crc32@~0.2.3: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + +buffer@4.9.1: + version "4.9.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +buffer@^3.0.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb" + dependencies: + base64-js "0.0.8" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-modules@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +bytes@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.5.0.tgz#4c9423ea2d252c270c41b2bdefeff9bb6b62c06a" + +caching-transform@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/caching-transform/-/caching-transform-1.0.1.tgz#6dbdb2f20f8d8fbce79f3e94e9d1742dcdf5c0a1" + dependencies: + md5-hex "^1.2.0" + mkdirp "^0.5.1" + write-file-atomic "^1.1.4" + +call-matcher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-matcher/-/call-matcher-1.0.1.tgz#5134d077984f712a54dad3cbf62de28dce416ca8" + dependencies: + core-js "^2.0.0" + deep-equal "^1.0.0" + espurify "^1.6.0" + estraverse "^4.0.0" + +call-signature@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/call-signature/-/call-signature-0.0.2.tgz#a84abc825a55ef4cb2b028bd74e205a65b9a4996" + +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + dependencies: + callsites "^0.2.0" + +callsite@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" + +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + +camelcase@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + +capture-stack-trace@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + +caw@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/caw/-/caw-2.0.0.tgz#11f8bddc2f801469952d5e3225ba98495a2fa0ff" + dependencies: + get-proxy "^1.0.1" + tunnel-agent "^0.4.0" + +chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.4.0.tgz#5199a3ddcd0c1efe23bc08c1b027b06176e0c64f" + dependencies: + ansi-styles "~1.0.0" + has-color "~0.1.0" + strip-ansi "~0.1.0" + +chalk@^2.0.0, chalk@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.0.1.tgz#dbec49436d2ae15f536114e76d14656cdbc0f44d" + dependencies: + ansi-styles "^3.1.0" + escape-string-regexp "^1.0.5" + supports-color "^4.0.0" + +chokidar@^1.4.2, chokidar@^1.6.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" + dependencies: + anymatch "^1.3.0" + async-each "^1.0.0" + glob-parent "^2.0.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^2.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + optionalDependencies: + fsevents "^1.0.0" + +chownr@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" + +ci-info@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.0.0.tgz#dc5285f2b4e251821683681c381c3388f46ec534" + +circular-json@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" + +clean-stack@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-1.3.0.tgz#9e821501ae979986c46b1d66d2d432db2fd4ae31" + +clean-yaml-object@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz#63fb110dc2ce1a84dc21f6d9334876d010ae8b68" + +cli-boxes@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" + +cli-cursor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" + dependencies: + restore-cursor "^1.0.1" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + dependencies: + restore-cursor "^2.0.0" + +cli-spinners@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" + +cli-spinners@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.0.0.tgz#ef987ed3d48391ac3dab9180b406a742180d6e6a" + +cli-truncate@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" + dependencies: + slice-ansi "0.0.4" + string-width "^1.0.1" + +cli-truncate@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-1.0.0.tgz#21eb91f47b3f6560f004db77a769b4668d9c5518" + dependencies: + slice-ansi "0.0.4" + string-width "^2.0.0" + +cli-width@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" + +clipboardy@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-1.1.4.tgz#51b17574fc682588e2dd295cfa6e6aa109eab5ee" + dependencies: + execa "^0.6.0" + +co-with-promise@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co-with-promise/-/co-with-promise-4.6.0.tgz#413e7db6f5893a60b942cf492c4bec93db415ab7" + dependencies: + pinkie-promise "^1.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +code-excerpt@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/code-excerpt/-/code-excerpt-2.1.0.tgz#5dcc081e88f4a7e3b554e9e35d7ef232d47f8147" + dependencies: + convert-to-spaces "^1.0.1" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +color-convert@^1.0.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" + dependencies: + color-name "^1.1.1" + +color-name@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d" + +combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" + dependencies: + delayed-stream "~1.0.0" + +commander@^2.8.1, commander@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + +commander@~2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4" + dependencies: + graceful-readlink ">= 1.0.0" + +common-path-prefix@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-1.0.0.tgz#cd52f6f0712e0baab97d6f9732874f22f47752c0" + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + +component-bind@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" + +component-emitter@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + +component-inherit@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" + +compress-commons@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-1.2.0.tgz#58587092ef20d37cb58baf000112c9278ff73b9f" + dependencies: + buffer-crc32 "^0.2.1" + crc32-stream "^2.0.0" + normalize-path "^2.0.0" + readable-stream "^2.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +concat-stream@^1.4.7, concat-stream@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +concordance@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/concordance/-/concordance-2.0.0.tgz#c3c5dbffa83c29537df202bded8fa1d6aa94e805" + dependencies: + esutils "^2.0.2" + fast-diff "^1.1.1" + function-name-support "^0.2.0" + js-string-escape "^1.0.1" + lodash.clonedeep "^4.5.0" + lodash.flattendeep "^4.4.0" + lodash.merge "^4.6.0" + md5-hex "^2.0.0" + moment "^2.18.1" + semver "^5.3.0" + well-known-symbols "^1.0.0" + +configstore@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.0.tgz#45df907073e26dfa1cf4b2d52f5b60545eaa11d1" + dependencies: + dot-prop "^4.1.0" + graceful-fs "^4.1.2" + make-dir "^1.0.0" + unique-string "^1.0.0" + write-file-atomic "^2.0.0" + xdg-basedir "^3.0.0" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + +content-disposition@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + +convert-source-map@^1.1.0, convert-source-map@^1.2.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" + +convert-stream@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/convert-stream/-/convert-stream-1.0.2.tgz#152a7f10f4635e2bd000425b1fd025d1f114ff4d" + dependencies: + bluebird "^3.4.1" + +convert-to-spaces@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/convert-to-spaces/-/convert-to-spaces-1.0.2.tgz#7e3e48bbe6d997b1417ddca2868204b4d3d85715" + +core-assert@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/core-assert/-/core-assert-0.2.1.tgz#f85e2cf9bfed28f773cc8b3fa5c5b69bdc02fe3f" + dependencies: + buf-compare "^1.0.0" + is-error "^2.2.0" + +core-js@^2.0.0, core-js@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cosmiconfig@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-1.1.0.tgz#0dea0f9804efdfb929fbb1b188e25553ea053d37" + dependencies: + graceful-fs "^4.1.2" + js-yaml "^3.4.3" + minimist "^1.2.0" + object-assign "^4.0.1" + os-homedir "^1.0.1" + parse-json "^2.2.0" + pinkie-promise "^2.0.0" + require-from-string "^1.1.0" + +crc32-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-2.0.0.tgz#e3cdd3b4df3168dd74e3de3fbbcb7b297fe908f4" + dependencies: + crc "^3.4.4" + readable-stream "^2.0.0" + +crc@^3.4.4: + version "3.4.4" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b" + +create-error-class@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" + dependencies: + capture-stack-trace "^1.0.0" + +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + dependencies: + boom "2.x.x" + +crypto-browserify@1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-1.0.9.tgz#cc5449685dfb85eb11c9828acc7cb87ab5bbfcc0" + +crypto-random-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" + +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + dependencies: + array-find-index "^1.0.1" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +date-fns@^1.27.2: + version "1.28.5" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.28.5.tgz#257cfc45d322df45ef5658665967ee841cd73faf" + +date-time@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/date-time/-/date-time-0.1.1.tgz#ed2f6d93d9790ce2fd66d5b5ff3edd5bbcbf3b07" + +debug@^2.1.1, debug@^2.2.0, debug@^2.6.8, debug@~2.6.4: + version "2.6.8" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" + dependencies: + ms "2.0.0" + +decamelize@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +decompress-response@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" + dependencies: + mimic-response "^1.0.0" + +decompress-tar@^4.0.0, decompress-tar@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.0.tgz#1f092ab698440558c72fc78e77d246d3ecb453b0" + dependencies: + file-type "^3.8.0" + is-stream "^1.1.0" + tar-stream "^1.5.2" + +decompress-tarbz2@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.0.tgz#fbab58d5de73f3fd213cac3af1c18334f51cb891" + dependencies: + decompress-tar "^4.1.0" + file-type "^3.8.0" + is-stream "^1.1.0" + pify "^2.3.0" + seek-bzip "^1.0.5" + unbzip2-stream "^1.0.9" + +decompress-targz@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.0.tgz#475b9c406be621ae836274802d9b25f9913ead59" + dependencies: + decompress-tar "^4.0.0" + file-type "^4.3.0" + is-stream "^1.1.0" + +decompress-unzip@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69" + dependencies: + file-type "^3.8.0" + get-stream "^2.2.0" + pify "^2.3.0" + yauzl "^2.4.2" + +decompress@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d" + dependencies: + decompress-tar "^4.0.0" + decompress-tarbz2 "^4.0.0" + decompress-targz "^4.0.0" + decompress-unzip "^4.0.1" + graceful-fs "^4.1.10" + make-dir "^1.0.0" + pify "^2.3.0" + strip-dirs "^2.0.0" + +deep-equal@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + +deep-extend@~0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + +del@^2.0.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" + dependencies: + globby "^5.0.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + rimraf "^2.2.8" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +deployment-type@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deployment-type/-/deployment-type-1.0.1.tgz#5f47e338bf5d2cd26abee971c60c584ea4af9bc1" + dependencies: + fs-extra "3.0.1" + path-exists "3.0.0" + path-type "2.0.0" + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + dependencies: + repeating "^2.0.0" + +detect-indent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + +docker-file-parser@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/docker-file-parser/-/docker-file-parser-1.0.2.tgz#b0c63d18ceee2ac5d5385968d077feab88c37e26" + +doctrine@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +dot-prop@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.1.1.tgz#a8493f0b7b5eeec82525b5c7587fa7de7ca859c1" + dependencies: + is-obj "^1.0.0" + +dotenv@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" + +download@^6.2.5: + version "6.2.5" + resolved "https://registry.yarnpkg.com/download/-/download-6.2.5.tgz#acd6a542e4cd0bb42ca70cfc98c9e43b07039714" + dependencies: + caw "^2.0.0" + content-disposition "^0.5.2" + decompress "^4.0.0" + ext-name "^5.0.0" + file-type "5.2.0" + filenamify "^2.0.0" + get-stream "^3.0.0" + got "^7.0.0" + make-dir "^1.0.0" + p-event "^1.0.0" + pify "^3.0.0" + +duplexer3@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + +ecc-jsbn@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + dependencies: + jsbn "~0.1.0" + +elegant-spinner@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" + +email-prompt@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/email-prompt/-/email-prompt-0.3.1.tgz#62f0fe9914a7388897ab42b5409dd0f37b086c06" + dependencies: + ansi-escapes "2.0.0" + chalk "1.1.3" + +email-validator@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/email-validator/-/email-validator-1.0.7.tgz#4621ca32fc741eb833ac98d5fb55670b7e056c95" + +empower-core@^0.6.1: + version "0.6.2" + resolved "https://registry.yarnpkg.com/empower-core/-/empower-core-0.6.2.tgz#5adef566088e31fba80ba0a36df47d7094169144" + dependencies: + call-signature "0.0.2" + core-js "^2.0.0" + +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + dependencies: + iconv-lite "~0.4.13" + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.0.tgz#7a90d833efda6cfa6eac0f4949dbb0fad3a63206" + dependencies: + once "^1.4.0" + +engine.io-client@~3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-3.1.1.tgz#415a9852badb14fa008fa3ef1e31608db6761325" + dependencies: + component-emitter "1.2.1" + component-inherit "0.0.3" + debug "~2.6.4" + engine.io-parser "~2.1.1" + has-cors "1.1.0" + indexof "0.0.1" + parsejson "0.0.3" + parseqs "0.0.5" + parseuri "0.0.5" + ws "~2.3.1" + xmlhttprequest-ssl "1.5.3" + yeast "0.1.2" + +engine.io-parser@~2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-2.1.1.tgz#e0fb3f0e0462f7f58bb77c1a52e9f5a7e26e4668" + dependencies: + after "0.8.2" + arraybuffer.slice "0.0.6" + base64-arraybuffer "0.1.5" + blob "0.0.4" + has-binary2 "~1.0.2" + +equal-length@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/equal-length/-/equal-length-1.0.1.tgz#21ca112d48ab24b4e1e7ffc0e5339d31fdfc274c" + +error-ex@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + dependencies: + is-arrayish "^0.2.1" + +es6-error@^4.0.1, es6-error@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.0.2.tgz#eec5c726eacef51b7f6b73c20db6e1b13b069c98" + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.4, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +eslint-scope@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.2.0.tgz#a2b3184111b198e02e9c7f3cca625a5e01c56b3d" + dependencies: + ajv "^5.2.0" + babel-code-frame "^6.22.0" + chalk "^1.1.3" + concat-stream "^1.6.0" + debug "^2.6.8" + doctrine "^2.0.0" + eslint-scope "^3.7.1" + espree "^3.4.3" + esquery "^1.0.0" + estraverse "^4.2.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + glob "^7.1.2" + globals "^9.17.0" + ignore "^3.3.3" + imurmurhash "^0.1.4" + inquirer "^3.0.6" + is-resolvable "^1.0.0" + js-yaml "^3.8.4" + json-stable-stringify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.4" + minimatch "^3.0.2" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + pluralize "^4.0.0" + progress "^2.0.0" + require-uncached "^1.0.3" + strip-json-comments "~2.0.1" + table "^4.0.1" + text-table "~0.2.0" + +espower-location-detector@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/espower-location-detector/-/espower-location-detector-1.0.0.tgz#a17b7ecc59d30e179e2bef73fb4137704cb331b5" + dependencies: + is-url "^1.2.1" + path-is-absolute "^1.0.0" + source-map "^0.5.0" + xtend "^4.0.0" + +espree@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.4.3.tgz#2910b5ccd49ce893c2ffffaab4fd8b3a31b82374" + dependencies: + acorn "^5.0.1" + acorn-jsx "^3.0.0" + +esprima@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + +espurify@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/espurify/-/espurify-1.7.0.tgz#1c5cf6cbccc32e6f639380bd4f991fab9ba9d226" + dependencies: + core-js "^2.0.0" + +esquery@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa" + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" + dependencies: + estraverse "^4.1.0" + object-assign "^4.0.1" + +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +events@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + +execa@^0.6.0: + version "0.6.3" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.6.3.tgz#57b69a594f081759c69e5370f0d17b9cb11658fe" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +exit-hook@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" + +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + dependencies: + is-posix-bracket "^0.1.0" + +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + dependencies: + fill-range "^2.1.0" + +ext-list@^2.0.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/ext-list/-/ext-list-2.2.2.tgz#0b98e64ed82f5acf0f2931babf69212ef52ddd37" + dependencies: + mime-db "^1.28.0" + +ext-name@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ext-name/-/ext-name-5.0.0.tgz#70781981d183ee15d13993c8822045c506c8f0a6" + dependencies: + ext-list "^2.0.0" + sort-keys-length "^1.0.0" + +extend@~3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +external-editor@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.4.tgz#1ed9199da9cbfe2ef2f7a31b2fde8b0d12368972" + dependencies: + iconv-lite "^0.4.17" + jschardet "^1.4.2" + tmp "^0.0.31" + +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + dependencies: + is-extglob "^1.0.0" + +extsprintf@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" + +fast-deep-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" + +fast-diff@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.1.tgz#0aea0e4e605b6a2189f0e936d4b7fbaf1b7cfd9b" + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + +fd-slicer@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" + dependencies: + pend "~1.2.0" + +figures@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" + dependencies: + escape-string-regexp "^1.0.5" + object-assign "^4.1.0" + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + +file-type@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6" + +file-type@^3.8.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" + +file-type@^4.3.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-4.4.0.tgz#1b600e5fca1fbdc6e80c0a70c71c8dba5f7906c5" + +filename-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + +filename-reserved-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz#abf73dfab735d045440abfea2d91f389ebbfa229" + +filenamify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/filenamify/-/filenamify-2.0.0.tgz#bd162262c0b6e94bfbcdcf19a3bbb3764f785695" + dependencies: + filename-reserved-regex "^2.0.0" + strip-outer "^1.0.0" + trim-repeated "^1.0.0" + +fill-range@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + dependencies: + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^1.1.3" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + +find-cache-dir@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9" + dependencies: + commondir "^1.0.1" + mkdirp "^0.5.1" + pkg-dir "^1.0.0" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + +flat-cache@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96" + dependencies: + circular-json "^0.3.1" + del "^2.0.2" + graceful-fs "^4.1.2" + write "^0.2.1" + +flow-bin@^0.49.1: + version "0.49.1" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.49.1.tgz#c9e456b3173a7535a4ffaf28956352c63bb8e3e9" + +flow-remove-types@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/flow-remove-types/-/flow-remove-types-1.2.1.tgz#58e261bf8b842bd234c86cafb982a1213aff0edb" + dependencies: + babylon "^6.15.0" + vlq "^0.2.1" + +fn-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fn-name/-/fn-name-2.0.1.tgz#5214d7537a4d06a4a301c0cc262feb84188002e7" + +for-in@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +for-own@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + dependencies: + for-in "^1.0.1" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + +fs-extra@3.0.1, fs-extra@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^3.0.0" + universalify "^0.1.0" + +fs-readdir-recursive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz#8cd1745c8b4f8a29c8caec392476921ba195f560" + +fs.promised@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/fs.promised/-/fs.promised-3.0.0.tgz#ab77379f7c1ad0939e1262a8c2ced93fa6c39d3b" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +fsevents@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4" + dependencies: + nan "^2.3.0" + node-pre-gyp "^0.6.36" + +fstream-ignore@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" + dependencies: + fstream "^1.0.0" + inherits "2" + minimatch "^3.0.0" + +fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +function-name-support@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/function-name-support/-/function-name-support-0.2.0.tgz#55d3bfaa6eafd505a50f9bc81fdf57564a0bb071" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +get-port@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-3.1.0.tgz#ef01b18a84ca6486970ff99e54446141a73ffd3e" + +get-proxy@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/get-proxy/-/get-proxy-1.1.0.tgz#894854491bc591b0f147d7ae570f5c678b7256eb" + dependencies: + rc "^1.1.2" + +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + +get-stream@^2.2.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" + dependencies: + object-assign "^4.0.1" + pinkie-promise "^2.0.0" + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + dependencies: + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + dependencies: + is-glob "^2.0.0" + +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^9.0.0, globals@^9.17.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + +globby@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" + dependencies: + array-union "^1.0.1" + arrify "^1.0.0" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +globby@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +got@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" + dependencies: + create-error-class "^3.0.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" + is-redirect "^1.0.0" + is-retry-allowed "^1.0.0" + is-stream "^1.0.0" + lowercase-keys "^1.0.0" + safe-buffer "^5.0.1" + timed-out "^4.0.0" + unzip-response "^2.0.1" + url-parse-lax "^1.0.0" + +got@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" + dependencies: + decompress-response "^3.2.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" + is-plain-obj "^1.1.0" + is-retry-allowed "^1.0.0" + is-stream "^1.0.0" + isurl "^1.0.0-alpha5" + lowercase-keys "^1.0.0" + p-cancelable "^0.3.0" + p-timeout "^1.1.1" + safe-buffer "^5.0.1" + timed-out "^4.0.0" + url-parse-lax "^1.0.0" + url-to-options "^1.0.1" + +graceful-fs@^4.1.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +"graceful-readlink@>= 1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-binary2@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-binary2/-/has-binary2-1.0.2.tgz#e83dba49f0b9be4d026d27365350d9f03f54be98" + dependencies: + isarray "2.0.1" + +has-color@~0.1.0: + version "0.1.7" + resolved "https://registry.yarnpkg.com/has-color/-/has-color-0.1.7.tgz#67144a5260c34fc3cca677d041daf52fe7b78b2f" + +has-cors@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + +has-flag@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + +has-symbol-support-x@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.0.tgz#442d89b1d0ac6cf5ff2f7b916ee539869b93a256" + +has-to-string-tag-x@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.0.tgz#49d7bcde85c2409be38ac327e3e119a451657c7b" + dependencies: + has-symbol-support-x "^1.4.0" + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +has-yarn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-yarn/-/has-yarn-1.0.0.tgz#89e25db604b725c8f5976fff0addc921b828a5a7" + +hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + +home-or-tmp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.1" + +hosted-git-info@^2.1.4: + version "2.5.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" + +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +hullabaloo-config-manager@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/hullabaloo-config-manager/-/hullabaloo-config-manager-1.1.1.tgz#1d9117813129ad035fd9e8477eaf066911269fe3" + dependencies: + dot-prop "^4.1.0" + es6-error "^4.0.2" + graceful-fs "^4.1.11" + indent-string "^3.1.0" + json5 "^0.5.1" + lodash.clonedeep "^4.5.0" + lodash.clonedeepwith "^4.5.0" + lodash.isequal "^4.5.0" + lodash.merge "^4.6.0" + md5-hex "^2.0.0" + package-hash "^2.0.0" + pkg-dir "^2.0.0" + resolve-from "^3.0.0" + safe-buffer "^5.0.1" + +iconv-lite@^0.4.17, iconv-lite@~0.4.13: + version "0.4.18" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" + +ieee754@^1.1.4: + version "1.1.8" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" + +ignore-by-default@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" + +ignore@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d" + +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + +import-local@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-0.1.1.tgz#b1179572aacdc11c6a91009fb430dbcab5f668a8" + dependencies: + pkg-dir "^2.0.0" + resolve-cwd "^2.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + dependencies: + repeating "^2.0.0" + +indent-string@^3.0.0, indent-string@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.1.0.tgz#08ff4334603388399b329e6b9538dc7a3cf5de7d" + +indexof@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +ini@^1.3.4, ini@~1.3.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" + +inquirer@^3.0.6, inquirer@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.2.0.tgz#45b44c2160c729d7578c54060b3eed94487bb42b" + dependencies: + ansi-escapes "^2.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^2.0.4" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + +invariant@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" + dependencies: + loose-envify "^1.0.0" + +irregular-plurals@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.3.0.tgz#7af06931bdf74be33dcf585a13e06fccc16caecf" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + +is-ci@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" + dependencies: + ci-info "^1.0.0" + +is-dotfile@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + dependencies: + is-primitive "^2.0.0" + +is-error@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-error/-/is-error-2.2.1.tgz#684a96d84076577c98f4cdb40c6d26a5123bf19c" + +is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + +is-finite@^1.0.0, is-finite@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-generator-fn@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-1.0.0.tgz#969d49e1bb3329f6bb7f09089be26578b2ddd46a" + +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + dependencies: + is-extglob "^1.0.0" + +is-natural-number@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" + +is-npm@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" + +is-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + dependencies: + kind-of "^3.0.2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + +is-obj@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + +is-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" + +is-observable@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-0.2.0.tgz#b361311d83c6e5d726cabf5e250b0237106f5ae2" + dependencies: + symbol-observable "^0.2.2" + +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + dependencies: + path-is-inside "^1.0.1" + +is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + +is-posix-bracket@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +is-redirect@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24" + +is-resolvable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" + dependencies: + tryit "^1.0.1" + +is-retry-allowed@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" + +is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +is-url@^1.2.1, is-url@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.2.tgz#498905a593bf47cc2d9e7f738372bbf7696c7f26" + +is-utf8@^0.2.0, is-utf8@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isarray@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.1.tgz#a37d94ed9cda2d59865c9f76fe596ee1f338741e" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +isurl@^1.0.0-alpha5: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" + dependencies: + has-to-string-tag-x "^1.2.0" + is-object "^1.0.1" + +jmespath@0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217" + +js-string-escape@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" + +js-tokens@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +js-yaml@^3.4.3, js-yaml@^3.8.2, js-yaml@^3.8.4: + version "3.9.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.0.tgz#4ffbbf25c2ac963b8299dc74da7e3740de1c18ce" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + +jschardet@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.4.2.tgz#2aa107f142af4121d145659d44f50830961e699a" + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +json5@^0.5.0, json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + +jsonfile@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66" + optionalDependencies: + graceful-fs "^4.1.6" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +jsprim@^1.2.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" + dependencies: + assert-plus "1.0.0" + extsprintf "1.0.2" + json-schema "0.2.3" + verror "1.3.6" + +kind-of@^3.0.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + +last-line-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/last-line-stream/-/last-line-stream-1.0.0.tgz#d1b64d69f86ff24af2d04883a2ceee14520a5600" + dependencies: + through2 "^2.0.0" + +latest-version@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" + dependencies: + package-json "^4.0.0" + +lazystream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4" + dependencies: + readable-stream "^2.0.5" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lint-staged@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-4.0.1.tgz#05365469898439dbade8a455893cf11e24d12b0f" + dependencies: + app-root-path "^2.0.0" + cosmiconfig "^1.1.0" + execa "^0.7.0" + listr "^0.12.0" + lodash.chunk "^4.2.0" + minimatch "^3.0.0" + npm-which "^3.0.1" + p-map "^1.1.1" + staged-git-files "0.0.4" + +listr-silent-renderer@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" + +listr-update-renderer@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz#ca80e1779b4e70266807e8eed1ad6abe398550f9" + dependencies: + chalk "^1.1.3" + cli-truncate "^0.2.1" + elegant-spinner "^1.0.1" + figures "^1.7.0" + indent-string "^3.0.0" + log-symbols "^1.0.2" + log-update "^1.0.2" + strip-ansi "^3.0.1" + +listr-verbose-renderer@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.4.0.tgz#44dc01bb0c34a03c572154d4d08cde9b1dc5620f" + dependencies: + chalk "^1.1.3" + cli-cursor "^1.0.2" + date-fns "^1.27.2" + figures "^1.7.0" + +listr@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/listr/-/listr-0.12.0.tgz#6bce2c0f5603fa49580ea17cd6a00cc0e5fa451a" + dependencies: + chalk "^1.1.3" + cli-truncate "^0.2.1" + figures "^1.7.0" + indent-string "^2.1.0" + is-promise "^2.1.0" + is-stream "^1.1.0" + listr-silent-renderer "^1.1.1" + listr-update-renderer "^0.2.0" + listr-verbose-renderer "^0.4.0" + log-symbols "^1.0.2" + log-update "^1.0.2" + ora "^0.2.3" + p-map "^1.1.1" + rxjs "^5.0.0-beta.11" + stream-to-observable "^0.1.0" + strip-ansi "^3.0.1" + +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash.chunk@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.chunk/-/lodash.chunk-4.2.0.tgz#66e5ce1f76ed27b4303d8c6512e8d1216e8106bc" + +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + +lodash.clonedeepwith@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeepwith/-/lodash.clonedeepwith-4.5.0.tgz#6ee30573a03a1a60d670a62ef33c10cf1afdbdd4" + +lodash.debounce@^4.0.3: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + +lodash.difference@^4.3.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + +lodash.flatten@^4.2.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + +lodash.merge@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.0.tgz#69884ba144ac33fe699737a6086deffadd0f89c5" + +lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.8.0: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + +log-symbols@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" + dependencies: + chalk "^1.0.0" + +log-update@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1" + dependencies: + ansi-escapes "^1.0.0" + cli-cursor "^1.0.2" + +loose-envify@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +loud-rejection@^1.0.0, loud-rejection@^1.2.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + +lowercase-keys@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" + +lru-cache@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +make-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978" + dependencies: + pify "^2.3.0" + +map-obj@^1.0.0, map-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + +matcher@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/matcher/-/matcher-0.1.2.tgz#ef20cbde64c24c50cc61af5b83ee0b1b8ff00101" + dependencies: + escape-string-regexp "^1.0.4" + +md5-hex@^1.2.0, md5-hex@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-1.3.0.tgz#d2c4afe983c4370662179b8cad145219135046c4" + dependencies: + md5-o-matic "^0.1.1" + +md5-hex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-2.0.0.tgz#d0588e9f1c74954492ecd24ac0ac6ce997d92e33" + dependencies: + md5-o-matic "^0.1.1" + +md5-o-matic@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/md5-o-matic/-/md5-o-matic-0.1.1.tgz#822bccd65e117c514fab176b25945d54100a03c3" + +meow@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + +micromatch@^2.1.5: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + +mime-db@^1.28.0: + version "1.29.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.29.0.tgz#48d26d235589651704ac5916ca06001914266878" + +mime-db@~1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" + +mime-types@^2.1.12, mime-types@~2.1.7: + version "2.1.15" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" + dependencies: + mime-db "~1.27.0" + +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + +mimic-response@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.0.tgz#df3d3652a73fded6b9b0b24146e6fd052353458e" + +minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@^1.1.3, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +mkdirp-promise@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1" + dependencies: + mkdirp "*" + +mkdirp@*, "mkdirp@>=0.5 0", mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +moment@^2.18.1: + version "2.18.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" + +ms@2.0.0, ms@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +ms@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-1.0.0.tgz#59adcd22edc543f7b5381862d31387b1f4bc9473" + +multimatch@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" + dependencies: + array-differ "^1.0.0" + array-union "^1.0.1" + arrify "^1.0.0" + minimatch "^3.0.0" + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + +nan@^2.3.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45" + +native-or-bluebird@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz#39c47bfd7825d1fb9ffad32210ae25daadf101c9" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + +node-fetch@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.1.tgz#899cb3d0a3c92f952c47f1b876f4c8aeabd400d5" + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +node-pre-gyp@^0.6.36: + version "0.6.36" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786" + dependencies: + mkdirp "^0.5.1" + nopt "^4.0.1" + npmlog "^4.0.2" + rc "^1.1.7" + request "^2.81.0" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^2.2.1" + tar-pack "^3.4.0" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: + version "2.4.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.0.0, normalize-path@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + dependencies: + remove-trailing-separator "^1.0.1" + +npm-path@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/npm-path/-/npm-path-2.0.3.tgz#15cff4e1c89a38da77f56f6055b24f975dfb2bbe" + dependencies: + which "^1.2.10" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + +npm-which@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/npm-which/-/npm-which-3.0.1.tgz#9225f26ec3a285c209cae67c3b11a6b4ab7140aa" + dependencies: + commander "^2.9.0" + npm-path "^2.0.2" + which "^1.2.10" + +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +oauth-sign@~0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + +object-assign@^4.0.1, object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object-component@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" + +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +observable-to-promise@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/observable-to-promise/-/observable-to-promise-0.5.0.tgz#c828f0f0dc47e9f86af8a4977c5d55076ce7a91f" + dependencies: + is-observable "^0.2.0" + symbol-observable "^1.0.4" + +once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +onetime@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + dependencies: + mimic-fn "^1.0.0" + +opn@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.1.0.tgz#72ce2306a17dbea58ff1041853352b4a8fc77519" + dependencies: + is-wsl "^1.1.0" + +option-chain@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/option-chain/-/option-chain-0.1.1.tgz#e9b811e006f1c0f54802f28295bfc8970f8dcfbd" + dependencies: + object-assign "^4.0.1" + +optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +ora@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4" + dependencies: + chalk "^1.1.1" + cli-cursor "^1.0.2" + cli-spinners "^0.1.2" + object-assign "^4.0.1" + +ora@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-1.3.0.tgz#80078dd2b92a934af66a3ad72a5b910694ede51a" + dependencies: + chalk "^1.1.1" + cli-cursor "^2.1.0" + cli-spinners "^1.0.0" + log-symbols "^1.0.2" + +os-homedir@^1.0.0, os-homedir@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +os-shim@^0.1.2: + version "0.1.3" + resolved "https://registry.yarnpkg.com/os-shim/-/os-shim-0.1.3.tgz#6b62c3791cf7909ea35ed46e17658bb417cb3917" + +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +osenv@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +output-file-sync@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/output-file-sync/-/output-file-sync-1.1.2.tgz#d0a33eefe61a205facb90092e826598d5245ce76" + dependencies: + graceful-fs "^4.1.4" + mkdirp "^0.5.1" + object-assign "^4.1.0" + +p-cancelable@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" + +p-event@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-event/-/p-event-1.3.0.tgz#8e6b4f4f65c72bc5b6fe28b75eda874f96a4a085" + dependencies: + p-timeout "^1.1.1" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + +p-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +p-map@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a" + +p-timeout@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.0.tgz#9820f99434c5817868b4f34809ee5291660d5b6c" + dependencies: + p-finally "^1.0.0" + +package-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-1.2.0.tgz#003e56cd57b736a6ed6114cc2b81542672770e44" + dependencies: + md5-hex "^1.3.0" + +package-hash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/package-hash/-/package-hash-2.0.0.tgz#78ae326c89e05a4d813b68601977af05c00d2a0d" + dependencies: + graceful-fs "^4.1.11" + lodash.flattendeep "^4.4.0" + md5-hex "^2.0.0" + release-zalgo "^1.0.0" + +package-json@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" + dependencies: + got "^6.7.1" + registry-auth-token "^3.0.1" + registry-url "^3.0.3" + semver "^5.1.0" + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + +parse-ms@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-0.1.2.tgz#dd3fa25ed6c2efc7bdde12ad9b46c163aa29224e" + +parse-ms@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-1.0.1.tgz#56346d4749d78f23430ca0c713850aef91aa361d" + +parsejson@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab" + dependencies: + better-assert "~1.0.0" + +parseqs@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" + dependencies: + better-assert "~1.0.0" + +parseuri@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" + dependencies: + better-assert "~1.0.0" + +path-exists@3.0.0, path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-is-inside@^1.0.1, path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + +path-type@2.0.0, path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +pend@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + +pify@^2.0.0, pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + +pinkie-promise@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-1.0.0.tgz#d1da67f5482563bb7cf57f286ae2822ecfbf3670" + dependencies: + pinkie "^1.0.0" + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + dependencies: + pinkie "^2.0.0" + +pinkie@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-1.0.0.tgz#5a47f28ba1015d0201bda7bf0f358e47bec8c7e4" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + +pipe-streams-to-promise@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/pipe-streams-to-promise/-/pipe-streams-to-promise-0.2.0.tgz#11c89724b1c35e411d1dd40972a6b73b6aa74d47" + dependencies: + bluebird "^2.9.13" + stream-to-promise "^1.0.4" + +pkg-conf@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-conf/-/pkg-conf-2.0.0.tgz#071c87650403bccfb9c627f58751bfe47c067279" + dependencies: + find-up "^2.0.0" + load-json-file "^2.0.0" + +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + dependencies: + find-up "^1.0.0" + +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + dependencies: + find-up "^2.1.0" + +plur@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/plur/-/plur-1.0.0.tgz#db85c6814f5e5e5a3b49efc28d604fec62975156" + +plur@^2.0.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/plur/-/plur-2.1.2.tgz#7482452c1a0f508e3e344eaec312c91c29dc655a" + dependencies: + irregular-plurals "^1.0.0" + +pluralize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-4.0.0.tgz#59b708c1c0190a2f692f1c7618c446b052fd1762" + +pre-commit@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/pre-commit/-/pre-commit-1.2.2.tgz#dbcee0ee9de7235e57f79c56d7ce94641a69eec6" + dependencies: + cross-spawn "^5.0.1" + spawn-sync "^1.0.15" + which "1.2.x" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + +prepend-http@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + +prettier@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.5.2.tgz#7ea0751da27b93bfb6cecfcec509994f52d83bb3" + +pretty-ms@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-0.2.2.tgz#da879a682ff33a37011046f13d627f67c73b84f6" + dependencies: + parse-ms "^0.1.0" + +pretty-ms@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-2.1.0.tgz#4257c256df3fb0b451d6affaab021884126981dc" + dependencies: + is-finite "^1.0.1" + parse-ms "^1.0.0" + plur "^1.0.0" + +private@^0.1.6: + version "0.1.7" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1" + +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +pump@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.2.tgz#3b3ee6512f94f0e575538c17995f9f16990a5d51" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + +randomatic@^1.1.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +rc@^1.0.1, rc@^1.1.2, rc@^1.1.6, rc@^1.1.7: + version "1.2.1" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95" + dependencies: + deep-extend "~0.4.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + safe-buffer "~5.1.1" + string_decoder "~1.0.3" + util-deprecate "~1.0.1" + +readdirp@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" + dependencies: + graceful-fs "^4.1.2" + minimatch "^3.0.2" + readable-stream "^2.0.2" + set-immediate-shim "^1.0.1" + +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + +regenerate@^1.2.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260" + +regenerator-runtime@^0.10.0: + version "0.10.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" + +regex-cache@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145" + dependencies: + is-equal-shallow "^0.1.3" + is-primitive "^2.0.0" + +regexpu-core@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +registry-auth-token@^3.0.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.1.tgz#fb0d3289ee0d9ada2cbb52af5dfe66cb070d3006" + dependencies: + rc "^1.1.6" + safe-buffer "^5.0.1" + +registry-url@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942" + dependencies: + rc "^1.0.1" + +regjsgen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + +regjsparser@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + dependencies: + jsesc "~0.5.0" + +release-zalgo@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/release-zalgo/-/release-zalgo-1.0.0.tgz#09700b7e5074329739330e535c5a90fb67851730" + dependencies: + es6-error "^4.0.1" + +remove-trailing-separator@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz#69b062d978727ad14dc6b56ba4ab772fd8d70511" + +repeat-element@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + +request@^2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +require-from-string@^1.1.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-1.2.1.tgz#529c9ccef27380adfec9a2f965b649bbee636418" + +require-precompiled@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/require-precompiled/-/require-precompiled-0.1.0.tgz#5a1b52eb70ebed43eb982e974c85ab59571e56fa" + +require-uncached@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + dependencies: + caller-path "^0.1.0" + resolve-from "^1.0.0" + +resolve-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-1.0.0.tgz#4eaeea41ed040d1702457df64a42b2b07d246f9f" + dependencies: + resolve-from "^2.0.0" + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + dependencies: + resolve-from "^3.0.0" + +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + +resolve-from@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-2.0.0.tgz#9480ab20e94ffa1d9e80a804c7ea147611966b57" + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + +restore-cursor@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" + dependencies: + exit-hook "^1.0.0" + onetime "^1.0.0" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +resumer@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/resumer/-/resumer-0.0.0.tgz#f1e8f461e4064ba39e82af3cdc2a8c893d076759" + dependencies: + through "~2.3.4" + +retry@0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" + +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" + dependencies: + glob "^7.0.5" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + dependencies: + is-promise "^2.1.0" + +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + +rxjs@^5.0.0-beta.11: + version "5.4.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.2.tgz#2a3236fcbf03df57bae06fd6972fd99e5c08fcf7" + dependencies: + symbol-observable "^1.0.1" + +safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + +safe-buffer@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" + +sax@1.2.1, sax@>=0.6.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a" + +seek-bzip@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc" + dependencies: + commander "~2.8.1" + +semver-diff@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36" + dependencies: + semver "^5.0.3" + +"semver@2 || 3 || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + +set-immediate-shim@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + +slice-ansi@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" + +slide@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" + +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + dependencies: + hoek "2.x.x" + +socket.io-client@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-2.0.3.tgz#6caf4aff9f85b19fd91b6ce13d69adb564f8873b" + dependencies: + backo2 "1.0.2" + base64-arraybuffer "0.1.5" + component-bind "1.0.0" + component-emitter "1.2.1" + debug "~2.6.4" + engine.io-client "~3.1.0" + has-cors "1.1.0" + indexof "0.0.1" + object-component "0.0.3" + parseqs "0.0.5" + parseuri "0.0.5" + socket.io-parser "~3.1.1" + to-array "0.1.4" + +socket.io-parser@~3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-3.1.2.tgz#dbc2282151fc4faebbe40aeedc0772eba619f7f2" + dependencies: + component-emitter "1.2.1" + debug "~2.6.4" + has-binary2 "~1.0.2" + isarray "2.0.1" + +sort-keys-length@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sort-keys-length/-/sort-keys-length-1.0.1.tgz#9cb6f4f4e9e48155a6aa0671edd336ff1479a188" + dependencies: + sort-keys "^1.0.0" + +sort-keys@^1.0.0, sort-keys@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" + +sort-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128" + dependencies: + is-plain-obj "^1.0.0" + +source-map-support@^0.4.0, source-map-support@^0.4.2: + version "0.4.15" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" + dependencies: + source-map "^0.5.6" + +source-map@^0.5.0, source-map@^0.5.6: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + +spawn-sync@^1.0.15: + version "1.0.15" + resolved "https://registry.yarnpkg.com/spawn-sync/-/spawn-sync-1.0.15.tgz#b00799557eb7fb0c8376c29d44e8a1ea67e57476" + dependencies: + concat-stream "^1.4.7" + os-shim "^0.1.2" + +spdx-correct@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" + dependencies: + spdx-license-ids "^1.0.2" + +spdx-expression-parse@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" + +spdx-license-ids@^1.0.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" + +split-array@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split-array/-/split-array-1.0.1.tgz#7d0c10366705f3aa4620529ab755bf7ed2220da1" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + +sshpk@^1.7.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + +stack-utils@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620" + +staged-git-files@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-0.0.4.tgz#d797e1b551ca7a639dec0237dc6eb4bb9be17d35" + +stream-to-array@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-2.3.0.tgz#bbf6b39f5f43ec30bc71babcb37557acecf34353" + dependencies: + any-promise "^1.1.0" + +stream-to-observable@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe" + +stream-to-promise@^1.0.4: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stream-to-promise/-/stream-to-promise-1.1.1.tgz#838f5df7c92b8c9ba8fb95d7aa3ac312eec7527f" + dependencies: + bluebird "~3.0.6" + stream-to-array "~2.3.0" + +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string-width@^2.0.0, string-width@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.0.tgz#030664561fc146c9423ec7d978fe2457437fe6d0" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + +stringstream@~0.0.4: + version "0.0.5" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + +strip-ansi@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991" + +strip-bom-buf@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz#1cb45aaf57530f4caf86c7f75179d2c9a51dd572" + dependencies: + is-utf8 "^0.2.1" + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + dependencies: + is-utf8 "^0.2.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +strip-dirs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.0.0.tgz#610cdb2928200da0004f41dcb90fc95cd919a0b6" + dependencies: + is-natural-number "^4.0.1" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + dependencies: + get-stdin "^4.0.1" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + +strip-outer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-outer/-/strip-outer-1.0.0.tgz#aac0ba60d2e90c5d4f275fd8869fd9a2d310ffb8" + dependencies: + escape-string-regexp "^1.0.2" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + dependencies: + has-flag "^1.0.0" + +supports-color@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.0.tgz#ad986dc7eb2315d009b4d77c8169c2231a684037" + dependencies: + has-flag "^2.0.0" + +symbol-observable@^0.2.2: + version "0.2.4" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40" + +symbol-observable@^1.0.1, symbol-observable@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" + +table@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.1.tgz#a8116c133fac2c61f4a420ab6cdf5c4d61f0e435" + dependencies: + ajv "^4.7.0" + ajv-keywords "^1.0.0" + chalk "^1.1.1" + lodash "^4.0.0" + slice-ansi "0.0.4" + string-width "^2.0.0" + +tar-fs@^1.15.3: + version "1.15.3" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-1.15.3.tgz#eccf935e941493d8151028e636e51ce4c3ca7f20" + dependencies: + chownr "^1.0.1" + mkdirp "^0.5.1" + pump "^1.0.0" + tar-stream "^1.1.2" + +tar-pack@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984" + dependencies: + debug "^2.2.0" + fstream "^1.0.10" + fstream-ignore "^1.0.5" + once "^1.3.3" + readable-stream "^2.1.4" + rimraf "^2.5.1" + tar "^2.2.1" + uid-number "^0.0.6" + +tar-stream@^1.1.2, tar-stream@^1.5.0, tar-stream@^1.5.2: + version "1.5.4" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.4.tgz#36549cf04ed1aee9b2a30c0143252238daf94016" + dependencies: + bl "^1.0.0" + end-of-stream "^1.0.0" + readable-stream "^2.0.0" + xtend "^4.0.0" + +tar@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + dependencies: + block-stream "*" + fstream "^1.0.2" + inherits "2" + +term-size@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" + dependencies: + execa "^0.7.0" + +text-table@^0.2.0, text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + +then-sleep@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/then-sleep/-/then-sleep-1.0.1.tgz#759823bdc4de56ba2a20812868eb872a803ed1f9" + dependencies: + native-or-bluebird "^1.2.0" + +through2@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" + dependencies: + readable-stream "^2.1.5" + xtend "~4.0.1" + +through@^2.3.6, through@~2.3.4: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + +time-require@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/time-require/-/time-require-0.1.2.tgz#f9e12cb370fc2605e11404582ba54ef5ca2b2d98" + dependencies: + chalk "^0.4.0" + date-time "^0.1.1" + pretty-ms "^0.2.1" + text-table "^0.2.0" + +timed-out@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" + +tmp-promise@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-1.0.3.tgz#3b450927ab78c6aedca5e628c677f536cae38bc5" + dependencies: + bluebird "^3.3.1" + tmp "0.0.31" + +tmp@0.0.31, tmp@^0.0.31: + version "0.0.31" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" + dependencies: + os-tmpdir "~1.0.1" + +to-array@0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" + +to-fast-properties@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + +tough-cookie@~2.3.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" + dependencies: + punycode "^1.4.1" + +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + +trim-off-newlines@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz#9f9ba9d9efa8764c387698bcbfeb2c848f11adb3" + +trim-repeated@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-repeated/-/trim-repeated-1.0.0.tgz#e3646a2ea4e891312bf7eace6cfb05380bc01c21" + dependencies: + escape-string-regexp "^1.0.2" + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + +tryit@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" + +tunnel-agent@^0.4.0: + version "0.4.3" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + dependencies: + prelude-ls "~1.1.2" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + +uid-number@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" + +uid-promise@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/uid-promise/-/uid-promise-1.0.0.tgz#68ef7c70a19dea4d637c7e3df2e0e548106f1a37" + +uid2@0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" + +ultron@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864" + +unbzip2-stream@^1.0.9: + version "1.2.4" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.4.tgz#8c84c84d5b4cc28fc1f9f577203bbd3cb860a16a" + dependencies: + buffer "^3.0.1" + through "^2.3.6" + +unique-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" + dependencies: + crypto-random-string "^1.0.0" + +unique-temp-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-temp-dir/-/unique-temp-dir-1.0.0.tgz#6dce95b2681ca003eebfb304a415f9cbabcc5385" + dependencies: + mkdirp "^0.5.1" + os-tmpdir "^1.0.1" + uid2 "0.0.3" + +universalify@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.0.tgz#9eb1c4651debcc670cc94f1a75762332bb967778" + +unzip-response@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" + +update-notifier@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.2.0.tgz#1b5837cf90c0736d88627732b661c138f86de72f" + dependencies: + boxen "^1.0.0" + chalk "^1.0.0" + configstore "^3.0.0" + import-lazy "^2.1.0" + is-npm "^1.0.0" + latest-version "^3.0.0" + semver-diff "^2.0.0" + xdg-basedir "^3.0.0" + +url-parse-lax@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" + dependencies: + prepend-http "^1.0.1" + +url-to-options@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" + +url@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64" + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +user-home@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +uuid@3.0.1, uuid@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" + +v8flags@^2.0.10: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" + dependencies: + user-home "^1.1.1" + +validate-npm-package-license@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + dependencies: + spdx-correct "~1.0.0" + spdx-expression-parse "~1.0.0" + +verror@1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" + dependencies: + extsprintf "1.0.2" + +vlq@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.2.tgz#e316d5257b40b86bb43cb8d5fea5d7f54d6b0ca1" + +walkdir@^0.0.11: + version "0.0.11" + resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.0.11.tgz#a16d025eb931bd03b52f308caed0f40fcebe9532" + +well-known-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/well-known-symbols/-/well-known-symbols-1.0.0.tgz#73c78ae81a7726a8fa598e2880801c8b16225518" + +which@1.2.x, which@^1.2.10, which@^1.2.9: + version "1.2.14" + resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + dependencies: + string-width "^1.0.2" + +widest-line@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-1.0.0.tgz#0c09c85c2a94683d0d7eaf8ee097d564bf0e105c" + dependencies: + string-width "^1.0.1" + +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +write-file-atomic@^1.1.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.4.tgz#f807a4f0b1d9e913ae7a48112e6cc3af1991b45f" + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + slide "^1.1.5" + +write-file-atomic@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.1.0.tgz#1769f4b551eedce419f0505deae2e26763542d37" + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + slide "^1.1.5" + +write-json-file@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.2.0.tgz#51862506bbb3b619eefab7859f1fd6c6d0530876" + dependencies: + detect-indent "^5.0.0" + graceful-fs "^4.1.2" + make-dir "^1.0.0" + pify "^2.0.0" + sort-keys "^1.1.1" + write-file-atomic "^2.0.0" + +write-pkg@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-3.1.0.tgz#030a9994cc9993d25b4e75a9f1a1923607291ce9" + dependencies: + sort-keys "^2.0.0" + write-json-file "^2.2.0" + +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + dependencies: + mkdirp "^0.5.1" + +ws@~2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-2.3.1.tgz#6b94b3e447cb6a363f785eaf94af6359e8e81c80" + dependencies: + safe-buffer "~5.0.1" + ultron "~1.1.0" + +xdg-basedir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" + +xml2js@0.4.17: + version "0.4.17" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868" + dependencies: + sax ">=0.6.0" + xmlbuilder "^4.1.0" + +xmlbuilder@4.2.1, xmlbuilder@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5" + dependencies: + lodash "^4.0.0" + +xmlhttprequest-ssl@1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" + +xtend@^4.0.0, xtend@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + +yauzl@^2.4.2: + version "2.8.0" + resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.8.0.tgz#79450aff22b2a9c5a41ef54e02db907ccfbf9ee2" + dependencies: + buffer-crc32 "~0.2.3" + fd-slicer "~1.0.1" + +yeast@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" + +zip-stream@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-1.2.0.tgz#a8bc45f4c1b49699c6b90198baacaacdbcd4ba04" + dependencies: + archiver-utils "^1.3.0" + compress-commons "^1.2.0" + lodash "^4.8.0" + readable-stream "^2.0.0"