diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c83ce7c --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +**/*.bundle.js +**/*.js.map +**/coverage +**/files +**/node_modules/* +**/dist +**/vendor +**/yarn-error diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..fa9ad2d --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,38 @@ +builds: + - + main: server.go + env: + - CGO_ENABLED=0 + binary: quickshare + goos: + - windows + - darwin + - linux + goarch: + - amd64 +archive: + name_template: "quickshare_{{ .Tag }}_{{ .Os }}_{{ .Arch }}" + format: zip + wrap_in_directory: true + replacements: + darwin: macos + linux: linux + windows: windows + amd64: x86_64 + files: + - LICENSE + - README.md + - config.json + - public/* + - public/dist/* + - docs/* +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ .Tag }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..03afe0c --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,21 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/pkg/errors" + packages = ["."] + revision = "645ef00459ed84a119197bfb8d8205042c6df63d" + version = "v0.8.0" + +[[projects]] + name = "github.com/robbert229/jwt" + packages = ["."] + revision = "81ddea8e91eecffef557c5e4ce8e78a1d472d7d7" + version = "v2.0.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "09f88c8b25db4caace633bcccce4f4d942a810603dd2befe65a69191b0869b14" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..979f33a --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,34 @@ +# Gopkg.toml example +# +# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[prune] + go-tests = true + unused-packages = true + +[[constraint]] + name = "github.com/robbert229/jwt" + version = "2.0.0" diff --git a/README.md b/README.md index c53d3ac..14237f7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,69 @@ -# share-heap -A simple file sharing server. \ No newline at end of file +

+ Quickshare +

+

+ A succinct file sharing server +

+

+ + + +

+ +![Quickshare demo](./demo.jpg) + +Choose Language: English | [简体中文](./docs/README_zh-cn.md) + +## Download + +| Linux | Mac | Windows | +| ------------ | ------------ | ------------ | +| [Download]() | [Download]() | [Download]() | + +## Features + +* Upload and download in browser, no client +* Share files among desktop and mobile devices +* Portable software +* Add files from local +* Add download limit for resource +* Download from interrupted point + +## Installation + +2 steps are needed to start a quickshare: unzip it and start it. + +The first step, unzip and start quickshare + +### Linux + +* Unzip the package: `unzip [package].` (`[package]` could be `quickshare_0.0.8_linux_x86_6 4.zip`) +* Start quickshare `./quickshare` + +### Mac + +* Unzip the package: `unzip [package].` (`[package]` could be `quickshare_0.0.8_macos_x86_64.zip`) +* Start quickshare `./quickshare` + +### Windows + +* Unzip the package +* Go into folder and click `quickshare.exe` + +Last step, meet quickshare in browser + +* Quickshare will start and show `quickshare starts @ [URL]` in terminal (e.g. `URL` could be `192.168.0.1:8888`) +* Open `URL` in browser and login with `admin` and `quicksh@re` +* Enjoy (But don't forget to change password according to [FAQ document](./docs/FAQ_en-us.md)) + +### FAQ + +Please refer [FAQ document](./docs/FAQ_en-us.md) + +### Configuration + +Please refer [Configuration document](./docs/CONFIG_en-us.md) + +### Contribution + +Will add it soon... diff --git a/client/.babelrc b/client/.babelrc new file mode 100644 index 0000000..de40a79 --- /dev/null +++ b/client/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["env", "react", "stage-0", "stage-2"] +} diff --git a/client/components/composite/auth_pane.jsx b/client/components/composite/auth_pane.jsx new file mode 100644 index 0000000..0ba99df --- /dev/null +++ b/client/components/composite/auth_pane.jsx @@ -0,0 +1,145 @@ +import React from "react"; +import { Button } from "../control/button"; +import { Input } from "../control/input"; + +import { config } from "../../config"; +import { getIcon } from "../display/icon"; +import { makePostBody } from "../../libs/utils"; +import { styleButtonLabel } from "./info_bar"; + +export const classLogin = "auth-pane-login"; +export const classLogout = "auth-pane-logout"; +const IconSignIn = getIcon("signIn"); +const IconSignOut = getIcon("signOut"); +const IconAngRight = getIcon("angRight"); + +export class AuthPane extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + adminId: "", + adminPwd: "" + }; + } + + onLogin = e => { + e.preventDefault(); + this.props.onLogin( + this.props.serverAddr, + this.state.adminId, + this.state.adminPwd + ); + }; + + onLogout = () => { + this.props.onLogout(this.props.serverAddr); + }; + + onChangeAdminId = adminId => { + this.setState({ adminId }); + }; + + onChangeAdminPwd = adminPwd => { + this.setState({ adminPwd }); + }; + + render() { + if (this.props.isLogin) { + return ( + + + + ); + } +} + +Button.defaultProps = { + className: "btn", + isEnabled: true, + icon: null, + onClick: () => true, + styleContainer: {}, + styleDefault: {}, + styleDisabled: {}, + styleLabel: {}, + styleIcon: {}, + styleStr: undefined +}; diff --git a/client/components/control/input.jsx b/client/components/control/input.jsx new file mode 100644 index 0000000..f69e38d --- /dev/null +++ b/client/components/control/input.jsx @@ -0,0 +1,128 @@ +import React from "react"; + +const styleContainer = { + backgroundColor: "#ccc", + display: "inline-block", + height: "2.5rem" +}; + +const styleIcon = { + lineHeight: "2.5rem", + height: "2.5rem", + margin: "0 0.25rem 0 0.5rem" +}; + +const styleInputBase = { + backgroundColor: "transparent", + border: "none", + display: "inline-block", + fontSize: "0.875rem", + height: "2.5rem", + lineHeight: "2.5rem", + outline: "none", + overflowY: "hidden", + padding: "0 0.75rem", + verticalAlign: "middle" +}; + +const styleDefault = { + ...styleInputBase, + color: "#333" +}; + +const styleInvalid = { + ...styleInputBase, + color: "#e74c3c" +}; + +const inputClassName = "qs-input"; +const styleStr = ` +.${inputClassName}:hover { + // box-shadow: 0px 0px -5px rgba(0, 0, 0, 1); + opacity: 0.7; + transition: opacity 0.25s; +} + +.${inputClassName}:active { + // box-shadow: 0px 0px -5px rgba(0, 0, 0, 1); + opacity: 0.7; + transition: opacity 0.25s; +} + +.${inputClassName}:disabled { + color: #ccc; +} +`; + +export class Input extends React.PureComponent { + constructor(props) { + super(props); + this.state = { isValid: true }; + this.inputRef = undefined; + } + + onChange = e => { + this.props.onChange(e.target.value); + this.props.onChangeEvent(e); + this.props.onChangeTarget(e.target); + this.setState({ isValid: this.props.validate(e.target.value) }); + }; + + getRef = input => { + this.inputRef = input; + this.props.inputRef(this.inputRef); + }; + + render() { + const style = this.state.isValid ? styleDefault : styleInvalid; + const icon = + this.props.icon != null ? ( + {this.props.icon} + ) : ( + + ); + + return ( +

+ {icon} + + +
+ ); + } +} + +Input.defaultProps = { + className: "input", + maxLength: "32", + placeholder: "placeholder", + readOnly: false, + style: {}, + styleContainer: {}, + styleInvalid: {}, + type: "text", + disabled: false, + width: "auto", + value: "", + icon: null, + onChange: () => true, + onChangeEvent: () => true, + onChangeTarget: () => true, + validate: () => true, + inputRef: () => true +}; diff --git a/client/components/display/icon.js b/client/components/display/icon.js new file mode 100644 index 0000000..2190eb0 --- /dev/null +++ b/client/components/display/icon.js @@ -0,0 +1,177 @@ +const IconFile = require("react-icons/lib/fa/file-o"); +const IconImg = require("react-icons/lib/md/image"); +const IconZip = require("react-icons/lib/md/archive"); +const IconVideo = require("react-icons/lib/md/ondemand-video"); +const IconAudio = require("react-icons/lib/md/music-video"); +const IconText = require("react-icons/lib/md/description"); +const IconExcel = require("react-icons/lib/fa/file-excel-o"); +const IconPPT = require("react-icons/lib/fa/file-powerpoint-o"); +const IconPdf = require("react-icons/lib/md/picture-as-pdf"); +const IconWord = require("react-icons/lib/fa/file-word-o"); +const IconCode = require("react-icons/lib/md/code"); +const IconApk = require("react-icons/lib/md/android"); +const IconExe = require("react-icons/lib/fa/cog"); + +const IconBars = require("react-icons/lib/fa/bars"); +const IconSpinner = require("react-icons/lib/md/autorenew"); +const IconCirUp = require("react-icons/lib/fa/arrow-circle-up"); +const IconSignIn = require("react-icons/lib/fa/sign-in"); +const IconSignOut = require("react-icons/lib/fa/sign-out"); +const IconAngUp = require("react-icons/lib/fa/angle-up"); +const IconAngRight = require("react-icons/lib/fa/angle-right"); +const IconAngDown = require("react-icons/lib/fa/angle-down"); +const IconAngLeft = require("react-icons/lib/fa/angle-left"); +const IconTimesCir = require("react-icons/lib/md/cancel"); +const IconPlusSqu = require("react-icons/lib/md/add-box"); +const IconPlusCir = require("react-icons/lib/fa/plus-circle"); +const IconPlus = require("react-icons/lib/md/add"); +const IconSearch = require("react-icons/lib/fa/search"); +const IconThList = require("react-icons/lib/fa/th-list"); +const IconCalendar = require("react-icons/lib/fa/calendar-o"); + +const IconCheckCir = require("react-icons/lib/fa/check-circle"); +const IconExTri = require("react-icons/lib/fa/exclamation-triangle"); +const IconInfoCir = require("react-icons/lib/fa/info-circle"); +const IconRefresh = require("react-icons/lib/fa/refresh"); + +const fileTypeIconMap = { + // text + txt: { icon: IconText, color: "#333" }, + rtf: { icon: IconText, color: "#333" }, + htm: { icon: IconText, color: "#333" }, + html: { icon: IconText, color: "#333" }, + xml: { icon: IconText, color: "#333" }, + yml: { icon: IconText, color: "#333" }, + json: { icon: IconText, color: "#333" }, + toml: { icon: IconText, color: "#333" }, + md: { icon: IconText, color: "#333" }, + // office + ppt: { icon: IconPPT, color: "#e67e22" }, + pptx: { icon: IconPPT, color: "#e67e22" }, + xls: { icon: IconExcel, color: "#16a085" }, + xlsx: { icon: IconExcel, color: "#16a085" }, + xlsm: { icon: IconExcel, color: "#16a085" }, + doc: { icon: IconWord, color: "#2980b9" }, + docx: { icon: IconWord, color: "#2980b9" }, + docx: { icon: IconWord, color: "#2980b9" }, + pdf: { icon: IconPdf, color: "#c0392b" }, + // code + c: { icon: IconCode, color: "#666" }, + cpp: { icon: IconCode, color: "#666" }, + java: { icon: IconCode, color: "#666" }, + js: { icon: IconCode, color: "#666" }, + py: { icon: IconCode, color: "#666" }, + pyc: { icon: IconCode, color: "#666" }, + rb: { icon: IconCode, color: "#666" }, + php: { icon: IconCode, color: "#666" }, + go: { icon: IconCode, color: "#666" }, + sh: { icon: IconCode, color: "#666" }, + vb: { icon: IconCode, color: "#666" }, + sql: { icon: IconCode, color: "#666" }, + r: { icon: IconCode, color: "#666" }, + swift: { icon: IconCode, color: "#666" }, + oc: { icon: IconCode, color: "#666" }, + // misc + apk: { icon: IconApk, color: "#2ecc71" }, + exe: { icon: IconExe, color: "#333" }, + deb: { icon: IconExe, color: "#333" }, + rpm: { icon: IconExe, color: "#333" }, + // img + bmp: { icon: IconImg, color: "#1abc9c" }, + gif: { icon: IconImg, color: "#1abc9c" }, + jpg: { icon: IconImg, color: "#1abc9c" }, + jpeg: { icon: IconImg, color: "#1abc9c" }, + tiff: { icon: IconImg, color: "#1abc9c" }, + psd: { icon: IconImg, color: "#1abc9c" }, + png: { icon: IconImg, color: "#1abc9c" }, + svg: { icon: IconImg, color: "#1abc9c" }, + pcx: { icon: IconImg, color: "#1abc9c" }, + dxf: { icon: IconImg, color: "#1abc9c" }, + wmf: { icon: IconImg, color: "#1abc9c" }, + emf: { icon: IconImg, color: "#1abc9c" }, + eps: { icon: IconImg, color: "#1abc9c" }, + tga: { icon: IconImg, color: "#1abc9c" }, + // compress + gz: { icon: IconZip, color: "#34495e" }, + zip: { icon: IconZip, color: "#34495e" }, + "7z": { icon: IconZip, color: "#34495e" }, + rar: { icon: IconZip, color: "#34495e" }, + tar: { icon: IconZip, color: "#34495e" }, + gzip: { icon: IconZip, color: "#34495e" }, + cab: { icon: IconZip, color: "#34495e" }, + uue: { icon: IconZip, color: "#34495e" }, + arj: { icon: IconZip, color: "#34495e" }, + bz2: { icon: IconZip, color: "#34495e" }, + lzh: { icon: IconZip, color: "#34495e" }, + jar: { icon: IconZip, color: "#34495e" }, + ace: { icon: IconZip, color: "#34495e" }, + iso: { icon: IconZip, color: "#34495e" }, + z: { icon: IconZip, color: "#34495e" }, + // video + asf: { icon: IconVideo, color: "#f39c12" }, + avi: { icon: IconVideo, color: "#f39c12" }, + flv: { icon: IconVideo, color: "#f39c12" }, + mkv: { icon: IconVideo, color: "#f39c12" }, + mov: { icon: IconVideo, color: "#f39c12" }, + mp4: { icon: IconVideo, color: "#f39c12" }, + mpeg: { icon: IconVideo, color: "#f39c12" }, + mpg: { icon: IconVideo, color: "#f39c12" }, + ram: { icon: IconVideo, color: "#f39c12" }, + rmvb: { icon: IconVideo, color: "#f39c12" }, + qt: { icon: IconVideo, color: "#f39c12" }, + wmv: { icon: IconVideo, color: "#f39c12" }, + // audio + cda: { icon: IconAudio, color: "#d35400" }, + cmf: { icon: IconAudio, color: "#d35400" }, + mid: { icon: IconAudio, color: "#d35400" }, + mp1: { icon: IconAudio, color: "#d35400" }, + mp2: { icon: IconAudio, color: "#d35400" }, + mp3: { icon: IconAudio, color: "#d35400" }, + rm: { icon: IconAudio, color: "#d35400" }, + rmi: { icon: IconAudio, color: "#d35400" }, + vqf: { icon: IconAudio, color: "#d35400" }, + wav: { icon: IconAudio, color: "#d35400" } +}; + +const fileIconMap = { + ...fileTypeIconMap, + // other + spinner: { icon: IconSpinner, color: "#1abc9c" }, + cirup: { icon: IconCirUp, color: "#fff" }, + signin: { icon: IconSignIn, color: "#fff" }, + signout: { icon: IconSignOut, color: "#fff" }, + angup: { icon: IconAngUp, color: "#2c3e50" }, + angright: { icon: IconAngRight, color: "#2c3e50" }, + angdown: { icon: IconAngDown, color: "#2c3e50" }, + angleft: { icon: IconAngLeft, color: "#2c3e50" }, + timescir: { icon: IconTimesCir, color: "#c0392b" }, + plussqu: { icon: IconPlusSqu, color: "#2ecc71" }, + pluscir: { icon: IconPlusCir, color: "#2ecc71" }, + plus: { icon: IconPlus, color: "#2ecc71" }, + search: { icon: IconSearch, color: "#ccc" }, + checkcir: { icon: IconCheckCir, color: "#27ae60" }, + extri: { icon: IconExTri, color: "#f39c12" }, + infocir: { icon: IconInfoCir, color: "#2c3e50" }, + refresh: { icon: IconRefresh, color: "#8e44ad" }, + thlist: { icon: IconThList, color: "#fff" }, + bars: { icon: IconBars, color: "#666" }, + calendar: { icon: IconCalendar, color: "#333" } +}; + +export const getIcon = extend => { + if (fileIconMap[extend.toUpperCase()]) { + return fileIconMap[extend.toUpperCase()].icon; + } else if (fileIconMap[extend.toLowerCase()]) { + return fileIconMap[extend.toLowerCase()].icon; + } + return IconFile; +}; + +export const getIconColor = extend => { + if (fileIconMap[extend.toUpperCase()]) { + return fileIconMap[extend.toUpperCase()].color; + } else if (fileIconMap[extend.toLowerCase()]) { + return fileIconMap[extend.toLowerCase()].color; + } + return "#333"; +}; diff --git a/client/components/layout/grids.jsx b/client/components/layout/grids.jsx new file mode 100644 index 0000000..d99ecdb --- /dev/null +++ b/client/components/layout/grids.jsx @@ -0,0 +1,27 @@ +import React from "react"; + +const styleGridBase = { + float: "left", + margin: 0 +}; + +export const Grids = props => ( +
+ {props.nodes.map(node => ( +
+ {node.component} +
+ ))} +
+
+); + +Grids.defaultProps = { + nodes: [{ key: "key", component: , style: {} }], + gridStyle: styleGridBase, + containerStyle: {} +}; diff --git a/client/config.js b/client/config.js new file mode 100644 index 0000000..e04b9a3 --- /dev/null +++ b/client/config.js @@ -0,0 +1,7 @@ +export const config = { + serverAddr: "", + testId: "admin", + testPwd: "quicksh@re", + rootSize: 16, + colWidth: 20 +}; diff --git a/client/libs/__mocks__/api_auth.js b/client/libs/__mocks__/api_auth.js new file mode 100644 index 0000000..7c5f3aa --- /dev/null +++ b/client/libs/__mocks__/api_auth.js @@ -0,0 +1,7 @@ +export function login(serverAddr, adminId, adminPwd, axiosConfig) { + return Promise.resolve(true); +} + +export function logout(serverAddr, axiosConfig) { + return Promise.resolve(true); +} diff --git a/client/libs/__mocks__/api_share.js b/client/libs/__mocks__/api_share.js new file mode 100644 index 0000000..98db7d8 --- /dev/null +++ b/client/libs/__mocks__/api_share.js @@ -0,0 +1,41 @@ +let _infos = []; +const shadowedId = "shadowedId"; +const publicId = "publicId"; + +export function __addInfos(infos) { + _infos = [..._infos, ...infos]; +} + +export function __truncInfos(info) { + _infos = []; +} + +export const del = shareId => { + _infos = _infos.filter(info => { + return !info.shareId == shareId; + }); + return Promise.resolve(true); +}; + +export const list = () => { + return Promise.resolve(_infos); +}; + +export const shadowId = shareId => { + return Promise.resolve(shadowedId); +}; + +export const publishId = shareId => { + return Promise.resolve(publicId); +}; + +export const setDownLimit = (shareId, downLimit) => { + _infos = _infos.map(info => { + return info.shareId == shareId ? { ...info, downLimit } : info; + }); + return Promise.resolve(true); +}; + +export const addLocalFiles = () => { + return Promise.resolve(true); +}; diff --git a/client/libs/api_auth.js b/client/libs/api_auth.js new file mode 100644 index 0000000..eee72bc --- /dev/null +++ b/client/libs/api_auth.js @@ -0,0 +1,29 @@ +import axios from "axios"; +import { config } from "../config"; +import { makePostBody } from "./utils"; + +export function login(serverAddr, adminId, adminPwd, axiosConfig) { + return axios + .post( + `${serverAddr}/login`, + makePostBody( + { + act: "login", + adminid: adminId, + adminpwd: adminPwd + }, + axiosConfig + ) + ) + .then(response => { + return response.data.Code === 200; + }); +} + +export function logout(serverAddr, axiosConfig) { + return axios + .post(`${serverAddr}/login`, makePostBody({ act: "logout" }), axiosConfig) + .then(response => { + return response.data.Code === 200; + }); +} diff --git a/client/libs/api_share.js b/client/libs/api_share.js new file mode 100644 index 0000000..88164c4 --- /dev/null +++ b/client/libs/api_share.js @@ -0,0 +1,51 @@ +import axios from "axios"; +import { config } from "../config"; + +export const del = shareId => { + return axios + .delete(`${config.serverAddr}/fileinfo?shareid=${shareId}`) + .then(response => response.data.Code === 200); +}; + +export const list = () => { + return axios.get(`${config.serverAddr}/fileinfo`).then(response => { + // TODO check status code + return response.data.List; + }); +}; + +export const shadowId = shareId => { + const act = "shadowid"; + return axios + .patch(`${config.serverAddr}/fileinfo?act=${act}&shareid=${shareId}`) + .then(response => { + return response.data.ShareId; + }); +}; + +export const publishId = shareId => { + const act = "publishid"; + return axios + .patch(`${config.serverAddr}/fileinfo?act=${act}&shareid=${shareId}`) + .then(response => { + return response.data.ShareId; + }); +}; + +export const setDownLimit = (shareId, downLimit) => { + const act = "setdownlimit"; + return axios + .patch( + `${ + config.serverAddr + }/fileinfo?act=${act}&shareid=${shareId}&downlimit=${downLimit}` + ) + .then(response => response.data.Code === 200); +}; + +export const addLocalFiles = () => { + const act = "addlocalfiles"; + return axios + .patch(`${config.serverAddr}/fileinfo?act=${act}`) + .then(response => response.data.Code === 200); +}; diff --git a/client/libs/api_upload.js b/client/libs/api_upload.js new file mode 100644 index 0000000..d8f122e --- /dev/null +++ b/client/libs/api_upload.js @@ -0,0 +1,202 @@ +import axios from "axios"; +import { config } from "../config"; +import { makePostBody } from "./utils"; + +const wait = 5000; // TODO: should tune according to backend +const retryMax = 100000; +const maxUploadLen = 20 * 1024 * 1024; + +// TODO: add to react-intl +const msgUploadFailed = "Fail to upload, upload is stopped."; +const msgUploadFailedAndRetry = "Fail to upload, retrying..."; +const msgFileExists = "File exists."; +const msgTooBigChunk = "Too big chunk."; +const msgFileNotFound = "File not found, upload stopped."; + +function randomWait() { + return Math.random() * wait; +} + +function isKnownErr(res) { + return res != null && res.Code != null && res.Msg != null; +} + +export class FileUploader { + constructor(onStart, onProgress, onFinish, onError) { + this.onStart = onStart; + this.onProgress = onProgress; + this.onFinish = onFinish; + this.onError = onError; + this.retry = retryMax; + this.reader = new FileReader(); + + this.uploadFile = file => { + return this.startUpload(file); + }; + + this.startUpload = file => { + return axios + .post( + `${config.serverAddr}/startupload`, + makePostBody({ + fname: file.name + }) + ) + .then(response => { + if ( + response.data.ShareId == null || + response.data.Start === null || + response.data.Length === null + ) { + throw response; + } else { + this.onStart(response.data.ShareId, file.name); + return this.upload( + { + shareId: response.data.ShareId, + start: response.data.Start, + length: response.data.Length + }, + file + ); + } + }) + .catch(response => { + // TODO: this is not good because error may not be response + if (isKnownErr(response.data) && response.data.Code === 429) { + setTimeout(this.startUpload, randomWait(), file); + } else if (isKnownErr(response.data) && response.data.Code === 412) { + this.onError(msgFileExists); + } else if (isKnownErr(response.data) && response.data.Code === 404) { + this.onError(msgFileNotFound); + } else if (this.retry > 0) { + this.retry--; + this.onError(msgUploadFailedAndRetry); + console.trace(response); + setTimeout(this.startUpload, randomWait(), file); + } else { + this.onError(msgUploadFailed); + console.trace(response); + } + }); + }; + + this.prepareReader = (shareInfo, end, resolve, reject) => { + this.reader.onerror = err => { + reject(err); + }; + + this.reader.onloadend = event => { + const formData = new FormData(); + formData.append("shareid", shareInfo.shareId); + formData.append("start", shareInfo.start); + formData.append("len", end - shareInfo.start); + formData.append("chunk", new Blob([event.target.result])); + + const url = `${config.serverAddr}/upload`; + const headers = { + "Content-Type": "multipart/form-data" + }; + + try { + axios + .post(url, formData, { headers }) + .then(response => resolve(response)) + .catch(err => { + reject(err); + }); + } catch (err) { + reject(err); + } + }; + }; + + this.upload = (shareInfo, file) => { + const uploaded = shareInfo.start + shareInfo.length; + const end = uploaded < file.size ? uploaded : file.size; + + return new Promise((resolve, reject) => { + if ( + end == null || + shareInfo.start == null || + end - shareInfo.start >= maxUploadLen + ) { + throw new Error(msgTooBigChunk); + } + + const chunk = file.slice(shareInfo.start, end); + this.prepareReader(shareInfo, end, resolve, reject); + this.reader.readAsArrayBuffer(chunk); + }) + .then(response => { + if ( + response.data.ShareId == null || + response.data.Start == null || + response.data.Length == null || + response.data.Start !== end + ) { + throw response; + } else { + if (end < file.size) { + this.onProgress(shareInfo.shareId, end / file.size); + return this.upload( + { + shareId: shareInfo.shareId, + start: shareInfo.start + shareInfo.length, + length: shareInfo.length + }, + file + ); + } else { + return this.finishUpload(shareInfo); + } + } + }) + .catch(response => { + // possible error: response.data.Start == null || response.data.Start !== end + if (isKnownErr(response.data) && response.data.Code === 429) { + setTimeout(this.upload, randomWait(), shareInfo, file); + } else if (isKnownErr(response.data) && response.data.Code === 404) { + this.onError(msgFileNotFound); + } else if (this.retry > 0) { + this.retry--; + setTimeout(this.upload, randomWait(), shareInfo, file); + this.onError(msgUploadFailedAndRetry); + console.trace(response); + } else { + this.onError(msgUploadFailed); + console.trace(response); + } + }); + }; + + this.finishUpload = shareInfo => { + return axios + .post(`${config.serverAddr}/finishupload?shareid=${shareInfo.shareId}`) + .then(response => { + // TODO: should check Code instead of Url + if (response.data.ShareId != null && response.data.Start == null) { + this.onFinish(); + return response.data.ShareId; + } else { + throw response; + } + }) + .catch(response => { + if (isKnownErr(response.data) && response.data.Code === 429) { + setTimeout(this.finishUpload, randomWait(), shareInfo); + } else if (isKnownErr(response.data) && response.data.Code === 404) { + this.onError(msgFileNotFound); + } else if (this.retry > 0) { + this.retry--; + setTimeout(this.finishUpload, randomWait(), shareInfo); + this.onError(msgUploadFailedAndRetry); + console.trace(response); + } else { + this.onError(msgUploadFailed); + console.trace(response); + } + }); + }; + } +} diff --git a/client/libs/file_type.js b/client/libs/file_type.js new file mode 100644 index 0000000..0e89473 --- /dev/null +++ b/client/libs/file_type.js @@ -0,0 +1,18 @@ +const fileTypeMap = { + jpg: "image", + jpeg: "image", + png: "image", + bmp: "image", + gz: "archive", + mov: "video", + mp4: "video", + mov: "video", + avi: "video" +}; + +export const getFileExt = fileName => fileName.split(".").pop(); + +export const getFileType = fileName => { + const ext = getFileExt(fileName); + return fileTypeMap[ext] != null ? fileTypeMap[ext] : "file"; +}; diff --git a/client/libs/test/api_auth_test.js b/client/libs/test/api_auth_test.js new file mode 100644 index 0000000..29cd955 --- /dev/null +++ b/client/libs/test/api_auth_test.js @@ -0,0 +1,34 @@ +import { login, logout } from "../api_auth"; +import { config } from "../../config"; + +const serverAddr = config.serverAddr; +const testId = config.testId; +const testPwd = config.testPwd; + +export function testAuth() { + return testLogin() + .then(testLogout) + .catch(err => { + console.error("auth: fail", err); + }); +} + +export function testLogin() { + return login(serverAddr, testId, testPwd).then(ok => { + if (ok === true) { + console.log("login api: ok"); + } else { + throw new Error("login api: failed"); + } + }); +} + +export function testLogout() { + return logout(serverAddr).then(ok => { + if (ok === true) { + console.log("logout api: ok"); + } else { + throw new Error("logout api: failed"); + } + }); +} diff --git a/client/libs/test/api_share_test.js b/client/libs/test/api_share_test.js new file mode 100644 index 0000000..99ad2a1 --- /dev/null +++ b/client/libs/test/api_share_test.js @@ -0,0 +1,167 @@ +import { FileUploader } from "../api_upload"; +import { + del, + list, + shadowId, + publishId, + setDownLimit, + addLocalFiles +} from "../api_share"; +import { testLogin, testLogout } from "./api_auth_test"; + +const fileName = "filename"; + +function upload(fileName) { + return new Promise(resolve => { + const onStart = () => true; + const onProgress = () => true; + const onFinish = () => resolve(); + const onError = err => { + throw new Error(JSON.stringify(err)); + }; + const file = new File(["foo"], fileName, { + type: "text/plain" + }); + + const uploader = new FileUploader(onStart, onProgress, onFinish, onError); + uploader.uploadFile(file); + }); +} + +function getIdFromList(list, fileName) { + if (list == null) { + throw new Error("list: list fail"); + } + + // TODO: should verify file name + const filterInfo = list.find(info => { + return info.PathLocal.includes(fileName); + }); + + if (filterInfo == null) { + console.error(list); + throw new Error("list: file name not found"); + } else { + return filterInfo.Id; + } +} + +function delWithName(fileName) { + return list().then(infoList => { + const infoToDel = infoList.find(info => { + return info.PathLocal.includes(fileName); + }); + + if (infoToDel == null) { + console.warn("delWithName: name not found"); + } else { + return del(infoToDel.Id); + } + }); +} + +export function testShadowPublishId() { + return testLogin() + .then(() => upload(fileName)) + .then(list) + .then(infoList => { + return getIdFromList(infoList, fileName); + }) + .then(shareId => { + return shadowId(shareId).then(secretId => { + if (shareId === secretId) { + throw new Error("shadowId: id not changed"); + } else { + return secretId; + } + }); + }) + .then(secretId => { + return list().then(infoList => { + const info = infoList.find(info => { + return info.Id === secretId; + }); + + if (info.PathLocal.includes(fileName)) { + console.log("shadowId api: ok", secretId); + return secretId; + } else { + throw new Error("shadowId pai: file not found", infoList, fileName); + } + }); + }) + .then(secretId => { + return publishId(secretId).then(publicId => { + if (publicId === secretId) { + // TODO: it is not enough to check they are not equal + throw new Error("publicId: id not changed"); + } else { + console.log("publishId api: ok", publicId); + return publicId; + } + }); + }) + .then(shareId => del(shareId)) + .then(testLogout) + .catch(err => { + console.error(err); + delWithName(fileName); + }); +} + +export function testSetDownLimit() { + const downLimit = 777; + + return testLogin() + .then(() => upload(fileName)) + .then(list) + .then(infoList => { + return getIdFromList(infoList, fileName); + }) + .then(shareId => { + return setDownLimit(shareId, downLimit).then(ok => { + if (!ok) { + throw new Error("setDownLimit: failed"); + } else { + return shareId; + } + }); + }) + .then(shareId => { + return list().then(infoList => { + const info = infoList.find(info => { + return info.Id == shareId; + }); + + if (info.DownLimit === downLimit) { + console.log("setDownLimit api: ok"); + return shareId; + } else { + throw new Error("setDownLimit api: limit unchanged"); + } + }); + }) + .then(shareId => del(shareId)) + .then(testLogout) + .catch(err => { + console.error(err); + delWithName(fileName); + }); +} + +// TODO: need to add local file and test +export function testAddLocalFiles() { + return testLogin() + .then(() => addLocalFiles()) + .then(ok => { + if (ok) { + console.log("addLocalFiles api: ok"); + } else { + throw new Error("addLocalFiles api: failed"); + } + }) + .then(() => testLogout()) + .catch(err => { + console.error(err); + }); +} diff --git a/client/libs/test/api_test.js b/client/libs/test/api_test.js new file mode 100644 index 0000000..5ff75cf --- /dev/null +++ b/client/libs/test/api_test.js @@ -0,0 +1,25 @@ +import { testAuth } from "./api_auth_test"; +import { testUploadOneFile } from "./api_upload_test"; +import { + testAddLocalFiles, + testSetDownLimit, + testShadowPublishId +} from "./api_share_test"; +import { testUpDownBatch } from "./api_up_down_batch_test"; + +console.log("Test started"); + +const fileName = `test_filename${Date.now()}`; +const file = new File(["foo"], fileName, { + type: "text/plain" +}); + +testAuth() + .then(testShadowPublishId) + .then(() => testUploadOneFile(file, fileName)) + .then(testSetDownLimit) + .then(testAddLocalFiles) + .then(testUpDownBatch) + .then(() => { + console.log("Tests are finished"); + }); diff --git a/client/libs/test/api_up_down_batch_test.js b/client/libs/test/api_up_down_batch_test.js new file mode 100644 index 0000000..6011b1d --- /dev/null +++ b/client/libs/test/api_up_down_batch_test.js @@ -0,0 +1,97 @@ +import axios from "axios"; +import md5 from "md5"; + +import { config } from "../../config"; +import { testUpload } from "./api_upload_test"; +import { list, del } from "../api_share"; +import { testLogin, testLogout } from "./api_auth_test"; + +export function testUpDownBatch() { + const fileInfos = [ + { + fileName: "test_2MB_1", + content: new Array(1024 * 1024 * 2).join("x") + }, + { + fileName: "test_1MB_1", + content: new Array(1024 * 1024 * 1).join("x") + }, + { + fileName: "test_2MB_2", + content: new Array(1024 * 1024 * 2).join("x") + }, + { + fileName: "test_1B", + content: `${new Array(3).join("o")}${new Array(3).join("x")}` + } + ]; + + return testLogin() + .then(() => { + const promises = fileInfos.map(info => { + const file = new File([info.content], info.fileName, { + type: "text/plain" + }); + + return testUpAndDownOneFile(file, info.fileName); + }); + + return Promise.all(promises); + }) + .then(() => { + testLogout(); + }) + .catch(err => console.error(err)); +} + +export function testUpAndDownOneFile(file, fileName) { + return delTestFile(fileName) + .then(() => testUpload(file)) + .then(shareId => testDownload(shareId, file)) + .catch(err => console.error(err)); +} + +function delTestFile(fileName) { + return list().then(infos => { + const info = infos.find(info => { + return info.PathLocal === fileName; + }); + + if (info == null) { + console.log("up-down: file not found", fileName); + } else { + return del(info.Id); + } + }); +} + +function testDownload(shareId, file) { + return axios + .get(`${config.serverAddr}/download?shareid=${shareId}`) + .then(response => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = event => { + const upHash = md5(event.target.result); + const downHash = md5(response.data); + if (upHash !== downHash) { + console.error( + "up&down: hash unmatch", + file.name, + upHash, + downHash, + upHash.length, + downHash.length + ); + } else { + console.log("up&down: ok: hash match", file.name, upHash, downHash); + resolve(); + } + }; + + reader.onerror = err => reject(err); + + reader.readAsText(file); + }); + }); +} diff --git a/client/libs/test/api_upload_test.js b/client/libs/test/api_upload_test.js new file mode 100644 index 0000000..bbd3d99 --- /dev/null +++ b/client/libs/test/api_upload_test.js @@ -0,0 +1,63 @@ +import { FileUploader } from "../api_upload"; +import { list, del } from "../api_share"; +import { testLogin, testLogout } from "./api_auth_test"; + +function verify(fileName) { + return list() + .then(list => { + if (list == null) { + throw new Error("upload: list fail"); + } + + // TODO: should verify file name + const filterInfo = list.find(info => { + return info.PathLocal.includes(fileName); + }); + + if (filterInfo == null) { + console.error(list); + throw new Error("upload: file name not found"); + } else { + return filterInfo.Id; + } + }) + .then(shareId => { + console.log("upload api: ok"); + del(shareId); + }) + .then(testLogout) + .catch(err => { + throw err; + }); +} + +export function testUpload(file) { + const onStart = () => true; + const onProgress = () => true; + const onFinish = () => true; + const onError = err => { + throw new Error(JSON.stringify(err)); + }; + const uploader = new FileUploader(onStart, onProgress, onFinish, onError); + + return uploader.uploadFile(file).catch(err => { + console.error(err); + }); +} + +export function testUploadOneFile(file, fileName) { + const onStart = () => true; + const onProgress = () => true; + const onFinish = () => true; + const onError = err => { + throw new Error(JSON.stringify(err)); + }; + const uploader = new FileUploader(onStart, onProgress, onFinish, onError); + + return testLogin() + .then(() => uploader.uploadFile(file)) + .then(() => verify(fileName)) + .catch(err => { + console.error(err); + }); +} diff --git a/client/libs/utils.js b/client/libs/utils.js new file mode 100644 index 0000000..dddbc26 --- /dev/null +++ b/client/libs/utils.js @@ -0,0 +1,5 @@ +export function makePostBody(paramMap) { + return Object.keys(paramMap) + .map(key => `${key}=${paramMap[key]}`) + .join("&"); +} diff --git a/client/panels/admin.jsx b/client/panels/admin.jsx new file mode 100644 index 0000000..b098b1f --- /dev/null +++ b/client/panels/admin.jsx @@ -0,0 +1,150 @@ +import axios from "axios"; +import React from "react"; +import ReactDOM from "react-dom"; + +import { config } from "../config"; +import { addLocalFiles, list } from "../libs/api_share"; +import { login, logout } from "../libs/api_auth"; +import { FilePane } from "../components/composite/file_pane"; +import { InfoBar } from "../components/composite/info_bar"; +import { Log } from "../components/composite/log"; + +function getWidth() { + if (window.innerWidth >= window.innerHeight) { + return `${Math.floor( + window.innerWidth * 0.95 / config.rootSize / config.colWidth + ) * config.colWidth}rem`; + } + return "auto"; +} + +const styleLogContainer = { + paddingTop: "1rem", + textAlign: "center", + height: "2rem", + overflowX: "hidden" // TODO: should no hidden +}; + +const styleLogContent = { + color: "#333", + fontSize: "0.875rem", + opacity: 0.6, + backgroundColor: "#fff", + borderRadius: "1rem", + whiteSpace: "nowrap" +}; + +class AdminPanel extends React.PureComponent { + constructor(props) { + super(props); + this.state = { + isLogin: false, + filterName: "", + serverAddr: `${window.location.protocol}//${window.location.hostname}:${ + window.location.port + }`, + width: getWidth() + }; + this.log = { + ok: msg => console.log(msg), + warning: msg => console.log(msg), + info: msg => console.log(msg), + error: msg => console.log(msg), + start: msg => console.log(msg), + end: msg => console.log(msg) + }; + this.logComponent = ; + } + + componentWillMount() { + list().then(infos => { + if (infos != null) { + this.setState({ isLogin: true }); + } + }); + } + + setWidth = () => { + this.setState({ width: getWidth() }); + }; + + // componentDidMount() { + // window.addEventListener("resize", this.setWidth); + // } + + // componentWillUnmount() { + // window.removeEventListener("resize", this.setWidth); + // } + + onLogin = (serverAddr, adminId, adminPwd) => { + login(serverAddr, adminId, adminPwd).then(ok => { + if (ok === true) { + this.setState({ isLogin: true }); + } else { + this.log.error("Fail to login"); + this.setState({ isLogin: false }); + } + }); + }; + + onLogout = serverAddr => { + logout(serverAddr).then(ok => { + if (ok === false) { + this.log.error("Fail to log out"); + } else { + this.log.ok("You are logged out"); + } + this.setState({ isLogin: false }); + }); + }; + + onSearch = fileName => { + this.setState({ filterName: fileName }); + }; + + assignLog = logRef => { + this.log = logRef; + this.log.info( + + Know more about Quickshare + + ); + }; + + render() { + const width = this.state.width; + + return ( +
+ +
{this.logComponent}
+
+ {this.state.isLogin ? ( + + ) : ( +
+ )} +
+ ); + } +} + +ReactDOM.render(, document.getElementById("app")); diff --git a/client/tests/enzyme_setup.js b/client/tests/enzyme_setup.js new file mode 100644 index 0000000..814fd50 --- /dev/null +++ b/client/tests/enzyme_setup.js @@ -0,0 +1,4 @@ +import { configure } from "enzyme"; +import Adapter from "enzyme-adapter-react-15"; + +configure({ adapter: new Adapter() }); diff --git a/client/tests/test_helper.js b/client/tests/test_helper.js new file mode 100644 index 0000000..32d76ac --- /dev/null +++ b/client/tests/test_helper.js @@ -0,0 +1,73 @@ +// function should be called after async operation is finished +export function execFuncs(instance, execs) { + // instance: enzyme mounted component + // const execs = [ + // { + // func: "componentWillMount", + // args: [] + // } + // ]; + return execs.reduce((prePromise, nextFunc) => { + return prePromise.then(() => instance[nextFunc.func](...nextFunc.args)); + }, Promise.resolve()); +} + +export function execsToStr(execs) { + // const execs = [ + // { + // func: "componentWillMount", + // args: [] + // } + // ]; + const execList = execs.map( + funcInfo => `${funcInfo.func}(${funcInfo.args.join(", ")})` + ); + + return execList.join(", "); +} + +export function getDesc(componentName, testCase) { + // const testCase = { + // execs: [ + // { + // func: "onAddLocalFiles", + // args: [] + // } + // ], + // state: { + // filterFileName: "" + // }, + // calls: [ + // { + // func: "onAddLocalFiles", + // count: 1 + // } + // ] + // } + return `${componentName} should satisfy following by exec ${execsToStr( + testCase.execs + )} + state=${JSON.stringify(testCase.state)} + calls=${JSON.stringify(testCase.calls)} `; +} + +export function verifyCalls(calls, stubs) { + // const calls: [ + // { + // func: "funcName", + // count: 1 + // } + // ]; + // const stubs = { + // funcName: jest.fn(), + // }; + let err = null; + calls.forEach(called => { + if (stubs[called.func].mock.calls.length != called.count) { + err = `InfoBar: ${called.func} should be called ${called.count} but ${ + stubs[called.func].mock.calls.length + }`; + } + }); + return err; +} diff --git a/client/webpack.config.common.js b/client/webpack.config.common.js new file mode 100644 index 0000000..51ea9cd --- /dev/null +++ b/client/webpack.config.common.js @@ -0,0 +1,60 @@ +const webpack = require("webpack"); +const CleanWebpackPlugin = require("clean-webpack-plugin"); +// const HtmlWebpackPlugin = require("html-webpack-plugin"); + +const outputPath = `${__dirname}/../public/dist`; + +module.exports = { + context: __dirname, + entry: { + assets: ["axios", "immutable", "react", "react-dom"], + admin: "./panels/admin" + }, + output: { + path: outputPath, + filename: "[name].bundle.js" + }, + module: { + rules: [ + { + test: /\.js|jsx$/, + use: [ + { + loader: "babel-loader", + options: { + presets: ["es2015", "react", "stage-2"] + } + } + ] + }, + { + test: /\.css$/, + use: ["style-loader", "css-loader"] + }, + { + test: /\.(png|jpg|gif)$/, + use: [ + { + loader: "file-loader", + options: {} + } + ] + } + ] + }, + resolve: { + extensions: [".js", ".json", ".jsx", ".css"] + }, + plugins: [ + new webpack.optimize.CommonsChunkPlugin({ + name: "assets", + // filename: "vendor.js" + // (Give the chunk a different name) + minChunks: Infinity + // (with more entries, this ensures that no other module + // goes into the vendor chunk) + }), + // new HtmlWebpackPlugin(), + new CleanWebpackPlugin([outputPath]) + ] +}; diff --git a/client/webpack.config.dev.js b/client/webpack.config.dev.js new file mode 100644 index 0000000..45be7e1 --- /dev/null +++ b/client/webpack.config.dev.js @@ -0,0 +1,17 @@ +const merge = require("webpack-merge"); +const common = require("./webpack.config.common.js"); + +module.exports = merge(common, { + entry: { + api_test: "./libs/test/api_test" + }, + devtool: "inline-source-map", + devServer: { + contentBase: "./dist" + }, + watchOptions: { + aggregateTimeout: 1000, + poll: 1000, + ignored: /node_modules/ + } +}); diff --git a/client/webpack.config.prod.js b/client/webpack.config.prod.js new file mode 100644 index 0000000..f7ffae0 --- /dev/null +++ b/client/webpack.config.prod.js @@ -0,0 +1,16 @@ +const common = require("./webpack.config.common.js"); +const merge = require("webpack-merge"); +const UglifyJS = require("uglifyjs-webpack-plugin"); +const webpack = require("webpack"); + +module.exports = merge(common, { + devtool: "source-map", + plugins: [ + new UglifyJS({ + sourceMap: true + }), + new webpack.DefinePlugin({ + "process.env.NODE_ENV": JSON.stringify("production") + }) + ] +}); diff --git a/config.json b/config.json new file mode 100644 index 0000000..5b64a7e --- /dev/null +++ b/config.json @@ -0,0 +1,77 @@ +{ + "AppName": "qs", + "AdminId": "admin", + "AdminPwd": "quicksh@re", + "SecretKey": "qs", + "Production": true, + "HostName": "", + "Port": 8888, + "MaxUpBytesPerSec": 2000000, + "MaxDownBytesPerSec": 1000000, + "MaxRangeLength": 10485760, + "Timeout": 7200000, + "ReadTimeout": 5000, + "WriteTimeout": 7200000, + "IdleTimeout": 10000, + "WorkerPoolSize": 16, + "TaskQueueSize": 16, + "QueueSize": 16, + "ParseFormBufSize": 5000000, + "MaxHeaderBytes": 1024, + "DownLimit": -1, + "MaxShares": 16384, + "LocalFileLimit": -1, + "CookieDomain": "", + "CookieHttpOnly": false, + "CookieMaxAge": 604800, + "CookiePath": "/", + "CookieSecure": false, + "KeyAdminId": "adminid", + "KeyAdminPwd": "adminpwd", + "KeyToken": "token", + "KeyFileName": "fname", + "KeyFileSize": "size", + "KeyShareId": "shareid", + "KeyStart": "start", + "KeyLen": "len", + "KeyChunk": "chunk", + "KeyAct": "act", + "KeyExpires": "expires", + "KeyDownLimit": "downlimit", + "ActStartUpload": "startupload", + "ActUpload": "upload", + "ActFinishUpload": "finishupload", + "ActLogin": "login", + "ActLogout": "logout", + "ActShadowId": "shadowid", + "ActPublishId": "publishid", + "ActSetDownLimit": "setdownlimit", + "ActAddLocalFiles": "addlocalfiles", + "AllUsers": "addlocalfiles", + "OpIdIpVisit": 0, + "OpIdUpload": 1, + "OpIdDownload": 2, + "OpIdLogin": 3, + "OpIdGetFInfo": 4, + "OpIdDelFInfo": 5, + "OpIdOpFInfo": 6, + "PathLocal": "files", + "PathLogin": "/login", + "PathDownloadLogin": "/download-login", + "PathDownload": "/download", + "PathUpload": "/upload", + "PathStartUpload": "/startupload", + "PathFinishUpload": "/finishupload", + "PathFileInfo": "/fileinfo", + "PathClient": "/", + "LimiterCap": 256, + "LimiterTtl": 3600, + "LimiterCyc": 1, + "BucketCap": 10, + "SpecialCapsStr": { + "0": 30, + "1": 10, + "2": 10, + "3": 1 + } +} diff --git a/demo.jpg b/demo.jpg new file mode 100644 index 0000000..5a1a1c7 Binary files /dev/null and b/demo.jpg differ diff --git a/docs/CONFIG_en-us.md b/docs/CONFIG_en-us.md new file mode 100644 index 0000000..7b43de2 --- /dev/null +++ b/docs/CONFIG_en-us.md @@ -0,0 +1,82 @@ +## Configuration + +```Javascript +{ + "AppName": "qs", + "AdminId": "admin", // login user name + "AdminPwd": "quicksh@re", // login password + "SecretKey": "qs", // key for hashing cookie (jwt) + "Production": true, + "HostName": "", // listening address + "Port": 8888, // listening port + "MaxUpBytesPerSec": 2000000, // upload speed limit + "MaxDownBytesPerSec": 1000000, // download speed limit + "MaxRangeLength": 10485760, // max length of chunk to upload at once + "Timeout": 7200000, // connection timeout + "ReadTimeout": 5000, // connection read request timeout + "WriteTimeout": 7200000, // connection write response timeout + "IdleTimeout": 10000, // connection idle timeout + "WorkerPoolSize": 16, // number of workers, it decides how many download connections are provided at same time + "TaskQueueSize": 16, // how many requests can be queued + "QueueSize": 16, + "ParseFormBufSize": 5000000, // buffer for parsing request + "MaxHeaderBytes": 1024, // max header size in byte + "DownLimit": -1, // default download limit + "MaxShares": 16384, // max number of sharing + "LocalFileLimit": -1, // max number of listing file at once + "CookieDomain": "", + "CookieHttpOnly": false, + "CookieMaxAge": 604800, + "CookiePath": "/", + "CookieSecure": false, + "KeyAdminId": "adminid", + "KeyAdminPwd": "adminpwd", + "KeyToken": "token", + "KeyFileName": "fname", + "KeyFileSize": "size", + "KeyShareId": "shareid", + "KeyStart": "start", + "KeyLen": "len", + "KeyChunk": "chunk", + "KeyAct": "act", + "KeyExpires": "expires", + "KeyDownLimit": "downlimit", + "ActStartUpload": "startupload", + "ActUpload": "upload", + "ActFinishUpload": "finishupload", + "ActLogin": "login", + "ActLogout": "logout", + "ActShadowId": "shadowid", + "ActPublishId": "publishid", + "ActSetDownLimit": "setdownlimit", + "ActAddLocalFiles": "addlocalfiles", + "AllUsers": "addlocalfiles", + "OpIdIpVisit": 0, + "OpIdUpload": 1, + "OpIdDownload": 2, + "OpIdLogin": 3, + "OpIdGetFInfo": 4, + "OpIdDelFInfo": 5, + "OpIdOpFInfo": 6, + "PathLocal": "files", + "PathLogin": "/login", + "PathDownloadLogin": "/download-login", + "PathDownload": "/download", + "PathUpload": "/upload", + "PathStartUpload": "/startupload", + "PathFinishUpload": "/finishupload", + "PathFileInfo": "/fileinfo", + "PathClient": "/", + "LimiterCap": 256, + "LimiterTtl": 3600, + "LimiterCyc": 1, + "BucketCap": 10, // operation is allowed at most 10 times per second, but SpecialCapsStr will override this value + "SpecialCapsStr": { + "0": 30, // IpVisit is allowed at most 30 times per second + "1": 10, // Uploading is allowed at most 10 times per second + "2": 10, // Downloading is allowed at most 10 times per second + "3": 1 // Login/Logout is allowed at most 1 time per second + // You can also add rate limits according to OpIdxxx above. + } +} +``` diff --git a/docs/FAQ_en-us.md b/docs/FAQ_en-us.md new file mode 100644 index 0000000..3290f00 --- /dev/null +++ b/docs/FAQ_en-us.md @@ -0,0 +1,18 @@ +## FAQ + +* How to change accound name and password? + * Go to quickshare folder + * Open config.json using text editor. (e.g. notpad++, sublime, vscode, etc...) + * Search for line `"AdminPwd": "quicksh@re",` + * Replace `quicksh@re` with your password, e.g. `"AdminPwd": "myPassword",` + * Then you can also update user name `"AdminId": "myUserName",` in above way. +* How to change listening address(or port)? + * Go to quickshare folder + * Open config.json using text editor. (e.g. notpad++, sublime, vscode, etc...) + * Search for line `"HostName": "",` + * Change the value of `HostName`, e.g. `"HostName": "192.168.0.6",` + * You can also change port value (`"Port": 8888,`) in above way +* How to change background? + * Go to `public` folder under quickshare folder + * Open style.css using text editor. (e.g. notpad++, sublime, vscode, etc...) + * Update `body`'s css. diff --git a/docs/FAQ_zh-cn.md b/docs/FAQ_zh-cn.md new file mode 100644 index 0000000..9edf39f --- /dev/null +++ b/docs/FAQ_zh-cn.md @@ -0,0 +1,17 @@ +## 常见问题 + +* 怎么改用户名和密码? + * 进入 quickshare 文件夹 + * 用文本编辑器打开 config.json. (比如 notpad++, sublime, vscode, 等等...) + * 查找行 `"AdminPwd": "quicksh@re",` + * 用你的密码替换 `quicksh@re`, 比如 `"AdminPwd": "你的密码",` +* 怎么改监听地址(端口)? + * 进入 quickshare 文件夹 + * 用文本编辑器打开 config.json. (比如 notpad++, sublime, vscode, 等等...) + * 查找行 `"HostName": "",` + * 更改`HostName`的值, 比如 `"HostName": "192.168.0.6",` + * 你可以用上面的方法更改端口值(`"Port": 8888,`) +* 怎么更改背景? + * 进入 quickshare 文件夹下的`public`文件夹 + * 使用文本编辑器打开 style.css. (比如 notpad++, sublime, vscode, 等等...) + * 更新 `body`'的 css diff --git a/docs/README_zh-cn.md b/docs/README_zh-cn.md new file mode 100644 index 0000000..fcaa026 --- /dev/null +++ b/docs/README_zh-cn.md @@ -0,0 +1,69 @@ +

+ Quickshare +

+

+ 一个小而美的文件共享服务器 +

+

+ + + +

+ +![Quickshare 演示](../demo.jpg) + +选择语言: [English](../README.md) | 简体中文 + +## 下载 + +| Linux | Mac | Windows | +| -------- | -------- | -------- | +| [下载]() | [下载]() | [下载]() | + +## 特点 + +* 在浏览器中上传下载, 无需安装任何客户端 +* 在桌面电脑和移动设备件间共享文件 +* 绿色软件 +* 支持添加本地文件 +* 支持添加文件下载次数上限 +* 支持断点续传 + +## 安装 + +开始使用 quickshare 主要需要两步: 解压, 启动. + +第一步, 解压下载包, 启动 quickshare. + +### Linux + +* 解压包: `unzip [package].` (`[package]` 可以是`quickshare_0.0.8_linux_x86_6 4.zip`) +* 进入文件夹, 启动 quickshare `./quickshare` + +### Mac + +* 解压包: `unzip [package].` (`[package]` 可以是`quickshare_0.0.8_macos_x86_6 4.zip`) +* 进入文件夹, 启动 quickshare `./quickshare` + +### Windows + +* 解压包 +* 进入文件夹, 双击 `quickshare.exe` 启动 quickshare + +最后一步, 在浏览器中面基 quickshare. + +* Quickshare 会在启动后, 在命令中输出中显示 `quickshare starts @ [URL]` (举个栗子 `URL` 可以是 `192.168.0.1:8888`) +* 在浏览器中打开 `URL` 并使用 `admin` 和 `quicksh@re` 登入 +* 开耍(不过别忘改密码[常见问题文档](./FAQ_zh-cn.md)) + +### 常见问题 + +请参考[常见问题文档](./FAQ_zh-cn.md) + +### 配置 + +请参考[配置文档](./CONFIG_en-us.md) + +### 贡献代码 + +待添加... diff --git a/package.json b/package.json new file mode 100644 index 0000000..8536fae --- /dev/null +++ b/package.json @@ -0,0 +1,68 @@ +{ + "name": "quickshare", + "version": "0.0.11", + "description": "A succinct file sharing server.", + "main": "", + "scripts": { + "start": "yarn build-client && go run server.go", + "test": "go test -v ./... && yarn jest --coverage", + "fmt": "go fmt ./...", + "clean": "go clean && rm -r ./dist && rm -r ./public/dist", + "build": + "rm -rf ./dist && rm -rf ./public/dist && yarn build-client && goreleaser --snapshot", + "build-client": "webpack --config ./client/webpack.config.prod.js", + "build-client-dev": "webpack --config ./client/webpack.config.dev.js" + }, + "author": "hexxa", + "license": "ISC", + "devDependencies": { + "babel-core": "^6.26.0", + "babel-jest": "^21.2.0", + "babel-loader": "^6.4.1", + "babel-preset-env": "^1.2.2", + "babel-preset-es2015": "^6.24.0", + "babel-preset-react": "^6.24.1", + "babel-preset-stage-0": "^6.24.1", + "babel-preset-stage-2": "^6.24.1", + "clean-webpack-plugin": "^0.1.19", + "css-loader": "^0.28.11", + "enzyme": "^3.3.0", + "enzyme-adapter-react-15": "^1.0.5", + "eslint": "^4.12.0", + "eslint-config-airbnb": "^16.0.0", + "eslint-plugin-import": "^2.7.0", + "eslint-plugin-jsx-a11y": "^6.0.2", + "eslint-plugin-react": "^7.4.0", + "html-webpack-plugin": "^3.2.0", + "jest": "^21.2.1", + "md5": "^2.2.1", + "react-addons-test-utils": "^15.6.2", + "react-test-renderer": "15", + "regenerator-runtime": "^0.11.1", + "style-loader": "^0.21.0", + "uglifyjs-webpack-plugin": "^1.2.5", + "uuid": "^3.1.0", + "value-equal": "^0.4.0", + "webpack": "^2.0.0", + "webpack-dev-server": "^2.9.5" + }, + "dependencies": { + "axios": "^0.16.2", + "byte-size": "^4.0.2", + "debounce": "^1.1.0", + "file-loader": "^1.1.11", + "immutable": "^3.8.2", + "lodash.throttle": "^4.1.1", + "react": "^15.6.2", + "react-dom": "^15.6.2", + "react-icons": "^2.2.7", + "webpack-merge": "^4.1.2" + }, + "jest": { + "transform": { + "^.+\\.jsx?$": "babel-jest" + }, + "setupFiles": ["./client/tests/enzyme_setup.js"], + "verbose": true + } +} diff --git a/public/api_test.html b/public/api_test.html new file mode 100644 index 0000000..45f7f60 --- /dev/null +++ b/public/api_test.html @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/ggb.jpg b/public/ggb.jpg new file mode 100644 index 0000000..3176036 Binary files /dev/null and b/public/ggb.jpg differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..74e1dab --- /dev/null +++ b/public/index.html @@ -0,0 +1,16 @@ + + + + + + + + + + + +

+ + + + \ No newline at end of file diff --git a/public/squared_metal.png b/public/squared_metal.png new file mode 100644 index 0000000..771231f Binary files /dev/null and b/public/squared_metal.png differ diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000..cfd36b3 --- /dev/null +++ b/public/style.css @@ -0,0 +1,68 @@ +html { + background: url("ggb.jpg") no-repeat left center fixed; + background-size: auto 100%; + /* background-image: url("squared_metal.png"); repeat fixed*/ + background-color: black; + height: 100%; +} + +body { + height: 100%; + min-height: 100%; + color: #333; + font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif; + font-size: 16px; + margin: 0; + padding: 0; + outline: none; + border: none; +} + +a { + outline: none; + border: none; +} + +.anm-rotate { + animation: rotate 1s infinite linear; +} + +@keyframes rotate { + 20% { + transform: rotate(72deg); + } + 40% { + transform: rotate(144deg); + } + 60% { + transform: rotate(216deg); + } + 80% { + transform: rotate(288deg); + } + 100% { + transform: rotate(360deg); + } +} + +.anm-lighter:hover { + animation: lighter 0.5 1; +} + +@keyframes lighter { + 0% { + opacity: 100%; + } + 50% { + opacity: 50%; + } +} + +::-moz-selection { + background: #2ecc71; + color: #fff; +} +::selection { + background: #2ecc71; + color: #fff; +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..26b3f49 --- /dev/null +++ b/server.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "time" +) + +import ( + "quickshare/server/apis" + "quickshare/server/libs/cfg" +) + +func main() { + config := cfg.NewConfigFrom("config.json") + srvShare := apis.NewSrvShare(config) + + // TODO: using httprouter instead + mux := http.NewServeMux() + mux.HandleFunc(config.PathLogin, srvShare.LoginHandler) + mux.HandleFunc(config.PathStartUpload, srvShare.StartUploadHandler) + mux.HandleFunc(config.PathUpload, srvShare.UploadHandler) + mux.HandleFunc(config.PathFinishUpload, srvShare.FinishUploadHandler) + mux.HandleFunc(config.PathDownload, srvShare.DownloadHandler) + mux.HandleFunc(config.PathFileInfo, srvShare.FileInfoHandler) + mux.HandleFunc(config.PathClient, srvShare.ClientHandler) + + server := &http.Server{ + Addr: fmt.Sprintf("%s:%d", config.HostName, config.Port), + Handler: mux, + MaxHeaderBytes: config.MaxHeaderBytes, + ReadTimeout: time.Duration(config.ReadTimeout) * time.Millisecond, + WriteTimeout: time.Duration(config.WriteTimeout) * time.Millisecond, + IdleTimeout: time.Duration(config.IdleTimeout) * time.Millisecond, + } + + log.Printf("quickshare starts @ %s:%d", config.HostName, config.Port) + log.Fatal(server.ListenAndServe()) +} diff --git a/server/apis/auth.go b/server/apis/auth.go new file mode 100644 index 0000000..ff5afd5 --- /dev/null +++ b/server/apis/auth.go @@ -0,0 +1,105 @@ +package apis + +import ( + "net/http" + "time" +) + +import ( + "quickshare/server/libs/httputil" + "quickshare/server/libs/httpworker" +) + +func (srv *SrvShare) LoginHandler(res http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + srv.Http.Fill(httputil.Err404, res) + return + } + + act := req.FormValue(srv.Conf.KeyAct) + todo := func(res http.ResponseWriter, req *http.Request) interface{} { return httputil.Err404 } + switch act { + case srv.Conf.ActLogin: + todo = srv.Login + case srv.Conf.ActLogout: + todo = srv.Logout + default: + srv.Http.Fill(httputil.Err404, res) + return + } + + ack := make(chan error, 1) + ok := srv.WorkerPool.Put(&httpworker.Task{ + Ack: ack, + Do: srv.Wrap(todo), + Res: res, + Req: req, + }) + if !ok { + srv.Http.Fill(httputil.Err503, res) + return + } + + execErr := srv.WorkerPool.IsInTime(ack, time.Duration(srv.Conf.Timeout)*time.Millisecond) + if srv.Err.IsErr(execErr) { + srv.Http.Fill(httputil.Err500, res) + } +} + +func (srv *SrvShare) Login(res http.ResponseWriter, req *http.Request) interface{} { + // all users need to pass same wall to login + if !srv.Walls.PassIpLimit(GetRemoteIp(req.RemoteAddr)) || + !srv.Walls.PassOpLimit(srv.Conf.AllUsers, srv.Conf.OpIdLogin) { + return httputil.Err504 + } + + return srv.login( + req.FormValue(srv.Conf.KeyAdminId), + req.FormValue(srv.Conf.KeyAdminPwd), + res, + ) +} + +func (srv *SrvShare) login(adminId string, adminPwd string, res http.ResponseWriter) interface{} { + if adminId != srv.Conf.AdminId || + adminPwd != srv.Conf.AdminPwd { + return httputil.Err401 + } + + token := srv.Walls.MakeLoginToken(srv.Conf.AdminId) + if token == "" { + return httputil.Err500 + } + + srv.Http.SetCookie(res, srv.Conf.KeyToken, token) + return httputil.Ok200 +} + +func (srv *SrvShare) Logout(res http.ResponseWriter, req *http.Request) interface{} { + srv.Http.SetCookie(res, srv.Conf.KeyToken, "-") + return httputil.Ok200 +} + +func (srv *SrvShare) IsValidLength(length int64) bool { + return length > 0 && length <= srv.Conf.MaxUpBytesPerSec +} + +func (srv *SrvShare) IsValidStart(start, expectStart int64) bool { + return start == expectStart +} + +func (srv *SrvShare) IsValidShareId(shareId string) bool { + // id could be 0 for dev environment + if srv.Conf.Production { + return len(shareId) == 64 + } + return true +} + +func (srv *SrvShare) IsValidDownLimit(limit int) bool { + return limit >= -1 +} + +func IsValidFileName(fileName string) bool { + return fileName != "" && len(fileName) < 240 +} diff --git a/server/apis/auth_test.go b/server/apis/auth_test.go new file mode 100644 index 0000000..e46375b --- /dev/null +++ b/server/apis/auth_test.go @@ -0,0 +1,78 @@ +package apis + +import ( + "fmt" + "strings" + "testing" +) + +import ( + "quickshare/server/libs/cfg" + "quickshare/server/libs/encrypt" + "quickshare/server/libs/httputil" +) + +func TestLogin(t *testing.T) { + conf := cfg.NewConfig() + + type testCase struct { + Desc string + AdminId string + AdminPwd string + Result interface{} + VerifyToken bool + } + + testCases := []testCase{ + testCase{ + Desc: "invalid input", + AdminId: "", + AdminPwd: "", + Result: httputil.Err401, + VerifyToken: false, + }, + testCase{ + Desc: "account not match", + AdminId: "unknown", + AdminPwd: "unknown", + Result: httputil.Err401, + VerifyToken: false, + }, + testCase{ + Desc: "succeed to login", + AdminId: conf.AdminId, + AdminPwd: conf.AdminPwd, + Result: httputil.Ok200, + VerifyToken: true, + }, + } + + for _, testCase := range testCases { + srv := NewSrvShare(conf) + res := &stubWriter{Headers: map[string][]string{}} + ret := srv.login(testCase.AdminId, testCase.AdminPwd, res) + + if ret != testCase.Result { + t.Fatalf("login: reponse=%v testCase=%v", ret, testCase.Result) + } + + // verify cookie (only token.adminid part)) + if testCase.VerifyToken { + cookieVal := strings.Replace( + res.Header().Get("Set-Cookie"), + fmt.Sprintf("%s=", conf.KeyToken), + "", + 1, + ) + + gotTokenStr := strings.Split(cookieVal, ";")[0] + token := encrypt.JwtEncrypterMaker(conf.SecretKey) + token.FromStr(gotTokenStr) + gotToken, found := token.Get(conf.KeyAdminId) + if !found || conf.AdminId != gotToken { + t.Fatalf("login: token admin id unmatch got=%v expect=%v", gotToken, conf.AdminId) + } + } + + } +} diff --git a/server/apis/client.go b/server/apis/client.go new file mode 100644 index 0000000..63f6724 --- /dev/null +++ b/server/apis/client.go @@ -0,0 +1,66 @@ +package apis + +import ( + "net/http" + "path/filepath" + "strings" + "time" +) + +import ( + "quickshare/server/libs/httputil" + "quickshare/server/libs/httpworker" +) + +func (srv *SrvShare) ClientHandler(res http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodGet { + srv.Http.Fill(httputil.Err404, res) + return + } + + ack := make(chan error, 1) + ok := srv.WorkerPool.Put(&httpworker.Task{ + Ack: ack, + Do: srv.Wrap(srv.GetClient), + Res: res, + Req: req, + }) + if !ok { + srv.Http.Fill(httputil.Err503, res) + return + } + + execErr := srv.WorkerPool.IsInTime(ack, time.Duration(srv.Conf.Timeout)*time.Millisecond) + if srv.Err.IsErr(execErr) { + srv.Http.Fill(httputil.Err500, res) + } +} + +func (srv *SrvShare) GetClient(res http.ResponseWriter, req *http.Request) interface{} { + if !srv.Walls.PassIpLimit(GetRemoteIp(req.RemoteAddr)) { + return httputil.Err504 + } + + return srv.getClient(res, req, req.URL.EscapedPath()) +} + +func (srv *SrvShare) getClient(res http.ResponseWriter, req *http.Request, relPath string) interface{} { + if strings.HasSuffix(relPath, "/") { + relPath = relPath + "index.html" + } + if !IsValidClientPath(relPath) { + return httputil.Err400 + } + + fullPath := filepath.Clean(filepath.Join("./public", relPath)) + http.ServeFile(res, req, fullPath) + return 0 +} + +func IsValidClientPath(fullPath string) bool { + if strings.Contains(fullPath, "..") { + return false + } + + return true +} diff --git a/server/apis/download.go b/server/apis/download.go new file mode 100644 index 0000000..9174786 --- /dev/null +++ b/server/apis/download.go @@ -0,0 +1,69 @@ +package apis + +import ( + "net/http" + "time" +) + +import ( + "quickshare/server/libs/fileidx" + "quickshare/server/libs/httputil" + "quickshare/server/libs/httpworker" +) + +func (srv *SrvShare) DownloadHandler(res http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodGet { + srv.Http.Fill(httputil.Err404, res) + } + + ack := make(chan error, 1) + ok := srv.WorkerPool.Put(&httpworker.Task{ + Ack: ack, + Do: srv.Wrap(srv.Download), + Res: res, + Req: req, + }) + if !ok { + srv.Http.Fill(httputil.Err503, res) + } + + // using WriteTimeout instead of Timeout + // After timeout, connection will be lost, and worker will fail to write and return + execErr := srv.WorkerPool.IsInTime(ack, time.Duration(srv.Conf.WriteTimeout)*time.Millisecond) + if srv.Err.IsErr(execErr) { + srv.Http.Fill(httputil.Err500, res) + } +} + +func (srv *SrvShare) Download(res http.ResponseWriter, req *http.Request) interface{} { + shareId := req.FormValue(srv.Conf.KeyShareId) + if !srv.Walls.PassIpLimit(GetRemoteIp(req.RemoteAddr)) || + !srv.Walls.PassOpLimit(shareId, srv.Conf.OpIdDownload) { + return httputil.Err429 + } + + return srv.download(shareId, res, req) +} + +func (srv *SrvShare) download(shareId string, res http.ResponseWriter, req *http.Request) interface{} { + if !srv.IsValidShareId(shareId) { + return httputil.Err400 + } + + fileInfo, found := srv.Index.Get(shareId) + switch { + case !found || fileInfo.State != fileidx.StateDone: + return httputil.Err404 + case fileInfo.DownLimit == 0: + return httputil.Err412 + default: + updated, _ := srv.Index.DecrDownLimit(shareId) + if updated != 1 { + return httputil.Err500 + } + } + + err := srv.Downloader.ServeFile(res, req, fileInfo) + srv.Err.IsErr(err) + return 0 +} diff --git a/server/apis/download_test.go b/server/apis/download_test.go new file mode 100644 index 0000000..8317d31 --- /dev/null +++ b/server/apis/download_test.go @@ -0,0 +1,271 @@ +package apis + +import ( + "net/http" + "os" + "testing" + "time" +) + +import ( + "quickshare/server/libs/cfg" + "quickshare/server/libs/errutil" + "quickshare/server/libs/fileidx" + "quickshare/server/libs/httputil" + "quickshare/server/libs/logutil" + "quickshare/server/libs/qtube" +) + +func initServiceForDownloadTest(config *cfg.Config, indexMap map[string]*fileidx.FileInfo, content string) *SrvShare { + setDownloader := func(srv *SrvShare) { + srv.Downloader = stubDownloader{Content: content} + } + + setIndex := func(srv *SrvShare) { + srv.Index = fileidx.NewMemFileIndexWithMap(len(indexMap), indexMap) + } + + setFs := func(srv *SrvShare) { + srv.Fs = &stubFsUtil{ + MockFile: &qtube.StubFile{ + Content: content, + Offset: 0, + }, + } + } + + logger := logutil.NewSlog(os.Stdout, config.AppName) + setLog := func(srv *SrvShare) { + srv.Log = logger + } + + setErr := func(srv *SrvShare) { + srv.Err = errutil.NewErrChecker(!config.Production, logger) + } + + return InitSrvShare(config, setDownloader, setIndex, setFs, setLog, setErr) +} + +func TestDownload(t *testing.T) { + conf := cfg.NewConfig() + conf.Production = false + + type Init struct { + Content string + IndexMap map[string]*fileidx.FileInfo + } + type Input struct { + ShareId string + } + type Output struct { + IndexMap map[string]*fileidx.FileInfo + Response interface{} + Body string + } + type testCase struct { + Desc string + Init + Input + Output + } + + testCases := []testCase{ + testCase{ + Desc: "empty file index", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{}, + }, + Input: Input{ + ShareId: "0", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{}, + Response: httputil.Err404, + }, + }, + testCase{ + Desc: "file info not found", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{ + "1": &fileidx.FileInfo{}, + }, + }, + Input: Input{ + ShareId: "0", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + "1": &fileidx.FileInfo{}, + }, + Response: httputil.Err404, + }, + }, + testCase{ + Desc: "file not found because of state=uploading", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + DownLimit: 1, + ModTime: time.Now().UnixNano(), + PathLocal: "path", + State: fileidx.StateUploading, + Uploaded: 1, + }, + }, + }, + Input: Input{ + ShareId: "0", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + DownLimit: 1, + ModTime: time.Now().UnixNano(), + PathLocal: "path", + State: fileidx.StateUploading, + Uploaded: 1, + }, + }, + Response: httputil.Err404, + }, + }, + testCase{ + Desc: "download failed because download limit = 0", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + DownLimit: 0, + ModTime: time.Now().UnixNano(), + PathLocal: "path", + State: fileidx.StateDone, + Uploaded: 1, + }, + }, + }, + Input: Input{ + ShareId: "0", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + DownLimit: 0, + ModTime: time.Now().UnixNano(), + PathLocal: "path", + State: fileidx.StateDone, + Uploaded: 1, + }, + }, + Response: httputil.Err412, + }, + }, + testCase{ + Desc: "succeed to download", + Init: Init{ + Content: "content", + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + DownLimit: 1, + ModTime: time.Now().UnixNano(), + PathLocal: "path", + State: fileidx.StateDone, + Uploaded: 1, + }, + }, + }, + Input: Input{ + ShareId: "0", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + DownLimit: 0, + ModTime: time.Now().UnixNano(), + PathLocal: "path", + State: fileidx.StateDone, + Uploaded: 1, + }, + }, + Response: 0, + Body: "content", + }, + }, + testCase{ + Desc: "succeed to download DownLimit == -1", + Init: Init{ + Content: "content", + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + DownLimit: -1, + ModTime: time.Now().UnixNano(), + PathLocal: "path", + State: fileidx.StateDone, + Uploaded: 1, + }, + }, + }, + Input: Input{ + ShareId: "0", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + DownLimit: -1, + ModTime: time.Now().UnixNano(), + PathLocal: "path", + State: fileidx.StateDone, + Uploaded: 1, + }, + }, + Response: 0, + Body: "content", + }, + }, + } + + for _, testCase := range testCases { + srv := initServiceForDownloadTest(conf, testCase.Init.IndexMap, testCase.Content) + writer := &stubWriter{Headers: map[string][]string{}} + response := srv.download( + testCase.ShareId, + writer, + &http.Request{}, + ) + + // verify downlimit + if !sameMap(srv.Index.List(), testCase.Output.IndexMap) { + info, _ := srv.Index.Get(testCase.ShareId) + t.Fatalf( + "download: index incorrect got=%v want=%v", + info, + testCase.Output.IndexMap[testCase.ShareId], + ) + } + + // verify response + if response != testCase.Output.Response { + t.Fatalf( + "download: response incorrect response=%v testCase=%v", + response, + testCase.Output.Response, + ) + } + + // verify writerContent + if string(writer.Response) != testCase.Output.Body { + t.Fatalf( + "download: body incorrect got=%v want=%v", + string(writer.Response), + testCase.Output.Body, + ) + } + + } +} diff --git a/server/apis/file_info.go b/server/apis/file_info.go new file mode 100644 index 0000000..a6ad0d4 --- /dev/null +++ b/server/apis/file_info.go @@ -0,0 +1,234 @@ +package apis + +import ( + "fmt" + "math/rand" + "net/http" + "path/filepath" + "strconv" + "time" +) + +import ( + "quickshare/server/libs/fileidx" + "quickshare/server/libs/httputil" + "quickshare/server/libs/httpworker" +) + +func (srv *SrvShare) FileInfoHandler(res http.ResponseWriter, req *http.Request) { + tokenStr := srv.Http.GetCookie(req.Cookies(), srv.Conf.KeyToken) + if !srv.Walls.PassIpLimit(GetRemoteIp(req.RemoteAddr)) || + !srv.Walls.PassLoginCheck(tokenStr, req) { + srv.Http.Fill(httputil.Err429, res) + return + } + + todo := func(res http.ResponseWriter, req *http.Request) interface{} { return httputil.Err404 } + switch req.Method { + case http.MethodGet: + todo = srv.List + case http.MethodDelete: + todo = srv.Del + case http.MethodPatch: + act := req.FormValue(srv.Conf.KeyAct) + switch act { + case srv.Conf.ActShadowId: + todo = srv.ShadowId + case srv.Conf.ActPublishId: + todo = srv.PublishId + case srv.Conf.ActSetDownLimit: + todo = srv.SetDownLimit + case srv.Conf.ActAddLocalFiles: + todo = srv.AddLocalFiles + default: + srv.Http.Fill(httputil.Err404, res) + return + } + default: + srv.Http.Fill(httputil.Err404, res) + return + } + + ack := make(chan error, 1) + ok := srv.WorkerPool.Put(&httpworker.Task{ + Ack: ack, + Do: srv.Wrap(todo), + Res: res, + Req: req, + }) + if !ok { + srv.Http.Fill(httputil.Err503, res) + } + + execErr := srv.WorkerPool.IsInTime(ack, time.Duration(srv.Conf.Timeout)*time.Millisecond) + if srv.Err.IsErr(execErr) { + srv.Http.Fill(httputil.Err500, res) + } +} + +type ResInfos struct { + List []*fileidx.FileInfo +} + +func (srv *SrvShare) List(res http.ResponseWriter, req *http.Request) interface{} { + if !srv.Walls.PassOpLimit(srv.Conf.AllUsers, srv.Conf.OpIdGetFInfo) { + return httputil.Err429 + } + + return srv.list() +} + +func (srv *SrvShare) list() interface{} { + infos := make([]*fileidx.FileInfo, 0) + for _, info := range srv.Index.List() { + infos = append(infos, info) + } + + return &ResInfos{List: infos} +} + +func (srv *SrvShare) Del(res http.ResponseWriter, req *http.Request) interface{} { + shareId := req.FormValue(srv.Conf.KeyShareId) + if !srv.Walls.PassOpLimit(shareId, srv.Conf.OpIdDelFInfo) { + return httputil.Err504 + } + + return srv.del(shareId) +} + +func (srv *SrvShare) del(shareId string) interface{} { + if !srv.IsValidShareId(shareId) { + return httputil.Err400 + } + + fileInfo, found := srv.Index.Get(shareId) + if !found { + return httputil.Err404 + } + + srv.Index.Del(shareId) + fullPath := filepath.Join(srv.Conf.PathLocal, fileInfo.PathLocal) + if !srv.Fs.DelFile(fullPath) { + // TODO: may log file name because file not exist or delete is not authenticated + return httputil.Err500 + } + + return httputil.Ok200 +} + +func (srv *SrvShare) ShadowId(res http.ResponseWriter, req *http.Request) interface{} { + if !srv.Walls.PassOpLimit(srv.Conf.AllUsers, srv.Conf.OpIdOpFInfo) { + return httputil.Err429 + } + + shareId := req.FormValue(srv.Conf.KeyShareId) + return srv.shadowId(shareId) +} + +func (srv *SrvShare) shadowId(shareId string) interface{} { + if !srv.IsValidShareId(shareId) { + return httputil.Err400 + } + + info, found := srv.Index.Get(shareId) + if !found { + return httputil.Err404 + } + + secretId := srv.Encryptor.Encrypt( + []byte(fmt.Sprintf("%s%s", info.PathLocal, genPwd())), + ) + if !srv.Index.SetId(info.Id, secretId) { + return httputil.Err412 + } + + return &ShareInfo{ShareId: secretId} +} + +func (srv *SrvShare) PublishId(res http.ResponseWriter, req *http.Request) interface{} { + if !srv.Walls.PassOpLimit(srv.Conf.AllUsers, srv.Conf.OpIdOpFInfo) { + return httputil.Err429 + } + + shareId := req.FormValue(srv.Conf.KeyShareId) + return srv.publishId(shareId) +} + +func (srv *SrvShare) publishId(shareId string) interface{} { + if !srv.IsValidShareId(shareId) { + return httputil.Err400 + } + + info, found := srv.Index.Get(shareId) + if !found { + return httputil.Err404 + } + + publicId := srv.Encryptor.Encrypt([]byte(info.PathLocal)) + if !srv.Index.SetId(info.Id, publicId) { + return httputil.Err412 + } + + return &ShareInfo{ShareId: publicId} +} + +func (srv *SrvShare) SetDownLimit(res http.ResponseWriter, req *http.Request) interface{} { + if !srv.Walls.PassOpLimit(srv.Conf.AllUsers, srv.Conf.OpIdOpFInfo) { + return httputil.Err429 + } + + shareId := req.FormValue(srv.Conf.KeyShareId) + downLimit64, downLimitParseErr := strconv.ParseInt(req.FormValue(srv.Conf.KeyDownLimit), 10, 32) + downLimit := int(downLimit64) + if srv.Err.IsErr(downLimitParseErr) { + return httputil.Err400 + } + + return srv.setDownLimit(shareId, downLimit) +} + +func (srv *SrvShare) setDownLimit(shareId string, downLimit int) interface{} { + if !srv.IsValidShareId(shareId) || !srv.IsValidDownLimit(downLimit) { + return httputil.Err400 + } + + if !srv.Index.SetDownLimit(shareId, downLimit) { + return httputil.Err404 + } + return httputil.Ok200 +} + +func (srv *SrvShare) AddLocalFiles(res http.ResponseWriter, req *http.Request) interface{} { + return srv.AddLocalFilesImp() +} + +func (srv *SrvShare) AddLocalFilesImp() interface{} { + infos, err := srv.Fs.Readdir(srv.Conf.PathLocal, srv.Conf.LocalFileLimit) + if srv.Err.IsErr(err) { + panic(fmt.Sprintf("fail to readdir: %v", err)) + } + + for _, info := range infos { + info.DownLimit = srv.Conf.DownLimit + info.State = fileidx.StateDone + info.PathLocal = info.PathLocal + info.Id = srv.Encryptor.Encrypt([]byte(info.PathLocal)) + + addRet := srv.Index.Add(info) + switch { + case addRet == 0 || addRet == -1: + // TODO: return files not added + continue + case addRet == 1: + break + default: + return httputil.Err500 + } + } + + return httputil.Ok200 +} + +func genPwd() string { + return fmt.Sprintf("%d%d%d%d", rand.Intn(10), rand.Intn(10), rand.Intn(10), rand.Intn(10)) +} diff --git a/server/apis/file_info_test.go b/server/apis/file_info_test.go new file mode 100644 index 0000000..67733cb --- /dev/null +++ b/server/apis/file_info_test.go @@ -0,0 +1,584 @@ +package apis + +import ( + "os" + "path/filepath" + "testing" +) + +import ( + "quickshare/server/libs/cfg" + "quickshare/server/libs/errutil" + "quickshare/server/libs/fileidx" + "quickshare/server/libs/httputil" + "quickshare/server/libs/logutil" +) + +const mockShadowId = "shadowId" +const mockPublicId = "publicId" + +func initServiceForFileInfoTest( + config *cfg.Config, + indexMap map[string]*fileidx.FileInfo, + useShadowEnc bool, + localFileInfos []*fileidx.FileInfo, +) *SrvShare { + setIndex := func(srv *SrvShare) { + srv.Index = fileidx.NewMemFileIndexWithMap(len(indexMap), indexMap) + } + + setFs := func(srv *SrvShare) { + srv.Fs = &stubFsUtil{MockLocalFileInfos: localFileInfos} + } + + logger := logutil.NewSlog(os.Stdout, config.AppName) + setLog := func(srv *SrvShare) { + srv.Log = logger + } + + errChecker := errutil.NewErrChecker(!config.Production, logger) + setErr := func(srv *SrvShare) { + srv.Err = errChecker + } + + var setEncryptor AddDep + if useShadowEnc { + setEncryptor = func(srv *SrvShare) { + srv.Encryptor = &stubEncryptor{MockResult: mockShadowId} + } + } else { + setEncryptor = func(srv *SrvShare) { + srv.Encryptor = &stubEncryptor{MockResult: mockPublicId} + } + } + + return InitSrvShare(config, setIndex, setFs, setEncryptor, setLog, setErr) +} + +func TestList(t *testing.T) { + conf := cfg.NewConfig() + conf.Production = false + + type Output struct { + IndexMap map[string]*fileidx.FileInfo + } + type TestCase struct { + Desc string + Output + } + + testCases := []TestCase{ + TestCase{ + Desc: "success", + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + }, + "1": &fileidx.FileInfo{ + Id: "1", + }, + }, + }, + }, + } + + for _, testCase := range testCases { + srv := initServiceForFileInfoTest(conf, testCase.Output.IndexMap, true, []*fileidx.FileInfo{}) + response := srv.list() + resInfos := response.(*ResInfos) + + for _, info := range resInfos.List { + infoFromSrv, found := srv.Index.Get(info.Id) + if !found || infoFromSrv.Id != info.Id { + t.Fatalf("list: file infos are not identical") + } + } + + if len(resInfos.List) != len(srv.Index.List()) { + t.Fatalf("list: file infos are not identical") + } + } +} + +func TestDel(t *testing.T) { + conf := cfg.NewConfig() + conf.Production = false + + type Init struct { + IndexMap map[string]*fileidx.FileInfo + } + type Input struct { + ShareId string + } + type Output struct { + IndexMap map[string]*fileidx.FileInfo + Response httputil.MsgRes + } + type TestCase struct { + Desc string + Init + Input + Output + } + + testCases := []TestCase{ + TestCase{ + Desc: "success", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + }, + "1": &fileidx.FileInfo{ + Id: "1", + }, + }, + }, + Input: Input{ + ShareId: "0", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + "1": &fileidx.FileInfo{ + Id: "1", + }, + }, + Response: httputil.Ok200, + }, + }, + TestCase{ + Desc: "not found", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{ + "1": &fileidx.FileInfo{ + Id: "1", + }, + }, + }, + Input: Input{ + ShareId: "0", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + "1": &fileidx.FileInfo{ + Id: "1", + }, + }, + Response: httputil.Err404, + }, + }, + } + + for _, testCase := range testCases { + srv := initServiceForFileInfoTest(conf, testCase.Init.IndexMap, true, []*fileidx.FileInfo{}) + response := srv.del(testCase.ShareId) + res := response.(httputil.MsgRes) + + if !sameMap(srv.Index.List(), testCase.Output.IndexMap) { + t.Fatalf("del: index incorrect") + } + + if res != testCase.Output.Response { + t.Fatalf("del: response incorrect got: %v, want: %v", res, testCase.Output.Response) + } + } +} + +func TestShadowId(t *testing.T) { + conf := cfg.NewConfig() + conf.Production = false + + type Init struct { + IndexMap map[string]*fileidx.FileInfo + } + type Input struct { + ShareId string + } + type Output struct { + IndexMap map[string]*fileidx.FileInfo + Response interface{} + } + type TestCase struct { + Desc string + Init + Input + Output + } + + testCases := []TestCase{ + TestCase{ + Desc: "success", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + }, + }, + }, + Input: Input{ + ShareId: "0", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + mockShadowId: &fileidx.FileInfo{ + Id: mockShadowId, + }, + }, + Response: &ShareInfo{ + ShareId: mockShadowId, + }, + }, + }, + TestCase{ + Desc: "original id not exists", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{}, + }, + Input: Input{ + ShareId: "0", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{}, + Response: httputil.Err404, + }, + }, + TestCase{ + Desc: "dest id exists", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + }, + mockShadowId: &fileidx.FileInfo{ + Id: mockShadowId, + }, + }, + }, + Input: Input{ + ShareId: "0", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + }, + mockShadowId: &fileidx.FileInfo{ + Id: mockShadowId, + }, + }, + Response: httputil.Err412, + }, + }, + } + + for _, testCase := range testCases { + srv := initServiceForFileInfoTest(conf, testCase.Init.IndexMap, true, []*fileidx.FileInfo{}) + response := srv.shadowId(testCase.ShareId) + + switch response.(type) { + case *ShareInfo: + res := response.(*ShareInfo) + + if !sameMap(srv.Index.List(), testCase.Output.IndexMap) { + info, found := srv.Index.Get(mockShadowId) + t.Fatalf( + "shadowId: index incorrect got %v found: %v want %v", + info, + found, + testCase.Output.IndexMap[mockShadowId], + ) + } + + if res.ShareId != mockShadowId { + t.Fatalf("shadowId: mockId incorrect") + } + + case httputil.MsgRes: + res := response.(httputil.MsgRes) + + if !sameMap(srv.Index.List(), testCase.Output.IndexMap) { + t.Fatalf("shadowId: map not identical") + } + + if res != testCase.Output.Response { + t.Fatalf("shadowId: response incorrect") + } + default: + t.Fatalf("shadowId: return type not found") + } + } +} + +func TestPublishId(t *testing.T) { + conf := cfg.NewConfig() + conf.Production = false + + type Init struct { + IndexMap map[string]*fileidx.FileInfo + } + type Input struct { + ShareId string + } + type Output struct { + IndexMap map[string]*fileidx.FileInfo + Response interface{} + } + type TestCase struct { + Desc string + Init + Input + Output + } + + testCases := []TestCase{ + TestCase{ + Desc: "success", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{ + mockShadowId: &fileidx.FileInfo{ + Id: mockShadowId, + }, + }, + }, + Input: Input{ + ShareId: mockShadowId, + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + mockPublicId: &fileidx.FileInfo{ + Id: mockPublicId, + }, + }, + Response: &ShareInfo{ + ShareId: mockPublicId, + }, + }, + }, + TestCase{ + Desc: "original id not exists", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{}, + }, + Input: Input{ + ShareId: "0", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{}, + Response: httputil.Err404, + }, + }, + TestCase{ + Desc: "dest id exists", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{ + mockShadowId: &fileidx.FileInfo{ + Id: mockShadowId, + }, + mockPublicId: &fileidx.FileInfo{ + Id: mockPublicId, + }, + }, + }, + Input: Input{ + ShareId: mockShadowId, + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + mockShadowId: &fileidx.FileInfo{ + Id: mockShadowId, + }, + mockPublicId: &fileidx.FileInfo{ + Id: mockPublicId, + }, + }, + Response: httputil.Err412, + }, + }, + } + + for _, testCase := range testCases { + srv := initServiceForFileInfoTest(conf, testCase.Init.IndexMap, false, []*fileidx.FileInfo{}) + response := srv.publishId(testCase.ShareId) + + switch response.(type) { + case *ShareInfo: + res := response.(*ShareInfo) + + if !sameMap(srv.Index.List(), testCase.Output.IndexMap) { + info, found := srv.Index.Get(mockPublicId) + t.Fatalf( + "shadowId: index incorrect got %v found: %v want %v", + info, + found, + testCase.Output.IndexMap[mockPublicId], + ) + } + + if res.ShareId != mockPublicId { + t.Fatalf("shadowId: mockId incorrect", res.ShareId, mockPublicId) + } + + case httputil.MsgRes: + res := response.(httputil.MsgRes) + + if !sameMap(srv.Index.List(), testCase.Output.IndexMap) { + t.Fatalf("shadowId: map not identical") + } + + if res != testCase.Output.Response { + t.Fatalf("shadowId: response incorrect got: %v want: %v", res, testCase.Output.Response) + } + default: + t.Fatalf("shadowId: return type not found") + } + } +} + +func TestSetDownLimit(t *testing.T) { + conf := cfg.NewConfig() + conf.Production = false + mockDownLimit := 100 + + type Init struct { + IndexMap map[string]*fileidx.FileInfo + } + type Input struct { + ShareId string + DownLimit int + } + type Output struct { + IndexMap map[string]*fileidx.FileInfo + Response httputil.MsgRes + } + type TestCase struct { + Desc string + Init + Input + Output + } + + testCases := []TestCase{ + TestCase{ + Desc: "success", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + }, + }, + }, + Input: Input{ + ShareId: "0", + DownLimit: mockDownLimit, + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + "0": &fileidx.FileInfo{ + Id: "0", + DownLimit: mockDownLimit, + }, + }, + Response: httputil.Ok200, + }, + }, + TestCase{ + Desc: "not found", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{}, + }, + Input: Input{ + ShareId: "0", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{}, + Response: httputil.Err404, + }, + }, + } + + for _, testCase := range testCases { + srv := initServiceForFileInfoTest(conf, testCase.Init.IndexMap, true, []*fileidx.FileInfo{}) + response := srv.setDownLimit(testCase.ShareId, mockDownLimit) + res := response.(httputil.MsgRes) + + if !sameMap(srv.Index.List(), testCase.Output.IndexMap) { + info, _ := srv.Index.Get(testCase.ShareId) + t.Fatalf( + "setDownLimit: index incorrect got: %v want: %v", + info, + testCase.Output.IndexMap[testCase.ShareId], + ) + } + + if res != testCase.Output.Response { + t.Fatalf("setDownLimit: response incorrect got: %v, want: %v", res, testCase.Output.Response) + } + } +} + +func TestAddLocalFiles(t *testing.T) { + conf := cfg.NewConfig() + conf.Production = false + + type Init struct { + Infos []*fileidx.FileInfo + } + type Output struct { + IndexMap map[string]*fileidx.FileInfo + Response httputil.MsgRes + } + type TestCase struct { + Desc string + Init + Output + } + + testCases := []TestCase{ + TestCase{ + Desc: "success", + Init: Init{ + Infos: []*fileidx.FileInfo{ + &fileidx.FileInfo{ + Id: "", + DownLimit: 0, + ModTime: 13, + PathLocal: "filename1", + State: "", + Uploaded: 13, + }, + }, + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + mockPublicId: &fileidx.FileInfo{ + Id: mockPublicId, + DownLimit: conf.DownLimit, + ModTime: 13, + PathLocal: filepath.Join(conf.PathLocal, "filename1"), + State: fileidx.StateDone, + Uploaded: 13, + }, + }, + }, + }, + } + + for _, testCase := range testCases { + srv := initServiceForFileInfoTest(conf, testCase.Output.IndexMap, false, testCase.Init.Infos) + response := srv.AddLocalFilesImp() + res := response.(httputil.MsgRes) + + if res.Code != 200 { + t.Fatalf("addLocalFiles: code not correct") + } + + if !sameMap(srv.Index.List(), testCase.Output.IndexMap) { + t.Fatalf( + "addLocalFiles: indexes not identical got: %v want: %v", + srv.Index.List(), + testCase.Output.IndexMap, + ) + } + } +} diff --git a/server/apis/service.go b/server/apis/service.go new file mode 100644 index 0000000..9377a88 --- /dev/null +++ b/server/apis/service.go @@ -0,0 +1,145 @@ +package apis + +import ( + "log" + "net/http" + "os" + "strings" +) + +import ( + "quickshare/server/libs/cfg" + "quickshare/server/libs/encrypt" + "quickshare/server/libs/errutil" + "quickshare/server/libs/fileidx" + "quickshare/server/libs/fsutil" + "quickshare/server/libs/httputil" + "quickshare/server/libs/httpworker" + "quickshare/server/libs/limiter" + "quickshare/server/libs/logutil" + "quickshare/server/libs/qtube" + "quickshare/server/libs/walls" +) + +type AddDep func(*SrvShare) + +func NewSrvShare(config *cfg.Config) *SrvShare { + logger := logutil.NewSlog(os.Stdout, config.AppName) + setLog := func(srv *SrvShare) { + srv.Log = logger + } + + errChecker := errutil.NewErrChecker(!config.Production, logger) + setErr := func(srv *SrvShare) { + srv.Err = errChecker + } + + setWorkerPool := func(srv *SrvShare) { + workerPoolSize := config.WorkerPoolSize + taskQueueSize := config.TaskQueueSize + srv.WorkerPool = httpworker.NewWorkerPool(workerPoolSize, taskQueueSize, logger) + } + + setWalls := func(srv *SrvShare) { + encrypterMaker := encrypt.JwtEncrypterMaker + ipLimiter := limiter.NewRateLimiter( + config.LimiterCap, + config.LimiterTtl, + config.LimiterCyc, + config.BucketCap, + config.SpecialCaps, + ) + opLimiter := limiter.NewRateLimiter( + config.LimiterCap, + config.LimiterTtl, + config.LimiterCyc, + config.BucketCap, + config.SpecialCaps, + ) + srv.Walls = walls.NewAccessWalls(config, ipLimiter, opLimiter, encrypterMaker) + } + + setIndex := func(srv *SrvShare) { + srv.Index = fileidx.NewMemFileIndex(config.MaxShares) + } + + fs := fsutil.NewSimpleFs(errChecker) + setFs := func(srv *SrvShare) { + srv.Fs = fs + } + + setDownloader := func(srv *SrvShare) { + srv.Downloader = qtube.NewQTube( + config.PathLocal, + config.MaxDownBytesPerSec, + config.MaxRangeLength, + fs, + ) + } + + setEncryptor := func(srv *SrvShare) { + srv.Encryptor = &encrypt.HmacEncryptor{Key: config.SecretKeyByte} + } + + setHttp := func(srv *SrvShare) { + srv.Http = &httputil.QHttpUtil{ + CookieDomain: config.CookieDomain, + CookieHttpOnly: config.CookieHttpOnly, + CookieMaxAge: config.CookieMaxAge, + CookiePath: config.CookiePath, + CookieSecure: config.CookieSecure, + Err: errChecker, + } + } + + return InitSrvShare(config, setIndex, setWalls, setWorkerPool, setFs, setDownloader, setEncryptor, setLog, setErr, setHttp) +} + +func InitSrvShare(config *cfg.Config, addDeps ...AddDep) *SrvShare { + srv := &SrvShare{} + srv.Conf = config + for _, addDep := range addDeps { + addDep(srv) + } + + if !srv.Fs.MkdirAll(srv.Conf.PathLocal, os.FileMode(0775)) { + panic("fail to make ./files/ folder") + } + + if res := srv.AddLocalFilesImp(); res != httputil.Ok200 { + panic("fail to add local files") + } + + return srv +} + +type SrvShare struct { + Conf *cfg.Config + Encryptor encrypt.Encryptor + Err errutil.ErrUtil + Downloader qtube.Downloader + Http httputil.HttpUtil + Index fileidx.FileIndex + Fs fsutil.FsUtil + Log logutil.LogUtil + Walls walls.Walls + WorkerPool httpworker.Workers +} + +func (srv *SrvShare) Wrap(serviceFunc httpworker.ServiceFunc) httpworker.DoFunc { + return func(res http.ResponseWriter, req *http.Request) { + body := serviceFunc(res, req) + + if body != nil && body != 0 && srv.Http.Fill(body, res) <= 0 { + log.Println("Wrap: fail to fill body", body, res) + } + } +} + +func GetRemoteIp(addr string) string { + addrParts := strings.Split(addr, ":") + if len(addrParts) > 0 { + return addrParts[0] + } + return "unknown ip" +} diff --git a/server/apis/test_helper.go b/server/apis/test_helper.go new file mode 100644 index 0000000..3d4d5de --- /dev/null +++ b/server/apis/test_helper.go @@ -0,0 +1,117 @@ +package apis + +import ( + "fmt" + "io" + "net/http" + "os" +) + +import ( + "quickshare/server/libs/fileidx" + "quickshare/server/libs/qtube" +) + +type stubFsUtil struct { + MockLocalFileInfos []*fileidx.FileInfo + MockFile *qtube.StubFile +} + +var expectCreateFileName = "" + +func (fs *stubFsUtil) CreateFile(fileName string) error { + if fileName != expectCreateFileName { + panic( + fmt.Sprintf("CreateFile: got: %s expect: %s", fileName, expectCreateFileName), + ) + } + return nil +} + +func (fs *stubFsUtil) CopyChunkN(fullPath string, chunk io.Reader, start int64, len int64) bool { + return true +} + +func (fs *stubFsUtil) ServeFile(res http.ResponseWriter, req *http.Request, fileName string) { + return +} + +func (fs *stubFsUtil) DelFile(fullPath string) bool { + return true +} + +func (fs *stubFsUtil) MkdirAll(path string, mode os.FileMode) bool { + return true +} + +func (fs *stubFsUtil) Readdir(dirname string, n int) ([]*fileidx.FileInfo, error) { + return fs.MockLocalFileInfos, nil +} + +func (fs *stubFsUtil) Open(filePath string) (qtube.ReadSeekCloser, error) { + return fs.MockFile, nil +} + +type stubWriter struct { + Headers http.Header + Response []byte + StatusCode int +} + +func (w *stubWriter) Header() http.Header { + return w.Headers +} + +func (w *stubWriter) Write(body []byte) (int, error) { + w.Response = append(w.Response, body...) + return len(body), nil +} + +func (w *stubWriter) WriteHeader(statusCode int) { + w.StatusCode = statusCode +} + +type stubDownloader struct { + Content string +} + +func (d stubDownloader) ServeFile(w http.ResponseWriter, r *http.Request, fileInfo *fileidx.FileInfo) error { + _, err := w.Write([]byte(d.Content)) + return err +} + +func sameInfoWithoutTime(info1, info2 *fileidx.FileInfo) bool { + return info1.Id == info2.Id && + info1.DownLimit == info2.DownLimit && + info1.PathLocal == info2.PathLocal && + info1.State == info2.State && + info1.Uploaded == info2.Uploaded +} + +func sameMap(map1, map2 map[string]*fileidx.FileInfo) bool { + for key, info1 := range map1 { + info2, found := map2[key] + if !found || !sameInfoWithoutTime(info1, info2) { + fmt.Printf("infos are not same: \n%v \n%v", info1, info2) + return false + } + } + + for key, info2 := range map2 { + info1, found := map1[key] + if !found || !sameInfoWithoutTime(info1, info2) { + fmt.Printf("infos are not same: \n%v \n%v", info1, info2) + return false + } + } + + return true +} + +type stubEncryptor struct { + MockResult string +} + +func (enc *stubEncryptor) Encrypt(content []byte) string { + return enc.MockResult +} diff --git a/server/apis/upload.go b/server/apis/upload.go new file mode 100644 index 0000000..2cd2a78 --- /dev/null +++ b/server/apis/upload.go @@ -0,0 +1,253 @@ +package apis + +import ( + "io" + "net/http" + "path/filepath" + "strconv" + "time" +) + +import ( + "quickshare/server/libs/encrypt" + "quickshare/server/libs/fileidx" + "quickshare/server/libs/fsutil" + httpUtil "quickshare/server/libs/httputil" + worker "quickshare/server/libs/httpworker" +) + +const DefaultId = "0" + +type ByteRange struct { + ShareId string + Start int64 + Length int64 +} + +type ShareInfo struct { + ShareId string +} + +func (srv *SrvShare) StartUploadHandler(res http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + srv.Http.Fill(httpUtil.Err404, res) + return + } + + tokenStr := srv.Http.GetCookie(req.Cookies(), srv.Conf.KeyToken) + ipPass := srv.Walls.PassIpLimit(GetRemoteIp(req.RemoteAddr)) + loginPass := srv.Walls.PassLoginCheck(tokenStr, req) + opPass := srv.Walls.PassOpLimit(GetRemoteIp(req.RemoteAddr), srv.Conf.OpIdUpload) + if !ipPass || !loginPass || !opPass { + srv.Http.Fill(httpUtil.Err429, res) + return + } + + ack := make(chan error, 1) + ok := srv.WorkerPool.Put(&worker.Task{ + Ack: ack, + Do: srv.Wrap(srv.StartUpload), + Res: res, + Req: req, + }) + if !ok { + srv.Http.Fill(httpUtil.Err503, res) + } + + execErr := srv.WorkerPool.IsInTime(ack, time.Duration(srv.Conf.Timeout)*time.Millisecond) + if srv.Err.IsErr(execErr) { + srv.Http.Fill(httpUtil.Err500, res) + } +} + +func (srv *SrvShare) UploadHandler(res http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + srv.Http.Fill(httpUtil.Err404, res) + return + } + + tokenStr := srv.Http.GetCookie(req.Cookies(), srv.Conf.KeyToken) + ipPass := srv.Walls.PassIpLimit(GetRemoteIp(req.RemoteAddr)) + loginPass := srv.Walls.PassLoginCheck(tokenStr, req) + opPass := srv.Walls.PassOpLimit(GetRemoteIp(req.RemoteAddr), srv.Conf.OpIdUpload) + if !ipPass || !loginPass || !opPass { + srv.Http.Fill(httpUtil.Err429, res) + return + } + + multiFormErr := req.ParseMultipartForm(srv.Conf.ParseFormBufSize) + if srv.Err.IsErr(multiFormErr) { + srv.Http.Fill(httpUtil.Err400, res) + return + } + + srv.Log.Println("form", req.Form) + srv.Log.Println("pform", req.PostForm) + srv.Log.Println("mform", req.MultipartForm) + ack := make(chan error, 1) + ok := srv.WorkerPool.Put(&worker.Task{ + Ack: ack, + Do: srv.Wrap(srv.Upload), + Res: res, + Req: req, + }) + if !ok { + srv.Http.Fill(httpUtil.Err503, res) + } + + execErr := srv.WorkerPool.IsInTime(ack, time.Duration(srv.Conf.Timeout)*time.Millisecond) + if srv.Err.IsErr(execErr) { + srv.Http.Fill(httpUtil.Err500, res) + } +} + +func (srv *SrvShare) FinishUploadHandler(res http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + srv.Http.Fill(httpUtil.Err404, res) + return + } + + tokenStr := srv.Http.GetCookie(req.Cookies(), srv.Conf.KeyToken) + ipPass := srv.Walls.PassIpLimit(GetRemoteIp(req.RemoteAddr)) + loginPass := srv.Walls.PassLoginCheck(tokenStr, req) + opPass := srv.Walls.PassOpLimit(GetRemoteIp(req.RemoteAddr), srv.Conf.OpIdUpload) + if !ipPass || !loginPass || !opPass { + srv.Http.Fill(httpUtil.Err429, res) + return + } + + ack := make(chan error, 1) + ok := srv.WorkerPool.Put(&worker.Task{ + Ack: ack, + Do: srv.Wrap(srv.FinishUpload), + Res: res, + Req: req, + }) + if !ok { + srv.Http.Fill(httpUtil.Err503, res) + } + + execErr := srv.WorkerPool.IsInTime(ack, time.Duration(srv.Conf.Timeout)*time.Millisecond) + if srv.Err.IsErr(execErr) { + srv.Http.Fill(httpUtil.Err500, res) + } +} + +func (srv *SrvShare) StartUpload(res http.ResponseWriter, req *http.Request) interface{} { + return srv.startUpload(req.FormValue(srv.Conf.KeyFileName)) +} + +func (srv *SrvShare) startUpload(fileName string) interface{} { + if !IsValidFileName(fileName) { + return httpUtil.Err400 + } + + id := DefaultId + if srv.Conf.Production { + id = genInfoId(fileName, srv.Conf.SecretKeyByte) + } + + info := &fileidx.FileInfo{ + Id: id, + DownLimit: srv.Conf.DownLimit, + ModTime: time.Now().UnixNano(), + PathLocal: fileName, + Uploaded: 0, + State: fileidx.StateStarted, + } + + switch srv.Index.Add(info) { + case 0: + // go on + case -1: + return httpUtil.Err412 + case 1: + return httpUtil.Err500 // TODO: use correct status code + default: + srv.Index.Del(id) + return httpUtil.Err500 + } + + fullPath := filepath.Join(srv.Conf.PathLocal, info.PathLocal) + createFileErr := srv.Fs.CreateFile(fullPath) + switch { + case createFileErr == fsutil.ErrExists: + srv.Index.Del(id) + return httpUtil.Err412 + case createFileErr == fsutil.ErrUnknown: + srv.Index.Del(id) + return httpUtil.Err500 + default: + srv.Index.SetState(id, fileidx.StateUploading) + return &ByteRange{ + ShareId: id, + Start: 0, + Length: srv.Conf.MaxUpBytesPerSec, + } + } +} + +func (srv *SrvShare) Upload(res http.ResponseWriter, req *http.Request) interface{} { + shareId := req.FormValue(srv.Conf.KeyShareId) + start, startErr := strconv.ParseInt(req.FormValue(srv.Conf.KeyStart), 10, 64) + length, lengthErr := strconv.ParseInt(req.FormValue(srv.Conf.KeyLen), 10, 64) + chunk, _, chunkErr := req.FormFile(srv.Conf.KeyChunk) + + if srv.Err.IsErr(startErr) || + srv.Err.IsErr(lengthErr) || + srv.Err.IsErr(chunkErr) { + return httpUtil.Err400 + } + + return srv.upload(shareId, start, length, chunk) +} + +func (srv *SrvShare) upload(shareId string, start int64, length int64, chunk io.Reader) interface{} { + if !srv.IsValidShareId(shareId) { + return httpUtil.Err400 + } + + fileInfo, found := srv.Index.Get(shareId) + if !found { + return httpUtil.Err404 + } + + if !srv.IsValidStart(start, fileInfo.Uploaded) || !srv.IsValidLength(length) { + return httpUtil.Err400 + } + + fullPath := filepath.Join(srv.Conf.PathLocal, fileInfo.PathLocal) + if !srv.Fs.CopyChunkN(fullPath, chunk, start, length) { + return httpUtil.Err500 + } + + if srv.Index.IncrUploaded(shareId, length) == 0 { + return httpUtil.Err404 + } + + return &ByteRange{ + ShareId: shareId, + Start: start + length, + Length: srv.Conf.MaxUpBytesPerSec, + } +} + +func (srv *SrvShare) FinishUpload(res http.ResponseWriter, req *http.Request) interface{} { + shareId := req.FormValue(srv.Conf.KeyShareId) + return srv.finishUpload(shareId) +} + +func (srv *SrvShare) finishUpload(shareId string) interface{} { + if !srv.Index.SetState(shareId, fileidx.StateDone) { + return httpUtil.Err404 + } + + return &ShareInfo{ + ShareId: shareId, + } +} + +func genInfoId(content string, key []byte) string { + encrypter := encrypt.HmacEncryptor{Key: key} + return encrypter.Encrypt([]byte(content)) +} diff --git a/server/apis/upload_test.go b/server/apis/upload_test.go new file mode 100644 index 0000000..57c1e95 --- /dev/null +++ b/server/apis/upload_test.go @@ -0,0 +1,368 @@ +package apis + +import ( + "fmt" + "io" + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +import ( + "quickshare/server/libs/cfg" + "quickshare/server/libs/encrypt" + "quickshare/server/libs/errutil" + "quickshare/server/libs/fileidx" + "quickshare/server/libs/httputil" + "quickshare/server/libs/httpworker" + "quickshare/server/libs/limiter" + "quickshare/server/libs/logutil" + "quickshare/server/libs/walls" +) + +const testCap = 3 + +func initServiceForUploadTest(config *cfg.Config, indexMap map[string]*fileidx.FileInfo) *SrvShare { + logger := logutil.NewSlog(os.Stdout, config.AppName) + setLog := func(srv *SrvShare) { + srv.Log = logger + } + + setWorkerPool := func(srv *SrvShare) { + workerPoolSize := config.WorkerPoolSize + taskQueueSize := config.TaskQueueSize + srv.WorkerPool = httpworker.NewWorkerPool(workerPoolSize, taskQueueSize, logger) + } + + setWalls := func(srv *SrvShare) { + encrypterMaker := encrypt.JwtEncrypterMaker + ipLimiter := limiter.NewRateLimiter(config.LimiterCap, config.LimiterTtl, config.LimiterCyc, config.BucketCap, map[int16]int16{}) + opLimiter := limiter.NewRateLimiter(config.LimiterCap, config.LimiterTtl, config.LimiterCyc, config.BucketCap, map[int16]int16{}) + srv.Walls = walls.NewAccessWalls(config, ipLimiter, opLimiter, encrypterMaker) + } + + setIndex := func(srv *SrvShare) { + srv.Index = fileidx.NewMemFileIndexWithMap(len(indexMap)+testCap, indexMap) + } + + setFs := func(srv *SrvShare) { + srv.Fs = &stubFsUtil{} + } + + setEncryptor := func(srv *SrvShare) { + srv.Encryptor = &encrypt.HmacEncryptor{Key: config.SecretKeyByte} + } + + errChecker := errutil.NewErrChecker(!config.Production, logger) + setErr := func(srv *SrvShare) { + srv.Err = errChecker + } + + return InitSrvShare(config, setIndex, setWalls, setWorkerPool, setFs, setEncryptor, setLog, setErr) +} + +func TestStartUpload(t *testing.T) { + conf := cfg.NewConfig() + conf.Production = false + + type Init struct { + IndexMap map[string]*fileidx.FileInfo + } + type Input struct { + FileName string + } + type Output struct { + Response interface{} + IndexMap map[string]*fileidx.FileInfo + } + type testCase struct { + Desc string + Init + Input + Output + } + + testCases := []testCase{ + testCase{ + Desc: "invalid file name", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{}, + }, + Input: Input{ + FileName: "", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{}, + Response: httputil.Err400, + }, + }, + testCase{ + Desc: "succeed to start uploading", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{}, + }, + Input: Input{ + FileName: "filename", + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + DefaultId: &fileidx.FileInfo{ + Id: DefaultId, + DownLimit: conf.DownLimit, + ModTime: time.Now().UnixNano(), + PathLocal: "filename", + Uploaded: 0, + State: fileidx.StateUploading, + }, + }, + Response: &ByteRange{ + ShareId: DefaultId, + Start: 0, + Length: conf.MaxUpBytesPerSec, + }, + }, + }, + } + + for _, testCase := range testCases { + srv := initServiceForUploadTest(conf, testCase.Init.IndexMap) + + // verify CreateFile + expectCreateFileName = filepath.Join(conf.PathLocal, testCase.FileName) + + response := srv.startUpload(testCase.FileName) + + // verify index + if !sameMap(srv.Index.List(), testCase.Output.IndexMap) { + t.Fatalf("startUpload: index not equal got: %v, %v, expect: %v", srv.Index.List(), response, testCase.Output.IndexMap) + } + + // verify response + switch expectRes := testCase.Output.Response.(type) { + case *ByteRange: + res := response.(*ByteRange) + if res.ShareId != expectRes.ShareId || + res.Start != expectRes.Start || + res.Length != expectRes.Length { + t.Fatalf(fmt.Sprintf("startUpload: res=%v expect=%v", res, expectRes)) + } + case httputil.MsgRes: + if response != expectRes { + t.Fatalf(fmt.Sprintf("startUpload: reponse=%v expectRes=%v", response, expectRes)) + } + default: + t.Fatalf(fmt.Sprintf("startUpload: type not found: %T %T", testCase.Output.Response, httputil.Err400)) + } + } +} + +func TestUpload(t *testing.T) { + conf := cfg.NewConfig() + conf.Production = false + + type Init struct { + IndexMap map[string]*fileidx.FileInfo + } + type Input struct { + ShareId string + Start int64 + Len int64 + Chunk io.Reader + } + type Output struct { + IndexMap map[string]*fileidx.FileInfo + Response interface{} + } + type testCase struct { + Desc string + Init + Input + Output + } + + testCases := []testCase{ + testCase{ + Desc: "shareid does not exist", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{}, + }, + Input: Input{ + ShareId: DefaultId, + Start: 0, + Len: 1, + Chunk: strings.NewReader(""), + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{}, + Response: httputil.Err404, + }, + }, + testCase{ + Desc: "succeed", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{ + DefaultId: &fileidx.FileInfo{ + Id: DefaultId, + DownLimit: conf.MaxShares, + PathLocal: "path/filename", + State: fileidx.StateUploading, + Uploaded: 0, + }, + }, + }, + Input: Input{ + ShareId: DefaultId, + Start: 0, + Len: 1, + Chunk: strings.NewReader("a"), + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + DefaultId: &fileidx.FileInfo{ + Id: DefaultId, + DownLimit: conf.MaxShares, + PathLocal: "path/filename", + State: fileidx.StateUploading, + Uploaded: 1, + }, + }, + Response: &ByteRange{ + ShareId: DefaultId, + Start: 1, + Length: conf.MaxUpBytesPerSec, + }, + }, + }, + } + + for _, testCase := range testCases { + srv := initServiceForUploadTest(conf, testCase.Init.IndexMap) + + response := srv.upload( + testCase.Input.ShareId, + testCase.Input.Start, + testCase.Input.Len, + testCase.Input.Chunk, + ) + + // TODO: not verified copyChunk + + // verify index + if !sameMap(srv.Index.List(), testCase.Output.IndexMap) { + t.Fatalf("upload: index not identical got: %v want: %v", srv.Index.List(), testCase.Output.IndexMap) + } + // verify response + switch response.(type) { + case *ByteRange: + br := testCase.Output.Response.(*ByteRange) + res := response.(*ByteRange) + if res.ShareId != br.ShareId || res.Start != br.Start || res.Length != br.Length { + t.Fatalf(fmt.Sprintf("upload: response=%v expectRes=%v", res, br)) + } + default: + if response != testCase.Output.Response { + t.Fatalf(fmt.Sprintf("upload: response=%v expectRes=%v", response, testCase.Output.Response)) + } + } + } +} + +func TestFinishUpload(t *testing.T) { + conf := cfg.NewConfig() + conf.Production = false + + type Init struct { + IndexMap map[string]*fileidx.FileInfo + } + type Input struct { + ShareId string + Start int64 + Len int64 + Chunk io.Reader + } + type Output struct { + IndexMap map[string]*fileidx.FileInfo + Response interface{} + } + type testCase struct { + Desc string + Init + Input + Output + } + + testCases := []testCase{ + testCase{ + Desc: "success", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{ + DefaultId: &fileidx.FileInfo{ + Id: DefaultId, + DownLimit: conf.MaxShares, + PathLocal: "path/filename", + State: fileidx.StateUploading, + Uploaded: 1, + }, + }, + }, + Input: Input{ + ShareId: DefaultId, + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{ + DefaultId: &fileidx.FileInfo{ + Id: DefaultId, + DownLimit: conf.MaxShares, + PathLocal: "path/filename", + State: fileidx.StateDone, + Uploaded: 1, + }, + }, + Response: &ShareInfo{ + ShareId: DefaultId, + }, + }, + }, + testCase{ + Desc: "shareId exists", + Init: Init{ + IndexMap: map[string]*fileidx.FileInfo{}, + }, + Input: Input{ + ShareId: DefaultId, + }, + Output: Output{ + IndexMap: map[string]*fileidx.FileInfo{}, + Response: httputil.Err404, + }, + }, + } + + for _, testCase := range testCases { + srv := initServiceForUploadTest(conf, testCase.Init.IndexMap) + + response := srv.finishUpload(testCase.ShareId) + + if !sameMap(srv.Index.List(), testCase.Output.IndexMap) { + t.Fatalf("finishUpload: index not identical got: %v, want: %v", srv.Index.List(), testCase.Output.IndexMap) + } + + switch res := response.(type) { + case httputil.MsgRes: + expectRes := testCase.Output.Response.(httputil.MsgRes) + if res != expectRes { + t.Fatalf(fmt.Sprintf("finishUpload: reponse=%v expectRes=%v", res, expectRes)) + } + case *ShareInfo: + info, found := testCase.Output.IndexMap[res.ShareId] + if !found || info.State != fileidx.StateDone { + // TODO: should use isValidUrl or better to verify result + t.Fatalf(fmt.Sprintf("finishUpload: share info is not correct: received: %v expect: %v", res.ShareId, testCase.ShareId)) + } + default: + t.Fatalf(fmt.Sprintf("finishUpload: type not found: %T %T", response, testCase.Output.Response)) + } + } +} diff --git a/server/libs/cfg/cfg.go b/server/libs/cfg/cfg.go new file mode 100644 index 0000000..c1272de --- /dev/null +++ b/server/libs/cfg/cfg.go @@ -0,0 +1,251 @@ +package cfg + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net" + "strconv" + "strings" +) + +type Config struct { + AppName string + AdminId string + AdminPwd string + SecretKey string + SecretKeyByte []byte `json:",omitempty"` + // server + Production bool + HostName string + Port int + // performance + MaxUpBytesPerSec int64 + MaxDownBytesPerSec int64 + MaxRangeLength int64 + Timeout int // millisecond + ReadTimeout int + WriteTimeout int + IdleTimeout int + WorkerPoolSize int + TaskQueueSize int + QueueSize int + ParseFormBufSize int64 + MaxHeaderBytes int + DownLimit int + MaxShares int + LocalFileLimit int + // Cookie + CookieDomain string + CookieHttpOnly bool + CookieMaxAge int + CookiePath string + CookieSecure bool + // keys + KeyAdminId string + KeyAdminPwd string + KeyToken string + KeyFileName string + KeyFileSize string + KeyShareId string + KeyStart string + KeyLen string + KeyChunk string + KeyAct string + KeyExpires string + KeyDownLimit string + ActStartUpload string + ActUpload string + ActFinishUpload string + ActLogin string + ActLogout string + ActShadowId string + ActPublishId string + ActSetDownLimit string + ActAddLocalFiles string + // resource id + AllUsers string + // opIds + OpIdIpVisit int16 + OpIdUpload int16 + OpIdDownload int16 + OpIdLogin int16 + OpIdGetFInfo int16 + OpIdDelFInfo int16 + OpIdOpFInfo int16 + // local + PathLocal string + PathLogin string + PathDownloadLogin string + PathDownload string + PathUpload string + PathStartUpload string + PathFinishUpload string + PathFileInfo string + PathClient string + // rate Limiter + LimiterCap int64 + LimiterTtl int32 + LimiterCyc int32 + BucketCap int16 + SpecialCapsStr map[string]int16 + SpecialCaps map[int16]int16 +} + +func NewConfig() *Config { + config := &Config{ + // secrets + AppName: "qs", + AdminId: "admin", + AdminPwd: "qs", + SecretKey: "qs", + SecretKeyByte: []byte("qs"), + // server + Production: true, + HostName: "localhost", + Port: 8888, + // performance + MaxUpBytesPerSec: 500 * 1000, + MaxDownBytesPerSec: 500 * 1000, + MaxRangeLength: 10 * 1024 * 1024, + Timeout: 500, // millisecond, + ReadTimeout: 500, + WriteTimeout: 43200000, + IdleTimeout: 10000, + WorkerPoolSize: 2, + TaskQueueSize: 2, + QueueSize: 2, + ParseFormBufSize: 600, + MaxHeaderBytes: 1 << 15, // 32KB + DownLimit: -1, + MaxShares: 1 << 31, + LocalFileLimit: -1, + // Cookie + CookieDomain: "", + CookieHttpOnly: false, + CookieMaxAge: 3600 * 24 * 30, // one week, + CookiePath: "/", + CookieSecure: false, + // keys + KeyAdminId: "adminid", + KeyAdminPwd: "adminpwd", + KeyToken: "token", + KeyFileName: "fname", + KeyFileSize: "size", + KeyShareId: "shareid", + KeyStart: "start", + KeyLen: "len", + KeyChunk: "chunk", + KeyAct: "act", + KeyExpires: "expires", + KeyDownLimit: "downlimit", + ActStartUpload: "startupload", + ActUpload: "upload", + ActFinishUpload: "finishupload", + ActLogin: "login", + ActLogout: "logout", + ActShadowId: "shadowid", + ActPublishId: "publishid", + ActSetDownLimit: "setdownlimit", + ActAddLocalFiles: "addlocalfiles", + AllUsers: "allusers", + // opIds + OpIdIpVisit: 0, + OpIdUpload: 1, + OpIdDownload: 2, + OpIdLogin: 3, + OpIdGetFInfo: 4, + OpIdDelFInfo: 5, + OpIdOpFInfo: 6, + // local + PathLocal: "files", + PathLogin: "/login", + PathDownloadLogin: "/download-login", + PathDownload: "/download", + PathUpload: "/upload", + PathStartUpload: "/startupload", + PathFinishUpload: "/finishupload", + PathFileInfo: "/fileinfo", + PathClient: "/", + // rate Limiter + LimiterCap: 256, // how many op supported for each user + LimiterTtl: 3600, // second + LimiterCyc: 1, // second + BucketCap: 3, // how many op can do per LimiterCyc sec + SpecialCaps: map[int16]int16{ + 0: 5, // ip + 1: 1, // upload + 2: 1, // download + 3: 1, // login + }, + } + + return config +} + +func NewConfigFrom(path string) *Config { + configBytes, readErr := ioutil.ReadFile(path) + if readErr != nil { + panic(fmt.Sprintf("config file not found: %s", path)) + } + + config := &Config{} + marshalErr := json.Unmarshal(configBytes, config) + + // TODO: look for a better solution + config.SpecialCaps = make(map[int16]int16) + for strKey, value := range config.SpecialCapsStr { + key, parseKeyErr := strconv.ParseInt(strKey, 10, 16) + if parseKeyErr != nil { + panic("fail to parse SpecialCapsStr, its type should be map[int16]int16") + } + config.SpecialCaps[int16(key)] = value + } + + if marshalErr != nil { + panic("config file format is incorrect") + } + + config.SecretKeyByte = []byte(config.SecretKey) + if config.HostName == "" { + hostName, err := GetLocalAddr() + if err != nil { + panic(err) + } + config.HostName = hostName.String() + } + + return config +} + +func GetLocalAddr() (net.IP, error) { + fmt.Println(`config.HostName is empty(""), choose one IP for listening automatically.`) + infs, err := net.Interfaces() + if err != nil { + panic("fail to get net interfaces") + } + + for _, inf := range infs { + if inf.Flags&4 != 4 && !strings.Contains(inf.Name, "docker") { + addrs, err := inf.Addrs() + if err != nil { + panic("fail to get addrs of interface") + } + for _, addr := range addrs { + switch v := addr.(type) { + case *net.IPAddr: + if !strings.Contains(v.IP.String(), ":") { + return v.IP, nil + } + case *net.IPNet: + if !strings.Contains(v.IP.String(), ":") { + return v.IP, nil + } + } + } + } + } + + return nil, errors.New("no addr found") +} diff --git a/server/libs/encrypt/encrypter_hmac.go b/server/libs/encrypt/encrypter_hmac.go new file mode 100644 index 0000000..fc2687d --- /dev/null +++ b/server/libs/encrypt/encrypter_hmac.go @@ -0,0 +1,17 @@ +package encrypt + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" +) + +type HmacEncryptor struct { + Key []byte +} + +func (encryptor *HmacEncryptor) Encrypt(content []byte) string { + mac := hmac.New(sha256.New, encryptor.Key) + mac.Write(content) + return hex.EncodeToString(mac.Sum(nil)) +} diff --git a/server/libs/encrypt/encryptor.go b/server/libs/encrypt/encryptor.go new file mode 100644 index 0000000..e7438c9 --- /dev/null +++ b/server/libs/encrypt/encryptor.go @@ -0,0 +1,5 @@ +package encrypt + +type Encryptor interface { + Encrypt(content []byte) string +} diff --git a/server/libs/encrypt/jwt.go b/server/libs/encrypt/jwt.go new file mode 100644 index 0000000..e54b5cb --- /dev/null +++ b/server/libs/encrypt/jwt.go @@ -0,0 +1,53 @@ +package encrypt + +import ( + "github.com/robbert229/jwt" +) + +func JwtEncrypterMaker(secret string) TokenEncrypter { + return &JwtEncrypter{ + alg: jwt.HmacSha256(secret), + claims: jwt.NewClaim(), + } +} + +type JwtEncrypter struct { + alg jwt.Algorithm + claims *jwt.Claims +} + +func (encrypter *JwtEncrypter) Add(key string, value string) bool { + encrypter.claims.Set(key, value) + return true +} + +func (encrypter *JwtEncrypter) FromStr(token string) bool { + claims, err := encrypter.alg.Decode(token) + // TODO: should return error or error info will lost + if err != nil { + return false + } + + encrypter.claims = claims + return true +} + +func (encrypter *JwtEncrypter) Get(key string) (string, bool) { + iValue, err := encrypter.claims.Get(key) + // TODO: should return error or error info will lost + if err != nil { + return "", false + } + + return iValue.(string), true +} + +func (encrypter *JwtEncrypter) ToStr() (string, bool) { + token, err := encrypter.alg.Encode(encrypter.claims) + + // TODO: should return error or error info will lost + if err != nil { + return "", false + } + return token, true +} diff --git a/server/libs/encrypt/token_encrypter.go b/server/libs/encrypt/token_encrypter.go new file mode 100644 index 0000000..d400163 --- /dev/null +++ b/server/libs/encrypt/token_encrypter.go @@ -0,0 +1,11 @@ +package encrypt + +type EncrypterMaker func(string) TokenEncrypter + +// TODO: name should be Encrypter? +type TokenEncrypter interface { + Add(string, string) bool + FromStr(string) bool + Get(string) (string, bool) + ToStr() (string, bool) +} diff --git a/server/libs/errutil/ettutil.go b/server/libs/errutil/ettutil.go new file mode 100644 index 0000000..24c5831 --- /dev/null +++ b/server/libs/errutil/ettutil.go @@ -0,0 +1,59 @@ +package errutil + +import ( + "os" + "runtime/debug" +) + +import ( + "quickshare/server/libs/logutil" +) + +type ErrUtil interface { + IsErr(err error) bool + IsFatalErr(err error) bool + RecoverPanic() +} + +func NewErrChecker(logStack bool, logger logutil.LogUtil) ErrUtil { + return &ErrChecker{logStack: logStack, log: logger} +} + +type ErrChecker struct { + log logutil.LogUtil + logStack bool +} + +// IsErr checks if error occurs +func (e *ErrChecker) IsErr(err error) bool { + if err != nil { + e.log.Printf("Error:%q\n", err) + if e.logStack { + e.log.Println(debug.Stack()) + } + return true + } + return false +} + +// IsFatalPanic should be used with defer +func (e *ErrChecker) IsFatalErr(fe error) bool { + if fe != nil { + e.log.Printf("Panic:%q", fe) + if e.logStack { + e.log.Println(debug.Stack()) + } + os.Exit(1) + } + return false +} + +// RecoverPanic catchs the panic and logs panic information +func (e *ErrChecker) RecoverPanic() { + if r := recover(); r != nil { + e.log.Printf("Recovered:%v", r) + if e.logStack { + e.log.Println(debug.Stack()) + } + } +} diff --git a/server/libs/fileidx/file_idx.go b/server/libs/fileidx/file_idx.go new file mode 100644 index 0000000..3b96f34 --- /dev/null +++ b/server/libs/fileidx/file_idx.go @@ -0,0 +1,177 @@ +package fileidx + +import ( + "sync" +) + +const ( + // StateStarted = after startUpload before upload + StateStarted = "started" + // StateUploading =after upload before finishUpload + StateUploading = "uploading" + // StateDone = after finishedUpload + StateDone = "done" +) + +type FileInfo struct { + Id string + DownLimit int + ModTime int64 + PathLocal string + State string + Uploaded int64 +} + +type FileIndex interface { + Add(fileInfo *FileInfo) int + Del(id string) + SetId(id string, newId string) bool + SetDownLimit(id string, downLimit int) bool + DecrDownLimit(id string) (int, bool) + SetState(id string, state string) bool + IncrUploaded(id string, uploaded int64) int64 + Get(id string) (*FileInfo, bool) + List() map[string]*FileInfo +} + +func NewMemFileIndex(cap int) *MemFileIndex { + return &MemFileIndex{ + cap: cap, + infos: make(map[string]*FileInfo, 0), + } +} + +func NewMemFileIndexWithMap(cap int, infos map[string]*FileInfo) *MemFileIndex { + return &MemFileIndex{ + cap: cap, + infos: infos, + } +} + +type MemFileIndex struct { + cap int + infos map[string]*FileInfo + mux sync.RWMutex +} + +func (idx *MemFileIndex) Add(fileInfo *FileInfo) int { + idx.mux.Lock() + defer idx.mux.Unlock() + + if len(idx.infos) >= idx.cap { + return 1 + } + + if _, found := idx.infos[fileInfo.Id]; found { + return -1 + } + + idx.infos[fileInfo.Id] = fileInfo + return 0 +} + +func (idx *MemFileIndex) Del(id string) { + idx.mux.Lock() + defer idx.mux.Unlock() + + delete(idx.infos, id) +} + +func (idx *MemFileIndex) SetId(id string, newId string) bool { + if id == newId { + return true + } + + idx.mux.Lock() + defer idx.mux.Unlock() + + info, found := idx.infos[id] + if !found { + return false + } + + if _, foundNewId := idx.infos[newId]; foundNewId { + return false + } + + idx.infos[newId] = info + idx.infos[newId].Id = newId + delete(idx.infos, id) + return true +} + +func (idx *MemFileIndex) SetDownLimit(id string, downLimit int) bool { + idx.mux.Lock() + defer idx.mux.Unlock() + + info, found := idx.infos[id] + if !found { + return false + } + + info.DownLimit = downLimit + return true +} + +func (idx *MemFileIndex) DecrDownLimit(id string) (int, bool) { + idx.mux.Lock() + defer idx.mux.Unlock() + + info, found := idx.infos[id] + if !found || info.State != StateDone { + return 0, false + } + + if info.DownLimit == 0 { + return 1, false + } + + if info.DownLimit > 0 { + // info.DownLimit means unlimited + info.DownLimit = info.DownLimit - 1 + } + return 1, true +} + +func (idx *MemFileIndex) SetState(id string, state string) bool { + idx.mux.Lock() + defer idx.mux.Unlock() + + info, found := idx.infos[id] + if !found { + return false + } + + info.State = state + return true +} + +func (idx *MemFileIndex) IncrUploaded(id string, uploaded int64) int64 { + idx.mux.Lock() + defer idx.mux.Unlock() + + info, found := idx.infos[id] + if !found { + return 0 + } + + info.Uploaded = info.Uploaded + uploaded + return info.Uploaded +} + +func (idx *MemFileIndex) Get(id string) (*FileInfo, bool) { + idx.mux.RLock() + defer idx.mux.RUnlock() + + infos, found := idx.infos[id] + return infos, found +} + +func (idx *MemFileIndex) List() map[string]*FileInfo { + idx.mux.RLock() + defer idx.mux.RUnlock() + + return idx.infos +} + +// TODO: add unit tests diff --git a/server/libs/fsutil/fsutil.go b/server/libs/fsutil/fsutil.go new file mode 100644 index 0000000..dfecbc8 --- /dev/null +++ b/server/libs/fsutil/fsutil.go @@ -0,0 +1,118 @@ +package fsutil + +import ( + "errors" + "io" + "os" +) + +import ( + "quickshare/server/libs/errutil" + "quickshare/server/libs/fileidx" + "quickshare/server/libs/qtube" +) + +type FsUtil interface { + CreateFile(fullPath string) error + CopyChunkN(fullPath string, chunk io.Reader, start int64, length int64) bool + DelFile(fullPath string) bool + Open(fullPath string) (qtube.ReadSeekCloser, error) + MkdirAll(path string, mode os.FileMode) bool + Readdir(dirName string, n int) ([]*fileidx.FileInfo, error) +} + +func NewSimpleFs(errUtil errutil.ErrUtil) FsUtil { + return &SimpleFs{ + Err: errUtil, + } +} + +type SimpleFs struct { + Err errutil.ErrUtil +} + +var ( + ErrExists = errors.New("file exists") + ErrUnknown = errors.New("unknown error") +) + +func (sfs *SimpleFs) CreateFile(fullPath string) error { + flag := os.O_CREATE | os.O_EXCL | os.O_RDONLY + perm := os.FileMode(0644) + newFile, err := os.OpenFile(fullPath, flag, perm) + defer newFile.Close() + + if err == nil { + return nil + } else if os.IsExist(err) { + return ErrExists + } else { + return ErrUnknown + } +} + +func (sfs *SimpleFs) CopyChunkN(fullPath string, chunk io.Reader, start int64, length int64) bool { + flag := os.O_WRONLY + perm := os.FileMode(0644) + file, openErr := os.OpenFile(fullPath, flag, perm) + + defer file.Close() + if sfs.Err.IsErr(openErr) { + return false + } + + if _, err := file.Seek(start, io.SeekStart); sfs.Err.IsErr(err) { + return false + } + + if _, err := io.CopyN(file, chunk, length); sfs.Err.IsErr(err) && err != io.EOF { + return false + } + + return true +} + +func (sfs *SimpleFs) DelFile(fullPath string) bool { + return !sfs.Err.IsErr(os.Remove(fullPath)) +} + +func (sfs *SimpleFs) MkdirAll(path string, mode os.FileMode) bool { + err := os.MkdirAll(path, mode) + return !sfs.Err.IsErr(err) +} + +// TODO: not support read from last seek position +func (sfs *SimpleFs) Readdir(dirName string, n int) ([]*fileidx.FileInfo, error) { + dir, openErr := os.Open(dirName) + defer dir.Close() + + if sfs.Err.IsErr(openErr) { + return []*fileidx.FileInfo{}, openErr + } + + osFileInfos, readErr := dir.Readdir(n) + if sfs.Err.IsErr(readErr) && readErr != io.EOF { + return []*fileidx.FileInfo{}, readErr + } + + fileInfos := make([]*fileidx.FileInfo, 0) + for _, osFileInfo := range osFileInfos { + if osFileInfo.Mode().IsRegular() { + fileInfos = append( + fileInfos, + &fileidx.FileInfo{ + ModTime: osFileInfo.ModTime().UnixNano(), + PathLocal: osFileInfo.Name(), + Uploaded: osFileInfo.Size(), + }, + ) + } + } + + return fileInfos, readErr +} + +// the associated file descriptor has mode O_RDONLY as using os.Open +func (sfs *SimpleFs) Open(fullPath string) (qtube.ReadSeekCloser, error) { + return os.Open(fullPath) +} diff --git a/server/libs/httputil/httputil.go b/server/libs/httputil/httputil.go new file mode 100644 index 0000000..06a49df --- /dev/null +++ b/server/libs/httputil/httputil.go @@ -0,0 +1,84 @@ +package httputil + +import ( + "encoding/json" + "net/http" + "time" +) + +import ( + "quickshare/server/libs/errutil" +) + +type MsgRes struct { + Code int + Msg string +} + +var ( + Err400 = MsgRes{Code: http.StatusBadRequest, Msg: "Bad Request"} + Err401 = MsgRes{Code: http.StatusUnauthorized, Msg: "Unauthorized"} + Err404 = MsgRes{Code: http.StatusNotFound, Msg: "Not Found"} + Err412 = MsgRes{Code: http.StatusPreconditionFailed, Msg: "Precondition Failed"} + Err429 = MsgRes{Code: http.StatusTooManyRequests, Msg: "Too Many Requests"} + Err500 = MsgRes{Code: http.StatusInternalServerError, Msg: "Internal Server Error"} + Err503 = MsgRes{Code: http.StatusServiceUnavailable, Msg: "Service Unavailable"} + Err504 = MsgRes{Code: http.StatusGatewayTimeout, Msg: "Gateway Timeout"} + Ok200 = MsgRes{Code: http.StatusOK, Msg: "OK"} +) + +type HttpUtil interface { + GetCookie(cookies []*http.Cookie, key string) string + SetCookie(res http.ResponseWriter, key string, val string) + Fill(msg interface{}, res http.ResponseWriter) int +} + +type QHttpUtil struct { + CookieDomain string + CookieHttpOnly bool + CookieMaxAge int + CookiePath string + CookieSecure bool + Err errutil.ErrUtil +} + +func (q *QHttpUtil) GetCookie(cookies []*http.Cookie, key string) string { + for _, cookie := range cookies { + if cookie.Name == key { + return cookie.Value + } + } + return "" +} + +func (q *QHttpUtil) SetCookie(res http.ResponseWriter, key string, val string) { + cookie := http.Cookie{ + Name: key, + Value: val, + Domain: q.CookieDomain, + Expires: time.Now().Add(time.Duration(q.CookieMaxAge) * time.Second), + HttpOnly: q.CookieHttpOnly, + MaxAge: q.CookieMaxAge, + Secure: q.CookieSecure, + Path: q.CookiePath, + } + + res.Header().Set("Set-Cookie", cookie.String()) +} + +func (q *QHttpUtil) Fill(msg interface{}, res http.ResponseWriter) int { + if msg == nil { + return 0 + } + + msgBytes, marsErr := json.Marshal(msg) + if q.Err.IsErr(marsErr) { + return 0 + } + + wrote, writeErr := res.Write(msgBytes) + if q.Err.IsErr(writeErr) { + return 0 + } + return wrote +} diff --git a/server/libs/httpworker/worker.go b/server/libs/httpworker/worker.go new file mode 100644 index 0000000..06aec0f --- /dev/null +++ b/server/libs/httpworker/worker.go @@ -0,0 +1,130 @@ +package httpworker + +import ( + "errors" + "net/http" + "runtime/debug" + "time" +) + +import ( + "quickshare/server/libs/logutil" +) + +var ( + ErrWorkerNotFound = errors.New("worker not found") + ErrTimeout = errors.New("timeout") +) + +type DoFunc func(http.ResponseWriter, *http.Request) + +type Task struct { + Ack chan error + Do DoFunc + Res http.ResponseWriter + Req *http.Request +} + +type Workers interface { + Put(*Task) bool + IsInTime(ack chan error, msec time.Duration) error +} + +type WorkerPool struct { + queue chan *Task + size int + workers []*Worker + log logutil.LogUtil // TODO: should not pass log here +} + +func NewWorkerPool(poolSize int, queueSize int, log logutil.LogUtil) Workers { + queue := make(chan *Task, queueSize) + workers := make([]*Worker, 0, poolSize) + + for i := 0; i < poolSize; i++ { + worker := &Worker{ + Id: uint64(i), + queue: queue, + log: log, + } + + go worker.Start() + workers = append(workers, worker) + } + + return &WorkerPool{ + queue: queue, + size: poolSize, + workers: workers, + log: log, + } +} + +func (pool *WorkerPool) Put(task *Task) bool { + if len(pool.queue) >= pool.size { + return false + } + + pool.queue <- task + return true +} + +func (pool *WorkerPool) IsInTime(ack chan error, msec time.Duration) error { + start := time.Now().UnixNano() + timeout := make(chan error) + + go func() { + time.Sleep(msec) + timeout <- ErrTimeout + }() + + select { + case err := <-ack: + if err == nil { + pool.log.Printf( + "finish cost: %d usec", + (time.Now().UnixNano()-start)/1000, + ) + } else { + pool.log.Printf( + "finish with error cost: %d usec", + (time.Now().UnixNano()-start)/1000, + ) + } + return err + case errTimeout := <-timeout: + pool.log.Printf("timeout cost: %d usec", (time.Now().UnixNano()-start)/1000) + return errTimeout + } +} + +type Worker struct { + Id uint64 + queue chan *Task + log logutil.LogUtil +} + +func (worker *Worker) RecoverPanic() { + if r := recover(); r != nil { + worker.log.Printf("Recovered:%v stack: %v", r, debug.Stack()) + // restart worker and IsInTime will return timeout error for last task + worker.Start() + } +} + +func (worker *Worker) Start() { + defer worker.RecoverPanic() + + for { + task := <-worker.queue + if task.Do != nil { + task.Do(task.Res, task.Req) + task.Ack <- nil + } else { + task.Ack <- ErrWorkerNotFound + } + } +} + +// ServiceFunc lets you return struct directly +type ServiceFunc func(http.ResponseWriter, *http.Request) interface{} diff --git a/server/libs/limiter/limiter.go b/server/libs/limiter/limiter.go new file mode 100644 index 0000000..307037d --- /dev/null +++ b/server/libs/limiter/limiter.go @@ -0,0 +1,5 @@ +package limiter + +type Limiter interface { + Access(string, int16) bool +} diff --git a/server/libs/limiter/rate_limiter.go b/server/libs/limiter/rate_limiter.go new file mode 100644 index 0000000..7bccf1a --- /dev/null +++ b/server/libs/limiter/rate_limiter.go @@ -0,0 +1,220 @@ +package limiter + +import ( + "sync" + "time" +) + +func now() int32 { + return int32(time.Now().Unix()) +} + +func afterCyc(cyc int32) int32 { + return int32(time.Now().Unix()) + cyc +} + +func afterTtl(ttl int32) int32 { + return int32(time.Now().Unix()) + ttl +} + +type Bucket struct { + Refresh int32 + Tokens int16 +} + +func NewBucket(cyc int32, cap int16) *Bucket { + return &Bucket{ + Refresh: afterCyc(cyc), + Tokens: cap, + } +} + +type Item struct { + Expired int32 + Buckets map[int16]*Bucket +} + +func NewItem(ttl int32) *Item { + return &Item{ + Expired: afterTtl(ttl), + Buckets: make(map[int16]*Bucket), + } +} + +type RateLimiter struct { + items map[string]*Item + bucketCap int16 + customCaps map[int16]int16 + cap int64 + cyc int32 // how much time, item autoclean will be executed, bucket will be refreshed + ttl int32 // how much time, item will be expired(but not cleaned) + mux sync.RWMutex + snapshot map[string]map[int16]*Bucket +} + +func NewRateLimiter(cap int64, ttl int32, cyc int32, bucketCap int16, customCaps map[int16]int16) Limiter { + if cap < 1 || ttl < 1 || cyc < 1 || bucketCap < 1 { + panic("cap | bucketCap | ttl | cycle cant be less than 1") + } + + limiter := &RateLimiter{ + items: make(map[string]*Item, cap), + bucketCap: bucketCap, + customCaps: customCaps, + cap: cap, + ttl: ttl, + cyc: cyc, + } + + go limiter.autoClean() + + return limiter +} + +func (limiter *RateLimiter) getBucketCap(opId int16) int16 { + bucketCap, existed := limiter.customCaps[opId] + if !existed { + return limiter.bucketCap + } + return bucketCap +} + +func (limiter *RateLimiter) Access(itemId string, opId int16) bool { + limiter.mux.Lock() + defer limiter.mux.Unlock() + + item, itemExisted := limiter.items[itemId] + if !itemExisted { + if int64(len(limiter.items)) >= limiter.cap { + return false + } + + limiter.items[itemId] = NewItem(limiter.ttl) + limiter.items[itemId].Buckets[opId] = NewBucket(limiter.cyc, limiter.getBucketCap(opId)-1) + return true + } + + bucket, bucketExisted := item.Buckets[opId] + if !bucketExisted { + item.Buckets[opId] = NewBucket(limiter.cyc, limiter.getBucketCap(opId)-1) + return true + } + + if bucket.Refresh > now() { + if bucket.Tokens > 0 { + bucket.Tokens-- + return true + } + return false + } + + bucket.Refresh = afterCyc(limiter.cyc) + bucket.Tokens = limiter.getBucketCap(opId) - 1 + return true +} + +func (limiter *RateLimiter) GetCap() int64 { + return limiter.cap +} + +func (limiter *RateLimiter) GetSize() int64 { + limiter.mux.RLock() + defer limiter.mux.RUnlock() + return int64(len(limiter.items)) +} + +func (limiter *RateLimiter) ExpandCap(cap int64) bool { + limiter.mux.RLock() + defer limiter.mux.RUnlock() + + if cap <= int64(len(limiter.items)) { + return false + } + + limiter.cap = cap + return true +} + +func (limiter *RateLimiter) GetTTL() int32 { + return limiter.ttl +} + +func (limiter *RateLimiter) UpdateTTL(ttl int32) bool { + if ttl < 1 { + return false + } + + limiter.ttl = ttl + return true +} + +func (limiter *RateLimiter) GetCyc() int32 { + return limiter.cyc +} + +func (limiter *RateLimiter) UpdateCyc(cyc int32) bool { + if limiter.cyc < 1 { + return false + } + + limiter.cyc = cyc + return true +} + +func (limiter *RateLimiter) Snapshot() map[string]map[int16]*Bucket { + return limiter.snapshot +} + +func (limiter *RateLimiter) autoClean() { + for { + if limiter.cyc == 0 { + break + } + time.Sleep(time.Duration(int64(limiter.cyc) * 1000000000)) + limiter.clean() + } +} + +// clean may add affect other operations, do frequently? +func (limiter *RateLimiter) clean() { + limiter.snapshot = make(map[string]map[int16]*Bucket) + now := now() + + limiter.mux.RLock() + defer limiter.mux.RUnlock() + for key, item := range limiter.items { + if item.Expired <= now { + delete(limiter.items, key) + } else { + limiter.snapshot[key] = item.Buckets + } + } +} + +// Only for test +func (limiter *RateLimiter) exist(id string) bool { + limiter.mux.RLock() + defer limiter.mux.RUnlock() + + _, existed := limiter.items[id] + return existed +} + +// Only for test +func (limiter *RateLimiter) truncate() { + limiter.mux.RLock() + defer limiter.mux.RUnlock() + + for key, _ := range limiter.items { + delete(limiter.items, key) + } +} + +// Only for test +func (limiter *RateLimiter) get(id string) (*Item, bool) { + limiter.mux.RLock() + defer limiter.mux.RUnlock() + + item, existed := limiter.items[id] + return item, existed +} diff --git a/server/libs/limiter/rate_limiter_test.go b/server/libs/limiter/rate_limiter_test.go new file mode 100644 index 0000000..3f90dcf --- /dev/null +++ b/server/libs/limiter/rate_limiter_test.go @@ -0,0 +1,161 @@ +package limiter + +import ( + "fmt" + "math/rand" + "testing" + "time" +) + +var rnd = rand.New(rand.NewSource(time.Now().UnixNano())) + +const rndCap = 10000 +const addCap = 1 + +// how to set time +// extend: wait can be greater than ttl/2 +// cyc is smaller than ttl and wait, then it can be clean in time +const cap = 40 +const ttl = 3 +const cyc = 1 +const bucketCap = 2 +const id1 = "id1" +const id2 = "id2" +const op1 int16 = 0 +const op2 int16 = 1 + +var customCaps = map[int16]int16{ + op2: 1000, +} + +const wait = 1 + +var limiter = NewRateLimiter(cap, ttl, cyc, bucketCap, customCaps).(*RateLimiter) + +func printItem(id string) { + item, existed := limiter.get(id1) + if existed { + fmt.Println("expired, now, existed", item.Expired, now(), existed) + for id, bucket := range item.Buckets { + fmt.Println("\tid, bucket", id, bucket) + } + } else { + fmt.Println("not existed") + } +} + +var idSeed = 0 + +func randId() string { + idSeed++ + return fmt.Sprintf("%d", idSeed) +} + +func TestAccess(t *testing.T) { + func(t *testing.T) { + canAccess := limiter.Access(id1, op1) + if !canAccess { + t.Fatal("access: fail") + } + + for i := 0; i < bucketCap; i++ { + canAccess = limiter.Access(id1, op1) + } + + if canAccess { + t.Fatal("access: fail to deny access") + } + + time.Sleep(time.Duration(limiter.GetCyc()) * time.Second) + + canAccess = limiter.Access(id1, op1) + if !canAccess { + t.Fatal("access: fail to refresh tokens") + } + }(t) +} + +func TestCap(t *testing.T) { + originalCap := limiter.GetCap() + fmt.Printf("cap:info: %d\n", originalCap) + + ok := limiter.ExpandCap(originalCap + addCap) + + if !ok || limiter.GetCap() != originalCap+addCap { + t.Fatal("cap: fail to expand") + } + + ok = limiter.ExpandCap(limiter.GetSize() - addCap) + if ok { + t.Fatal("cap: shrink cap") + } + + ids := []string{} + for limiter.GetSize() < limiter.GetCap() { + id := randId() + ids = append(ids, id) + + ok := limiter.Access(id, 0) + if !ok { + t.Fatal("cap: not full") + } + } + + if limiter.GetSize() != limiter.GetCap() { + t.Fatal("cap: incorrect size") + } + + if limiter.Access(randId(), 0) { + t.Fatal("cap: more than cap") + } + + limiter.truncate() +} + +func TestTtl(t *testing.T) { + var addTtl int32 = 1 + originalTTL := limiter.GetTTL() + fmt.Printf("ttl:info: %d\n", originalTTL) + + limiter.UpdateTTL(originalTTL + addTtl) + if limiter.GetTTL() != originalTTL+addTtl { + t.Fatal("ttl: update fail") + } +} + +func cycTest(t *testing.T) { + var addCyc int32 = 1 + originalCyc := limiter.GetCyc() + fmt.Printf("cyc:info: %d\n", originalCyc) + + limiter.UpdateCyc(originalCyc + addCyc) + if limiter.GetCyc() != originalCyc+addCyc { + t.Fatal("cyc: update fail") + } +} + +func autoCleanTest(t *testing.T) { + ids := []string{ + randId(), + randId(), + } + + for _, id := range ids { + ok := limiter.Access(id, 0) + if ok { + t.Fatal("autoClean: warning: add fail") + } + } + + time.Sleep(time.Duration(limiter.GetTTL()+wait) * time.Second) + + for _, id := range ids { + _, exist := limiter.get(id) + if exist { + t.Fatal("autoClean: item still exist") + } + } +} + +// func snapshotTest(t *testing.T) { +// } diff --git a/server/libs/logutil/logutil.go b/server/libs/logutil/logutil.go new file mode 100644 index 0000000..15b2f6a --- /dev/null +++ b/server/libs/logutil/logutil.go @@ -0,0 +1,7 @@ +package logutil + +type LogUtil interface { + Print(v ...interface{}) + Printf(format string, v ...interface{}) + Println(v ...interface{}) +} diff --git a/server/libs/logutil/slogger.go b/server/libs/logutil/slogger.go new file mode 100644 index 0000000..f0cd3f6 --- /dev/null +++ b/server/libs/logutil/slogger.go @@ -0,0 +1,12 @@ +package logutil + +import ( + "io" + "log" +) + +func NewSlog(out io.Writer, prefix string) LogUtil { + return log.New(out, prefix, log.Ldate|log.Ltime|log.Lshortfile) +} + +type Slog *log.Logger diff --git a/server/libs/qtube/downloader.go b/server/libs/qtube/downloader.go new file mode 100644 index 0000000..92a425d --- /dev/null +++ b/server/libs/qtube/downloader.go @@ -0,0 +1,13 @@ +package qtube + +import ( + "net/http" +) + +import ( + "quickshare/server/libs/fileidx" +) + +type Downloader interface { + ServeFile(res http.ResponseWriter, req *http.Request, fileInfo *fileidx.FileInfo) error +} diff --git a/server/libs/qtube/qtube.go b/server/libs/qtube/qtube.go new file mode 100644 index 0000000..bd43825 --- /dev/null +++ b/server/libs/qtube/qtube.go @@ -0,0 +1,280 @@ +package qtube + +import ( + "errors" + "fmt" + "io" + "net/http" + "path/filepath" + "strconv" + "strings" + "time" +) + +import ( + "quickshare/server/libs/fileidx" +) + +var ( + ErrCopy = errors.New("ServeFile: copy error") + ErrUnknown = errors.New("ServeFile: unknown error") +) + +type httpRange struct { + start, length int64 +} + +func (ra *httpRange) GetStart() int64 { + return ra.start +} +func (ra *httpRange) GetLength() int64 { + return ra.length +} +func (ra *httpRange) SetStart(start int64) { + ra.start = start +} +func (ra *httpRange) SetLength(length int64) { + ra.length = length +} + +func NewQTube(root string, copySpeed, maxRangeLen int64, filer FileReadSeekCloser) Downloader { + return &QTube{ + Root: root, + BytesPerSec: copySpeed, + MaxRangeLen: maxRangeLen, + Filer: filer, + } +} + +type QTube struct { + Root string + BytesPerSec int64 + MaxRangeLen int64 + Filer FileReadSeekCloser +} + +type FileReadSeekCloser interface { + Open(filePath string) (ReadSeekCloser, error) +} + +type ReadSeekCloser interface { + io.Reader + io.Seeker + io.Closer +} + +const ( + ErrorInvalidRange = "ServeFile: invalid Range" + ErrorInvalidSize = "ServeFile: invalid Range total size" +) + +func (tb *QTube) ServeFile(res http.ResponseWriter, req *http.Request, fileInfo *fileidx.FileInfo) error { + headerRange := req.Header.Get("Range") + + switch { + case req.Method == http.MethodHead: + res.Header().Set("Accept-Ranges", "bytes") + res.Header().Set("Content-Length", fmt.Sprintf("%d", fileInfo.Uploaded)) + res.Header().Set("Content-Type", "application/octet-stream") + res.WriteHeader(http.StatusOK) + + return nil + case headerRange == "": + return tb.serveAll(res, fileInfo) + default: + return tb.serveRanges(res, headerRange, fileInfo) + } +} + +func (tb *QTube) serveAll(res http.ResponseWriter, fileInfo *fileidx.FileInfo) error { + res.Header().Set("Accept-Ranges", "bytes") + res.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filepath.Base(fileInfo.PathLocal))) + res.Header().Set("Content-Length", fmt.Sprintf("%d", fileInfo.Uploaded)) + res.Header().Set("Content-Type", "application/octet-stream") + res.Header().Set("Last-Modified", time.Unix(fileInfo.ModTime, 0).UTC().Format(http.TimeFormat)) + res.WriteHeader(http.StatusOK) + + // TODO: need verify path + file, openErr := tb.Filer.Open(filepath.Join(tb.Root, fileInfo.PathLocal)) + defer file.Close() + if openErr != nil { + return openErr + } + + copyErr := tb.throttledCopyN(res, file, fileInfo.Uploaded) + if copyErr != nil && copyErr != io.EOF { + return copyErr + } + + return nil +} + +func (tb *QTube) serveRanges(res http.ResponseWriter, headerRange string, fileInfo *fileidx.FileInfo) error { + ranges, rangeErr := getRanges(headerRange, fileInfo.Uploaded) + if rangeErr != nil { + http.Error(res, rangeErr.Error(), http.StatusRequestedRangeNotSatisfiable) + return errors.New(rangeErr.Error()) + } + + switch { + case len(ranges) == 1 || len(ranges) > 1: + if tb.copyRange(res, ranges[0], fileInfo) != nil { + return ErrCopy + } + default: + // TODO: add support for multiple ranges + return ErrUnknown + } + + return nil +} + +func getRanges(headerRange string, size int64) ([]httpRange, error) { + ranges, raParseErr := parseRange(headerRange, size) + // TODO: check max number of ranges, range start end + if len(ranges) <= 0 || raParseErr != nil { + return nil, errors.New(ErrorInvalidRange) + } + if sumRangesSize(ranges) > size { + return nil, errors.New(ErrorInvalidSize) + } + + return ranges, nil +} + +func (tb *QTube) copyRange(res http.ResponseWriter, ra httpRange, fileInfo *fileidx.FileInfo) error { + // TODO: comfirm this wont cause problem + if ra.GetLength() > tb.MaxRangeLen { + ra.SetLength(tb.MaxRangeLen) + } + + // TODO: add headers(ETag): https://tools.ietf.org/html/rfc7233#section-4.1 p11 2nd paragraph + res.Header().Set("Accept-Ranges", "bytes") + res.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filepath.Base(fileInfo.PathLocal))) + res.Header().Set("Content-Type", "application/octet-stream") + res.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", ra.start, ra.start+ra.length-1, fileInfo.Uploaded)) + res.Header().Set("Content-Length", strconv.FormatInt(ra.GetLength(), 10)) + res.Header().Set("Last-Modified", time.Unix(fileInfo.ModTime, 0).UTC().Format(http.TimeFormat)) + res.WriteHeader(http.StatusPartialContent) + + // TODO: need verify path + file, openErr := tb.Filer.Open(filepath.Join(tb.Root, fileInfo.PathLocal)) + defer file.Close() + if openErr != nil { + return openErr + } + + if _, seekErr := file.Seek(ra.start, io.SeekStart); seekErr != nil { + return seekErr + } + + copyErr := tb.throttledCopyN(res, file, ra.length) + if copyErr != nil && copyErr != io.EOF { + return copyErr + } + + return nil +} + +func (tb *QTube) throttledCopyN(dst io.Writer, src io.Reader, length int64) error { + sum := int64(0) + timeSlot := time.Duration(1 * time.Second) + + for sum < length { + start := time.Now() + chunkSize := length - sum + if length-sum > tb.BytesPerSec { + chunkSize = tb.BytesPerSec + } + + copied, err := io.CopyN(dst, src, chunkSize) + if err != nil { + return err + } + + sum += copied + end := time.Now() + if end.Before(start.Add(timeSlot)) { + time.Sleep(start.Add(timeSlot).Sub(end)) + } + } + + return nil +} + +func parseRange(headerRange string, size int64) ([]httpRange, error) { + if headerRange == "" { + return nil, nil // header not present + } + + const keyByte = "bytes=" + if !strings.HasPrefix(headerRange, keyByte) { + return nil, errors.New("byte= not found") + } + + var ranges []httpRange + noOverlap := false + for _, ra := range strings.Split(headerRange[len(keyByte):], ",") { + ra = strings.TrimSpace(ra) + if ra == "" { + continue + } + + i := strings.Index(ra, "-") + if i < 0 { + return nil, errors.New("- not found") + } + + start, end := strings.TrimSpace(ra[:i]), strings.TrimSpace(ra[i+1:]) + var r httpRange + if start == "" { + i, err := strconv.ParseInt(end, 10, 64) + if err != nil { + return nil, errors.New("invalid range") + } + if i > size { + i = size + } + r.start = size - i + r.length = size - r.start + } else { + i, err := strconv.ParseInt(start, 10, 64) + if err != nil || i < 0 { + return nil, errors.New("invalid range") + } + if i >= size { + // If the range begins after the size of the content, + // then it does not overlap. + noOverlap = true + continue + } + r.start = i + if end == "" { + // If no end is specified, range extends to end of the file. + r.length = size - r.start + } else { + i, err := strconv.ParseInt(end, 10, 64) + if err != nil || r.start > i { + return nil, errors.New("invalid range") + } + if i >= size { + i = size - 1 + } + r.length = i - r.start + 1 + } + } + ranges = append(ranges, r) + } + if noOverlap && len(ranges) == 0 { + // The specified ranges did not overlap with the content. + return nil, errors.New("parseRanges: no overlap") + } + return ranges, nil +} + +func sumRangesSize(ranges []httpRange) (size int64) { + for _, ra := range ranges { + size += ra.length + } + return +} diff --git a/server/libs/qtube/qtube_test.go b/server/libs/qtube/qtube_test.go new file mode 100644 index 0000000..00f95f3 --- /dev/null +++ b/server/libs/qtube/qtube_test.go @@ -0,0 +1,354 @@ +package qtube + +import ( + "bytes" + "fmt" + "net/http" + "strings" + "testing" + "time" +) + +import ( + "quickshare/server/libs/fileidx" +) + +// Range format examples: +// Range: =- +// Range: =- +// Range: =-, - +// Range: =-, -, - +func TestGetRanges(t *testing.T) { + type Input struct { + HeaderRange string + Size int64 + } + type Output struct { + Ranges []httpRange + ErrorMsg string + } + type testCase struct { + Desc string + Input + Output + } + + testCases := []testCase{ + testCase{ + Desc: "invalid range", + Input: Input{ + HeaderRange: "bytes=start-invalid end", + Size: 0, + }, + Output: Output{ + ErrorMsg: ErrorInvalidRange, + }, + }, + testCase{ + Desc: "invalid range total size", + Input: Input{ + HeaderRange: "bytes=0-1, 2-3, 0-1, 0-2", + Size: 3, + }, + Output: Output{ + ErrorMsg: ErrorInvalidSize, + }, + }, + testCase{ + Desc: "range ok", + Input: Input{ + HeaderRange: "bytes=0-1, 2-3", + Size: 4, + }, + Output: Output{ + Ranges: []httpRange{ + httpRange{start: 0, length: 2}, + httpRange{start: 2, length: 2}, + }, + ErrorMsg: "", + }, + }, + } + + for _, tCase := range testCases { + ranges, err := getRanges(tCase.HeaderRange, tCase.Size) + if err != nil { + if err.Error() != tCase.ErrorMsg || len(tCase.Ranges) != 0 { + t.Fatalf("getRanges: incorrect errorMsg want: %v got: %v", tCase.ErrorMsg, err.Error()) + } else { + continue + } + } else { + for id, ra := range ranges { + if ra.GetStart() != tCase.Ranges[id].GetStart() { + t.Fatalf("getRanges: incorrect range start, got: %v want: %v", ra.GetStart(), tCase.Ranges[id]) + } + if ra.GetLength() != tCase.Ranges[id].GetLength() { + t.Fatalf("getRanges: incorrect range length, got: %v want: %v", ra.GetLength(), tCase.Ranges[id]) + } + } + } + } +} + +func TestThrottledCopyN(t *testing.T) { + type Init struct { + BytesPerSec int64 + MaxRangeLen int64 + } + type Input struct { + Src string + Length int64 + } + // after starting throttledCopyN by DstAtTime.AtMs millisecond, + // copied valueshould equal to DstAtTime.Dst. + type DstAtTime struct { + AtMS int + Dst string + } + type Output struct { + ExpectDsts []DstAtTime + } + type testCase struct { + Desc string + Init + Input + Output + } + + verifyDsts := func(dst *bytes.Buffer, expectDsts []DstAtTime) { + for _, expectDst := range expectDsts { + // fmt.Printf("sleep: %d\n", time.Now().UnixNano()) + time.Sleep(time.Duration(expectDst.AtMS) * time.Millisecond) + dstStr := string(dst.Bytes()) + // fmt.Printf("check: %d\n", time.Now().UnixNano()) + if dstStr != expectDst.Dst { + panic( + fmt.Sprintf( + "throttledCopyN want: <%s> | got: <%s> | at: %d", + expectDst.Dst, + dstStr, + expectDst.AtMS, + ), + ) + } + } + } + + testCases := []testCase{ + testCase{ + Desc: "4 byte per sec", + Init: Init{ + BytesPerSec: 5, + MaxRangeLen: 10, + }, + Input: Input{ + Src: "aaaa_aaaa_", + Length: 10, + }, + Output: Output{ + ExpectDsts: []DstAtTime{ + DstAtTime{AtMS: 200, Dst: "aaaa_"}, + DstAtTime{AtMS: 200, Dst: "aaaa_"}, + DstAtTime{AtMS: 200, Dst: "aaaa_"}, + DstAtTime{AtMS: 600, Dst: "aaaa_aaaa_"}, + DstAtTime{AtMS: 200, Dst: "aaaa_aaaa_"}, + DstAtTime{AtMS: 200, Dst: "aaaa_aaaa_"}, + }, + }, + }, + } + + for _, tCase := range testCases { + tb := NewQTube("", tCase.BytesPerSec, tCase.MaxRangeLen, &stubFiler{}).(*QTube) + dst := bytes.NewBuffer(make([]byte, len(tCase.Src))) + dst.Reset() + + go verifyDsts(dst, tCase.ExpectDsts) + tb.throttledCopyN(dst, strings.NewReader(tCase.Src), tCase.Length) + } +} + +// TODO: using same stub with testhelper +type stubWriter struct { + Headers http.Header + Response []byte + StatusCode int +} + +func (w *stubWriter) Header() http.Header { + return w.Headers +} + +func (w *stubWriter) Write(body []byte) (int, error) { + w.Response = append(w.Response, body...) + return len(body), nil +} + +func (w *stubWriter) WriteHeader(statusCode int) { + w.StatusCode = statusCode +} + +func TestCopyRange(t *testing.T) { + type Init struct { + Content string + } + type Input struct { + Range httpRange + Info fileidx.FileInfo + } + type Output struct { + StatusCode int + Headers map[string][]string + Body string + } + type testCase struct { + Desc string + Init + Input + Output + } + + testCases := []testCase{ + testCase{ + Desc: "copy ok", + Init: Init{ + Content: "abcd_abcd_", + }, + Input: Input{ + Range: httpRange{ + start: 6, + length: 3, + }, + Info: fileidx.FileInfo{ + ModTime: 0, + Uploaded: 10, + PathLocal: "filename.jpg", + }, + }, + Output: Output{ + StatusCode: 206, + Headers: map[string][]string{ + "Accept-Ranges": []string{"bytes"}, + "Content-Disposition": []string{`attachment; filename="filename.jpg"`}, + "Content-Type": []string{"application/octet-stream"}, + "Content-Range": []string{"bytes 6-8/10"}, + "Content-Length": []string{"3"}, + "Last-Modified": []string{time.Unix(0, 0).UTC().Format(http.TimeFormat)}, + }, + Body: "abc", + }, + }, + } + + for _, tCase := range testCases { + filer := &stubFiler{ + &StubFile{ + Content: tCase.Content, + Offset: 0, + }, + } + tb := NewQTube("", 100, 100, filer).(*QTube) + res := &stubWriter{ + Headers: make(map[string][]string), + Response: make([]byte, 0), + } + err := tb.copyRange(res, tCase.Range, &tCase.Info) + if err != nil { + t.Fatalf("copyRange: %v", err) + } + if res.StatusCode != tCase.Output.StatusCode { + t.Fatalf("copyRange: statusCode not match got: %v want: %v", res.StatusCode, tCase.Output.StatusCode) + } + if string(res.Response) != tCase.Output.Body { + t.Fatalf("copyRange: body not match \ngot: %v \nwant: %v", string(res.Response), tCase.Output.Body) + } + for key, vals := range tCase.Output.Headers { + if res.Header().Get(key) != vals[0] { + t.Fatalf("copyRange: header not match %v got: %v want: %v", key, res.Header().Get(key), vals[0]) + } + } + if res.StatusCode != tCase.Output.StatusCode { + t.Fatalf("copyRange: statusCodes are not match %v", res.StatusCode, tCase.Output.StatusCode) + } + } +} + +func TestServeAll(t *testing.T) { + type Init struct { + Content string + } + type Input struct { + Info fileidx.FileInfo + } + type Output struct { + StatusCode int + Headers map[string][]string + Body string + } + type testCase struct { + Desc string + Init + Input + Output + } + + testCases := []testCase{ + testCase{ + Desc: "copy ok", + Init: Init{ + Content: "abcd_abcd_", + }, + Input: Input{ + Info: fileidx.FileInfo{ + ModTime: 0, + Uploaded: 10, + PathLocal: "filename.jpg", + }, + }, + Output: Output{ + StatusCode: 200, + Headers: map[string][]string{ + "Accept-Ranges": []string{"bytes"}, + "Content-Disposition": []string{`attachment; filename="filename.jpg"`}, + "Content-Type": []string{"application/octet-stream"}, + "Content-Length": []string{"10"}, + "Last-Modified": []string{time.Unix(0, 0).UTC().Format(http.TimeFormat)}, + }, + Body: "abcd_abcd_", + }, + }, + } + + for _, tCase := range testCases { + filer := &stubFiler{ + &StubFile{ + Content: tCase.Content, + Offset: 0, + }, + } + tb := NewQTube("", 100, 100, filer).(*QTube) + res := &stubWriter{ + Headers: make(map[string][]string), + Response: make([]byte, 0), + } + err := tb.serveAll(res, &tCase.Info) + if err != nil { + t.Fatalf("serveAll: %v", err) + } + if res.StatusCode != tCase.Output.StatusCode { + t.Fatalf("serveAll: statusCode not match got: %v want: %v", res.StatusCode, tCase.Output.StatusCode) + } + if string(res.Response) != tCase.Output.Body { + t.Fatalf("serveAll: body not match \ngot: %v \nwant: %v", string(res.Response), tCase.Output.Body) + } + for key, vals := range tCase.Output.Headers { + if res.Header().Get(key) != vals[0] { + t.Fatalf("serveAll: header not match %v got: %v want: %v", key, res.Header().Get(key), vals[0]) + } + } + if res.StatusCode != tCase.Output.StatusCode { + t.Fatalf("serveAll: statusCodes are not match %v", res.StatusCode, tCase.Output.StatusCode) + } + } +} diff --git a/server/libs/qtube/test_helper.go b/server/libs/qtube/test_helper.go new file mode 100644 index 0000000..e0fa910 --- /dev/null +++ b/server/libs/qtube/test_helper.go @@ -0,0 +1,28 @@ +package qtube + +type StubFile struct { + Content string + Offset int64 +} + +func (file *StubFile) Read(p []byte) (int, error) { + copied := copy(p[:], []byte(file.Content)[:len(p)]) + return copied, nil +} + +func (file *StubFile) Seek(offset int64, whence int) (int64, error) { + file.Offset = offset + return offset, nil +} + +func (file *StubFile) Close() error { + return nil +} + +type stubFiler struct { + file *StubFile +} + +func (filer *stubFiler) Open(filePath string) (ReadSeekCloser, error) { + return filer.file, nil +} diff --git a/server/libs/walls/access_walls.go b/server/libs/walls/access_walls.go new file mode 100644 index 0000000..e1d3b65 --- /dev/null +++ b/server/libs/walls/access_walls.go @@ -0,0 +1,102 @@ +package walls + +import ( + "fmt" + "net/http" + "strconv" + "time" +) + +import ( + "quickshare/server/libs/cfg" + "quickshare/server/libs/encrypt" + "quickshare/server/libs/limiter" +) + +type AccessWalls struct { + cf *cfg.Config + IpLimiter limiter.Limiter + OpLimiter limiter.Limiter + EncrypterMaker encrypt.EncrypterMaker +} + +func NewAccessWalls( + cf *cfg.Config, + ipLimiter limiter.Limiter, + opLimiter limiter.Limiter, + encrypterMaker encrypt.EncrypterMaker, +) Walls { + return &AccessWalls{ + cf: cf, + IpLimiter: ipLimiter, + OpLimiter: opLimiter, + EncrypterMaker: encrypterMaker, + } +} + +func (walls *AccessWalls) PassIpLimit(remoteAddr string) bool { + if !walls.cf.Production { + return true + } + return walls.IpLimiter.Access(remoteAddr, walls.cf.OpIdIpVisit) + +} + +func (walls *AccessWalls) PassOpLimit(resourceId string, opId int16) bool { + if !walls.cf.Production { + return true + } + return walls.OpLimiter.Access(resourceId, opId) +} + +func (walls *AccessWalls) PassLoginCheck(tokenStr string, req *http.Request) bool { + if !walls.cf.Production { + return true + } + + return walls.passLoginCheck(tokenStr) +} + +func (walls *AccessWalls) passLoginCheck(tokenStr string) bool { + token, getLoginTokenOk := walls.GetLoginToken(tokenStr) + return getLoginTokenOk && token.AdminId == walls.cf.AdminId +} + +func (walls *AccessWalls) GetLoginToken(tokenStr string) (*LoginToken, bool) { + tokenMaker := walls.EncrypterMaker(string(walls.cf.SecretKeyByte)) + if !tokenMaker.FromStr(tokenStr) { + return nil, false + } + + adminIdFromToken, adminIdOk := tokenMaker.Get(walls.cf.KeyAdminId) + expiresStr, expiresStrOk := tokenMaker.Get(walls.cf.KeyExpires) + if !adminIdOk || !expiresStrOk { + return nil, false + } + + expires, expiresParseErr := strconv.ParseInt(expiresStr, 10, 64) + if expiresParseErr != nil || + adminIdFromToken != walls.cf.AdminId || + expires <= time.Now().Unix() { + return nil, false + } + + return &LoginToken{ + AdminId: adminIdFromToken, + Expires: expires, + }, true +} + +func (walls *AccessWalls) MakeLoginToken(userId string) string { + expires := time.Now().Add(time.Duration(walls.cf.CookieMaxAge) * time.Second).Unix() + + tokenMaker := walls.EncrypterMaker(string(walls.cf.SecretKeyByte)) + tokenMaker.Add(walls.cf.KeyAdminId, userId) + tokenMaker.Add(walls.cf.KeyExpires, fmt.Sprintf("%d", expires)) + + tokenStr, ok := tokenMaker.ToStr() + if !ok { + return "" + } + return tokenStr +} diff --git a/server/libs/walls/access_walls_test.go b/server/libs/walls/access_walls_test.go new file mode 100644 index 0000000..5e3afad --- /dev/null +++ b/server/libs/walls/access_walls_test.go @@ -0,0 +1,145 @@ +package walls + +import ( + "fmt" + "testing" + "time" +) + +import ( + "quickshare/server/libs/cfg" + "quickshare/server/libs/encrypt" + "quickshare/server/libs/limiter" +) + +func newAccessWalls(limiterCap int64, limiterTtl int32, limiterCyc int32, bucketCap int16) *AccessWalls { + config := cfg.NewConfig() + config.Production = true + config.LimiterCap = limiterCap + config.LimiterTtl = limiterTtl + config.LimiterCyc = limiterCyc + config.BucketCap = bucketCap + encrypterMaker := encrypt.JwtEncrypterMaker + ipLimiter := limiter.NewRateLimiter(config.LimiterCap, config.LimiterTtl, config.LimiterCyc, config.BucketCap, map[int16]int16{}) + opLimiter := limiter.NewRateLimiter(config.LimiterCap, config.LimiterTtl, config.LimiterCyc, config.BucketCap, map[int16]int16{}) + + return NewAccessWalls(config, ipLimiter, opLimiter, encrypterMaker).(*AccessWalls) +} +func TestIpLimit(t *testing.T) { + ip := "0.0.0.0" + limit := int16(10) + ttl := int32(60) + cyc := int32(5) + walls := newAccessWalls(1000, ttl, cyc, limit) + + testIpLimit(t, walls, ip, limit) + // wait for tokens are re-fullfilled + time.Sleep(time.Duration(cyc) * time.Second) + testIpLimit(t, walls, ip, limit) + + fmt.Println("ip limit: passed") +} + +func testIpLimit(t *testing.T, walls Walls, ip string, limit int16) { + for i := int16(0); i < limit; i++ { + if !walls.PassIpLimit(ip) { + t.Fatalf("ipLimiter: should be passed", time.Now().Unix()) + } + } + + if walls.PassIpLimit(ip) { + t.Fatalf("ipLimiter: should not be passed", time.Now().Unix()) + } +} + +func TestOpLimit(t *testing.T) { + resourceId := "id" + op1 := int16(1) + op2 := int16(2) + limit := int16(10) + ttl := int32(1) + walls := newAccessWalls(1000, 5, ttl, limit) + + testOpLimit(t, walls, resourceId, op1, limit) + testOpLimit(t, walls, resourceId, op2, limit) + // wait for tokens are re-fullfilled + time.Sleep(time.Duration(ttl) * time.Second) + testOpLimit(t, walls, resourceId, op1, limit) + testOpLimit(t, walls, resourceId, op2, limit) + + fmt.Println("op limit: passed") +} + +func testOpLimit(t *testing.T, walls Walls, resourceId string, op int16, limit int16) { + for i := int16(0); i < limit; i++ { + if !walls.PassOpLimit(resourceId, op) { + t.Fatalf("opLimiter: should be passed") + } + } + + if walls.PassOpLimit(resourceId, op) { + t.Fatalf("opLimiter: should not be passed") + } +} + +func TestLoginCheck(t *testing.T) { + walls := newAccessWalls(1000, 5, 1, 10) + + testValidToken(t, walls) + testInvalidAdminIdToken(t, walls) + testExpiredToken(t, walls) +} + +func testValidToken(t *testing.T, walls *AccessWalls) { + config := cfg.NewConfig() + + tokenMaker := encrypt.JwtEncrypterMaker(string(config.SecretKeyByte)) + tokenMaker.Add(config.KeyAdminId, config.AdminId) + tokenMaker.Add(config.KeyExpires, fmt.Sprintf("%d", time.Now().Unix()+int64(10))) + tokenStr, getTokenOk := tokenMaker.ToStr() + if !getTokenOk { + t.Fatalf("passLoginCheck: fail to generate token") + } + + if !walls.passLoginCheck(tokenStr) { + t.Fatalf("loginCheck: should be passed") + } + + fmt.Println("loginCheck: valid token passed") +} + +func testInvalidAdminIdToken(t *testing.T, walls *AccessWalls) { + config := cfg.NewConfig() + + tokenMaker := encrypt.JwtEncrypterMaker(string(config.SecretKeyByte)) + tokenMaker.Add(config.KeyAdminId, "invalid admin id") + tokenMaker.Add(config.KeyExpires, fmt.Sprintf("%d", time.Now().Unix()+int64(10))) + tokenStr, getTokenOk := tokenMaker.ToStr() + if !getTokenOk { + t.Fatalf("passLoginCheck: fail to generate token") + } + + if walls.passLoginCheck(tokenStr) { + t.Fatalf("loginCheck: should not be passed") + } + + fmt.Println("loginCheck: invalid admin id passed") +} + +func testExpiredToken(t *testing.T, walls *AccessWalls) { + config := cfg.NewConfig() + + tokenMaker := encrypt.JwtEncrypterMaker(string(config.SecretKeyByte)) + tokenMaker.Add(config.KeyAdminId, config.AdminId) + tokenMaker.Add(config.KeyExpires, fmt.Sprintf("%d", time.Now().Unix()-int64(1))) + tokenStr, getTokenOk := tokenMaker.ToStr() + if !getTokenOk { + t.Fatalf("passLoginCheck: fail to generate token") + } + + if walls.passLoginCheck(tokenStr) { + t.Fatalf("loginCheck: should not be passed") + } + + fmt.Println("loginCheck: expired token passed") +} diff --git a/server/libs/walls/walls.go b/server/libs/walls/walls.go new file mode 100644 index 0000000..d42306d --- /dev/null +++ b/server/libs/walls/walls.go @@ -0,0 +1,17 @@ +package walls + +import ( + "net/http" +) + +type Walls interface { + PassIpLimit(remoteAddr string) bool + PassOpLimit(resourceId string, opId int16) bool + PassLoginCheck(tokenStr string, req *http.Request) bool + MakeLoginToken(uid string) string +} + +type LoginToken struct { + AdminId string + Expires int64 +} diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..c74c478 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,6993 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node@*": + version "10.1.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.1.2.tgz#1b928a0baa408fc8ae3ac012cc81375addc147c6" + +abab@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + +accepts@~1.3.4, accepts@~1.3.5: + version "1.3.5" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" + dependencies: + mime-types "~2.1.18" + negotiator "0.6.1" + +acorn-dynamic-import@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4" + dependencies: + acorn "^4.0.3" + +acorn-globals@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf" + dependencies: + acorn "^4.0.4" + +acorn-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + dependencies: + acorn "^3.0.4" + +acorn@^3.0.4: + version "3.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + +acorn@^4.0.3, acorn@^4.0.4: + version "4.0.13" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" + +acorn@^5.0.0, acorn@^5.5.0: + version "5.5.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.5.3.tgz#f473dd47e0277a08e28e9bec5aeeb04751f0b8c9" + +ajv-keywords@^1.1.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" + +ajv-keywords@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.1.tgz#617997fc5f60576894c435f940d819e135b80762" + +ajv-keywords@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" + +ajv@^4.7.0: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + +ajv@^5.1.0, ajv@^5.2.3, ajv@^5.3.0: + version "5.5.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + +ajv@^6.1.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.0.tgz#4c8affdf80887d8f132c9c52ab8a2dc4d0b7b24c" + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + uri-js "^4.2.1" + +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" + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + +alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + +ansi-escapes@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" + +ansi-html@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + dependencies: + color-convert "^1.9.0" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +append-transform@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" + dependencies: + default-require-extensions "^1.0.0" + +aproba@^1.0.3, aproba@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + +are-we-there-yet@~1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + dependencies: + sprintf-js "~1.0.2" + +aria-query@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-0.7.1.tgz#26cbb5aff64144b0a825be1846e0b16cfa00b11e" + dependencies: + ast-types-flow "0.0.7" + commander "^2.11.0" + +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + dependencies: + arr-flatten "^1.0.1" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + +arr-flatten@^1.0.1, arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + +array-flatten@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296" + +array-includes@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.7.0" + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + +arrify@^1.0.0, arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + +asap@~2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + +asn1.js@^4.0.0: + version "4.10.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +asn1@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +assert@^1.1.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" + dependencies: + util "0.10.3" + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + +ast-types-flow@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" + +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + +async-each@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + +async@^1.4.0, async@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + +async@^2.1.2, async@^2.1.4: + version "2.6.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" + dependencies: + lodash "^4.17.10" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +atob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.1.tgz#ae2d5a729477f289d60dd7f96a6314a22dd6c22a" + +autoprefixer@^6.3.1: + version "6.7.7" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" + dependencies: + browserslist "^1.7.6" + caniuse-db "^1.0.30000634" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + postcss "^5.2.16" + postcss-value-parser "^3.2.3" + +aws-sign2@~0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + +aws4@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289" + +axios@^0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.16.2.tgz#ba4f92f17167dfbab40983785454b9ac149c3c6d" + dependencies: + follow-redirects "^1.2.3" + is-buffer "^1.1.5" + +axobject-query@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-0.1.0.tgz#62f59dbc59c9f9242759ca349960e7a2fe3c36c0" + dependencies: + ast-types-flow "0.0.7" + +babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-core@^6.0.0, babel-core@^6.26.0: + version "6.26.3" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" + dependencies: + babel-code-frame "^6.26.0" + babel-generator "^6.26.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.26.0" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + convert-source-map "^1.5.1" + debug "^2.6.9" + json5 "^0.5.1" + lodash "^4.17.4" + minimatch "^3.0.4" + path-is-absolute "^1.0.1" + private "^0.1.8" + slash "^1.0.0" + source-map "^0.5.7" + +babel-generator@^6.18.0, babel-generator@^6.26.0: + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.17.4" + source-map "^0.5.7" + trim-right "^1.0.1" + +babel-helper-bindify-decorators@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz#14c19e5f142d7b47f19a52431e52b1ccbc40a330" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" + dependencies: + babel-helper-explode-assignable-expression "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-builder-react-jsx@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0" + dependencies: + babel-runtime "^6.26.0" + babel-types "^6.26.0" + esutils "^2.0.2" + +babel-helper-call-delegate@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-define-map@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-helper-explode-assignable-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-explode-class@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz#7dc2a3910dee007056e1e31d640ced3d54eaa9eb" + dependencies: + babel-helper-bindify-decorators "^6.24.1" + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-function-name@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" + dependencies: + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-get-function-arity@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-hoist-variables@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-optimise-call-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-regex@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" + dependencies: + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-helper-remap-async-to-generator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-replace-supers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" + dependencies: + babel-helper-optimise-call-expression "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helpers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-jest@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-21.2.0.tgz#2ce059519a9374a2c46f2455b6fbef5ad75d863e" + dependencies: + babel-plugin-istanbul "^4.0.0" + babel-preset-jest "^21.2.0" + +babel-loader@^6.4.1: + version "6.4.1" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-6.4.1.tgz#0b34112d5b0748a8dcdbf51acf6f9bd42d50b8ca" + dependencies: + find-cache-dir "^0.1.1" + loader-utils "^0.2.16" + mkdirp "^0.5.1" + object-assign "^4.0.1" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-check-es2015-constants@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-istanbul@^4.0.0: + version "4.1.6" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45" + dependencies: + babel-plugin-syntax-object-rest-spread "^6.13.0" + find-up "^2.1.0" + istanbul-lib-instrument "^1.10.1" + test-exclude "^4.2.1" + +babel-plugin-jest-hoist@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-21.2.0.tgz#2cef637259bd4b628a6cace039de5fcd14dbb006" + +babel-plugin-syntax-async-functions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + +babel-plugin-syntax-async-generators@^6.5.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a" + +babel-plugin-syntax-class-constructor-call@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz#9cb9d39fe43c8600bec8146456ddcbd4e1a76416" + +babel-plugin-syntax-class-properties@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" + +babel-plugin-syntax-decorators@^6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b" + +babel-plugin-syntax-do-expressions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz#5747756139aa26d390d09410b03744ba07e4796d" + +babel-plugin-syntax-dynamic-import@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" + +babel-plugin-syntax-exponentiation-operator@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" + +babel-plugin-syntax-export-extensions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz#70a1484f0f9089a4e84ad44bac353c95b9b12721" + +babel-plugin-syntax-flow@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" + +babel-plugin-syntax-function-bind@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz#48c495f177bdf31a981e732f55adc0bdd2601f46" + +babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + +babel-plugin-syntax-object-rest-spread@^6.13.0, babel-plugin-syntax-object-rest-spread@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" + +babel-plugin-syntax-trailing-function-commas@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" + +babel-plugin-transform-async-generator-functions@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz#f058900145fd3e9907a6ddf28da59f215258a5db" + dependencies: + babel-helper-remap-async-to-generator "^6.24.1" + babel-plugin-syntax-async-generators "^6.5.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-async-to-generator@^6.22.0, babel-plugin-transform-async-to-generator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" + dependencies: + babel-helper-remap-async-to-generator "^6.24.1" + babel-plugin-syntax-async-functions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-class-constructor-call@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz#80dc285505ac067dcb8d6c65e2f6f11ab7765ef9" + dependencies: + babel-plugin-syntax-class-constructor-call "^6.18.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-class-properties@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac" + dependencies: + babel-helper-function-name "^6.24.1" + babel-plugin-syntax-class-properties "^6.8.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-decorators@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz#788013d8f8c6b5222bdf7b344390dfd77569e24d" + dependencies: + babel-helper-explode-class "^6.24.1" + babel-plugin-syntax-decorators "^6.13.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-do-expressions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz#28ccaf92812d949c2cd1281f690c8fdc468ae9bb" + dependencies: + babel-plugin-syntax-do-expressions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-arrow-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoping@^6.23.0, babel-plugin-transform-es2015-block-scoping@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" + dependencies: + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-plugin-transform-es2015-classes@^6.23.0, babel-plugin-transform-es2015-classes@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" + dependencies: + babel-helper-define-map "^6.24.1" + babel-helper-function-name "^6.24.1" + babel-helper-optimise-call-expression "^6.24.1" + babel-helper-replace-supers "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-computed-properties@^6.22.0, babel-plugin-transform-es2015-computed-properties@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-destructuring@^6.22.0, babel-plugin-transform-es2015-destructuring@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-duplicate-keys@^6.22.0, babel-plugin-transform-es2015-duplicate-keys@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-for-of@^6.22.0, babel-plugin-transform-es2015-for-of@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-function-name@^6.22.0, babel-plugin-transform-es2015-function-name@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" + dependencies: + babel-plugin-transform-es2015-modules-commonjs "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: + version "6.26.2" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" + dependencies: + babel-plugin-transform-strict-mode "^6.24.1" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-types "^6.26.0" + +babel-plugin-transform-es2015-modules-systemjs@^6.23.0, babel-plugin-transform-es2015-modules-systemjs@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-umd@^6.23.0, babel-plugin-transform-es2015-modules-umd@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" + dependencies: + babel-plugin-transform-es2015-modules-amd "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-object-super@^6.22.0, babel-plugin-transform-es2015-object-super@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" + dependencies: + babel-helper-replace-supers "^6.24.1" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-parameters@^6.23.0, babel-plugin-transform-es2015-parameters@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" + dependencies: + babel-helper-call-delegate "^6.24.1" + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-shorthand-properties@^6.22.0, babel-plugin-transform-es2015-shorthand-properties@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-spread@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-sticky-regex@^6.22.0, babel-plugin-transform-es2015-sticky-regex@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-template-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-typeof-symbol@^6.22.0, babel-plugin-transform-es2015-typeof-symbol@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-unicode-regex@^6.22.0, babel-plugin-transform-es2015-unicode-regex@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + regexpu-core "^2.0.0" + +babel-plugin-transform-exponentiation-operator@^6.22.0, babel-plugin-transform-exponentiation-operator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" + dependencies: + babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" + babel-plugin-syntax-exponentiation-operator "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-export-extensions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz#53738b47e75e8218589eea946cbbd39109bbe653" + dependencies: + babel-plugin-syntax-export-extensions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-flow-strip-types@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" + dependencies: + babel-plugin-syntax-flow "^6.18.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-function-bind@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz#c6fb8e96ac296a310b8cf8ea401462407ddf6a97" + dependencies: + babel-plugin-syntax-function-bind "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-object-rest-spread@^6.22.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" + dependencies: + babel-plugin-syntax-object-rest-spread "^6.8.0" + babel-runtime "^6.26.0" + +babel-plugin-transform-react-display-name@^6.23.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz#67e2bf1f1e9c93ab08db96792e05392bf2cc28d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx-self@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz#df6d80a9da2612a121e6ddd7558bcbecf06e636e" + dependencies: + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx-source@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6" + dependencies: + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3" + dependencies: + babel-helper-builder-react-jsx "^6.24.1" + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-regenerator@^6.22.0, babel-plugin-transform-regenerator@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" + dependencies: + regenerator-transform "^0.10.0" + +babel-plugin-transform-strict-mode@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-preset-env@^1.2.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a" + dependencies: + babel-plugin-check-es2015-constants "^6.22.0" + babel-plugin-syntax-trailing-function-commas "^6.22.0" + babel-plugin-transform-async-to-generator "^6.22.0" + babel-plugin-transform-es2015-arrow-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoping "^6.23.0" + babel-plugin-transform-es2015-classes "^6.23.0" + babel-plugin-transform-es2015-computed-properties "^6.22.0" + babel-plugin-transform-es2015-destructuring "^6.23.0" + babel-plugin-transform-es2015-duplicate-keys "^6.22.0" + babel-plugin-transform-es2015-for-of "^6.23.0" + babel-plugin-transform-es2015-function-name "^6.22.0" + babel-plugin-transform-es2015-literals "^6.22.0" + babel-plugin-transform-es2015-modules-amd "^6.22.0" + babel-plugin-transform-es2015-modules-commonjs "^6.23.0" + babel-plugin-transform-es2015-modules-systemjs "^6.23.0" + babel-plugin-transform-es2015-modules-umd "^6.23.0" + babel-plugin-transform-es2015-object-super "^6.22.0" + babel-plugin-transform-es2015-parameters "^6.23.0" + babel-plugin-transform-es2015-shorthand-properties "^6.22.0" + babel-plugin-transform-es2015-spread "^6.22.0" + babel-plugin-transform-es2015-sticky-regex "^6.22.0" + babel-plugin-transform-es2015-template-literals "^6.22.0" + babel-plugin-transform-es2015-typeof-symbol "^6.23.0" + babel-plugin-transform-es2015-unicode-regex "^6.22.0" + babel-plugin-transform-exponentiation-operator "^6.22.0" + babel-plugin-transform-regenerator "^6.22.0" + browserslist "^3.2.6" + invariant "^2.2.2" + semver "^5.3.0" + +babel-preset-es2015@^6.24.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939" + dependencies: + babel-plugin-check-es2015-constants "^6.22.0" + babel-plugin-transform-es2015-arrow-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoping "^6.24.1" + babel-plugin-transform-es2015-classes "^6.24.1" + babel-plugin-transform-es2015-computed-properties "^6.24.1" + babel-plugin-transform-es2015-destructuring "^6.22.0" + babel-plugin-transform-es2015-duplicate-keys "^6.24.1" + babel-plugin-transform-es2015-for-of "^6.22.0" + babel-plugin-transform-es2015-function-name "^6.24.1" + babel-plugin-transform-es2015-literals "^6.22.0" + babel-plugin-transform-es2015-modules-amd "^6.24.1" + babel-plugin-transform-es2015-modules-commonjs "^6.24.1" + babel-plugin-transform-es2015-modules-systemjs "^6.24.1" + babel-plugin-transform-es2015-modules-umd "^6.24.1" + babel-plugin-transform-es2015-object-super "^6.24.1" + babel-plugin-transform-es2015-parameters "^6.24.1" + babel-plugin-transform-es2015-shorthand-properties "^6.24.1" + babel-plugin-transform-es2015-spread "^6.22.0" + babel-plugin-transform-es2015-sticky-regex "^6.24.1" + babel-plugin-transform-es2015-template-literals "^6.22.0" + babel-plugin-transform-es2015-typeof-symbol "^6.22.0" + babel-plugin-transform-es2015-unicode-regex "^6.24.1" + babel-plugin-transform-regenerator "^6.24.1" + +babel-preset-flow@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d" + dependencies: + babel-plugin-transform-flow-strip-types "^6.22.0" + +babel-preset-jest@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-21.2.0.tgz#ff9d2bce08abd98e8a36d9a8a5189b9173b85638" + dependencies: + babel-plugin-jest-hoist "^21.2.0" + babel-plugin-syntax-object-rest-spread "^6.13.0" + +babel-preset-react@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.24.1.tgz#ba69dfaea45fc3ec639b6a4ecea6e17702c91380" + dependencies: + babel-plugin-syntax-jsx "^6.3.13" + babel-plugin-transform-react-display-name "^6.23.0" + babel-plugin-transform-react-jsx "^6.24.1" + babel-plugin-transform-react-jsx-self "^6.22.0" + babel-plugin-transform-react-jsx-source "^6.22.0" + babel-preset-flow "^6.23.0" + +babel-preset-stage-0@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz#5642d15042f91384d7e5af8bc88b1db95b039e6a" + dependencies: + babel-plugin-transform-do-expressions "^6.22.0" + babel-plugin-transform-function-bind "^6.22.0" + babel-preset-stage-1 "^6.24.1" + +babel-preset-stage-1@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz#7692cd7dcd6849907e6ae4a0a85589cfb9e2bfb0" + dependencies: + babel-plugin-transform-class-constructor-call "^6.24.1" + babel-plugin-transform-export-extensions "^6.22.0" + babel-preset-stage-2 "^6.24.1" + +babel-preset-stage-2@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz#d9e2960fb3d71187f0e64eec62bc07767219bdc1" + dependencies: + babel-plugin-syntax-dynamic-import "^6.18.0" + babel-plugin-transform-class-properties "^6.24.1" + babel-plugin-transform-decorators "^6.24.1" + babel-preset-stage-3 "^6.24.1" + +babel-preset-stage-3@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz#836ada0a9e7a7fa37cb138fb9326f87934a48395" + dependencies: + babel-plugin-syntax-trailing-function-commas "^6.22.0" + babel-plugin-transform-async-generator-functions "^6.24.1" + babel-plugin-transform-async-to-generator "^6.24.1" + babel-plugin-transform-exponentiation-operator "^6.24.1" + babel-plugin-transform-object-rest-spread "^6.22.0" + +babel-register@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" + dependencies: + babel-core "^6.26.0" + babel-runtime "^6.26.0" + core-js "^2.5.0" + home-or-tmp "^2.0.0" + lodash "^4.17.4" + mkdirp "^0.5.1" + source-map-support "^0.4.15" + +babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" + dependencies: + babel-runtime "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + lodash "^4.17.4" + +babel-traverse@^6.18.0, babel-traverse@^6.24.1, babel-traverse@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + dependencies: + babel-code-frame "^6.26.0" + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" + lodash "^4.17.4" + +babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + +balanced-match@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +base64-js@^1.0.2: + version "1.3.0" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + +bcrypt-pbkdf@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + dependencies: + tweetnacl "^0.14.3" + +big.js@^3.1.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" + +binary-extensions@^1.0.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" + +bluebird@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: + version "4.11.8" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" + +body-parser@1.18.2: + version "1.18.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" + dependencies: + bytes "3.0.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.1" + http-errors "~1.6.2" + iconv-lite "0.4.19" + on-finished "~2.3.0" + qs "6.5.1" + raw-body "2.3.2" + type-is "~1.6.15" + +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + +boolbase@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" + +braces@^2.3.0, braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +brorand@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + +browser-resolve@^1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" + dependencies: + resolve "1.1.7" + +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.1.tgz#3343124db6d7ad53e26a8826318712bdc8450f9c" + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + +browserify-rsa@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + dependencies: + bn.js "^4.1.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" + dependencies: + bn.js "^4.1.1" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.2" + elliptic "^6.0.0" + inherits "^2.0.1" + parse-asn1 "^5.0.0" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + dependencies: + pako "~1.0.5" + +browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: + version "1.7.7" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" + dependencies: + caniuse-db "^1.0.30000639" + electron-to-chromium "^1.2.7" + +browserslist@^3.2.6: + version "3.2.8" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6" + dependencies: + caniuse-lite "^1.0.30000844" + electron-to-chromium "^1.3.47" + +bser@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531" + +buffer-indexof@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + +buffer@^4.3.0: + version "4.9.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-modules@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + +byte-size@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-4.0.3.tgz#b7c095efc68eadf82985fccd9a2df43a74fa2ccd" + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + +cacache@^10.0.4: + version "10.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460" + dependencies: + bluebird "^3.5.1" + chownr "^1.0.1" + glob "^7.1.2" + graceful-fs "^4.1.11" + lru-cache "^4.1.1" + mississippi "^2.0.0" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + promise-inflight "^1.0.1" + rimraf "^2.6.2" + ssri "^5.2.4" + unique-filename "^1.1.0" + y18n "^4.0.0" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + dependencies: + callsites "^0.2.0" + +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + +camel-case@3.0.x: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73" + dependencies: + no-case "^2.2.0" + upper-case "^1.1.1" + +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + +camelcase@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + +caniuse-api@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" + dependencies: + browserslist "^1.3.6" + caniuse-db "^1.0.30000529" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: + version "1.0.30000846" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000846.tgz#d9c86f914738db4da098eeded997413c44561bd2" + +caniuse-lite@^1.0.30000844: + version "1.0.30000844" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000844.tgz#de7c84cde0582143cf4f5abdf1b98e5a0539ad4a" + +capture-exit@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-1.2.0.tgz#1c5fcc489fd0ab00d4f1ac7ae1072e3173fbab6f" + dependencies: + rsvp "^3.3.3" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chardet@^0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2" + +charenc@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" + +cheerio@^1.0.0-rc.2: + version "1.0.0-rc.2" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" + dependencies: + css-select "~1.2.0" + dom-serializer "~0.1.0" + entities "~1.1.1" + htmlparser2 "^3.9.1" + lodash "^4.15.0" + parse5 "^3.0.1" + +chokidar@^2.0.0, chokidar@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.3.tgz#dcbd4f6cbb2a55b4799ba8a840ac527e5f4b1176" + dependencies: + anymatch "^2.0.0" + async-each "^1.0.0" + braces "^2.3.0" + glob-parent "^3.1.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^2.1.1" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + upath "^1.0.0" + optionalDependencies: + fsevents "^1.1.2" + +chownr@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" + +ci-info@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.1.3.tgz#710193264bb05c77b8c90d02f5aaf22216a667b2" + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +circular-json@^0.3.1: + version "0.3.3" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" + +clap@^1.0.9: + version "1.2.3" + resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.3.tgz#4f36745b32008492557f46412d66d50cb99bce51" + dependencies: + chalk "^1.1.3" + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +clean-css@4.1.x: + version "4.1.11" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.11.tgz#2ecdf145aba38f54740f26cefd0ff3e03e125d6a" + dependencies: + source-map "0.5.x" + +clean-webpack-plugin@^0.1.19: + version "0.1.19" + resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-0.1.19.tgz#ceda8bb96b00fe168e9b080272960d20fdcadd6d" + dependencies: + rimraf "^2.6.1" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + dependencies: + restore-cursor "^2.0.0" + +cli-width@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" + +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +coa@~1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd" + dependencies: + q "^1.1.2" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.3.0, color-convert@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" + dependencies: + color-name "^1.1.1" + +color-name@^1.0.0, color-name@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + +color-string@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991" + dependencies: + color-name "^1.0.0" + +color@^0.11.0: + version "0.11.4" + resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764" + dependencies: + clone "^1.0.2" + color-convert "^1.3.0" + color-string "^0.3.0" + +colormin@^1.0.5: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colormin/-/colormin-1.1.2.tgz#ea2f7420a72b96881a38aae59ec124a6f7298133" + dependencies: + color "^0.11.0" + css-color-names "0.0.4" + has "^1.0.1" + +colors@0.5.x: + version "0.5.1" + resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774" + +colors@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" + +combined-stream@1.0.6, combined-stream@~1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" + dependencies: + delayed-stream "~1.0.0" + +commander@2.15.x, commander@^2.11.0, commander@~2.15.0: + version "2.15.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" + +commander@~2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + +compare-versions@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.2.1.tgz#a49eb7689d4caaf0b6db5220173fd279614000f7" + +component-emitter@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + +compressible@~2.0.13: + version "2.0.13" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.13.tgz#0d1020ab924b2fdb4d6279875c7d6daba6baa7a9" + dependencies: + mime-db ">= 1.33.0 < 2" + +compression@^1.5.2: + version "1.7.2" + resolved "http://registry.npmjs.org/compression/-/compression-1.7.2.tgz#aaffbcd6aaf854b44ebb280353d5ad1651f59a69" + dependencies: + accepts "~1.3.4" + bytes "3.0.0" + compressible "~2.0.13" + debug "2.6.9" + on-headers "~1.0.1" + safe-buffer "5.1.1" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +concat-stream@^1.5.0, concat-stream@^1.6.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +connect-history-api-fallback@^1.3.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz#b06873934bc5e344fef611a196a6faae0aee015a" + +console-browserify@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" + dependencies: + date-now "^0.1.4" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + +content-type-parser@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + +convert-source-map@^1.4.0, convert-source-map@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.1.tgz#b8278097b9bc229365de5c62cf5fcaed8b5599e5" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + +cookie@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" + +copy-concurrently@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" + dependencies: + aproba "^1.1.1" + fs-write-stream-atomic "^1.0.8" + iferr "^0.1.5" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.0" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + +core-js@^2.4.0, core-js@^2.5.0: + version "2.5.6" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.6.tgz#0fe6d45bf3cac3ac364a9d72de7576f4eb221b9d" + +core-util-is@1.0.2, 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" + +create-ecdh@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" + dependencies: + bn.js "^4.1.0" + elliptic "^6.0.0" + +create-hash@^1.1.0, create-hash@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +create-react-class@^15.6.0: + version "15.6.3" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + object-assign "^4.1.1" + +cross-spawn@^5.0.1, cross-spawn@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + +crypt@~0.0.1: + version "0.0.2" + resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" + +crypto-browserify@^3.11.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + +css-color-names@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" + +css-loader@^0.28.11: + version "0.28.11" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.11.tgz#c3f9864a700be2711bb5a2462b2389b1a392dab7" + dependencies: + babel-code-frame "^6.26.0" + css-selector-tokenizer "^0.7.0" + cssnano "^3.10.0" + icss-utils "^2.1.0" + loader-utils "^1.0.2" + lodash.camelcase "^4.3.0" + object-assign "^4.1.1" + postcss "^5.0.6" + postcss-modules-extract-imports "^1.2.0" + postcss-modules-local-by-default "^1.2.0" + postcss-modules-scope "^1.1.0" + postcss-modules-values "^1.3.0" + postcss-value-parser "^3.3.0" + source-list-map "^2.0.0" + +css-select@^1.1.0, css-select@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" + dependencies: + boolbase "~1.0.0" + css-what "2.1" + domutils "1.5.1" + nth-check "~1.0.1" + +css-selector-tokenizer@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86" + dependencies: + cssesc "^0.1.0" + fastparse "^1.1.1" + regexpu-core "^1.0.0" + +css-what@2.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.0.tgz#9467d032c38cfaefb9f2d79501253062f87fa1bd" + +cssesc@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-0.1.0.tgz#c814903e45623371a0477b40109aaafbeeaddbb4" + +cssnano@^3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-3.10.0.tgz#4f38f6cea2b9b17fa01490f23f1dc68ea65c1c38" + dependencies: + autoprefixer "^6.3.1" + decamelize "^1.1.2" + defined "^1.0.0" + has "^1.0.1" + object-assign "^4.0.1" + postcss "^5.0.14" + postcss-calc "^5.2.0" + postcss-colormin "^2.1.8" + postcss-convert-values "^2.3.4" + postcss-discard-comments "^2.0.4" + postcss-discard-duplicates "^2.0.1" + postcss-discard-empty "^2.0.1" + postcss-discard-overridden "^0.1.1" + postcss-discard-unused "^2.2.1" + postcss-filter-plugins "^2.0.0" + postcss-merge-idents "^2.1.5" + postcss-merge-longhand "^2.0.1" + postcss-merge-rules "^2.0.3" + postcss-minify-font-values "^1.0.2" + postcss-minify-gradients "^1.0.1" + postcss-minify-params "^1.0.4" + postcss-minify-selectors "^2.0.4" + postcss-normalize-charset "^1.1.0" + postcss-normalize-url "^3.0.7" + postcss-ordered-values "^2.1.0" + postcss-reduce-idents "^2.2.2" + postcss-reduce-initial "^1.0.0" + postcss-reduce-transforms "^1.0.3" + postcss-svgo "^2.1.1" + postcss-unique-selectors "^2.0.2" + postcss-value-parser "^3.2.3" + postcss-zindex "^2.0.1" + +csso@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/csso/-/csso-2.3.2.tgz#ddd52c587033f49e94b71fc55569f252e8ff5f85" + dependencies: + clap "^1.0.9" + source-map "^0.5.3" + +cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" + +"cssstyle@>= 0.2.37 < 0.3.0": + version "0.2.37" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" + dependencies: + cssom "0.3.x" + +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + dependencies: + array-find-index "^1.0.1" + +cyclist@~0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" + +damerau-levenshtein@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.4.tgz#03191c432cb6eea168bb77f3a55ffdccb8978514" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +date-now@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" + +debounce@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.1.0.tgz#6a1a4ee2a9dc4b7c24bb012558dbcdb05b37f408" + +debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.6, debug@^2.6.8, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + dependencies: + ms "2.0.0" + +debug@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + dependencies: + ms "2.0.0" + +decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + +deep-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + +deep-extend@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.5.1.tgz#b894a9dd90d3023fbf1c55a394fb858eb2066f1f" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + +default-require-extensions@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" + dependencies: + strip-bom "^2.0.0" + +define-properties@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" + dependencies: + foreach "^2.0.5" + object-keys "^1.0.8" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +defined@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" + +del@^2.0.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" + dependencies: + globby "^5.0.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + rimraf "^2.2.8" + +del@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" + dependencies: + globby "^6.1.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + p-map "^1.1.1" + pify "^3.0.0" + rimraf "^2.2.8" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +depd@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" + +depd@~1.1.1, depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + +des.js@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + dependencies: + repeating "^2.0.0" + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + +detect-node@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" + +diff@^3.2.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +discontinuous-range@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" + +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + +dns-packet@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + dependencies: + buffer-indexof "^1.0.0" + +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^2.0.2, doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + dependencies: + esutils "^2.0.2" + +dom-converter@~0.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.1.4.tgz#a45ef5727b890c9bffe6d7c876e7b19cb0e17f3b" + dependencies: + utila "~0.3" + +dom-serializer@0, dom-serializer@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" + dependencies: + domelementtype "~1.1.1" + entities "~1.1.1" + +domain-browser@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + +domelementtype@1, domelementtype@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" + +domelementtype@~1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" + +domhandler@2.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.1.0.tgz#d2646f5e57f6c3bab11cf6cb05d3c0acf7412594" + dependencies: + domelementtype "1" + +domhandler@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" + dependencies: + domelementtype "1" + +domutils@1.1: + version "1.1.6" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.1.6.tgz#bddc3de099b9a2efacc51c623f28f416ecc57485" + dependencies: + domelementtype "1" + +domutils@1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + dependencies: + dom-serializer "0" + domelementtype "1" + +domutils@^1.5.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" + dependencies: + dom-serializer "0" + domelementtype "1" + +duplexify@^3.4.2, duplexify@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.0.tgz#592903f5d80b38d037220541264d69a198fb3410" + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +ecc-jsbn@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + dependencies: + jsbn "~0.1.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + +electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.47: + version "1.3.48" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.48.tgz#d3b0d8593814044e092ece2108fc3ac9aea4b900" + +elliptic@^6.0.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" + dependencies: + bn.js "^4.4.0" + brorand "^1.0.1" + hash.js "^1.0.0" + hmac-drbg "^1.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.0" + +emoji-regex@^6.1.0: + version "6.5.1" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.5.1.tgz#9baea929b155565c11ea41c6626eaa65cef992c2" + +emojis-list@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + +encoding@^0.1.11: + version "0.1.12" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" + dependencies: + iconv-lite "~0.4.13" + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + dependencies: + once "^1.4.0" + +enhanced-resolve@^3.3.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.4.0" + object-assign "^4.0.1" + tapable "^0.2.7" + +entities@^1.1.1, entities@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" + +enzyme-adapter-react-15@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-15/-/enzyme-adapter-react-15-1.0.5.tgz#99f9a03ff2c2303e517342935798a6bdfbb75fac" + dependencies: + enzyme-adapter-utils "^1.1.0" + lodash "^4.17.4" + object.assign "^4.0.4" + object.values "^1.0.4" + prop-types "^15.5.10" + +enzyme-adapter-utils@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.3.0.tgz#d6c85756826c257a8544d362cc7a67e97ea698c7" + dependencies: + lodash "^4.17.4" + object.assign "^4.0.4" + prop-types "^15.6.0" + +enzyme@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.3.0.tgz#0971abd167f2d4bf3f5bd508229e1c4b6dc50479" + dependencies: + cheerio "^1.0.0-rc.2" + function.prototype.name "^1.0.3" + has "^1.0.1" + is-boolean-object "^1.0.0" + is-callable "^1.1.3" + is-number-object "^1.0.3" + is-string "^1.0.4" + is-subset "^0.1.1" + lodash "^4.17.4" + object-inspect "^1.5.0" + object-is "^1.0.1" + object.assign "^4.1.0" + object.entries "^1.0.4" + object.values "^1.0.4" + raf "^3.4.0" + rst-selector-parser "^2.2.3" + +errno@^0.1.3, errno@~0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" + dependencies: + prr "~1.0.1" + +error-ex@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + dependencies: + is-arrayish "^0.2.1" + +es-abstract@^1.5.1, es-abstract@^1.6.1, es-abstract@^1.7.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.11.0.tgz#cce87d518f0496893b1a30cd8461835535480681" + dependencies: + es-to-primitive "^1.1.1" + function-bind "^1.1.1" + has "^1.0.1" + is-callable "^1.1.3" + is-regex "^1.0.4" + +es-to-primitive@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d" + dependencies: + is-callable "^1.1.1" + is-date-object "^1.0.1" + is-symbol "^1.0.1" + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +escodegen@^1.6.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.9.1.tgz#dbae17ef96c8e4bedb1356f4504fa4cc2f7cb7e2" + dependencies: + esprima "^3.1.3" + estraverse "^4.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +eslint-config-airbnb-base@^12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-12.1.0.tgz#386441e54a12ccd957b0a92564a4bafebd747944" + dependencies: + eslint-restricted-globals "^0.1.1" + +eslint-config-airbnb@^16.0.0: + version "16.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-16.1.0.tgz#2546bfb02cc9fe92284bf1723ccf2e87bc45ca46" + dependencies: + eslint-config-airbnb-base "^12.1.0" + +eslint-import-resolver-node@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" + dependencies: + debug "^2.6.9" + resolve "^1.5.0" + +eslint-module-utils@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz#b270362cd88b1a48ad308976ce7fa54e98411746" + dependencies: + debug "^2.6.8" + pkg-dir "^1.0.0" + +eslint-plugin-import@^2.7.0: + version "2.12.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.12.0.tgz#dad31781292d6664b25317fd049d2e2b2f02205d" + dependencies: + contains-path "^0.1.0" + debug "^2.6.8" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.1" + eslint-module-utils "^2.2.0" + has "^1.0.1" + lodash "^4.17.4" + minimatch "^3.0.3" + read-pkg-up "^2.0.0" + resolve "^1.6.0" + +eslint-plugin-jsx-a11y@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.0.3.tgz#54583d1ae442483162e040e13cc31865465100e5" + dependencies: + aria-query "^0.7.0" + array-includes "^3.0.3" + ast-types-flow "0.0.7" + axobject-query "^0.1.0" + damerau-levenshtein "^1.0.0" + emoji-regex "^6.1.0" + jsx-ast-utils "^2.0.0" + +eslint-plugin-react@^7.4.0: + version "7.8.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.8.2.tgz#e95c9c47fece55d2303d1a67c9d01b930b88a51d" + dependencies: + doctrine "^2.0.2" + has "^1.0.1" + jsx-ast-utils "^2.0.1" + prop-types "^15.6.0" + +eslint-restricted-globals@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7" + +eslint-scope@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-visitor-keys@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" + +eslint@^4.12.0: + version "4.19.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.19.1.tgz#32d1d653e1d90408854bfb296f076ec7e186a300" + dependencies: + ajv "^5.3.0" + babel-code-frame "^6.22.0" + chalk "^2.1.0" + concat-stream "^1.6.0" + cross-spawn "^5.1.0" + debug "^3.1.0" + doctrine "^2.1.0" + eslint-scope "^3.7.1" + eslint-visitor-keys "^1.0.0" + espree "^3.5.4" + esquery "^1.0.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^11.0.1" + ignore "^3.3.3" + imurmurhash "^0.1.4" + inquirer "^3.0.6" + is-resolvable "^1.0.0" + js-yaml "^3.9.1" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.4" + minimatch "^3.0.2" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + pluralize "^7.0.0" + progress "^2.0.0" + regexpp "^1.0.1" + require-uncached "^1.0.3" + semver "^5.3.0" + strip-ansi "^4.0.0" + strip-json-comments "~2.0.1" + table "4.0.2" + text-table "~0.2.0" + +espree@^3.5.4: + version "3.5.4" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" + dependencies: + acorn "^5.5.0" + acorn-jsx "^3.0.0" + +esprima@^2.6.0: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + +esprima@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + +esprima@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + +esquery@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + dependencies: + estraverse "^4.1.0" + +estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + +eventemitter3@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" + +events@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" + +eventsource@0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-0.1.6.tgz#0acede849ed7dd1ccc32c811bb11b944d4f29232" + dependencies: + original ">=0.0.5" + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +exec-sh@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.1.tgz#163b98a6e89e6b65b47c2a28d215bc1f63989c38" + dependencies: + merge "^1.1.3" + +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + dependencies: + is-posix-bracket "^0.1.0" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + dependencies: + fill-range "^2.1.0" + +expect@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/expect/-/expect-21.2.1.tgz#003ac2ac7005c3c29e73b38a272d4afadd6d1d7b" + dependencies: + ansi-styles "^3.2.0" + jest-diff "^21.2.1" + jest-get-type "^21.2.0" + jest-matcher-utils "^21.2.1" + jest-message-util "^21.2.1" + jest-regex-util "^21.2.0" + +express@^4.16.2: + version "4.16.3" + resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" + dependencies: + accepts "~1.3.5" + array-flatten "1.1.1" + body-parser "1.18.2" + content-disposition "0.5.2" + content-type "~1.0.4" + cookie "0.3.1" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.1.1" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.2" + path-to-regexp "0.1.7" + proxy-addr "~2.0.3" + qs "6.5.1" + range-parser "~1.2.0" + safe-buffer "5.1.1" + send "0.16.2" + serve-static "1.13.2" + setprototypeof "1.1.0" + statuses "~1.4.0" + type-is "~1.6.16" + utils-merge "1.0.1" + vary "~1.1.2" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +external-editor@^2.0.4: + version "2.2.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.2.0.tgz#045511cfd8d133f3846673d1047c154e214ad3d5" + dependencies: + chardet "^0.4.0" + iconv-lite "^0.4.17" + tmp "^0.0.33" + +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + dependencies: + is-extglob "^1.0.0" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +extsprintf@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + +extsprintf@^1.2.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + +fast-deep-equal@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" + +fast-deep-equal@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + +fast-json-stable-stringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + +fastparse@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.1.tgz#d1e2643b38a94d7583b479060e6c4affc94071f8" + +faye-websocket@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4" + dependencies: + websocket-driver ">=0.5.1" + +faye-websocket@~0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38" + dependencies: + websocket-driver ">=0.5.1" + +fb-watchman@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" + dependencies: + bser "^2.0.0" + +fbjs@^0.8.16, fbjs@^0.8.9: + version "0.8.16" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.9" + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + +file-loader@^1.1.11: + version "1.1.11" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-1.1.11.tgz#6fe886449b0f2a936e43cabaac0cdbfb369506f8" + dependencies: + loader-utils "^1.0.2" + schema-utils "^0.4.5" + +filename-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + +fileset@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" + dependencies: + glob "^7.0.3" + minimatch "^3.0.3" + +fill-range@^2.1.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" + dependencies: + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^3.0.0" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +finalhandler@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.2" + statuses "~1.4.0" + unpipe "~1.0.0" + +find-cache-dir@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-0.1.1.tgz#c8defae57c8a52a8a784f9e31c57c742e993a0b9" + dependencies: + commondir "^1.0.1" + mkdirp "^0.5.1" + pkg-dir "^1.0.0" + +find-cache-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" + dependencies: + commondir "^1.0.1" + make-dir "^1.0.0" + pkg-dir "^2.0.0" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + +flat-cache@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.0.tgz#d3030b32b38154f4e3b7e9c709f490f7ef97c481" + dependencies: + circular-json "^0.3.1" + del "^2.0.2" + graceful-fs "^4.1.2" + write "^0.2.1" + +flatten@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" + +flush-write-stream@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd" + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.4" + +follow-redirects@^1.0.0, follow-redirects@^1.2.3: + version "1.5.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.0.tgz#234f49cf770b7f35b40e790f636ceba0c3a0ab77" + dependencies: + debug "^3.1.0" + +for-in@^1.0.1, for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +for-own@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + dependencies: + for-in "^1.0.1" + +foreach@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" + dependencies: + asynckit "^0.4.0" + combined-stream "1.0.6" + mime-types "^2.1.12" + +forwarded@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + +from2@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" + +fs-minipass@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + dependencies: + minipass "^2.2.1" + +fs-write-stream-atomic@^1.0.8: + version "1.0.10" + resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" + dependencies: + graceful-fs "^4.1.2" + iferr "^0.1.5" + imurmurhash "^0.1.4" + readable-stream "1 || 2" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +fsevents@^1.1.2, fsevents@^1.2.3: + version "1.2.4" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" + dependencies: + nan "^2.9.2" + node-pre-gyp "^0.10.0" + +function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + +function.prototype.name@^1.0.3: + version "1.1.0" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.0.tgz#8bd763cc0af860a859cc5d49384d74b932cd2327" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + is-callable "^1.1.3" + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +get-caller-file@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" + +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + dependencies: + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + dependencies: + is-glob "^2.0.0" + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^11.0.1: + version "11.5.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.5.0.tgz#6bc840de6771173b191f13d3a9c94d441ee92642" + +globals@^9.18.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + +globby@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" + dependencies: + array-union "^1.0.1" + arrify "^1.0.0" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + +handle-thing@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" + +handlebars@^4.0.3: + version "4.0.11" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + +har-schema@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + +har-validator@~5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" + dependencies: + ajv "^5.1.0" + har-schema "^2.0.0" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" + dependencies: + function-bind "^1.0.2" + +hash-base@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.0" + +he@1.1.x: + version "1.1.1" + resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" + +hmac-drbg@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +home-or-tmp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.1" + +hosted-git-info@^2.1.4: + version "2.6.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.6.0.tgz#23235b29ab230c576aab0d4f13fc046b0b038222" + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-comment-regex@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" + +html-encoding-sniffer@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" + dependencies: + whatwg-encoding "^1.0.1" + +html-entities@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" + +html-minifier@^3.2.3: + version "3.5.16" + resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.16.tgz#39f5aabaf78bdfc057fe67334226efd7f3851175" + dependencies: + camel-case "3.0.x" + clean-css "4.1.x" + commander "2.15.x" + he "1.1.x" + param-case "2.1.x" + relateurl "0.2.x" + uglify-js "3.3.x" + +html-webpack-plugin@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz#b01abbd723acaaa7b37b6af4492ebda03d9dd37b" + dependencies: + html-minifier "^3.2.3" + loader-utils "^0.2.16" + lodash "^4.17.3" + pretty-error "^2.0.2" + tapable "^1.0.0" + toposort "^1.0.0" + util.promisify "1.0.0" + +htmlparser2@^3.9.1: + version "3.9.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" + dependencies: + domelementtype "^1.3.0" + domhandler "^2.3.0" + domutils "^1.5.1" + entities "^1.1.1" + inherits "^2.0.1" + readable-stream "^2.0.2" + +htmlparser2@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.3.0.tgz#cc70d05a59f6542e43f0e685c982e14c924a9efe" + dependencies: + domelementtype "1" + domhandler "2.1" + domutils "1.1" + readable-stream "1.0" + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + +http-errors@1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" + dependencies: + depd "1.1.1" + inherits "2.0.3" + setprototypeof "1.0.3" + statuses ">= 1.3.1 < 2" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-parser-js@>=0.4.0: + version "0.4.13" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.13.tgz#3bd6d6fde6e3172c9334c3b33b6c193d80fe1137" + +http-proxy-middleware@~0.17.4: + version "0.17.4" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz#642e8848851d66f09d4f124912846dbaeb41b833" + dependencies: + http-proxy "^1.16.2" + is-glob "^3.1.0" + lodash "^4.17.2" + micromatch "^2.3.11" + +http-proxy@^1.16.2: + version "1.17.0" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" + dependencies: + eventemitter3 "^3.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +http-signature@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + dependencies: + assert-plus "^1.0.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + +iconv-lite@0.4.19: + version "0.4.19" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" + +iconv-lite@^0.4.17, iconv-lite@^0.4.4, iconv-lite@~0.4.13: + version "0.4.23" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-replace-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + +icss-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-2.1.0.tgz#83f0a0ec378bf3246178b6c2ad9136f135b1c962" + dependencies: + postcss "^6.0.1" + +ieee754@^1.1.4: + version "1.1.11" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455" + +iferr@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" + +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + dependencies: + minimatch "^3.0.4" + +ignore@^3.3.3: + version "3.3.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.8.tgz#3f8e9c35d38708a3a7e0e9abb6c73e7ee7707b2b" + +immutable@^3.8.2: + version "3.8.2" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3" + +import-local@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc" + dependencies: + pkg-dir "^2.0.0" + resolve-cwd "^2.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + dependencies: + repeating "^2.0.0" + +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + +indexof@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + +inquirer@^3.0.6: + version "3.3.0" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^2.0.4" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.1.0" + strip-ansi "^4.0.0" + through "^2.3.6" + +internal-ip@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" + dependencies: + meow "^3.3.0" + +interpret@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" + +invariant@^2.2.2: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + dependencies: + loose-envify "^1.0.0" + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + +ip@^1.1.0, ip@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + +ipaddr.js@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b" + +is-absolute-url@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + dependencies: + kind-of "^6.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + dependencies: + binary-extensions "^1.0.0" + +is-boolean-object@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93" + +is-buffer@^1.1.5, is-buffer@~1.1.1: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + +is-callable@^1.1.1, is-callable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2" + +is-ci@^1.0.10: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" + dependencies: + ci-info "^1.0.0" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-dotfile@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + dependencies: + is-primitive "^2.0.0" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + dependencies: + is-extglob "^1.0.0" + +is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + dependencies: + is-extglob "^2.1.1" + +is-number-object@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.3.tgz#f265ab89a9f445034ef6aff15a8f00b00f551799" + +is-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + dependencies: + kind-of "^3.0.2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + +is-odd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-2.0.0.tgz#7646624671fd7ea558ccd9a2795182f2958f1b24" + dependencies: + is-number "^4.0.0" + +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz#5ac48b345ef675339bd6c7a48a912110b241cf52" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" + dependencies: + path-is-inside "^1.0.1" + +is-plain-obj@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" + +is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + dependencies: + isobject "^3.0.1" + +is-posix-bracket@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + dependencies: + has "^1.0.1" + +is-resolvable@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" + +is-stream@^1.0.1, is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + +is-string@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64" + +is-subset@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-subset/-/is-subset-0.1.1.tgz#8a59117d932de1de00f245fcdd39ce43f1e939a6" + +is-svg@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" + dependencies: + html-comment-regex "^1.1.0" + +is-symbol@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572" + +is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +istanbul-api@^1.1.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.3.1.tgz#4c3b05d18c0016d1022e079b98dc82c40f488954" + dependencies: + async "^2.1.4" + compare-versions "^3.1.0" + fileset "^2.0.2" + istanbul-lib-coverage "^1.2.0" + istanbul-lib-hook "^1.2.0" + istanbul-lib-instrument "^1.10.1" + istanbul-lib-report "^1.1.4" + istanbul-lib-source-maps "^1.2.4" + istanbul-reports "^1.3.0" + js-yaml "^3.7.0" + mkdirp "^0.5.1" + once "^1.4.0" + +istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.1.2, istanbul-lib-coverage@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz#f7d8f2e42b97e37fe796114cb0f9d68b5e3a4341" + +istanbul-lib-hook@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.2.0.tgz#ae556fd5a41a6e8efa0b1002b1e416dfeaf9816c" + dependencies: + append-transform "^0.4.0" + +istanbul-lib-instrument@^1.10.1, istanbul-lib-instrument@^1.4.2: + version "1.10.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz#724b4b6caceba8692d3f1f9d0727e279c401af7b" + dependencies: + babel-generator "^6.18.0" + babel-template "^6.16.0" + babel-traverse "^6.18.0" + babel-types "^6.18.0" + babylon "^6.18.0" + istanbul-lib-coverage "^1.2.0" + semver "^5.3.0" + +istanbul-lib-report@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.4.tgz#e886cdf505c4ebbd8e099e4396a90d0a28e2acb5" + dependencies: + istanbul-lib-coverage "^1.2.0" + mkdirp "^0.5.1" + path-parse "^1.0.5" + supports-color "^3.1.2" + +istanbul-lib-source-maps@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.3.tgz#20fb54b14e14b3fb6edb6aca3571fd2143db44e6" + dependencies: + debug "^3.1.0" + istanbul-lib-coverage "^1.1.2" + mkdirp "^0.5.1" + rimraf "^2.6.1" + source-map "^0.5.3" + +istanbul-lib-source-maps@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.4.tgz#cc7ccad61629f4efff8e2f78adb8c522c9976ec7" + dependencies: + debug "^3.1.0" + istanbul-lib-coverage "^1.2.0" + mkdirp "^0.5.1" + rimraf "^2.6.1" + source-map "^0.5.3" + +istanbul-reports@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.3.0.tgz#2f322e81e1d9520767597dca3c20a0cce89a3554" + dependencies: + handlebars "^4.0.3" + +jest-changed-files@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-21.2.0.tgz#5dbeecad42f5d88b482334902ce1cba6d9798d29" + dependencies: + throat "^4.0.0" + +jest-cli@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-21.2.1.tgz#9c528b6629d651911138d228bdb033c157ec8c00" + dependencies: + ansi-escapes "^3.0.0" + chalk "^2.0.1" + glob "^7.1.2" + graceful-fs "^4.1.11" + is-ci "^1.0.10" + istanbul-api "^1.1.1" + istanbul-lib-coverage "^1.0.1" + istanbul-lib-instrument "^1.4.2" + istanbul-lib-source-maps "^1.1.0" + jest-changed-files "^21.2.0" + jest-config "^21.2.1" + jest-environment-jsdom "^21.2.1" + jest-haste-map "^21.2.0" + jest-message-util "^21.2.1" + jest-regex-util "^21.2.0" + jest-resolve-dependencies "^21.2.0" + jest-runner "^21.2.1" + jest-runtime "^21.2.1" + jest-snapshot "^21.2.1" + jest-util "^21.2.1" + micromatch "^2.3.11" + node-notifier "^5.0.2" + pify "^3.0.0" + slash "^1.0.0" + string-length "^2.0.0" + strip-ansi "^4.0.0" + which "^1.2.12" + worker-farm "^1.3.1" + yargs "^9.0.0" + +jest-config@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-21.2.1.tgz#c7586c79ead0bcc1f38c401e55f964f13bf2a480" + dependencies: + chalk "^2.0.1" + glob "^7.1.1" + jest-environment-jsdom "^21.2.1" + jest-environment-node "^21.2.1" + jest-get-type "^21.2.0" + jest-jasmine2 "^21.2.1" + jest-regex-util "^21.2.0" + jest-resolve "^21.2.0" + jest-util "^21.2.1" + jest-validate "^21.2.1" + pretty-format "^21.2.1" + +jest-diff@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-21.2.1.tgz#46cccb6cab2d02ce98bc314011764bb95b065b4f" + dependencies: + chalk "^2.0.1" + diff "^3.2.0" + jest-get-type "^21.2.0" + pretty-format "^21.2.1" + +jest-docblock@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-21.2.0.tgz#51529c3b30d5fd159da60c27ceedc195faf8d414" + +jest-environment-jsdom@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-21.2.1.tgz#38d9980c8259b2a608ec232deee6289a60d9d5b4" + dependencies: + jest-mock "^21.2.0" + jest-util "^21.2.1" + jsdom "^9.12.0" + +jest-environment-node@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-21.2.1.tgz#98c67df5663c7fbe20f6e792ac2272c740d3b8c8" + dependencies: + jest-mock "^21.2.0" + jest-util "^21.2.1" + +jest-get-type@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-21.2.0.tgz#f6376ab9db4b60d81e39f30749c6c466f40d4a23" + +jest-haste-map@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-21.2.0.tgz#1363f0a8bb4338f24f001806571eff7a4b2ff3d8" + dependencies: + fb-watchman "^2.0.0" + graceful-fs "^4.1.11" + jest-docblock "^21.2.0" + micromatch "^2.3.11" + sane "^2.0.0" + worker-farm "^1.3.1" + +jest-jasmine2@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-21.2.1.tgz#9cc6fc108accfa97efebce10c4308548a4ea7592" + dependencies: + chalk "^2.0.1" + expect "^21.2.1" + graceful-fs "^4.1.11" + jest-diff "^21.2.1" + jest-matcher-utils "^21.2.1" + jest-message-util "^21.2.1" + jest-snapshot "^21.2.1" + p-cancelable "^0.3.0" + +jest-matcher-utils@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-21.2.1.tgz#72c826eaba41a093ac2b4565f865eb8475de0f64" + dependencies: + chalk "^2.0.1" + jest-get-type "^21.2.0" + pretty-format "^21.2.1" + +jest-message-util@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-21.2.1.tgz#bfe5d4692c84c827d1dcf41823795558f0a1acbe" + dependencies: + chalk "^2.0.1" + micromatch "^2.3.11" + slash "^1.0.0" + +jest-mock@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-21.2.0.tgz#7eb0770e7317968165f61ea2a7281131534b3c0f" + +jest-regex-util@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-21.2.0.tgz#1b1e33e63143babc3e0f2e6c9b5ba1eb34b2d530" + +jest-resolve-dependencies@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-21.2.0.tgz#9e231e371e1a736a1ad4e4b9a843bc72bfe03d09" + dependencies: + jest-regex-util "^21.2.0" + +jest-resolve@^21.2.0: + version "21.2.0" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-21.2.0.tgz#068913ad2ba6a20218e5fd32471f3874005de3a6" + dependencies: + browser-resolve "^1.11.2" + chalk "^2.0.1" + is-builtin-module "^1.0.0" + +jest-runner@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-21.2.1.tgz#194732e3e518bfb3d7cbfc0fd5871246c7e1a467" + dependencies: + jest-config "^21.2.1" + jest-docblock "^21.2.0" + jest-haste-map "^21.2.0" + jest-jasmine2 "^21.2.1" + jest-message-util "^21.2.1" + jest-runtime "^21.2.1" + jest-util "^21.2.1" + pify "^3.0.0" + throat "^4.0.0" + worker-farm "^1.3.1" + +jest-runtime@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-21.2.1.tgz#99dce15309c670442eee2ebe1ff53a3cbdbbb73e" + dependencies: + babel-core "^6.0.0" + babel-jest "^21.2.0" + babel-plugin-istanbul "^4.0.0" + chalk "^2.0.1" + convert-source-map "^1.4.0" + graceful-fs "^4.1.11" + jest-config "^21.2.1" + jest-haste-map "^21.2.0" + jest-regex-util "^21.2.0" + jest-resolve "^21.2.0" + jest-util "^21.2.1" + json-stable-stringify "^1.0.1" + micromatch "^2.3.11" + slash "^1.0.0" + strip-bom "3.0.0" + write-file-atomic "^2.1.0" + yargs "^9.0.0" + +jest-snapshot@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-21.2.1.tgz#29e49f16202416e47343e757e5eff948c07fd7b0" + dependencies: + chalk "^2.0.1" + jest-diff "^21.2.1" + jest-matcher-utils "^21.2.1" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + pretty-format "^21.2.1" + +jest-util@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-21.2.1.tgz#a274b2f726b0897494d694a6c3d6a61ab819bb78" + dependencies: + callsites "^2.0.0" + chalk "^2.0.1" + graceful-fs "^4.1.11" + jest-message-util "^21.2.1" + jest-mock "^21.2.0" + jest-validate "^21.2.1" + mkdirp "^0.5.1" + +jest-validate@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-21.2.1.tgz#cc0cbca653cd54937ba4f2a111796774530dd3c7" + dependencies: + chalk "^2.0.1" + jest-get-type "^21.2.0" + leven "^2.1.0" + pretty-format "^21.2.1" + +jest@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/jest/-/jest-21.2.1.tgz#c964e0b47383768a1438e3ccf3c3d470327604e1" + dependencies: + jest-cli "^21.2.1" + +js-base64@^2.1.9: + version "2.4.5" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.5.tgz#e293cd3c7c82f070d700fc7a1ca0a2e69f101f92" + +js-tokens@^3.0.0, js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + +js-yaml@^3.7.0, js-yaml@^3.9.1: + version "3.11.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@~3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" + dependencies: + argparse "^1.0.7" + esprima "^2.6.0" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + +jsdom@^9.12.0: + version "9.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4" + dependencies: + abab "^1.0.3" + acorn "^4.0.4" + acorn-globals "^3.1.0" + array-equal "^1.0.0" + content-type-parser "^1.0.1" + cssom ">= 0.3.2 < 0.4.0" + cssstyle ">= 0.2.37 < 0.3.0" + escodegen "^1.6.1" + html-encoding-sniffer "^1.0.1" + nwmatcher ">= 1.3.9 < 2.0.0" + parse5 "^1.5.1" + request "^2.79.0" + sax "^1.2.1" + symbol-tree "^3.2.1" + tough-cookie "^2.3.2" + webidl-conversions "^4.0.0" + whatwg-encoding "^1.0.1" + whatwg-url "^4.3.0" + xml-name-validator "^2.0.1" + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + +json-loader@^0.5.4: + version "0.5.7" + resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" + +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +json3@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" + +json5@^0.5.0, json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +jsprim@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + dependencies: + assert-plus "1.0.0" + extsprintf "1.3.0" + json-schema "0.2.3" + verror "1.10.0" + +jsx-ast-utils@^2.0.0, jsx-ast-utils@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz#e801b1b39985e20fffc87b40e3748080e2dcac7f" + dependencies: + array-includes "^3.0.3" + +killable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.0.tgz#da8b84bd47de5395878f95d64d02f2449fe05e6b" + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + dependencies: + invert-kv "^1.0.0" + +leven@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +loader-runner@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" + +loader-utils@^0.2.16: + version "0.2.17" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" + dependencies: + big.js "^3.1.3" + emojis-list "^2.0.0" + json5 "^0.5.0" + object-assign "^4.0.1" + +loader-utils@^1.0.2, loader-utils@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" + dependencies: + big.js "^3.1.3" + emojis-list "^2.0.0" + json5 "^0.5.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + +lodash.flattendeep@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + +lodash.throttle@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + +lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0: + version "4.17.10" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" + +loglevel@^1.4.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" + +longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + +lower-case@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" + +lru-cache@^4.0.1, lru-cache@^4.1.1: + version "4.1.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + +make-dir@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" + dependencies: + pify "^3.0.0" + +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + dependencies: + tmpl "1.0.x" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + +map-obj@^1.0.0, map-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + dependencies: + object-visit "^1.0.0" + +math-expression-evaluator@^1.2.14: + version "1.2.17" + resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" + +math-random@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" + +md5.js@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +md5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" + dependencies: + charenc "~0.0.1" + crypt "~0.0.1" + is-buffer "~1.1.1" + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + +mem@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" + dependencies: + mimic-fn "^1.0.0" + +memory-fs@^0.4.0, memory-fs@~0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +meow@^3.3.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + +merge@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + +micromatch@^2.3.11: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + +micromatch@^3.1.4, micromatch@^3.1.8: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +"mime-db@>= 1.33.0 < 2", mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + +mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.18: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + dependencies: + mime-db "~1.33.0" + +mime@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" + +mime@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + +mimic-fn@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" + +minimalistic-assert@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + +minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + +minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + +minipass@^2.2.1, minipass@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.3.tgz#a7dcc8b7b833f5d368759cce544dccb55f50f233" + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb" + dependencies: + minipass "^2.2.1" + +mississippi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f" + dependencies: + concat-stream "^1.5.0" + duplexify "^3.4.2" + end-of-stream "^1.1.0" + flush-write-stream "^1.0.0" + from2 "^2.1.0" + parallel-transform "^1.1.0" + pump "^2.0.1" + pumpify "^1.3.3" + stream-each "^1.1.0" + through2 "^2.0.0" + +mixin-deep@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +move-concurrently@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" + dependencies: + aproba "^1.1.1" + copy-concurrently "^1.0.0" + fs-write-stream-atomic "^1.0.8" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.3" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + +multicast-dns@^6.0.1: + version "6.2.3" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" + dependencies: + dns-packet "^1.3.1" + thunky "^1.0.2" + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + +nan@^2.9.2: + version "2.10.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" + +nanomatch@^1.2.9: + version "1.2.9" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-odd "^2.0.0" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + +nearley@^2.7.10: + version "2.13.0" + resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.13.0.tgz#6e7b0f4e68bfc3e74c99eaef2eda39e513143439" + dependencies: + nomnom "~1.6.2" + railroad-diagrams "^1.0.0" + randexp "0.4.6" + semver "^5.4.1" + +needle@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.1.tgz#b5e325bd3aae8c2678902fa296f729455d1d3a7d" + dependencies: + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" + +negotiator@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" + +neo-async@^2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.1.tgz#acb909e327b1e87ec9ef15f41b8a269512ad41ee" + +no-case@^2.2.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac" + dependencies: + lower-case "^1.1.1" + +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +node-forge@0.7.5: + version "0.7.5" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.5.tgz#6c152c345ce11c52f465c2abd957e8639cd674df" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + +node-libs-browser@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df" + dependencies: + assert "^1.1.1" + browserify-zlib "^0.2.0" + buffer "^4.3.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "^3.11.0" + domain-browser "^1.1.1" + events "^1.0.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" + path-browserify "0.0.0" + process "^0.11.10" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.3.3" + stream-browserify "^2.0.1" + stream-http "^2.7.2" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.10.3" + vm-browserify "0.0.4" + +node-notifier@^5.0.2: + version "5.2.1" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.2.1.tgz#fa313dd08f5517db0e2502e5758d664ac69f9dea" + dependencies: + growly "^1.3.0" + semver "^5.4.1" + shellwords "^0.1.1" + which "^1.3.0" + +node-pre-gyp@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.0.tgz#6e4ef5bb5c5203c6552448828c852c40111aac46" + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.0" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.1.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +nomnom@~1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.6.2.tgz#84a66a260174408fc5b77a18f888eccc44fb6971" + dependencies: + colors "0.5.x" + underscore "~1.4.4" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: + version "2.4.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.0.1, normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + +normalize-url@^1.4.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c" + dependencies: + object-assign "^4.0.1" + prepend-http "^1.0.0" + query-string "^4.1.0" + sort-keys "^1.0.0" + +npm-bundled@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.3.tgz#7e71703d973af3370a9591bafe3a63aca0be2308" + +npm-packlist@^1.1.6: + version "1.1.10" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.10.tgz#1039db9e985727e464df066f4cf0ab6ef85c398a" + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +nth-check@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.1.tgz#9929acdf628fc2c41098deab82ac580cf149aae4" + dependencies: + boolbase "~1.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +"nwmatcher@>= 1.3.9 < 2.0.0": + version "1.4.4" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.4.tgz#2285631f34a95f0d0395cd900c96ed39b58f346e" + +oauth-sign@~0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-inspect@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" + +object-is@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" + +object-keys@^1.0.11, object-keys@^1.0.8: + version "1.0.11" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + dependencies: + isobject "^3.0.0" + +object.assign@^4.0.4, object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + +object.entries@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.6.1" + function-bind "^1.1.0" + has "^1.0.1" + +object.getownpropertydescriptors@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.1" + +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + dependencies: + isobject "^3.0.1" + +object.values@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a" + dependencies: + define-properties "^1.1.2" + es-abstract "^1.6.1" + function-bind "^1.1.0" + has "^1.0.1" + +obuf@^1.0.0, obuf@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + dependencies: + mimic-fn "^1.0.0" + +opn@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/opn/-/opn-5.3.0.tgz#64871565c863875f052cfdf53d3e3cb5adb53b1c" + dependencies: + is-wsl "^1.1.0" + +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + +optionator@^0.8.1, optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +original@>=0.0.5: + version "1.0.1" + resolved "https://registry.yarnpkg.com/original/-/original-1.0.1.tgz#b0a53ff42ba997a8c9cd1fb5daaeb42b9d693190" + dependencies: + url-parse "~1.4.0" + +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + dependencies: + lcid "^1.0.0" + +os-locale@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" + dependencies: + execa "^0.7.0" + lcid "^1.0.0" + mem "^1.1.0" + +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +p-cancelable@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + +p-limit@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.2.0.tgz#0e92b6bedcb59f022c13d0f1949dc82d15909f1c" + dependencies: + p-try "^1.0.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +p-map@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + +pako@~1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" + +parallel-transform@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06" + dependencies: + cyclist "~0.2.2" + inherits "^2.0.3" + readable-stream "^2.1.5" + +param-case@2.1.x: + version "2.1.1" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247" + dependencies: + no-case "^2.2.0" + +parse-asn1@^5.0.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" + dependencies: + asn1.js "^4.0.0" + browserify-aes "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + +parse5@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" + +parse5@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" + dependencies: + "@types/node" "*" + +parseurl@~1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + +path-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + +path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-is-inside@^1.0.1, path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + +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" + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + +pbkdf2@^3.0.3: + version "3.0.16" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c" + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +performance-now@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + dependencies: + find-up "^1.0.0" + +pkg-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" + dependencies: + find-up "^2.1.0" + +pluralize@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-7.0.0.tgz#298b89df8b93b0221dbf421ad2b1b1ea23fc6777" + +portfinder@^1.0.9: + version "1.0.13" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.13.tgz#bb32ecd87c27104ae6ee44b5a3ccbf0ebb1aede9" + dependencies: + async "^1.5.2" + debug "^2.2.0" + mkdirp "0.5.x" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + +postcss-calc@^5.2.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-5.3.1.tgz#77bae7ca928ad85716e2fda42f261bf7c1d65b5e" + dependencies: + postcss "^5.0.2" + postcss-message-helpers "^2.0.0" + reduce-css-calc "^1.2.6" + +postcss-colormin@^2.1.8: + version "2.2.2" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-2.2.2.tgz#6631417d5f0e909a3d7ec26b24c8a8d1e4f96e4b" + dependencies: + colormin "^1.0.5" + postcss "^5.0.13" + postcss-value-parser "^3.2.3" + +postcss-convert-values@^2.3.4: + version "2.6.1" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz#bbd8593c5c1fd2e3d1c322bb925dcae8dae4d62d" + dependencies: + postcss "^5.0.11" + postcss-value-parser "^3.1.2" + +postcss-discard-comments@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz#befe89fafd5b3dace5ccce51b76b81514be00e3d" + dependencies: + postcss "^5.0.14" + +postcss-discard-duplicates@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz#b9abf27b88ac188158a5eb12abcae20263b91932" + dependencies: + postcss "^5.0.4" + +postcss-discard-empty@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz#d2b4bd9d5ced5ebd8dcade7640c7d7cd7f4f92b5" + dependencies: + postcss "^5.0.14" + +postcss-discard-overridden@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz#8b1eaf554f686fb288cd874c55667b0aa3668d58" + dependencies: + postcss "^5.0.16" + +postcss-discard-unused@^2.2.1: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz#bce30b2cc591ffc634322b5fb3464b6d934f4433" + dependencies: + postcss "^5.0.14" + uniqs "^2.0.0" + +postcss-filter-plugins@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz#82245fdf82337041645e477114d8e593aa18b8ec" + dependencies: + postcss "^5.0.4" + +postcss-merge-idents@^2.1.5: + version "2.1.7" + resolved "https://registry.yarnpkg.com/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz#4c5530313c08e1d5b3bbf3d2bbc747e278eea270" + dependencies: + has "^1.0.1" + postcss "^5.0.10" + postcss-value-parser "^3.1.1" + +postcss-merge-longhand@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz#23d90cd127b0a77994915332739034a1a4f3d658" + dependencies: + postcss "^5.0.4" + +postcss-merge-rules@^2.0.3: + version "2.1.2" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz#d1df5dfaa7b1acc3be553f0e9e10e87c61b5f721" + dependencies: + browserslist "^1.5.2" + caniuse-api "^1.5.2" + postcss "^5.0.4" + postcss-selector-parser "^2.2.2" + vendors "^1.0.0" + +postcss-message-helpers@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz#a4f2f4fab6e4fe002f0aed000478cdf52f9ba60e" + +postcss-minify-font-values@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz#4b58edb56641eba7c8474ab3526cafd7bbdecb69" + dependencies: + object-assign "^4.0.1" + postcss "^5.0.4" + postcss-value-parser "^3.0.2" + +postcss-minify-gradients@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz#5dbda11373703f83cfb4a3ea3881d8d75ff5e6e1" + dependencies: + postcss "^5.0.12" + postcss-value-parser "^3.3.0" + +postcss-minify-params@^1.0.4: + version "1.2.2" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz#ad2ce071373b943b3d930a3fa59a358c28d6f1f3" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.2" + postcss-value-parser "^3.0.2" + uniqs "^2.0.0" + +postcss-minify-selectors@^2.0.4: + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz#b2c6a98c0072cf91b932d1a496508114311735bf" + dependencies: + alphanum-sort "^1.0.2" + has "^1.0.1" + postcss "^5.0.14" + postcss-selector-parser "^2.0.0" + +postcss-modules-extract-imports@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz#66140ecece38ef06bf0d3e355d69bf59d141ea85" + dependencies: + postcss "^6.0.1" + +postcss-modules-local-by-default@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-scope@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" + dependencies: + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" + +postcss-modules-values@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" + dependencies: + icss-replace-symbols "^1.1.0" + postcss "^6.0.1" + +postcss-normalize-charset@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz#ef9ee71212d7fe759c78ed162f61ed62b5cb93f1" + dependencies: + postcss "^5.0.5" + +postcss-normalize-url@^3.0.7: + version "3.0.8" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz#108f74b3f2fcdaf891a2ffa3ea4592279fc78222" + dependencies: + is-absolute-url "^2.0.0" + normalize-url "^1.4.0" + postcss "^5.0.14" + postcss-value-parser "^3.2.3" + +postcss-ordered-values@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz#eec6c2a67b6c412a8db2042e77fe8da43f95c11d" + dependencies: + postcss "^5.0.4" + postcss-value-parser "^3.0.1" + +postcss-reduce-idents@^2.2.2: + version "2.4.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz#c2c6d20cc958284f6abfbe63f7609bf409059ad3" + dependencies: + postcss "^5.0.4" + postcss-value-parser "^3.0.2" + +postcss-reduce-initial@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz#68f80695f045d08263a879ad240df8dd64f644ea" + dependencies: + postcss "^5.0.4" + +postcss-reduce-transforms@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz#ff76f4d8212437b31c298a42d2e1444025771ae1" + dependencies: + has "^1.0.1" + postcss "^5.0.8" + postcss-value-parser "^3.0.1" + +postcss-selector-parser@^2.0.0, postcss-selector-parser@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz#f9437788606c3c9acee16ffe8d8b16297f27bb90" + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + +postcss-svgo@^2.1.1: + version "2.1.6" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-2.1.6.tgz#b6df18aa613b666e133f08adb5219c2684ac108d" + dependencies: + is-svg "^2.0.0" + postcss "^5.0.14" + postcss-value-parser "^3.2.3" + svgo "^0.7.0" + +postcss-unique-selectors@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz#981d57d29ddcb33e7b1dfe1fd43b8649f933ca1d" + dependencies: + alphanum-sort "^1.0.1" + postcss "^5.0.4" + uniqs "^2.0.0" + +postcss-value-parser@^3.0.1, postcss-value-parser@^3.0.2, postcss-value-parser@^3.1.1, postcss-value-parser@^3.1.2, postcss-value-parser@^3.2.3, postcss-value-parser@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz#87f38f9f18f774a4ab4c8a232f5c5ce8872a9d15" + +postcss-zindex@^2.0.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/postcss-zindex/-/postcss-zindex-2.2.0.tgz#d2109ddc055b91af67fc4cb3b025946639d2af22" + dependencies: + has "^1.0.1" + postcss "^5.0.4" + uniqs "^2.0.0" + +postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.16: + version "5.2.18" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.18.tgz#badfa1497d46244f6390f58b319830d9107853c5" + dependencies: + chalk "^1.1.3" + js-base64 "^2.1.9" + source-map "^0.5.6" + supports-color "^3.2.3" + +postcss@^6.0.1: + version "6.0.22" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.22.tgz#e23b78314905c3b90cbd61702121e7a78848f2a3" + dependencies: + chalk "^2.4.1" + source-map "^0.6.1" + supports-color "^5.4.0" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + +prepend-http@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" + +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + +pretty-error@^2.0.2: + version "2.1.1" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" + dependencies: + renderkid "^2.0.1" + utila "~0.4" + +pretty-format@^21.2.1: + version "21.2.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-21.2.1.tgz#ae5407f3cf21066cd011aa1ba5fce7b6a2eddb36" + dependencies: + ansi-regex "^3.0.0" + ansi-styles "^3.2.0" + +private@^0.1.6, private@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + dependencies: + asap "~2.0.3" + +prop-types@^15.5.10, prop-types@^15.6.0: + version "15.6.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.3.1" + object-assign "^4.1.1" + +proxy-addr@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" + dependencies: + forwarded "~0.1.2" + ipaddr.js "1.6.0" + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + +pseudomap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" + +public-encrypt@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.2.tgz#46eb9107206bf73489f8b85b69d91334c6610994" + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + +pump@^2.0.0, pump@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.3: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + dependencies: + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + +punycode@^1.2.4, punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +punycode@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + +q@^1.1.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" + +qs@6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + +qs@~6.5.1: + version "6.5.2" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" + +query-string@^4.1.0: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + +querystring-es3@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + +querystringify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.0.0.tgz#fa3ed6e68eb15159457c89b37bc6472833195755" + +raf@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" + dependencies: + performance-now "^2.1.0" + +railroad-diagrams@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" + +randexp@0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/randexp/-/randexp-0.4.6.tgz#e986ad5e5e31dae13ddd6f7b3019aa7c87f60ca3" + dependencies: + discontinuous-range "1.0.0" + ret "~0.1.10" + +randomatic@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.0.0.tgz#d35490030eb4f7578de292ce6dfb04a91a128923" + dependencies: + is-number "^4.0.0" + kind-of "^6.0.0" + math-random "^1.0.1" + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +range-parser@^1.0.3, range-parser@~1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + +raw-body@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" + dependencies: + bytes "3.0.0" + http-errors "1.6.2" + iconv-lite "0.4.19" + unpipe "1.0.0" + +rc@^1.1.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.7.tgz#8a10ca30d588d00464360372b890d06dacd02297" + dependencies: + deep-extend "^0.5.1" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +react-addons-test-utils@^15.6.2: + version "15.6.2" + resolved "https://registry.yarnpkg.com/react-addons-test-utils/-/react-addons-test-utils-15.6.2.tgz#c12b6efdc2247c10da7b8770d185080a7b047156" + +react-dom@^15.6.2: + version "15.6.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.6.2.tgz#41cfadf693b757faf2708443a1d1fd5a02bef730" + dependencies: + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + +react-icon-base@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-icon-base/-/react-icon-base-2.1.0.tgz#a196e33fdf1e7aaa1fda3aefbb68bdad9e82a79d" + +react-icons@^2.2.7: + version "2.2.7" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-2.2.7.tgz#d7860826b258557510dac10680abea5ca23cf650" + dependencies: + react-icon-base "2.1.0" + +react-test-renderer@15: + version "15.6.2" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-15.6.2.tgz#d0333434fc2c438092696ca770da5ed48037efa8" + dependencies: + fbjs "^0.8.9" + object-assign "^4.1.0" + +react@^15.6.2: + version "15.6.2" + resolved "https://registry.yarnpkg.com/react/-/react-15.6.2.tgz#dba0434ab439cfe82f108f0f511663908179aa72" + dependencies: + create-react-class "^15.6.0" + fbjs "^0.8.9" + loose-envify "^1.1.0" + object-assign "^4.1.0" + prop-types "^15.5.10" + +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3, readable-stream@^2.3.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + 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@1.0: + version "1.0.34" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + +readdirp@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" + dependencies: + graceful-fs "^4.1.2" + minimatch "^3.0.2" + readable-stream "^2.0.2" + set-immediate-shim "^1.0.1" + +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + +reduce-css-calc@^1.2.6: + version "1.3.0" + resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716" + dependencies: + balanced-match "^0.4.2" + math-expression-evaluator "^1.2.14" + reduce-function-call "^1.0.1" + +reduce-function-call@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99" + dependencies: + balanced-match "^0.4.2" + +regenerate@^1.2.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + +regenerator-runtime@^0.11.0, regenerator-runtime@^0.11.1: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + +regenerator-transform@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" + dependencies: + babel-runtime "^6.18.0" + babel-types "^6.19.0" + private "^0.1.6" + +regex-cache@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" + dependencies: + is-equal-shallow "^0.1.3" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexpp@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-1.1.0.tgz#0e3516dd0b7904f413d2d4193dce4618c3a689ab" + +regexpu-core@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-1.0.0.tgz#86a763f58ee4d7c2f6b102e4764050de7ed90c6b" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +regexpu-core@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +regjsgen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + +regjsparser@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + dependencies: + jsesc "~0.5.0" + +relateurl@0.2.x: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + +renderkid@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.1.tgz#898cabfc8bede4b7b91135a3ffd323e58c0db319" + dependencies: + css-select "^1.1.0" + dom-converter "~0.1" + htmlparser2 "~3.3.0" + strip-ansi "^3.0.0" + utila "~0.3" + +repeat-element@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + +repeat-string@^1.5.2, repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + +request@^2.79.0: + version "2.87.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.6.0" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.1" + forever-agent "~0.6.1" + form-data "~2.3.1" + har-validator "~5.0.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.17" + oauth-sign "~0.8.2" + performance-now "^2.1.0" + qs "~6.5.1" + safe-buffer "^5.1.1" + tough-cookie "~2.3.3" + tunnel-agent "^0.6.0" + uuid "^3.1.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + +require-uncached@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + dependencies: + caller-path "^0.1.0" + resolve-from "^1.0.0" + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + +resolve-cwd@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" + dependencies: + resolve-from "^3.0.0" + +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + +resolve-from@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + +resolve@^1.5.0, resolve@^1.6.0: + version "1.7.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" + dependencies: + path-parse "^1.0.5" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + dependencies: + align-text "^0.1.1" + +rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + dependencies: + glob "^7.0.5" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +rst-selector-parser@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/rst-selector-parser/-/rst-selector-parser-2.2.3.tgz#81b230ea2fcc6066c89e3472de794285d9b03d91" + dependencies: + lodash.flattendeep "^4.4.0" + nearley "^2.7.10" + +rsvp@^3.3.3: + version "3.6.2" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + dependencies: + is-promise "^2.1.0" + +run-queue@^1.0.0, run-queue@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" + dependencies: + aproba "^1.1.1" + +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + +safe-buffer@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, 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" + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + +sane@^2.0.0: + version "2.5.2" + resolved "https://registry.yarnpkg.com/sane/-/sane-2.5.2.tgz#b4dc1861c21b427e929507a3e751e2a2cb8ab3fa" + dependencies: + anymatch "^2.0.0" + capture-exit "^1.2.0" + exec-sh "^0.2.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + watch "~0.18.0" + optionalDependencies: + fsevents "^1.2.3" + +sax@^1.2.1, sax@^1.2.4, sax@~1.2.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + +schema-utils@^0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.5.tgz#21836f0608aac17b78f9e3e24daff14a5ca13a3e" + dependencies: + ajv "^6.1.0" + ajv-keywords "^3.1.0" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + +selfsigned@^1.9.1: + version "1.10.3" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.3.tgz#d628ecf9e3735f84e8bafba936b3cf85bea43823" + dependencies: + node-forge "0.7.5" + +"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1: + version "5.5.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" + +send@0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" + 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.6.2" + mime "1.4.1" + ms "2.0.0" + on-finished "~2.3.0" + range-parser "~1.2.0" + statuses "~1.4.0" + +serialize-javascript@^1.4.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.5.0.tgz#1aa336162c88a890ddad5384baebc93a655161fe" + +serve-index@^1.7.2: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.13.2: + version "1.13.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.2" + send "0.16.2" + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + +set-immediate-shim@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + +set-value@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.1" + to-object-path "^0.3.0" + +set-value@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.4, setimmediate@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + +setprototypeof@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + +slice-ansi@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-1.0.0.tgz#044f1a49d8842ff307aad6b505ed178bd950134d" + dependencies: + is-fullwidth-code-point "^2.0.0" + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +sockjs-client@1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12" + dependencies: + debug "^2.6.6" + eventsource "0.1.6" + faye-websocket "~0.11.0" + inherits "^2.0.1" + json3 "^3.3.2" + url-parse "^1.1.8" + +sockjs@0.3.19: + version "0.3.19" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d" + dependencies: + faye-websocket "^0.10.0" + uuid "^3.0.1" + +sort-keys@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad" + dependencies: + is-plain-obj "^1.0.0" + +source-list-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" + +source-map-resolve@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" + dependencies: + atob "^2.1.1" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.4.15: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + dependencies: + source-map "^0.5.6" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + +source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + +source-map@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + dependencies: + amdefine ">=0.0.4" + +source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + +spdx-correct@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82" + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9" + +spdx-expression-parse@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" + +spdy-transport@^2.0.18: + version "2.1.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.1.0.tgz#4bbb15aaffed0beefdd56ad61dbdc8ba3e2cb7a1" + dependencies: + debug "^2.6.8" + detect-node "^2.0.3" + hpack.js "^2.1.6" + obuf "^1.1.1" + readable-stream "^2.2.9" + safe-buffer "^5.0.1" + wbuf "^1.7.2" + +spdy@^3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc" + dependencies: + debug "^2.6.8" + handle-thing "^1.2.5" + http-deceiver "^1.2.7" + safe-buffer "^5.0.1" + select-hose "^2.0.0" + spdy-transport "^2.0.18" + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + +sshpk@^1.7.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.1.tgz#130f5975eddad963f1d56f92b9ac6c51fa9f83eb" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + +ssri@^5.2.4: + version "5.3.0" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.3.0.tgz#ba3872c9c6d33a0704a7d71ff045e5ec48999d06" + dependencies: + safe-buffer "^5.1.1" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + +statuses@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" + +stream-browserify@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + +stream-each@^1.1.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.2.tgz#8e8c463f91da8991778765873fe4d960d8f616bd" + dependencies: + end-of-stream "^1.1.0" + stream-shift "^1.0.0" + +stream-http@^2.7.2: + version "2.8.2" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.2.tgz#4126e8c6b107004465918aa2fc35549e77402c87" + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.3.6" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + +stream-shift@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" + +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + +string-length@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" + dependencies: + astral-regex "^1.0.0" + strip-ansi "^4.0.0" + +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string-width@^2.0.0, string-width@^2.1.0, string-width@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@^1.0.0, string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + dependencies: + safe-buffer "~5.1.0" + +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + +strip-bom@3.0.0, strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + dependencies: + is-utf8 "^0.2.0" + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + dependencies: + get-stdin "^4.0.1" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + +style-loader@^0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-0.21.0.tgz#68c52e5eb2afc9ca92b6274be277ee59aea3a852" + dependencies: + loader-utils "^1.1.0" + schema-utils "^0.4.5" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^3.1.0, supports-color@^3.1.2, supports-color@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + dependencies: + has-flag "^1.0.0" + +supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" + dependencies: + has-flag "^3.0.0" + +svgo@^0.7.0: + version "0.7.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-0.7.2.tgz#9f5772413952135c6fefbf40afe6a4faa88b4bb5" + dependencies: + coa "~1.0.1" + colors "~1.1.2" + csso "~2.3.1" + js-yaml "~3.7.0" + mkdirp "~0.5.1" + sax "~1.2.1" + whet.extend "~0.9.9" + +symbol-tree@^3.2.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" + +table@4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36" + dependencies: + ajv "^5.2.3" + ajv-keywords "^2.1.0" + chalk "^2.1.0" + lodash "^4.17.4" + slice-ansi "1.0.0" + string-width "^2.1.1" + +tapable@^0.2.7, tapable@~0.2.5: + version "0.2.8" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" + +tapable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2" + +tar@^4: + version "4.4.3" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.3.tgz#d6bd509dc7f6b5a5d2c13aa0f7d57b03269b8376" + dependencies: + chownr "^1.0.1" + fs-minipass "^1.2.5" + minipass "^2.3.3" + minizlib "^1.1.0" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.2" + +test-exclude@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.1.tgz#dfa222f03480bca69207ca728b37d74b45f724fa" + dependencies: + arrify "^1.0.1" + micromatch "^3.1.8" + object-assign "^4.1.0" + read-pkg-up "^1.0.1" + require-main-filename "^1.0.1" + +text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + +throat@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" + +through2@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" + dependencies: + readable-stream "^2.1.5" + xtend "~4.0.1" + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + +thunky@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.0.2.tgz#a862e018e3fb1ea2ec3fce5d55605cf57f247371" + +time-stamp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357" + +timers-browserify@^2.0.4: + version "2.0.10" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" + dependencies: + setimmediate "^1.0.4" + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + dependencies: + os-tmpdir "~1.0.2" + +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + +to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toposort@^1.0.0: + version "1.0.7" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" + +tough-cookie@^2.3.2, tough-cookie@~2.3.3: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + dependencies: + punycode "^1.4.1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + +tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + dependencies: + prelude-ls "~1.1.2" + +type-is@~1.6.15, type-is@~1.6.16: + version "1.6.16" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" + dependencies: + media-typer "0.3.0" + mime-types "~2.1.18" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + +ua-parser-js@^0.7.9: + version "0.7.18" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.18.tgz#a7bfd92f56edfb117083b69e31d2aa8882d4b1ed" + +uglify-es@^3.3.4: + version "3.3.9" + resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" + dependencies: + commander "~2.13.0" + source-map "~0.6.1" + +uglify-js@3.3.x: + version "3.3.27" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.3.27.tgz#eb8c3c9429969f86ff5b0a2422ffc78c3cea8cc0" + dependencies: + commander "~2.15.0" + source-map "~0.6.1" + +uglify-js@^2.6, uglify-js@^2.8.27: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + +uglify-to-browserify@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + +uglifyjs-webpack-plugin@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.2.5.tgz#2ef8387c8f1a903ec5e44fa36f9f3cbdcea67641" + dependencies: + cacache "^10.0.4" + find-cache-dir "^1.0.0" + schema-utils "^0.4.5" + serialize-javascript "^1.4.0" + source-map "^0.6.1" + uglify-es "^3.3.4" + webpack-sources "^1.1.0" + worker-farm "^1.5.2" + +underscore@~1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" + +union-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^0.4.3" + +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + +uniqs@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" + +unique-filename@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.0.tgz#d05f2fe4032560871f30e93cbe735eea201514f3" + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.0.tgz#db6676e7c7cc0629878ff196097c78855ae9f4ab" + dependencies: + imurmurhash "^0.1.4" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +upath@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" + +upper-case@^1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" + +uri-js@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.1.tgz#4595a80a51f356164e22970df64c7abd6ade9850" + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + +url-parse@^1.1.8, url-parse@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.0.tgz#6bfdaad60098c7fe06f623e42b22de62de0d3d75" + dependencies: + querystringify "^2.0.0" + requires-port "^1.0.0" + +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.0.tgz#14716bf03fdfefd03040aef58d8b4b85f3a7c544" + dependencies: + kind-of "^6.0.2" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +util.promisify@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + +util@0.10.3, util@^0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + dependencies: + inherits "2.0.1" + +utila@~0.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.3.3.tgz#d7e8e7d7e309107092b05f8d9688824d633a4226" + +utila@~0.4: + version "0.4.0" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + +uuid@^3.0.1, uuid@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" + +validate-npm-package-license@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338" + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +value-equal@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + +vendors@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.2.tgz#7fcb5eef9f5623b156bcea89ec37d63676f21801" + +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + dependencies: + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" + +vm-browserify@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" + dependencies: + indexof "0.0.1" + +walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + dependencies: + makeerror "1.0.x" + +watch@~0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/watch/-/watch-0.18.0.tgz#28095476c6df7c90c963138990c0a5423eb4b986" + dependencies: + exec-sh "^0.2.0" + minimist "^1.2.0" + +watchpack@^1.3.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" + dependencies: + chokidar "^2.0.2" + graceful-fs "^4.1.2" + neo-async "^2.5.0" + +wbuf@^1.1.0, wbuf@^1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + dependencies: + minimalistic-assert "^1.0.0" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + +webidl-conversions@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad" + +webpack-dev-middleware@1.12.2: + version "1.12.2" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz#f8fc1120ce3b4fc5680ceecb43d777966b21105e" + dependencies: + memory-fs "~0.4.1" + mime "^1.5.0" + path-is-absolute "^1.0.0" + range-parser "^1.0.3" + time-stamp "^2.0.0" + +webpack-dev-server@^2.9.5: + version "2.11.2" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.11.2.tgz#1f4f4c78bf1895378f376815910812daf79a216f" + dependencies: + ansi-html "0.0.7" + array-includes "^3.0.3" + bonjour "^3.5.0" + chokidar "^2.0.0" + compression "^1.5.2" + connect-history-api-fallback "^1.3.0" + debug "^3.1.0" + del "^3.0.0" + express "^4.16.2" + html-entities "^1.2.0" + http-proxy-middleware "~0.17.4" + import-local "^1.0.0" + internal-ip "1.2.0" + ip "^1.1.5" + killable "^1.0.0" + loglevel "^1.4.1" + opn "^5.1.0" + portfinder "^1.0.9" + selfsigned "^1.9.1" + serve-index "^1.7.2" + sockjs "0.3.19" + sockjs-client "1.1.4" + spdy "^3.4.1" + strip-ansi "^3.0.0" + supports-color "^5.1.0" + webpack-dev-middleware "1.12.2" + yargs "6.6.0" + +webpack-merge@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.1.2.tgz#5d372dddd3e1e5f8874f5bf5a8e929db09feb216" + dependencies: + lodash "^4.17.5" + +webpack-sources@^1.0.1, webpack-sources@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.1.0.tgz#a101ebae59d6507354d71d8013950a3a8b7a5a54" + dependencies: + source-list-map "^2.0.0" + source-map "~0.6.1" + +webpack@^2.0.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.7.0.tgz#b2a1226804373ffd3d03ea9c6bd525067034f6b1" + dependencies: + acorn "^5.0.0" + acorn-dynamic-import "^2.0.0" + ajv "^4.7.0" + ajv-keywords "^1.1.1" + async "^2.1.2" + enhanced-resolve "^3.3.0" + interpret "^1.0.0" + json-loader "^0.5.4" + json5 "^0.5.1" + loader-runner "^2.3.0" + loader-utils "^0.2.16" + memory-fs "~0.4.1" + mkdirp "~0.5.0" + node-libs-browser "^2.0.0" + source-map "^0.5.3" + supports-color "^3.1.0" + tapable "~0.2.5" + uglify-js "^2.8.27" + watchpack "^1.3.1" + webpack-sources "^1.0.1" + yargs "^6.0.0" + +websocket-driver@>=0.5.1: + version "0.7.0" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" + dependencies: + http-parser-js ">=0.4.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" + +whatwg-encoding@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz#57c235bc8657e914d24e1a397d3c82daee0a6ba3" + dependencies: + iconv-lite "0.4.19" + +whatwg-fetch@>=0.10.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" + +whatwg-url@^4.3.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0" + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +whet.extend@~0.9.9: + version "0.9.9" + resolved "https://registry.yarnpkg.com/whet.extend/-/whet.extend-0.9.9.tgz#f877d5bf648c97e5aa542fadc16d6a259b9c11a1" + +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + +which@^1.2.12, which@^1.2.9, which@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + dependencies: + string-width "^1.0.2" + +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + +worker-farm@^1.3.1, worker-farm@^1.5.2: + version "1.6.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0" + dependencies: + errno "~0.1.7" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +write-file-atomic@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.3.0.tgz#1ff61575c2e2a4e8e510d6fa4e243cce183999ab" + dependencies: + graceful-fs "^4.1.11" + imurmurhash "^0.1.4" + signal-exit "^3.0.2" + +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + dependencies: + mkdirp "^0.5.1" + +xml-name-validator@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" + +xtend@^4.0.0, xtend@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +y18n@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + +y18n@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" + +yallist@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + +yallist@^3.0.0, yallist@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" + +yargs-parser@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" + dependencies: + camelcase "^3.0.0" + +yargs-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" + dependencies: + camelcase "^4.1.0" + +yargs@6.6.0, yargs@^6.0.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^4.2.0" + +yargs@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-9.0.1.tgz#52acc23feecac34042078ee78c0c007f5085db4c" + dependencies: + camelcase "^4.1.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + read-pkg-up "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^7.0.0" + +yargs@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + dependencies: + camelcase "^1.0.2" + cliui "^2.1.0" + decamelize "^1.0.0" + window-size "0.1.0"