Basic functionality in place, need improved errors and logged in redirect

This commit is contained in:
Tom Moor 2018-11-03 20:27:57 -07:00
parent 07e61bd347
commit 21b1c0747c
24 changed files with 512 additions and 130 deletions

View File

@ -12,6 +12,7 @@ REDIS_URL=redis://redis:6379
URL=http://localhost:3000
DEPLOYMENT=self
ENABLE_UPDATES=true
SUBDOMAINS_ENABLED=false
DEBUG=sql,cache,presenters,events
# Third party signin credentials (at least one is required)

View File

@ -36,7 +36,7 @@ class RevisionMenu extends React.Component<Props> {
render() {
const { label, className, onOpen, onClose } = this.props;
const url = `${process.env.URL}${documentHistoryUrl(
const url = `${window.location.origin}${documentHistoryUrl(
this.props.document,
this.props.revision.id
)}`;

View File

@ -25,12 +25,14 @@ class Details extends React.Component<Props> {
form: ?HTMLFormElement;
@observable name: string;
@observable subdomain: string;
@observable subdomain: ?string;
@observable avatarUrl: ?string;
componentDidMount() {
if (this.props.auth.team) {
this.name = this.props.auth.team.name;
const { team } = this.props.auth;
if (team) {
this.name = team.name;
this.subdomain = team.subdomain;
}
}
@ -41,11 +43,16 @@ class Details extends React.Component<Props> {
handleSubmit = async (ev: SyntheticEvent<*>) => {
ev.preventDefault();
await this.props.auth.updateTeam({
name: this.name,
avatarUrl: this.avatarUrl,
});
this.props.ui.showToast('Settings saved', 'success');
try {
await this.props.auth.updateTeam({
name: this.name,
avatarUrl: this.avatarUrl,
subdomain: this.subdomain,
});
this.props.ui.showToast('Settings saved', 'success');
} catch (err) {
this.props.ui.showToast('Could not save');
}
};
handleNameChange = (ev: SyntheticInputEvent<*>) => {
@ -115,22 +122,24 @@ class Details extends React.Component<Props> {
required
short
/>
<Input
label="Subdomain"
name="subdomain"
value={this.subdomain}
onChange={this.handleSubdomainChange}
placeholder="Optional"
short
/>
{this.subdomain && (
<HelpText small>
You will be able to access your wiki at{' '}
<strong>{this.subdomain}.getoutline.com</strong>
</HelpText>
{process.env.SUBDOMAINS_ENABLED && (
<React.Fragment>
<Input
label="Subdomain"
name="subdomain"
value={this.subdomain}
onChange={this.handleSubdomainChange}
placeholder="Optional"
short
/>
{this.subdomain && (
<HelpText small>
You will be able to access your wiki at{' '}
<strong>{this.subdomain}.getoutline.com</strong>
</HelpText>
)}
</React.Fragment>
)}
<Button type="submit" disabled={isSaving || !this.isValid}>
{isSaving ? 'Saving…' : 'Save'}
</Button>

View File

@ -46,6 +46,8 @@ export type Team = {
slackConnected: boolean,
googleConnected: boolean,
sharing: boolean,
subdomain?: string,
url: string,
};
export type NavigationNode = {

View File

@ -29,7 +29,7 @@ services:
ports:
- "3000:3000"
volumes:
- .:/opt/outline
- .:/opt/outline:cached
depends_on:
- postgres
- redis

View File

@ -139,6 +139,7 @@
"normalizr": "2.0.1",
"outline-icons": "^1.3.2",
"oy-vey": "^0.10.0",
"parse-domain": "2.1.6",
"pg": "^6.1.5",
"pg-hstore": "2.3.2",
"polished": "1.2.1",

View File

@ -20,7 +20,7 @@ router.post('team.update', auth(), async ctx => {
authorize(user, 'update', team);
if (name) team.name = name;
if (subdomain) team.subdomain = subdomain;
if (subdomain !== undefined) team.subdomain = subdomain;
if (sharing !== undefined) team.sharing = sharing;
if (avatarUrl && avatarUrl.startsWith(`${endpoint}/uploads/${user.id}`)) {
team.avatarUrl = avatarUrl;

View File

@ -2,6 +2,7 @@
import crypto from 'crypto';
import Router from 'koa-router';
import addMonths from 'date-fns/add_months';
import { stripSubdomain } from '../utils/domains';
import { capitalize } from 'lodash';
import { OAuth2Client } from 'google-auth-library';
import { User, Team } from '../models';
@ -100,13 +101,15 @@ router.get('google.callback', async ctx => {
ctx.cookies.set('lastSignedIn', 'google', {
httpOnly: false,
expires: new Date('2100'),
domain: stripSubdomain(ctx.request.hostname),
});
ctx.cookies.set('accessToken', user.getJwtToken(), {
httpOnly: false,
expires: addMonths(new Date(), 1),
domain: stripSubdomain(ctx.request.hostname),
});
ctx.redirect('/');
ctx.redirect(team.url);
});
export default router;

View File

@ -5,6 +5,7 @@ import addHours from 'date-fns/add_hours';
import addMonths from 'date-fns/add_months';
import { slackAuth } from '../../shared/utils/routeHelpers';
import { Authentication, Integration, User, Team } from '../models';
import { stripSubdomain } from '../utils/domains';
import * as Slack from '../slack';
const router = new Router();
@ -18,6 +19,7 @@ router.get('slack', async ctx => {
ctx.cookies.set('state', state, {
httpOnly: false,
expires: addHours(new Date(), 1),
domain: stripSubdomain(ctx.request.hostname),
});
ctx.redirect(slackAuth(state));
});
@ -29,7 +31,7 @@ router.get('slack.callback', async ctx => {
ctx.assertPresent(state, 'state is required');
if (state !== ctx.cookies.get('state') || error) {
ctx.redirect('/?notice=auth-error');
ctx.redirect(`/?notice=auth-error`);
return;
}
@ -69,13 +71,15 @@ router.get('slack.callback', async ctx => {
ctx.cookies.set('lastSignedIn', 'slack', {
httpOnly: false,
expires: new Date('2100'),
domain: stripSubdomain(ctx.request.hostname),
});
ctx.cookies.set('accessToken', user.getJwtToken(), {
httpOnly: false,
expires: addMonths(new Date(), 1),
domain: stripSubdomain(ctx.request.hostname),
});
ctx.redirect('/');
ctx.redirect(team.url);
});
router.get('slack.commands', auth(), async ctx => {

View File

@ -1,10 +1,10 @@
// @flow
import { type Context } from 'koa';
import type { Context } from 'koa';
export default function subdomainRedirect() {
return async function subdomainRedirectMiddleware(
export default function apexRedirect() {
return async function apexRedirectMiddleware(
ctx: Context,
next: () => Promise<void>
next: () => Promise<*>
) {
if (ctx.headers.host === 'getoutline.com') {
ctx.redirect(`https://www.${ctx.headers.host}${ctx.path}`);

View File

@ -1,35 +1,52 @@
// @flow
import uuid from 'uuid';
import url from 'url';
import { DataTypes, sequelize, Op } from '../sequelize';
import { publicS3Endpoint, uploadToS3FromUrl } from '../utils/s3';
import { RESERVED_SUBDOMAINS } from '../domains';
import { RESERVED_SUBDOMAINS } from '../utils/domains';
import Collection from './Collection';
import User from './User';
const Team = sequelize.define('team', {
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: DataTypes.STRING,
subdomain: {
type: DataTypes.STRING,
allowNull: true,
validate: {
isLowercase: true,
isAlphanumeric: true,
len: [4, 32],
notIn: [RESERVED_SUBDOMAINS],
const Team = sequelize.define(
'team',
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
unique: true,
name: DataTypes.STRING,
subdomain: {
type: DataTypes.STRING,
allowNull: true,
validate: {
isLowercase: true,
is: [/^[a-z\d-]+$/, 'i'],
len: [4, 32],
notIn: [RESERVED_SUBDOMAINS],
},
unique: true,
},
slackId: { type: DataTypes.STRING, allowNull: true },
googleId: { type: DataTypes.STRING, allowNull: true },
avatarUrl: { type: DataTypes.STRING, allowNull: true },
sharing: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true },
slackData: DataTypes.JSONB,
},
slackId: { type: DataTypes.STRING, allowNull: true },
googleId: { type: DataTypes.STRING, allowNull: true },
avatarUrl: { type: DataTypes.STRING, allowNull: true },
sharing: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: true },
slackData: DataTypes.JSONB,
});
{
getterMethods: {
url() {
if (!this.subdomain) return process.env.URL;
const u = url.parse(process.env.URL);
if (u.hostname) {
u.hostname = `${this.subdomain}.${u.hostname}`;
return u.href;
}
},
},
}
);
Team.associate = models => {
Team.hasMany(models.Collection, { as: 'collections' });

View File

@ -4,8 +4,9 @@ import { Helmet } from 'react-helmet';
import styled from 'styled-components';
import Grid from 'styled-components-grid';
import breakpoint from 'styled-components-breakpoint';
import Notice from '../../shared/components/Notice';
import AuthErrors from './components/AuthErrors';
import Hero from './components/Hero';
import HeroText from './components/HeroText';
import Centered from './components/Centered';
import SigninButtons from './components/SigninButtons';
import SlackLogo from '../../shared/components/SlackLogo';
@ -36,24 +37,7 @@ function Home(props: Props) {
<p>
<SigninButtons {...props} />
</p>
{props.notice === 'google-hd' && (
<Notice>
Sorry, Google sign in cannot be used with a personal email. Please
try signing in with your company Google account.
</Notice>
)}
{props.notice === 'hd-not-allowed' && (
<Notice>
Sorry, your Google apps domain is not allowed. Please try again
with an allowed company domain.
</Notice>
)}
{props.notice === 'auth-error' && (
<Notice>
Authentication failed - we were unable to sign you in at this
time. Please try again.
</Notice>
)}
<AuthErrors notice={props.notice} />
</Hero>
<Mask>
<Features>
@ -232,13 +216,4 @@ const Footer = styled.div`
`};
`;
const HeroText = styled.p`
font-size: 22px;
color: #666;
font-weight: 500;
text-align: left;
max-width: 600px;
margin-bottom: 2em;
`;
export default Home;

View File

@ -0,0 +1,81 @@
// @flow
import * as React from 'react';
import { Helmet } from 'react-helmet';
import styled from 'styled-components';
import Grid from 'styled-components-grid';
import Hero from './components/Hero';
import HeroText from './components/HeroText';
import SigninButtons from './components/SigninButtons';
import AuthErrors from './components/AuthErrors';
import Centered from './components/Centered';
import { Team } from '../models';
type Props = {
team: Team,
notice?: 'google-hd' | 'auth-error' | 'hd-not-allowed',
lastSignedIn: string,
googleSigninEnabled: boolean,
slackSigninEnabled: boolean,
hostname: string,
};
function SubdomainSignin({
team,
lastSignedIn,
notice,
googleSigninEnabled,
slackSigninEnabled,
hostname,
}: Props) {
googleSigninEnabled = !!team.googleId && googleSigninEnabled;
slackSigninEnabled = !!team.slackId && slackSigninEnabled;
// only show the "last signed in" hint if there is more than one option available
const signinHint =
googleSigninEnabled && slackSigninEnabled ? lastSignedIn : undefined;
return (
<React.Fragment>
<Helmet>
<title>Outline - Sign in to {team.name}</title>
</Helmet>
<Grid>
<Hero>
<h1>{lastSignedIn ? 'Welcome back,' : 'Hey there,'}</h1>
<HeroText>
Sign in with your team account to continue to {team.name}.
<Subdomain>{hostname}</Subdomain>
</HeroText>
<p>
<SigninButtons
googleSigninEnabled={googleSigninEnabled}
slackSigninEnabled={slackSigninEnabled}
lastSignedIn={signinHint}
/>
</p>
<AuthErrors notice={notice} />
</Hero>
</Grid>
<Alternative>
<p>
Trying to create or sign in to a different team?{' '}
<a href={process.env.URL}>Head to the homepage</a>.
</p>
</Alternative>
</React.Fragment>
);
}
const Subdomain = styled.span`
display: block;
font-weight: 500;
font-size: 16px;
margin-top: 0;
`;
const Alternative = styled(Centered)`
padding: 2em 0;
text-align: center;
`;
export default SubdomainSignin;

View File

@ -0,0 +1,32 @@
// @flow
import * as React from 'react';
import Notice from '../../../shared/components/Notice';
type Props = {
notice?: string,
};
export default function AuthErrors({ notice }: Props) {
return (
<React.Fragment>
{notice === 'google-hd' && (
<Notice>
Sorry, Google sign in cannot be used with a personal email. Please try
signing in with your company Google account.
</Notice>
)}
{notice === 'hd-not-allowed' && (
<Notice>
Sorry, your Google apps domain is not allowed. Please try again with
an allowed company domain.
</Notice>
)}
{notice === 'auth-error' && (
<Notice>
Authentication failed - we were unable to sign you in at this time.
Please try again.
</Notice>
)}
</React.Fragment>
);
}

View File

@ -11,6 +11,11 @@ const Hero = styled(Centered)`
font-size: 3.5em;
line-height: 1em;
}
h2 {
font-size: 2.5em;
line-height: 1em;
}
`;
export default Hero;

View File

@ -0,0 +1,13 @@
// @flow
import styled from 'styled-components';
const HeroText = styled.p`
font-size: 22px;
color: #666;
font-weight: 500;
text-align: left;
max-width: 600px;
margin-bottom: 2em;
`;
export default HeroText;

View File

@ -16,7 +16,7 @@ import {
function TopNavigation() {
return (
<Nav>
<Brand href="/">Outline</Brand>
<Brand href={process.env.URL}>Outline</Brand>
<Menu>
<MenuItemDesktop>
<a href="/#features">Features</a>

View File

@ -8,7 +8,7 @@ import SlackLogo from '../../../shared/components/SlackLogo';
import breakpoint from 'styled-components-breakpoint';
type Props = {
lastSignedIn: string,
lastSignedIn?: string,
googleSigninEnabled: boolean,
slackSigninEnabled: boolean,
};

View File

@ -12,6 +12,8 @@ function present(ctx: Object, team: Team) {
slackConnected: !!team.slackId,
googleConnected: !!team.googleId,
sharing: team.sharing,
subdomain: team.subdomain,
url: team.url,
};
}

View File

@ -5,10 +5,12 @@ import Koa from 'koa';
import Router from 'koa-router';
import sendfile from 'koa-sendfile';
import serve from 'koa-static';
import subdomainRedirect from './middlewares/subdomainRedirect';
import parseDomain from 'parse-domain';
import apexRedirect from './middlewares/apexRedirect';
import renderpage from './utils/renderpage';
import { robotsResponse } from './utils/robots';
import { NotFoundError } from './errors';
import { Team } from './models';
import Home from './pages/Home';
import About from './pages/About';
@ -16,6 +18,7 @@ import Changelog from './pages/Changelog';
import Privacy from './pages/Privacy';
import Pricing from './pages/Pricing';
import Api from './pages/Api';
import SubdomainSignin from './pages/SubdomainSignin';
const isProduction = process.env.NODE_ENV === 'production';
const koa = new Koa();
@ -64,20 +67,44 @@ router.get('/changelog', async ctx => {
router.get('/', async ctx => {
const lastSignedIn = ctx.cookies.get('lastSignedIn');
const accessToken = ctx.cookies.get('accessToken');
const subdomain = parseDomain(ctx.request.hostname).subdomain;
console.log('subdomain', subdomain);
if (accessToken) {
await renderapp(ctx);
} else {
await renderpage(
ctx,
<Home
notice={ctx.request.query.notice}
lastSignedIn={lastSignedIn}
googleSigninEnabled={!!process.env.GOOGLE_CLIENT_ID}
slackSigninEnabled={!!process.env.SLACK_KEY}
/>
);
return renderapp(ctx);
}
if (subdomain) {
const team = await Team.find({
where: { subdomain },
});
if (team && process.env.SUBDOMAINS_ENABLED) {
return renderpage(
ctx,
<SubdomainSignin
team={team}
notice={ctx.request.query.notice}
lastSignedIn={lastSignedIn}
googleSigninEnabled={!!process.env.GOOGLE_CLIENT_ID}
slackSigninEnabled={!!process.env.SLACK_KEY}
hostname={ctx.request.hostname}
/>
);
}
ctx.redirect(process.env.URL);
return;
}
return renderpage(
ctx,
<Home
notice={ctx.request.query.notice}
lastSignedIn={lastSignedIn}
googleSigninEnabled={!!process.env.GOOGLE_CLIENT_ID}
slackSigninEnabled={!!process.env.SLACK_KEY}
/>
);
});
// Other
@ -90,7 +117,7 @@ router.get('*', async ctx => {
});
// middleware
koa.use(subdomainRedirect());
koa.use(apexRedirect());
koa.use(router.routes());
export default koa;

View File

@ -1,4 +1,12 @@
// @flow
import parseDomain from 'parse-domain';
export function stripSubdomain(hostname) {
const parsed = parseDomain(hostname);
if (parsed.tld) return `${parsed.domain}.${parsed.tld}`;
return parsed.domain;
}
export const RESERVED_SUBDOMAINS = [
'admin',
'api',

View File

@ -31,6 +31,9 @@ module.exports = {
{
test: /\.js$/,
loader: 'babel-loader',
exclude: [
path.join(__dirname, 'node_modules')
],
include: [
path.join(__dirname, 'app'),
path.join(__dirname, 'shared'),

View File

@ -37,6 +37,7 @@ productionWebpackConfig.plugins = [
URL: JSON.stringify(process.env.URL),
NODE_ENV: JSON.stringify('production'),
GOOGLE_ANALYTICS_ID: JSON.stringify(process.env.GOOGLE_ANALYTICS_ID),
SUBDOMAINS_ENABLED: JSON.stringify(process.env.SUBDOMAINS_ENABLED)
},
}),
];

260
yarn.lock
View File

@ -87,6 +87,10 @@
version "0.6.6"
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.6.6.tgz#004b98298d04c7ca3b4f50ca2035d4f60d2eed1b"
"@sindresorhus/is@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
"@tommoor/remove-markdown@0.3.1":
version "0.3.1"
resolved "https://registry.yarnpkg.com/@tommoor/remove-markdown/-/remove-markdown-0.3.1.tgz#25e7b845d52fcfadf149a3a6a468a931fee7619b"
@ -417,6 +421,10 @@ assert@^1.1.1:
dependencies:
util "0.10.3"
assertion-error@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b"
ast-types-flow@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad"
@ -1399,6 +1407,10 @@ browser-resolve@^1.11.2:
dependencies:
resolve "1.1.7"
browser-stdout@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f"
browserify-aes@^1.0.0, browserify-aes@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.6.tgz#5e7725dbdef1fd5930d4ebab48567ce451c48a0a"
@ -1569,6 +1581,18 @@ cacache@^10.0.4:
unique-filename "^1.1.0"
y18n "^4.0.0"
cacheable-request@^2.1.1:
version "2.1.4"
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d"
dependencies:
clone-response "1.0.2"
get-stream "3.0.0"
http-cache-semantics "3.8.1"
keyv "3.0.0"
lowercase-keys "1.0.0"
normalize-url "2.0.1"
responselike "1.0.2"
caller-path@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f"
@ -1660,6 +1684,17 @@ center-align@^0.1.1:
align-text "^0.1.3"
lazy-cache "^1.0.3"
chai@^4.1.2:
version "4.2.0"
resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5"
dependencies:
assertion-error "^1.1.0"
check-error "^1.0.2"
deep-eql "^3.0.1"
get-func-name "^2.0.0"
pathval "^1.1.0"
type-detect "^4.0.5"
chainsaw@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98"
@ -1735,6 +1770,10 @@ charenc@~0.0.1:
version "0.0.2"
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
check-error@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82"
cheerio@^0.22.0:
version "0.22.0"
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-0.22.0.tgz#a9baa860a3f9b595a6b81b1a86873121ed3a269e"
@ -1892,6 +1931,12 @@ clone-buffer@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
clone-response@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
dependencies:
mimic-response "^1.0.0"
clone-stats@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1"
@ -2004,6 +2049,10 @@ combined-stream@^1.0.5, combined-stream@~1.0.5:
dependencies:
delayed-stream "~1.0.0"
commander@2.11.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563"
commander@2.8.x:
version "2.8.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
@ -2554,15 +2603,15 @@ debug@2.6.9, debug@^2.6.9:
dependencies:
ms "2.0.0"
debug@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.0.1.tgz#0564c612b521dc92d9f2988f0549e34f9c98db64"
debug@3.1.0, debug@^3.0.1, debug@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
dependencies:
ms "2.0.0"
debug@^3.0.1, debug@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
debug@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.0.1.tgz#0564c612b521dc92d9f2988f0549e34f9c98db64"
dependencies:
ms "2.0.0"
@ -2580,12 +2629,22 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
decompress-response@^3.2.0:
decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
decompress-response@^3.2.0, decompress-response@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
dependencies:
mimic-response "^1.0.0"
deep-eql@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df"
dependencies:
type-detect "^4.0.0"
deep-equal@^1.0.1, deep-equal@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
@ -2696,6 +2755,10 @@ detect-newline@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
diff@3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75"
diff@3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
@ -3138,7 +3201,7 @@ escape-html@~1.0.1, escape-html@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
@ -3835,7 +3898,7 @@ fresh@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.0.tgz#f474ca5e6a9246d6fd8e0953cfa9b9c805afa78e"
from2@^2.1.0:
from2@^2.1.0, from2@^2.1.1:
version "2.3.0"
resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
dependencies:
@ -3995,11 +4058,15 @@ get-document@1:
version "1.0.0"
resolved "https://registry.npmjs.org/get-document/-/get-document-1.0.0.tgz#4821bce66f1c24cb0331602be6cb6b12c4f01c4b"
get-func-name@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
get-stdin@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
get-stream@^3.0.0:
get-stream@3.0.0, get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
@ -4057,16 +4124,7 @@ glob2base@^0.0.12:
dependencies:
find-index "^0.1.1"
glob@^4.3.1:
version "4.5.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f"
dependencies:
inflight "^1.0.4"
inherits "2"
minimatch "^2.0.1"
once "^1.3.0"
glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2:
glob@7.1.2, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2:
version "7.1.2"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
dependencies:
@ -4077,6 +4135,15 @@ glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2:
once "^1.3.0"
path-is-absolute "^1.0.0"
glob@^4.3.1:
version "4.5.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f"
dependencies:
inflight "^1.0.4"
inherits "2"
minimatch "^2.0.1"
once "^1.3.0"
glob@~3.1.21:
version "3.1.21"
resolved "https://registry.yarnpkg.com/glob/-/glob-3.1.21.tgz#d29e0a055dea5138f4d07ed40e8982e83c2066cd"
@ -4212,6 +4279,28 @@ got@^7.1.0:
url-parse-lax "^1.0.0"
url-to-options "^1.0.1"
got@^8.0.1:
version "8.3.2"
resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937"
dependencies:
"@sindresorhus/is" "^0.7.0"
cacheable-request "^2.1.1"
decompress-response "^3.3.0"
duplexer3 "^0.1.4"
get-stream "^3.0.0"
into-stream "^3.1.0"
is-retry-allowed "^1.1.0"
isurl "^1.0.0-alpha5"
lowercase-keys "^1.0.0"
mimic-response "^1.0.0"
p-cancelable "^0.4.0"
p-timeout "^2.0.1"
pify "^3.0.0"
safe-buffer "^5.1.1"
timed-out "^4.0.1"
url-parse-lax "^3.0.0"
url-to-options "^1.0.1"
graceful-fs@^3.0.0:
version "3.0.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818"
@ -4230,6 +4319,10 @@ graceful-fs@~1.2.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
growl@1.10.3:
version "1.10.3"
resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.3.tgz#1926ba90cf3edfe2adb4927f5880bc22c66c790f"
growly@^1.2.0, growly@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@ -4422,7 +4515,7 @@ hawk@~6.0.2:
hoek "4.x.x"
sntp "2.x.x"
he@1.1.x:
he@1.1.1, he@1.1.x:
version "1.1.1"
resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd"
@ -4596,6 +4689,10 @@ http-assert@^1.1.0:
deep-equal "~1.0.1"
http-errors "~1.6.1"
http-cache-semantics@3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
http-errors@1.4.0, http-errors@^1.2.8, http-errors@^1.3.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.4.0.tgz#6c0242dea6b3df7afda153c71089b31c6e82aabf"
@ -4822,6 +4919,13 @@ interpret@^1.0.0:
version "1.0.3"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90"
into-stream@^3.1.0:
version "3.1.0"
resolved "http://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6"
dependencies:
from2 "^2.1.1"
p-is-promise "^1.1.0"
invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
@ -5100,7 +5204,7 @@ is-resolvable@^1.0.0:
dependencies:
tryit "^1.0.1"
is-retry-allowed@^1.0.0:
is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
@ -5647,6 +5751,10 @@ jsesc@~0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
json-buffer@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
json-loader@0.5.4:
version "0.5.4"
resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de"
@ -5784,6 +5892,12 @@ keygrip@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.2.tgz#ad3297c557069dea8bcfe7a4fa491b75c5ddeb91"
keyv@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373"
dependencies:
json-buffer "3.0.0"
kind-of@^3.0.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
@ -6490,7 +6604,7 @@ lower-case@^1.1.0, lower-case@^1.1.1, lower-case@^1.1.2:
version "1.1.4"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
lowercase-keys@^1.0.0:
lowercase-keys@1.0.0, lowercase-keys@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
@ -6780,7 +6894,7 @@ mississippi@^2.0.0:
stream-each "^1.1.0"
through2 "^2.0.0"
"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
mkdirp@0.5.1, "mkdirp@>=0.5 0", 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:
@ -6800,6 +6914,21 @@ mobx@^3.1.9:
version "3.2.2"
resolved "https://registry.yarnpkg.com/mobx/-/mobx-3.2.2.tgz#aa671459bededfd9880c948889a3f62bce09279c"
mocha@^4.0.1:
version "4.1.0"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-4.1.0.tgz#7d86cfbcf35cb829e2754c32e17355ec05338794"
dependencies:
browser-stdout "1.3.0"
commander "2.11.0"
debug "3.1.0"
diff "3.3.1"
escape-string-regexp "1.0.5"
glob "7.1.2"
growl "1.10.3"
he "1.1.1"
mkdirp "0.5.1"
supports-color "4.4.0"
moment-timezone@^0.5.0:
version "0.5.14"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.14.tgz#4eb38ff9538b80108ba467a458f3ed4268ccfcb1"
@ -7112,6 +7241,14 @@ normalize-range@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
normalize-url@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6"
dependencies:
prepend-http "^2.0.0"
query-string "^5.0.1"
sort-keys "^2.0.0"
normalize-url@^1.4.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
@ -7397,10 +7534,18 @@ p-cancelable@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa"
p-cancelable@^0.4.0:
version "0.4.1"
resolved "http://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0"
p-finally@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
p-is-promise@^1.1.0:
version "1.1.0"
resolved "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e"
p-limit@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc"
@ -7421,6 +7566,12 @@ p-timeout@^1.1.1:
dependencies:
p-finally "^1.0.0"
p-timeout@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038"
dependencies:
p-finally "^1.0.0"
package-json@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/package-json/-/package-json-1.2.0.tgz#c8ecac094227cdf76a316874ed05e27cc939a0e0"
@ -7460,6 +7611,15 @@ parse-asn1@^5.0.0:
evp_bytestokey "^1.0.0"
pbkdf2 "^3.0.3"
parse-domain@2.1.6:
version "2.1.6"
resolved "https://registry.yarnpkg.com/parse-domain/-/parse-domain-2.1.6.tgz#3baac0a1c6b7028dfea0013c99a83a1ecd806ed0"
dependencies:
chai "^4.1.2"
got "^8.0.1"
mkdirp "^0.5.1"
mocha "^4.0.1"
parse-entities@^1.0.2:
version "1.1.1"
resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-1.1.1.tgz#8112d88471319f27abae4d64964b122fe4e1b890"
@ -7585,6 +7745,10 @@ path-type@^2.0.0:
dependencies:
pify "^2.0.0"
pathval@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
pause-stream@0.0.11:
version "0.0.11"
resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445"
@ -7976,6 +8140,10 @@ prepend-http@^1.0.0, prepend-http@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
prepend-http@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
@ -8159,6 +8327,14 @@ query-string@^4.1.0, query-string@^4.3.4:
object-assign "^4.1.0"
strict-uri-encode "^1.0.0"
query-string@^5.0.1:
version "5.1.1"
resolved "http://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb"
dependencies:
decode-uri-component "^0.2.0"
object-assign "^4.1.0"
strict-uri-encode "^1.0.0"
querystring-es3@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
@ -8850,6 +9026,12 @@ response-time@~2.3.1:
depd "~1.1.0"
on-headers "~1.0.1"
responselike@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
dependencies:
lowercase-keys "^1.0.0"
restore-cursor@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541"
@ -9390,6 +9572,12 @@ sort-keys@^1.0.0:
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@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
@ -9745,6 +9933,12 @@ stylis@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.0.tgz#016fa239663d77f868fef5b67cf201c4b7c701e1"
supports-color@4.4.0, supports-color@^4.0.0, supports-color@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e"
dependencies:
has-flag "^2.0.0"
supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
@ -9755,12 +9949,6 @@ supports-color@^3.1.2, supports-color@^3.2.3:
dependencies:
has-flag "^1.0.0"
supports-color@^4.0.0, supports-color@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e"
dependencies:
has-flag "^2.0.0"
supports-color@^4.2.1:
version "4.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.5.0.tgz#be7a0de484dec5c5cddf8b3d59125044912f635b"
@ -9925,7 +10113,7 @@ timed-out@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-2.0.0.tgz#f38b0ae81d3747d628001f41dafc652ace671c0a"
timed-out@^4.0.0:
timed-out@^4.0.0, timed-out@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
@ -10085,6 +10273,10 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
type-detect@^4.0.0, type-detect@^4.0.5:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
type-is@^1.5.5, type-is@^1.6.14, type-is@~1.6.6:
version "1.6.15"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410"
@ -10343,6 +10535,12 @@ url-parse-lax@^1.0.0:
dependencies:
prepend-http "^1.0.1"
url-parse-lax@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
dependencies:
prepend-http "^2.0.0"
url-to-options@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"