feat(cp-frontend): DeploymentGroup reprovision flow

This commit is contained in:
Sérgio Ramos 2017-06-27 17:45:38 +01:00 committed by Judit Greskovits
parent 3aa7ec54bb
commit 4e241191bf
16 changed files with 540 additions and 343 deletions

View File

@ -28,6 +28,7 @@
"jest-cli": "^20.0.4",
"joyent-manifest-editor": "^1.0.0",
"joyent-ui-toolkit": "^1.1.0",
"lodash.get": "^4.4.2",
"lodash.isstring": "^4.0.1",
"normalized-styled-components": "^1.0.8",
"param-case": "^2.1.1",

View File

@ -45,8 +45,8 @@ const ButtonsRow = Row.extend`
margin-bottom: ${remcalc(60)};
`;
const Editor = ManifestEditor => ({ input }) =>
<ManifestEditor mode="yaml" {...input} />;
const Editor = ManifestEditor => ({ input, defaultValue }) =>
<ManifestEditor mode="yaml" {...input} value={input.value || defaultValue} />;
export const Name = ({ handleSubmit, onCancel, dirty }) =>
<form onSubmit={handleSubmit}>
@ -64,12 +64,23 @@ export const Name = ({ handleSubmit, onCancel, dirty }) =>
</ButtonsRow>
</form>;
export const Manifest = ({ handleSubmit, onCancel, dirty, mode, loading }) =>
export const Manifest = ({
handleSubmit,
onCancel,
dirty,
defaultValue,
mode,
loading
}) =>
<form onSubmit={handleSubmit}>
<Bundle load={() => import('joyent-manifest-editor')}>
{ManifestEditor =>
ManifestEditor
? <Field name="manifest" component={Editor(ManifestEditor)} />
? <Field
name="manifest"
defaultValue={defaultValue}
component={Editor(ManifestEditor)}
/>
: <Dots2 />}
</Bundle>
<ButtonsRow>
@ -82,7 +93,7 @@ export const Manifest = ({ handleSubmit, onCancel, dirty, mode, loading }) =>
export const Review = ({ handleSubmit, onCancel, dirty, ...state }) => {
const serviceList = forceArray(state.services).map(({ name, image }) =>
<ServiceCard>
<ServiceCard key={name}>
<Dl>
<dt><ServiceName>{name}</ServiceName></dt>
<dt><ImageTitle>Image:</ImageTitle> <Image>{image}</Image></dt>

View File

@ -18,11 +18,14 @@ import {
const STATUSES = [
'CREATED',
'RESTARTING',
'PROVISIONING',
'READY',
'RUNNING',
'PAUSED',
'EXITED',
'DELETED',
'STOPPED',
'STOPPING',
'FAILED'
];
@ -46,6 +49,14 @@ const Dot = styled.div`
background-color: yellow;
`};
${is('provisioning')`
background-color: yellow;
`};
${is('ready')`
background-color: yellow;
`};
${is('running')`
background-color: ${props => props.theme.green};
`};
@ -62,6 +73,10 @@ const Dot = styled.div`
background-color: ${props => props.theme.secondaryActive};
`};
${is('stopping')`
background-color: orange;
`};
${is('stopped')`
background-color: ${props => props.theme.red};
`};

View File

@ -1,270 +1,18 @@
import React, { Component } from 'react';
import { reduxForm } from 'redux-form';
import { compose, graphql } from 'react-apollo';
import { Redirect } from 'react-router-dom';
import intercept from 'apr-intercept';
import paramCase from 'param-case';
import React from 'react';
import DeploymentGroupBySlug from '@graphql/DeploymentGroupBySlug.gql';
import DeploymentGroupCreateMutation from '@graphql/DeploymentGroupCreate.gql';
import DeploymentGroupProvisionMutation from '@graphql/DeploymentGroupProvision.gql';
import DeploymentGroupConfigQuery from '@graphql/DeploymentGroupConfig.gql';
import { client } from '@state/store';
import DeploymentGroupEditOrCreate from './edit-or-create';
import { LayoutContainer } from '@components/layout';
import { Name, Manifest, Review } from '@components/deployment-groups/create';
import { DeploymentGroupsLoading } from '@components/deployment-groups';
import { H2 } from 'joyent-ui-toolkit';
const validateName = async ({ name = '' }) => {
const { data } = await client.query({
fetchPolicy: 'network-only',
query: DeploymentGroupBySlug,
variables: {
slug: paramCase(name.trim())
}
});
if (data.deploymentGroups.length) {
// eslint-disable-next-line no-throw-literal
throw { name: `"${name}" already exists!` };
}
};
const NameForm = reduxForm({
form: 'create-deployment-group',
destroyOnUnmount: true,
forceUnregisterOnUnmount: true,
asyncValidate: validateName
})(Name);
const ManifestForm = reduxForm({
form: 'create-deployment-group',
destroyOnUnmount: true,
forceUnregisterOnUnmount: true
})(Manifest);
const ReviewForm = reduxForm({
form: 'create-deployment-group',
destroyOnUnmount: true,
forceUnregisterOnUnmount: true
})(Review);
// TODO: move state to redux. why: because in redux we can cache transactional
// state between refreshes
class DeploymentGroupCreate extends Component {
state = {
name: '',
manifest: '',
services: [],
loading: false,
error: null
};
constructor() {
super();
this.stages = {
name: this.renderNameForm.bind(this),
manifest: this.renderManifestEditor.bind(this),
review: this.renderReview.bind(this)
};
this.handleNameSubmit = this.handleNameSubmit.bind(this);
this.handleManifestSubmit = this.handleManifestSubmit.bind(this);
this.handleReviewSubmit = this.handleReviewSubmit.bind(this);
this.handleCancel = this.handleCancel.bind(this);
}
handleNameSubmit({ name = '' }) {
this.setState({ name }, () =>
this.redirect({ stage: 'manifest', prog: true })
);
}
handleManifestSubmit({ manifest = '' }) {
const { config } = this.props;
const { name } = this.state;
const getConfig = async () => {
const [err, conf] = await intercept(
config({
deploymentGroupName: name,
type: 'COMPOSE',
format: 'YAML',
raw: manifest
})
);
if (err) {
return this.setState({
error: err.message
});
}
const { data } = conf;
const { config: services } = data;
this.setState({ loading: false, services }, () => {
this.redirect({ stage: 'review', prog: true });
});
};
this.setState({ manifest, loading: true }, getConfig);
}
handleReviewSubmit() {
const { history } = this.props;
const submit = async () => {
const { id, slug } = await this.createDeploymentGroup();
if (!id) {
return;
}
const manifest = await this.provision(id);
if (!manifest) {
return;
}
history.push(`/deployment-groups/${slug}`);
};
this.setState({ loading: true }, submit);
}
createDeploymentGroup = async () => {
const { name } = this.state;
const { createDeploymentGroup } = this.props;
const [err, res] = await intercept(createDeploymentGroup({ name }));
if (err) {
this.setState({
error: err.message
});
}
return err ? null : res.data.createDeploymentGroup;
};
provision = async deploymentGroupId => {
const { manifest } = this.state;
const { provisionManifest } = this.props;
const [err] = await intercept(
provisionManifest({
deploymentGroupId,
type: 'COMPOSE',
format: 'YAML',
raw: manifest
})
);
if (err) {
this.setState({
error: err.message
});
}
return err ? null : true;
};
renderNameForm() {
return (
<NameForm onSubmit={this.handleNameSubmit} onCancel={this.handleCancel} />
);
}
renderManifestEditor() {
return (
<ManifestForm
onSubmit={this.handleManifestSubmit}
onCancel={this.handleCancel}
loading={this.state.loading}
/>
);
}
renderReview() {
return (
<ReviewForm
onSubmit={this.handleReviewSubmit}
onCancel={this.handleCancel}
{...this.state}
/>
);
}
handleCancel() {
const { history } = this.props;
history.push('/');
}
redirect({ stage = 'name', prog = false }) {
const { match, history } = this.props;
const pathname = match.url.replace(/~create(.*)/, '~create');
const to = `${pathname}/${stage}`;
if (!prog) {
return <Redirect to={to} />;
}
history.push(to);
}
render() {
const { err } = this.state;
const { match } = this.props;
const stage = match.params.stage;
if (!stage) {
return this.redirect({ stage: 'name' });
}
if (!this.stages[stage]) {
return this.redirect({ stage: 'name' });
}
if (stage !== 'name' && !this.state.name) {
return this.redirect({ stage: 'name' });
}
if (stage === 'review' && !this.state.manifest) {
return this.redirect({ stage: 'manifest' });
}
const view = this.stages[stage]();
const error = err ? <span>{err}</span> : null;
return (
<LayoutContainer>
<H2>Creating deployment group</H2>
{error}
{view}
</LayoutContainer>
);
}
}
export default compose(
graphql(DeploymentGroupCreateMutation, {
props: ({ mutate }) => ({
createDeploymentGroup: variables => mutate({ variables })
})
}),
graphql(DeploymentGroupProvisionMutation, {
props: ({ mutate }) => ({
provisionManifest: variables => mutate({ variables })
})
}),
graphql(DeploymentGroupConfigQuery, {
props: ({ mutate }) => ({
config: variables => mutate({ variables })
})
})
)(DeploymentGroupCreate);
export default ({ loading, error, manifest = '', deploymentGroup = null }) =>
<LayoutContainer>
<H2>Creating deployment group</H2>
{loading && <DeploymentGroupsLoading />}
{error && <span>{error.toString()}</span>}
<DeploymentGroupEditOrCreate
create
manifest={manifest}
deploymentGroup={deploymentGroup}
/>
</LayoutContainer>;

View File

@ -0,0 +1,298 @@
import React, { Component } from 'react';
import { reduxForm } from 'redux-form';
import { compose, graphql } from 'react-apollo';
import { withRouter } from 'react-router';
import { Redirect } from 'react-router-dom';
import intercept from 'apr-intercept';
import paramCase from 'param-case';
import DeploymentGroupBySlugQuery from '@graphql/DeploymentGroupBySlug.gql';
import DeploymentGroupCreateMutation from '@graphql/DeploymentGroupCreate.gql';
import DeploymentGroupProvisionMutation from '@graphql/DeploymentGroupProvision.gql';
import DeploymentGroupConfigQuery from '@graphql/DeploymentGroupConfig.gql';
import { client } from '@state/store';
import { Name, Manifest, Review } from '@components/deployment-groups/create';
// TODO: move state to redux. why: because in redux we can cache transactional
// state between refreshes
class DeploymentGroupEditOrCreate extends Component {
constructor(props) {
super(props);
const { create, edit } = props;
const type = create ? 'create' : 'edit';
const NameForm =
create &&
reduxForm({
form: `${type}-deployment-group`,
destroyOnUnmount: true,
forceUnregisterOnUnmount: true,
asyncValidate: async ({ name = '' }) => {
const { data } = await client.query({
fetchPolicy: 'network-only',
query: DeploymentGroupBySlugQuery,
variables: {
slug: paramCase(name.trim())
}
});
if (data.deploymentGroups.length) {
// eslint-disable-next-line no-throw-literal
throw { name: `"${name}" already exists!` };
}
}
})(Name);
const ManifestForm = reduxForm({
form: `${type}-deployment-group`,
destroyOnUnmount: true,
forceUnregisterOnUnmount: true
})(Manifest);
const ReviewForm = reduxForm({
form: `${type}-deployment-group`,
destroyOnUnmount: true,
forceUnregisterOnUnmount: true
})(Review);
this.state = {
defaultStage: create ? 'name' : 'edit',
manifestStage: create ? 'manifest' : 'edit',
name: '',
manifest: '',
services: [],
loading: false,
error: null,
NameForm,
ManifestForm,
ReviewForm
};
this.stages = {
name: create && this.renderNameForm.bind(this),
[create ? 'manifest' : 'edit']: this.renderManifestEditor.bind(this),
review: this.renderReview.bind(this)
};
this.handleNameSubmit =
type === 'create' && this.handleNameSubmit.bind(this);
this.handleManifestSubmit = this.handleManifestSubmit.bind(this);
this.handleReviewSubmit = this.handleReviewSubmit.bind(this);
this.handleCancel = this.handleCancel.bind(this);
if (edit) {
setTimeout(this.getDeploymentGroup, 16);
}
}
createDeploymentGroup = async () => {
const { createDeploymentGroup, deploymentGroup, edit } = this.props;
if (edit && (!deploymentGroup || !deploymentGroup.id)) {
this.setState({
error: 'Unexpected Error: Inexistent DeploymentGroup!'
});
return {};
}
if (deploymentGroup && deploymentGroup.id) {
return deploymentGroup;
}
const { name } = this.state;
const [err, res] = await intercept(createDeploymentGroup({ name }));
if (err) {
this.setState({
error: err.message
});
}
return err ? {} : res.data.createDeploymentGroup;
};
provision = async deploymentGroupId => {
const { manifest } = this.state;
const { provisionManifest } = this.props;
const [err] = await intercept(
provisionManifest({
deploymentGroupId,
type: 'COMPOSE',
format: 'YAML',
raw: manifest
})
);
if (err) {
this.setState({
error: err.message
});
}
return err ? null : true;
};
handleNameSubmit({ name = '' }) {
this.setState({ name }, () =>
this.redirect({ stage: 'manifest', prog: true })
);
}
handleManifestSubmit({ manifest = '' }) {
const { name } = this.state;
const getConfig = async () => {
const [err, conf] = await intercept(
client.query({
query: DeploymentGroupConfigQuery,
variables: {
deploymentGroupName: name,
type: 'COMPOSE',
format: 'YAML',
raw: manifest
}
})
);
if (err) {
return this.setState({
error: err.message
});
}
const { data } = conf;
const { config: services } = data;
this.setState({ loading: false, services }, () => {
this.redirect({ stage: 'review', prog: true });
});
};
this.setState({ manifest, loading: true }, getConfig);
}
handleReviewSubmit() {
const { history } = this.props;
const submit = async () => {
const { id, slug } = await this.createDeploymentGroup();
if (!id) {
return;
}
const manifest = await this.provision(id);
if (!manifest) {
return;
}
history.push(`/deployment-groups/${slug}`);
};
this.setState({ loading: true }, submit);
}
handleCancel() {
const { history, create, deploymentGroup } = this.props;
history.push(create ? '/' : `/deployment-groups/${deploymentGroup.slug}`);
}
redirect({ stage = 'name', prog = false }) {
const { match, history, create } = this.props;
const regex = create ? /\/~create(.*)/ : /\/manifest(.*)/;
const to = match.url.replace(
regex,
create ? `/~create/${stage}` : `/manifest/${stage}`
);
if (!prog) {
return <Redirect to={to} />;
}
history.push(to);
}
renderNameForm() {
const { NameForm } = this.state;
return (
<NameForm onSubmit={this.handleNameSubmit} onCancel={this.handleCancel} />
);
}
renderManifestEditor() {
const { ManifestForm } = this.state;
return (
<ManifestForm
defaultValue={this.props.manifest}
onSubmit={this.handleManifestSubmit}
onCancel={this.handleCancel}
loading={this.state.loading}
/>
);
}
renderReview() {
const { ReviewForm } = this.state;
return (
<ReviewForm
onSubmit={this.handleReviewSubmit}
onCancel={this.handleCancel}
{...this.state}
/>
);
}
render() {
const { error, defaultStage, manifestStage } = this.state;
if (error) {
return <span>{error}</span>;
}
const { match, create } = this.props;
const stage = match.params.stage;
if (!stage) {
return this.redirect({ stage: defaultStage });
}
if (!this.stages[stage]) {
return this.redirect({ stage: defaultStage });
}
if (create && stage !== 'name' && !this.state.name) {
return this.redirect({ stage: defaultStage });
}
if (stage === 'review' && !this.state.manifest) {
return this.redirect({ stage: manifestStage });
}
return this.stages[stage]();
}
}
export default compose(
graphql(DeploymentGroupCreateMutation, {
props: ({ mutate }) => ({
createDeploymentGroup: variables => mutate({ variables })
})
}),
graphql(DeploymentGroupProvisionMutation, {
props: ({ mutate }) => ({
provisionManifest: variables => mutate({ variables })
})
})
)(withRouter(DeploymentGroupEditOrCreate));

View File

@ -1,12 +1,11 @@
import React, { Component } from 'react';
import { compose, graphql } from 'react-apollo';
import { graphql } from 'react-apollo';
import intercept from 'apr-intercept';
import DeploymentGroupImportMutation from '@graphql/DeploymentGroupImport.gql';
import { LayoutContainer } from '@components/layout';
import { DeploymentGroupsLoading } from '@components/deployment-groups';
import { Dots2 } from 'styled-text-spinners';
import { H2 } from 'joyent-ui-toolkit';
class DeploymentGroupImport extends Component {

View File

@ -1,3 +1,4 @@
export { default as DeploymentGroupList } from './list';
export { default as DeploymentGroupCreate } from './create';
export { default as DeploymentGroupImport } from './import';
export { default as DeploymentGroupManifest } from './manifest';

View File

@ -8,11 +8,11 @@ import forceArray from 'force-array';
import remcalc from 'remcalc';
import { LayoutContainer } from '@components/layout';
import { Loader, ErrorMessage } from '@components/messaging';
import { ErrorMessage } from '@components/messaging';
import { DeploymentGroupsLoading } from '@components/deployment-groups';
import DeploymentGroupsQuery from '@graphql/DeploymentGroups.gql';
import DeploymentGroupsImportableQuery from '@graphql/DeploymentGroupsImportable.gql';
import { Button, H2, H3, Small } from 'joyent-ui-toolkit';
import { H2, H3, Small } from 'joyent-ui-toolkit';
const Title = H2.extend`
margin-top: ${remcalc(2)};

View File

@ -0,0 +1,67 @@
import React from 'react';
import { compose, graphql } from 'react-apollo';
import get from 'lodash.get';
import ManifestQuery from '@graphql/Manifest.gql';
import DeploymentGroupBySlugQuery from '@graphql/DeploymentGroupBySlug.gql';
import DeploymentGroupEditOrCreate from './edit-or-create';
import { LayoutContainer } from '@components/layout';
import { DeploymentGroupsLoading } from '@components/deployment-groups';
import { H2 } from 'joyent-ui-toolkit';
const Manifest = ({
loading,
error,
manifest = '',
deploymentGroup = null
}) => {
const _loading = !loading ? null : <DeploymentGroupsLoading />;
const _error = !error ? null : <span>{error.toString()}</span>;
const _view = (loading || !deploymentGroup)
? null
: <DeploymentGroupEditOrCreate
edit
manifest={manifest}
deploymentGroup={deploymentGroup}
/>;
return (
<LayoutContainer>
<H2>Edit Manifest</H2>
{_error}
{_loading}
{_view}
</LayoutContainer>
);
};
export default compose(
graphql(ManifestQuery, {
options: props => ({
variables: {
deploymentGroupSlug: props.match.params.deploymentGroup
}
}),
props: ({ data: { deploymentGroup, loading, error } }) => ({
manifest: get(deploymentGroup, 'version.manifest.raw', ''),
loading,
error
})
}),
graphql(DeploymentGroupBySlugQuery, {
options: props => ({
variables: {
slug: props.match.params.deploymentGroup
}
}),
props: ({ data: { deploymentGroups, loading, error } }) => ({
deploymentGroup: deploymentGroups && deploymentGroups.length
? deploymentGroups[0]
: null,
loading,
error
})
})
)(Manifest);

View File

@ -6,7 +6,7 @@ import { Row } from 'react-styled-flexboxgrid';
import remcalc from 'remcalc';
import { LayoutContainer } from '@components/layout';
import { Loader, ErrorMessage } from '@components/messaging';
import { ErrorMessage } from '@components/messaging';
import { InstanceListItem, EmptyInstances } from '@components/instances';
import { DeploymentGroupsLoading } from '@components/deployment-groups';
import { H2 } from 'joyent-ui-toolkit';
@ -40,6 +40,8 @@ class InstanceList extends Component {
return (
<LayoutContainer>
<Title>Instances</Title>
{_error}
{_loading}
{instanceList}
</LayoutContainer>
);

View File

@ -1,5 +1,7 @@
#import "./DeploymentGroupInfo.gql"
query DeploymentGroupBySlug($slug: String!) {
deploymentGroups(slug: $slug) {
id
...DeploymentGroupInfo
}
}

View File

@ -2,4 +2,5 @@ fragment DeploymentGroupInfo on DeploymentGroup {
id
name
slug
imported
}

View File

@ -1,6 +1,5 @@
mutation provisionManifest($deploymentGroupId: ID!, $type: ManifestType!, $format: ManifestFormat!, $raw: String!) {
provisionManifest(deploymentGroupId: $deploymentGroupId, type: $type, format: $format, raw: $raw) {
manifestId
scale {
serviceName
replicas

View File

@ -0,0 +1,12 @@
query ManifestById($deploymentGroupSlug: String!) {
deploymentGroup(slug: $deploymentGroupSlug) {
version {
manifest {
id
type
format
raw
}
}
}
}

View File

@ -9,7 +9,8 @@ import { InstanceList } from '@containers/instances';
import {
DeploymentGroupList,
DeploymentGroupCreate,
DeploymentGroupImport
DeploymentGroupImport,
DeploymentGroupManifest
} from '@containers/deployment-groups';
import {
@ -66,6 +67,11 @@ const Router = (
<Route path="/" exact component={rootRedirect} />
<Route path="/deployment-groups" exact component={DeploymentGroupList} />
<Route
path="/deployment-groups/:deploymentGroup/services-list"
component={ServicesMenu}
/>
<Switch>
<Route
path="/deployment-groups/~create/:stage?"
@ -87,22 +93,36 @@ const Router = (
exact
component={deploymentGroupRedirect}
/>
<Route
path="/deployment-groups/:deploymentGroup/instances"
exact
component={InstanceList}
/>
<Route
path="/deployment-groups/:deploymentGroup/manifest/:stage?"
exact
component={DeploymentGroupManifest}
/>
<Route
path="/deployment-groups/:deploymentGroup/services-list"
component={ServiceList}
/>
<Route
path="/deployment-groups/:deploymentGroup/services-topology"
component={ServicesTopology}
/>
<Route
path="/deployment-groups/:deploymentGroup/services/:service/instances"
exact
component={InstanceList}
/>
</Switch>
<Route
path="/deployment-groups/:deploymentGroup/instances"
exact
component={InstanceList}
/>
<Route
path="/deployment-groups/:deploymentGroup/services-list"
component={ServicesMenu}
/>
<Route
path="/deployment-groups/:deploymentGroup/services-list"
component={ServiceList}
/>
<Route
path="/deployment-groups/:deploymentGroup/services-list/:service/scale"
exact
@ -118,10 +138,7 @@ const Router = (
path="/deployment-groups/:deploymentGroup/services-topology"
component={ServicesMenu}
/>
<Route
path="/deployment-groups/:deploymentGroup/services-topology"
component={ServicesTopology}
/>
<Route
path="/deployment-groups/:deploymentGroup/services-topology/:service/scale"
exact
@ -138,11 +155,6 @@ const Router = (
exact
component={serviceRedirect}
/>
<Route
path="/deployment-groups/:deploymentGroup/services/:service/instances"
exact
component={InstanceList}
/>
</Container>
</BrowserRouter>

111
yarn.lock
View File

@ -186,6 +186,10 @@ ansi-regex@^2.0.0, ansi-regex@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
ansi-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
ansi-styles@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de"
@ -216,8 +220,8 @@ anymatch@^1.3.0:
micromatch "^2.1.5"
apollo-client@^1.4.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-1.5.0.tgz#c0ce752f2fe1945dca2eaaa62c7907fdde384563"
version "1.6.0"
resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-1.6.0.tgz#d2b831fd8e6e6c045ce91dc8b86a920d44fd77a9"
dependencies:
graphql "^0.10.0"
graphql-anywhere "^3.0.1"
@ -493,6 +497,10 @@ ast-types@0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.0.tgz#c8721c8747ae4d5b29b929e99c5317b4e8745623"
ast-types@0.9.6:
version "0.9.6"
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.9.6.tgz#102c9e9e9005d3e7e3829bf0c4fa24ee862ee9b9"
async-each@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
@ -1757,12 +1765,12 @@ camelcase@^4.0.0, camelcase@^4.1.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
caniuse-db@^1.0.30000187, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
version "1.0.30000694"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000694.tgz#02009f4f82d2f0126e4c691b7cd5adb351935c01"
version "1.0.30000696"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000696.tgz#e71f5c61e1f96c7a3af4e791ac5db55e11737604"
caniuse-lite@^1.0.30000684:
version "1.0.30000694"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000694.tgz#1492dab7c10c608c9d37a723e6e3e7873e0ce94f"
version "1.0.30000696"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000696.tgz#30f2695d2a01a0dfd779a26ab83f4d134b3da5cc"
capture-stack-trace@^1.0.0:
version "1.0.0"
@ -3358,8 +3366,8 @@ eslint-plugin-import@^2.3.0:
read-pkg-up "^2.0.0"
eslint-plugin-jsx-a11y@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-5.0.3.tgz#4a939f76ec125010528823331bf948cc573380b6"
version "5.1.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-5.1.0.tgz#4a829634344e7a90391a9fb0fbd19810737d79c5"
dependencies:
aria-query "^0.5.0"
array-includes "^3.0.3"
@ -3489,7 +3497,7 @@ esprima@^2.6.0, esprima@~2.7.1:
version "2.7.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581"
esprima@^3.1.1:
esprima@^3.1.1, esprima@~3.1.0:
version "3.1.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
@ -4374,8 +4382,8 @@ hash-base@^2.0.0:
inherits "^2.0.1"
hash.js@^1.0.0, hash.js@^1.0.3:
version "1.1.1"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.1.tgz#5cb2e796499224e69fd0b00ed01d2d4a16e7a323"
version "1.1.2"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.2.tgz#bf5c887825cfe40b9efde7bf11bd2db26e6bf01b"
dependencies:
inherits "^2.0.3"
minimalistic-assert "^1.0.0"
@ -4439,12 +4447,12 @@ home-or-tmp@^2.0.0:
os-tmpdir "^1.0.1"
hosted-git-info@^2.1.4:
version "2.4.2"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.4.2.tgz#0076b9f46a270506ddbaaea56496897460612a67"
version "2.5.0"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
html-tags@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-1.2.0.tgz#c78de65b5663aa597989dd2b7ab49200d7e4db98"
html-tags@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b"
http-errors@^1.3.0, http-errors@~1.6.1:
version "1.6.1"
@ -4999,7 +5007,7 @@ istanbul-lib-hook@^1.0.7:
dependencies:
append-transform "^0.4.0"
istanbul-lib-instrument@^1.7.2:
istanbul-lib-instrument@^1.7.2, istanbul-lib-instrument@^1.7.3:
version "1.7.3"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.3.tgz#925b239163eabdd68cc4048f52c2fa4f899ecfa7"
dependencies:
@ -5712,7 +5720,7 @@ lodash.flattendeep@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
lodash.get@^4.1.2:
lodash.get@^4.1.2, lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
@ -6344,8 +6352,8 @@ nopt@~1.0.10:
abbrev "1"
normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5:
version "2.3.8"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.8.tgz#d819eda2a9dedbd1ffa563ea4071d936782295bb"
version "2.4.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
dependencies:
hosted-git-info "^2.1.4"
is-builtin-module "^1.0.0"
@ -6387,8 +6395,8 @@ npm-run-path@^2.0.0:
path-key "^2.0.0"
npmlog@^4.0.2, npmlog@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.0.tgz#dc59bee85f64f00ed424efb2af0783df25d1c0b5"
version "4.1.2"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
dependencies:
are-we-there-yet "~1.1.2"
console-control-strings "~1.1.0"
@ -6411,8 +6419,8 @@ number-is-nan@^1.0.0:
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
nyc@^11.0.2:
version "11.0.2"
resolved "https://registry.yarnpkg.com/nyc/-/nyc-11.0.2.tgz#9e592a697186028253b668516c38f079c39c08f3"
version "11.0.3"
resolved "https://registry.yarnpkg.com/nyc/-/nyc-11.0.3.tgz#0c28bc669a851621709bf7a08503034bee3812b6"
dependencies:
archy "^1.0.0"
arrify "^1.0.1"
@ -6426,7 +6434,7 @@ nyc@^11.0.2:
glob "^7.0.6"
istanbul-lib-coverage "^1.1.1"
istanbul-lib-hook "^1.0.7"
istanbul-lib-instrument "^1.7.2"
istanbul-lib-instrument "^1.7.3"
istanbul-lib-report "^1.1.1"
istanbul-lib-source-maps "^1.2.1"
istanbul-reports "^1.1.1"
@ -6437,7 +6445,7 @@ nyc@^11.0.2:
resolve-from "^2.0.0"
rimraf "^2.5.4"
signal-exit "^3.0.1"
spawn-wrap "^1.3.6"
spawn-wrap "^1.3.7"
test-exclude "^4.1.1"
yargs "^8.0.1"
yargs-parser "^5.0.0"
@ -7399,8 +7407,8 @@ read-installed@~4.0.3:
graceful-fs "^4.1.2"
read-package-json@^2.0.0:
version "2.0.5"
resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.0.5.tgz#f93a64e641529df68a08c64de46389e8a3f88845"
version "2.0.9"
resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.0.9.tgz#fd3c21750d760a96c02abda60081e62133213a26"
dependencies:
glob "^7.1.1"
json-parse-helpfulerror "^1.0.2"
@ -7517,7 +7525,7 @@ readline2@^1.0.1:
is-fullwidth-code-point "^1.0.0"
mute-stream "0.0.5"
recast@0.11.12, recast@^0.11.5:
recast@0.11.12:
version "0.11.12"
resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.12.tgz#a79e4d3f82d5d72a82ee177aeaa791e793bbe5d6"
dependencies:
@ -7526,6 +7534,15 @@ recast@0.11.12, recast@^0.11.5:
private "~0.1.5"
source-map "~0.5.0"
recast@^0.11.5:
version "0.11.23"
resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3"
dependencies:
ast-types "0.9.6"
esprima "~3.1.0"
private "~0.1.5"
source-map "~0.5.0"
rechoir@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
@ -8257,6 +8274,12 @@ sort-keys@^1.1.1, sort-keys@^1.1.2:
dependencies:
is-plain-obj "^1.0.0"
sort-keys@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"
dependencies:
is-plain-obj "^1.0.0"
source-list-map@^1.1.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.2.tgz#9889019d1024cce55cdc069498337ef6186a11a1"
@ -8304,7 +8327,7 @@ spache@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/spache/-/spache-1.1.0.tgz#8c68ba807630f0199429c2035c82ed96f5438cd5"
spawn-wrap@^1.3.6:
spawn-wrap@^1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/spawn-wrap/-/spawn-wrap-1.3.7.tgz#beb8bf4426d64b2b06871e0d7dee2643f1f8d1bc"
dependencies:
@ -8492,11 +8515,11 @@ string-width@^1.0.0, string-width@^1.0.1, string-width@^1.0.2:
strip-ansi "^3.0.0"
string-width@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e"
version "2.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.0.tgz#030664561fc146c9423ec7d978fe2457437fe6d0"
dependencies:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^3.0.0"
strip-ansi "^4.0.0"
string_decoder@^0.10.25, string_decoder@~0.10.x:
version "0.10.31"
@ -8533,6 +8556,12 @@ strip-ansi@^0.3.0:
dependencies:
ansi-regex "^0.2.1"
strip-ansi@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
dependencies:
ansi-regex "^3.0.0"
strip-ansi@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991"
@ -8662,8 +8691,8 @@ stylelint-selector-no-utility@^1.5.0:
stylelint "^7.0.0"
stylelint@^7.0.0, stylelint@^7.0.3, stylelint@^7.11.1:
version "7.11.1"
resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-7.11.1.tgz#c816c658baf7d9e5d167d82273fead37c97ae49d"
version "7.12.0"
resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-7.12.0.tgz#bf302c265d7c2d6fe79b154a9fd873a80f8b4aa4"
dependencies:
autoprefixer "^6.0.0"
balanced-match "^0.4.0"
@ -8677,7 +8706,7 @@ stylelint@^7.0.0, stylelint@^7.0.3, stylelint@^7.11.1:
get-stdin "^5.0.0"
globby "^6.0.0"
globjoin "^0.1.4"
html-tags "^1.1.1"
html-tags "^2.0.0"
ignore "^3.2.0"
imurmurhash "^0.1.4"
known-css-properties "^0.2.0"
@ -9677,7 +9706,7 @@ write-file-stdout@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/write-file-stdout/-/write-file-stdout-0.0.2.tgz#c252d7c7c5b1b402897630e3453c7bfe690d9ca1"
write-json-file@^2.0.0, write-json-file@^2.1.0:
write-json-file@^2.0.0, write-json-file@^2.1.0, write-json-file@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-2.2.0.tgz#51862506bbb3b619eefab7859f1fd6c6d0530876"
dependencies:
@ -9696,11 +9725,11 @@ write-pkg@^2.0.0:
write-json-file "^2.0.0"
write-pkg@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-3.0.1.tgz#f95245805be6f6a4eb1d6c31c43b57226815e6e3"
version "3.1.0"
resolved "https://registry.yarnpkg.com/write-pkg/-/write-pkg-3.1.0.tgz#030a9994cc9993d25b4e75a9f1a1923607291ce9"
dependencies:
sort-keys "^1.1.2"
write-json-file "^2.0.0"
sort-keys "^2.0.0"
write-json-file "^2.2.0"
write@^0.2.1:
version "0.2.1"