From 235891c6639841cf98ee144a9b136c23e1fd11b7 Mon Sep 17 00:00:00 2001 From: Evan Louie <evan.louie@outlook.com> Date: Thu, 8 Feb 2018 01:18:01 -0800 Subject: [PATCH] Service Catalog (#49) * listable catalog stuff * Add instance and binding * Add secret info to binding * Concurrent service bindings * immutable refactor * wip * flexbox plans * provision * update provision * selectable chart versions (#39) * selectable chart versions - select chart versions from chart versions list - adds route /chart/:repo/:chart/versions/:version to drill down on version details - chartview is rendered based on the selected chart version type (latest by default) instead of the chart type * style show all link correctly * listable catalog stuff * wip * stashing * card lists * working provision * sample parameters for provision * fixed routes * add CardContainer * status message styling * added tags to class cards * working plan card images * grid based cards * catalog sync * fixed card widths & removed template code from containers * fixed Card infinite width growth * retrieve name and bullets from service plan metadata * update catalog fetch time in brokerview * remove string[] from body * move components to own folders * Cleanup - Migrated several `fetch` queries to use axios - Changed the AppRepoForm appear in a modal instead of its own URL - Refactored repo dispatch code from container to actions - Moved Card css into separate CSS file - Changed nulls to `undefined` --- .gitignore | 1 + package.json | 7 +- src/actions/catalog.ts | 138 ++++ src/actions/index.ts | 4 + src/actions/releases.ts | 0 src/actions/repos.ts | 76 ++ src/components/AppRepoList/AppRepoButton.tsx | 101 +++ src/components/AppRepoList/index.tsx | 56 ++ src/components/BrokerView/index.tsx | 143 ++++ src/components/Card/Card.css | 55 ++ src/components/Card/index.tsx | 44 ++ .../ChartView/ChartDeployButton.tsx | 4 +- src/components/ClassList/index.tsx | 62 ++ src/components/ClassView/index.tsx | 83 +++ src/components/Dashboard/Dashboard.tsx | 2 +- src/components/ProvisionButton/index.tsx | 208 ++++++ src/components/ServiceBrokerList/index.tsx | 31 + src/components/ServiceCatalog/index.tsx | 43 ++ src/components/ServiceList/index.tsx | 53 ++ src/components/Sidebar/Sidebar.tsx | 23 +- src/components/SyncButton/index.tsx | 49 ++ src/containers/BrokerView.ts | 56 ++ src/containers/ClassListContainer.ts | 48 ++ src/containers/ClassView.ts | 49 ++ src/containers/RepoListContainer.ts | 28 + src/containers/Root.tsx | 35 +- src/containers/ServiceCatalogContainer.ts | 28 + src/containers/ServiceListContainer.ts | 23 + src/reducers/catalog.ts | 63 ++ src/reducers/index.ts | 4 + src/reducers/repos.ts | 65 ++ src/shared/AppRepository.ts | 39 ++ src/shared/HelmRelease.ts | 40 ++ src/shared/ServiceCatalog.ts | 322 +++++++++ src/shared/types.ts | 173 +++++ src/shared/url.ts | 20 + tsconfig.json | 4 +- tslint.json | 4 +- yarn.lock | 647 +++++++++--------- 39 files changed, 2495 insertions(+), 336 deletions(-) create mode 100644 src/actions/catalog.ts create mode 100644 src/actions/releases.ts create mode 100644 src/actions/repos.ts create mode 100644 src/components/AppRepoList/AppRepoButton.tsx create mode 100644 src/components/AppRepoList/index.tsx create mode 100644 src/components/BrokerView/index.tsx create mode 100644 src/components/Card/Card.css create mode 100644 src/components/Card/index.tsx create mode 100644 src/components/ClassList/index.tsx create mode 100644 src/components/ClassView/index.tsx create mode 100644 src/components/ProvisionButton/index.tsx create mode 100644 src/components/ServiceBrokerList/index.tsx create mode 100644 src/components/ServiceCatalog/index.tsx create mode 100644 src/components/ServiceList/index.tsx create mode 100644 src/components/SyncButton/index.tsx create mode 100644 src/containers/BrokerView.ts create mode 100644 src/containers/ClassListContainer.ts create mode 100644 src/containers/ClassView.ts create mode 100644 src/containers/RepoListContainer.ts create mode 100644 src/containers/ServiceCatalogContainer.ts create mode 100644 src/containers/ServiceListContainer.ts create mode 100644 src/reducers/catalog.ts create mode 100644 src/reducers/repos.ts create mode 100644 src/shared/AppRepository.ts create mode 100644 src/shared/HelmRelease.ts create mode 100644 src/shared/ServiceCatalog.ts diff --git a/.gitignore b/.gitignore index 8181d999893..c9de38e3571 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ .env.development.local .env.test.local .env.production.local +.vscode npm-debug.log* yarn-debug.log* diff --git a/package.json b/package.json index f788683c423..b98eda7588c 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "axios": "^0.17.1", "enzyme": "^3.3.0", "enzyme-adapter-react-16": "^1.1.1", "raf": "^3.4.0", @@ -14,7 +15,7 @@ "react-redux": "^5.0.6", "react-router-dom": "^4.2.2", "react-router-redux": "^5.0.0-alpha.9", - "react-scripts-ts": "2.12.0", + "react-scripts-ts": "^2.13.0", "react-test-renderer": "^16.2.0", "redux": "^3.7.2", "redux-thunk": "^2.2.0", @@ -49,8 +50,8 @@ "@types/react-router-dom": "^4.2.3", "@types/react-router-redux": "^5.0.11", "@types/react-test-renderer": "^16.0.0", - "husky": "^0.15.0-rc.2", - "lint-staged": "^6.0.0", + "husky": "^0.14.3", + "lint-staged": "^6.1.0", "prettier": "^1.10.2", "tslint": "^5.9.1", "tslint-config-prettier": "^1.6.0", diff --git a/src/actions/catalog.ts b/src/actions/catalog.ts new file mode 100644 index 00000000000..7cb4dc7affb --- /dev/null +++ b/src/actions/catalog.ts @@ -0,0 +1,138 @@ +import { Dispatch } from "redux"; +import { createAction, getReturnOfExpression } from "typesafe-actions"; + +import { + IServiceBinding, + IServiceBroker, + IServiceClass, + IServiceInstance, + IServicePlan, + ServiceCatalog, +} from "../shared/ServiceCatalog"; +import { IStoreState } from "../shared/types"; + +export const checkCatalogInstall = createAction("CHECK_INSTALL"); +export const installed = createAction("INSTALLED"); +export const notInstalled = createAction("_NOT_INSTALLED"); +export const requestBrokers = createAction("REQUEST_BROKERS"); +export const receiveBrokers = createAction("RECEIVE_BROKERS", (brokers: IServiceBroker[]) => ({ + brokers, + type: "RECEIVE_BROKERS", +})); +export const requestPlans = createAction("REQUEST_PLANS"); +export const receivePlans = createAction("RECEIVE_PLANS", (plans: IServicePlan[]) => ({ + plans, + type: "RECEIVE_PLANS", +})); +export const requestInstances = createAction("REQUEST_INSTANCES"); +export const receiveInstances = createAction( + "RECEIVE_INSTANCES", + (instances: IServiceInstance[]) => ({ type: "RECEIVE_INSTANCES", instances }), +); +export const requestBindings = createAction("REQUEST_BINDINGS"); +export const receiveBindings = createAction("RECEIVE_BINDINGS", (bindings: IServiceBinding[]) => ({ + bindings, + type: "RECEIVE_BINDINGS", +})); +export const requestClasses = createAction("REQUEST_PLANS"); +export const receiveClasses = createAction("RECEIVE_CLASSES", (classes: IServiceClass[]) => ({ + classes, + type: "RECEIVE_CLASSES", +})); + +const actions = [ + checkCatalogInstall, + installed, + notInstalled, + requestBrokers, + receiveBrokers, + requestPlans, + receivePlans, + requestInstances, + receiveInstances, + requestBindings, + receiveBindings, + requestClasses, + receiveClasses, +].map(getReturnOfExpression); + +export function provision( + releaseName: string, + namespace: string, + className: string, + planName: string, + parameters: {}, +) { + return async (dispatch: Dispatch<IStoreState>) => { + return ServiceCatalog.provisionInstance( + releaseName, + namespace, + className, + planName, + parameters, + ); + }; +} + +export function sync(broker: IServiceBroker) { + return async (dispatch: Dispatch<IStoreState>) => { + return ServiceCatalog.syncBroker(broker); + }; +} + +export type ServiceCatalogAction = typeof actions[number]; + +export function getBindings() { + return async (dispatch: Dispatch<IStoreState>) => { + dispatch(requestBindings()); + const bindings = await ServiceCatalog.getServiceBindings(); + dispatch(receiveBindings(bindings)); + return bindings; + }; +} + +export function getBrokers() { + return async (dispatch: Dispatch<IStoreState>) => { + dispatch(requestBrokers()); + const brokers = await ServiceCatalog.getServiceBrokers(); + dispatch(receiveBrokers(brokers)); + return brokers; + }; +} + +export function getClasses() { + return async (dispatch: Dispatch<IStoreState>) => { + dispatch(requestClasses()); + const classes = await ServiceCatalog.getServiceClasses(); + dispatch(receiveClasses(classes)); + return classes; + }; +} + +export function getInstances() { + return async (dispatch: Dispatch<IStoreState>) => { + dispatch(requestInstances()); + const instances = await ServiceCatalog.getServiceInstances(); + dispatch(receiveInstances(instances)); + return instances; + }; +} + +export function getPlans() { + return async (dispatch: Dispatch<IStoreState>) => { + dispatch(requestPlans()); + const plans = await ServiceCatalog.getServicePlans(); + dispatch(receivePlans(plans)); + return plans; + }; +} + +export function getCatalog() { + return async (dispatch: Dispatch<IStoreState>) => { + dispatch(getBindings()); + dispatch(getBrokers()); + dispatch(getClasses()); + dispatch(getInstances()); + dispatch(getPlans()); + }; +} diff --git a/src/actions/index.ts b/src/actions/index.ts index 4a6edbabda3..e5c048a29ca 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -1,5 +1,9 @@ +import * as catalog from "./catalog"; import * as charts from "./charts"; +import * as repos from "./repos"; export default { + catalog, charts, + repos, }; diff --git a/src/actions/releases.ts b/src/actions/releases.ts new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/actions/repos.ts b/src/actions/repos.ts new file mode 100644 index 00000000000..85224763fb5 --- /dev/null +++ b/src/actions/repos.ts @@ -0,0 +1,76 @@ +import { createAction, getReturnOfExpression } from "typesafe-actions"; + +import { Dispatch } from "react-redux"; +import { AppRepository } from "../shared/AppRepository"; +import { IAppRepository, IStoreState } from "../shared/types"; + +export const addRepo = createAction("ADD_REPO"); +export const addedRepo = createAction("ADDED_REPO", (added: IAppRepository) => ({ + added, + type: "ADDED_REPO", +})); +export const requestRepos = createAction("REQUEST_REPOS"); +export const receiveRepos = createAction("RECEIVE_REPOS", (repos: IAppRepository[]) => { + return { + repos, + type: "RECEIVE_REPOS", + }; +}); +export const showForm = createAction("SHOW_FORM"); +export const hideForm = createAction("HIDE_FORM"); +export const resetForm = createAction("RESET_FORM"); +export const submitForm = createAction("SUBMIT_FROM"); +export const updateForm = createAction( + "UPDATE_FORM", + (values: { name?: string; namespace?: string; url?: string }) => { + return { + type: "UPDATE_FORM", + values, + }; + }, +); +export const redirect = createAction("REDIRECT", (path: string) => ({ type: "REDIRECT", path })); +export const redirected = createAction("REDIRECTED"); + +const allActions = [ + addRepo, + addedRepo, + requestRepos, + receiveRepos, + resetForm, + submitForm, + updateForm, + showForm, + hideForm, + redirect, + redirected, +].map(getReturnOfExpression); +export type AppReposAction = typeof allActions[number]; + +export const deleteRepo = (name: string, namespace: string = "kubeapps") => { + return async (dispatch: Dispatch<IStoreState>) => { + await AppRepository.delete(name, namespace); + dispatch(requestRepos()); + const repos = await AppRepository.list(); + dispatch(receiveRepos(repos.items)); + return repos; + }; +}; + +export const fetchRepos = () => { + return async (dispatch: Dispatch<IStoreState>) => { + dispatch(requestRepos()); + const repos = await AppRepository.list(); + dispatch(receiveRepos(repos.items)); + return repos; + }; +}; + +export const installRepo = (name: string, url: string, namespace: string) => { + return async (dispatch: Dispatch<IStoreState>) => { + dispatch(addRepo()); + const added = await AppRepository.create(name, url, namespace); + dispatch(addedRepo(added)); + return added; + }; +}; diff --git a/src/components/AppRepoList/AppRepoButton.tsx b/src/components/AppRepoList/AppRepoButton.tsx new file mode 100644 index 00000000000..99c1ead8fc3 --- /dev/null +++ b/src/components/AppRepoList/AppRepoButton.tsx @@ -0,0 +1,101 @@ +import * as React from "react"; +import * as Modal from "react-modal"; +import { Redirect } from "react-router"; + +interface IAppRepoFormProps { + name: string; + url: string; + message?: string; + redirectTo?: string; + install: (name: string, url: string) => Promise<any>; + update: (values: { name?: string; url?: string }) => void; + onAfterInstall?: () => Promise<any>; +} + +export const AppRepoForm = (props: IAppRepoFormProps) => { + const { name, url, update, install, onAfterInstall } = props; + const handleInstallClick = async () => { + await install(name, url); + if (onAfterInstall) { + await onAfterInstall(); + } + }; + const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => + update({ name: e.target.value }); + const handleURLChange = (e: React.ChangeEvent<HTMLInputElement>) => + update({ url: e.target.value }); + return ( + <div className="app-repo-form"> + <h1>Add an App Repository</h1> + <label> + Name: + <input type="text" value={name} onChange={handleNameChange} /> + </label> + <label> + URL: + <input type="text" value={url} onChange={handleURLChange} /> + </label> + <button className="button button-primary" onClick={handleInstallClick}> + Install Repo + </button> + {props.redirectTo && <Redirect to={props.redirectTo} />} + </div> + ); +}; + +interface IAppRepoAddButtonProps { + install: (name: string, url: string) => Promise<any>; + redirectTo?: string; +} +interface IAppRepoAddButtonState { + error?: string; + modalIsOpen: boolean; + name: string; + url: string; +} + +export class AppRepoAddButton extends React.Component< + IAppRepoAddButtonProps, + IAppRepoAddButtonState +> { + public state = { + error: undefined, + modalIsOpen: false, + name: "", + url: "", + }; + + public render() { + const { redirectTo, install } = this.props; + const { name, url } = this.state; + return ( + <div className="AppRepoAddButton"> + <button className="button button-primary" onClick={this.openModal}> + Add App Repository + </button> + <Modal + isOpen={this.state.modalIsOpen} + onRequestClose={this.closeModal} + contentLabel="Modal" + > + {this.state.error && ( + <div className="container padding-v-bigger bg-action">{this.state.error}</div> + )} + <AppRepoForm + name={name} + url={url} + update={this.updateValues} + install={install} + onAfterInstall={this.closeModal} + /> + </Modal> + {redirectTo && <Redirect to={redirectTo} />} + </div> + ); + } + + private closeModal = async () => this.setState({ modalIsOpen: false }); + private openModal = async () => this.setState({ modalIsOpen: true }); + private updateValues = async (values: { name: string; url: string }) => + this.setState({ ...values }); +} diff --git a/src/components/AppRepoList/index.tsx b/src/components/AppRepoList/index.tsx new file mode 100644 index 00000000000..dc5da7c6d76 --- /dev/null +++ b/src/components/AppRepoList/index.tsx @@ -0,0 +1,56 @@ +import * as React from "react"; + +import { IAppRepository } from "../../shared/types"; +import { AppRepoAddButton } from "./AppRepoButton"; + +export interface IAppRepoListProps { + repos: IAppRepository[]; + fetchRepos: () => Promise<any>; + deleteRepo: (name: string) => Promise<any>; + install: (name: string, url: string) => Promise<any>; +} + +export class AppRepoList extends React.Component<IAppRepoListProps> { + public componentDidMount() { + this.props.fetchRepos(); + } + + public render() { + const { repos, install } = this.props; + return ( + <div className="app-repo-list"> + <h1>App Repositories</h1> + <table> + <thead> + <tr> + <th>Repo</th> + <th>URL</th> + <th>Actions</th> + </tr> + </thead> + <tbody> + {repos.map(repo => { + return ( + <tr key={repo.metadata.name}> + <td>{repo.metadata.name}</td> + <td>{repo.spec && repo.spec.url}</td> + <td> + <button + className="button button-secondary" + onClick={this.handleDeleteClick(repo.metadata.name)} + > + Delete + </button> + </td> + </tr> + ); + })} + </tbody> + </table> + <AppRepoAddButton install={install} /> + </div> + ); + } + + private handleDeleteClick = (repoName: string) => () => this.props.deleteRepo(repoName); +} diff --git a/src/components/BrokerView/index.tsx b/src/components/BrokerView/index.tsx new file mode 100644 index 00000000000..af8c8edce28 --- /dev/null +++ b/src/components/BrokerView/index.tsx @@ -0,0 +1,143 @@ +import * as React from "react"; +import { Link } from "react-router-dom"; + +import { + IServiceBinding, + IServiceBroker, + IServiceClass, + IServiceInstance, + IServicePlan, +} from "../../shared/ServiceCatalog"; +import { Card, CardContainer } from "../Card"; +import SyncButton from "../SyncButton"; + +export interface IBrokerViewProps { + bindings: IServiceBinding[]; + broker: IServiceBroker | undefined; + classes: IServiceClass[]; + getCatalog: () => Promise<any>; + instances: IServiceInstance[]; + plans: IServicePlan[]; + sync: (broker: IServiceBroker) => Promise<any>; +} + +export class BrokerView extends React.PureComponent<IBrokerViewProps> { + public async componentDidMount() { + this.props.getCatalog(); + } + + public render() { + const { bindings, broker, instances } = this.props; + + return ( + <div className="broker"> + {broker && ( + <div> + <h1>{broker.metadata.name}</h1> + <div>Catalog last updated at {broker.status.lastCatalogRetrievalTime}</div> + <div + style={{ + display: "flex", + flexDirection: "row", + }} + > + <Link to={window.location.pathname + "/classes"}> + <button className="button button-primary">Provision New Service</button> + </Link> + <SyncButton sync={this.props.sync} broker={broker} /> + </div> + <h3>Service Instances</h3> + <p>Most recent statuses for your brokers:</p> + <table> + <thead> + <tr> + <th>Instance</th> + <th>Status</th> + <th>Message</th> + </tr> + </thead> + <tbody> + {instances.map(instance => { + const conditions = [...instance.status.conditions]; + const status = conditions.shift(); // first in list is most recent + const reason = status ? status.reason : ""; + const message = status ? status.message : ""; + + return ( + <tr key={instance.metadata.uid}> + <td key={instance.metadata.name}> + <strong> + {instance.metadata.namespace}/{instance.metadata.name} + </strong> + </td> + <td key={reason}> + <code>{reason}</code> + </td> + <td key={message}> + <code>{message}</code> + </td> + </tr> + ); + })} + </tbody> + </table> + + <h3>Bindings</h3> + <CardContainer> + {bindings.length > 0 && + bindings.map(binding => { + const { + instanceRef, + secretName, + secretDatabase, + secretHost, + secretPassword, + secretPort, + secretUsername, + } = binding.spec; + const statuses: Array<[string, string | undefined]> = [ + ["Instance", instanceRef.name], + ["Secret", secretName], + ["Database", secretDatabase], + ["Host", secretHost], + ["Password", secretPassword], + ["Port", secretPort], + ["Username", secretUsername], + ]; + const body = ( + <div style={{ display: "flex", flexWrap: "wrap", flexDirection: "column" }}> + {statuses.map(statusPair => { + const [key, value] = statusPair; + return ( + <div key={key} style={{ display: "flex" }}> + <strong key={key} style={{ flex: "0 0 5em" }}> + {key}: + </strong> + <code + key={value || "null"} + style={{ flex: "1 1", wordBreak: "break-all" }} + > + {value} + </code> + </div> + ); + })} + </div> + ); + const card = ( + <Card + key={binding.metadata.name} + header={binding.metadata.name} + body={body} + button={<span />} + /> + ); + return card; + })} + </CardContainer> + </div> + )} + </div> + ); + } +} diff --git a/src/components/Card/Card.css b/src/components/Card/Card.css new file mode 100644 index 00000000000..322a6a42a89 --- /dev/null +++ b/src/components/Card/Card.css @@ -0,0 +1,55 @@ +/* Margin: 0.5em */ + +.CardContainer { + display: flex; + flex-wrap: wrap; + margin: -0.5em; + padding: 1em 0; +} + +.Card { + border-bottom: 1px solid #f2f2f0; + border-radius: 2px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); + display: grid; + flex: 0 1 0; + grid-template-areas: + "title title title icon" + "body body body body" + "notes button button button"; + grid-template-columns: 5fr 1fr 2fr 1fr; + grid-template-rows: auto auto auto; + margin: 0.5em; + min-width: 25em; + padding: 1em; +} + +.Card__header { + grid-area: title; + color: #333333; + margin: 0; +} + +.Card__body { + grid-area: body; + color: #666666; + margin: 1em 0; +} + +.Card__notes { + grid-area: notes; +} + +.Card__button { + grid-area: button; + justify-self: end; + align-self: end; +} + +.Card__icon { + grid-area: icon; +} + +.Card__img { + max-width: 100%; +} diff --git a/src/components/Card/index.tsx b/src/components/Card/index.tsx new file mode 100644 index 00000000000..9d49b51047b --- /dev/null +++ b/src/components/Card/index.tsx @@ -0,0 +1,44 @@ +import * as React from "react"; +import { Link } from "react-router-dom"; + +import "./Card.css"; + +export interface ICardProps { + header: string | JSX.Element | JSX.Element[]; + body: string | JSX.Element | JSX.Element[]; + button?: JSX.Element; + buttonText?: string | JSX.Element; + onClick?: () => (...args: any[]) => Promise<any>; + linkTo?: string; + notes?: JSX.Element; + icon?: string; +} + +export const CardContainer = (props: any) => { + return <div className="CardContainer">{props.children}</div>; +}; + +export const Card = (props: ICardProps) => { + const { header, body, buttonText, onClick, linkTo, notes, icon } = props; + let button = props.button ? ( + props.button + ) : ( + <button onClick={onClick} className="button button-primary" style={{ width: "fit-content" }}> + {buttonText} + </button> + ); + if (linkTo) { + button = <Link to={linkTo}>{button}</Link>; + } + return ( + <div className="Card"> + <h5 className="Card__header">{header}</h5> + <div className="Card__body">{body}</div> + <div className="Card__notes">{notes}</div> + <div className="Card__button">{button}</div> + <div className="Card__icon"> + <img className="Card__img" src={icon} /> + </div> + </div> + ); +}; diff --git a/src/components/ChartView/ChartDeployButton.tsx b/src/components/ChartView/ChartDeployButton.tsx index 56d94b9f77a..0ac5a9c98e8 100644 --- a/src/components/ChartView/ChartDeployButton.tsx +++ b/src/components/ChartView/ChartDeployButton.tsx @@ -27,12 +27,12 @@ interface IChartDeployButtonState { namespace: string; values: string; valuesModified: boolean; - error: string | null; + error?: string; } class ChartDeployButton extends React.Component<IChartDeployButtonProps, IChartDeployButtonState> { public state: IChartDeployButtonState = { - error: null, + error: undefined, isDeploying: false, modalIsOpen: false, namespace: "default", diff --git a/src/components/ClassList/index.tsx b/src/components/ClassList/index.tsx new file mode 100644 index 00000000000..eeb25985b9a --- /dev/null +++ b/src/components/ClassList/index.tsx @@ -0,0 +1,62 @@ +import * as React from "react"; + +import { IServiceBroker, IServiceClass } from "../../shared/ServiceCatalog"; +import { Card, CardContainer } from "../Card"; + +export interface IClassListProps { + broker: IServiceBroker | undefined; + classes: IServiceClass[]; + getBrokers: () => Promise<IServiceBroker[]>; + getClasses: () => Promise<IServiceClass[]>; +} + +export class ClassList extends React.Component<IClassListProps> { + public componentDidMount() { + this.props.getBrokers(); + this.props.getClasses(); + } + + public render() { + const { classes } = this.props; + return ( + <div> + <h2>Classes</h2> + <p>Types of services available from this broker</p> + <CardContainer> + {/* {classes.map(svcClass => { */} + {classes + .sort((a, b) => a.spec.externalName.localeCompare(b.spec.externalName)) + .map(svcClass => { + const tags = svcClass.spec.tags.reduce<string>((joined, tag) => { + return `${joined} ${tag},`; + }, ""); + const { spec } = svcClass; + const { externalMetadata } = spec; + const name = externalMetadata ? externalMetadata.displayName : spec.externalName; + const description = externalMetadata + ? externalMetadata.longDescription + : spec.description; + const imageUrl = externalMetadata && externalMetadata.imageUrl; + + const card = ( + <Card + key={svcClass.metadata.uid} + header={name} + icon={imageUrl} + body={description} + buttonText="View Plans" + linkTo={`${location.pathname}/${svcClass.spec.externalName}`} + notes={ + <span style={{ fontSize: "small" }}> + <strong>Tags:</strong> {tags} + </span> + } + /> + ); + return card; + })} + </CardContainer> + </div> + ); + } +} diff --git a/src/components/ClassView/index.tsx b/src/components/ClassView/index.tsx new file mode 100644 index 00000000000..6f54c2d6799 --- /dev/null +++ b/src/components/ClassView/index.tsx @@ -0,0 +1,83 @@ +import * as React from "react"; +import { RouterAction } from "react-router-redux"; + +import { IServiceClass, IServicePlan } from "../../shared/ServiceCatalog"; +import { Card, CardContainer } from "../Card"; +import ProvisionButton from "../ProvisionButton"; + +interface IClassViewProps { + classes: IServiceClass[]; + classname: string; + getCatalog: () => Promise<any>; + plans: IServicePlan[]; + provision: ( + instanceName: string, + namespace: string, + className: string, + planName: string, + parameters: {}, + ) => Promise<any>; + push: (location: string) => RouterAction; + svcClass: IServiceClass | undefined; +} + +export class ClassView extends React.Component<IClassViewProps> { + public componentDidMount() { + this.props.getCatalog(); + } + + public render() { + const { classes, classname, plans, provision, push, svcClass } = this.props; + const classPlans = svcClass + ? plans.filter(plan => plan.spec.clusterServiceClassRef.name === svcClass.metadata.name) + : []; + + return ( + <div className="class-view"> + <h3>Plans: {classname}</h3> + <p>Service Plans available for provisioning under {classname}</p> + <CardContainer> + {svcClass && + classPlans.map(plan => { + const serviceClass = classes.find( + potential => potential.metadata.name === plan.spec.clusterServiceClassRef.name, + ); + const { spec } = plan; + const { externalMetadata } = spec; + const name = externalMetadata ? externalMetadata.displayName : spec.externalName; + const description = + externalMetadata && externalMetadata.bullets + ? externalMetadata.bullets + : [spec.description]; + const free = plan.spec.free ? <span>Free ✓</span> : undefined; + const bullets = ( + <div> + <ul>{description.map(bullet => <li key={bullet}>{bullet}</li>)}</ul> + </div> + ); + + const card = ( + <Card + key={plan.spec.externalID} + header={name} + body={bullets} + notes={free} + button={ + <ProvisionButton + selectedClass={serviceClass} + selectedPlan={plan} + plans={plans} + classes={classes} + provision={provision} + push={push} + /> + } + /> + ); + return card; + })} + </CardContainer> + </div> + ); + } +} diff --git a/src/components/Dashboard/Dashboard.tsx b/src/components/Dashboard/Dashboard.tsx index c5f0b4ffca7..7d3a0e5ac32 100644 --- a/src/components/Dashboard/Dashboard.tsx +++ b/src/components/Dashboard/Dashboard.tsx @@ -13,7 +13,7 @@ class Dashboard extends React.Component { <div>No Apps installed</div> <div className="padding-normal"> <Link className="button button-primary" to="/charts"> - deploy one + Deploy Chart </Link> </div> </main> diff --git a/src/components/ProvisionButton/index.tsx b/src/components/ProvisionButton/index.tsx new file mode 100644 index 00000000000..ab27c5540c5 --- /dev/null +++ b/src/components/ProvisionButton/index.tsx @@ -0,0 +1,208 @@ +import * as React from "react"; +import AceEditor from "react-ace"; +import * as Modal from "react-modal"; +import { RouterAction } from "react-router-redux"; + +import { IServiceClass, IServicePlan } from "../../shared/ServiceCatalog"; + +import "brace/mode/json"; +import "brace/theme/xcode"; + +interface IProvisionButtonProps { + plans: IServicePlan[]; + classes: IServiceClass[]; + selectedClass?: IServiceClass; + selectedPlan?: IServicePlan; + provision: ( + releaseName: string, + namespace: string, + className: string, + planName: string, + parameters: {}, + ) => Promise<{}>; + push: (location: string) => RouterAction; +} + +interface IProvisionButtonState { + isProvisioning: boolean; + modalIsOpen: boolean; + // deployment options + releaseName: string; + namespace: string; + selectedPlan: IServicePlan | undefined; + selectedClass: IServiceClass | undefined; + parameters: string; + error?: string; +} + +class ProvisionButton extends React.Component<IProvisionButtonProps, IProvisionButtonState> { + public state: IProvisionButtonState = { + error: undefined, + isProvisioning: false, + modalIsOpen: false, + namespace: "default", + parameters: JSON.stringify({ resourceGroup: "default", location: "eastus" }, undefined, 2), + releaseName: "", + selectedClass: this.props.selectedClass, + selectedPlan: this.props.selectedPlan, + }; + + public render() { + const { plans, classes } = this.props; + const { selectedClass, selectedPlan } = this.state; + return ( + <div className="ProvisionButton"> + {this.state.isProvisioning && <div>Provisioning...</div>} + <button + className="button button-primary" + onClick={this.openModel} + disabled={this.state.isProvisioning} + > + Provision + </button> + <Modal + isOpen={this.state.modalIsOpen} + onRequestClose={this.closeModal} + contentLabel="Modal" + > + {this.state.error && ( + <div className="container padding-v-bigger bg-action">{this.state.error}</div> + )} + <form onSubmit={this.handleProvision}> + <div> + <label htmlFor="releaseName">Name</label> + <input + id="releaseName" + onChange={this.handleReleaseNameChange} + value={this.state.releaseName} + required={true} + /> + </div> + <div> + <label htmlFor="namespace">Namespace</label> + <input + name="namespace" + onChange={this.handleNamespaceChange} + value={this.state.namespace} + /> + </div> + <div> + <label htmlFor="classes">Classes</label> + <select onChange={this.onClassChange}> + {classes.map(c => ( + <option + key={c.spec.externalName} + selected={c.metadata.name === (selectedClass && selectedClass.metadata.name)} + value={c.spec.externalName} + > + {c.spec.externalName} + </option> + ))} + </select> + </div> + <div> + <label htmlFor="plans">Plans</label> + <select onChange={this.onPlanChange}> + {plans + .filter( + plan => + plan.spec.clusterServiceClassRef.name === + (selectedClass && selectedClass.metadata.name), + ) + .map(p => ( + <option + key={p.spec.externalName} + value={p.spec.externalName} + selected={p.metadata.name === (selectedPlan && selectedPlan.metadata.name)} + > + {p.spec.externalName} + </option> + ))} + </select> + </div> + <div style={{ marginBottom: "1em" }}> + <label htmlFor="values">Parameters (JSON)</label> + <AceEditor + mode="json" + theme="xcode" + name="values" + width="100%" + height="200px" + onChange={this.handleParametersChange} + setOptions={{ showPrintMargin: false }} + value={this.state.parameters} + /> + </div> + <div> + <button className="button button-primary" type="submit"> + Submit + </button> + <button className="button" onClick={this.closeModal}> + Cancel + </button> + </div> + </form> + </Modal> + </div> + ); + } + + public openModel = () => { + this.setState({ + modalIsOpen: true, + }); + }; + + public closeModal = () => { + this.setState({ + modalIsOpen: false, + }); + }; + + public handleProvision = async (e: React.FormEvent<HTMLFormElement>) => { + e.preventDefault(); + const { provision, push } = this.props; + this.setState({ isProvisioning: true }); + const { releaseName, namespace, selectedClass, selectedPlan, parameters } = this.state; + + try { + const parametersObject = JSON.parse(parameters); + if (selectedClass && selectedPlan) { + await provision( + releaseName, + namespace, + selectedClass.spec.externalName, + selectedPlan.spec.externalName, + parametersObject, + ); + push(`/services`); + } + } catch (err) { + this.setState({ isProvisioning: false, error: err.toString() }); + } + }; + + public handleReleaseNameChange = (e: React.FormEvent<HTMLInputElement>) => { + this.setState({ releaseName: e.currentTarget.value }); + }; + public handleNamespaceChange = (e: React.FormEvent<HTMLInputElement>) => { + this.setState({ namespace: e.currentTarget.value }); + }; + public handleParametersChange = (parameter: string) => { + this.setState({ parameters: parameter }); + }; + + public onClassChange = (e: React.ChangeEvent<HTMLSelectElement>) => + this.setState({ + selectedClass: + this.props.classes.find(svcClass => svcClass.spec.externalName === e.target.value) || + undefined, + }); + public onPlanChange = (e: React.ChangeEvent<HTMLSelectElement>) => + this.setState({ + selectedPlan: + this.props.plans.find(plan => plan.spec.externalName === e.target.value) || undefined, + }); +} + +export default ProvisionButton; diff --git a/src/components/ServiceBrokerList/index.tsx b/src/components/ServiceBrokerList/index.tsx new file mode 100644 index 00000000000..e2b62aaa757 --- /dev/null +++ b/src/components/ServiceBrokerList/index.tsx @@ -0,0 +1,31 @@ +import * as React from "react"; + +import { IServiceBroker } from "../../shared/ServiceCatalog"; +import { Card, CardContainer } from "../Card"; + +interface IServiceBrokerListProps { + brokers: IServiceBroker[]; +} + +export const ServiceBrokerList = (props: IServiceBrokerListProps) => { + const { brokers } = props; + return ( + <div className="service-broker-list"> + <h3>My Brokers</h3> + <CardContainer> + {brokers.map(broker => { + const card = ( + <Card + key={broker.metadata.uid} + header={broker.metadata.name} + body={broker.spec.url} + buttonText="View" + linkTo={`/services/brokers/${broker.metadata.name}`} + /> + ); + return card; + })} + </CardContainer> + </div> + ); +}; diff --git a/src/components/ServiceCatalog/index.tsx b/src/components/ServiceCatalog/index.tsx new file mode 100644 index 00000000000..4884fa5a705 --- /dev/null +++ b/src/components/ServiceCatalog/index.tsx @@ -0,0 +1,43 @@ +import * as React from "react"; +import { Link } from "react-router-dom"; + +import { ServiceBrokerList } from "../../components/ServiceBrokerList"; +import { IServiceCatalogState } from "../../reducers/catalog"; + +export interface IServiceCatalogDispatch { + checkCatalogInstalled: () => Promise<boolean>; + getCatalog: () => Promise<any>; +} + +export class ServiceCatalogView extends React.Component< + IServiceCatalogDispatch & IServiceCatalogState +> { + public async componentDidMount() { + this.props.checkCatalogInstalled(); + this.props.getCatalog(); + } + + public render() { + const { brokers, isInstalled } = this.props; + + return ( + <div className="service-list-container"> + <h1>Service Catalog</h1> + {!isInstalled ? ( + <div> + <p>Service Catalog not installed.</p> + <div className="padding-normal"> + <Link className="button button-primary" to={`/charts`}> + Install Catalog + </Link> + </div> + </div> + ) : ( + <div> + <ServiceBrokerList brokers={brokers} /> + </div> + )} + </div> + ); + } +} diff --git a/src/components/ServiceList/index.tsx b/src/components/ServiceList/index.tsx new file mode 100644 index 00000000000..ce0ca54c942 --- /dev/null +++ b/src/components/ServiceList/index.tsx @@ -0,0 +1,53 @@ +import * as React from "react"; +import { IServiceBroker, IServiceClass, IServicePlan } from "../../shared/ServiceCatalog"; + +interface IServiceListProps { + brokers: IServiceBroker[]; + classes: IServiceClass[]; + plans: IServicePlan[]; + getCatalog: () => Promise<any>; +} + +export class ServiceList extends React.Component<IServiceListProps> { + public async componentDidMount() { + this.props.getCatalog(); + } + + public render() { + const { brokers, classes, plans } = this.props; + return ( + <div className="service-list-container"> + <h2>Brokers</h2> + <dl> + {brokers.length > 0 && + brokers.map(broker => { + return [ + <dt key={broker.metadata.name}>{broker.metadata.name}</dt>, + <dd key={broker.spec.url}>{broker.spec.url}</dd>, + ]; + })} + </dl> + <h2>Classes</h2> + <dl> + {classes.length > 0 && + classes.map(serviceClass => { + return [ + <dt key={serviceClass.spec.externalName}>{serviceClass.spec.externalName}</dt>, + <dd key={serviceClass.spec.description}>{serviceClass.spec.description}</dd>, + ]; + })} + </dl> + <h2>Plans</h2> + <dl> + {plans.length > 0 && + plans.map(plan => { + return [ + <dt key={plan.spec.externalName}>{plan.spec.externalName}</dt>, + <dd key={plan.spec.description}>{plan.spec.description}</dd>, + ]; + })} + </dl> + </div> + ); + } +} diff --git a/src/components/Sidebar/Sidebar.tsx b/src/components/Sidebar/Sidebar.tsx index 769a5227d53..317e02e14db 100644 --- a/src/components/Sidebar/Sidebar.tsx +++ b/src/components/Sidebar/Sidebar.tsx @@ -4,6 +4,22 @@ import { Link } from "react-router-dom"; import placeholder from "../../placeholder.png"; import "./Sidebar.css"; +const sidebarItem = (props: { to: string; text: string; imageUrl?: string }) => { + const { to, text, imageUrl } = props; + const imageSrc: string = imageUrl || placeholder; + + return ( + <li className="padding-v-normal"> + <Link to={to}> + <img src={imageSrc} height="48" /> + <div className="type-small"> + <span>{text}</span> + </div> + </Link> + </li> + ); +}; + class Sidebar extends React.Component { public render() { return ( @@ -19,10 +35,9 @@ class Sidebar extends React.Component { <img src={placeholder} height="48" /> <div className="type-small">Functions</div> </li> - <li className="padding-v-normal"> - <img src={placeholder} height="48" /> - <div className="type-small">Service Catalog</div> - </li> + {sidebarItem({ to: "/charts", text: "Charts" })} + {sidebarItem({ to: "/services", text: "Service Catalog" })} + {sidebarItem({ to: "/repos", text: "App Repositories" })} </ul> </aside> ); diff --git a/src/components/SyncButton/index.tsx b/src/components/SyncButton/index.tsx new file mode 100644 index 00000000000..703902996fa --- /dev/null +++ b/src/components/SyncButton/index.tsx @@ -0,0 +1,49 @@ +import * as React from "react"; +import { IServiceBroker } from "../../shared/ServiceCatalog"; + +interface ISyncButtonProps { + broker: IServiceBroker; + sync: (broker: IServiceBroker) => Promise<{}>; +} + +interface ISyncButtonState { + broker: IServiceBroker | undefined; + isSyncing: boolean; + error: string | undefined; +} + +class SyncButton extends React.Component<ISyncButtonProps, ISyncButtonState> { + public state: ISyncButtonState = { + broker: this.props.broker, + error: undefined, + isSyncing: false, + }; + + public handleSync = async () => { + const { sync, broker } = this.props; + this.setState({ isSyncing: true }); + + try { + await sync(broker).then(async () => this.setState({ isSyncing: false })); + } catch (err) { + this.setState({ isSyncing: false, error: err.toString() }); + } + }; + + public render() { + return ( + <div className="SyncButton"> + {this.state.isSyncing && <div>Syncing...</div>} + <button + className="button button-primary" + disabled={this.state.isSyncing} + onClick={this.handleSync} + > + Sync + </button> + </div> + ); + } +} + +export default SyncButton; diff --git a/src/containers/BrokerView.ts b/src/containers/BrokerView.ts new file mode 100644 index 00000000000..ed90d72ac2b --- /dev/null +++ b/src/containers/BrokerView.ts @@ -0,0 +1,56 @@ +import { connect } from "react-redux"; +import { Dispatch } from "redux"; + +import actions from "../actions"; +import { BrokerView } from "../components/BrokerView"; +import { IServiceBroker } from "../shared/ServiceCatalog"; +import { IStoreState } from "../shared/types"; + +interface IRouteProps { + match: { + params: { + brokerName: string; + }; + }; +} + +function mapStateToProps({ catalog }: IStoreState, { match: { params } }: IRouteProps) { + const broker = + catalog.brokers.find( + potental => !!potental.metadata.name.match(new RegExp(params.brokerName, "i")), + ) || undefined; + const plans = broker + ? catalog.plans.filter( + plan => !!plan.spec.clusterServiceBrokerName.match(new RegExp(broker.metadata.name, "i")), + ) + : []; + const classes = broker + ? catalog.classes.filter( + serviceClass => + !!serviceClass.spec.clusterServiceBrokerName.match(new RegExp(broker.metadata.name, "i")), + ) + : []; + const instances = broker ? catalog.instances : []; + const bindings = broker ? catalog.bindings : []; + return { + bindings, + broker, + classes, + instances, + plans, + }; +} + +function mapDispatchToProps(dispatch: Dispatch<IStoreState>) { + return { + getCatalog: async () => { + dispatch(actions.catalog.getCatalog()); + }, + sync: async (broker: IServiceBroker) => { + await dispatch(actions.catalog.sync(broker)); + await dispatch(actions.catalog.getCatalog()); + }, + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(BrokerView); diff --git a/src/containers/ClassListContainer.ts b/src/containers/ClassListContainer.ts new file mode 100644 index 00000000000..edd09806ccc --- /dev/null +++ b/src/containers/ClassListContainer.ts @@ -0,0 +1,48 @@ +import { connect } from "react-redux"; +import { Dispatch } from "redux"; + +import actions from "../actions"; +import { ClassList } from "../components/ClassList"; +import { IStoreState } from "../shared/types"; + +interface IRouteProps { + match: { + params: { + brokerName: string; + className: string; + }; + }; +} + +function mapStateToProps({ catalog }: IStoreState, props: IRouteProps) { + const broker = + catalog.brokers.find( + potental => !!potental.metadata.name.match(new RegExp(props.match.params.brokerName, "i")), + ) || undefined; + const classes = broker + ? catalog.classes.filter( + serviceClass => + !!serviceClass.spec.clusterServiceBrokerName.match(new RegExp(broker.metadata.name, "i")), + ) + : []; + + return { + broker, + classes, + }; +} + +function mapDispatchToProps(dispatch: Dispatch<IStoreState>) { + return { + getBrokers: async () => { + const brokers = await dispatch(actions.catalog.getBrokers()); + return brokers; + }, + getClasses: async () => { + const classes = await dispatch(actions.catalog.getClasses()); + return classes; + }, + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(ClassList); diff --git a/src/containers/ClassView.ts b/src/containers/ClassView.ts new file mode 100644 index 00000000000..aef0b79cb7d --- /dev/null +++ b/src/containers/ClassView.ts @@ -0,0 +1,49 @@ +import { connect } from "react-redux"; +import { push } from "react-router-redux"; +import { Dispatch } from "redux"; + +import actions from "../actions"; +import { ClassView } from "../components/ClassView"; +import { IStoreState } from "../shared/types"; + +interface IRouteProps { + match: { + params: { + brokerName: string; + className: string; + }; + }; +} + +function mapStateToProps({ catalog }: IStoreState, { match: { params } }: IRouteProps) { + const svcClass = + catalog.classes.find( + potential => !!potential.spec.externalName.match(new RegExp(params.className, "i")), + ) || undefined; + return { + classes: catalog.classes, + classname: params.className, + plans: catalog.plans, + svcClass, + }; +} + +function mapDispatchToProps(dispatch: Dispatch<IStoreState>) { + return { + getCatalog: async () => { + dispatch(actions.catalog.getCatalog()); + }, + provision: async ( + instanceName: string, + namespace: string, + className: string, + planName: string, + parameters: {}, + ) => { + dispatch(actions.catalog.provision(instanceName, namespace, className, planName, parameters)); + }, + push: (location: string) => dispatch(push(location)), + }; +} + +export const ClassViewContainer = connect(mapStateToProps, mapDispatchToProps)(ClassView); diff --git a/src/containers/RepoListContainer.ts b/src/containers/RepoListContainer.ts new file mode 100644 index 00000000000..2ecc43c6c87 --- /dev/null +++ b/src/containers/RepoListContainer.ts @@ -0,0 +1,28 @@ +import { connect } from "react-redux"; +import { Dispatch } from "redux"; + +import actions from "../actions"; +import { AppRepoList } from "../components/AppRepoList"; +import { IStoreState } from "../shared/types"; + +function mapStateToProps({ repos }: IStoreState) { + return { + repos: repos.repos, + }; +} + +function mapDispatchToProps(dispatch: Dispatch<IStoreState>) { + return { + deleteRepo: async (name: string, namespace: string = "kubeapps") => { + return dispatch(actions.repos.deleteRepo(name, namespace)); + }, + fetchRepos: async () => { + return dispatch(actions.repos.fetchRepos()); + }, + install: async (name: string, url: string, namespace: string = "kubeapps") => { + return dispatch(actions.repos.installRepo(name, url, namespace)); + }, + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(AppRepoList); diff --git a/src/containers/Root.tsx b/src/containers/Root.tsx index a54a0c0deb3..014da0af2ab 100644 --- a/src/containers/Root.tsx +++ b/src/containers/Root.tsx @@ -1,7 +1,7 @@ import createHistory from "history/createBrowserHistory"; import * as React from "react"; import { Provider } from "react-redux"; -import { Route } from "react-router"; +import { Route, RouteComponentProps } from "react-router"; import { ConnectedRouter } from "react-router-redux"; import Dashboard from "../components/Dashboard"; @@ -10,25 +10,40 @@ import configureStore from "../store"; import ChartList from "./ChartListContainer"; import ChartView from "./ChartViewContainer"; +import BrokerView from "./BrokerView"; +import ClassListContainer from "./ClassListContainer"; +import { ClassViewContainer } from "./ClassView"; +import RepoListContainer from "./RepoListContainer"; +import ServiceCatalogContainer from "./ServiceCatalogContainer"; + const history = createHistory(); const store = configureStore(history); class Root extends React.Component { + public static exactRoutes: { + [route: string]: React.ComponentType<RouteComponentProps<any>> | React.ComponentType<any>; + } = { + "/": Dashboard, + "/charts": ChartList, + "/charts/:repo": ChartList, + "/charts/:repo/:id": ChartView, + "/charts/:repo/:id/versions/:version": ChartView, + "/repos": RepoListContainer, + "/services": ServiceCatalogContainer, + "/services/brokers/:brokerName/classes": ClassListContainer, + "/services/brokers/:brokerName/classes/:className": ClassViewContainer, + "/services/brokers/:name": BrokerView, + }; + public render() { return ( <Provider store={store}> <ConnectedRouter history={history}> <Layout> <section className="routes"> - <Route exact={true} path="/" component={Dashboard} /> - <Route exact={true} path="/charts" component={ChartList} /> - <Route exact={true} path="/charts/:repo" component={ChartList} /> - <Route exact={true} path="/charts/:repo/:id" component={ChartView} /> - <Route - exact={true} - path="/charts/:repo/:id/versions/:version" - component={ChartView} - /> + {Object.keys(Root.exactRoutes).map(route => ( + <Route exact={true} path={route} component={Root.exactRoutes[route]} /> + ))} </section> </Layout> </ConnectedRouter> diff --git a/src/containers/ServiceCatalogContainer.ts b/src/containers/ServiceCatalogContainer.ts new file mode 100644 index 00000000000..aaaee4f7e71 --- /dev/null +++ b/src/containers/ServiceCatalogContainer.ts @@ -0,0 +1,28 @@ +import { connect } from "react-redux"; +import { Dispatch } from "redux"; + +import actions from "../actions"; +import { ServiceCatalogView } from "../components/ServiceCatalog"; +import { ServiceCatalog } from "../shared/ServiceCatalog"; +import { IStoreState } from "../shared/types"; + +function mapStateToProps({ catalog }: IStoreState) { + return { + ...catalog, + }; +} + +function mapDispatchToProps(dispatch: Dispatch<IStoreState>) { + return { + checkCatalogInstalled: async () => { + const isInstalled = await ServiceCatalog.isCatalogInstalled(); + isInstalled + ? dispatch(actions.catalog.installed()) + : dispatch(actions.catalog.notInstalled()); + return isInstalled; + }, + getCatalog: () => dispatch(actions.catalog.getCatalog()), + }; +} + +export default connect(mapStateToProps, mapDispatchToProps)(ServiceCatalogView); diff --git a/src/containers/ServiceListContainer.ts b/src/containers/ServiceListContainer.ts new file mode 100644 index 00000000000..bc0f896d0aa --- /dev/null +++ b/src/containers/ServiceListContainer.ts @@ -0,0 +1,23 @@ +import { connect } from "react-redux"; +import { Dispatch } from "redux"; + +import actions from "../actions"; +import { ServiceList } from "../components/ServiceList"; +import { IStoreState } from "../shared/types"; + +function mapStateToProps({ catalog }: IStoreState) { + const { brokers, classes, plans } = catalog; + return { + brokers, + classes, + plans, + }; +} + +function mapDispatchToProps(dispatch: Dispatch<IStoreState>) { + return { + getCatalog: async () => dispatch(actions.catalog.getCatalog()), + }; +} + +export const ServiceListContainer = connect(mapStateToProps, mapDispatchToProps)(ServiceList); diff --git a/src/reducers/catalog.ts b/src/reducers/catalog.ts new file mode 100644 index 00000000000..7ad514f58d4 --- /dev/null +++ b/src/reducers/catalog.ts @@ -0,0 +1,63 @@ +import { getType } from "typesafe-actions"; + +import actions from "../actions"; +import { ServiceCatalogAction } from "../actions/catalog"; +import { + IServiceBinding, + IServiceBroker, + IServiceClass, + IServiceInstance, + IServicePlan, +} from "../shared/ServiceCatalog"; + +export interface IServiceCatalogState { + bindings: IServiceBinding[]; + brokers: IServiceBroker[]; + classes: IServiceClass[]; + instances: IServiceInstance[]; + isChecking: boolean; + isInstalled: boolean; + plans: IServicePlan[]; +} + +const initialState: IServiceCatalogState = { + bindings: [], + brokers: [], + classes: [], + instances: [], + isChecking: true, + isInstalled: false, + plans: [], +}; + +export const catalogReducer = ( + state: IServiceCatalogState = initialState, + action: ServiceCatalogAction, +): IServiceCatalogState => { + const { catalog } = actions; + switch (action.type) { + case getType(catalog.installed): + return { ...state, isChecking: false, isInstalled: true }; + case getType(catalog.notInstalled): + return { ...state, isChecking: false, isInstalled: false }; + case getType(catalog.checkCatalogInstall): + return { ...state, isChecking: true }; + case getType(catalog.receiveBrokers): + const { brokers } = action; + return { ...state, brokers }; + case getType(catalog.receiveBindings): + const { bindings } = action; + return { ...state, bindings }; + case getType(catalog.receiveClasses): + const { classes } = action; + return { ...state, classes }; + case getType(catalog.receiveInstances): + const { instances } = action; + return { ...state, instances }; + case getType(catalog.receivePlans): + const { plans } = action; + return { ...state, plans }; + default: + return { ...state }; + } +}; diff --git a/src/reducers/index.ts b/src/reducers/index.ts index 03c6f3cdbfb..2ebdce86af4 100644 --- a/src/reducers/index.ts +++ b/src/reducers/index.ts @@ -2,10 +2,14 @@ import { routerReducer } from "react-router-redux"; import { combineReducers } from "redux"; import { IStoreState } from "../shared/types"; +import { catalogReducer } from "./catalog"; import chartsReducer from "./charts"; +import { reposReducer } from "./repos"; const rootReducer = combineReducers<IStoreState>({ + catalog: catalogReducer, charts: chartsReducer, + repos: reposReducer, router: routerReducer, }); diff --git a/src/reducers/repos.ts b/src/reducers/repos.ts new file mode 100644 index 00000000000..ca7030488cf --- /dev/null +++ b/src/reducers/repos.ts @@ -0,0 +1,65 @@ +import { getType } from "typesafe-actions"; + +import actions from "../actions"; +import { AppReposAction } from "../actions/repos"; +import { IAppRepository } from "../shared/types"; + +export interface IAppRepositoryState { + addingRepo: boolean; + lastAdded?: IAppRepository; + isFetching: boolean; + repos: IAppRepository[]; + selected?: IAppRepository; + form: { + name: string; + namespace: string; + url: string; + show: boolean; + }; + redirectTo?: string; +} + +const initialState: IAppRepositoryState = { + addingRepo: false, + form: { + name: "", + namespace: "", + show: false, + url: "", + }, + isFetching: false, + repos: [], +}; + +export const reposReducer = ( + state: IAppRepositoryState = initialState, + action: AppReposAction, +): IAppRepositoryState => { + switch (action.type) { + case getType(actions.repos.receiveRepos): + const { repos } = action; + return { ...state, isFetching: false, repos }; + case getType(actions.repos.requestRepos): + return { ...state, isFetching: true }; + case getType(actions.repos.addRepo): + return { ...state, addingRepo: true }; + case getType(actions.repos.addedRepo): + const { added } = action; + return { ...state, addingRepo: false, lastAdded: added, repos: [...state.repos, added] }; + case getType(actions.repos.resetForm): + return { ...state, form: { ...state.form, name: "", namespace: "", url: "" } }; + case getType(actions.repos.updateForm): + const { values } = action; + return { ...state, form: { ...state.form, ...values } }; + case getType(actions.repos.showForm): + return { ...state, form: { ...state.form, show: true } }; + case getType(actions.repos.hideForm): + return { ...state, form: { ...state.form, show: false } }; + case getType(actions.repos.redirect): + return { ...state, redirectTo: action.path }; + case getType(actions.repos.redirected): + return { ...state, redirectTo: undefined }; + default: + return state; + } +}; diff --git a/src/shared/AppRepository.ts b/src/shared/AppRepository.ts new file mode 100644 index 00000000000..625849dc72f --- /dev/null +++ b/src/shared/AppRepository.ts @@ -0,0 +1,39 @@ +import axios from "axios"; + +import { IAppRepository, IAppRepositoryList } from "./types"; + +export class AppRepository { + public static async list() { + const { data } = await axios.get<IAppRepositoryList>( + `${AppRepository.APIEndpoint}/apprepositories`, + ); + return data; + } + + public static async delete(name: string, namespace: string = "default") { + const { data } = await axios.delete(AppRepository.getSelfLink(name, namespace)); + return data; + } + + public static async create(name: string, url: string, namespace: string = "default") { + const { data } = await axios.post<IAppRepository>( + `${AppRepository.APIEndpoint}/namespaces/${namespace}/apprepositories`, + { + apiVersion: "kubeapps.com/v1alpha1", + kind: "AppRepository", + metadata: { + name, + namespace, + }, + spec: { type: "helm", url }, + }, + ); + return data; + } + + // private static serviceCatalogURL: string = "https://svc-catalog-charts.storage.googleapis.com"; + private static APIEndpoint: string = "/api/kube/apis/kubeapps.com/v1alpha1"; + private static getSelfLink(name: string, namespace: string = "default"): string { + return `${AppRepository.APIEndpoint}/namespaces/${namespace}/apprepositories/${name}`; + } +} diff --git a/src/shared/HelmRelease.ts b/src/shared/HelmRelease.ts new file mode 100644 index 00000000000..9d013501cd2 --- /dev/null +++ b/src/shared/HelmRelease.ts @@ -0,0 +1,40 @@ +import axios from "axios"; +import { IChart } from "./types"; + +export class HelmRelease { + public static async create(chart: IChart, releaseName: string, namespace: string) { + const endpoint = HelmRelease.getResourceLink(namespace); + const { data } = await axios.post(endpoint, { + data: { + apiVersion: "helm.bitnami.com/v1", + kind: "HelmRelease", + metadata: { + releaseName, + }, + spec: { + chartName: chart.attributes.name, + repoUrl: chart.attributes.repo.url, + version: chart.relationships.latestChartVersion.data.version, + }, + }, + }); + return data; + } + + public static async delete(selfLink: string) { + const { data } = await axios.delete(selfLink); + return data; + } + + // private static getSelfLink(name: string, namespace: string) { + // return `/api/kube/apis/helm.bitnami.com/v1/namespaces/${namespace}/helmreleases/${name}`; + // } + + private static getResourceLink(namespace?: string) { + if (namespace) { + return `/api/kube/apis/helm.bitnami.com/v1/namespaces/${namespace}/helmreleases`; + } else { + return `/api/kube/apis/helm.bitnami.com/v1/helmreleases`; + } + } +} diff --git a/src/shared/ServiceCatalog.ts b/src/shared/ServiceCatalog.ts new file mode 100644 index 00000000000..3f47fbea489 --- /dev/null +++ b/src/shared/ServiceCatalog.ts @@ -0,0 +1,322 @@ +import axios from "axios"; + +import * as urls from "../shared/url"; +import { AppRepository } from "./AppRepository"; +import { IAppRepository, IK8sList, IStatus } from "./types"; + +export class ServiceCatalog { + public static async getCatalogRepo(): Promise<IAppRepository | undefined> { + const repos = await AppRepository.list(); + const svcRepo = repos.items.find(repo => { + return !!( + repo.spec && + repo.spec.url && + repo.spec.url === "https://svc-catalog-charts.storage.googleapis.com" + ); + }); + + return svcRepo; + } + + public static async installCatalog(name: string, namespace: string) { + const url = "https://svc-catalog-charts.storage.googleapis.com"; + await AppRepository.create(name, url, namespace); + } + + public static async getServiceClasses() { + return ServiceCatalog.getItems<IServiceClass>("/clusterserviceclasses"); + } + + public static async getServiceBrokers() { + return ServiceCatalog.getItems<IServiceBroker>("/clusterservicebrokers"); + } + + public static async getServicePlans() { + return ServiceCatalog.getItems<IServicePlan>("/clusterserviceplans"); + } + + public static async getServiceBindings(): Promise<IServiceBinding[]> { + const bindings = await ServiceCatalog.getItems<IServiceBinding>("/servicebindings"); + + // initiate with undefined secrets + for (const binding of bindings) { + binding.spec = { + ...binding.spec, + secretDatabase: undefined, + secretHost: undefined, + secretPassword: undefined, + secretPort: undefined, + secretUsername: undefined, + }; + } + + return Promise.all( + bindings.map(binding => { + const { secretName } = binding.spec; + const { namespace } = binding.metadata; + return axios + .get<IK8sApiSecretResponse>(ServiceCatalog.secretEndpoint(namespace) + secretName) + .then(response => { + const { database, host, password, port, username } = response.data.data; + const spec = { + ...binding.spec, + secretDatabase: atob(database), + secretHost: atob(host), + secretPassword: atob(password), + secretPort: atob(port), + secretUsername: atob(username), + }; + return { ...binding, spec }; + }) + .catch(err => { + // return with undefined secrets + return { ...binding }; + }); + }), + ); + } + + public static async provisionInstance( + releaseName: string, + namespace: string, + className: string, + planName: string, + parameters: {}, + ) { + const { data } = await axios.post<IStatus>( + urls.api.serviceinstances.create(namespace), + { + apiVersion: "servicecatalog.k8s.io/v1beta1", + kind: "ServiceInstance", + metadata: { + name: releaseName, + }, + spec: { + clusterServiceClassExternalName: className, + clusterServicePlanExternalName: planName, + parameters, + }, + }, + { + validateStatus: statusCode => true, + }, + ); + + if (data.status === "Failure") { + throw new Error(data.message); + } + + return data; + } + + public static async syncBroker(broker: IServiceBroker) { + const { data } = await axios.patch<IStatus>( + urls.api.clusterservicebrokers.sync(broker), + { + spec: { + relistRequests: broker.spec.relistRequests + 1, + }, + }, + { + headers: { "Content-Type": "application/merge-patch+json" }, + validateStatus: statusCode => true, + }, + ); + + if (data.status === "Failure") { + throw new Error(data.message); + } + + return data; + } + + public static async getServiceInstances() { + return ServiceCatalog.getItems<IServiceInstance>("/serviceinstances"); + } + + public static async isCatalogInstalled(): Promise<boolean> { + try { + const { status } = await axios.get(ServiceCatalog.endpoint); + return status === 200; + } catch (err) { + return false; + } + } + + private static endpoint: string = "/api/kube/apis/servicecatalog.k8s.io/v1beta1"; + + private static secretEndpoint = (namespace: string = "default") => + `/api/kube/api/v1/namespaces/${namespace}/secrets/`; + + private static async getItems<T>(endpoint: string): Promise<T[]> { + const response = await axios.get<IK8sList<T, {}>>(ServiceCatalog.endpoint + endpoint); + // const response = await axios.get<IK8sApiListResponse<T>>(ServiceCatalog.endpoint + endpoint); + const json = response.data; + return json.items; + } +} + +interface IK8sApiSecretResponse { + kind: string; + apiVersion: string; + metadata: { + selfLink: string; + resourceVersion: string; + }; + data: { + database: string; + host: string; + password: string; + port: string; + username: string; + }; +} + +export interface IK8sApiListResponse<T> { + kind: string; + apiVersion: string; + metadata: { + selfLink: string; + resourceVersion: string; + }; + items: T[]; +} + +export interface IServiceClass { + metadata: { + creationTimestamp: string; + name: string; + resourceVersion: string; + selfLink: string; + uid: string; + }; + spec: { + bindable: boolean; + binding_retrievable: boolean; + clusterServiceBrokerName: string; + description: string; + externalID: string; + externalName: string; + planUpdatable: boolean; + tags: string[]; + externalMetadata?: { + displayName: string; + documentationUrl: string; + imageUrl: string; + longDescription: string; + supportUrl: string; + }; + }; + status: { + removedFromBrokerCatalog: boolean; + }; +} + +export interface ICondition { + type: string; + status: string; + lastTransitionTime: string; + reason: string; + message: string; +} + +export interface IServiceBroker { + metadata: { + name: string; + selfLink: string; + uid: string; + resourceVersion: string; + generation: number; + creationTimestamp: string; + finalizers: string[]; + }; + spec: { + url: string; + authInfo: any; // Look into + relistBehavior: string; + relistDuration: string; + relistRequests: number; + }; + status: { + conditions: ICondition[]; + reconciledGeneration: number; + lastCatalogRetrievalTime: string; + }; +} + +export interface IServicePlan { + metadata: { + name: string; + selfLink: string; + uid: string; + resourceVersion: string; + creationTimestamp: string; + }; + spec: { + clusterServiceBrokerName: string; + externalName: string; + externalID: string; + description: string; + externalMetadata?: { + displayName: string; + bullets: string[]; + }; + free: boolean; + clusterServiceClassRef: { + name: string; + }; + }; + status: { + removedFromBrokerCatalog: boolean; + }; +} + +export interface IServiceInstance { + metadata: { + name: string; + namespace: string; + selfLink: string; + uid: string; + resourceVersion: string; + creationTimestamp: string; + finalizers: string[]; + generation: number; + }; + spec: { + clusterServiceClassExternalName: string; + clusterServicePlanExternalName: string; + externalID: string; + clusterServicePlanRef: { + name: string; + }; + clusterServiceClassRef: { + name: string; + }; + }; + status: { conditions: ICondition[] }; +} + +export interface IServiceBinding { + metadata: { + name: string; + selfLink: string; + uid: string; + resourceVersion: string; + creationTimestamp: string; + finalizers: string[]; + generation: number; + namespace: string; + }; + spec: { + externalID: string; + instanceRef: { + name: string; + }; + secretName: string | undefined; + secretDatabase: string | undefined; + secretHost: string | undefined; + secretPassword: string | undefined; + secretPort: string | undefined; + secretUsername: string | undefined; + }; +} diff --git a/src/shared/types.ts b/src/shared/types.ts index 059928c9cd9..9d59c66f9ae 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -1,3 +1,6 @@ +import { IServiceCatalogState } from "../reducers/catalog"; +import { IAppRepositoryState } from "../reducers/repos"; + export interface IChartVersion { id: string; attributes: IChartVersionAttributes; @@ -53,5 +56,175 @@ export interface IChartState { } export interface IStoreState { + catalog: IServiceCatalogState; charts: IChartState; + repos: IAppRepositoryState; +} + +interface IK8sResource { + apiVersion: string; + kind: string; +} + +/** @see https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#objects */ +export interface IK8sObject<M, SP, ST> extends IK8sResource { + metadata: { + annotations?: { [key: string]: string }; + creationTimestamp?: string; + deletionTimestamp?: string | null; + generation?: number; + labels?: { [key: string]: string }; + name: string; + namespace: string; + resourceVersion?: string; + uid: string; + selfLink?: string; // Not in docs, but seems to exist everywhere + } & M; + spec?: SP; + status?: ST; +} + +/** @see https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#lists-and-simple-kinds */ +export interface IK8sList<I, M> extends IK8sResource { + items: I[]; + metadata?: { + resourceVersion?: string; + selfLink?: string; // Not in docs, but seems to exist everywhere + } & M; +} + +export interface IAppRepository + extends IK8sObject< + { + clusterName: string; + creationTimestamp: string; + deletionGracePeriodSeconds: string | null; + deletionTimestamp: string | null; + resourceVersion: string; + selfLink: string; + }, + { type: string; url: string }, + undefined + > {} + +export interface IAppRepositoryList + extends IK8sList< + IAppRepository, + { + continue: string; + resourceVersion: string; + selfLink: string; + } + > {} + +export interface IClusterServicePlan + extends IK8sObject< + { + namespace: undefined; + selfLink: string; + resourceVersion: string; + creationTimestamp: string; + }, + { + clusterServiceBrokerName: string; + externalName: string; + externalID: string; + description: string; + free: boolean; + clusterServiceClassRef: { [key: string]: string }; + }, + { removedFromBrokerCatalog: boolean } + > {} + +export interface IClusterServicePlanList + extends IK8sList<IClusterServicePlan, { selfLink: string; resourceVersion: string }> {} + +export interface IClusterServiceClass + extends IK8sObject< + { + creationTimestamp: string; + namespace: undefined; + resourceVersion: string; + selfLink: string; + }, + { + clusterServiceBrokerName: string; + externalName: string; + externalID: string; + description: string; + bindable: boolean; + binding_retrievable: string; + planUpdatable: string; + tags: string[]; + }, + { removedFromBrokerCatalog: boolean } + > {} + +export interface IClusterServiceClassList + extends IK8sList<IClusterServiceClass, { selfLink: string; resourceVersion: string }> {} + +interface ICondition { + type: string; + status: string; + lastTransitionTime: string; + reason: string; + message: string; +} + +/** + * @property authInfo Unknown what kind of auth types their are + */ +export interface IClusterServiceBroker + extends IK8sObject< + { + resourceVersion: string; + generation: number; + creationTimestamp: string; + finalizers: string[]; + }, + { + url: string; + authInfo: any; + relistBehavior: string; + relistDuration: string; + relistRequests: number; + }, + { + conditions: ICondition[]; + reconciledGeneration: number; + lastCatalogRetrievalTime: string; + } + > {} + +export interface IClusterServiceBrokerList + extends IK8sList<IClusterServiceBroker, { selfLink: string; resourceVersion: string }> {} + +/** @see https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md#response-status-kind */ +export interface IStatus extends IK8sResource { + kind: "Status"; + status: "Success" | "Failure"; + message: string; + reason: + | "BadRequest" + | "Unauthorized" + | "Forbidden" + | "NotFound" + | "AlreadyExists" + | "Conflict" + | "Invalid" + | "Timeout" + | "ServerTimeout" + | "MethodNotAllowed" + | "InternalError"; + details?: { + kind?: string; + name?: string; + causes?: IStatusCause[] | string; + }; +} + +interface IStatusCause { + field: string; + message: string; + reason: string; } diff --git a/src/shared/url.ts b/src/shared/url.ts index 729413c4e41..e77ed3d6b52 100644 --- a/src/shared/url.ts +++ b/src/shared/url.ts @@ -1,3 +1,4 @@ +import { IServiceBroker } from "./ServiceCatalog"; import { IChartVersion } from "./types"; export const app = { @@ -21,8 +22,27 @@ export const api = { listVersions: (id: string) => `${api.charts.get(id)}/versions`, }, + // /api/kube exposes kubectl add ?watch=true helmreleases: { create: (namespace = "default") => `/api/kube/apis/helm.bitnami.com/v1/namespaces/${namespace}/helmreleases`, }, + + apprepostories: { + base: `/api/kube/apis/kubeapps.com/v1alpha1`, + create: (namespace = "default") => + `${api.apprepostories.base}/namespaces/${namespace}/apprepositories`, + }, + + serviceinstances: { + base: `/api/kube/apis/servicecatalog.k8s.io/v1beta1`, + create: (namespace = "default") => + `${api.serviceinstances.base}/namespaces/${namespace}/serviceinstances`, + }, + + clusterservicebrokers: { + base: `/api/kube/apis/servicecatalog.k8s.io/v1beta1`, + sync: (broker: IServiceBroker) => + `${api.clusterservicebrokers.base}/clusterservicebrokers/${broker.metadata.name}`, + }, }; diff --git a/tsconfig.json b/tsconfig.json index 85973541495..25095b4c0bb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,9 @@ { "compilerOptions": { + "baseUrl": "src", "outDir": "build/dist", "module": "esnext", - "target": "es5", - "lib": ["es6", "dom"], + "target": "es6", "sourceMap": true, "allowJs": true, "jsx": "react", diff --git a/tslint.json b/tslint.json index 1e9f337d964..4e6c651683c 100644 --- a/tslint.json +++ b/tslint.json @@ -2,6 +2,8 @@ "defaultSeverity": "error", "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], "jsRules": {}, - "rules": {}, + "rules": { + "no-console": false + }, "rulesDirectory": [] } diff --git a/yarn.lock b/yarn.lock index b62eda00a62..481e7cbb110 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,8 +3,8 @@ "@babel/code-frame@^7.0.0-beta.35": - version "7.0.0-beta.38" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.38.tgz#c0af5930617e55e050336838e3a3670983b0b2b2" + version "7.0.0-beta.39" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.39.tgz#91c90bb65207fc5a55128cb54956ded39e850457" dependencies: chalk "^2.0.0" esutils "^2.0.2" @@ -21,8 +21,8 @@ "@types/enzyme" "*" "@types/enzyme@*", "@types/enzyme@^3.1.6": - version "3.1.7" - resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.1.7.tgz#358a28dda8df6841db665659278437d04571a094" + version "3.1.8" + resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.1.8.tgz#e0a4492994fafb2fccc1726f8b4d9960097a4a8c" dependencies: "@types/cheerio" "*" "@types/react" "*" @@ -32,12 +32,12 @@ resolved "https://registry.yarnpkg.com/@types/history/-/history-4.6.2.tgz#12cfaba693ba20f114ed5765467ff25fdf67ddb0" "@types/jest@^22.0.0": - version "22.0.1" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.0.1.tgz#6370a6d60cce3845e4cd5d00bf65f654264685bc" + version "22.1.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-22.1.1.tgz#231d7c60ed130200af9e96c82469ed25b59a7ea2" "@types/node@*", "@types/node@^9.3.0": - version "9.3.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-9.3.0.tgz#3a129cda7c4e5df2409702626892cb4b96546dd5" + version "9.4.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-9.4.0.tgz#b85a0bcf1e1cc84eb4901b7e96966aedc6f078d1" "@types/react-dom@^16.0.3": version "16.0.3" @@ -76,8 +76,8 @@ redux "^3.7.2" "@types/react-router@*", "@types/react-router@^4.0.20": - version "4.0.20" - resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.0.20.tgz#3404f54e44bba2239ea4320ea701d86d92f05486" + version "4.0.21" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-4.0.21.tgz#d6c7ea3b45dba02eb8f869629f3b7d7b6e9a7938" dependencies: "@types/history" "*" "@types/react" "*" @@ -89,10 +89,10 @@ "@types/react" "*" "@types/react@*", "@types/react@^16.0.34": - version "16.0.34" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.34.tgz#7a8f795afd8a404a9c4af9539b24c75d3996914e" + version "16.0.36" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.0.36.tgz#ceb5639013bdb92a94147883052e69bb2c22c69b" -abab@^1.0.3: +abab@^1.0.3, abab@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.4.tgz#5faad9c2c07f60dd76770f71cf025b62a63cfd4e" @@ -119,7 +119,7 @@ acorn-globals@^3.1.0: dependencies: acorn "^4.0.4" -acorn-globals@^4.0.0: +acorn-globals@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.1.0.tgz#ab716025dbe17c54d3ef81d32ece2b2d99fe2538" dependencies: @@ -129,9 +129,9 @@ 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.1.2: - version "5.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.3.0.tgz#7446d39459c54fb49a80e6ee6478149b940ec822" +acorn@^5.0.0, acorn@^5.3.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102" address@1.0.3, address@^1.0.1: version "1.0.3" @@ -173,11 +173,11 @@ amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" -ansi-align@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-1.1.0.tgz#2f0c1658829739add5ebb15e6b0c6e3423f016ba" +ansi-align@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f" dependencies: - string-width "^1.0.1" + string-width "^2.0.0" ansi-escapes@^1.0.0, ansi-escapes@^1.4.0: version "1.4.0" @@ -344,6 +344,10 @@ async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" +async-limiter@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + async@^1.4.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -392,6 +396,13 @@ aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" +axios@^0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.17.1.tgz#2d8e3e5d0bdbd7327f91bc814f5c57660f81824d" + dependencies: + follow-redirects "^1.2.5" + is-buffer "^1.1.5" + babel-code-frame@6.26.0, babel-code-frame@^6.11.0, 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" @@ -425,8 +436,8 @@ babel-core@^6.0.0, babel-core@^6.24.1, babel-core@^6.26.0: source-map "^0.5.6" babel-generator@^6.18.0, babel-generator@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" + 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" @@ -434,7 +445,7 @@ babel-generator@^6.18.0, babel-generator@^6.26.0: detect-indent "^4.0.0" jsesc "^1.3.0" lodash "^4.17.4" - source-map "^0.5.6" + source-map "^0.5.7" trim-right "^1.0.1" babel-helpers@^6.24.1: @@ -659,19 +670,17 @@ boom@5.x.x: dependencies: hoek "4.x.x" -boxen@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/boxen/-/boxen-0.6.0.tgz#8364d4248ac34ff0ef1b2f2bf49a6c60ce0d81b6" +boxen@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" dependencies: - ansi-align "^1.1.0" - camelcase "^2.1.0" - chalk "^1.1.1" + ansi-align "^2.0.0" + camelcase "^4.0.0" + chalk "^2.0.1" cli-boxes "^1.0.0" - filled-array "^1.0.0" - object-assign "^4.0.1" - repeating "^2.0.0" - string-width "^1.0.1" - widest-line "^1.0.0" + string-width "^2.0.0" + term-size "^1.2.0" + widest-line "^2.0.0" brace-expansion@^1.0.0, brace-expansion@^1.1.7: version "1.1.8" @@ -834,7 +843,7 @@ camelcase@^1.0.2: version "1.2.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" -camelcase@^2.0.0, camelcase@^2.1.0: +camelcase@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" @@ -842,7 +851,7 @@ camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" -camelcase@^4.1.0: +camelcase@^4.0.0, camelcase@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" @@ -856,12 +865,12 @@ caniuse-api@^1.5.2: lodash.uniq "^4.5.0" caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000793" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000793.tgz#3c00c66e423a7a1907c7dd96769a78b2afa8a72e" + version "1.0.30000802" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000802.tgz#f7dca342da1c12cf84ff2c80432e24c7e1cc36d9" caniuse-lite@^1.0.30000748, caniuse-lite@^1.0.30000792: - version "1.0.30000792" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000792.tgz#d0cea981f8118f3961471afbb43c9a1e5bbf0332" + version "1.0.30000802" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000802.tgz#807ead8e2b59a3b90f26702fb0b220623ad9f287" capture-stack-trace@^1.0.0: version "1.0.0" @@ -1092,7 +1101,11 @@ commander@2.12.x: version "2.12.2" resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555" -commander@^2.11.0, commander@^2.12.1, commander@^2.9.0, commander@~2.13.0: +commander@^2.11.0, commander@^2.12.1, commander@^2.9.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.14.0.tgz#7b25325963e6aace20d3a9285b09379b0c2208b5" + +commander@~2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" @@ -1118,19 +1131,16 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -configstore@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1" +configstore@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.1.tgz#094ee662ab83fad9917678de114faaea8fcdca90" dependencies: - dot-prop "^3.0.0" + dot-prop "^4.1.0" graceful-fs "^4.1.2" - mkdirp "^0.5.0" - object-assign "^4.0.1" - os-tmpdir "^1.0.0" - osenv "^0.1.0" - uuid "^2.0.1" - write-file-atomic "^1.1.2" - xdg-basedir "^2.0.0" + make-dir "^1.0.0" + unique-string "^1.0.0" + write-file-atomic "^2.0.0" + xdg-basedir "^3.0.0" connect-history-api-fallback@^1.3.0: version "1.5.0" @@ -1154,7 +1164,7 @@ 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: +content-type-parser@^1.0.1, content-type-parser@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.2.tgz#caabe80623e63638b2502fd4c7f12ff4ce2352e7" @@ -1230,7 +1240,7 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" -create-error-class@^3.0.1: +create-error-class@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6" dependencies: @@ -1292,6 +1302,10 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +crypto-random-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e" + 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" @@ -1452,6 +1466,10 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" +deepmerge@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.0.1.tgz#25c1c24f110fb914f80001b925264dd77f3f4312" + 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" @@ -1560,7 +1578,7 @@ 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.0.1: +dns-packet@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" dependencies: @@ -1593,8 +1611,8 @@ dom-urls@^1.1.0: urijs "^1.16.1" domain-browser@^1.1.1: - version "1.1.7" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" + 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" @@ -1642,9 +1660,9 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" -dot-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-3.0.0.tgz#1b708af094a49c9a0e7dbcad790aba539dac1177" +dot-prop@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.0.tgz#1f19e0c2e1aa0e32797c49799f2837ac6af69c57" dependencies: is-obj "^1.0.0" @@ -1652,11 +1670,9 @@ dotenv@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" -duplexer2@^0.1.4: +duplexer3@^0.1.4: version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - dependencies: - readable-stream "^2.0.2" + resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" duplexer@^0.1.1: version "0.1.1" @@ -1673,8 +1689,8 @@ ee-first@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.30: - version "1.3.31" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.31.tgz#00d832cba9fe2358652b0c48a8816c8e3a037e9f" + version "1.3.32" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.32.tgz#11d0684c0840e003c4be8928f8ac5f35dbc2b4e6" elegant-spinner@^1.0.1: version "1.0.1" @@ -1697,8 +1713,8 @@ emojis-list@^2.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" encodeurl@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" encoding@^0.1.11: version "0.1.12" @@ -1817,8 +1833,8 @@ es6-map@^0.1.3: event-emitter "~0.3.5" es6-promise@^4.0.5: - version "4.2.2" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.2.tgz#f722d7769af88bd33bc13ec6605e1f92966b82d9" + version "4.2.4" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29" es6-set@~0.1.5: version "0.1.5" @@ -1963,18 +1979,6 @@ execa@^0.8.0: signal-exit "^3.0.0" strip-eof "^1.0.0" -execa@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-0.9.0.tgz#adb7ce62cf985071f60580deb4a88b9e34712d01" - 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" - exenv@^1.2.0: version "1.2.2" resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" @@ -2178,10 +2182,6 @@ fill-range@^2.1.0: repeat-element "^1.1.2" repeat-string "^1.5.2" -filled-array@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filled-array/-/filled-array-1.1.0.tgz#c3c4f6c663b923459a9aa29912d2d031f1507f84" - finalhandler@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" @@ -2219,6 +2219,12 @@ flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" +follow-redirects@^1.2.5: + version "1.4.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.4.1.tgz#d8120f4518190f55aac65bb6fc7b85fcd666d6aa" + dependencies: + debug "^3.1.0" + for-in@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -2412,6 +2418,12 @@ glob@^7.0.3, glob@^7.0.5, glob@^7.1.1: once "^1.3.0" path-is-absolute "^1.0.0" +global-dirs@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" + dependencies: + ini "^1.3.4" + global-modules@1.0.0, global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -2455,24 +2467,20 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -got@^5.0.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35" +got@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0" dependencies: - create-error-class "^3.0.1" - duplexer2 "^0.1.4" + create-error-class "^3.0.0" + duplexer3 "^0.1.4" + get-stream "^3.0.0" is-redirect "^1.0.0" is-retry-allowed "^1.0.0" is-stream "^1.0.0" lowercase-keys "^1.0.0" - node-status-codes "^1.0.0" - object-assign "^4.0.1" - parse-json "^2.1.0" - pinkie-promise "^2.0.0" - read-all-stream "^3.0.0" - readable-stream "^2.0.5" - timed-out "^3.0.0" - unzip-response "^1.0.2" + safe-buffer "^5.0.1" + timed-out "^4.0.0" + unzip-response "^2.0.1" url-parse-lax "^1.0.0" graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9: @@ -2655,7 +2663,7 @@ 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: +html-encoding-sniffer@^1.0.1, html-encoding-sniffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8" dependencies: @@ -2723,8 +2731,8 @@ http-errors@1.6.2, http-errors@~1.6.2: statuses ">= 1.3.1 < 2" http-parser-js@>=0.4.0: - version "0.4.9" - resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.9.tgz#ea1a04fb64adff0242e9974f297dd4c3cad271e1" + version "0.4.10" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" http-proxy-middleware@~0.17.4: version "0.17.4" @@ -2762,18 +2770,13 @@ https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" -husky@^0.15.0-rc.2: - version "0.15.0-rc.2" - resolved "https://registry.yarnpkg.com/husky/-/husky-0.15.0-rc.2.tgz#5fac1eabf0ead19645eb3dc8bd14bc14fe16068e" +husky@^0.14.3: + version "0.14.3" + resolved "https://registry.yarnpkg.com/husky/-/husky-0.14.3.tgz#c69ed74e2d2779769a17ba8399b54ce0b63c12c3" dependencies: - cosmiconfig "^4.0.0" - execa "^0.9.0" - is-ci "^1.1.0" - pkg-dir "^2.0.0" - pupa "^1.0.0" - read-pkg "^3.0.0" - run-node "^0.2.0" - slash "^1.0.0" + is-ci "^1.0.10" + normalize-path "^1.0.0" + strip-indent "^2.0.0" iconv-lite@0.4.19, iconv-lite@^0.4.17, iconv-lite@~0.4.13: version "0.4.19" @@ -2793,6 +2796,10 @@ ieee754@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" +import-lazy@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43" + import-local@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/import-local/-/import-local-0.1.1.tgz#b1179572aacdc11c6a91009fb430dbcab5f668a8" @@ -2931,7 +2938,7 @@ 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, is-ci@^1.1.0: +is-ci@^1.0.10: version "1.1.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" dependencies: @@ -3013,6 +3020,13 @@ is-hexadecimal@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.1.tgz#6e084bbc92061fbb0971ec58b6ce6d404e24da69" +is-installed-globally@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" + dependencies: + global-dirs "^0.1.0" + is-path-inside "^1.0.0" + is-npm@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4" @@ -3585,8 +3599,8 @@ jest@20.0.4: jest-cli "^20.0.4" js-base64@^2.1.9: - version "2.4.1" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.1.tgz#e02813181cd53002888e918935467acb2910e596" + version "2.4.3" + resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.3.tgz#2e545ec2b0f2957f41356510205214e98fad6582" js-tokens@^3.0.0, js-tokens@^3.0.2: version "3.0.2" @@ -3611,33 +3625,35 @@ jsbn@~0.1.0: resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" jsdom@^11.5.1: - version "11.5.1" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.5.1.tgz#5df753b8d0bca20142ce21f4f6c039f99a992929" + version "11.6.2" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.6.2.tgz#25d1ef332d48adf77fc5221fe2619967923f16bb" dependencies: - abab "^1.0.3" - acorn "^5.1.2" - acorn-globals "^4.0.0" + abab "^1.0.4" + acorn "^5.3.0" + acorn-globals "^4.1.0" array-equal "^1.0.0" browser-process-hrtime "^0.1.2" - content-type-parser "^1.0.1" + content-type-parser "^1.0.2" cssom ">= 0.3.2 < 0.4.0" cssstyle ">= 0.2.37 < 0.3.0" domexception "^1.0.0" escodegen "^1.9.0" - html-encoding-sniffer "^1.0.1" + html-encoding-sniffer "^1.0.2" left-pad "^1.2.0" nwmatcher "^1.4.3" - parse5 "^3.0.2" - pn "^1.0.0" + parse5 "4.0.0" + pn "^1.1.0" request "^2.83.0" - request-promise-native "^1.0.3" - sax "^1.2.1" - symbol-tree "^3.2.1" + request-promise-native "^1.0.5" + sax "^1.2.4" + symbol-tree "^3.2.2" tough-cookie "^2.3.3" + w3c-hr-time "^1.0.1" webidl-conversions "^4.0.2" - whatwg-encoding "^1.0.1" - whatwg-url "^6.3.0" - xml-name-validator "^2.0.1" + whatwg-encoding "^1.0.3" + whatwg-url "^6.4.0" + ws "^4.0.0" + xml-name-validator "^3.0.0" jsdom@^9.12.0: version "9.12.0" @@ -3758,20 +3774,16 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" -latest-version@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-2.0.0.tgz#56f8d6139620847b8017f8f1f4d78e211324168b" +latest-version@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" dependencies: - package-json "^2.0.0" + package-json "^4.0.0" lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" -lazy-req@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/lazy-req/-/lazy-req-1.1.0.tgz#bdaebead30f8d824039ce0ce149d4daa07ba1fac" - lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -3793,9 +3805,9 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -lint-staged@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-6.0.1.tgz#855f2993ab4a265430e2fd9828427e648d65e6b4" +lint-staged@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-6.1.0.tgz#28f600c10a6cbd249ceb003118a1552e53544a93" dependencies: app-root-path "^2.0.0" chalk "^2.1.0" @@ -3885,15 +3897,6 @@ load-json-file@^2.0.0: pify "^2.0.0" strip-bom "^3.0.0" -load-json-file@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" - dependencies: - graceful-fs "^4.1.2" - parse-json "^4.0.0" - pify "^3.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" @@ -3923,8 +3926,8 @@ locate-path@^2.0.0: path-exists "^3.0.0" lodash-es@^4.2.0, lodash-es@^4.2.1: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7" + version "4.17.5" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.5.tgz#9fc6e737b1c4d151d8f9cae2247305d552ce748f" lodash._reinterpolate@~3.0.0: version "3.0.0" @@ -3955,8 +3958,8 @@ lodash.isequal@^4.1.1: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" lodash.isfunction@^3.0.8: - version "3.0.8" - resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.8.tgz#4db709fc81bc4a8fd7127a458a5346c5cdce2c6b" + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" lodash.isstring@^4.0.1: version "4.0.1" @@ -3992,8 +3995,8 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" "lodash@>=3.5 <5", lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0: - version "4.17.4" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + version "4.17.5" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" log-symbols@^1.0.2: version "1.0.2" @@ -4054,6 +4057,12 @@ macaddress@^0.2.8: version "0.2.8" resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" +make-dir@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.1.0.tgz#19b4369fe48c116f53c2af95ad102c0e39e85d51" + 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" @@ -4171,8 +4180,8 @@ mime@^1.4.1, mime@^1.5.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" mimic-fn@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + 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.0" @@ -4206,7 +4215,7 @@ minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" -mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.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: @@ -4221,11 +4230,11 @@ multicast-dns-service-types@^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.2" - resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.2.tgz#300b6133361f8aaaf2b8d1248e85c363fe5b95a0" + version "6.2.3" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.2.3.tgz#a0ec7bd9055c4282f790c3c82f4e28db3b31b229" dependencies: - dns-packet "^1.0.1" - thunky "^0.1.0" + dns-packet "^1.3.1" + thunky "^1.0.2" mute-stream@0.0.7: version "0.0.7" @@ -4270,9 +4279,9 @@ node-fetch@^1.0.1: encoding "^0.1.11" is-stream "^1.0.1" -node-forge@0.6.33: - version "0.6.33" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc" +node-forge@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.1.tgz#9da611ea08982f4b94206b3beb4cc9665f20c300" node-int64@^0.4.0: version "0.4.0" @@ -4331,10 +4340,6 @@ node-pre-gyp@^0.6.36, node-pre-gyp@^0.6.39: tar "^2.2.1" tar-pack "^3.4.0" -node-status-codes@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-status-codes/-/node-status-codes-1.0.0.tgz#5ae5541d024645d32a58fcddc9ceecea7ae3ac2f" - nomnom@~1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/nomnom/-/nomnom-1.6.2.tgz#84a66a260174408fc5b77a18f888eccc44fb6971" @@ -4358,6 +4363,10 @@ normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" +normalize-path@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-1.0.0.tgz#32d0e472f91ff345701c15a8311018d3b0a90379" + normalize-path@^2.0.0, normalize-path@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" @@ -4579,7 +4588,7 @@ 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.0, osenv@^0.1.4: +osenv@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" dependencies: @@ -4610,11 +4619,11 @@ p-try@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" -package-json@^2.0.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/package-json/-/package-json-2.4.0.tgz#0d15bd67d1cbbddbb2ca222ff2edb86bcb31a8bb" +package-json@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed" dependencies: - got "^5.0.0" + got "^6.7.1" registry-auth-token "^3.0.1" registry-url "^3.0.3" semver "^5.1.0" @@ -4659,7 +4668,7 @@ parse-glob@^3.0.4: is-extglob "^1.0.0" is-glob "^2.0.0" -parse-json@^2.1.0, parse-json@^2.2.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: @@ -4676,11 +4685,15 @@ parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" +parse5@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" + parse5@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" -parse5@^3.0.1, parse5@^3.0.2: +parse5@^3.0.1: version "3.0.3" resolved "https://registry.yarnpkg.com/parse5/-/parse5-3.0.3.tgz#042f792ffdd36851551cf4e9e066b3874ab45b5c" dependencies: @@ -4744,12 +4757,6 @@ path-type@^2.0.0: dependencies: pify "^2.0.0" -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - dependencies: - pify "^3.0.0" - pbkdf2@^3.0.3: version "3.0.14" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade" @@ -4792,7 +4799,7 @@ pkg-dir@^2.0.0: dependencies: find-up "^2.1.0" -pn@^1.0.0: +pn@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb" @@ -5081,8 +5088,8 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0 supports-color "^3.2.3" postcss@^6.0.0, postcss@^6.0.1, postcss@^6.0.13: - version "6.0.16" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.16.tgz#112e2fe2a6d2109be0957687243170ea5589e146" + version "6.0.17" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.17.tgz#e259a051ca513f81e9afd0c21f7f82eda50c65c5" dependencies: chalk "^2.3.0" source-map "^0.6.1" @@ -5205,10 +5212,6 @@ punycode@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" -pupa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/pupa/-/pupa-1.0.0.tgz#9a9568a5af7e657b8462a6e9d5328743560ceff6" - q@^1.1.2: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" @@ -5295,8 +5298,8 @@ raw-body@2.3.2: unpipe "1.0.0" rc@^1.0.1, rc@^1.1.6, rc@^1.1.7: - version "1.2.4" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.4.tgz#a0f606caae2a3b862bbd0ef85482c0125b315fa3" + version "1.2.5" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.5.tgz#275cd687f6e3b36cc756baa26dfee80a790301fd" dependencies: deep-extend "~0.4.0" ini "~1.3.0" @@ -5349,8 +5352,8 @@ react-error-overlay@^3.0.0: resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-3.0.0.tgz#c2bc8f4d91f1375b3dad6d75265d51cd5eeaf655" react-markdown@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-3.1.4.tgz#4f37aa5ac4f78f64f93b41b12ad0c08e92eb1680" + version "3.1.5" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-3.1.5.tgz#b1de8e3d421d31b8e1a679c66380fff20ee9a096" dependencies: prop-types "^15.6.0" remark-parse "^4.0.0" @@ -5359,8 +5362,8 @@ react-markdown@^3.1.4: xtend "^4.0.1" react-modal@^3.1.11: - version "3.1.11" - resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.1.11.tgz#95c8223fcee7013258ad2d149c38c9f870c89958" + version "3.1.12" + resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.1.12.tgz#e80ab4e553ce946a6c96faf85eb31e0f9bd07470" dependencies: exenv "^1.2.0" prop-types "^15.5.10" @@ -5417,9 +5420,9 @@ react-router@^4.2.0: prop-types "^15.5.4" warning "^3.0.0" -react-scripts-ts@2.12.0: - version "2.12.0" - resolved "https://registry.yarnpkg.com/react-scripts-ts/-/react-scripts-ts-2.12.0.tgz#3ee9e7f507dbfa8b07dcb8476f86af3131b38491" +react-scripts-ts@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/react-scripts-ts/-/react-scripts-ts-2.13.0.tgz#d5493035e6d521030f0d3e5e8e5b5872f7df1a28" dependencies: autoprefixer "7.1.6" case-sensitive-paths-webpack-plugin "2.1.1" @@ -5443,10 +5446,9 @@ react-scripts-ts@2.12.0: sw-precache-webpack-plugin "0.11.4" ts-jest "^20.0.7" ts-loader "^2.3.7" + tsconfig-paths-webpack-plugin "^2.0.0" tslint "^5.7.0" - tslint-loader "^3.5.3" tslint-react "^3.2.0" - typescript "^2.6.2" url-loader "0.6.2" webpack "3.8.1" webpack-dev-server "2.9.4" @@ -5472,13 +5474,6 @@ react@^16.2.0: object-assign "^4.1.1" prop-types "^15.6.0" -read-all-stream@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/read-all-stream/-/read-all-stream-3.1.0.tgz#35c3e177f2078ef789ee4bfafa4373074eaef4fa" - dependencies: - pinkie-promise "^2.0.0" - readable-stream "^2.0.0" - 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" @@ -5509,14 +5504,6 @@ read-pkg@^2.0.0: normalize-package-data "^2.3.2" path-type "^2.0.0" -read-pkg@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" - dependencies: - load-json-file "^4.0.0" - normalize-package-data "^2.3.2" - path-type "^3.0.0" - readable-stream@1.0: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" @@ -5526,7 +5513,7 @@ readable-stream@1.0: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.9, readable-stream@^2.3.3: +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.9, readable-stream@^2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: @@ -5610,8 +5597,8 @@ regexpu-core@^1.0.0: regjsparser "^0.1.4" registry-auth-token@^3.0.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.1.tgz#fb0d3289ee0d9ada2cbb52af5dfe66cb070d3006" + version "3.3.2" + resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20" dependencies: rc "^1.1.6" safe-buffer "^5.0.1" @@ -5694,7 +5681,7 @@ request-promise-core@1.1.1: dependencies: lodash "^4.13.1" -request-promise-native@^1.0.3: +request-promise-native@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.5.tgz#5281770f68e0c9719e5163fd3fab482215f4fda5" dependencies: @@ -5831,7 +5818,7 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2, rimraf@^2.2.8, rimraf@^2.4.4, rimraf@^2.5.1, rimraf@^2.6.1: +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: @@ -5857,10 +5844,6 @@ run-async@^2.2.0: dependencies: is-promise "^2.1.0" -run-node@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/run-node/-/run-node-0.2.0.tgz#b26e942e94205dedbe532cddf0fd1dbd56649af6" - 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" @@ -5893,7 +5876,7 @@ sane@~1.6.0: walker "~1.0.5" watch "~0.10.0" -sax@^1.2.1, sax@~1.2.1: +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" @@ -5908,10 +5891,10 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" selfsigned@^1.9.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.1.tgz#bf8cb7b83256c4551e31347c6311778db99eec52" + version "1.10.2" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.2.tgz#b4449580d99929b65b10a48389301a6592088758" dependencies: - node-forge "0.6.33" + node-forge "0.7.1" semver-diff@^2.0.0: version "2.1.0" @@ -5987,8 +5970,8 @@ setprototypeof@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.9" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.9.tgz#98f64880474b74f4a38b8da9d3c0f2d104633e7d" + version "2.4.10" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.10.tgz#b1fde5cd7d11a5626638a07c604ab909cfa31f9b" dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" @@ -6028,10 +6011,6 @@ slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" -slide@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" - sntp@1.x.x: version "1.0.9" resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" @@ -6087,12 +6066,12 @@ source-map-support@^0.4.15, source-map-support@^0.4.4: source-map "^0.5.6" source-map-support@^0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.2.tgz#1a6297fd5b2e762b39688c7fc91233b60984f0a5" + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76" dependencies: source-map "^0.6.0" -source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.6: +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, source-map@~0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -6244,8 +6223,8 @@ string_decoder@~0.10.x: resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" stringify-object@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.2.1.tgz#2720c2eff940854c819f6ee252aaeb581f30624d" + version "3.2.2" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.2.2.tgz#9853052e5a88fb605a44cd27445aa257ad7ffbcd" dependencies: get-own-enumerable-property-symbols "^2.0.1" is-obj "^1.0.1" @@ -6287,7 +6266,11 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" -strip-json-comments@~2.0.1: +strip-indent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68" + +strip-json-comments@^2.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" @@ -6347,8 +6330,8 @@ sw-precache-webpack-plugin@0.11.4: uglify-js "^3.0.13" sw-precache@^5.1.1: - version "5.2.0" - resolved "https://registry.yarnpkg.com/sw-precache/-/sw-precache-5.2.0.tgz#eb6225ce580ceaae148194578a0ad01ab7ea199c" + version "5.2.1" + resolved "https://registry.yarnpkg.com/sw-precache/-/sw-precache-5.2.1.tgz#06134f319eec68f3b9583ce9a7036b1c119f7179" dependencies: dom-urls "^1.1.0" es6-promise "^4.0.5" @@ -6359,7 +6342,7 @@ sw-precache@^5.1.1: mkdirp "^0.5.1" pretty-bytes "^4.0.2" sw-toolbox "^3.4.0" - update-notifier "^1.0.3" + update-notifier "^2.3.0" sw-toolbox@^3.4.0: version "3.6.0" @@ -6377,10 +6360,10 @@ symbol-observable@^0.2.2: resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-0.2.4.tgz#95a83db26186d6af7e7a18dbd9760a2f86d08f40" symbol-observable@^1.0.3: - version "1.1.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.1.0.tgz#5c68fd8d54115d9dfb72a84720549222e8db9b32" + version "1.2.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" -symbol-tree@^3.2.1: +symbol-tree@^3.2.1, symbol-tree@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" @@ -6409,6 +6392,12 @@ tar@^2.2.1: fstream "^1.0.2" inherits "2" +term-size@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" + dependencies: + execa "^0.7.0" + test-exclude@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26" @@ -6431,21 +6420,21 @@ through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" -thunky@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e" +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" -timed-out@^3.0.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-3.1.3.tgz#95860bfcc5c76c277f8f8326fd0f5b2e20eba217" +timed-out@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" timers-browserify@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.4.tgz#96ca53f4b794a5e7c0e1bd7cc88a372298fa01e6" + version "2.0.6" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.6.tgz#241e76927d9ca05f4d959819022f5b3664b64bae" dependencies: setimmediate "^1.0.4" @@ -6523,8 +6512,8 @@ ts-jest@^20.0.7: yargs "^8.0.1" ts-jest@^22.0.1: - version "22.0.1" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-22.0.1.tgz#48942936a466c2e76e259b02e2f1356f1839afc3" + version "22.0.3" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-22.0.3.tgz#4d30f42c1b26a78d438ad908f9b0bcb4b1e6e32b" dependencies: babel-core "^6.24.1" babel-plugin-istanbul "^4.1.4" @@ -6535,7 +6524,7 @@ ts-jest@^22.0.1: jest-config "^22.0.1" pkg-dir "^2.0.0" source-map-support "^0.5.0" - yargs "^10.0.3" + yargs "^11.0.0" ts-loader@^2.3.7: version "2.3.7" @@ -6546,23 +6535,28 @@ ts-loader@^2.3.7: loader-utils "^1.0.2" semver "^5.0.1" +tsconfig-paths-webpack-plugin@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-2.0.0.tgz#7652dc684bb3206c8e7e446831ca01cbf4d11772" + dependencies: + chalk "^2.3.0" + tsconfig-paths "^3.1.1" + +tsconfig-paths@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.1.1.tgz#368478faed54a247f2c8e59e44cb81418b2ae515" + dependencies: + deepmerge "^2.0.1" + strip-bom "^3.0.0" + strip-json-comments "^2.0.1" + tslib@^1.8.0, tslib@^1.8.1: version "1.9.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" tslint-config-prettier@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.6.0.tgz#fec1ee8fb07e8f033c63fed6b135af997f31962a" - -tslint-loader@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/tslint-loader/-/tslint-loader-3.5.3.tgz#343f74122d94f356b689457d3f59f64a69ab606f" - dependencies: - loader-utils "^1.0.2" - mkdirp "^0.5.1" - object-assign "^4.1.1" - rimraf "^2.4.4" - semver "^5.3.0" + version "1.7.0" + resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.7.0.tgz#1588f794c6863ba31420e8b1d14ae91326063815" tslint-react@^3.2.0, tslint-react@^3.4.0: version "3.4.0" @@ -6588,8 +6582,8 @@ tslint@^5.7.0, tslint@^5.9.1: tsutils "^2.12.1" tsutils@^2.12.1, tsutils@^2.13.1: - version "2.19.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.19.1.tgz#76d7ebdea9d7a7bf4a05f50ead3701b0168708d7" + version "2.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.21.0.tgz#43466a2283a0abce64e2209bc732ad72f8a04fab" dependencies: tslib "^1.8.1" @@ -6625,16 +6619,16 @@ typesafe-actions@^1.1.2: resolved "https://registry.yarnpkg.com/typesafe-actions/-/typesafe-actions-1.1.2.tgz#af88ede3ee254be425c3e0e02de11b182830ae48" typescript@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.6.2.tgz#3c5b6fd7f6de0914269027f03c0946758f7673a4" + version "2.7.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.1.tgz#bb3682c2c791ac90e7c6210b26478a8da085c359" ua-parser-js@^0.7.9: version "0.7.17" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" uglify-js@3.3.x, uglify-js@^3.0.13: - version "3.3.8" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.3.8.tgz#51e9a5db73afb53ac98603d08224edcd0be45fd8" + version "3.3.9" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.3.9.tgz#33869666c8ab7f7658ce3d22f0f1ced40097d33a" dependencies: commander "~2.13.0" source-map "~0.6.1" @@ -6664,6 +6658,10 @@ uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" +ultron@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" + underscore@~1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" @@ -6701,6 +6699,12 @@ uniqs@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" +unique-string@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a" + dependencies: + crypto-random-string "^1.0.0" + unist-util-is@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.1.tgz#0c312629e3f960c66e931e812d3d80e77010947b" @@ -6729,22 +6733,23 @@ unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" -unzip-response@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-1.0.2.tgz#b984f0877fc0a89c2c773cc1ef7b5b232b5b06fe" +unzip-response@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97" -update-notifier@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-1.0.3.tgz#8f92c515482bd6831b7c93013e70f87552c7cf5a" +update-notifier@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.3.0.tgz#4e8827a6bb915140ab093559d7014e3ebb837451" dependencies: - boxen "^0.6.0" - chalk "^1.0.0" - configstore "^2.0.0" + boxen "^1.2.1" + chalk "^2.0.1" + configstore "^3.0.0" + import-lazy "^2.1.0" + is-installed-globally "^0.1.0" is-npm "^1.0.0" - latest-version "^2.0.0" - lazy-req "^1.1.0" + latest-version "^3.0.0" semver-diff "^2.0.0" - xdg-basedir "^2.0.0" + xdg-basedir "^3.0.0" upper-case@^1.1.1: version "1.1.3" @@ -6811,7 +6816,7 @@ utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" -uuid@^2.0.1, uuid@^2.0.2: +uuid@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" @@ -6871,6 +6876,12 @@ vm-browserify@0.0.4: dependencies: indexof "0.0.1" +w3c-hr-time@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045" + dependencies: + browser-process-hrtime "^0.1.2" + walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" @@ -7003,7 +7014,7 @@ 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: +whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz#57c235bc8657e914d24e1a397d3c82daee0a6ba3" dependencies: @@ -7020,7 +7031,7 @@ whatwg-url@^4.3.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -whatwg-url@^6.3.0: +whatwg-url@^6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.0.tgz#08fdf2b9e872783a7a1f6216260a1d66cc722e08" dependencies: @@ -7052,11 +7063,11 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2" -widest-line@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-1.0.0.tgz#0c09c85c2a94683d0d7eaf8ee097d564bf0e105c" +widest-line@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.0.tgz#0142a4e8a243f8882c0233aa0e0281aa76152273" dependencies: - string-width "^1.0.1" + string-width "^2.1.1" window-size@0.1.0: version "0.1.0" @@ -7092,13 +7103,21 @@ wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" -write-file-atomic@^1.1.2: - version "1.3.4" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-1.3.4.tgz#f807a4f0b1d9e913ae7a48112e6cc3af1991b45f" +write-file-atomic@^2.0.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" - slide "^1.1.5" + signal-exit "^3.0.2" + +ws@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-4.0.0.tgz#bfe1da4c08eeb9780b986e0e4d10eccd7345999f" + dependencies: + async-limiter "~1.0.0" + safe-buffer "~5.1.0" + ultron "~1.1.0" x-is-function@^1.0.4: version "1.0.4" @@ -7108,11 +7127,9 @@ x-is-string@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/x-is-string/-/x-is-string-0.1.0.tgz#474b50865af3a49a9c4657f05acd145458f77d82" -xdg-basedir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-2.0.0.tgz#edbc903cc385fc04523d966a335504b5504d1bd2" - dependencies: - os-homedir "^1.0.0" +xdg-basedir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4" xml-char-classes@^1.0.0: version "1.0.0" @@ -7122,6 +7139,10 @@ 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" +xml-name-validator@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" + xtend@^4.0.0, xtend@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" @@ -7152,15 +7173,15 @@ yargs-parser@^7.0.0: dependencies: camelcase "^4.1.0" -yargs-parser@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950" +yargs-parser@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" dependencies: camelcase "^4.1.0" -yargs@^10.0.3: - version "10.1.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.1.tgz#5fe1ea306985a099b33492001fa19a1e61efe285" +yargs@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.0.0.tgz#c052931006c5eee74610e5fc0354bedfd08a201b" dependencies: cliui "^4.0.0" decamelize "^1.1.1" @@ -7173,7 +7194,7 @@ yargs@^10.0.3: string-width "^2.0.0" which-module "^2.0.0" y18n "^3.2.1" - yargs-parser "^8.1.0" + yargs-parser "^9.0.2" yargs@^6.6.0: version "6.6.0"