diff --git a/.changeset/c417a04d/changes.json b/.changeset/c417a04d/changes.json new file mode 100644 index 00000000000..9320290f7ef --- /dev/null +++ b/.changeset/c417a04d/changes.json @@ -0,0 +1,33 @@ +{ + "releases": [ + { "name": "@keystone-alpha/api-tests", "type": "patch" }, + { "name": "@keystone-alpha/demo-project-blog", "type": "minor" }, + { "name": "@keystone-alpha/demo-project-meetup", "type": "minor" }, + { "name": "@keystone-alpha/demo-project-todo", "type": "minor" }, + { "name": "@keystone-alpha/admin-ui", "type": "major" }, + { "name": "@keystone-alpha/core", "type": "major" }, + { "name": "create-keystone-app", "type": "minor" }, + { "name": "@keystone-alpha/fields-wysiwyg-tinymce", "type": "major" }, + { "name": "@keystone-alpha/keystone", "type": "major" }, + { "name": "@keystone-alpha/server-graphql", "type": "major" }, + { "name": "@keystone-alpha/server-next", "type": "major" }, + { "name": "@keystone-alpha/server-static", "type": "major" }, + { "name": "@keystone-alpha/test-utils", "type": "patch" }, + { "name": "@keystone-alpha/cypress-project-access-control", "type": "minor" }, + { "name": "@keystone-alpha/cypress-project-basic", "type": "minor" }, + { "name": "@keystone-alpha/cypress-project-login", "type": "minor" }, + { "name": "@keystone-alpha/cypress-project-social-login", "type": "minor" } + ], + "dependents": [ + { + "name": "@keystone-alpha/adapter-knex", + "type": "patch", + "dependencies": ["@keystone-alpha/keystone"] + }, + { + "name": "@keystone-alpha/adapter-mongoose", + "type": "patch", + "dependencies": ["@keystone-alpha/keystone"] + } + ] +} diff --git a/.changeset/c417a04d/changes.md b/.changeset/c417a04d/changes.md new file mode 100644 index 00000000000..bd9f2739206 --- /dev/null +++ b/.changeset/c417a04d/changes.md @@ -0,0 +1,27 @@ +Specify custom servers from within the index.js file + - Major Changes: + - The `index.js` export for `admin` must now be exported in the `servers` + array: + ```diff + module.exports = { + keystone, + - admin, + + servers: [admin], + } + ``` + - The `keystone.prepare()` method (often used within a _Custom Server_ + `server.js`) no longer returns a `server`, it now returns a `middlewares` + array: + ```diff + +const express = require('express'); + const port = 3000; + keystone.prepare({ port }) + - .then(async ({ server, keystone: keystoneApp }) => { + + .then(async ({ middlewares, keystone: keystoneApp }) => { + await keystoneApp.connect(); + - await server.start(); + + const app = express(); + + app.use(middlewares); + + app.listen(port) + }); + ``` diff --git a/api-tests/auth-header.test.js b/api-tests/auth-header.test.js index 0380e85dc34..0eabb3a997b 100644 --- a/api-tests/auth-header.test.js +++ b/api-tests/auth-header.test.js @@ -1,7 +1,8 @@ const supertest = require('supertest-light'); const { Keystone, PasswordAuthStrategy } = require('@keystone-alpha/keystone'); const { Text, Password } = require('@keystone-alpha/fields'); -const { WebServer } = require('@keystone-alpha/server'); +const GraphQLServer = require('@keystone-alpha/server-graphql'); +const express = require('express'); const bodyParser = require('body-parser'); const cookieSignature = require('cookie-signature'); const { multiAdapterRunners } = require('@keystone-alpha/test-utils'); @@ -26,7 +27,7 @@ const initialData = { const COOKIE_SECRET = 'qwerty'; -function setupKeystone() { +async function setupKeystone() { const keystone = new Keystone({ name: `Jest Test Project For Login Auth ${cuid()}`, adapter: new MongooseAdapter(), @@ -48,13 +49,17 @@ function setupKeystone() { list: 'User', }); - const server = new WebServer(keystone, { + const app = express(); + + const graphQLServer = new GraphQLServer(keystone, { cookieSecret: COOKIE_SECRET, apiPath: '/admin/api', graphiqlPath: '/admin/graphiql', }); - server.app.post( + app.use(await graphQLServer.prepareMiddleware({ keystone, dev: true })); + + app.post( '/signin', bodyParser.json(), bodyParser.urlencoded({ extended: true }), @@ -83,11 +88,11 @@ function setupKeystone() { } ); - return { keystone, server }; + return { keystone, app }; } -function login(server, username, password) { - return supertest(server.app) +function login(app, username, password) { + return supertest(app) .set('Accept', 'application/json') .post('/signin', { username, password }) .then(res => { @@ -104,10 +109,10 @@ multiAdapterRunners().map(({ runner, adapterName }) => describe('Auth testing', () => { test( 'Gives access denied when not logged in', - runner(setupKeystone, async ({ keystone, server }) => { + runner(setupKeystone, async ({ keystone, app }) => { // seed the db await keystone.createItems(initialData); - return supertest(server.app) + return supertest(app) .set('Accept', 'application/json') .post('/admin/api', { query: '{ allUsers { id } }' }) .then(function(res) { @@ -122,10 +127,10 @@ multiAdapterRunners().map(({ runner, adapterName }) => describe('logged in', () => { test( 'Allows access with bearer token', - runner(setupKeystone, async ({ keystone, server }) => { + runner(setupKeystone, async ({ keystone, app }) => { await keystone.createItems(initialData); const { success, token } = await login( - server, + app, initialData.User[0].email, initialData.User[0].password ); @@ -133,7 +138,7 @@ multiAdapterRunners().map(({ runner, adapterName }) => expect(success).toBe(true); expect(token).toBeTruthy(); - return supertest(server.app) + return supertest(app) .set('Authorization', `Bearer ${token}`) .set('Accept', 'application/json') .post('/admin/api', { query: '{ allUsers { id } }' }) @@ -149,10 +154,10 @@ multiAdapterRunners().map(({ runner, adapterName }) => test( 'Allows access with cookie', - runner(setupKeystone, async ({ keystone, server }) => { + runner(setupKeystone, async ({ keystone, app }) => { await keystone.createItems(initialData); const { success, token } = await login( - server, + app, initialData.User[0].email, initialData.User[0].password ); @@ -160,7 +165,7 @@ multiAdapterRunners().map(({ runner, adapterName }) => expect(success).toBe(true); expect(token).toBeTruthy(); - return supertest(server.app) + return supertest(app) .set('Cookie', `keystone.sid=${signCookie(token)}`) .set('Accept', 'application/json') .post('/admin/api', { query: '{ allUsers { id } }' }) diff --git a/api-tests/package.json b/api-tests/package.json index 797ad2911f3..6e038bc1648 100644 --- a/api-tests/package.json +++ b/api-tests/package.json @@ -12,7 +12,7 @@ "@keystone-alpha/adapter-mongoose": "^2.0.0", "@keystone-alpha/fields": "^6.1.1", "@keystone-alpha/keystone": "^4.0.0", - "@keystone-alpha/server": "^5.0.0", + "@keystone-alpha/server-graphql": "^5.0.0", "@keystone-alpha/session": "^1.0.2", "@keystone-alpha/test-utils": "^2.0.2", "body-parser": "^1.18.2", @@ -20,5 +20,8 @@ "cuid": "^2.1.6", "supertest-light": "^1.0.2", "testcheck": "^1.0.0-rc.2" + }, + "dependencies": { + "express": "^4.16.3" } } \ No newline at end of file diff --git a/demo-projects/blog/app/pages/index.js b/demo-projects/blog/app/pages/index.js index 1be81f61f68..cc368b05a02 100644 --- a/demo-projects/blog/app/pages/index.js +++ b/demo-projects/blog/app/pages/index.js @@ -25,16 +25,16 @@ const Post = ({ post }) => { }} > {post.image ? : null} -
+

{post.title}

-

{post.body}

+

Posted by {post.author ? post.author.name : 'someone'} on{' '} {format(post.posted, 'DD/MM/YYYY')}

-
+ ); diff --git a/demo-projects/blog/app/pages/post.js b/demo-projects/blog/app/pages/post.js index 22e64aec5bf..7bcffc918a4 100644 --- a/demo-projects/blog/app/pages/post.js +++ b/demo-projects/blog/app/pages/post.js @@ -235,9 +235,9 @@ class PostPage extends React.Component { {post.title} {post.image ? : null} -
+

{post.title}

-

{post.body}

+

-
+ diff --git a/demo-projects/blog/index.js b/demo-projects/blog/index.js index fd7ee4abd8b..5f932e07de0 100644 --- a/demo-projects/blog/index.js +++ b/demo-projects/blog/index.js @@ -2,6 +2,8 @@ const { AdminUI } = require('@keystone-alpha/admin-ui'); const { Keystone, PasswordAuthStrategy } = require('@keystone-alpha/keystone'); const { MongooseAdapter } = require('@keystone-alpha/adapter-mongoose'); +const NextServer = require('@keystone-alpha/server-next'); +const StaticServer = require('@keystone-alpha/server-static'); const { staticRoute, staticPath } = require('./config'); const { User, Post, PostCategory, Comment } = require('./schema'); @@ -43,11 +45,14 @@ const admin = new AdminUI(keystone, { children: ['User'], }, ], + disableDefaultRoute: true, }); module.exports = { - staticRoute, - staticPath, keystone, - admin, + servers: [ + admin, + new StaticServer({ route: staticRoute, path: staticPath }), + new NextServer({ dir: 'app' }), + ], }; diff --git a/demo-projects/blog/package.json b/demo-projects/blog/package.json index bbe37b0f7e9..e185f29366d 100644 --- a/demo-projects/blog/package.json +++ b/demo-projects/blog/package.json @@ -9,7 +9,7 @@ "node": ">=8.4.0" }, "scripts": { - "start": "DISABLE_LOGGING=true node server.js", + "start": "NODE_ENV=development DISABLE_LOGGING=true node server.js", "build": "next build app && keystone build" }, "dependencies": { @@ -24,14 +24,16 @@ "@keystone-alpha/fields-wysiwyg-tinymce": "^2.0.1", "@keystone-alpha/file-adapters": "^1.0.2", "@keystone-alpha/keystone": "^4.0.0", + "@keystone-alpha/server-next": "^0.0.0", + "@keystone-alpha/server-static": "^0.0.0", "apollo-boost": "^0.3.1", "apollo-client": "^2.5.1", "apollo-upload-client": "^10.0.0", "date-fns": "^1.30.1", + "express": "^4.16.3", "graphql-tag": "^2.10.1", "isomorphic-unfetch": "^3.0.0", "next": "^8.0.3", - "next-apollo": "^2.0.9", "node-fetch": "^2.3.0", "react": "^16.8.6", "react-apollo": "2.4.0" diff --git a/demo-projects/blog/server.js b/demo-projects/blog/server.js index f576c570d0c..e6585d04631 100644 --- a/demo-projects/blog/server.js +++ b/demo-projects/blog/server.js @@ -1,19 +1,14 @@ +const express = require('express'); const keystone = require('@keystone-alpha/core'); -const { Wysiwyg } = require('@keystone-alpha/fields-wysiwyg-tinymce'); -const next = require('next'); -const { port, staticRoute, staticPath } = require('./config'); +const { port } = require('./config'); const initialData = require('./initialData'); -const nextApp = next({ - dir: 'app', - distDir: 'build', - dev: process.env.NODE_ENV !== 'production', -}); - -Promise.all([keystone.prepare({ port }), nextApp.prepare()]) - .then(async ([{ server, keystone: keystoneApp }]) => { +keystone + .prepare({ port, dev: process.env.NODE_ENV !== 'production' }) + .then(async ({ middlewares, keystone: keystoneApp }) => { await keystoneApp.connect(process.env.MONGODB_URI); + // Initialise some data. // NOTE: This is only for demo purposes and should not be used in production const users = await keystoneApp.lists.User.adapter.findAll(); @@ -21,10 +16,13 @@ Promise.all([keystone.prepare({ port }), nextApp.prepare()]) await keystoneApp.createItems(initialData); } - Wysiwyg.bindStaticMiddleware(server); - server.app.use(staticRoute, server.express.static(staticPath)); - server.app.use(nextApp.getRequestHandler()); - await server.start(); + const app = express(); + + app.use(middlewares); + + app.listen(port, error => { + if (error) throw error; + }); }) .catch(error => { console.error(error); diff --git a/demo-projects/meetup/index.js b/demo-projects/meetup/index.js index fbf14ef239b..1bf3efba6f5 100644 --- a/demo-projects/meetup/index.js +++ b/demo-projects/meetup/index.js @@ -2,6 +2,8 @@ const { AdminUI } = require('@keystone-alpha/admin-ui'); const { Keystone, PasswordAuthStrategy } = require('@keystone-alpha/keystone'); const { MongooseAdapter } = require('@keystone-alpha/adapter-mongoose'); +const NextServer = require('@keystone-alpha/server-next'); +const routes = require('./routes'); const { Event, Talk, User, Rsvp, Organiser, Sponsor } = require('./schema'); @@ -37,9 +39,10 @@ const admin = new AdminUI(keystone, { children: ['User', 'Rsvp'], }, ], + disableDefaultRoute: true, }); module.exports = { keystone, - admin, + servers: [admin, new NextServer({ dir: 'site', nextRoutes: routes })], }; diff --git a/demo-projects/meetup/package.json b/demo-projects/meetup/package.json index 6f607dd15e3..350e845649b 100644 --- a/demo-projects/meetup/package.json +++ b/demo-projects/meetup/package.json @@ -9,8 +9,8 @@ "node": ">=8.4.0" }, "scripts": { - "start": "DISABLE_LOGGING=true node server.js", - "build": "next build" + "start": "NODE_ENV=development DISABLE_LOGGING=true node server.js", + "build": "NODE_ENV=production next build" }, "dependencies": { "@emotion/core": "^10.0.10", @@ -21,6 +21,7 @@ "@keystone-alpha/fields-wysiwyg-tinymce": "^2.0.1", "@keystone-alpha/file-adapters": "^1.0.2", "@keystone-alpha/keystone": "^4.0.0", + "@keystone-alpha/server-next": "^0.0.0", "@keystone-alpha/session": "^1.0.2", "apollo-boost": "^0.3.1", "apollo-client": "^2.5.1", @@ -32,8 +33,8 @@ "graphql-tag": "^2.10.1", "isomorphic-unfetch": "^3.0.0", "next": "^8.0.3", - "prop-types": "^15.7.2", "next-routes": "^1.4.2", + "prop-types": "^15.7.2", "react": "^16.8.6", "react-apollo": "2.4.0", "react-apollo-hooks": "^0.4.4", diff --git a/demo-projects/meetup/server.js b/demo-projects/meetup/server.js index 8b458caf9d4..c007d4f782d 100644 --- a/demo-projects/meetup/server.js +++ b/demo-projects/meetup/server.js @@ -1,23 +1,15 @@ require('dotenv').config(); const keystone = require('@keystone-alpha/core'); -const { Wysiwyg } = require('@keystone-alpha/fields-wysiwyg-tinymce'); -const next = require('next'); +const express = require('express'); const initialData = require('./initialData'); -const routes = require('./routes'); const port = process.env.PORT || 3000; const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/keystonejs-meetup'; -const nextApp = next({ - dir: 'site', - distDir: 'build', - dev: process.env.NODE_ENV !== 'production', -}); -const handler = routes.getRequestHandler(nextApp); - -Promise.all([keystone.prepare({ port }), nextApp.prepare()]) - .then(async ([{ server, keystone: keystoneApp }]) => { +keystone + .prepare({ port, dev: process.env.NODE_ENV !== 'production' }) + .then(async ({ middlewares, keystone: keystoneApp }) => { await keystoneApp.connect(mongoUri); // Initialise some data. @@ -30,9 +22,13 @@ Promise.all([keystone.prepare({ port }), nextApp.prepare()]) await keystoneApp.createItems(initialData); } - Wysiwyg.bindStaticMiddleware(server); - server.app.use(handler); - await server.start(); + const app = express(); + + app.use(middlewares); + + app.listen(port, error => { + if (error) throw error; + }); }) .catch(error => { console.error(error); diff --git a/demo-projects/todo/index.js b/demo-projects/todo/index.js index f3484899feb..9535cb97a21 100644 --- a/demo-projects/todo/index.js +++ b/demo-projects/todo/index.js @@ -2,6 +2,7 @@ const { Keystone } = require('@keystone-alpha/keystone'); const { AdminUI } = require('@keystone-alpha/admin-ui'); const { MongooseAdapter } = require('@keystone-alpha/adapter-mongoose'); const { Text } = require('@keystone-alpha/fields'); +const StaticServer = require('@keystone-alpha/server-static'); const keystone = new Keystone({ name: 'Keystone To-Do List', @@ -20,5 +21,5 @@ const admin = new AdminUI(keystone); module.exports = { keystone, - admin, + servers: [new StaticServer({ route: '/', path: 'public' }), admin], }; diff --git a/demo-projects/todo/package.json b/demo-projects/todo/package.json index 930b1ea7e66..9f03fb5a7be 100644 --- a/demo-projects/todo/package.json +++ b/demo-projects/todo/package.json @@ -9,13 +9,14 @@ "node": ">=8.4.0" }, "scripts": { - "start": "DISABLE_LOGGING=true node server.js" + "start": "NODE_ENV=development DISABLE_LOGGING=true keystone" }, "dependencies": { "@keystone-alpha/adapter-mongoose": "^2.0.0", "@keystone-alpha/admin-ui": "^3.2.1", "@keystone-alpha/core": "^2.0.4", "@keystone-alpha/fields": "^6.1.1", - "@keystone-alpha/keystone": "^4.0.0" + "@keystone-alpha/keystone": "^4.0.0", + "@keystone-alpha/server-static": "^0.0.0" } -} \ No newline at end of file +} diff --git a/demo-projects/todo/server.js b/demo-projects/todo/server.js deleted file mode 100644 index 08b01d0b1c8..00000000000 --- a/demo-projects/todo/server.js +++ /dev/null @@ -1,22 +0,0 @@ -const keystone = require('@keystone-alpha/core'); -const path = require('path'); -const PORT = process.env.PORT || 3000; - -keystone - .prepare({ - entryFile: 'index.js', - port: PORT, - }) - .then(async ({ server, keystone: keystoneApp }) => { - await keystoneApp.connect(process.env.MONGODB_URI); - - server.app.use(server.express.static(path.join(__dirname, 'public'))); - return server.start(); - }) - .then(({ port }) => { - console.log(`Listening on port ${port}`); - }) - .catch(error => { - console.error(error); - process.exit(1); - }); diff --git a/package.json b/package.json index ae659d7a162..631c195cc82 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "cookie": "^0.3.1", "cookie-signature": "^1.1.0", "cors": "^2.8.4", + "cpy": "^7.2.0", "cross-fetch": "^2.2.0", "cuid": "^2.1.6", "dataloader": "^1.4.0", @@ -171,7 +172,6 @@ "mongoose": "^5.5.2", "ms": "^2.1.1", "next": "^8.0.3", - "next-apollo": "^2.0.9", "next-routes": "^1.4.2", "node-fetch": "^2.3.0", "object-hash": "^1.3.1", diff --git a/packages/admin-ui/server/AdminUI.js b/packages/admin-ui/server/AdminUI.js index 6a18a4a4161..918ed1a3cdc 100644 --- a/packages/admin-ui/server/AdminUI.js +++ b/packages/admin-ui/server/AdminUI.js @@ -15,22 +15,34 @@ const pkgInfo = require('../package.json'); const getWebpackConfig = require('./getWebpackConfig'); module.exports = class AdminUI { - constructor(keystone, config = {}) { - this.keystone = keystone; - - if (config.adminPath === '/') { + constructor( + keystone, + { + adminPath = '/admin', + apiPath = '/admin/api', + graphiqlPath = '/admin/graphiql', + authStrategy, + pages, + disableDefaultRoute = false, + } = {} + ) { + if (adminPath === '/') { throw new Error("Admin path cannot be the root path. Try; '/admin'"); } - this.adminPath = config.adminPath || '/admin'; - const { authStrategy } = config; if (authStrategy && authStrategy.authType !== 'password') { throw new Error('Keystone 5 Admin currently only supports the `PasswordAuthStrategy`'); } + + this.keystone = keystone; + this.adminPath = adminPath; this.authStrategy = authStrategy; + this.pages = pages; + this.apiPath = apiPath; + this.graphiqlPath = graphiqlPath; + this.disableDefaultRoute = disableDefaultRoute; - this.config = { - ...config, + this.routes = { signinPath: `${this.adminPath}/signin`, signoutPath: `${this.adminPath}/signout`, sessionPath: `${this.adminPath}/session`, @@ -41,16 +53,14 @@ module.exports = class AdminUI { return { adminPath: this.adminPath, authList: this.authStrategy ? this.authStrategy.listKey : null, - pages: this.config.pages, - sessionPath: this.config.sessionPath, - signinPath: this.config.signinPath, - signoutPath: this.config.signoutPath, + pages: this.pages, + ...this.routes, withAuth: !!this.authStrategy, }; } createSessionMiddleware() { - const { signinPath, signoutPath, sessionPath } = this.config; + const { signinPath, signoutPath, sessionPath } = this.routes; // This session allows the user to authenticate as part of the 'admin' audience. // This isn't used by anything just yet. In the near future we will set up the admin-ui // application and api to be non-public. @@ -62,10 +72,12 @@ module.exports = class AdminUI { ); } - staticBuild({ distDir, apiPath, graphiqlPath }) { + build({ distDir }) { + console.log('Building Admin UI!'); + const builtAdminRoot = path.join(distDir, 'admin'); - const adminMeta = this.getAdminUIMeta({ apiPath, graphiqlPath }); + const adminMeta = this.getAdminUIMeta(); const compilers = []; @@ -106,18 +118,26 @@ module.exports = class AdminUI { ); } - getAdminUIMeta({ apiPath, graphiqlPath }) { + getAdminUIMeta() { const { adminPath } = this; return { adminPath, - apiPath, - graphiqlPath, + apiPath: this.apiPath, + graphiqlPath: this.graphiqlPath, ...this.getAdminMeta(), ...this.keystone.getAdminMeta(), }; } + prepareMiddleware({ port, distDir, dev }) { + if (dev) { + return this.createDevMiddleware({ distDir, port }); + } else { + return this.createProdMiddleware({ distDir }); + } + } + createProdMiddleware({ distDir }) { const app = express.Router(); @@ -156,14 +176,23 @@ module.exports = class AdminUI { } const _app = express.Router(); + if (this.authStrategy) { _app.use(this.createSessionMiddleware()); } + _app.use(this.adminPath, app); + + if (!this.disableDefaultRoute) { + // Attach this last onto the root so the `this.adminPath` can overwrite it + // if necessary + _app.get('/', (req, res) => res.sendFile(path.resolve(__dirname, './default.html'))); + } + return _app; } - createDevMiddleware({ apiPath, graphiqlPath, port }) { + createDevMiddleware({ port }) { const app = express(); const { adminPath } = this; @@ -188,7 +217,7 @@ module.exports = class AdminUI { // add the webpack dev middleware - let adminMeta = this.getAdminUIMeta({ apiPath, graphiqlPath }); + let adminMeta = this.getAdminUIMeta(); const webpackMiddlewareConfig = { publicPath: adminPath, @@ -231,6 +260,13 @@ module.exports = class AdminUI { app.use(secureMiddleware); app.use(secureHotMiddleware); } + + if (!this.disableDefaultRoute) { + // Attach this last onto the root so the `adminPath` can overwrite it if + // necessary + app.get('/', (req, res) => res.sendFile(path.resolve(__dirname, './default.html'))); + } + // handle errors // eslint-disable-next-line no-unused-vars app.use(function(err, req, res, next) { diff --git a/packages/server/lib/default.html b/packages/admin-ui/server/default.html similarity index 100% rename from packages/server/lib/default.html rename to packages/admin-ui/server/default.html diff --git a/packages/core/lib/core.js b/packages/core/lib/core.js index dd00d27d850..8a0d70a2516 100644 --- a/packages/core/lib/core.js +++ b/packages/core/lib/core.js @@ -1,6 +1,4 @@ -const { WebServer } = require('@keystone-alpha/server'); -const endent = require('endent'); -const pick = require('lodash.pick'); +const GraphQLServer = require('@keystone-alpha/server-graphql'); const path = require('path'); const DEFAULT_PORT = 3000; @@ -8,77 +6,50 @@ const DEFAULT_ENTRY = 'index.js'; const DEFAULT_SERVER = 'server.js'; const DEFAULT_DIST_DIR = 'dist'; -function cleanServerConfig(config) { - return pick(config, [ - 'cookieSecret', - 'sessionStore', - 'pinoOptions', - 'cors', - 'apiPath', - 'graphiqlPath', - 'apollo', - ]); -} - module.exports = { DEFAULT_PORT, DEFAULT_ENTRY, DEFAULT_SERVER, DEFAULT_DIST_DIR, - prepare: ({ + prepare: async ({ port = DEFAULT_PORT, entryFile = DEFAULT_ENTRY, - serverConfig, + dev = false, distDir, _cwd = process.cwd(), - } = {}) => - new Promise((resolve, reject) => { - let appEntry; - try { - appEntry = require(path.resolve(_cwd, entryFile)); - } catch (error) { - return reject(error); - } - - if (!appEntry.keystone) { - return reject(new Error(`No 'keystone' export found in ${entryFile}`)); - } - - // Enforce moving server config closer to where the server is... configured, - // when using a custom server with keystone - if (appEntry.serverConfig && serverConfig) { - return reject( - new Error(endent` - Ambiguous 'serverConfig' detected. - When using a custom server, you should move 'serverConfig' from your index.js export to your 'server.js' file: - > keystone.prepre({ serverConfig: { ... } }) - `) - ); - } - - const resolvedConfig = appEntry.serverConfig || serverConfig; - - if (resolvedConfig && typeof resolvedConfig !== 'object') { - return reject( - new Error( - `'serverConfig' must be an object. Check ${ - appEntry.serverConfig ? entryFile : 'your custom server' - }.` - ) - ); - } - - const cleanedServerConfig = cleanServerConfig(resolvedConfig || {}); - - const server = new WebServer(appEntry.keystone, { - // Set all the other options - ...cleanedServerConfig, - // Force the admin & port - ...(appEntry.admin ? { adminUI: appEntry.admin } : {}), - port, - distDir: distDir || appEntry.distDir || DEFAULT_DIST_DIR, - }); - - return resolve({ server, keystone: appEntry.keystone }); - }), + } = {}) => { + const appEntry = require(path.resolve(_cwd, entryFile)); + + if (!appEntry.keystone) { + throw new Error(`No 'keystone' export found in ${entryFile}`); + } + + const servers = appEntry.servers || []; + + // Inject graphQL server if the user hasn't specified it + if (!servers.find(server => server.constructor === GraphQLServer)) { + servers.unshift(new GraphQLServer()); + } + + const middlewares = await Promise.all( + [ + // Inject any field middlewares (eg; WYSIWIG's static assets) + // We do this first to avoid it conflicting with any catch-all routes the + // user may have specified + ...appEntry.keystone.registeredTypes, + ...appEntry.servers, + ] + .filter(({ prepareMiddleware }) => !!prepareMiddleware) + .map(server => + server.prepareMiddleware({ + keystone: appEntry.keystone, + port, + dev, + distDir: distDir || appEntry.distDir || DEFAULT_DIST_DIR, + }) + ) + ); + + return { middlewares, keystone: appEntry.keystone }; + }, }; diff --git a/packages/core/package.json b/packages/core/package.json index 01445dfbde7..c37f97d4b5a 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -8,7 +8,7 @@ "node": ">=8.4.0" }, "dependencies": { - "@keystone-alpha/server": "^5.0.0", + "@keystone-alpha/server-graphql": "^5.0.0", "endent": "^1.3.0", "lodash.pick": "^4.4.0" }, @@ -16,4 +16,4 @@ "p-is-promise": "^2.1.0", "tmp": "^0.1.0" } -} \ No newline at end of file +} diff --git a/packages/core/tests/index.test.js b/packages/core/tests/index.test.js index dc8b1cebdcd..94bfaeeddf9 100644 --- a/packages/core/tests/index.test.js +++ b/packages/core/tests/index.test.js @@ -78,7 +78,7 @@ describe('@keystone-alpha/core/index.js', () => { jest.restoreAllMocks(); // See: https://twitter.com/JessTelford/status/1102801062489018369 jest.resetModules(); - jest.dontMock('@keystone-alpha/server'); + jest.dontMock('@keystone-alpha/server-graphql'); }); test('Cannot pass both entryFile#serverConfig and .serverConfig', () => { @@ -116,9 +116,9 @@ describe('@keystone-alpha/core/index.js', () => { // here, and replace the implementation on a per-test basis const mockServerClass = jest.fn(() => {}); jest.resetModules(); - jest.doMock('@keystone-alpha/server', () => ({ + jest.doMock('@keystone-alpha/server-graphql', () => ({ // Mock the class to do nothing - WebServer: mockServerClass, + GraphQLServer: mockServerClass, })); const entryFileObj = tmp.fileSync({ postfix: '.js' }); @@ -135,9 +135,9 @@ describe('@keystone-alpha/core/index.js', () => { // here, and replace the implementation on a per-test basis const mockServerClass = jest.fn(() => {}); jest.resetModules(); - jest.doMock('@keystone-alpha/server', () => ({ + jest.doMock('@keystone-alpha/server-graphql', () => ({ // Mock the class to do nothing - WebServer: mockServerClass, + GraphQLServer: mockServerClass, })); const entryFileObj = tmp.fileSync({ postfix: '.js' }); @@ -161,19 +161,19 @@ describe('@keystone-alpha/core/index.js', () => { // here, and replace the implementation on a per-test basis const mockServerClass = jest.fn(() => {}); jest.resetModules(); - jest.doMock('@keystone-alpha/server', () => ({ + jest.doMock('@keystone-alpha/server-graphql', () => ({ // Mock the class to do nothing - WebServer: mockServerClass, + GraphQLServer: mockServerClass, })); const entryFileObj = tmp.fileSync({ postfix: '.js' }); fs.writeFileSync( entryFileObj.fd, endent` - module.exports = { - keystone: { auth: {} }, - admin: {}, - }; + module.exports = { + keystone: { auth: {} }, + admin: {}, + }; ` ); @@ -212,9 +212,9 @@ describe('@keystone-alpha/core/index.js', () => { const mockServerResult = {}; const mockServerClass = jest.fn(() => mockServerResult); jest.resetModules(); - jest.doMock('@keystone-alpha/server', () => ({ + jest.doMock('@keystone-alpha/server-graphql', () => ({ // Mock the class to do nothing - WebServer: mockServerClass, + GraphQLServer: mockServerClass, })); const entryFileObj = tmp.fileSync({ postfix: '.js' }); @@ -232,16 +232,16 @@ describe('@keystone-alpha/core/index.js', () => { const mockServerResult = {}; const mockServerClass = jest.fn(() => mockServerResult); jest.resetModules(); - jest.doMock('@keystone-alpha/server', () => ({ + jest.doMock('@keystone-alpha/server-graphql', () => ({ // Mock the class to do nothing - WebServer: mockServerClass, + GraphQLServer: mockServerClass, })); const entryFileObj = tmp.fileSync({ postfix: '.js' }); fs.writeFileSync(entryFileObj.fd, `module.exports = { keystone: { auth: {}, hi: 'bye' } }`); const localCore = require('../'); - const { keystone } = await localCore.prepare({ entryFile: entryFileObj.name }); + const { keystone } = await localCore.prepare({ entryFile: entryFileObj.name, dev: true }); expect(keystone).toEqual( expect.objectContaining({ diff --git a/packages/create-keystone-app/templates/todo/README.md b/packages/create-keystone-app/templates/todo/README.md index ccad97090e8..cf1c5601939 100644 --- a/packages/create-keystone-app/templates/todo/README.md +++ b/packages/create-keystone-app/templates/todo/README.md @@ -16,16 +16,6 @@ Then visit: - The KeystoneJS Admin UI at [`http://localhost:3000/admin`](http://localhost:3000/admin) - The GraphQL Playground at [`http://localhost:3000/admin/graphiql`](http://localhost:3000/admin/graphiql) -Set the port with: - -```sh -PORT=5000 yarn start -``` - ## React App The one 'extra' that this project includes is an example React App that consumes the data from Keystone via GraphQl. The app uses React's UMD build and is housed within the `/public` directory. It allows you to see how easy it is to create an app that can use Keystone's GraphQL APIs. - -## Custom Server - -This project includes a _Custom Server_ in `server.js`. It is used to serve the react app only. If you wish to run Keystone as an API & AdminUI only, you can delete `server.js`, the `/public` directory and change the `start` script from running `node server.js` to running `keystone` diff --git a/packages/create-keystone-app/templates/todo/index.js.ejs b/packages/create-keystone-app/templates/todo/index.js.ejs index fa6eaa6ecf6..0535eb3d7c2 100644 --- a/packages/create-keystone-app/templates/todo/index.js.ejs +++ b/packages/create-keystone-app/templates/todo/index.js.ejs @@ -2,6 +2,7 @@ const { Keystone } = require('@keystone-alpha/keystone'); const { AdminUI } = require('@keystone-alpha/admin-ui'); const { MongooseAdapter } = require('@keystone-alpha/adapter-mongoose'); const { Text } = require('@keystone-alpha/fields'); +const StaticServer = require('@keystone-alpha/server-static'); // The core `keystone` instance is always required. // Here you can setup the database adapter, set the name used throughout the @@ -27,7 +28,8 @@ keystone.createList('Todo', { }, }); -// Setup the optional Admin UI +// Setup the optional Admin UI which will generate the Admin UI based on the +// lists and fields you've setup. // If no admin is created, you will still have a fully functioning GraphQL API. const admin = new AdminUI(keystone); @@ -36,7 +38,17 @@ module.exports = { // a database, and associated GraphQL APIs. keystone, - // Optionally export the 'admin' instance, which will generate the Admin UI - // based on the lists and fields you've setup. - admin, + // A running instance of KeystoneJS is comprised of a series of server + // middlewares. + // Using the `servers` export, we can inject our own middlewares into the + // default express app. + servers: [ + // Serve our website from the root route. + new StaticServer({ route: '/', path: 'public' }), + + // Optionally export the 'admin' instance. + // If no admin is exported, you will still have a fully functioning GraphQL + // API. + admin, + ], }; diff --git a/packages/create-keystone-app/templates/todo/package.json.ejs b/packages/create-keystone-app/templates/todo/package.json.ejs index 811d2f4479e..91c729be8a0 100644 --- a/packages/create-keystone-app/templates/todo/package.json.ejs +++ b/packages/create-keystone-app/templates/todo/package.json.ejs @@ -2,14 +2,14 @@ "name": "<%= appName%>", "version": "0.0.0", "scripts": { - "start": "DISABLE_LOGGING=true node server.js" + "start": "NODE_ENV=development DISABLE_LOGGING=true keystone" }, "private": true, "dependencies": { "@keystone-alpha/adapter-mongoose": "latest", "@keystone-alpha/admin-ui": "latest", - "@keystone-alpha/core": "latest", "@keystone-alpha/fields": "latest", - "@keystone-alpha/keystone": "latest" + "@keystone-alpha/keystone": "latest", + "@keystone-alpha/server-static": "latest" } } diff --git a/packages/create-keystone-app/templates/todo/server.js b/packages/create-keystone-app/templates/todo/server.js deleted file mode 100644 index 32605bdb1a1..00000000000 --- a/packages/create-keystone-app/templates/todo/server.js +++ /dev/null @@ -1,27 +0,0 @@ -const keystone = require('@keystone-alpha/core'); -const path = require('path'); -const PORT = process.env.PORT || 3000; - -// Keystone will prepare the Admin UI (if configured in index.js) and GraphQL -// APIs here. -keystone - .prepare({ - entryFile: 'index.js', - port: PORT, - }) - // 'server' is an express instance, to which you can attach your own routes, - // middlewares, etc. - .then(async ({ server, keystone: keystoneApp }) => { - await keystoneApp.connect(); - - // In this project, we attach a single route handler for `/public` to serve - // our app - server.app.use(server.express.static(path.join(__dirname, 'public'))); - // Finally, we must start the server so we can access the Admin UI and - // GraphQL API - return server.start(); - }) - .catch(error => { - console.error(error); - process.exit(1); - }); diff --git a/packages/fields-wysiwyg-tinymce/package.json b/packages/fields-wysiwyg-tinymce/package.json index 25f8458efca..a2f2b71fa9e 100644 --- a/packages/fields-wysiwyg-tinymce/package.json +++ b/packages/fields-wysiwyg-tinymce/package.json @@ -21,6 +21,9 @@ "react": "^16.8.6", "tinymce": "^5.0.3" }, + "peerDependencies": { + "express": "^4.16.3" + }, "preconstruct": { "entrypoints": [ "views/Field" @@ -28,4 +31,4 @@ }, "main": "dist/fields-wysiwyg-tinymce.cjs.js", "module": "dist/fields-wysiwyg-tinymce.esm.js" -} \ No newline at end of file +} diff --git a/packages/fields-wysiwyg-tinymce/src/index.js b/packages/fields-wysiwyg-tinymce/src/index.js index 7c1833b611a..8770c714fd8 100644 --- a/packages/fields-wysiwyg-tinymce/src/index.js +++ b/packages/fields-wysiwyg-tinymce/src/index.js @@ -1,10 +1,13 @@ +import express from 'express'; import { Text } from '@keystone-alpha/fields'; import { importView } from '@keystone-alpha/build-field-types'; -function bindStaticMiddleware(server) { +function prepareMiddleware() { const tinymce = require.resolve('tinymce'); const tinymcePath = tinymce.substr(0, tinymce.lastIndexOf('/')); - server.app.use('/tinymce-assets', server.express.static(tinymcePath)); + const app = express(); + app.use('/tinymce-assets', express.static(tinymcePath)); + return app; } export let Wysiwyg = { @@ -16,5 +19,5 @@ export let Wysiwyg = { Filter: Text.views.Filter, }, adapters: Text.adapters, - bindStaticMiddleware, + prepareMiddleware, }; diff --git a/packages/keystone/bin/commands/build.js b/packages/keystone/bin/commands/build.js index 318711192fa..b5fa834fa53 100644 --- a/packages/keystone/bin/commands/build.js +++ b/packages/keystone/bin/commands/build.js @@ -21,7 +21,7 @@ module.exports = { exec: async (args, { exeName, _cwd = process.cwd() } = {}) => { process.env.NODE_ENV = 'production'; let entryFile = await getEntryFileFullPath(args, { exeName, _cwd }); - let { admin, distDir = keystone.DEFAULT_DIST_DIR } = require(entryFile); + let { servers, distDir = keystone.DEFAULT_DIST_DIR } = require(entryFile); if (args['--out']) { distDir = args['--out']; @@ -29,13 +29,17 @@ module.exports = { let resolvedDistDir = path.resolve(_cwd, distDir); await fs.remove(resolvedDistDir); - if (admin) { - console.log('Building Admin UI!'); - await admin.staticBuild({ - apiPath: '/admin/api', - distDir: resolvedDistDir, - graphiqlPath: '/admin/graphiql', - }); + if (servers) { + await Promise.all( + servers.map(server => { + return server.build({ + apiPath: '/admin/api', + distDir: resolvedDistDir, + graphiqlPath: '/admin/graphiql', + }); + }) + ); + console.log('Built Admin UI!'); } else { console.log('Nothing to build.'); diff --git a/packages/keystone/bin/utils.js b/packages/keystone/bin/utils.js index 7f37ece1eff..6eabcde4778 100644 --- a/packages/keystone/bin/utils.js +++ b/packages/keystone/bin/utils.js @@ -1,4 +1,5 @@ const keystone = require('@keystone-alpha/core'); +const express = require('express'); const endent = require('endent'); const path = require('path'); @@ -19,12 +20,18 @@ function getEntryFileFullPath(args, { exeName, _cwd }) { function executeDefaultServer(args, entryFile, distDir) { const port = args['--port'] ? args['--port'] : keystone.DEFAULT_PORT; + const app = express(); + return keystone - .prepare({ entryFile, port, distDir }) - .then(async ({ server, keystone: keystoneApp }) => { + .prepare({ entryFile, port, distDir, dev: process.env.NODE_ENV !== 'production' }) + .then(async ({ middlewares, keystone: keystoneApp }) => { await keystoneApp.connect(); - return server.start(); + middlewares.forEach(middleware => app.use(middleware)); + + return new Promise((resolve, reject) => { + app.listen(port, error => (error ? reject(error) : resolve({ port }))); + }); }) .then(() => { console.log(`KeystoneJS ready on port ${port}`); diff --git a/packages/keystone/lib/Keystone/index.js b/packages/keystone/lib/Keystone/index.js index 6ee10ac3f53..e9c717f708a 100644 --- a/packages/keystone/lib/Keystone/index.js +++ b/packages/keystone/lib/Keystone/index.js @@ -43,6 +43,7 @@ module.exports = class Keystone { this.listsArray = []; this.getListByKey = key => this.lists[key]; this._graphQLQuery = {}; + this.registeredTypes = new Set(); if (adapters) { this.adapters = adapters; @@ -75,6 +76,7 @@ module.exports = class Keystone { adapter: adapters[adapterName], defaultAccess: this.defaultAccess, getAuth: () => this.auth[key], + registerType: type => this.registeredTypes.add(type), isAuxList, createAuxList: (auxKey, auxConfig) => { if (isAuxList) { diff --git a/packages/keystone/lib/List/index.js b/packages/keystone/lib/List/index.js index ffafb12d879..7a3d56c33a3 100644 --- a/packages/keystone/lib/List/index.js +++ b/packages/keystone/lib/List/index.js @@ -121,7 +121,16 @@ module.exports = class List { path, adapterConfig = {}, }, - { getListByKey, getGraphQLQuery, adapter, defaultAccess, getAuth, createAuxList, isAuxList } + { + getListByKey, + getGraphQLQuery, + adapter, + defaultAccess, + getAuth, + registerType, + createAuxList, + isAuxList, + } ) { this.key = key; this._fields = fields; @@ -223,13 +232,18 @@ module.exports = class List { if (!graphQLQuery) { return Promise.reject( - new Error('No executable schema is available. Have you setup `@keystone-alpha/server`?') + new Error( + 'No executable schema is available. Have you setup `@keystone-alpha/server-graphql`?' + ) ); } return graphQLQuery(queryString, passThroughContext, variables); }, }; + + // Tell Keystone about all the types we've seen + Object.values(fields).forEach(({ type }) => registerType(type)); } initFields() { diff --git a/packages/keystone/package.json b/packages/keystone/package.json index b9b0e91b678..b77044eb369 100644 --- a/packages/keystone/package.json +++ b/packages/keystone/package.json @@ -20,6 +20,7 @@ "apollo-errors": "^1.9.0", "arg": "^4.1.0", "endent": "^1.3.0", + "express": "^4.16.3", "fast-memoize": "^2.4.0", "fs-extra": "^7.0.0", "globby": "^9.1.0", @@ -35,4 +36,4 @@ "devDependencies": { "tmp": "^0.1.0" } -} \ No newline at end of file +} diff --git a/packages/keystone/tests/List.test.js b/packages/keystone/tests/List.test.js index 709146b7254..e23a5d03abe 100644 --- a/packages/keystone/tests/List.test.js +++ b/packages/keystone/tests/List.test.js @@ -117,6 +117,7 @@ const listExtras = (getAuth = () => true, queryMethod = undefined) => ({ getAuth, defaultAccess: { list: true, field: true }, getGraphQLQuery: () => queryMethod, + registerType: () => {}, }); const setup = (extraConfig, getAuth, queryMethod) => { @@ -1326,6 +1327,7 @@ describe('Maps from Native JS types to Keystone types', () => { adapter, getAuth: () => {}, defaultAccess: { list: true, field: true }, + registerType: () => {}, } ); list.initFields(); diff --git a/packages/server/.npmignore b/packages/server-graphql/.npmignore similarity index 100% rename from packages/server/.npmignore rename to packages/server-graphql/.npmignore diff --git a/packages/server/CHANGELOG.md b/packages/server-graphql/CHANGELOG.md similarity index 99% rename from packages/server/CHANGELOG.md rename to packages/server-graphql/CHANGELOG.md index b043f537e1e..214ed46a164 100644 --- a/packages/server/CHANGELOG.md +++ b/packages/server-graphql/CHANGELOG.md @@ -1,3 +1,5 @@ +# @keystone-alpha/server-graphql + # @keystone-alpha/server ## 5.0.0 diff --git a/packages/server/README.md b/packages/server-graphql/README.md similarity index 100% rename from packages/server/README.md rename to packages/server-graphql/README.md diff --git a/packages/server-graphql/index.js b/packages/server-graphql/index.js new file mode 100644 index 00000000000..1f3de9633c8 --- /dev/null +++ b/packages/server-graphql/index.js @@ -0,0 +1,3 @@ +const GraphQLServer = require('./lib/index'); + +module.exports = GraphQLServer; diff --git a/packages/server/lib/apolloServer.js b/packages/server-graphql/lib/apolloServer.js similarity index 94% rename from packages/server/lib/apolloServer.js rename to packages/server-graphql/lib/apolloServer.js index c0bd4d2ed8c..d79dd4fb8b7 100644 --- a/packages/server/lib/apolloServer.js +++ b/packages/server-graphql/lib/apolloServer.js @@ -139,7 +139,7 @@ const _formatError = error => { } }; -function createApolloServer(keystone, apolloConfig, schemaName) { +function createApolloServer(keystone, apolloConfig, schemaName, dev) { // add the Admin GraphQL API const server = new ApolloServer({ maxFileSize: 200 * 1024 * 1024, @@ -154,7 +154,10 @@ function createApolloServer(keystone, apolloConfig, schemaName) { } : { engine: false, - tracing: process.env.NODE_ENV !== 'production', + // Only enable tracing in dev mode so we can get local debug info, but + // don't bother returning that info on prod when the `engine` is + // disabled. + tracing: dev, }), formatError: _formatError, }); diff --git a/packages/server/lib/devQuery.js b/packages/server-graphql/lib/devQuery.js similarity index 100% rename from packages/server/lib/devQuery.js rename to packages/server-graphql/lib/devQuery.js diff --git a/packages/server/lib/graphql.js b/packages/server-graphql/lib/graphql.js similarity index 100% rename from packages/server/lib/graphql.js rename to packages/server-graphql/lib/graphql.js diff --git a/packages/server/lib/graphqlErrors.js b/packages/server-graphql/lib/graphqlErrors.js similarity index 100% rename from packages/server/lib/graphqlErrors.js rename to packages/server-graphql/lib/graphqlErrors.js diff --git a/packages/server-graphql/lib/index.js b/packages/server-graphql/lib/index.js new file mode 100644 index 00000000000..3a446ff019f --- /dev/null +++ b/packages/server-graphql/lib/index.js @@ -0,0 +1,66 @@ +const createCorsMiddleware = require('cors'); +const falsey = require('falsey'); +const { commonSessionMiddleware } = require('@keystone-alpha/session'); +const createGraphQLMiddleware = require('./graphql'); +const { createApolloServer } = require('./apolloServer'); + +module.exports = class GraphQLServer { + constructor({ + cors = { origin: true, credentials: true }, + cookieSecret = 'qwerty', + apiPath = '/admin/api', + graphiqlPath = '/admin/graphiql', + schemaName = 'admin', + apollo = {}, + sessionStore, + pinoOptions, + } = {}) { + this._apiPath = apiPath; + this._graphiqlPath = graphiqlPath; + this._pinoOptions = pinoOptions; + this._cors = cors; + this._cookieSecret = cookieSecret; + this._sessionStore = sessionStore; + this._apollo = apollo; + this._schemaName = schemaName; + } + + /** + * @return Array + */ + prepareMiddleware({ keystone, port, dev }) { + const middlewares = []; + + if (falsey(process.env.DISABLE_LOGGING)) { + middlewares.push(require('express-pino-logger')(this._pinoOptions)); + } + + if (this._cors) { + middlewares.push(createCorsMiddleware(this._cors)); + } + + if (Object.keys(keystone.auth).length > 0) { + middlewares.push(commonSessionMiddleware(keystone, this._cookieSecret, this._sessionStore)); + } + + const server = createApolloServer(keystone, this._apollo, this._schemaName, dev); + // GraphQL API always exists independent of any adminUI or Session + // settings We currently make the admin UI public. In the future we want + // to be able to restrict this to a limited audience, while setting up a + // separate public API with much stricter access control. + middlewares.push( + createGraphQLMiddleware( + server, + { apiPath: this._apiPath, graphiqlPath: this._graphiqlPath, port }, + { isPublic: true } + ) + ); + + return middlewares; + } + + /** + * @param Options { distDir } + */ + build() {} +}; diff --git a/packages/server/package.json b/packages/server-graphql/package.json similarity index 80% rename from packages/server/package.json rename to packages/server-graphql/package.json index 2d27583a834..e111bc00479 100644 --- a/packages/server/package.json +++ b/packages/server-graphql/package.json @@ -1,6 +1,6 @@ { - "name": "@keystone-alpha/server", - "description": "Internal @keystone-alpha API & Admin UI server. For Custom Servers, see: @keystone-alpha/core", + "name": "@keystone-alpha/server-graphql", + "description": "KeystoneJS GraphQL Server. For more Custom Servers, see: @keystone-alpha/core", "version": "5.0.0", "author": "The KeystoneJS Development Team", "license": "MIT", @@ -18,7 +18,6 @@ "cors": "^2.8.4", "cuid": "^2.1.6", "ensure-error": "^1.0.0", - "express": "^4.16.3", "express-pino-logger": "^4.0.0", "falsey": "^1.0.0", "graphql": "^14.0.2", @@ -29,5 +28,8 @@ "serialize-error": "^3.0.0", "stack-utils": "^1.0.2", "terminal-link": "^1.3.0" + }, + "peerDependencies": { + "express": "^4.16.3" } -} \ No newline at end of file +} diff --git a/packages/server-next/.npmignore b/packages/server-next/.npmignore new file mode 100644 index 00000000000..851b108d115 --- /dev/null +++ b/packages/server-next/.npmignore @@ -0,0 +1,2 @@ +**/*.md +**/*.test.js diff --git a/packages/server-next/CHANGELOG.md b/packages/server-next/CHANGELOG.md new file mode 100644 index 00000000000..d1eecfa1472 --- /dev/null +++ b/packages/server-next/CHANGELOG.md @@ -0,0 +1 @@ +# @keystone-alpha/server-next diff --git a/packages/server-next/README.md b/packages/server-next/README.md new file mode 100644 index 00000000000..defae15add3 --- /dev/null +++ b/packages/server-next/README.md @@ -0,0 +1,10 @@ +--- +section: packages +title: KeystoneJS Next.js Server +--- + +# KeystoneJS Next.js Server + +```DOCS_TODO +TODO +``` diff --git a/packages/server-next/index.js b/packages/server-next/index.js new file mode 100644 index 00000000000..22a05949e3e --- /dev/null +++ b/packages/server-next/index.js @@ -0,0 +1,24 @@ +const next = require('next'); +const build = require('next/dist/build'); + +module.exports = class NextServer { + constructor({ dir, nextRoutes }) { + this._dir = dir; + this._nextRoutes = nextRoutes; + } + + async prepareMiddleware({ dev, distDir }) { + const nextApp = next({ distDir, dir: this._dir, dev }); + await nextApp.prepare(); + // Add support for fridays/next-routes npm module + if (this._nextRoutes) { + return this._nextRoutes.getRequestHandler(nextApp); + } else { + return nextApp.getRequestHandler(); + } + } + + async build() { + return build(this._dir); + } +}; diff --git a/packages/server-next/package.json b/packages/server-next/package.json new file mode 100644 index 00000000000..b088c5bb7a9 --- /dev/null +++ b/packages/server-next/package.json @@ -0,0 +1,13 @@ +{ + "name": "@keystone-alpha/server-next", + "description": "KeystoneJS Next.js Server. For more Custom Servers, see: @keystone-alpha/core", + "version": "0.0.0", + "author": "The KeystoneJS Development Team", + "license": "MIT", + "engines": { + "node": ">=8.4.0" + }, + "dependencies": { + "next": "^8.0.3" + } +} \ No newline at end of file diff --git a/packages/server-static/.npmignore b/packages/server-static/.npmignore new file mode 100644 index 00000000000..851b108d115 --- /dev/null +++ b/packages/server-static/.npmignore @@ -0,0 +1,2 @@ +**/*.md +**/*.test.js diff --git a/packages/server-static/CHANGELOG.md b/packages/server-static/CHANGELOG.md new file mode 100644 index 00000000000..3bd1e69ac8d --- /dev/null +++ b/packages/server-static/CHANGELOG.md @@ -0,0 +1 @@ +# @keystone-alpha/server-static diff --git a/packages/server-static/README.md b/packages/server-static/README.md new file mode 100644 index 00000000000..dd67a226278 --- /dev/null +++ b/packages/server-static/README.md @@ -0,0 +1,10 @@ +--- +section: packages +title: KeystoneJS Static File Server +--- + +# KeystoneJS Static File Server + +```DOCS_TODO +TODO +``` diff --git a/packages/server-static/index.js b/packages/server-static/index.js new file mode 100644 index 00000000000..8d3e8fa3f90 --- /dev/null +++ b/packages/server-static/index.js @@ -0,0 +1,26 @@ +const express = require('express'); +const pathModule = require('path'); +const cpy = require('cpy'); + +module.exports = class StaticServer { + constructor({ route, path }) { + this._route = route; + this._path = path; + } + + prepareMiddleware({ dev, distDir = '.' } = {}) { + const app = express(); + let path = this._path; + if (!dev) { + path = pathModule.join(distDir, this._path); + } + app.use(this._route, express.static(pathModule.resolve(path))); + return app; + } + + build({ distDir }) { + const source = pathModule.resolve(this._path); + const destination = pathModule.resolve(pathModule.join(distDir, this._path)); + return cpy(source, destination); + } +}; diff --git a/packages/server-static/package.json b/packages/server-static/package.json new file mode 100644 index 00000000000..9b09d7db0fb --- /dev/null +++ b/packages/server-static/package.json @@ -0,0 +1,16 @@ +{ + "name": "@keystone-alpha/server-static", + "description": "KeystoneJS Static File Server. For more Custom Servers, see: @keystone-alpha/core", + "version": "0.0.0", + "author": "The KeystoneJS Development Team", + "license": "MIT", + "engines": { + "node": ">=8.4.0" + }, + "peerDependencies": { + "express": "^4.16.3" + }, + "dependencies": { + "cpy": "^7.2.0" + } +} \ No newline at end of file diff --git a/packages/server/index.js b/packages/server/index.js deleted file mode 100644 index dfbb98b6c73..00000000000 --- a/packages/server/index.js +++ /dev/null @@ -1,7 +0,0 @@ -const WebServer = require('./lib/index'); -const { createApolloServer } = require('./lib/apolloServer'); - -module.exports = { - WebServer, - createApolloServer, -}; diff --git a/packages/server/lib/index.js b/packages/server/lib/index.js deleted file mode 100644 index 37586003085..00000000000 --- a/packages/server/lib/index.js +++ /dev/null @@ -1,71 +0,0 @@ -const express = require('express'); -const corsMiddleware = require('cors'); -const path = require('path'); -const falsey = require('falsey'); -const { commonSessionMiddleware } = require('@keystone-alpha/session'); -const createGraphQLMiddleware = require('./graphql'); -const { createApolloServer } = require('./apolloServer'); - -module.exports = class WebServer { - constructor( - keystone, - { - port, - adminUI, - cors = { origin: true, credentials: true }, - apollo, - sessionStore, - cookieSecret = 'qwerty', - apiPath = '/admin/api', - graphiqlPath = '/admin/graphiql', - pinoOptions, - distDir, - } - ) { - this.keystone = keystone; - this.express = express; - this.port = port || process.env.PORT || 3000; - this.app = express(); - - if (falsey(process.env.DISABLE_LOGGING)) { - this.app.use(require('express-pino-logger')(pinoOptions)); - } - - if (cors) { - this.app.use(corsMiddleware(cors)); - } - - if (Object.keys(keystone.auth).length > 0) { - this.app.use(commonSessionMiddleware(keystone, cookieSecret, sessionStore)); - } - - const server = createApolloServer(keystone, apollo, 'admin'); - - // GraphQL API always exists independent of any adminUI or Session settings - // We currently make the admin UI public. In the future we want to be able - // to restrict this to a limited audience, while setting up a separate - // public API with much stricter access control. - this.app.use( - createGraphQLMiddleware(server, { apiPath, graphiqlPath, port }, { isPublic: true }) - ); - - if (adminUI) { - if (process.env.NODE_ENV === 'production') { - this.app.use(adminUI.createProdMiddleware({ apiPath, graphiqlPath, port, distDir })); - } else { - // This must be last as it's the "catch all" which falls into Webpack to - // serve the Admin UI. - this.app.use(adminUI.createDevMiddleware({ apiPath, graphiqlPath, port })); - } - } - } - - async start() { - const { app, port } = this; - - return new Promise((resolve, reject) => { - app.get('/', (req, res) => res.sendFile(path.resolve(__dirname, './default.html'))); - app.listen(port, error => (error ? reject(error) : resolve({ port }))); - }); - } -}; diff --git a/packages/test-utils/lib/test-utils.js b/packages/test-utils/lib/test-utils.js index d8514af9b67..d38b3ca3799 100644 --- a/packages/test-utils/lib/test-utils.js +++ b/packages/test-utils/lib/test-utils.js @@ -1,8 +1,8 @@ const MongoDBMemoryServer = require('mongodb-memory-server').default; const pFinally = require('p-finally'); const url = require('url'); -const { createApolloServer } = require('@keystone-alpha/server'); const { Keystone } = require('@keystone-alpha/keystone'); +const GraphQLServer = require('@keystone-alpha/server-graphql'); const { KnexAdapter } = require('@keystone-alpha/adapter-knex'); const { MongooseAdapter } = require('@keystone-alpha/adapter-mongoose'); @@ -18,7 +18,8 @@ function setupServer({ name, adapterName, createLists = () => {} }) { createLists(keystone); - createApolloServer(keystone, {}, SCHEMA_NAME); + // Has the side-effect of registering the schema with the keystone object + new GraphQLServer({ schemaName: SCHEMA_NAME }).prepareMiddleware({ keystone, dev: true }); return { keystone }; } @@ -82,7 +83,7 @@ function getUpdate(keystone) { function keystoneMongoTest(setupKeystoneFn, testFn) { return async function() { - const setup = setupKeystoneFn('mongoose'); + const setup = await setupKeystoneFn('mongoose'); const { keystone } = setup; const { mongoUri, dbName } = await getMongoMemoryServerConfig(); @@ -104,7 +105,7 @@ function keystoneMongoTest(setupKeystoneFn, testFn) { function keystoneKnexTest(setupKeystoneFn, testFn) { return async function() { - const setup = setupKeystoneFn('knex'); + const setup = await setupKeystoneFn('knex'); const { keystone } = setup; await keystone.connect(); diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 59d72d21daf..7d74462d2b9 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -11,8 +11,8 @@ "@keystone-alpha/adapter-knex": "^1.0.7", "@keystone-alpha/adapter-mongoose": "^2.0.0", "@keystone-alpha/keystone": "^4.0.0", - "@keystone-alpha/server": "^5.0.0", + "@keystone-alpha/server-graphql": "^5.0.0", "mongodb-memory-server": "^5.0.4", "p-finally": "^1.0.0" } -} \ No newline at end of file +} diff --git a/test-projects/access-control/index.js b/test-projects/access-control/index.js index a55cb282c20..07ab49ee24f 100644 --- a/test-projects/access-control/index.js +++ b/test-projects/access-control/index.js @@ -145,5 +145,5 @@ const admin = new AdminUI(keystone, { module.exports = { keystone, - admin, + servers: [admin], }; diff --git a/test-projects/access-control/package.json b/test-projects/access-control/package.json index 566019513b0..25d2f195486 100644 --- a/test-projects/access-control/package.json +++ b/test-projects/access-control/package.json @@ -23,7 +23,8 @@ "@keystone-alpha/core": "^2.0.4", "@keystone-alpha/fields": "^6.1.1", "@keystone-alpha/keystone": "^4.0.0", - "@keystone-alpha/utils": "^3.0.0" + "@keystone-alpha/utils": "^3.0.0", + "express": "^4.16.3" }, "devDependencies": { "cypress": "^3.2.0", diff --git a/test-projects/access-control/server.js b/test-projects/access-control/server.js index c4ee115c46d..72ebc07ec23 100644 --- a/test-projects/access-control/server.js +++ b/test-projects/access-control/server.js @@ -1,12 +1,12 @@ +const express = require('express'); const keystone = require('@keystone-alpha/core'); const { port } = require('./config'); - const initialData = require('./data'); keystone - .prepare({ port }) - .then(async ({ server, keystone: keystoneApp }) => { + .prepare({ port, dev: process.env.NODE_ENV !== 'production' }) + .then(async ({ middlewares, keystone: keystoneApp }) => { await keystoneApp.connect(); // Initialise some data. @@ -19,14 +19,21 @@ keystone await keystoneApp.createItems(initialData); } - server.app.get('/reset-db', async (req, res) => { + const app = express(); + + app.get('/reset-db', async (req, res) => { Object.values(keystoneApp.adapters).forEach(async adapter => { await adapter.dropDatabase(); }); await keystoneApp.createItems(initialData); res.redirect('/admin'); }); - await server.start(); + + app.use(middlewares); + + app.listen(port, error => { + if (error) throw error; + }); }) .catch(error => { console.error(error); diff --git a/test-projects/basic/index.js b/test-projects/basic/index.js index dce3f4d9069..9f151891e88 100644 --- a/test-projects/basic/index.js +++ b/test-projects/basic/index.js @@ -17,6 +17,7 @@ const { Decimal, } = require('@keystone-alpha/fields'); const { CloudinaryAdapter, LocalFileAdapter } = require('@keystone-alpha/file-adapters'); +const StaticServer = require('@keystone-alpha/server-static'); const { staticRoute, staticPath, cloudinary } = require('./config'); @@ -157,5 +158,5 @@ const admin = new AdminUI(keystone); module.exports = { keystone, - admin, + servers: [admin, new StaticServer({ route: staticRoute, path: staticPath })], }; diff --git a/test-projects/basic/package.json b/test-projects/basic/package.json index 2980254a3df..5a8b97f452c 100644 --- a/test-projects/basic/package.json +++ b/test-projects/basic/package.json @@ -25,7 +25,9 @@ "@keystone-alpha/fields": "^6.1.1", "@keystone-alpha/file-adapters": "^1.0.2", "@keystone-alpha/keystone": "^4.0.0", + "@keystone-alpha/server-static": "^0.0.0", "date-fns": "^1.30.1", + "express": "^4.16.3", "react": "^16.8.6" }, "devDependencies": { diff --git a/test-projects/basic/server.js b/test-projects/basic/server.js index c6522404421..7d74ffbbc91 100644 --- a/test-projects/basic/server.js +++ b/test-projects/basic/server.js @@ -1,12 +1,13 @@ +const express = require('express'); const keystone = require('@keystone-alpha/core'); -const { port, staticRoute, staticPath } = require('./config'); +const { port } = require('./config'); const initialData = require('./data'); keystone - .prepare({ port }) - .then(async ({ server, keystone: keystoneApp }) => { + .prepare({ port, dev: process.env.NODE_ENV !== 'production' }) + .then(async ({ middlewares, keystone: keystoneApp }) => { await keystoneApp.connect(process.env.MONGODB_URI); // Initialise some data. @@ -19,15 +20,21 @@ keystone await keystoneApp.createItems(initialData); } - server.app.get('/reset-db', async (req, res) => { + const app = express(); + + app.get('/reset-db', async (req, res) => { Object.values(keystoneApp.adapters).forEach(async adapter => { await adapter.dropDatabase(); }); await keystoneApp.createItems(initialData); res.redirect('/admin'); }); - server.app.use(staticRoute, server.express.static(staticPath)); - await server.start(); + + app.use(middlewares); + + app.listen(port, error => { + if (error) throw error; + }); }) .catch(error => { console.error(error); diff --git a/test-projects/login/index.js b/test-projects/login/index.js index 2ce7df3c4fb..23c1da5381e 100644 --- a/test-projects/login/index.js +++ b/test-projects/login/index.js @@ -1,8 +1,9 @@ const { AdminUI } = require('@keystone-alpha/admin-ui'); const { Keystone, PasswordAuthStrategy } = require('@keystone-alpha/keystone'); const { Text, Password, Relationship } = require('@keystone-alpha/fields'); - const { MongooseAdapter } = require('@keystone-alpha/adapter-mongoose'); +const StaticServer = require('@keystone-alpha/server-static'); +const { staticRoute, staticPath } = require('./config'); const keystone = new Keystone({ name: 'Cypress Test Project For Login', @@ -43,5 +44,5 @@ const admin = new AdminUI(keystone, { module.exports = { keystone, - admin, + servers: [admin, new StaticServer({ route: staticRoute, path: staticPath })], }; diff --git a/test-projects/login/package.json b/test-projects/login/package.json index 410854a2952..3c393be6e37 100644 --- a/test-projects/login/package.json +++ b/test-projects/login/package.json @@ -23,7 +23,10 @@ "@keystone-alpha/core": "^2.0.4", "@keystone-alpha/fields": "^6.1.1", "@keystone-alpha/keystone": "^4.0.0", - "body-parser": "^1.18.2" + "@keystone-alpha/server-static": "^0.0.0", + "@keystone-alpha/session": "^1.0.2", + "body-parser": "^1.18.2", + "express": "^4.16.3" }, "devDependencies": { "cypress": "^3.2.0", diff --git a/test-projects/login/server.js b/test-projects/login/server.js index 583ad46f868..6c0c3e699d1 100644 --- a/test-projects/login/server.js +++ b/test-projects/login/server.js @@ -1,12 +1,13 @@ +const express = require('express'); const keystone = require('@keystone-alpha/core'); -const { port, staticRoute, staticPath } = require('./config'); +const { port } = require('./config'); const initialData = require('./data'); keystone - .prepare({ port }) - .then(async ({ server, keystone: keystoneApp }) => { + .prepare({ port, dev: process.env.NODE_ENV !== 'production' }) + .then(async ({ middlewares, keystone: keystoneApp }) => { await keystoneApp.connect(); // Initialise some data. @@ -19,7 +20,9 @@ keystone await keystoneApp.createItems(initialData); } - server.app.get('/reset-db', async (req, res) => { + const app = express(); + + app.get('/reset-db', async (req, res) => { Object.values(keystoneApp.adapters).forEach(async adapter => { await adapter.dropDatabase(); }); @@ -27,9 +30,11 @@ keystone res.redirect('/admin'); }); - server.app.use(staticRoute, server.express.static(staticPath)); + app.use(middlewares); - await server.start(); + app.listen(port, error => { + if (error) throw error; + }); }) .catch(error => { console.error(error); diff --git a/test-projects/social-login/auth/setupAuthRoutes.js b/test-projects/social-login/auth/setupAuthRoutes.js index 172a6e77745..087790abc50 100644 --- a/test-projects/social-login/auth/setupAuthRoutes.js +++ b/test-projects/social-login/auth/setupAuthRoutes.js @@ -1,15 +1,16 @@ const { startAuthedSession } = require('@keystone-alpha/session'); +const express = require('express'); module.exports = ({ strategy, - server, + app, authRoot = '/auth', successRedirect = '/api/session', failureRedirect = '/', }) => { const basePath = `${authRoot}/${strategy.authType}`; // Hit this route to start the auth process for service - server.app.get( + app.get( basePath, strategy.loginMiddleware({ // If not set, will just call `next()` @@ -22,7 +23,7 @@ module.exports = ({ ); // oAuth service will redirect the user to this URL after approval. - server.app.get( + app.get( `${basePath}/callback`, strategy.authenticateMiddleware({ verified: async (item, { list }, req, res) => { @@ -52,7 +53,7 @@ module.exports = ({ // Sample page to collect a name, submits to the completion step which will // create a user - server.app.get(`${basePath}/create`, (req, res) => { + app.get(`${basePath}/create`, (req, res) => { // Redirect if we're already signed in if (req.user) { return res.redirect(successRedirect); @@ -72,9 +73,9 @@ module.exports = ({ }); // Gets the name and creates a new User - server.app.post( + app.post( `${basePath}/complete`, - server.express.urlencoded({ extended: true }), + express.urlencoded({ extended: true }), async (req, res, next) => { // Redirect if we're already signed in if (req.user) { diff --git a/test-projects/social-login/index.js b/test-projects/social-login/index.js index dff065beac7..8c3d2923173 100644 --- a/test-projects/social-login/index.js +++ b/test-projects/social-login/index.js @@ -10,6 +10,7 @@ const { CloudinaryImage, } = require('@keystone-alpha/fields'); const { CloudinaryAdapter, LocalFileAdapter } = require('@keystone-alpha/file-adapters'); +const StaticServer = require('@keystone-alpha/server-static'); const { staticRoute, staticPath, cloudinary } = require('./config'); @@ -155,5 +156,5 @@ const admin = new AdminUI(keystone, { authStrategy: DISABLE_AUTH ? undefined : a module.exports = { keystone, - admin, + servers: [admin, new StaticServer({ route: staticRoute, path: staticPath })], }; diff --git a/test-projects/social-login/package.json b/test-projects/social-login/package.json index b83fd072ec7..b4907c3a17a 100644 --- a/test-projects/social-login/package.json +++ b/test-projects/social-login/package.json @@ -21,9 +21,11 @@ "@keystone-alpha/core": "^2.0.4", "@keystone-alpha/fields": "^6.1.1", "@keystone-alpha/file-adapters": "^1.0.2", - "@keystone-alpha/passport-auth": "^1.0.0", "@keystone-alpha/keystone": "^4.0.0", + "@keystone-alpha/passport-auth": "^1.0.0", + "@keystone-alpha/server-static": "^0.0.0", "@keystone-alpha/session": "^1.0.2", + "express": "^4.16.3", "passport-wordpress": "^0.0.4", "react": "^16.8.6" }, diff --git a/test-projects/social-login/server.js b/test-projects/social-login/server.js index e900f12a187..9092345cd45 100644 --- a/test-projects/social-login/server.js +++ b/test-projects/social-login/server.js @@ -1,3 +1,4 @@ +const express = require('express'); const keystone = require('@keystone-alpha/core'); const { endAuthedSession } = require('@keystone-alpha/session'); const { @@ -7,8 +8,6 @@ const { googleAuthEnabled, wpAuthEnabled, port, - staticRoute, - staticPath, } = require('./config'); const { configureFacebookAuth, @@ -22,8 +21,8 @@ const { const initialData = require('./data'); keystone - .prepare({ port }) - .then(async ({ server, keystone: keystoneApp }) => { + .prepare({ port, dev: process.env.NODE_ENV !== 'production' }) + .then(async ({ middlewares, keystone: keystoneApp }) => { const socialLogins = []; if (facebookAuthEnabled) { socialLogins.push(configureFacebookAuth(keystoneApp)); @@ -47,11 +46,13 @@ keystone await keystoneApp.connect(); + const app = express(); + if (socialLogins.length > 0) { - InitializePassportAuthStrategies(server.app); + InitializePassportAuthStrategies(app); } - socialLogins.forEach(strategy => setupAuthRoutes({ strategy, server })); + socialLogins.forEach(strategy => setupAuthRoutes({ strategy, app })); // Initialise some data. // NOTE: This is only for test purposes and should not be used in production @@ -63,7 +64,7 @@ keystone await keystoneApp.createItems(initialData); } - server.app.get('/reset-db', async (req, res) => { + app.get('/reset-db', async (req, res) => { Object.values(keystoneApp.adapters).forEach(async adapter => { await adapter.dropDatabase(); }); @@ -71,7 +72,7 @@ keystone res.redirect('/admin'); }); - server.app.get('/api/session', (req, res) => { + app.get('/api/session', (req, res) => { res.json({ signedIn: !!req.session.keystoneItemId, userId: req.session.keystoneItemId, @@ -79,7 +80,7 @@ keystone }); }); - server.app.get('/api/signout', async (req, res, next) => { + app.get('/api/signout', async (req, res, next) => { try { await endAuthedSession(req); res.json({ @@ -90,8 +91,11 @@ keystone } }); - server.app.use(staticRoute, server.express.static(staticPath)); - await server.start(); + app.use(middlewares); + + app.listen(port, error => { + if (error) throw error; + }); }) .catch(error => { console.error(error); diff --git a/yarn.lock b/yarn.lock index bb6604d872c..986edd005cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3240,16 +3240,6 @@ apollo-cache-control@0.5.2: apollo-server-env "2.2.0" graphql-extensions "0.5.4" -apollo-cache-inmemory@^1.1.7: - version "1.4.2" - resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.4.2.tgz#c91aeb4adff45cdc7872d603cbff055fa9cd5021" - integrity sha512-fDVmj5j1e3W+inyuSwjIcMgbQ4edcFgmiKTBMFAEKAq0jg33X7FrbDX8JT2t5Vuf75Mva50JDlt5wXdu7C6WuA== - dependencies: - apollo-cache "^1.1.25" - apollo-utilities "^1.1.2" - optimism "^0.6.9" - tslib "^1.9.3" - apollo-cache-inmemory@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.5.1.tgz#265d1ee67b0bf0aca9c37629d410bfae44e62953" @@ -3261,14 +3251,6 @@ apollo-cache-inmemory@^1.5.1: ts-invariant "^0.2.1" tslib "^1.9.3" -apollo-cache@1.1.25, apollo-cache@^1.1.25: - version "1.1.25" - resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.1.25.tgz#87a15a2a19993bb07234ccee6839b59d6fb49ac5" - integrity sha512-9HhI/tVEHAeGaJJvi1Vpf6PzXUCA0PqNbigi2G3uOc180JjxbcaBvEbKXMEDb/UyTXkFWzI4PiPDuDQFqmIMSA== - dependencies: - apollo-utilities "^1.1.2" - tslib "^1.9.3" - apollo-cache@1.2.1, apollo-cache@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.2.1.tgz#aae71eb4a11f1f7322adc343f84b1a39b0693644" @@ -3277,31 +3259,6 @@ apollo-cache@1.2.1, apollo-cache@^1.2.1: apollo-utilities "^1.2.1" tslib "^1.9.3" -apollo-client-preset@1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/apollo-client-preset/-/apollo-client-preset-1.0.8.tgz#23bd7176849d0d815f12c648774d009b258a449e" - integrity sha512-vRrdBfoOBkSboUmkec/zDWK9dT22GoZ2NgTKxfPXaTRh82HGDejDAblMr7BuDtZQ6zxMUiD9kghmO+3HXsHKdQ== - dependencies: - apollo-cache-inmemory "^1.1.7" - apollo-client "^2.2.2" - apollo-link "^1.0.6" - apollo-link-http "^1.3.1" - graphql-tag "^2.4.2" - -apollo-client@^2.2.2: - version "2.4.12" - resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.4.12.tgz#9fa15f502d04f8cc788a9fbb825163b437681504" - integrity sha512-E5ClFSB9btJLYibLKwLDSCg+w9tI+25eZgXOM+DClawu7of4d/xhuV/xvpuZpsMP3qwrp0QPacBnfG4tUJs3/w== - dependencies: - "@types/zen-observable" "^0.8.0" - apollo-cache "1.1.25" - apollo-link "^1.0.0" - apollo-link-dedup "^1.0.0" - apollo-utilities "1.1.2" - symbol-observable "^1.0.2" - tslib "^1.9.3" - zen-observable "^0.8.0" - apollo-client@^2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.5.1.tgz#36126ed1d32edd79c3713c6684546a3bea80e6d1" @@ -3543,14 +3500,6 @@ apollo-upload-client@^10.0.0: apollo-link-http-common "^0.2.8" extract-files "^5.0.0" -apollo-utilities@1.1.2, apollo-utilities@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.1.2.tgz#aa5eca9d1f1eb721c381a22e0dde03559d856db3" - integrity sha512-EjDx8vToK+zkWIxc76ZQY/irRX52puNg04xf/w8R0kVTDAgHuVfnFVC01O5vE25kFnIaa5em0pFI0p9b6YMkhQ== - dependencies: - fast-json-stable-stringify "^2.0.0" - tslib "^1.9.3" - apollo-utilities@1.2.1, apollo-utilities@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.2.1.tgz#1c3a1ebf5607d7c8efe7636daaf58e7463b41b3c" @@ -6282,6 +6231,27 @@ cosmiconfig@^5.2.0: js-yaml "^3.13.1" parse-json "^4.0.0" +cp-file@^6.1.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/cp-file/-/cp-file-6.2.0.tgz#40d5ea4a1def2a9acdd07ba5c0b0246ef73dc10d" + integrity sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA== + dependencies: + graceful-fs "^4.1.2" + make-dir "^2.0.0" + nested-error-stacks "^2.0.0" + pify "^4.0.1" + safe-buffer "^5.0.1" + +cpy@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/cpy/-/cpy-7.2.0.tgz#6f0f39ec720712628b4702c32263816f4720a364" + integrity sha512-CUYi9WYd7vdtEcq1NKqiS/yY2WdaDCNOBA/AoTQHVJzlpJMqctB8py9JrHgGIft6TgO5m8ZidI4l1ZD+RMr/wA== + dependencies: + arrify "^1.0.1" + cp-file "^6.1.0" + globby "^9.2.0" + nested-error-stacks "^2.1.0" + crc@3.4.4: version "3.4.4" resolved "https://registry.yarnpkg.com/crc/-/crc-3.4.4.tgz#9da1e980e3bd44fc5c93bf5ab3da3378d85e466b" @@ -10292,7 +10262,7 @@ graphql-upload@^8.0.2: http-errors "^1.7.1" object-path "^0.11.4" -graphql@0.11.7, graphql@^0.11.7: +graphql@^0.11.7: version "0.11.7" resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.11.7.tgz#e5abaa9cb7b7cccb84e9f0836bf4370d268750c6" integrity sha512-x7uDjyz8Jx+QPbpCFCMQ8lltnQa4p4vSYHx6ADe8rVYRTdsyhCJbvSty5DAsLVmU6cGakl+r8HQYolKHxk/tiw== @@ -11940,7 +11910,7 @@ isomorphic-base64@^1.0.2: resolved "https://registry.yarnpkg.com/isomorphic-base64/-/isomorphic-base64-1.0.2.tgz#f426aae82569ba8a4ec5ca73ad21a44ab1ee7803" integrity sha1-9Caq6CVpuopOxcpzrSGkSrHueAM= -isomorphic-fetch@2.2.1, isomorphic-fetch@^2.1.1, isomorphic-fetch@^2.2.1: +isomorphic-fetch@^2.1.1, isomorphic-fetch@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= @@ -13221,11 +13191,6 @@ lodash.isequal@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA= -lodash.isfunction@3.0.9: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" - integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== - lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" @@ -13539,6 +13504,14 @@ make-dir@^1.0.0, make-dir@^1.2.0, make-dir@^1.3.0: dependencies: pify "^3.0.0" +make-dir@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + make-iterator@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/make-iterator/-/make-iterator-1.0.1.tgz#29b33f312aa8f547c4a5e490f56afcec99133ad6" @@ -14755,28 +14728,16 @@ neo-async@^2.6.0: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c" integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw== +nested-error-stacks@^2.0.0, nested-error-stacks@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz#0fbdcf3e13fe4994781280524f8b96b0cdff9c61" + integrity sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug== + netmask@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" integrity sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU= -next-apollo@^2.0.9: - version "2.0.9" - resolved "https://registry.yarnpkg.com/next-apollo/-/next-apollo-2.0.9.tgz#b7dcdfb4b9048e41dbaf0f689fc6b815ab639a02" - integrity sha512-f21jrkWi8FTHTyQaHWVfu2xE5fm50Nszu0YOLxFvOx6y59BWLxy0DbyCJVZrZH1NLCUHTdVdm48k+TeiedCAkA== - dependencies: - apollo-client-preset "1.0.8" - graphql "0.11.7" - isomorphic-fetch "2.2.1" - lodash.isfunction "3.0.9" - next "^8.0.1" - prop-types "15.7.2" - prop-types-exact "1.2.0" - react "16.8.2" - react-apollo "2.4.1" - react-dom "16.8.2" - url "0.11.0" - next-routes@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/next-routes/-/next-routes-1.4.2.tgz#736a382579a792ea69f35ae70b449acdfefa7944" @@ -14802,7 +14763,7 @@ next-tick@^1.0.0: resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= -next@^8.0.1, next@^8.0.3: +next@^8.0.3: version "8.0.3" resolved "https://registry.yarnpkg.com/next/-/next-8.0.3.tgz#7f27172e9441aacb21b04aaaaa663af43aceea3b" integrity sha512-Q4GHUZPzQrjBAWPOBBbWVlKUj9XPc/OsLc7svEXG+c/k9K9piQHDewtI9g4jQPAQlKOqt9BcRk8s5teV4SFPfQ== @@ -17108,7 +17069,7 @@ prop-types@15.6.2, prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.5.8, loose-envify "^1.3.1" object-assign "^4.1.1" -prop-types@15.7.2, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.7.2: +prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -17546,18 +17507,6 @@ react-apollo@2.4.0: lodash.isequal "^4.5.0" prop-types "^15.6.0" -react-apollo@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-2.4.1.tgz#89db63ebacf01c1603553bb476f089492aaeab2c" - integrity sha512-fSaiwXzY5xRmqKuGCtkMON8paL4/rKf4pB2aSI/0Ot8tn+gJisFqZ0iz9Pxvl6MHnJm1/gjJD3X1J4KmbYN2Yw== - dependencies: - fbjs "^1.0.0" - hoist-non-react-statics "^3.0.0" - invariant "^2.2.2" - lodash.flowright "^3.5.0" - lodash.isequal "^4.5.0" - prop-types "^15.6.0" - react-codemirror@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/react-codemirror/-/react-codemirror-1.0.0.tgz#91467b53b1f5d80d916a2fd0b4c7adb85a9001ba" @@ -17627,16 +17576,6 @@ react-document-title@^2.0.3: prop-types "^15.5.6" react-side-effect "^1.0.2" -react-dom@16.8.2: - version "16.8.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.2.tgz#7c8a69545dd554d45d66442230ba04a6a0a3c3d3" - integrity sha512-cPGfgFfwi+VCZjk73buu14pYkYBR1b/SRMSYqkLDdhSEHnSwcuYTPu6/Bh6ZphJFIk80XLvbSe2azfcRzNF+Xg== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.13.2" - react-dom@^16.3.1: version "16.8.4" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.8.4.tgz#1061a8e01a2b3b0c8160037441c3bf00a0e3bc48" @@ -17969,16 +17908,6 @@ react-window@^1.7.0: "@babel/runtime" "^7.0.0" memoize-one ">=3.1.1 <6" -react@16.8.2: - version "16.8.2" - resolved "https://registry.yarnpkg.com/react/-/react-16.8.2.tgz#83064596feaa98d9c2857c4deae1848b542c9c0c" - integrity sha512-aB2ctx9uQ9vo09HVknqv3DGRpI7OIGJhCx3Bt0QqoRluEjHSaObJl+nG12GDdYH6sTgE7YiPJ6ZUyMx9kICdXw== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.13.2" - react@^16.3.1: version "16.8.4" resolved "https://registry.yarnpkg.com/react/-/react-16.8.4.tgz#fdf7bd9ae53f03a9c4cd1a371432c206be1c4768" @@ -19135,14 +19064,6 @@ scheduler@^0.11.2: loose-envify "^1.1.0" object-assign "^4.1.1" -scheduler@^0.13.2: - version "0.13.3" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.3.tgz#bed3c5850f62ea9c716a4d781f9daeb9b2a58896" - integrity sha512-UxN5QRYWtpR1egNWzJcVLk8jlegxAugswQc984lD3kU7NuobsO37/sRfbpTdBjtnD5TBNFA2Q2oLV5+UmPSmEQ== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler@^0.13.4: version "0.13.4" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.4.tgz#8fef05e7a3580c76c0364d2df5e550e4c9140298"