From b36df699a474e43a9f960721dc8f370f15c9a08e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Sauv=C3=A9?= Date: Mon, 10 Dec 2018 14:04:45 -0500 Subject: [PATCH] Add sewing-kit-koa (#436) --- README.md | 1 + packages/sewing-kit-koa/CHANGELOG.md | 12 ++ packages/sewing-kit-koa/README.md | 107 ++++++++++++ packages/sewing-kit-koa/package.json | 43 +++++ packages/sewing-kit-koa/src/assets.ts | 88 ++++++++++ packages/sewing-kit-koa/src/index.ts | 2 + packages/sewing-kit-koa/src/middleware.ts | 53 ++++++ .../sewing-kit-koa/src/tests/assets.test.ts | 151 +++++++++++++++++ .../src/tests/middleware.test.ts | 36 ++++ packages/sewing-kit-koa/tsconfig.build.json | 15 ++ yarn.lock | 154 +++++++++++++++++- 11 files changed, 657 insertions(+), 5 deletions(-) create mode 100644 packages/sewing-kit-koa/CHANGELOG.md create mode 100644 packages/sewing-kit-koa/README.md create mode 100644 packages/sewing-kit-koa/package.json create mode 100644 packages/sewing-kit-koa/src/assets.ts create mode 100644 packages/sewing-kit-koa/src/index.ts create mode 100644 packages/sewing-kit-koa/src/middleware.ts create mode 100644 packages/sewing-kit-koa/src/tests/assets.test.ts create mode 100644 packages/sewing-kit-koa/src/tests/middleware.test.ts create mode 100644 packages/sewing-kit-koa/tsconfig.build.json diff --git a/README.md b/README.md index 7313a9f604..7edb856e2d 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ Each package has its own `README` and documentation describing usage. | koa-shopify-graphql-proxy | [directory](packages/koa-shopify-graphql-proxy) | [![npm version](https://badge.fury.io/js/%40shopify%2Fkoa-shopify-graphql-proxy.svg)](https://badge.fury.io/js/%40shopify%2Fkoa-shopify-graphql-proxy) | | logger | [directory](packages/logger) | [![npm version](https://badge.fury.io/js/%40shopify%2Flogger.svg)](https://badge.fury.io/js/%40shopify%2Flogger) | | network | [directory](packages/network) | [![npm version](https://badge.fury.io/js/%40shopify%2Fnetwork.svg)](https://badge.fury.io/js/%40shopify%2Fnetwork) | +| polyfills | [directory](packages/polyfills) | [![npm version](https://badge.fury.io/js/%40shopify%2Fpolyfills.svg)](https://badge.fury.io/js/%40shopify%2Fpolyfills) | | react-compose | [directory](packages/react-compose) | [![npm version](https://badge.fury.io/js/%40shopify%2Freact-compose.svg)](https://badge.fury.io/js/%40shopify%2Freact-compose) | | react-effect | [directory](packages/react-effect) | [![npm version](https://badge.fury.io/js/%40shopify%2Freact-effect.svg)](https://badge.fury.io/js/%40shopify%2Freact-effect) | | react-form-state | [directory](packages/react-form-state) | [![npm version](https://badge.fury.io/js/%40shopify%2Freact-form-state.svg)](https://badge.fury.io/js/%40shopify%2Freact-form-state) | diff --git a/packages/sewing-kit-koa/CHANGELOG.md b/packages/sewing-kit-koa/CHANGELOG.md new file mode 100644 index 0000000000..6c4c0a0912 --- /dev/null +++ b/packages/sewing-kit-koa/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) +and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- `@shopify/sewing-kit-koa` package diff --git a/packages/sewing-kit-koa/README.md b/packages/sewing-kit-koa/README.md new file mode 100644 index 0000000000..86ef9b2593 --- /dev/null +++ b/packages/sewing-kit-koa/README.md @@ -0,0 +1,107 @@ +# `@shopify/sewing-kit-koa` + +[![Build Status](https://travis-ci.org/Shopify/quilt.svg?branch=master)](https://travis-ci.org/Shopify/quilt) +[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE.md) [![npm version](https://badge.fury.io/js/%40shopify%2Fsewing-kit-koa.svg)](https://badge.fury.io/js/%40shopify%2Fsewing-kit-koa.svg) + +Easily access [sewing kit](https://github.com/Shopify/sewing-kit) assets from a Koa server. + +## Installation + +```bash +$ yarn add @shopify/sewing-kit-koa +``` + +## Usage + +Add the supplied `middleware` to your Koa application. This is usually done near the start of your app so that all subsequent middleware can make use of Sewing Kit-related information: + +```ts +import Koa from 'koa'; +import {middleware} from '@shopify/sewing-kit-koa'; + +const app = new Koa(); +app.use(middleware()); +``` + +In subsequent middleware, you can now reference `ctx.state.assets`, which has `style` and `script` methods for fetching asset paths asynchronously: + +```ts +app.use(async ctx => { + // Both `styles` and `scripts` return a Promise for an array of objects. + // Each object has a `path` for its resolved URL, and an optional `integrity` + // field for its integrity SHA. You can pass these arrays as-is into + // the `Html` component from @shopify/react-html. + const styles = (await ctx.assets.styles()).map(({path}) => path); + const scripts = (await ctx.assets.scripts()).map(({path}) => path); + + ctx.body = `You need the following assets: ${[...styles, ...scripts].join( + ', ', + )}`; +}); +``` + +By default, the styles and scripts of the main bundle will be returned to you. This is the default bundle sewing kit creates, or the one you have specifically named `main`. You can optionally pass a custom name to retrieve only the assets for that bundle (which would match to the name you gave it when using [sewing kit’s entry plugin](https://github.com/Shopify/sewing-kit/blob/master/docs/plugins/entry.md)): + +```ts +// In your sewing-kit.config.ts... + +module.exports = function sewingKitConfig(plugins) { + return { + plugins: [ + plugins.entry({ + main: __dirname + '/client', + error: __dirname + '/client/error', + }), + ], + }; +}; +``` + +```ts +// In your server... + +app.use(async ctx => { + const styles = (await ctx.assets.styles({name: 'error'})).map( + ({path}) => path, + ); + const scripts = (await ctx.assets.scripts({name: 'error'})).map( + ({path}) => path, + ); + + ctx.body = `Error page needs the following assets: ${[ + ...styles, + ...scripts, + ].join(', ')}`; +}); +``` + +### Options + +The middleware accepts some optional parameters that you can use to customize how sewing kit-generated assets will be served: + +- `assetHost`: the prefix to use for all assets. This is used primary to decide where to mount a static file server if `serveAssets` is true (see next section for details). If not provided, `assetHost` will default to sewing kit’s default development asset server URL. If you set a [custom CDN](https://github.com/Shopify/sewing-kit/blob/master/docs/plugins/cdn.md) in your sewing kit config, you should pass that same value to this option. + +- `serveAssets`: whether this middleware should also serve assets from within your application server. This can be useful when running the application locally, but attempting to replicate more of a production environment (and, therefore, would not be able to use the true production CDN). When this option is passed, `assetHost` must be passed with a path that can be safely mounted to for your server (this same path should be used as the custom CDN for sewing kit so that the paths sewing kit generates make sense). The middleware will then take over that endpoint for asset serving: + + ```ts + // In sewing-kit.config.ts... + // In this example, we want our application to serve assets only when we pass an + // environment variable that indicates we are performing an end-to-end test. + + module.exports = function sewingKitConfig(plugins) { + const plugins = process.env.E2E ? [plugins.cdn('/e2e-assets/')] : []; + + return {plugins}; + }; + ``` + + ```ts + // In your server... + + app.use( + middleware({ + serveAssets: true, + assetHost: '/e2e-assets/', + }), + ); + ``` diff --git a/packages/sewing-kit-koa/package.json b/packages/sewing-kit-koa/package.json new file mode 100644 index 0000000000..789311c092 --- /dev/null +++ b/packages/sewing-kit-koa/package.json @@ -0,0 +1,43 @@ +{ + "name": "@shopify/sewing-kit-koa", + "version": "0.0.0", + "license": "MIT", + "description": "Easily access Sewing Kit assets from a Koa server.", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "sideEffects": false, + "scripts": { + "build": "tsc --p tsconfig.build.json", + "prepublishOnly": "yarn run build" + }, + "publishConfig": { + "access": "public", + "@shopify:registry": "https://registry.npmjs.org" + }, + "author": "Shopify Inc.", + "repository": { + "type": "git", + "url": "git+https://github.com/Shopify/quilt.git" + }, + "bugs": { + "url": "https://github.com/shopify/quilt/issues" + }, + "homepage": "https://github.com/Shopify/quilt/blob/master/packages/sewing-kit-koa/README.md", + "dependencies": { + "@types/koa-mount": "^3.0.1", + "@types/koa-static": "^4.0.0", + "app-root-path": "^2.1.0", + "fs-extra": "^7.0.1", + "koa-compose": "^4.1.0", + "koa-mount": "^4.0.0", + "koa-static": "^5.0.0" + }, + "devDependencies": { + "@types/app-root-path": "^1.2.4", + "@types/fs-extra": "^5.0.4", + "typescript": "~3.0.1" + }, + "files": [ + "dist/*" + ] +} diff --git a/packages/sewing-kit-koa/src/assets.ts b/packages/sewing-kit-koa/src/assets.ts new file mode 100644 index 0000000000..35e3cadca6 --- /dev/null +++ b/packages/sewing-kit-koa/src/assets.ts @@ -0,0 +1,88 @@ +import {join} from 'path'; +import {readJson} from 'fs-extra'; +import appRoot from 'app-root-path'; + +export interface Asset { + path: string; + integrity?: string; +} + +interface Entrypoint { + js: Asset[]; + css: Asset[]; +} + +interface AssetList { + entrypoints: {[key: string]: Entrypoint}; +} + +interface Options { + assetHost: string; +} + +export default class Assets { + assetHost: string; + private resolvedAssetList?: AssetList; + + constructor({assetHost}: Options) { + this.assetHost = assetHost; + } + + async scripts({name = 'main'} = {}) { + const {js} = getAssetsForEntrypoint(name, await this.getAssetList()); + + const scripts = + // Sewing Kit does not currently include the vendor DLL in its asset + // manifest, so we manually add it here (it only exists in dev). + // eslint-disable-next-line no-process-env + process.env.NODE_ENV === 'development' + ? [{path: `${this.assetHost}dll/vendor.js`}, ...js] + : js; + + return scripts; + } + + async styles({name = 'main'} = {}) { + const {css} = getAssetsForEntrypoint(name, await this.getAssetList()); + return css; + } + + private async getAssetList() { + if (this.resolvedAssetList) { + return this.resolvedAssetList; + } + + this.resolvedAssetList = await loadConsolidatedManifest(); + return this.resolvedAssetList; + } +} + +let consolidatedManifestPromise: Promise | null = null; + +function loadConsolidatedManifest() { + if (consolidatedManifestPromise) { + return consolidatedManifestPromise; + } + + consolidatedManifestPromise = readJson( + join(appRoot.path, 'build/client/assets.json'), + ); + + return consolidatedManifestPromise; +} + +export function internalOnlyClearCache() { + consolidatedManifestPromise = null; +} + +function getAssetsForEntrypoint(name: string, {entrypoints}: AssetList) { + if (!entrypoints.hasOwnProperty(name)) { + throw new Error( + `No entrypoints found with the name '${name}'. Available entrypoints: ${Object.keys( + entrypoints, + ).join(', ')}`, + ); + } + + return entrypoints[name]; +} diff --git a/packages/sewing-kit-koa/src/index.ts b/packages/sewing-kit-koa/src/index.ts new file mode 100644 index 0000000000..b4bb0ca0a3 --- /dev/null +++ b/packages/sewing-kit-koa/src/index.ts @@ -0,0 +1,2 @@ +export {default as Assets} from './assets'; +export {default as middleware} from './middleware'; diff --git a/packages/sewing-kit-koa/src/middleware.ts b/packages/sewing-kit-koa/src/middleware.ts new file mode 100644 index 0000000000..f190d86d75 --- /dev/null +++ b/packages/sewing-kit-koa/src/middleware.ts @@ -0,0 +1,53 @@ +import {join} from 'path'; +import {Context} from 'koa'; +import serve from 'koa-static'; +import compose from 'koa-compose'; +import mount from 'koa-mount'; +import appRoot from 'app-root-path'; + +import Assets, {Asset} from './assets'; + +export {Assets, Asset}; + +export interface State { + assets: Assets; +} + +export interface Options { + assetHost?: string; + serveAssets?: boolean; +} + +export default function middleware({ + serveAssets = false, + assetHost = defaultAssetHost(serveAssets), +}: Options = {}) { + async function sewingKitMiddleware(ctx: Context, next: () => Promise) { + const assets = new Assets({ + assetHost, + }); + ctx.state.assets = assets; + await next(); + } + + return serveAssets && assetHost.startsWith('/') + ? compose([ + sewingKitMiddleware, + mount(assetHost, serve(join(appRoot.path, 'build/client'))), + ]) + : sewingKitMiddleware; +} + +function defaultAssetHost(serveAssets: boolean) { + // In development, Sewing Kit defaults to running an asset server on + // http://localhost:8080/webpack/assets/. When running in `serveAssets` + // mode (the application server also serves the assets), we default to + // assuming they have set the asset endpoint to be under /assets. In order + // to use this feature, a developer would need to add the following to the + // Sewing Kit config that built the assets: + // + // { + // plugins: [plugins.cdn('/assets/')], + // } + return serveAssets ? '/assets/' : 'http://localhost:8080/webpack/assets/'; +} diff --git a/packages/sewing-kit-koa/src/tests/assets.test.ts b/packages/sewing-kit-koa/src/tests/assets.test.ts new file mode 100644 index 0000000000..7c177f9379 --- /dev/null +++ b/packages/sewing-kit-koa/src/tests/assets.test.ts @@ -0,0 +1,151 @@ +import {join} from 'path'; +import withEnv from '@shopify/with-env'; +import appRoot from 'app-root-path'; +import Assets, {internalOnlyClearCache} from '../assets'; + +jest.mock('fs-extra', () => ({ + ...require.requireActual('fs-extra'), + readJson: jest.fn(() => ({ + entrypoints: {}, + })), +})); + +const {readJson} = require.requireMock('fs-extra'); + +describe('Assets', () => { + const defaultOptions = {assetHost: '/assets/'}; + + beforeEach(() => { + readJson.mockReset(); + readJson.mockImplementation(() => createMockAssetList()); + }); + + afterEach(() => { + internalOnlyClearCache(); + }); + + it('reads the asset cache', async () => { + const assets = new Assets(defaultOptions); + + await assets.styles(); + + expect(readJson).toHaveBeenCalledWith( + join(appRoot.path, 'build/client/assets.json'), + ); + }); + + it('only reads the asset cache once', async () => { + await new Assets(defaultOptions).styles(); + await new Assets(defaultOptions).scripts(); + + expect(readJson).toHaveBeenCalledTimes(1); + }); + + describe('scripts', () => { + it('returns the main scripts by default', async () => { + const js = '/style.js'; + + readJson.mockImplementation(() => + createMockAssetList({name: 'main', scripts: [js]}), + ); + + const assets = new Assets(defaultOptions); + + expect(await assets.scripts()).toEqual([{path: js}]); + }); + + it('returns the scripts for a named bundle', async () => { + const js = '/style.js'; + + readJson.mockImplementation(() => + createMockAssetList({name: 'custom', scripts: [js]}), + ); + + const assets = new Assets(defaultOptions); + + expect(await assets.scripts({name: 'custom'})).toEqual([{path: js}]); + }); + + it('throws an error when no scripts exist for the passed entrypoint', async () => { + const assets = new Assets(defaultOptions); + await expect( + assets.scripts({name: 'non-existent'}), + ).rejects.toBeInstanceOf(Error); + }); + + it('prefixes the list with the vendor DLL in development', async () => { + const js = '/style.js'; + const assetHost = '/sewing-kit-assets/'; + + readJson.mockImplementation(() => + createMockAssetList({name: 'custom', scripts: [js]}), + ); + + const assets = new Assets({...defaultOptions, assetHost}); + const scripts = await withEnv('development', () => + assets.scripts({name: 'custom'}), + ); + + expect(scripts).toEqual([ + {path: `${assetHost}dll/vendor.js`}, + {path: js}, + ]); + }); + }); + + describe('styles', () => { + it('returns the main styles by default', async () => { + const css = '/style.css'; + + readJson.mockImplementation(() => + createMockAssetList({name: 'main', styles: [css]}), + ); + + const assets = new Assets(defaultOptions); + + expect(await assets.styles()).toEqual([{path: css}]); + }); + + it('returns the styles for a named bundle', async () => { + const css = '/style.css'; + + readJson.mockImplementation(() => + createMockAssetList({name: 'custom', styles: [css]}), + ); + + const assets = new Assets(defaultOptions); + + expect(await assets.styles({name: 'custom'})).toEqual([{path: css}]); + }); + + it('throws an error when no styles exist for the passed entrypoint', async () => { + const assets = new Assets(defaultOptions); + await expect( + assets.styles({name: 'non-existent'}), + ).rejects.toBeInstanceOf(Error); + }); + }); +}); + +function createMockAssetList({ + name = 'main', + styles = [], + scripts = [], +}: { + name?: string; + styles?: string[]; + scripts?: string[]; +} = {}) { + return { + entrypoints: { + [name]: { + js: scripts.map(path => ({ + path, + })), + css: styles.map(path => ({ + path, + })), + }, + }, + }; +} diff --git a/packages/sewing-kit-koa/src/tests/middleware.test.ts b/packages/sewing-kit-koa/src/tests/middleware.test.ts new file mode 100644 index 0000000000..0cb597cfb1 --- /dev/null +++ b/packages/sewing-kit-koa/src/tests/middleware.test.ts @@ -0,0 +1,36 @@ +import {createMockContext} from '@shopify/jest-koa-mocks'; +import middleware from '../middleware'; +import Assets from '../assets'; + +describe('middleware', () => { + it('adds an instance of Assets with the specified assetHost to state', async () => { + const assetHost = '/sewing-kit-assets/'; + const context = createMockContext(); + await middleware({assetHost})(context, () => Promise.resolve()); + + expect(context.state).toHaveProperty('assets'); + expect(context.state.assets).toBeInstanceOf(Assets); + expect(context.state.assets).toHaveProperty('assetHost', assetHost); + }); + + it('defaults the asset host to Sewing Kit’s dev server', async () => { + const context = createMockContext(); + await middleware()(context, () => Promise.resolve()); + expect(context.state.assets).toHaveProperty( + 'assetHost', + 'http://localhost:8080/webpack/assets/', + ); + }); + + it('defaults the asset host to /assets/ when serveAssets is true', async () => { + const context = createMockContext(); + await middleware({serveAssets: true})(context, () => Promise.resolve()); + expect(context.state.assets).toHaveProperty('assetHost', '/assets/'); + }); + + it('calls the next middleware', async () => { + const next = jest.fn(() => Promise.resolve()); + await middleware()(createMockContext(), next); + expect(next).toHaveBeenCalled(); + }); +}); diff --git a/packages/sewing-kit-koa/tsconfig.build.json b/packages/sewing-kit-koa/tsconfig.build.json new file mode 100644 index 0000000000..e836cbeb4e --- /dev/null +++ b/packages/sewing-kit-koa/tsconfig.build.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.json", + "compileOnSave": false, + "compilerOptions": { + "outDir": "./dist", + "baseUrl": "./src", + "rootDir": "./src" + }, + "include": [ + "../../config/typescript/**/*", + "./src/**/*.ts", + "./src/**/*.tsx" + ], + "exclude": ["**/*.test.ts", "**/*.test.tsx"] +} diff --git a/yarn.lock b/yarn.lock index 320acec1cc..e6422126c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -188,6 +188,11 @@ dependencies: "@types/node" "*" +"@types/app-root-path@^1.2.4": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/app-root-path/-/app-root-path-1.2.4.tgz#a78b703282b32ac54de768f5512ecc3569919dc7" + integrity sha1-p4twMoKzKsVN52j1US7MNWmRncc= + "@types/async@2.0.49": version "2.0.49" resolved "https://registry.yarnpkg.com/@types/async/-/async-2.0.49.tgz#92e33d13f74c895cb9a7f38ba97db8431ed14bc0" @@ -278,6 +283,13 @@ resolved "https://registry.yarnpkg.com/@types/fetch-mock/-/fetch-mock-6.0.1.tgz#df4e9f3a12fc81fae173f018ea17ad79441c10ba" integrity sha512-+DxTwd90Jl6ua47SPw3JkgtGR5vF0CoHKGnV0qvy3yvd9r37nVQtQQ9XJBS7L97PRVTaQU/tMsmFcjoJ8sRLqA== +"@types/fs-extra@^5.0.4": + version "5.0.4" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.4.tgz#b971134d162cc0497d221adde3dbb67502225599" + integrity sha512-DsknoBvD8s+RFfSGjmERJ7ZOP1HI0UZRA3FSI+Zakhrc/Gy26YQsLI+m5V5DHxroHRJqCDLKJp7Hixn8zyaF7g== + dependencies: + "@types/node" "*" + "@types/graphql@^0.13.0": version "0.13.1" resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.13.1.tgz#7d39750355c9ecb921816d6f76c080405b5f6bea" @@ -322,6 +334,21 @@ dependencies: "@types/koa" "*" +"@types/koa-send@*": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@types/koa-send/-/koa-send-4.1.1.tgz#88f57cbe0343c8204903f9096d8ff6b98ec3296f" + integrity sha512-ODeofnQxlkAl5PvJXhgOF/hjX9cH47oDFDqH9nE0G6zcp/Rr0vjswe7tuA+b0ZhlBrkfmN9X8VhRqQCBeOtZrw== + dependencies: + "@types/koa" "*" + +"@types/koa-static@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/koa-static/-/koa-static-4.0.0.tgz#4cddce6adf45221bacfb2842b3621dc92f4d642c" + integrity sha512-qjT7JIV79ZQ9MFjrWLvmnrX9St4DFuT2tzSRpkzQHx4QQBrLR04FSZHhoFkrgNgojkTuX2uxshdoThxhIMWVqw== + dependencies: + "@types/koa" "*" + "@types/koa-send" "*" + "@types/koa@*": version "2.0.45" resolved "https://registry.yarnpkg.com/@types/koa/-/koa-2.0.45.tgz#133cbda6cc8d12b73434b5d9663898c833f80aa2" @@ -381,6 +408,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.1.tgz#e2d374ef15b315b48e7efc308fa1a7cd51faa06c" integrity sha512-xwlHq5DXQFRpe+u6hmmNkzYk/3oxxqDp71a/AJMupOQYmxyaBetqrVMqdNlSQfbg7XTJYD8vARjf3Op06OzdtQ== +"@types/prop-types@*": + version "15.5.7" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.7.tgz#c6f1e0d0109ff358b132d98b7b4025c7a7b707c5" + integrity sha512-a6WH0fXkgPNiGIuLjjdpf0n/GnmgWZ4vLuVIJJnDwhmRDPEaiRBcy5ofQPh+EJFua0S1QWmk1745+JqZQGnJ8Q== + "@types/react-helmet@^5.0.6": version "5.0.6" resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-5.0.6.tgz#49607cbb72e1bb7dcefa9174cb591434d3b6f0af" @@ -396,13 +428,21 @@ "@types/history" "^3" "@types/react" "*" -"@types/react@*", "@types/react@16.3.18", "@types/react@>=16.4.0", "@types/react@^16.0.2": +"@types/react@*", "@types/react@16.3.18", "@types/react@^16.0.2": version "16.3.18" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.3.18.tgz#bf195aed4d77dc86f06e4c9bb760214a3b822b8d" integrity sha512-aWTvLHzKqbVWCiee8huwf5x7Ob4n4gxDwgJT/X31HqjGVZpeUeFeSFYH5Gvi5Dmm5HKF+s+dQkwa/nnEVVzzzg== dependencies: csstype "^2.2.0" +"@types/react@>=16.4.0": + version "16.7.13" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.13.tgz#d2369ae78377356d42fb54275d30218e84f2247a" + integrity sha512-WhqrQLAE9z65hfcvWqZfR6qUtIazFRyb8LXqHo8440R53dAQqNkt2OlVJ3FXwqOwAXXg4nfYxt0qgBvE18o5XA== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + "@types/safe-compare@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@types/safe-compare/-/safe-compare-1.1.0.tgz#47ed9b9ca51a3a791b431cd59b28f47fa9bf1224" @@ -562,7 +602,7 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -any-promise@^1.1.0: +any-promise@^1.0.0, any-promise@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= @@ -641,6 +681,11 @@ apollo-utilities@^1.0.0, apollo-utilities@^1.0.15: dependencies: fast-json-stable-stringify "^2.0.0" +app-root-path@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-2.1.0.tgz#98bf6599327ecea199309866e8140368fd2e646a" + integrity sha1-mL9lmTJ+zqGZMJhm6BQDaP0uZGo= + append-transform@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" @@ -1999,6 +2044,13 @@ debug@^2.2.0, debug@^2.3.3, debug@^2.6.1, debug@^2.6.8, debug@^2.6.9: dependencies: ms "2.0.0" +debug@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.0.tgz#373687bffa678b38b1cd91f861b63850035ddc87" + integrity sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg== + dependencies: + ms "^2.1.1" + decamelize-keys@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" @@ -3142,6 +3194,15 @@ fs-extra@^4.0.1: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" + integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -3719,7 +3780,7 @@ http-errors@1.6.2: setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" -http-errors@^1.2.8, http-errors@~1.6.1: +http-errors@^1.2.8, http-errors@~1.6.1, http-errors@~1.6.2: version "1.6.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= @@ -3729,6 +3790,17 @@ http-errors@^1.2.8, http-errors@~1.6.1: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" +http-errors@^1.6.3: + version "1.7.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.1.tgz#6a4ffe5d35188e1c39f872534690585852e1f027" + integrity sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -4868,6 +4940,11 @@ koa-compose@^4.0.0: resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.0.0.tgz#2800a513d9c361ef0d63852b038e4f6f2d5a773c" integrity sha1-KAClE9nDYe8NY4UrA45Pby1adzw= +koa-compose@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877" + integrity sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw== + koa-convert@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-1.2.0.tgz#da40875df49de0539098d1700b50820cebcd21d0" @@ -4889,6 +4966,32 @@ koa-mount@^3.0.0: debug "^2.6.1" koa-compose "^3.2.1" +koa-mount@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/koa-mount/-/koa-mount-4.0.0.tgz#e0265e58198e1a14ef889514c607254ff386329c" + integrity sha512-rm71jaA/P+6HeCpoRhmCv8KVBIi0tfGuO/dMKicbQnQW/YJntJ6MnnspkodoA4QstMVEZArsCphmd0bJEtoMjQ== + dependencies: + debug "^4.0.1" + koa-compose "^4.1.0" + +koa-send@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/koa-send/-/koa-send-5.0.0.tgz#5e8441e07ef55737734d7ced25b842e50646e7eb" + integrity sha512-90ZotV7t0p3uN9sRwW2D484rAaKIsD8tAVtypw/aBU+ryfV+fR2xrcAwhI8Wl6WRkojLUs/cB9SBSCuIb+IanQ== + dependencies: + debug "^3.1.0" + http-errors "^1.6.3" + mz "^2.7.0" + resolve-path "^1.4.0" + +koa-static@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/koa-static/-/koa-static-5.0.0.tgz#5e92fc96b537ad5219f425319c95b64772776943" + integrity sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ== + dependencies: + debug "^3.1.0" + koa-send "^5.0.0" + koa@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/koa/-/koa-2.5.0.tgz#b0fbe1e195e43b27588a04fd0be0ddaeca2c154c" @@ -5443,6 +5546,11 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= +ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + multimatch@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" @@ -5458,6 +5566,15 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= +mz@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + nan@^2.3.0: version "2.10.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" @@ -6010,7 +6127,7 @@ path-exists@^3.0.0: resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: +path-is-absolute@1.0.1, path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= @@ -6719,6 +6836,14 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-path@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/resolve-path/-/resolve-path-1.4.0.tgz#c4bda9f5efb2fce65247873ab36bb4d834fe16f7" + integrity sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc= + dependencies: + http-errors "~1.6.2" + path-is-absolute "1.0.1" + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -7134,7 +7259,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2", statuses@^1.2.0: +"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@^1.2.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= @@ -7388,6 +7513,20 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.0" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" + integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk= + dependencies: + any-promise "^1.0.0" + throat@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" @@ -7466,6 +7605,11 @@ to-regex@^3.0.1, to-regex@^3.0.2: regex-not "^1.0.2" safe-regex "^1.1.0" +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + tough-cookie@>=2.3.3, tough-cookie@^2.3.3, tough-cookie@~2.3.0, tough-cookie@~2.3.3: version "2.3.4" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655"