From 932889424a1691c110e2793d44091d832f132a7c Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Thu, 31 Dec 2015 08:26:36 +0100 Subject: [PATCH 1/2] detect win64 by user agent fix /download universal link (win64 artifact must be returned) --- lib/index.js | 8 ++------ lib/platforms.js | 10 ++++++++++ test/platforms.js | 9 +++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/index.js b/lib/index.js index cc1dc52..75d69bb 100644 --- a/lib/index.js +++ b/lib/index.js @@ -85,13 +85,9 @@ module.exports = function nuts(opts) { if (!filename) { // Detect platform from useragent if (!platform) { - if (req.useragent.isMac) platform = platforms.OSX; - if (req.useragent.isWindows) platform = platforms.WINDOWS; - if (req.useragent.isLinux) platform = platforms.LINUX; - if (req.useragent.isLinux64) platform = platforms.LINUX_64; + platform = platforms.detectPlatformByUserAgent(req.useragent) + if (!platform) return next(new Error('No platform specified and impossible to detect one')); } - - if (!platform) return next(new Error('No platform specified and impossible to detect one')); } else { platform = null; } diff --git a/lib/platforms.js b/lib/platforms.js index 764aece..f8a1764 100644 --- a/lib/platforms.js +++ b/lib/platforms.js @@ -52,6 +52,15 @@ function detectPlatform(platform) { return _.compact([prefix, suffix]).join('_'); } +function detectPlatformByUserAgent(useragent) { + if (useragent.isMac) return platforms.OSX; + else if (useragent.source.indexOf("WOW64") !== -1 || useragent.source.indexOf("Win64") !== -1) return platforms.WINDOWS_64; + else if (useragent.isWindows) return platforms.WINDOWS; + else if (useragent.isLinux) return platforms.LINUX; + else if (useragent.isLinux64) return platforms.LINUX_64; + else return null +} + // Satisfies a platform function satisfiesPlatform(platform, list) { if (_.contains(list, platform)) return true; @@ -108,6 +117,7 @@ function resolveForVersion(version, platformID, opts) { module.exports = platforms; module.exports.detect = detectPlatform; +module.exports.detectPlatformByUserAgent = detectPlatformByUserAgent; module.exports.satisfies = satisfiesPlatform; module.exports.toType = platformToType; module.exports.resolve = resolveForVersion; diff --git a/test/platforms.js b/test/platforms.js index f82d39b..b870484 100644 --- a/test/platforms.js +++ b/test/platforms.js @@ -1,5 +1,6 @@ var should = require('should'); var platforms = require('../lib/platforms'); +var useragent = require('express-useragent'); describe('Platforms', function() { @@ -16,6 +17,14 @@ describe('Platforms', function() { platforms.detect('RELEASES').should.be.exactly(platforms.WINDOWS_32); }); + it('should detect windows_64', function() { + platforms.detect('MyApp-x64.exe').should.be.exactly(platforms.WINDOWS_64); + var chrome = useragent.parse('Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.106 Safari/537.36') + platforms.detectPlatformByUserAgent(chrome).should.be.exactly(platforms.WINDOWS_64); + var edge = useragent.parse('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586') + platforms.detectPlatformByUserAgent(edge).should.be.exactly(platforms.WINDOWS_64); + }); + it('should detect linux', function() { platforms.detect('atom-amd64.deb').should.be.exactly(platforms.LINUX_64); }); From 35b7424de4991ce414f940edf68f486332439a75 Mon Sep 17 00:00:00 2001 From: Ben Williams Date: Tue, 16 Mar 2021 20:27:22 -0700 Subject: [PATCH 2/2] merged in main --- .cfignore | 31 + .circleci/config.yml | 65 + .dockerignore | 13 + .editorconfig | 19 + .eslintrc | 18 + .gitignore | 4 +- .prettierignore | 1 + .travis.yml | 5 - CHANGES.md | 5 + Dockerfile | 15 + Procfile | 1 - README.md | 179 +-- bin/web.js | 110 +- book.js | 24 + docs/README.md | 33 + docs/SUMMARY.md | 18 + docs/api.md | 27 + docs/assets.md | 40 + docs/deploy.md | 55 + docs/faq.md | 35 + docs/github.md | 23 + docs/module.md | 63 + schema.png => docs/schema.png | Bin docs/update-osx.md | 24 + docs/update-windows.md | 11 + docs/urls.md | 17 + docs/using-it.md | 7 + lib/api.js | 44 + lib/backends/backend.js | 122 ++ lib/backends/github.js | 92 ++ lib/backends/index.js | 10 + lib/config.js | 23 - lib/github.js | 112 -- lib/index.js | 377 +----- lib/nuts.js | 398 ++++++ lib/{ => utils}/notes.js | 0 lib/{ => utils}/platforms.js | 38 +- lib/{ => utils}/win-releases.js | 20 +- lib/versions.js | 275 ++-- package.json | 91 +- test/platforms.js | 157 ++- test/win-releases.js | 17 +- yarn.lock | 2090 +++++++++++++++++++++++++++++++ 43 files changed, 3738 insertions(+), 971 deletions(-) create mode 100644 .cfignore create mode 100644 .circleci/config.yml create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .eslintrc create mode 100644 .prettierignore delete mode 100644 .travis.yml create mode 100644 Dockerfile delete mode 100644 Procfile create mode 100644 book.js create mode 100644 docs/README.md create mode 100644 docs/SUMMARY.md create mode 100644 docs/api.md create mode 100644 docs/assets.md create mode 100644 docs/deploy.md create mode 100644 docs/faq.md create mode 100644 docs/github.md create mode 100644 docs/module.md rename schema.png => docs/schema.png (100%) create mode 100644 docs/update-osx.md create mode 100644 docs/update-windows.md create mode 100644 docs/urls.md create mode 100644 docs/using-it.md create mode 100644 lib/api.js create mode 100644 lib/backends/backend.js create mode 100644 lib/backends/github.js create mode 100644 lib/backends/index.js delete mode 100644 lib/config.js delete mode 100644 lib/github.js create mode 100644 lib/nuts.js rename lib/{ => utils}/notes.js (100%) rename lib/{ => utils}/platforms.js (74%) rename lib/{ => utils}/win-releases.js (82%) create mode 100644 yarn.lock diff --git a/.cfignore b/.cfignore new file mode 100644 index 0000000..c9f8187 --- /dev/null +++ b/.cfignore @@ -0,0 +1,31 @@ +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directory +# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git +node_modules + +.env + +docs/_book/ diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..fcdbdcd --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,65 @@ +version: 2.1 + +commands: + install_and_cache_yarn_linux: + steps: + - checkout + - restore_cache: + name: restore cache ➡ root + keys: + - dependencies-linux-root-{{ checksum "yarn.lock" }} + # fallback to using the latest cache if no exact match is found + - dependencies-linux-root- + - run: + name: yarn ➡ install + command: yarn + - save_cache: + name: save cache + paths: + - ~/repo/node_modules + key: dependencies-linux-root-{{ checksum "yarn.lock" }} + run_all_tests: + steps: + - run: + name: yarn test + command: yarn test + install_and_test: + steps: + - install_and_cache_yarn_linux + - run_all_tests + +# based on https://github.com/nodejs/Release schedule +jobs: + node_10: + docker: + - image: circleci/node:10 + steps: + - install_and_test + node_12: + docker: + - image: circleci/node:12 + steps: + - install_and_test + node_13: + docker: + - image: circleci/node:13 + steps: + - install_and_test + node_14: + docker: + - image: circleci/node:14 + steps: + - install_and_test + node_15: + docker: + - image: circleci/node:15 + steps: + - install_and_test +workflows: + main: + jobs: + - node_10 + - node_12 + - node_13 + - node_14 + - node_15 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0ae1e18 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +node_modules/ +docs/ +test/ +schema.png +CHANGES.md +LICENSE +README.md +Procfile +.eslintrc +.git/ +.gitignore +.npmignore +.travis.yml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..2feed95 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# https://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space + +# 4 space indentation +[*.{js,ts,tsx,jsx,json}] +indent_size = 2 + +# Tab indentation (no size specified) +[*.yml] +indent_size = 4 diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..7330839 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,18 @@ +{ + "rules": { + "indent": [ 2, 4 ], + "quotes": [ 2, "single" ], + "linebreak-style": [ 2, "unix" ], + "semi": [ 2, "always" ], + "no-unused-vars": [ 2, { + "vars": "all", + "args": "none" + } ], + "spaced-comment": [ 2, "always" ] + }, + "env": { + "node": true, + "mocha": true + }, + "extends": "eslint:recommended" +} diff --git a/.gitignore b/.gitignore index cf60a40..c9f8187 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,6 @@ build/Release # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git node_modules -.env \ No newline at end of file +.env + +docs/_book/ diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0a8b718..0000000 --- a/.travis.yml +++ /dev/null @@ -1,5 +0,0 @@ -sudo: false -language: node_js -node_js: - - "0.11" - - "0.10" \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index a124aca..b6d3518 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,11 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +### 3.0.0 +- Rewrite to be cleaner +- Support for other backends than GitHub +- Better disk caching + ### 2.6.0 - Add `/notes` endpoint to get changelog - GitHub authentication is now optional for public repos (Thanks @ide) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2438298 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM mhart/alpine-node:5.8.0 + +# Switch to /app +WORKDIR /app +# Install deps +COPY package.json ./ +RUN npm install --production +# Copy source +COPY . ./ + +# Ports +ENV PORT 80 +EXPOSE 80 + +ENTRYPOINT ["npm", "start"] diff --git a/Procfile b/Procfile deleted file mode 100644 index 548f486..0000000 --- a/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: node bin/web.js \ No newline at end of file diff --git a/README.md b/README.md index 84c7754..c436f4d 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Nuts is a simple (and smart) application to serve desktop-application releases. -![Schema](./schema.png) +![Schema](./docs/schema.png) It uses GitHub as a backend to store assets, and it can easily be deployed to Heroku as a stateless service. It supports GitHub private repositories (useful to store releases of a closed-source application available on GitHub). @@ -29,184 +29,17 @@ It uses GitHub as a backend to store assets, and it can easily be deployed to He - :sparkles: Release notes endpoint - `/notes/:version` - :sparkles: Up-to-date releases (GitHub webhooks) +- :sparkles: Atom/RSS feeds for versions/channels #### Deploy it / Start it -Install dependencies using: +[Follow our guide to deploy Nuts](https://nuts.gitbook.com/deploy.html). -``` -$ npm install -``` - -This service requires to be configured using environment variables: - -``` -# Set the port for the service -$ export PORT=6000 - -# Access token for the GitHub API (requires permissions to access the repository) -# If the repository is public you do not need to provide an access token -# you can also use GITHUB_USERNAME and GITHUB_PASSWORD -$ export GITHUB_TOKEN=... - -# ID for the GitHub repository -$ export GITHUB_REPO=Username/MyApp - -# Authentication for the private API -$ export API_USERNAME=hello -$ export API_PASSWORD=world -``` - -Then start the application using: - -``` -$ npm start -``` - -#### Assets for releases - -Nuts uses some filename/extension conventions to serve the correct asset to a specific request: - -Platform will be detected from the filename: - -- Windows: filename should contain `win` -- Mac/OS X: filename should contain `mac` or `osx` -- Linux: filename should contain `linux` - -By default releases are tagged as 32-bits (except for OSX), but 64-bits will also be detected from filenames. - -Filetype and usage will be detected from the extension: - -- `.dmg` will be served in priority to Mac users -- `.nupkg` will only be served to Squirrel.Windows requests -- Otherwise, `.zip` are advised (Linux, Mac, Windows and Squirrel.Mac) - -#### Download urls - -Nuts provides urls to access releases assets. These assets are cached on the disk. - -* Latest version for detected platform: `http://download.myapp.com/download/latest` or `http://download.myapp.com/download` -* Latest version for specific platform: `http://download.myapp.com/download/latest/osx` or ``http://download.myapp.com/download/osx` -* Specific version for detected platform: `http://download.myapp.com/download/1.1.0` -* Specific version for specific platform: `http://download.myapp.com/download/1.2.0/osx` -* Specific channel: `http://download.myapp.com/download/channel/beta` -* Specific channel for specific platform: `http://download.myapp.com/download/channel/beta/osx` - -#### Platforms - -Platforms can be detected from user-agent and are normalized to values: `osx`, `osx_32`, `osx_64`, `linux`, `linux_32`, `linux_64`, `windows`, `windows_32`, `windows_64`. - -Non-prefixed platform will be resolve to 32 bits (except for OSX). #### Auto-updater / Squirrel -This server provides an endpoint for [Squirrel auto-updater](https://github.com/atom/electron/blob/master/docs/api/auto-updater.md): `http://download.myapp.com/update/osx/:currentVersion`. - -###### Squirrel.Mac - -This url requires different parameters to return a correct version: `version` and `platform`. - -For example with Electron's `auto-updater` module: - -```js -var app = require('app'); -var os = require('os'); -var autoUpdater = require('auto-updater'); - -var platform = os.platform() + '_' + os.arch(); -var version = app.getVersion(); - -autoUpdater.setFeedUrl('http://download.myapp.com/update/'+platform+'/'+version); -``` - -###### Squirrel.Windows - -Nuts will serve NuGet packages on `http://download.myapp.com/update/win32/:version/RELEASES`. - -Your application just need to configurer `Update.exe` or `Squirrel.Windows` to use `http://download.myapp.com/update/win32/:version` as a feed url (:warning: without query parameters). - -You'll just need to upload as release assets: `RELEASES`, `*-delta.nupkg` and `-full.nupkg` (files generated by `Squirrel.Windows` releaser). - -#### ChangeLog - -Nuts provides a `/notes` endpoint that output release notes as text or json. - -#### Private API - -A private API is available to access more infos about releases and stats. This API can be protected by HTTP basic auth (username/password) using configuration `API_USERNAME` and `API_PASSWORD`. - -List versions: - -``` -GET http://download.myapp.com/api/versions -``` - -Get details about specific version: - -``` -GET http://download.myapp.com/api/version/1.1.0 -``` - -Resolve a version: - -``` -GET http://download.myapp.com/api/resolve?platform=osx&channel=alpha -``` - -List channels: - -``` -GET http://download.myapp.com/api/channels -``` - -Get stats about downloads: - -``` -GET http://download.myapp.com/api/stats -``` - -#### GitHub Webhook - -Add `http://download.myapp.com/refresh` as a GitHub webhook to refresh versions cache everytime you update a release on GitHub. - -The secret can be configured using `GITHUB_SECRET` (default value is `secret`). - -#### Integrate it as a middleware - -Nuts can be integrated into a Node.JS application as a middleware. Using the middleware, you can add custom authentication on downloads or analytics for downloads counts. - -```js -var express = require('express'); -var Nuts = require('nuts-serve'); - -var app = express(); -var nuts = Nuts( - // GitHub configuration - repository: "Me/MyRepo", - token: "my_api_token", - - // Timeout for releases cache (seconds) - timeout: 60*60, - - // Folder to cache assets (by default: a temporary folder) - cache: './assets', - - // Pre-fetch list of releases at startup - preFetch: true, - - // Secret for refresh webhook - refreshSecret: 'my-secret', +This server provides an endpoint for [Squirrel auto-updater](https://github.com/atom/electron/blob/master/docs/api/auto-updater.md), it supports both [OS X](https://nuts.gitbook.com/update-osx.html) and [Windows](https://nuts.gitbook.com/update-windows.html). - // Middlewares - onDownload: function(version, req, res, next) { - console.log('download', download.version.tag, "on channel", download.version.channel, "for", download.platform.type); - next(); - }, - onAPIAccess: function(req, res, next) { - next(); - } -); +#### Documentation -app.use('/myapp', nuts); -app.listen(4000); -``` +[Check out the documentation](https://nuts.gitbook.com) for more details. diff --git a/bin/web.js b/bin/web.js index 3d7ec5b..9e7c0aa 100644 --- a/bin/web.js +++ b/bin/web.js @@ -12,64 +12,82 @@ var apiAuth = { }; var analytics = undefined; +var downloadEvent = process.env.ANALYTICS_EVENT_DOWNLOAD || 'download'; if (process.env.ANALYTICS_TOKEN) { analytics = new Analytics(process.env.ANALYTICS_TOKEN); } -var myNuts = nuts({ +var myNuts = nuts.Nuts({ repository: process.env.GITHUB_REPO, token: process.env.GITHUB_TOKEN, + endpoint: process.env.GITHUB_ENDPOINT, username: process.env.GITHUB_USERNAME, password: process.env.GITHUB_PASSWORD, timeout: process.env.VERSIONS_TIMEOUT, cache: process.env.VERSIONS_CACHE, refreshSecret: process.env.GITHUB_SECRET, + proxyAssets: !Boolean(process.env.DONT_PROXY_ASSETS) +}); - onDownload: function(download, req, res, next) { - console.log('download', download.platform.filename, "for version", download.version.tag, "on channel", download.version.channel, "for", download.platform.type); - - // Track on segment if enabled - if (analytics) { - var userId = req.query.user; - - analytics.track({ - event: process.env.ANALYTICS_EVENT_DOWNLOAD || 'download', - anonymousId: userId? null : uuid.v4(), - userId: userId, - properties: { - version: download.version.tag, - channel: download.version.channel, - platform: download.platform.type, - os: nuts.platforms.toType(download.platform.type) - } - }); - } - - next(); - }, +// Control access to API +myNuts.before('api', function(access, next) { + if (!apiAuth.username) return next(); - onAPIAccess: function(req, res, next) { - if (!apiAuth.username) return next(); + function unauthorized() { + next(new Error('Invalid username/password for API')); + }; - function unauthorized(res) { - res.set('WWW-Authenticate', 'Basic realm=Authorization Required'); - return res.send(401); - }; + var user = basicAuth(access.req); + if (!user || !user.name || !user.pass) { + return unauthorized(); + }; - var user = basicAuth(req); + if (user.name === apiAuth.username && user.pass === apiAuth.password) { + return next(); + } else { + return unauthorized(); + }; +}); - if (!user || !user.name || !user.pass) { - return unauthorized(res); - }; +// Log download +myNuts.before('download', function(download, next) { + console.log('download', download.platform.filename, "for version", download.version.tag, "on channel", download.version.channel, "for", download.platform.type); - if (user.name === apiAuth.username && user.pass === apiAuth.password) { - return next(); - } else { - return unauthorized(res); - }; + next(); +}); +myNuts.after('download', function(download, next) { + console.log('downloaded', download.platform.filename, "for version", download.version.tag, "on channel", download.version.channel, "for", download.platform.type); + + // Track on segment if enabled + if (analytics) { + var userId = download.req.query.user; + + analytics.track({ + event: downloadEvent, + anonymousId: userId? null : uuid.v4(), + userId: userId, + properties: { + version: download.version.tag, + channel: download.version.channel, + platform: download.platform.type, + os: nuts.platforms.toType(download.platform.type) + } + }); } + + next(); }); +if (process.env.TRUST_PROXY) { + try { + var trustProxyObject = JSON.parse(process.env.TRUST_PROXY); + app.set('trust proxy', trustProxyObject); + } + catch (e) { + app.set('trust proxy', process.env.TRUST_PROXY); + } +} + app.use(myNuts.router); // Error handling @@ -99,9 +117,17 @@ app.use(function(err, req, res, next) { }); }); -var server = app.listen(process.env.PORT || 5000, function () { - var host = server.address().address; - var port = server.address().port; +myNuts.init() + +// Start the HTTP server +.then(function() { + var server = app.listen(process.env.PORT || 5000, function () { + var host = server.address().address; + var port = server.address().port; - console.log('Listening at http://%s:%s', host, port); + console.log('Listening at http://%s:%s', host, port); + }); +}, function(err) { + console.log(err.stack || err); + process.exit(1); }); diff --git a/book.js b/book.js new file mode 100644 index 0000000..32d867b --- /dev/null +++ b/book.js @@ -0,0 +1,24 @@ +var pkg = require('./package.json'); + +module.exports = { + // Documentation for Nuts is stored under "docs" + root: './docs', + title: 'Nuts Documentation', + + // Enforce use of GitBook v3 + gitbook: '>=3.0.0-pre.0', + + // Use the "official" theme + plugins: ['theme-official', 'sitemap'], + theme: 'official', + + variables: { + version: pkg.version + }, + + pluginsConfig: { + sitemap: { + hostname: 'https://nuts.gitbook.com' + } + } +}; diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..d1bae5e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,33 @@ +# Nuts Documentation + +Nuts is a simple (and smart) application to serve desktop-application releases. + +It uses GitHub as a backend to store assets, and it can easily be deployed to Heroku as a stateless service. GitHub private repositories are supported (useful to store releases of a closed-source application available on GitHub). + +![Schema](./schema.png) + +Please make sure that you use the documentation that match your Nuts version (Latest version is **{{ book.version }}**). + +### FAQ + +There are questions that are asked quite often, [check this out before creating an issue](faq.md). + +### Help and Support + +We're always happy to help out with any questions you might have. You can ask a question or signal an issue on [GitHub](https://github.com/GitbookIO/nuts/issues). + +### Guides + +We've created a few guides to help you getting started: + +- [Deploy Nuts](deploy.md) +- [Upload release's assets](assets.md) +- [Setup GitHub integration](github.md) +- [Setup OS X Auto-Updater](update-osx.md) +- [Setup Windows Auto-Updater](update-windows.md) +- [Debug API](api.md) +- [Use it as a Node.js middleware](module.md) + +---- + +Using Nuts for your application? [Add it to the list](using-it.md). diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md new file mode 100644 index 0000000..1e7601d --- /dev/null +++ b/docs/SUMMARY.md @@ -0,0 +1,18 @@ +# Summary + +- [F.A.Q](faq.md) +- [URL Routing](urls.md) + +--- + +- [Deploy Nuts](deploy.md) +- [Upload Releases](assets.md) +- [Setup GitHub webhook](github.md) +- [Mac / OS X Auto-Updater](update-osx.md) +- [Windows Auto-Updater](update-windows.md) + +--- + +- [Debug API](api.md) +- [Node.js Middleware](module.md) + diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..4c9f88c --- /dev/null +++ b/docs/api.md @@ -0,0 +1,27 @@ +# API + +A debug API is available to access more infos about releases. This API can be protected by HTTP basic auth (username/password) using configuration `API_USERNAME` and `API_PASSWORD`. + +#### List versions: + +``` +GET http://download.myapp.com/api/versions +``` + +#### Get details about specific version: + +``` +GET http://download.myapp.com/api/version/1.1.0 +``` + +#### Resolve a version: + +``` +GET http://download.myapp.com/api/resolve?platform=osx&channel=alpha +``` + +#### List channels: + +``` +GET http://download.myapp.com/api/channels +``` diff --git a/docs/assets.md b/docs/assets.md new file mode 100644 index 0000000..dd8eca7 --- /dev/null +++ b/docs/assets.md @@ -0,0 +1,40 @@ +# Upload assets for releases + +Nuts uses GitHub Releases and assets to serve the right file to the right user. + +See GitHub guides: [About Releases](https://help.github.com/articles/about-releases/) & [Creating Releases](https://help.github.com/articles/creating-releases/). + +### Naming + +Nuts uses some filename/extension conventions to serve the correct asset to a specific request: + +The platform/OS will be detected from the filename: + +- Windows: filename should contain `win` +- Mac/OS X: filename should contain `mac` or `osx` +- Linux: filename should contain `linux` + +By default releases are tagged as 32-bits (except for OSX), but 64-bits will also be detected from filenames. + +Filetype and usage will be detected from the extension: + +| Platform | Extensions (sorted by priority) | +| -------- | ---------- | +| Windows | `.exe`, `.nupkg`, `.zip` | +| OS X | `.dmg`, `.zip` | +| Linux | `.deb`, `.rpm`, `.zip` | + + +### Example + +Here is a list of files in one of the latest release of our [GitBook Editor](https://www.gitbook.com/editor): + +``` +gitbook-editor-5.0.0-beta.10-linux-ia32.deb +gitbook-editor-5.0.0-beta.10-linux-x64.deb +gitbook-editor-5.0.0-beta.10-osx-x64.dmg +gitbook-editor-5.0.0-beta.10-osx-x64.zip +GitBook.Editor.Setup.exe +GitBook_Editor-5.0.0.2010-full.nupkg +RELEASES +``` diff --git a/docs/deploy.md b/docs/deploy.md new file mode 100644 index 0000000..d49059d --- /dev/null +++ b/docs/deploy.md @@ -0,0 +1,55 @@ +# Deployment + +Nuts can easily be deployed to a state-less server or PaaS. It only uses the disk as a cache for assets. + +### On Heroku: + +Heroku is the perfect solution to host a Nuts instance. + +[![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy) + +### With docker + +Nuts can also be run as a Docker container: + +``` +docker run -it -p 80:80 -e GITHUB_REPO=username/repo gitbook/nuts +``` + +### On your own server: + +Install dependencies using: + +``` +$ npm install +``` + +The service requires configuration using environment variables: + +``` +# Set the port for the service +$ export PORT=6000 + +# Access token for the GitHub API (requires permissions to access the repository) +# If the repository is public you do not need to provide an access token +# you can also use GITHUB_USERNAME and GITHUB_PASSWORD +$ export GITHUB_TOKEN=... + +# ID for the GitHub repository +$ export GITHUB_REPO=Username/MyApp + +# Authentication for the private API +$ export API_USERNAME=hello +$ export API_PASSWORD=world + +# Express's "trust proxy" setting for trusting X-Forwarded-* headers when +# behind a reverse proxy like nginx +# http://expressjs.com/en/guide/behind-proxies.html +$ export TRUST_PROXY=loopback +``` + +Then start the application using: + +``` +$ npm start +``` diff --git a/docs/faq.md b/docs/faq.md new file mode 100644 index 0000000..3d6a3a6 --- /dev/null +++ b/docs/faq.md @@ -0,0 +1,35 @@ +# Nuts FAQ + +### Can I use a private repository? + +Nuts is designed to proxy assets from a private repository to the public. + +### Can I use a GitHub Enterprise / GitLab repository? + +Since version 3.0.0, Nuts can works with [other backends](https://github.com/GitbookIO/nuts/tree/master/lib/backends) than GitHub. Feel free to post a Pull-Request to implement such backends! + +### Can I deploy it to Heroku? + +[Yes you can](deploy.md)! + +### Can I use it in my Node.js application? + +[Yes you can](module.md)! + +### What file should I upload to the GitHub release? + +Nuts can detect the type of file from its filename, there is no strict policy on file naming. Nuts tries to respect the filename/extension conventions for the different platforms. request:) + +- Windows: `.exe`, `.nupkg` etc +- Linux: `.deb`, `.tar.gz`, etc +- OS X: `.dmg`, etc + +By default releases are tagged as 32-bits (except for OSX), but 64-bits will also be detected from filenames. + +### How should I tag my releases? + +Nuts requires applications to follow [SemVer](http://semver.org). And even if you're not using Nuts, you should follow it! + +### Does nuts provide an Atom feed of versions? + +Yes, [See Feed URLS](./urls.md). diff --git a/docs/github.md b/docs/github.md new file mode 100644 index 0000000..239d145 --- /dev/null +++ b/docs/github.md @@ -0,0 +1,23 @@ +# GitHub Integration + +By default Nuts fetches releases from GitHub Releases; but since Nuts is caching information, there might be a delay before the creation of the release and the release being served to users. + +To solve this issue, you can setup a webhook between Nuts and GitHub, to notify your nuts instance each time GitHub Releases are updated (created/removed/updated). + +### Webhook URL + +Add a [GitHub Webhook](https://help.github.com/articles/about-webhooks/) with the url: + +``` +http://download.myapp.com/refresh +``` + +Where download.myapp.com, is the URL of your Nuts server. + +Content-type should be `application/json` + +It'll refresh versions cache everytime you update a release on GitHub. + +### Secret + +The GitHub Webhook secret can be configured as a environment variable on Nuts: `GITHUB_SECRET` (default value is `secret`). diff --git a/docs/module.md b/docs/module.md new file mode 100644 index 0000000..effcd6b --- /dev/null +++ b/docs/module.md @@ -0,0 +1,63 @@ +# Use Nuts as a node module + +Nuts can be integrated into a Node.JS application as a node module. Using the middleware, you can add custom authentication on downloads or analytics for downloads counts. + +#### Installation + +Nuts can be installed as a local dependency using `npm`: + +``` +$ npm install nuts-serve +``` + +#### Usage + +```js +var express = require('express'); +var Nuts = require('nuts-serve').Nuts; + +var app = express(); + +var nuts = Nuts({ + // GitHub configuration + repository: "Me/MyRepo", + token: "my_api_token" +}); + +app.use('/myapp', nuts.router); +app.listen(4000); +``` + +### Configuration + +- `cache`: (string) Path to the cache folder, default value is a temprary folder +- `cacheMax`: (int) Max size of the cache (default is 500MB) +- `cacheMaxAge`: (int) Maximum age in ms (default is 1 hour) +- `preFetch`: (boolean) Pre-fetch list of releases at startup (default is true) + +GitHub specific configuration: + +- `refreshSecret`: (string) Secret for the GitHub webhook + +### Hooks + +You can bind interceptors (i.e. hooks) to certain asynchronous actions using `nuts.before(fn)` and `nuts.after(fn)`: + +- `download`: When an user is downloading a version +- `api`: when an user is accessing the API + +```js +nuts.before('download', function(download, next) { + console.log('user is downloading', download.platform.filename, "for version", download.version.tag, "on channel", download.version.channel, "for", download.platform.type); + + next(); +}); + +nuts.after('download', function(download, next) { + console.log('user downloaded', download.platform.filename, "for version", download.version.tag, "on channel", download.version.channel, "for", download.platform.type); + + next(); +}); +``` + + diff --git a/schema.png b/docs/schema.png similarity index 100% rename from schema.png rename to docs/schema.png diff --git a/docs/update-osx.md b/docs/update-osx.md new file mode 100644 index 0000000..c08d6b8 --- /dev/null +++ b/docs/update-osx.md @@ -0,0 +1,24 @@ +# Auto-updater on OS X + +Nuts provides a backend for the [Squirrel.Mac](https://github.com/Squirrel/Squirrel.Mac) auto-updater. Squirrel.Mac is integrated by default in [Electron applications](https://github.com/atom/electron). + +### Endpoint + +The endpoint for **Squirrel.Mac** is `http://download.myapp.com/update/osx/:currentVersion`. + +This url requires different parameters to return a correct version: `version` and `platform`. + +### Electron Example + +For example with Electron's `auto-updater` module: + +```js +var app = require('app'); +var os = require('os'); +var autoUpdater = require('auto-updater'); + +var platform = os.platform() + '_' + os.arch(); +var version = app.getVersion(); + +autoUpdater.setFeedURL('https://download.myapp.com/update/'+platform+'/'+version); +``` diff --git a/docs/update-windows.md b/docs/update-windows.md new file mode 100644 index 0000000..61d540e --- /dev/null +++ b/docs/update-windows.md @@ -0,0 +1,11 @@ +# Auto-updater on Windows + +Nuts provides a backend for the [Squirrel.Windows](https://github.com/Squirrel/Squirrel.Windows) auto-updater. + +Refer to the [Squirrel.Windows documentation](https://github.com/Squirrel/Squirrel.Windows/tree/master/docs) on how to setup your application. + +Nuts will serve NuGet packages on `http://download.myapp.com/update/win32/:version/RELEASES`. + +Your application just need to configure `Update.exe` or `Squirrel.Windows` to use `http://download.myapp.com/update/win32/:version` as a feed url (:warning: without query parameters). + +You'll just need to upload as release assets: `RELEASES`, `*-delta.nupkg` and `-full.nupkg` (files generated by `Squirrel.Windows` releaser). diff --git a/docs/urls.md b/docs/urls.md new file mode 100644 index 0000000..1d3c5ec --- /dev/null +++ b/docs/urls.md @@ -0,0 +1,17 @@ +# Download Urls + +Nuts provides urls to access releases assets. These assets are cached on the disk. + +* Latest version for detected platform: `http://download.myapp.com/download` +* Latest version for specific platform: `http://download.myapp.com/download/osx` +* Specific version for detected platform: `http://download.myapp.com/download/1.1.0` +* Specific version for specific platform: `http://download.myapp.com/download/1.2.0/osx` +* Specific channel: `http://download.myapp.com/download/channel/beta` +* Specific channel for specific platform: `http://download.myapp.com/download/channel/beta/osx` + +# Atom Feed Urls + +Nuts provides different Atom feeds: + +* All versions: `http://download.myapp.com/feed/channel/all.atom` +* Versions in specific channel: `http://download.myapp.com/feed/channel/:channel.atom` diff --git a/docs/using-it.md b/docs/using-it.md new file mode 100644 index 0000000..59c6f41 --- /dev/null +++ b/docs/using-it.md @@ -0,0 +1,7 @@ +# Who is using it? + +Using Nuts for your application? Post a Pull-Request! + +- [GitBook Editor](https://www.gitbook.com/editor) +- [DeckHub](https://getdeckhub.com) + diff --git a/lib/api.js b/lib/api.js new file mode 100644 index 0000000..70e789f --- /dev/null +++ b/lib/api.js @@ -0,0 +1,44 @@ +var startTime = Date.now(); +var Q = require('q'); + +module.exports = { + 'status': function () { + return { + uptime: (Date.now() - startTime)/1000 + }; + }, + + 'versions': function (req) { + return this.versions.filter({ + platform: req.query.platform, + channel: req.query.channel || '*' + }); + }, + + 'channels': function () { + return this.versions.channels(); + }, + + 'refresh': function () { + return Q() + .then(this.backend.onRelease) + .thenResolve({done: true} + ); + }, + + 'version/:tag': function (req) { + return this.versions.resolve({ + tag: req.params.tag, + channel: '*' + }); + }, + + 'resolve': function(req) { + return this.versions.resolve({ + channel: req.query.channel, + platform: req.query.platform, + tag: req.query.tag + }); + } +}; + diff --git a/lib/backends/backend.js b/lib/backends/backend.js new file mode 100644 index 0000000..eec766a --- /dev/null +++ b/lib/backends/backend.js @@ -0,0 +1,122 @@ +var _ = require('lodash'); +var Q = require('q'); +var path = require('path'); +var os = require('os'); +var destroy = require('destroy'); +var LRU = require('lru-diskcache'); +var streamRes = require('stream-res'); +var Buffer = require('buffer').Buffer; + +function Backend(nuts, opts) { + this.cacheId = 0; + this.nuts = nuts; + this.opts = _.defaults(opts || {}, { + // Folder to cache assets + cache: path.resolve(os.tmpdir(), 'nuts'), + + // Cache configuration + cacheMax: 500 * 1024 * 1024, + cacheMaxAge: 60 * 60 * 1000, + }); + + // Create cache + this.cache = LRU(opts.cache, { + max: opts.cacheMax, + maxAge: opts.cacheMaxAge + }); + + _.bindAll(this); +} + +// Memoize a function +Backend.prototype.memoize = function(fn) { + var that = this; + + return _.memoize(fn, function() { + return that.cacheId+Math.ceil(Date.now()/that.opts.cacheMaxAge) + }); +}; + +// New release? clear cache +Backend.prototype.onRelease = function() { + this.cacheId++; +}; + +// Initialize the backend +Backend.prototype.init = function() { + this.cache.init(); + return Q(); +}; + +// List all releases for this repository +Backend.prototype.releases = function() { + +}; + +// Return stream for an asset +Backend.prototype.serveAsset = function(asset, req, res) { + var that = this; + var cacheKey = asset.id; + + function outputStream(stream) { + var d = Q.defer(); + streamRes(res, stream, d.makeNodeResolver()); + return d.promise; + } + + res.header('Content-Length', asset.size); + res.attachment(asset.filename); + + // Key exists + if (that.cache.has(cacheKey)) { + return that.cache.getStream(cacheKey) + .then(outputStream); + } + + return that.getAssetStream(asset) + .then(function(stream) { + return Q.all([ + // Cache the stream + that.cache.set(cacheKey, stream), + + // Send the stream to the user + outputStream(stream) + ]); + }); +}; + +// Return stream for an asset +Backend.prototype.getAssetStream = function(asset) { + +}; + +// Return stream for an asset +Backend.prototype.readAsset = function(asset) { + return this.getAssetStream(asset) + .then(function(res) { + var d = Q.defer(); + var output = Buffer([]); + + function cleanup() { + destroy(res); + res.removeAllListeners(); + } + + res.on('data', function(buf) { + output = Buffer.concat([output, buf]); + }) + .on('error', function(err) { + cleanup(); + d.reject(err); + }) + .on('end', function() { + cleanup(); + d.resolve(output); + }); + + return d.promise + + }) +}; + +module.exports = Backend; diff --git a/lib/backends/github.js b/lib/backends/github.js new file mode 100644 index 0000000..3eaf155 --- /dev/null +++ b/lib/backends/github.js @@ -0,0 +1,92 @@ +var _ = require('lodash'); +var Q = require('q'); +var util = require('util'); +var destroy = require('destroy'); +var GitHub = require('octocat'); +var request = require('request'); +var Buffer = require('buffer').Buffer; +var githubWebhook = require('github-webhook-handler'); + +var Backend = require('./backend'); + +function GitHubBackend() { + var that = this; + Backend.apply(this, arguments); + + this.opts = _.defaults(this.opts || {}, { + proxyAssets: true + }); + + if ((!this.opts.username || !this.opts.password) && (!this.opts.token)) { + throw new Error('GitHub backend require "username" and "token" options'); + } + + this.client = new GitHub({ + token: this.opts.token, + endpoint: this.opts.endpoint, + username: this.opts.username, + password: this.opts.password + }); + + this.ghrepo = this.client.repo(this.opts.repository); + this.releases = this.memoize(this._releases); + + // GitHub webhook to refresh list of versions + this.webhookHandler = githubWebhook({ + path: '/refresh', + secret: this.opts.refreshSecret + }); + + // Webhook from GitHub + this.webhookHandler.on('release', function(event) { + that.onRelease(); + }); + this.nuts.router.use(this.webhookHandler); +} +util.inherits(GitHubBackend, Backend); + +// List all releases for this repository +GitHubBackend.prototype._releases = function() { + return this.ghrepo.releases() + .then(function(page) { + return page.all(); + }); +}; + +// Return stream for an asset +GitHubBackend.prototype.serveAsset = function(asset, req, res) { + if (!this.opts.proxyAssets) { + res.redirect(asset.raw.browser_download_url); + } else { + return Backend.prototype.serveAsset.apply(this, arguments); + } +}; + +// Return stream for an asset +GitHubBackend.prototype.getAssetStream = function(asset) { + var headers = { + 'User-Agent': 'nuts', + 'Accept': 'application/octet-stream' + }; + var httpAuth; + + if (this.opts.token) { + headers['Authorization'] = 'token '+this.opts.token; + } else if (this.opts.username) { + httpAuth = { + user: this.opts.username, + pass: this.opts.password, + sendImmediately: true + }; + } + + return Q(request({ + uri: asset.raw.url, + method: 'get', + headers: headers, + auth: httpAuth + })); +}; + + +module.exports = GitHubBackend; diff --git a/lib/backends/index.js b/lib/backends/index.js new file mode 100644 index 0000000..193703c --- /dev/null +++ b/lib/backends/index.js @@ -0,0 +1,10 @@ +var _ = require('lodash'); + +var BACKENDS = { + github: require('./github') +}; + +module.exports = function(backend) { + if (_.isString(backend)) return BACKENDS[backend]; + return backend; +}; diff --git a/lib/config.js b/lib/config.js deleted file mode 100644 index 3a0c086..0000000 --- a/lib/config.js +++ /dev/null @@ -1,23 +0,0 @@ -var os = require('os'); -var path = require('path'); - -module.exports = { - port: process.env.PORT || 6000, - - github: { - repository: process.env.GITHUB_REPO, - token: process.env.GITHUB_TOKEN, - username: process.env.GITHUB_USERNAME, - password: process.env.GITHUB_PASSWORD - }, - - api: { - username: process.env.API_USERNAME, - password: process.env.API_PASSWORD - }, - - versions: { - timeout: Number(process.env.VERSIONS_TIMEOUT || 60*60)*1000, - cache: process.env.VERSIONS_CACHE || path.resolve(os.tmpdir(), 'nuts') - } -}; diff --git a/lib/github.js b/lib/github.js deleted file mode 100644 index befad82..0000000 --- a/lib/github.js +++ /dev/null @@ -1,112 +0,0 @@ -var _ = require('lodash'); -var Q = require('q'); -var destroy = require('destroy'); -var github = require('octonode'); -var request = require('request'); -var Buffer = require('buffer').Buffer; - -module.exports = function(opts) { - // Create an API client - var client, ghrepo, cacheInstance = 0; - - if (opts.token) { - client = github.client(opts.token); - } else { - client = github.client({ - username: opts.username, - password: opts.password - }); - } - - // List releases - function listReleases(page) { - page = page || 1; - - var uri = "/repos/"+opts.repository+"/releases"; - - console.log('list releases', page); - - return Q.nfcall(client.get.bind(client), uri, { - page: page, - per_page: 100 - }) - .spread(function(statusCode, releases, headers) { - var hasNext = (headers.link || "").search('rel="next"') >= 0; - if (!hasNext) return releases; - - return listReleases(page+1) - .then(function(r) { - return releases.concat(r); - }); - }); - } - - var cacheListReleases =_.memoize(listReleases, function() { - return cacheInstance+Math.ceil(Date.now()/opts.timeout) - }); - - function clearCache() { - cacheInstance = cacheInstance + 1; - } - - // Stream a download to res - function streamAsset(uri) { - var headers = { - 'User-Agent': "releaser-server", - 'Accept': "application/octet-stream" - }; - var httpAuth = null; - - if (opts.token) { - headers['Authorization'] = 'token '+opts.token; - } else if (opts.username) { - httpAuth = { - user: opts.username, - pass: opts.password, - sendImmediately: true - }; - } - - return request({ - uri: uri, - method: 'get', - headers: headers, - auth: httpAuth - }); - } - - // Read a asset - function readAsset(uri) { - var d = Q.defer(); - var output = Buffer([]); - var res = streamAsset(uri); - - var cleanup = function() { - destroy(res); - res.removeAllListeners(); - }; - - res - .on('data', function(buf) { - output = Buffer.concat([output, buf]); - }) - .on('error', function(err) { - cleanup(); - d.reject(err); - }) - .on('end', function() { - cleanup(); - d.resolve(output); - }); - - return d.promise - } - - return { - clearCache: clearCache, - releases: cacheListReleases, - streamAsset: streamAsset, - readAsset: readAsset - }; -}; - diff --git a/lib/index.js b/lib/index.js index 75d69bb..bbe28f1 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,370 +1,9 @@ -var _ = require('lodash'); -var Q = require('q'); -var url = require('url'); -var os = require('os'); -var path = require('path'); -var express = require('express'); -var useragent = require('express-useragent'); -var stores = require('stores'); -var githubWebhook = require('github-webhook-handler'); - -var GitHub = require('./github'); -var Versions = require('./versions'); -var notes = require('./notes'); -var platforms = require('./platforms'); -var winReleases = require('./win-releases'); - -function getFullUrl(req) { - return req.protocol + '://' + req.get('host') + req.originalUrl; -} - -// Return a express router -module.exports = function nuts(opts) { - opts = _.defaults(opts || {}, { - // Timeout for releases cache (seconds) - timeout: 60*60, - - // Folder to cache assets - cache: path.resolve(os.tmpdir(), 'nuts'), - - // Pre-fetch list of releases at startup - preFetch: true, - - // Secret for GitHub webhook - refreshSecret: 'secret', - - // Middlewares - onDownload: function(version, req, next) { next(); }, - onAPIAccess: function(req, res, next) { next(); } - }); - opts.timeout = opts.timeout * 1000; - - var router = express.Router(); - var github = GitHub(opts); - var versions = Versions(github, opts); - var startTime = Date.now(); - - router.use(useragent.express()); - - // Download links - var cacheStore = stores(stores.FileStore, function(req, slot, next) { - github.streamAsset(req.url).pipe(slot) - }, { - root: opts.cache - }); - - var serveAsset = function(req, res, version, asset) { - // Call middleware - return Q.nfcall(opts.onDownload, { - version: version, - platform: asset - }, req, res) - .then(function() { - var d = Q.defer(); - res.header('Content-Length', asset.size); - res.attachment(asset.filename); - - // Set id for stores bucket - req.url = asset.download_url; - req._downloadStream = github.streamAsset(asset.download_url); - - cacheStore(req, res, d.makeNodeResolver()); - - return d.promise; - }); - }; - - var downloader = function(req, res, next) { - var channel = req.params.channel; - var platform = req.params.platform; - var tag = req.params.tag || 'latest'; - var filename = req.params.filename; - var filetypeWanted = req.query.filetype; - - // When serving a specific file, platform is not required - if (!filename) { - // Detect platform from useragent - if (!platform) { - platform = platforms.detectPlatformByUserAgent(req.useragent) - if (!platform) return next(new Error('No platform specified and impossible to detect one')); - } - } else { - platform = null; - } - - // If specific version, don't enforce a channel - if (tag != 'latest') channel = '*'; - - versions.resolve({ - channel: channel, - platform: platform, - tag: tag - }) - - // Fallback to any channels if no version found on stable one - .fail(function(err) { - if (channel || tag != 'latest') throw err; - - return versions.resolve({ - channel: '*', - platform: platform, - tag: tag - }); - }) - - // Serve downloads - .then(function(version) { - var asset; - - if (filename) { - asset = _.find(version.platforms, { - filename: filename - }); - } else { - asset = platforms.resolve(version, platform, { - wanted: filetypeWanted? '.'+filetypeWanted : null - }); - } - - if (!asset) throw new Error("No download available for platform "+platform+" for version "+version.tag+" ("+(channel || "beta")+")"); - - // Call analytic middleware, then serve - return serveAsset(req, res, version, asset); - }) - .fail(next); - }; - - router.get('/', downloader); - router.get('/download/channel/:channel/:platform?', downloader); - router.get('/download/version/:tag/:platform?', downloader); - router.get('/download/:tag/:filename', downloader); - router.get('/download/:platform?', downloader); - - // Redirect querystring request - router.get('/update', function(req, res, next) { - Q() - .then(function() { - if (!req.query.version) throw new Error('Requires "version" parameter'); - if (!req.query.platform) throw new Error('Requires "platform" parameter'); - - return res.redirect('/update/'+req.query.platform+'/'+req.query.version); - }) - .fail(next); - }); - - // Auto-updates: Status and Squirrel.Mac - router.get('/update/:platform/:version', function(req, res, next) { - var fullUrl = getFullUrl(req); - var platform = req.params.platform; - var tag = req.params.version; - - Q() - .then(function() { - if (!tag) throw new Error('Requires "version" parameter'); - if (!platform) throw new Error('Requires "platform" parameter'); - - platform = platforms.detect(platform); - - return versions.filter({ - tag: '>='+tag, - platform: platform, - channel: '*' - }); - }) - .then(function(versions) { - var latest = _.first(versions); - if (!latest || latest.tag == tag) return res.status(204).send('No updates'); - - var releaseNotes = notes.merge(versions.slice(0, -1), { includeTag: false }); - - res.status(200).send({ - "url": url.resolve(fullUrl, "/download/version/"+latest.tag+"/"+platform+"?filetype=zip"), - "name": latest.tag, - "notes": releaseNotes, - "pub_date": latest.published_at.toISOString() - }); - }) - .fail(next); - }); - - // Auto-updates: Squirrel.Windows: serve RELEASES from latest version - // Currently, it will only serve a full.nupkg of the latest release with a normalized filename (for pre-release) - router.get('/update/:platform/:version/RELEASES', function(req, res, next) { - var fullUrl = getFullUrl(req); - var platform = 'win_32'; - var tag = req.params.version; - - Q() - .then(function() { - platform = platforms.detect(platform); - - return versions.filter({ - tag: '>='+tag, - platform: platform, - channel: '*' - }); - }) - .then(function(versions) { - // Update needed? - var latest = _.first(versions); - if (!latest) throw new Error("Version not found"); - - // File exists - var asset = _.find(latest.platforms, { - filename: 'RELEASES' - }); - if (!asset) throw new Error("File not found"); - - return github.readAsset(asset.download_url) - .then(function(content) { - var releases = winReleases.parse(content.toString('utf-8')); - - releases = _.chain(releases) - - // Change filename to use download proxy - .map(function(entry) { - entry.filename = url.resolve(fullUrl, '/download/'+entry.version+'/'+entry.filename); - - return entry; - }) - - .value(); - - var output = winReleases.generate(releases); - - res.header('Content-Length', output.length); - res.attachment("RELEASES"); - res.send(output); - }); - }) - .fail(next); - }); - - // Get release notes for a specific version - router.get('/notes/:version?', function(req, res, next) { - var tag = req.params.version; - - Q() - .then(function() { - return versions.filter({ - tag: tag? '>='+tag : '*', - channel: '*' - }); - }) - .then(function(versions) { - var latest = _.first(versions); - - if (!latest) throw new Error('No versions matching'); - - res.format({ - 'text/plain': function(){ - res.send(notes.merge(versions)); - }, - 'application/json': function(){ - res.send({ - "notes": notes.merge(versions, { includeTag: false }), - "pub_date": latest.published_at.toISOString() - }); - }, - 'default': function() { - res.send(releaseNotes); - } - }); - }) - .fail(next); - }); - - // GitHub webhook to refresh list of versions - var webhookHandler = githubWebhook({ - path: '/refresh', - secret: opts.refreshSecret - }); - - webhookHandler.on('release', function(event) { - github.clearCache(); - versions.list(); - }); - router.use(webhookHandler); - - // Middleware to do auth on api - router.use('/api', opts.onAPIAccess); - - // Return status of the server - router.get('/api/status', function (req, res, next) { - res.send({ - uptime: (Date.now() - startTime)/1000 - }); - }); - - // List versions - router.get('/api/versions', function (req, res, next) { - versions.filter({ - platform: req.query.platform, - channel: req.query.channel || '*' - }) - .then(function(results) { - res.send(results); - }, next); - }); - - // List channels - router.get('/api/channels', function (req, res, next) { - versions.channels() - .then(function(results) { - res.send(results); - }, next); - }); - - // Returm a precise version - router.get('/api/version/:tag', function (req, res, next) { - versions.get(req.params.tag) - .then(function(result) { - res.send(result); - }, next); - }); - - // Resolve a version using querystring parameters - router.get('/api/resolve', function (req, res, next) { - versions.resolve({ - channel: req.query.channel, - platform: req.query.platform, - tag: req.query.tag - }) - .then(function(result) { - res.send(result); - }, next); - }); - - // Return stats about versions/platforms - router.get('/api/stats', function (req, res, next) { - var stats = { - total: 0, - platforms: {} - }; - - versions.list() - .then(function(_versions) { - _.each(_versions, function(version, i) { - _.each(version.platforms, function(platform, _platformID) { - // Increase platform count - var platformID = platforms.toType(platform.type); - stats.platforms[platformID] = (stats.platforms[platformID] || 0) + platform.download_count; - }); - }); - - stats.total = _.sum(stats.platforms); - - res.send(stats); - }) - .fail(next); - }); - - if (opts.preFetch) versions.list(); - - return { - versions: versions, - router: router - }; +var Nuts = require('./nuts'); +var platforms = require('./utils/platforms'); +var winReleases = require('./utils/win-releases'); + +module.exports = { + Nuts: Nuts, + platforms: platforms, + winReleases: winReleases }; -module.exports.platforms = platforms; -module.exports.winReleases = winReleases; diff --git a/lib/nuts.js b/lib/nuts.js new file mode 100644 index 0000000..a371dd8 --- /dev/null +++ b/lib/nuts.js @@ -0,0 +1,398 @@ +var _ = require('lodash'); +var Q = require('q'); +var Feed = require('feed'); +var urljoin = require('urljoin.js'); +var Understudy = require('understudy'); +var express = require('express'); +var useragent = require('express-useragent'); + +var BACKENDS = require('./backends'); +var Versions = require('./versions'); +var notes = require('./utils/notes'); +var platforms = require('./utils/platforms'); +var winReleases = require('./utils/win-releases'); +var API_METHODS = require('./api'); + +function getFullUrl(req) { + return req.protocol + '://' + req.get('host') + req.originalUrl; +} + +function Nuts(opts) { + if (!(this instanceof Nuts)) return new Nuts(opts); + var that = this; + + Understudy.call(this); + _.bindAll(this); + + this.opts = _.defaults(opts || {}, { + // Backend to use + backend: 'github', + + // Timeout for releases cache (seconds) + timeout: 60*60*1000, + + // Pre-fetch list of releases at startup + preFetch: true, + + // Secret for GitHub webhook + refreshSecret: 'secret' + }); + + // .init() is now a memoized version of ._init() + this.init = _.memoize(this._init); + + // Create router + this.router = express.Router(); + + // Create backend + this.backend = new (BACKENDS(this.opts.backend))(this, this.opts); + this.versions = new Versions(this.backend); + + // Bind routes + this.router.use(useragent.express()); + + this.router.get('/', this.onDownload); + this.router.get('/download/channel/:channel/:platform?', this.onDownload); + this.router.get('/download/version/:tag/:platform?', this.onDownload); + this.router.get('/download/:tag/:filename', this.onDownload); + this.router.get('/download/:platform?', this.onDownload); + + this.router.get('/feed/channel/:channel.atom', this.onServeVersionsFeed); + + this.router.get('/update', this.onUpdateRedirect); + this.router.get('/update/:platform/:version', this.onUpdate); + this.router.get('/update/channel/:channel/:platform/:version', this.onUpdate); + this.router.get('/update/:platform/:version/RELEASES', this.onUpdateWin); + this.router.get('/update/channel/:channel/:platform/:version/RELEASES', this.onUpdateWin); + + this.router.get('/notes/:version?', this.onServeNotes); + + // Bind API + this.router.use('/api', this.onAPIAccessControl); + _.each(API_METHODS, function(method, route) { + this.router.get('/api/' + route, function(req, res, next) { + return Q() + .then(function() { + return method.call(that, req); + }) + .then(function(result) { + res.send(result); + }, next); + }); + }, this); +} + +// _init does the real init work, initializing backend and prefetching versions +Nuts.prototype._init = function() { + var that = this; + return Q() + .then(function() { + return that.backend.init(); + }) + .then(function() { + if (!that.opts.preFetch) return + return that.versions.list(); + }); +} + + +// Perform a hook using promised functions +Nuts.prototype.performQ = function(name, arg, fn) { + var that = this; + fn = fn || function() { }; + + return Q.nfcall(this.perform, name, arg, function (next) { + Q() + .then(function() { + return fn.call(that, arg); + }) + .then(function() { + next(); + }, next); + }) +}; + +// Serve an asset to the response +Nuts.prototype.serveAsset = function(req, res, version, asset) { + var that = this; + + return that.init() + .then(function() { + return that.performQ('download', { + req: req, + version: version, + platform: asset + }, function() { + return that.backend.serveAsset(asset, req, res) + }); + }); +}; + +// Handler for download routes +Nuts.prototype.onDownload = function(req, res, next) { + var that = this; + var channel = req.params.channel; + var platform = req.params.platform; + var tag = req.params.tag || 'latest'; + var filename = req.params.filename; + var filetypeWanted = req.query.filetype; + + // When serving a specific file, platform is not required + if (!filename) { + // Detect platform from useragent + if (!platform) { + platform = platforms.detectPlatformByUserAgent(req.useragent) + } + if (!platform) { + res.status(400).send('No platform specified and impossible to detect one'); + return; + } + } else { + platform = null; + } + + // If specific version, don't enforce a channel + if (tag != 'latest') channel = '*'; + + this.versions.resolve({ + channel: channel, + platform: platform, + tag: tag + }) + + // Fallback to any channels if no version found on stable one + .fail(function(err) { + if (channel || tag != 'latest') throw err; + + return that.versions.resolve({ + channel: '*', + platform: platform, + tag: tag + }); + }) + + // Serve downloads + .then(function(version) { + var asset; + + if (filename) { + asset = _.find(version.platforms, { + filename: filename + }); + } else { + asset = platforms.resolve(version, platform, { + wanted: filetypeWanted? '.'+filetypeWanted : null + }); + } + + if (!asset) { + res.status(400).send("No download available for platform "+_.escape(platform)+" for version "+version.tag+" ("+(channel || "beta")+")"); + return; + } + + // Call analytic middleware, then serve + return that.serveAsset(req, res, version, asset); + }) + .fail(function() { + res.status(400).send("No download available for platform "+platform); + }); +}; + + +// Request to update +Nuts.prototype.onUpdateRedirect = function(req, res, next) { + Q() + .then(function() { + if (!req.query.version) throw new Error('Requires "version" parameter'); + if (!req.query.platform) throw new Error('Requires "platform" parameter'); + + return res.redirect('/update/'+_.escape(req.query.platform)+'/'+req.query.version); + }) + .fail(next); +}; + +// Updater used by OSX (Squirrel.Mac) and others +Nuts.prototype.onUpdate = function(req, res, next) { + var that = this; + var fullUrl = getFullUrl(req); + var platform = req.params.platform; + var channel = req.params.channel || '*'; + var tag = req.params.version; + var filetype = req.query.filetype ? req.query.filetype : "zip"; + + Q() + .then(function() { + if (!tag) throw new Error('Requires "version" parameter'); + if (!platform) throw new Error('Requires "platform" parameter'); + + platform = platforms.detect(platform); + + return that.versions.filter({ + tag: '>='+tag, + platform: platform, + channel: channel + }); + }) + .then(function(versions) { + var latest = _.first(versions); + if (!latest || latest.tag == tag) return res.status(204).send('No updates'); + + var notesSlice = versions.slice(0, -1); + if (versions.length === 1) { + notesSlice = [versions[0]]; + } + var releaseNotes = notes.merge(notesSlice, { includeTag: false }); + console.error(latest.tag); + var gitFilePath = (channel === '*' ? '/../../../' : '/../../../../../'); + res.status(200).send({ + "url": urljoin(fullUrl, gitFilePath, '/download/version/'+latest.tag+'/'+platform+'?filetype='+filetype), + "name": latest.tag, + "notes": releaseNotes, + "pub_date": latest.published_at.toISOString() + }); + }) + .fail(next); +}; + +// Update Windows (Squirrel.Windows) +// Auto-updates: Squirrel.Windows: serve RELEASES from latest version +// Currently, it will only serve a full.nupkg of the latest release with a normalized filename (for pre-release) +Nuts.prototype.onUpdateWin = function(req, res, next) { + var that = this; + + var fullUrl = getFullUrl(req); + var platform = 'win_32'; + var channel = req.params.channel || '*'; + var tag = req.params.version; + + that.init() + .then(function() { + platform = platforms.detect(platform); + + return that.versions.filter({ + tag: '>='+tag, + platform: platform, + channel: channel + }); + }) + .then(function(versions) { + // Update needed? + var latest = _.first(versions); + if (!latest) throw new Error("Version not found"); + + // File exists + var asset = _.find(latest.platforms, { + filename: 'RELEASES' + }); + if (!asset) throw new Error("File not found"); + + return that.backend.readAsset(asset) + .then(function(content) { + var releases = winReleases.parse(content.toString('utf-8')); + + releases = _.chain(releases) + + // Change filename to use download proxy + .map(function(entry) { + var gitFilePath = (channel === '*' ? '../../../../' : '../../../../../../'); + entry.filename = urljoin(fullUrl, gitFilePath, '/download/'+entry.semver+'/'+entry.filename); + + return entry; + }) + + .value(); + + var output = winReleases.generate(releases); + + res.header('Content-Length', output.length); + res.attachment("RELEASES"); + res.send(output); + }); + }) + .fail(next); +}; + +// Serve releases notes +Nuts.prototype.onServeNotes = function(req, res, next) { + var that = this; + var tag = req.params.version; + + Q() + .then(function() { + return that.versions.filter({ + tag: tag? '>='+tag : '*', + channel: '*' + }); + }) + .then(function(versions) { + var latest = _.first(versions); + + if (!latest) throw new Error('No versions matching'); + + res.format({ + 'text/plain': function(){ + res.send(notes.merge(versions)); + }, + 'application/json': function(){ + res.send({ + "notes": notes.merge(versions, { includeTag: false }), + "pub_date": latest.published_at.toISOString() + }); + }, + 'default': function() { + res.send(releaseNotes); + } + }); + }) + .fail(next); +}; + +// Serve versions list as RSS +Nuts.prototype.onServeVersionsFeed = function(req, res, next) { + var that = this; + var channel = req.params.channel || 'all'; + var channelId = channel === 'all'? '*' : channel; + var fullUrl = getFullUrl(req); + + var feed = new Feed({ + id: 'versions/channels/'+channel, + title: 'Versions (' + channel + ')', + link: fullUrl + }); + + Q() + .then(function() { + return that.versions.filter({ + channel: channelId + }); + }) + .then(function(versions) { + _.each(versions, function(version) { + feed.addItem({ + title: version.tag, + link: urljoin(fullUrl, '/../../../', '/download/version/'+version.tag), + description: version.notes, + date: version.published_at, + author: [] + }); + }); + + res.set('Content-Type', 'application/atom+xml; charset=utf-8'); + res.send(feed.render('atom-1.0')); + }) + .fail(next); +}; + +// Control access to the API +Nuts.prototype.onAPIAccessControl = function(req, res, next) { + this.performQ('api', { + req: req, + res: res + }) + .then(function() { + next(); + }, next); +}; + + +module.exports = Nuts; diff --git a/lib/notes.js b/lib/utils/notes.js similarity index 100% rename from lib/notes.js rename to lib/utils/notes.js diff --git a/lib/platforms.js b/lib/utils/platforms.js similarity index 74% rename from lib/platforms.js rename to lib/utils/platforms.js index f8a1764..fde3b46 100644 --- a/lib/platforms.js +++ b/lib/utils/platforms.js @@ -5,6 +5,12 @@ var platforms = { LINUX: 'linux', LINUX_32: 'linux_32', LINUX_64: 'linux_64', + LINUX_RPM: 'linux_rpm', + LINUX_RPM_32: 'linux_rpm_32', + LINUX_RPM_64: 'linux_rpm_64', + LINUX_DEB: 'linux_deb', + LINUX_DEB_32: 'linux_deb_32', + LINUX_DEB_64: 'linux_deb_64', OSX: 'osx', OSX_32: 'osx_32', OSX_64: 'osx_64', @@ -12,7 +18,8 @@ var platforms = { WINDOWS_32: 'windows_32', WINDOWS_64: 'windows_64', - detect: detectPlatform + detect: detectPlatform, + detectPlatformByUserAgent: detectPlatformByUserAgent }; // Reduce a platfrom id to its type @@ -26,21 +33,34 @@ function detectPlatform(platform) { var prefix = "", suffix = ""; // Detect NuGet/Squirrel.Windows files - if (name == 'releases' || path.extname(name) == '.nupkg') return platforms.WINDOWS_32; + if (name == 'releases' || hasSuffix(name, '.nupkg')) return platforms.WINDOWS_32; // Detect prefix: osx, widnows or linux if (_.contains(name, 'win') - || path.extname(name) == '.exe') prefix = platforms.WINDOWS; + || hasSuffix(name, '.exe')) prefix = platforms.WINDOWS; if (_.contains(name, 'linux') || _.contains(name, 'ubuntu') - || path.extname(name) == '.deb' - || path.extname(name) == '.rpm') prefix = platforms.LINUX; + || hasSuffix(name, '.deb') + || hasSuffix(name, '.rpm') + || hasSuffix(name, '.tgz') + || hasSuffix(name, '.tar.gz')) { + + if (_.contains(name, 'linux_deb') || hasSuffix(name, '.deb')) { + prefix = platforms.LINUX_DEB; + } + else if (_.contains(name, 'linux_rpm') || hasSuffix(name, '.rpm')) { + prefix = platforms.LINUX_RPM; + } + else if (_.contains(name, 'linux') || hasSuffix(name, '.tgz') || hasSuffix(name, '.tar.gz')) { + prefix = platforms.LINUX; + } + } if (_.contains(name, 'mac') || _.contains(name, 'osx') || name.indexOf('darwin') >= 0 - || path.extname(name) == '.dmg') prefix = platforms.OSX; + || hasSuffix(name, '.dmg')) prefix = platforms.OSX; // Detect suffix: 32 or 64 if (_.contains(name, '32') @@ -61,6 +81,10 @@ function detectPlatformByUserAgent(useragent) { else return null } +function hasSuffix(str, suffix) { + return str.slice(str.length-suffix.length) === suffix; +} + // Satisfies a platform function satisfiesPlatform(platform, list) { if (_.contains(list, platform)) return true; @@ -75,7 +99,7 @@ function satisfiesPlatform(platform, list) { function resolveForVersion(version, platformID, opts) { opts = _.defaults(opts || {}, { // Order for filetype - filePreference: ['.exe', '.dmg', '.deb', '.rpm', '.zip', '.nupkg'], + filePreference: ['.exe', '.dmg', '.deb', '.rpm', '.tgz', '.tar.gz', '.zip', '.nupkg'], wanted: null }); diff --git a/lib/win-releases.js b/lib/utils/win-releases.js similarity index 82% rename from lib/win-releases.js rename to lib/utils/win-releases.js index 29055c5..e468f97 100644 --- a/lib/win-releases.js +++ b/lib/utils/win-releases.js @@ -37,6 +37,22 @@ function normVersion(tag) { ].join('.') + (prerelease? '.'+prerelease : ''); } +// Map a windows version to a semver +function toSemver(tag) { + var parts = tag.split('.'); + var version = parts.slice(0, 3).join('.'); + var prerelease = Number(parts[3]); + + // semver == windows version + if (!prerelease) return version; + + var channelId = Math.floor(prerelease/CHANNEL_MAGINITUDE); + var channel = CHANNELS[channelId - 1]; + var count = prerelease - (channelId*CHANNEL_MAGINITUDE); + + return version + '-' + channel + '.' + count +} + // Parse RELEASES file // https://github.com/Squirrel/Squirrel.Windows/blob/0d1250aa6f0c25fe22e92add78af327d1277d97d/src/Squirrel/ReleaseExtensions.cs#L19 function parseRELEASES(content) { @@ -70,7 +86,8 @@ function parseRELEASES(content) { filename: filename, size: Number(parts[3]), isDelta: isDelta, - version: version + version: version, + semver: toSemver(version) }; }) .compact() @@ -101,6 +118,7 @@ function generateRELEASES(entries) { module.exports = { normVersion: normVersion, + toSemver: toSemver, parse: parseRELEASES, generate: generateRELEASES }; diff --git a/lib/versions.js b/lib/versions.js index 26db082..467d96a 100644 --- a/lib/versions.js +++ b/lib/versions.js @@ -2,161 +2,152 @@ var _ = require('lodash'); var Q = require('q'); var semver = require('semver'); -var platforms = require('./platforms'); +var platforms = require('./utils/platforms'); + +// Normalize tag name +function normalizeTag(tag) { + if (tag[0] == 'v') tag = tag.slice(1); + return tag; +} + +// Extract channel of version +function extractChannel(tag) { + var suffix = tag.split('-')[1]; + if (!suffix) return 'stable'; + + return suffix.split('.')[0]; +} + +// Normalize a release to a version +function normalizeVersion(release) { + // Ignore draft + if (release.draft) return null; + + var downloadCount = 0; + var releasePlatforms = _.chain(release.assets) + .map(function(asset) { + var platform = platforms.detect(asset.name); + if (!platform) return null; + + downloadCount = downloadCount + asset.download_count; + return { + id: String(asset.id), + type: platform, + filename: asset.name, + size: asset.size, + content_type: asset.content_type, + raw: asset + }; + }) + .compact() + .value(); -module.exports = function(github, opts) { - // Normalize tag name - function normalizeTag(tag) { - if (tag[0] == 'v') tag = tag.slice(1); - return tag; + return { + tag: normalizeTag(release.tag_name).split('-')[0], + channel: extractChannel(release.tag_name), + notes: release.body || "", + published_at: new Date(release.published_at), + platforms: releasePlatforms + }; +} + +// Compare two version +function compareVersions(v1, v2) { + if (semver.gt(v1.tag, v2.tag)) { + return -1; + } + if (semver.lt(v1.tag, v2.tag)) { + return 1; } + return 0; +} - // Extract channel of version - function extractChannel(tag) { - var suffix = tag.split('-')[1]; - if (!suffix) return 'stable'; +function Versions(backend) { + this.backend = backend; - return suffix.split('.')[0]; - } +} - // Normalize a release to a version - function normalizeVersion(release) { - // Ignore draft - if (release.draft) return null; - - var downloadCount = 0; - var releasePlatforms = _.chain(release.assets) - .map(function(asset) { - var platform = platforms.detect(asset.name); - if (!platform) return null; - - downloadCount = downloadCount + asset.download_count; - return { - type: platform, - filename: asset.name, - size: asset.size, - content_type: asset.content_type, - download_url: asset.url, - download_count: asset.download_count - }; - }) +// List versions normalized +Versions.prototype.list = function() { + return this.backend.releases() + .then(function(releases) { + return _.chain(releases) + .map(normalizeVersion) .compact() + .sort(compareVersions) .value(); + }); +}; - return { - tag: normalizeTag(release.tag_name), - channel: extractChannel(release.tag_name), - notes: release.body || "", - published_at: new Date(release.published_at), - platforms: releasePlatforms, - download_count: downloadCount - }; - } - - // Compare two version - function compareVersions(v1, v2) { - if (semver.gt(v1.tag, v2.tag)) { - return -1; - } - if (semver.lt(v1.tag, v2.tag)) { - return 1; - } - return 0; - } +// Get a specific version by its tag +Versions.prototype.get = function(tag) { + return this.resolve({ + tag: tag + }); +}; - // List all available version - function listVersions() { - return github.releases() - .then(function(releases) { - return _.chain(releases) - .map(normalizeVersion) - .compact() - .sort(compareVersions) - .value(); - }); - } +// Filter versions with criterias +Versions.prototype.filter = function(opts) { + opts = _.defaults(opts || {}, { + tag: 'latest', + platform: null, + channel: 'stable' + }); + if (opts.platform) opts.platform = platforms.detect(opts.platform); + + return this.list() + .then(function(versions) { + return _.chain(versions) + .filter(function(version) { + // Check channel + if (opts.channel != '*' && version.channel != opts.channel) return false; + + // Not available for requested paltform + if (opts.platform && !platforms.satisfies(opts.platform, _.pluck(version.platforms, 'type'))) return false; + + // Check tag satisfies request version + return opts.tag == 'latest' || semver.satisfies(version.tag, opts.tag); + }) + .value(); + }); +}; - // Get a specific version - function getVersion(tag) { - return resolveVersion({ - tag: tag - }); - } +// Resolve a platform, by filtering then taking the first result +Versions.prototype.resolve = function(opts) { + return this.filter(opts) + .then(function(versions) { + var version = _.first(versions); + if (!version) throw new Error('Version not found: '+opts.tag); - // Filter versions - function filterVersions(opts) { - opts = _.defaults(opts || {}, { - tag: 'latest', - platform: null, - channel: 'stable' - }); - if (opts.platform) opts.platform = platforms.detect(opts.platform); - - return listVersions() - .then(function(versions) { - return _.chain(versions) - .filter(function(version) { - // Check channel - if (opts.channel != '*' && version.channel != opts.channel) return false; - - // Not available for requested paltform - if (opts.platform && !platforms.satisfies(opts.platform, _.pluck(version.platforms, 'type'))) return false; - - // Check tag satisfies request version - return opts.tag == 'latest' || semver.satisfies(version.tag, opts.tag); - }) - .value(); - }); - } + return version; + }); +}; - // Resolve a platform - function resolveVersion(opts) { - return filterVersions(opts) - .then(function(versions) { - var version = _.first(versions); - if (!version) throw new Error('Version not found: '+opts.tag); - return version; - }); - } +// List all channels from releases +Versions.prototype.channels = function(opts) { + return this.list() + .then(function(versions) { + var channels = {}; + + _.each(versions, function(version) { + if (!channels[version.channel]) { + channels[version.channel] = { + latest: null, + versions_count: 0, + published_at: 0 + }; + } - // Extract list of channels - function listChannels() { - return listVersions() - .then(function(versions) { - var channels = {}; - - _.each(versions, function(version) { - if (!channels[version.channel]) { - channels[version.channel] = { - latest: null, - download_count: 0, - versions_count: 0, - published_at: 0 - }; - } - - channels[version.channel].download_count += version.download_count; - channels[version.channel].versions_count += 1; - if (channels[version.channel].published_at < version.published_at) { - channels[version.channel].latest = version.tag; - channels[version.channel].published_at = version.published_at; - } - }); - - return channels; - - _.chain(versions) - .pluck('channel') - .uniq() - .value(); + channels[version.channel].versions_count += 1; + if (channels[version.channel].published_at < version.published_at) { + channels[version.channel].latest = version.tag; + channels[version.channel].published_at = version.published_at; + } }); - } - return { - list: listVersions, - get: getVersion, - filter: filterVersions, - resolve: resolveVersion, - channels: listChannels - }; + return channels; + }); }; + + +module.exports = Versions; diff --git a/package.json b/package.json index db6bd17..759b075 100644 --- a/package.json +++ b/package.json @@ -1,47 +1,50 @@ { - "name": "nuts-serve", - "version": "2.6.2", - "description": "Server to make GitHub releases (private) available to download with Squirrel support", - "main": "./lib/index.js", - "homepage": "https://github.com/GitbookIO/nuts", - "license": "Apache-2.0", - "main": "./lib/index.js", - "dependencies": { - "express": "^4.13.3", - "lodash": "3.7.0", - "q": "1.2.0", - "body-parser": "1.12.3", - "octonode": "0.7.1", - "semver": "5.0.1", - "request": "2.60.0", - "basic-auth": "1.0.3", - "express-useragent": "0.1.9", - "stores": "0.0.2", - "analytics-node": "1.2.2", - "uuid": "2.0.1", - "github-webhook-handler": "0.5.0", - "strip-bom": "2.0.0", - "destroy": "1.0.3" - }, - "devDependencies": { - "mocha": "1.18.2", - "should": "7.0.4" - }, - "bugs": { - "url": "https://github.com/GitbookIO/nuts/issues" - }, - "authors": [ - { - "name": "Samy Pesse", - "email": "samypesse@gmail.com" - } - ], - "repository": { - "type" : "git", - "url" : "https://github.com/GitbookIO/nuts.git" - }, - "scripts": { - "start": "node bin/web.js", - "test": "mocha --reporter list" + "name": "nuts-serve", + "version": "3.1.1", + "description": "Server to make GitHub releases (private) available to download with Squirrel support", + "main": "./lib/index.js", + "homepage": "https://github.com/GitbookIO/nuts", + "license": "Apache-2.0", + "dependencies": { + "analytics-node": "2.1.1", + "basic-auth": "1.0.3", + "body-parser": "1.12.3", + "destroy": "1.0.3", + "express": "^4.13.3", + "express-useragent": "0.1.9", + "feed": "^0.3.0", + "github-webhook-handler": "0.5.0", + "lodash": "3.7.0", + "lru-diskcache": "1.1.1", + "octocat": "0.12.1", + "q": "1.2.0", + "request": "2.74.0", + "semver": "5.0.1", + "stream-res": "1.0.1", + "strip-bom": "2.0.0", + "understudy": "4.1.0", + "urljoin.js": "0.1.0", + "uuid": "2.0.1" + }, + "devDependencies": { + "mocha": "1.18.2", + "should": "7.0.4" + }, + "bugs": { + "url": "https://github.com/GitbookIO/nuts/issues" + }, + "authors": [ + { + "name": "Samy Pesse", + "email": "samypesse@gmail.com" } + ], + "repository": { + "type": "git", + "url": "https://github.com/GitbookIO/nuts.git" + }, + "scripts": { + "start": "node bin/web.js", + "test": "mocha --reporter list" + } } diff --git a/test/platforms.js b/test/platforms.js index b870484..10050f0 100644 --- a/test/platforms.js +++ b/test/platforms.js @@ -1,5 +1,5 @@ -var should = require('should'); -var platforms = require('../lib/platforms'); +require('should'); +var platforms = require('../lib/utils/platforms'); var useragent = require('express-useragent'); describe('Platforms', function() { @@ -26,7 +26,26 @@ describe('Platforms', function() { }); it('should detect linux', function() { - platforms.detect('atom-amd64.deb').should.be.exactly(platforms.LINUX_64); + platforms.detect('enterprise-amd64.tar.gz').should.be.exactly(platforms.LINUX_64); + platforms.detect('enterprise-amd64.tgz').should.be.exactly(platforms.LINUX_64); + platforms.detect('enterprise-ia32.tar.gz').should.be.exactly(platforms.LINUX_32); + platforms.detect('enterprise-ia32.tgz').should.be.exactly(platforms.LINUX_32); + }); + + it('should detect debian_32', function() { + platforms.detect('atom-ia32.deb').should.be.exactly(platforms.LINUX_DEB_32); + }); + + it('should detect debian_64', function() { + platforms.detect('atom-amd64.deb').should.be.exactly(platforms.LINUX_DEB_64); + }); + + it('should detect rpm_32', function() { + platforms.detect('atom-ia32.rpm').should.be.exactly(platforms.LINUX_RPM_32); + }); + + it('should detect rpm_64', function() { + platforms.detect('atom-amd64.rpm').should.be.exactly(platforms.LINUX_RPM_64); }); }); @@ -35,77 +54,123 @@ describe('Platforms', function() { var version = { platforms: [ { - "type": "osx_64", - "filename": "test-3.3.1-darwin.dmg", - "download_url": "https://api.github.com/repos/test/test2/releases/assets/793838", - "download_count": 2 + 'type': 'osx_64', + 'filename': 'test-3.3.1-darwin.dmg', + 'download_url': 'https://api.github.com/repos/test/test2/releases/assets/793838', + 'download_count': 2 + }, + { + 'type': 'osx_64', + 'filename': 'test-3.3.1-darwin-x64.zip', + 'download_url': 'https://api.github.com/repos/test/test2/releases/assets/793869', + 'download_count': 0 }, { - "type": "osx_64", - "filename": "test-3.3.1-darwin-x64.zip", - "download_url": "https://api.github.com/repos/test/test2/releases/assets/793869", - "download_count": 0 + 'type': 'windows_32', + 'filename': 'atom-1.0.9-delta.nupkg', + 'size': 1457531, + 'content_type': 'application/zip', + 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825732', + 'download_count': 55844 }, { - "type": "windows_32", - "filename": "atom-1.0.9-delta.nupkg", - "size": 1457531, - "content_type": "application/zip", - "download_url": "https://api.github.com/repos/atom/atom/releases/assets/825732", - "download_count": 55844 + 'type': 'windows_32', + 'filename': 'atom-1.0.9-full.nupkg', + 'size': 78181725, + 'content_type': 'application/zip', + 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825730', + 'download_count': 26987 }, { - "type": "windows_32", - "filename": "atom-1.0.9-full.nupkg", - "size": 78181725, - "content_type": "application/zip", - "download_url": "https://api.github.com/repos/atom/atom/releases/assets/825730", - "download_count": 26987 + 'type': 'linux_32', + 'filename': 'atom-ia32.tar.gz', + 'size': 71292506, + 'content_type': 'application/zip', + 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825658', + 'download_count': 2494 }, { - "type": "linux_64", - "filename": "atom-amd64.deb", - "size": 71292506, - "content_type": "application/zip", - "download_url": "https://api.github.com/repos/atom/atom/releases/assets/825658", - "download_count": 2494 + 'type': 'linux_64', + 'filename': 'atom-amd64.tar.gz', + 'size': 71292506, + 'content_type': 'application/zip', + 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825658', + 'download_count': 2494 }, { - "type": "windows_32", - "filename": "atom-windows.zip", - "size": 79815714, - "content_type": "application/zip", - "download_url": "https://api.github.com/repos/atom/atom/releases/assets/825729", - "download_count": 463 + 'type': 'linux_rpm_32', + 'filename': 'atom-ia32.rpm', + 'size': 71292506, + 'content_type': 'application/zip', + 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825658', + 'download_count': 2494 }, { - "type": "windows_32", - "filename": "AtomSetup.exe", - "size": 78675720, - "content_type": "application/zip", - "download_url": "https://api.github.com/repos/atom/atom/releases/assets/825728", - "download_count": 5612 + 'type': 'linux_rpm_64', + 'filename': 'atom-amd64.rpm', + 'size': 71292506, + 'content_type': 'application/zip', + 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825658', + 'download_count': 2494 + }, + { + 'type': 'linux_deb_32', + 'filename': 'atom-ia32.deb', + 'size': 71292506, + 'content_type': 'application/zip', + 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825658', + 'download_count': 2494 + }, + { + 'type': 'linux_deb_64', + 'filename': 'atom-amd64.deb', + 'size': 71292506, + 'content_type': 'application/zip', + 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825658', + 'download_count': 2494 + }, + { + 'type': 'windows_32', + 'filename': 'atom-windows.zip', + 'size': 79815714, + 'content_type': 'application/zip', + 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825729', + 'download_count': 463 + }, + { + 'type': 'windows_32', + 'filename': 'AtomSetup.exe', + 'size': 78675720, + 'content_type': 'application/zip', + 'download_url': 'https://api.github.com/repos/atom/atom/releases/assets/825728', + 'download_count': 5612 } ] }; it('should resolve to best platform', function() { - platforms.resolve(version, 'osx').filename.should.be.exactly("test-3.3.1-darwin.dmg"), - platforms.resolve(version, 'win32').filename.should.be.exactly("AtomSetup.exe") + platforms.resolve(version, 'osx').filename.should.be.exactly('test-3.3.1-darwin.dmg'); + platforms.resolve(version, 'win32').filename.should.be.exactly('AtomSetup.exe'); + platforms.resolve(version, 'linux_64').filename.should.be.exactly('atom-amd64.tar.gz'); + platforms.resolve(version, 'linux_32').filename.should.be.exactly('atom-ia32.tar.gz'); + platforms.resolve(version, 'linux_rpm_32').filename.should.be.exactly('atom-ia32.rpm'); + platforms.resolve(version, 'linux_rpm_64').filename.should.be.exactly('atom-amd64.rpm'); + platforms.resolve(version, 'linux_deb_32').filename.should.be.exactly('atom-ia32.deb'); + platforms.resolve(version, 'linux_deb_64').filename.should.be.exactly('atom-amd64.deb'); }); it('should resolve to best platform with a preferred filetype', function() { platforms.resolve(version, 'osx', { filePreference: ['.zip'] - }).filename.should.be.exactly("test-3.3.1-darwin-x64.zip") + }).filename.should.be.exactly('test-3.3.1-darwin-x64.zip'); }); it('should resolve to best platform with a wanted filetype', function() { platforms.resolve(version, 'osx', { wanted: '.zip' - }).filename.should.be.exactly("test-3.3.1-darwin-x64.zip") + }).filename.should.be.exactly('test-3.3.1-darwin-x64.zip'); }); - }) + }); }); diff --git a/test/win-releases.js b/test/win-releases.js index b2a1a0f..e06fac0 100644 --- a/test/win-releases.js +++ b/test/win-releases.js @@ -1,5 +1,5 @@ -var should = require('should'); -var winReleases = require('../lib/win-releases'); +require('should'); +var winReleases = require('../lib/utils/win-releases'); describe('Windows RELEASES', function() { @@ -19,6 +19,13 @@ describe('Windows RELEASES', function() { winReleases.normVersion('1.0.0-14').should.be.exactly('1.0.0.14'); }); + it('should correctly return to a semver', function() { + winReleases.toSemver('1.0.0.1001').should.be.exactly('1.0.0-alpha.1'); + winReleases.toSemver('1.0.0.2001').should.be.exactly('1.0.0-beta.1'); + winReleases.toSemver('1.0.0.2015').should.be.exactly('1.0.0-beta.15'); + winReleases.toSemver('1.0.0').should.be.exactly('1.0.0'); + }); + }); describe('Parsing', function() { @@ -36,7 +43,7 @@ describe('Windows RELEASES', function() { }); it('should parse a one-line file (with utf-8 BOM)', function() { - var oneRelease = winReleases.parse("\uFEFF24182FAD211FB9EB72610B1C086810FE37F70AE3 gitbook-editor-4.0.0-full.nupkg 46687158"); + var oneRelease = winReleases.parse('\uFEFF24182FAD211FB9EB72610B1C086810FE37F70AE3 gitbook-editor-4.0.0-full.nupkg 46687158'); oneRelease.length.should.be.exactly(1); }); @@ -60,8 +67,8 @@ describe('Windows RELEASES', function() { }); it('should correctly parse versions', function() { - releases[0].version.should.be.exactly("0.178.0"); - releases[1].version.should.be.exactly("0.178.1"); + releases[0].version.should.be.exactly('0.178.0'); + releases[1].version.should.be.exactly('0.178.1'); }); }); diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..4142fce --- /dev/null +++ b/yarn.lock @@ -0,0 +1,2090 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + +acorn@^5.2.1: + version "5.7.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" + integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== + +agent-base@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-1.0.2.tgz#6890d3fb217004b62b70f8928e0fae5f8952a706" + integrity sha1-aJDT+yFwBLYrcPiSjg+uX4lSpwY= + +align-text@^0.1.1, align-text@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + integrity sha1-DNkKVhCT810KmSVsIrcGlDP60Rc= + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + +alter@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/alter/-/alter-0.2.0.tgz#c7588808617572034aae62480af26b1d4d1cb3cd" + integrity sha1-x1iICGF1cgNKrmJICvJrHU0cs80= + dependencies: + stable "~0.1.3" + +analytics-node@1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/analytics-node/-/analytics-node-1.2.2.tgz#322d2546af4ed566ba914468b6ea39636008c5b5" + integrity sha1-Mi0lRq9O1Wa6kURotuo5Y2AIxbU= + dependencies: + uid "0.0.2" + clone "~0.1.17" + component-type "~1.0.0" + debug "~1.0.4" + join-component "~1.0.0" + lodash "~2.4.1" + superagent "~0.19.1" + superagent-proxy "~0.3.1" + superagent-retry "~0.4.0" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + +asn1@0.1.11: + version "0.1.11" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.1.11.tgz#559be18376d08a4ec4dbe80877d27818639b2df7" + integrity sha1-VZvhg3bQik7E2+gId9J4GGObLfc= + +assert-plus@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.1.5.tgz#ee74009413002d84cec7219c6ac811812e723160" + integrity sha1-7nQAlBMALYTOxyGcasgRgS5yMWA= + +ast-traverse@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ast-traverse/-/ast-traverse-0.1.1.tgz#69cf2b8386f19dcda1bb1e05d68fe359d8897de6" + integrity sha1-ac8rg4bxnc2hux4F1o/jWdiJfeY= + +ast-types@0.8.12: + version "0.8.12" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.8.12.tgz#a0d90e4351bb887716c83fd637ebf818af4adfcc" + integrity sha1-oNkOQ1G7iHcWyD/WN+v4GK9K38w= + +ast-types@0.9.6: + version "0.9.6" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9" + integrity sha1-ECyenpAF0+fjgpvwxPok7oYu6bk= + +ast-types@0.x.x: + version "0.14.2" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.14.2.tgz#600b882df8583e3cd4f2df5fa20fa83759d4bdfd" + integrity sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA== + dependencies: + tslib "^2.0.1" + +async@^2.0.1: + version "2.6.3" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff" + integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg== + dependencies: + lodash "^4.17.14" + +async@~0.9.0: + version "0.9.2" + resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" + integrity sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0= + +aws-sign2@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.5.0.tgz#c57103f7a17fc037f02d7c2e64b602ea223f7d63" + integrity sha1-xXED96F/wDfwLXwuZLYC6iI/fWM= + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +basic-auth@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-1.0.3.tgz#41f55523e589405038ee3567958c62a5ed70551a" + integrity sha1-QfVVI+WJQFA47jVnlYxipe1wVRo= + +bl@~0.9.4: + version "0.9.5" + resolved "https://registry.yarnpkg.com/bl/-/bl-0.9.5.tgz#c06b797af085ea00bc527afc8efcf11de2232054" + integrity sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ= + dependencies: + readable-stream "~1.0.26" + +bl@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.0.3.tgz#fc5421a28fd4226036c3b3891a66a25bc64d226e" + integrity sha1-/FQhoo/UImA2w7OJGmaiW8ZNIm4= + dependencies: + readable-stream "~2.0.5" + +bluebird@^2.9.30: + version "2.11.0" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" + integrity sha1-U0uQM8AiyVecVro7Plpcqvu2UOE= + +body-parser@1.12.3: + version "1.12.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.12.3.tgz#5f40bf17e7823be6895d4d35582752e36cf97f71" + integrity sha1-X0C/F+eCO+aJXU01WCdS42z5f3E= + dependencies: + bytes "1.0.0" + content-type "~1.0.1" + debug "~2.1.3" + depd "~1.0.1" + iconv-lite "0.4.8" + on-finished "~2.2.0" + qs "2.4.1" + raw-body "1.3.4" + type-is "~1.6.1" + +body-parser@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + integrity sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8= + dependencies: + hoek "2.x.x" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +breakable@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/breakable/-/breakable-1.0.0.tgz#784a797915a38ead27bad456b5572cb4bbaa78c1" + integrity sha1-eEp5eRWjjq0nutRWtVcstLuqeME= + +buffer@^5.1.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +bytes@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" + integrity sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g= + +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + +camelcase@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + integrity sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk= + +caseless@~0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" + integrity sha1-cVuW6phBWTzDMGeSP17GDr2k99c= + +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + integrity sha1-qg0yYptu6XIgBBHL1EYckHvCt60= + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + +chalk@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + 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" + +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + integrity sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE= + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + +clone@~0.1.17: + version "0.1.19" + resolved "https://registry.yarnpkg.com/clone/-/clone-0.1.19.tgz#613fb68639b26a494ac53253e15b1a6bd88ada85" + integrity sha1-YT+2hjmyaklKxTJT4Vsaa9iK2oU= + +co@~3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/co/-/co-3.0.6.tgz#1445f226c5eb956138e68c9ac30167ea7d2e6bda" + integrity sha1-FEXyJsXrlWE45oyawwFn6n0ua9o= + +combined-stream@^1.0.5, combined-stream@~1.0.1: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +combined-stream@~0.0.4: + version "0.0.7" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-0.0.7.tgz#0137e657baa5a7541c57ac37ac5fc07d73b4dc1f" + integrity sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8= + dependencies: + delayed-stream "0.0.5" + +commander@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06" + integrity sha1-+mihT2qUXVTbvlDYzbMyDp47GgY= + +commander@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.0.0.tgz#d1b86f901f8b64bd941bdeadaf924530393be928" + integrity sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg= + +commander@^2.5.0, commander@^2.8.1: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commoner@~0.10.3: + version "0.10.8" + resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5" + integrity sha1-NPw2cs0kOT6LtH5wyqApOBH08sU= + dependencies: + commander "^2.5.0" + detective "^4.3.1" + glob "^5.0.15" + graceful-fs "^4.1.2" + iconv-lite "^0.4.5" + mkdirp "^0.5.0" + private "^0.1.6" + q "^1.1.2" + recast "^0.11.17" + +component-emitter@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3" + integrity sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM= + +component-type@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/component-type/-/component-type-1.0.0.tgz#1ed8812e32dd65099d433570757f111ea3d3d871" + integrity sha1-HtiBLjLdZQmdQzVwdX8RHqPT2HE= + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" + +content-type@~1.0.1, content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + +cookiejar@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.0.1.tgz#3d12752f6adf68a892f332433492bd5812bb668f" + integrity sha1-PRJ1L2rfaKiS8zJDNJK9WBK7Zo8= + +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" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +crc@^3.4.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/crc/-/crc-3.8.0.tgz#ad60269c2c856f8c299e2c4cc0de4556914056c6" + integrity sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== + dependencies: + buffer "^5.1.0" + +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + integrity sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g= + dependencies: + boom "2.x.x" + +ctype@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/ctype/-/ctype-0.5.3.tgz#82c18c2461f74114ef16c135224ad0b9144ca12f" + integrity sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8= + +data-uri-to-buffer@0: + version "0.0.4" + resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-0.0.4.tgz#46e13ab9da8e309745c8d01ce547213ebdb2fe3f" + integrity sha1-RuE6udqOMJdFyNAc5UchPr2y/j8= + +debug@*: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + +debug@2, debug@2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@~1.0.1, debug@~1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.5.tgz#f7241217430f99dec4c2b473eab92228e874c2ac" + integrity sha1-9yQSF0MPmd7EwrRz6rkiKOh0wqw= + dependencies: + ms "2.0.0" + +debug@~2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.1.3.tgz#ce8ab1b5ee8fbee2bfa3b633cab93d366b63418e" + integrity sha1-zoqxte6PvuK/o7Yzyrk9NmtjQY4= + dependencies: + ms "0.7.0" + +debug@~2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" + integrity sha1-+HBX6ZWxofauaklgZkE3vFbwOdo= + dependencies: + ms "0.7.1" + +decamelize@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= + +defs@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/defs/-/defs-1.1.1.tgz#b22609f2c7a11ba7a3db116805c139b1caffa9d2" + integrity sha1-siYJ8sehG6ej2xFoBcE5scr/qdI= + dependencies: + alter "~0.2.0" + ast-traverse "~0.1.1" + breakable "~1.0.0" + esprima-fb "~15001.1001.0-dev-harmony-fb" + simple-fmt "~0.1.0" + simple-is "~0.2.0" + stringmap "~0.2.2" + stringset "~0.2.1" + tryor "~0.1.2" + yargs "~3.27.0" + +degenerator@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-1.0.4.tgz#fcf490a37ece266464d9cc431ab98c5819ced095" + integrity sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU= + dependencies: + ast-types "0.x.x" + escodegen "1.x.x" + esprima "3.x.x" + +delayed-stream@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-0.0.5.tgz#d4b1f43a93e8296dfe02694f4680bc37a313c73f" + integrity sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8= + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= + +depd@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.0.1.tgz#80aec64c9d6d97e65cc2a9caa93c0aa6abf73aaa" + integrity sha1-gK7GTJ1tl+ZcwqnKqTwKpqv3Oqo= + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +destroy@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.3.tgz#b433b4724e71fd8551d9885174851c5fc377e2c9" + integrity sha1-tDO0ck5x/YVR2YhRdIUcX8N34sk= + +destroy@^1.0.4, destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + +detective@^4.3.1: + version "4.7.1" + resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e" + integrity sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig== + dependencies: + acorn "^5.2.1" + defined "^1.0.0" + +diff@1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/diff/-/diff-1.0.7.tgz#24bbb001c4a7d5522169e7cabdb2c2814ed91cf4" + integrity sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ= + +ee-first@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.0.3.tgz#6c98c4089abecb5a7b85c1ac449aa603d3b3dabe" + integrity sha1-bJjECJq+y1p7hcGsRJqmA9Oz2r4= + +ee-first@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.0.tgz#6a0d7c6221e490feefd92ec3f441c9ce8cd097f4" + integrity sha1-ag18YiHkkP7v2S7D9EHJzozQl/Q= + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + +escape-string-regexp@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +escodegen@1.x.x: + version "1.14.3" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" + integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== + dependencies: + esprima "^4.0.1" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +esprima-fb@~15001.1001.0-dev-harmony-fb: + version "15001.1001.0-dev-harmony-fb" + resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz#43beb57ec26e8cf237d3dd8b33e42533577f2659" + integrity sha1-Q761fsJujPI3092LM+QlM1d/Jlk= + +esprima@3.x.x, esprima@~3.1.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= + +esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +estraverse@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + +express-useragent@0.1.9: + version "0.1.9" + resolved "https://registry.yarnpkg.com/express-useragent/-/express-useragent-0.1.9.tgz#95f35de41b0b97636c94fbd4a26137b3a6008a39" + integrity sha1-lfNd5BsLl2NslPvUomE3s6YAijk= + +express@^4.13.3: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +extend@3, extend@~3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +extend@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-1.2.1.tgz#a0f5fd6cfc83a5fe49ef698d60ec8a624dd4576c" + integrity sha1-oPX9bPyDpf5J72mNYOyKYk3UV2w= + +fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= + +feed@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/feed/-/feed-0.3.1.tgz#a79f111d0c57e9a9b626ebd606e259f72e6af66c" + integrity sha1-p58RHQxX6am2JuvWBuJZ9y5q9mw= + dependencies: + xml ">= 0.0.5" + +file-uri-to-path@0: + version "0.0.2" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-0.0.2.tgz#37cdd1b5b905404b3f05e1b23645be694ff70f82" + integrity sha1-N83RtbkFQEs/BeGyNkW+aU/3D4I= + +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + +finished@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/finished/-/finished-1.2.2.tgz#41608eafadfd65683b46a1220bc4b1ec3daedcd8" + integrity sha1-QWCOr639ZWg7RqEiC8Sx7D2u3Ng= + dependencies: + ee-first "1.0.3" + +forever-agent@~0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= + +form-data@0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-0.1.3.tgz#4ee4346e6eb5362e8344a02075bd8dbd8c7373ea" + integrity sha1-TuQ0bm61Ni6DRKAgdb2NvYxzc+o= + dependencies: + async "~0.9.0" + combined-stream "~0.0.4" + mime "~1.2.11" + +form-data@~1.0.0-rc1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.1.tgz#ae315db9a4907fa065502304a66d7733475ee37c" + integrity sha1-rjFduaSQf6BlUCMEpm13M0de43w= + dependencies: + async "^2.0.1" + combined-stream "^1.0.5" + mime-types "^2.1.11" + +formidable@1.0.14: + version "1.0.14" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.0.14.tgz#2b3f4c411cbb5fdd695c44843e2a23514a43231a" + integrity sha1-Kz9MQRy7X91pXESEPiojUUpDIxo= + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + +fs-extra@^0.26.5: + version "0.26.7" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9" + integrity sha1-muH92UiXeY7at20JGM9C0MMYT6k= + dependencies: + graceful-fs "^4.1.2" + jsonfile "^2.1.0" + klaw "^1.0.0" + path-is-absolute "^1.0.0" + rimraf "^2.2.8" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fswrite-stream@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fswrite-stream/-/fswrite-stream-1.0.0.tgz#7ac3cfe6092ddff998435675502389e652a36fae" + integrity sha1-esPP5gkt3/mYQ1Z1UCOJ5lKjb64= + dependencies: + length-stream "^0.1.1" + +ftp@~0.3.5: + version "0.3.10" + resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" + integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0= + dependencies: + readable-stream "1.1.x" + xregexp "2.0.0" + +generate-function@^2.0.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" + integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== + dependencies: + is-property "^1.0.2" + +generate-object-property@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" + integrity sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA= + dependencies: + is-property "^1.0.0" + +get-uri@~0.1.0: + version "0.1.4" + resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-0.1.4.tgz#35f8a7954c129fb132ff2ddf5ed81a57cb8a9e54" + integrity sha1-NfinlUwSn7Ey/y3fXtgaV8uKnlQ= + dependencies: + data-uri-to-buffer "0" + debug "2" + extend "3" + file-uri-to-path "0" + ftp "~0.3.5" + readable-stream "2" + +github-webhook-handler@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/github-webhook-handler/-/github-webhook-handler-0.5.0.tgz#c1fe95392ed49ca2f06864fbacaacfea3f8dd035" + integrity sha1-wf6VOS7UnKLwaGT7rKrP6j+N0DU= + dependencies: + bl "~0.9.4" + +glob@3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.3.tgz#e313eeb249c7affaa5c475286b0e115b59839467" + integrity sha1-4xPusknHr/qlxHUoaw4RW1mDlGc= + dependencies: + graceful-fs "~2.0.0" + inherits "2" + minimatch "~0.2.11" + +glob@^5.0.15: + version "5.0.15" + resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" + integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E= + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@^7.1.3: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: + version "4.2.6" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" + integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== + +graceful-fs@~2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-2.0.3.tgz#7cd2cdb228a4a3f36e95efa6cc142de7d1a136d0" + integrity sha1-fNLNsiiko/Nule+mzBQt59GhNtA= + +growl@1.7.x: + version "1.7.0" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.7.0.tgz#de2d66136d002e112ba70f3f10c31cf7c350b2da" + integrity sha1-3i1mE20ALhErpw8/EMMc98NQsto= + +har-validator@^1.6.1: + version "1.8.0" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-1.8.0.tgz#d83842b0eb4c435960aeb108a067a3aa94c0eeb2" + integrity sha1-2DhCsOtMQ1lgrrEIoGejqpTA7rI= + dependencies: + bluebird "^2.9.30" + chalk "^1.0.0" + commander "^2.8.1" + is-my-json-valid "^2.12.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + +hawk@~3.1.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + integrity sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ= + 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" + integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0= + +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-proxy-agent@0: + version "0.2.7" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-0.2.7.tgz#e17fda65f0902d952ce7921e62c7ff8862655a5e" + integrity sha1-4X/aZfCQLZUs55IeYsf/iGJlWl4= + dependencies: + agent-base "~1.0.1" + debug "2" + extend "3" + +http-signature@~0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-0.11.0.tgz#1796cf67a001ad5cd6849dca0991485f09089fe6" + integrity sha1-F5bPZ6ABrVzWhJ3KCZFIXwkIn+Y= + dependencies: + asn1 "0.1.11" + assert-plus "^0.1.5" + ctype "0.5.3" + +https-proxy-agent@0: + version "0.3.6" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-0.3.6.tgz#713fa38e5d353f50eb14a342febe29033ed1619b" + integrity sha1-cT+jjl01P1DrFKNC/r4pAz7RYZs= + dependencies: + agent-base "~1.0.1" + debug "2" + extend "3" + +iconv-lite@0.4.24, iconv-lite@^0.4.5: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@0.4.8: + version "0.4.8" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.8.tgz#c6019a7595f2cefca702eab694a010bcd9298d20" + integrity sha1-xgGadZXyzvynAuq2lKAQvNkpjSA= + +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= + +ip@^1.1.4: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + integrity sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo= + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-my-ip-valid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" + integrity sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ== + +is-my-json-valid@^2.12.0: + version "2.20.5" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.20.5.tgz#5eca6a8232a687f68869b7361be1612e7512e5df" + integrity sha512-VTPuvvGQtxvCeghwspQu1rBgjYUT6FGxPlvFKbYuFtgc4ADsX3U5ihZOYN0qyU6u+d4X9xXb0IT5O6QpXKt87A== + dependencies: + generate-function "^2.0.0" + generate-object-property "^1.1.0" + is-my-ip-valid "^1.0.0" + jsonpointer "^4.0.0" + xtend "^4.0.0" + +is-property@^1.0.0, is-property@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ= + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isstream@~0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= + +jade@0.26.3: + version "0.26.3" + resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c" + integrity sha1-jxDXl32NefL2/4YqgbBRPMslaGw= + dependencies: + commander "0.6.1" + mkdirp "0.3.0" + +join-component@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/join-component/-/join-component-1.0.0.tgz#cd2b2321c054be54e493815436b0ddc28a44235c" + integrity sha1-zSsjIcBUvlTkk4FUNrDdwopEI1w= + +js-base64@2.1.9: + version "2.1.9" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce" + integrity sha1-8OgK4DmkvWVLXygfyT8EqRSn/M4= + +json-stringify-safe@~5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + +jsonfile@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" + integrity sha1-NzaitCi4e72gzIO1P6PWM6NcKug= + optionalDependencies: + graceful-fs "^4.1.6" + +jsonpointer@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.1.0.tgz#501fb89986a2389765ba09e6053299ceb4f2c2cc" + integrity sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg== + +kind-of@^3.0.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +klaw@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/klaw/-/klaw-1.3.1.tgz#4088433b46b3b1ba259d78785d8e96f73ba02439" + integrity sha1-QIhDO0azsbolnXh4XY6W9zugJDk= + optionalDependencies: + graceful-fs "^4.1.9" + +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= + dependencies: + invert-kv "^1.0.0" + +length-stream@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/length-stream/-/length-stream-0.1.1.tgz#e724b1be2e3a961d4c4313437e49656910fae6d6" + integrity sha1-5ySxvi46lh1MQxNDfkllaRD65tY= + dependencies: + pass-stream "~0.1.0" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +lodash@3.10.1, lodash@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" + integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y= + +lodash@3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.7.0.tgz#3678bd8ab995057c07ade836ed2ef087da811d45" + integrity sha1-Nni9irmVBXwHreg27S7wh9qBHUU= + +lodash@^4.17.14: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +lodash@~2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-2.4.2.tgz#fadd834b9683073da179b3eae6d9c0d15053f73e" + integrity sha1-+t2DS5aDBz2hebPq5tnA0VBT9z4= + +longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc= + +lru-cache@2: + version "2.7.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" + integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI= + +lru-cache@^4.0.0: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +lru-cache@~2.5.0: + version "2.5.2" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.5.2.tgz#1fddad938aae1263ce138680be1b3f591c0ab41c" + integrity sha1-H92tk4quEmPOE4aAvhs/WRwKtBw= + +lru-diskcache@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/lru-diskcache/-/lru-diskcache-1.1.1.tgz#736139a0b0ca5d5992ba205fcbc2d419d65dd5b0" + integrity sha1-c2E5oLDKXVmSuiBfy8LUGdZd1bA= + dependencies: + crc "^3.4.0" + fs-extra "^0.26.5" + fswrite-stream "^1.0.0" + lodash "^3.10.1" + lru-cache "^4.0.0" + q "^1.4.1" + tmp "^0.0.28" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + +methods@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.0.1.tgz#75bc91943dffd7da037cf3eeb0ed73a0037cd14b" + integrity sha1-dbyRlD3/19oDfPPusO1zoAN80Us= + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + +mime-db@1.46.0: + version "1.46.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" + integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== + +mime-db@~1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.16.0.tgz#e83dce4f81ca5455d29048e6c3422e9de3154f70" + integrity sha1-6D3OT4HKVFXSkEjmw0IuneMVT3A= + +mime-types@2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.4.tgz#6562b328e341245cb63b14473b1d12b40dec5884" + integrity sha1-ZWKzKONBJFy2OxRHOx0StA3sWIQ= + dependencies: + mime-db "~1.16.0" + +mime-types@^2.1.11, mime-types@~2.1.2, mime-types@~2.1.24: + version "2.1.29" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" + integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== + dependencies: + mime-db "1.46.0" + +mime@1.2.11, mime@~1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10" + integrity sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA= + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +"minimatch@2 || 3", minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimatch@~0.2.11: + version "0.2.14" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a" + integrity sha1-x054BXT2PG+aCQ6Q775u9TpqdWo= + dependencies: + lru-cache "2" + sigmund "~1.0.0" + +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mkdirp@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e" + integrity sha1-G79asbqCevI1dRQ0kEJkVfSB/h4= + +mkdirp@0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7" + integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc= + +mkdirp@^0.5.0: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" + +mocha@1.18.2: + version "1.18.2" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-1.18.2.tgz#800848f8f7884c61eefcfa2a27304ba9e5446d0b" + integrity sha1-gAhI+PeITGHu/PoqJzBLqeVEbQs= + dependencies: + commander "2.0.0" + debug "*" + diff "1.0.7" + glob "3.2.3" + growl "1.7.x" + jade "0.26.3" + mkdirp "0.3.5" + +ms@0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.0.tgz#865be94c2e7397ad8a57da6a633a6e2f30798b83" + integrity sha1-hlvpTC5zl62KV9pqYzpuLzB5i4M= + +ms@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" + integrity sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg= + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + +netmask@~1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" + integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= + +node-uuid@~1.4.0: + version "1.4.8" + resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907" + integrity sha1-sEDrCSOWivq/jTL7HxfxFn/auQc= + +oauth-sign@~0.8.0: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= + +object-keys@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-0.4.0.tgz#28a6aae7428dd2c3a92f3d95f21335dd204e0336" + integrity sha1-KKaq50KN0sOpLz2V8hM13SBOAzY= + +octocat@0.10.2: + version "0.10.2" + resolved "https://registry.yarnpkg.com/octocat/-/octocat-0.10.2.tgz#d1f878ca0a4964c425d668787b6f3a413e8bf680" + integrity sha1-0fh4ygpJZMQl1mh4e286QT6L9oA= + dependencies: + js-base64 "2.1.9" + lodash "3.10.1" + mime-types "2.1.4" + progress-stream "1.1.1" + q "1.4.1" + request "2.64.0" + +on-finished@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.2.1.tgz#5c85c1cc36299f78029653f667f27b6b99ebc029" + integrity sha1-XIXBzDYpn3gCllP2Z/J7a5nrwCk= + dependencies: + ee-first "1.1.0" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= + dependencies: + lcid "^1.0.0" + +os-tmpdir@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +pac-proxy-agent@0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-0.2.0.tgz#ad902909d92f4fe7cc2e5f59f5bf5061bcfa71b2" + integrity sha1-rZApCdkvT+fMLl9Z9b9QYbz6cbI= + dependencies: + agent-base "~1.0.1" + debug "2" + extend "~1.2.1" + get-uri "~0.1.0" + pac-resolver "~1.2.1" + proxy-agent "1" + stream-to-array "~1.0.0" + +pac-resolver@~1.2.1: + version "1.2.6" + resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-1.2.6.tgz#ed03af0c5b5933505bdd3f07f75175466d5e7cfb" + integrity sha1-7QOvDFtZM1Bb3T8H91F1Rm1efPs= + dependencies: + co "~3.0.6" + degenerator "~1.0.0" + netmask "~1.0.4" + regenerator "~0.8.13" + thunkify "~2.1.1" + +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pass-stream@~0.1.0: + version "0.1.5" + resolved "https://registry.yarnpkg.com/pass-stream/-/pass-stream-0.1.5.tgz#9e3afa4d5825cdd1376075bdb56de36dfb33649f" + integrity sha1-njr6TVglzdE3YHW9tW3jbfszZJ8= + dependencies: + readable-stream "https://github.com/jeffbski/readable-stream/archive/v1.0.2-object-transform2-ret-self.tar.gz" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +private@^0.1.6, private@~0.1.5: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== + +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" + integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +progress-stream@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/progress-stream/-/progress-stream-1.1.1.tgz#9ecbf187932c4941d550219190d74dec0ac45f54" + integrity sha1-nsvxh5MsSUHVUCGRkNdN7ArEX1Q= + dependencies: + single-line-log "~0.3.1" + speedometer "~0.1.2" + through2 "~0.2.3" + +proxy-addr@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf" + integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw== + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.9.1" + +proxy-agent@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-1.1.1.tgz#fcb1eef5e58965c995f938f029d729fc81858b95" + integrity sha1-/LHu9eWJZcmV+TjwKdcp/IGFi5U= + dependencies: + http-proxy-agent "0" + https-proxy-agent "0" + lru-cache "~2.5.0" + pac-proxy-agent "0" + socks-proxy-agent "1" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= + +psl@^1.1.33: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + +punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +q@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/q/-/q-1.2.0.tgz#811705ce4a9802adff811ab0fcdbd01946e1fe22" + integrity sha1-gRcFzkqYAq3/gRqw/NvQGUbh/iI= + +q@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.4.1.tgz#55705bcd93c5f3673530c2c2cbc0c2b3addc286e" + integrity sha1-VXBbzZPF82c1MMLCy8DCs63cKG4= + +q@^1.1.2, q@^1.4.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= + +qs@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-1.2.0.tgz#ed079be28682147e6fd9a34cc2b0c1e0ec6453ee" + integrity sha1-7Qeb4oaCFH5v2aNMwrDB4OxkU+4= + +qs@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-2.4.1.tgz#68cbaea971013426a80c1404fad6b1a6b1175245" + integrity sha1-aMuuqXEBNCaoDBQE+taxprEXUkU= + +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + +qs@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-4.0.0.tgz#c31d9b74ec27df75e543a86c78728ed8d4623607" + integrity sha1-wx2bdOwn33XlQ6hseHKO2NRiNgc= + +qs@~5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-5.1.0.tgz#4d932e5c7ea411cca76a312d39a606200fd50cd9" + integrity sha1-TZMuXH6kEcynajEtOaYGIA/VDNk= + +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.3.4.tgz#ccc7ddfc46b72861cdd5bb433c840b70b6f27f54" + integrity sha1-zMfd/Ea3KGHN1btDPIQLcLbyf1Q= + dependencies: + bytes "1.0.0" + iconv-lite "0.4.8" + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + +readable-stream@1.0.27-1: + version "1.0.27-1" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.27-1.tgz#6b67983c20357cefd07f0165001a16d710d91078" + integrity sha1-a2eYPCA1fO/QfwFlABoW1xDZEHg= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@1.1.x, readable-stream@~1.1.9: + version "1.1.14" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@2: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +"readable-stream@https://github.com/jeffbski/readable-stream/archive/v1.0.2-object-transform2-ret-self.tar.gz": + version "1.0.2" + resolved "https://github.com/jeffbski/readable-stream/archive/v1.0.2-object-transform2-ret-self.tar.gz#129e553a773186632e8b58939c58e6f607542b63" + +readable-stream@~1.0.26: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readable-stream@~2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" + integrity sha1-j5A0HmilPMySh4jaz80Rs265t44= + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + string_decoder "~0.10.x" + util-deprecate "~1.0.1" + +recast@0.10.33: + version "0.10.33" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.10.33.tgz#942808f7aa016f1fa7142c461d7e5704aaa8d697" + integrity sha1-lCgI96oBbx+nFCxGHX5XBKqo1pc= + dependencies: + ast-types "0.8.12" + esprima-fb "~15001.1001.0-dev-harmony-fb" + private "~0.1.5" + source-map "~0.5.0" + +recast@^0.11.17: + version "0.11.23" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" + integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM= + dependencies: + ast-types "0.9.6" + esprima "~3.1.0" + private "~0.1.5" + source-map "~0.5.0" + +reduce-component@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/reduce-component/-/reduce-component-1.0.1.tgz#e0c93542c574521bea13df0f9488ed82ab77c5da" + integrity sha1-4Mk1QsV0UhvqE98PlIjtgqt3xdo= + +regenerator-runtime@~0.9.5: + version "0.9.6" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.9.6.tgz#d33eb95d0d2001a4be39659707c51b0cb71ce029" + integrity sha1-0z65XQ0gAaS+OWWXB8UbDLcc4Ck= + +regenerator@~0.8.13: + version "0.8.46" + resolved "https://registry.yarnpkg.com/regenerator/-/regenerator-0.8.46.tgz#154c327686361ed52cad69b2545efc53a3d07696" + integrity sha1-FUwydoY2HtUsrWmyVF78U6PQdpY= + dependencies: + commoner "~0.10.3" + defs "~1.1.0" + esprima-fb "~15001.1001.0-dev-harmony-fb" + private "~0.1.5" + recast "0.10.33" + regenerator-runtime "~0.9.5" + through "~2.3.8" + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +request@2.60.0: + version "2.60.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.60.0.tgz#498820957fcdded1d37749069610c85f61a29f2d" + integrity sha1-SYgglX/N3tHTd0kGlhDIX2Giny0= + dependencies: + aws-sign2 "~0.5.0" + bl "~1.0.0" + caseless "~0.11.0" + combined-stream "~1.0.1" + extend "~3.0.0" + forever-agent "~0.6.0" + form-data "~1.0.0-rc1" + har-validator "^1.6.1" + hawk "~3.1.0" + http-signature "~0.11.0" + isstream "~0.1.1" + json-stringify-safe "~5.0.0" + mime-types "~2.1.2" + node-uuid "~1.4.0" + oauth-sign "~0.8.0" + qs "~4.0.0" + stringstream "~0.0.4" + tough-cookie ">=0.12.0" + tunnel-agent "~0.4.0" + +request@2.64.0: + version "2.64.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.64.0.tgz#96a582423ce9b4b5c34e9b232e480173f14ba608" + integrity sha1-lqWCQjzptLXDTpsjLkgBc/FLpgg= + dependencies: + aws-sign2 "~0.5.0" + bl "~1.0.0" + caseless "~0.11.0" + combined-stream "~1.0.1" + extend "~3.0.0" + forever-agent "~0.6.0" + form-data "~1.0.0-rc1" + har-validator "^1.6.1" + hawk "~3.1.0" + http-signature "~0.11.0" + isstream "~0.1.1" + json-stringify-safe "~5.0.0" + mime-types "~2.1.2" + node-uuid "~1.4.0" + oauth-sign "~0.8.0" + qs "~5.1.0" + stringstream "~0.0.4" + tough-cookie ">=0.12.0" + tunnel-agent "~0.4.0" + +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + integrity sha1-YTObci/mo1FWiSENJOFMlhSGE+8= + dependencies: + align-text "^0.1.1" + +rimraf@^2.2.8: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +semver@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.1.tgz#9fb3f4004f900d83c47968fe42f7583e05832cc9" + integrity sha1-n7P0AE+QDYPEeWj+QvdYPgWDLMk= + +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serve-static@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + +should-equal@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-0.5.0.tgz#c797f135f3067feb69ebecdb306b1c3fe21b3e6f" + integrity sha1-x5fxNfMGf+tp6+zbMGscP+IbPm8= + dependencies: + should-type "0.2.0" + +should-format@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/should-format/-/should-format-0.3.0.tgz#42007ec0aa1c86ea44914cc9111f1b9f27d3ceac" + integrity sha1-QgB+wKochupEkUzJER8bnyfTzqw= + dependencies: + should-type "0.2.0" + +should-type@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/should-type/-/should-type-0.2.0.tgz#6707ef95529d989dcc098fe0753ab1f9136bb7f6" + integrity sha1-ZwfvlVKdmJ3MCY/gdTqx+RNrt/Y= + +should@7.0.4: + version "7.0.4" + resolved "https://registry.yarnpkg.com/should/-/should-7.0.4.tgz#d75a180a5f8cbcd22de6fc7784780f84a1b36657" + integrity sha1-11oYCl+MvNIt5vx3hHgPhKGzZlc= + dependencies: + should-equal "0.5.0" + should-format "0.3.0" + should-type "0.2.0" + +sigmund@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" + integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= + +simple-fmt@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/simple-fmt/-/simple-fmt-0.1.0.tgz#191bf566a59e6530482cb25ab53b4a8dc85c3a6b" + integrity sha1-GRv1ZqWeZTBILLJatTtKjchcOms= + +simple-is@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/simple-is/-/simple-is-0.2.0.tgz#2abb75aade39deb5cc815ce10e6191164850baf0" + integrity sha1-Krt1qt453rXMgVzhDmGRFkhQuvA= + +single-line-log@~0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/single-line-log/-/single-line-log-0.3.1.tgz#a7ad6507f218ce5dfe16c4bf2d659246419e7a06" + integrity sha1-p61lB/IYzl3+FsS/LWWSRkGeegY= + +smart-buffer@^1.0.13: + version "1.1.15" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-1.1.15.tgz#7f114b5b65fab3e2a35aa775bb12f0d1c649bf16" + integrity sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY= + +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + integrity sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg= + dependencies: + hoek "2.x.x" + +socks-proxy-agent@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-1.0.2.tgz#67e06b447fe5637417fde5733cbfdfec9ffe117f" + integrity sha1-Z+BrRH/lY3QX/eVzPL/f7J/+EX8= + dependencies: + agent-base "~1.0.1" + extend "~1.2.1" + socks "~1.1.5" + +socks@~1.1.5: + version "1.1.10" + resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.10.tgz#5b8b7fc7c8f341c53ed056e929b7bf4de8ba7b5a" + integrity sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o= + dependencies: + ip "^1.1.4" + smart-buffer "^1.0.13" + +source-map@~0.5.0: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +speedometer@~0.1.2: + version "0.1.4" + resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-0.1.4.tgz#9876dbd2a169d3115402d48e6ea6329c8816a50d" + integrity sha1-mHbb0qFp0xFUAtSObqYynIgWpQ0= + +stable@~0.1.3: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + +stream-res@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-res/-/stream-res-1.0.1.tgz#a0f6183be9ac16cb56d5ba9d7d4281049d19f219" + integrity sha1-oPYYO+msFstW1bqdfUKBBJ0Z8hk= + dependencies: + destroy "^1.0.4" + finished "^1.2.2" + +stream-to-array@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stream-to-array/-/stream-to-array-1.0.0.tgz#94166bb29f3ea24f082d2f8cd3ebb2cc0d6eca2c" + integrity sha1-lBZrsp8+ok8ILS+M0+uyzA1uyiw= + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +stringmap@~0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/stringmap/-/stringmap-0.2.2.tgz#556c137b258f942b8776f5b2ef582aa069d7d1b1" + integrity sha1-VWwTeyWPlCuHdvWy71gqoGnX0bE= + +stringset@~0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/stringset/-/stringset-0.2.1.tgz#ef259c4e349344377fcd1c913dd2e848c9c042b5" + integrity sha1-7yWcTjSTRDd/zRyRPdLoSMnAQrU= + +stringstream@~0.0.4: + version "0.0.6" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" + integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA== + +strip-ansi@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-bom@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + integrity sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4= + dependencies: + is-utf8 "^0.2.0" + +superagent-proxy@~0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/superagent-proxy/-/superagent-proxy-0.3.2.tgz#73c27ecd41915823070c90b265ee8390c3e1aa15" + integrity sha1-c8J+zUGRWCMHDJCyZe6DkMPhqhU= + dependencies: + debug "~2.2.0" + proxy-agent "1" + +superagent-retry@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/superagent-retry/-/superagent-retry-0.4.0.tgz#44e10b265c086e077d1b03681c9aa17494349f64" + integrity sha1-ROELJlwIbgd9GwNoHJqhdJQ0n2Q= + +superagent@~0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-0.19.1.tgz#d2614f82e8486120393d1b158084736f8473e2d9" + integrity sha1-0mFPguhIYSA5PRsVgIRzb4Rz4tk= + dependencies: + component-emitter "1.1.2" + cookiejar "2.0.1" + debug "~1.0.1" + extend "~1.2.1" + form-data "0.1.3" + formidable "1.0.14" + methods "1.0.1" + mime "1.2.11" + qs "1.2.0" + readable-stream "1.0.27-1" + reduce-component "1.0.1" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +through2@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f" + integrity sha1-6zKE2k6jEbbMis42U3SKUqvyWj8= + dependencies: + readable-stream "~1.1.9" + xtend "~2.1.1" + +through@~2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= + +thunkify@~2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/thunkify/-/thunkify-2.1.2.tgz#faa0e9d230c51acc95ca13a361ac05ca7e04553d" + integrity sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0= + +tmp@^0.0.28: + version "0.0.28" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.28.tgz#172735b7f614ea7af39664fa84cf0de4e515d120" + integrity sha1-Fyc1t/YU6nrzlmT6hM8N5OUV0SA= + dependencies: + os-tmpdir "~1.0.1" + +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + +tough-cookie@>=0.12.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.1.2" + +tryor@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/tryor/-/tryor-0.1.2.tgz#8145e4ca7caff40acde3ccf946e8b8bb75b4172b" + integrity sha1-gUXkynyv9ArN48z5Rui4u3W0Fys= + +tslib@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== + +tunnel-agent@~0.4.0: + version "0.4.3" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" + integrity sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us= + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + +type-is@~1.6.1, type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +uid@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/uid/-/uid-0.0.2.tgz#5e4a5d4b78138b4f70f89fd3c76fc59aa9d2f103" + integrity sha1-XkpdS3gTi09w+J/Tx2/FmqnS8QM= + +understudy@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/understudy/-/understudy-4.1.0.tgz#b6d6e9624ccbb7fa2e51969f5be2c748e969ef4a" + integrity sha1-ttbpYkzLt/ouUZafW+LHSOlp70o= + +universalify@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + +urljoin.js@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urljoin.js/-/urljoin.js-0.1.0.tgz#97914d5227b1c334236a9c843cb47e3ffa7edd6a" + integrity sha1-l5FNUiexwzQjapyEPLR+P/p+3Wo= + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + +uuid@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.1.tgz#c2a30dedb3e535d72ccf82e343941a50ba8533ac" + integrity sha1-wqMN7bPlNdcsz4LjQ5QaULqFM6w= + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + +window-size@^0.1.2: + version "0.1.4" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.4.tgz#f8e1aa1ee5a53ec5bf151ffa09742a6ad7697876" + integrity sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY= + +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + integrity sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8= + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +"xml@>= 0.0.5": + version "1.0.1" + resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5" + integrity sha1-eLpyAgApxbyHuKgaPPzXS0ovweU= + +xregexp@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" + integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +xtend@~2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-2.1.2.tgz#6efecc2a4dad8e6962c4901b337ce7ba87b5d28b" + integrity sha1-bv7MKk2tjmlixJAbM3znuoe10os= + dependencies: + object-keys "~0.4.0" + +y18n@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI= + +yargs@~3.27.0: + version "3.27.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.27.0.tgz#21205469316e939131d59f2da0c6d7f98221ea40" + integrity sha1-ISBUaTFuk5Ex1Z8toMbX+YIh6kA= + dependencies: + camelcase "^1.2.1" + cliui "^2.1.0" + decamelize "^1.0.0" + os-locale "^1.4.0" + window-size "^0.1.2" + y18n "^3.2.0"