Skip to content

Commit

Permalink
Next-lite example project (#5341)
Browse files Browse the repository at this point in the history
  • Loading branch information
emmatown authored Apr 6, 2021
1 parent 9bc05d5 commit 1886b43
Show file tree
Hide file tree
Showing 26 changed files with 336 additions and 9 deletions.
6 changes: 6 additions & 0 deletions .changeset/proud-hotels-agree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@keystone-next/keystone': minor
'@keystone-next/types': minor
---

Added `generateNextGraphqlAPI` and `generateNodeAPI` experimental options
5 changes: 5 additions & 0 deletions .changeset/six-feet-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/keystone': minor
---

Added `withKeystone` in `next` entrypoint
4 changes: 4 additions & 0 deletions docs-next/pages/apis/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ import { config } from '@keystone-next/keystone/schema';
export default config({
experimental: {
enableNextJsGraphqlApiEndpoint: true,
generateNextGraphqlAPI: true,
generateNodeAPI: true,
}
/* ... */
});
Expand All @@ -270,5 +272,7 @@ export default config({
Options:

- `enableNextJsGraphqlApiEndpoint`: (coming soon)
- `generateNextGraphqlAPI`: Creates a file at `node_modules/.keystone/next/graphql-api` with `default` and `config` exports that can be re-exported in a Next API route
- `generateNodeAPI`: Creates a file at `node_modules/.keystone/api` with a `lists` export

export default ({ children }) => <Markdown>{children}</Markdown>;
12 changes: 12 additions & 0 deletions examples-next/next-lite/keystone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { config } from '@keystone-next/keystone/schema';

import { Post } from './schema';

export default config({
db: { adapter: 'prisma_sqlite', url: 'file:./app.db' },
experimental: {
generateNextGraphqlAPI: true,
generateNodeAPI: true,
},
lists: { Post },
});
2 changes: 2 additions & 0 deletions examples-next/next-lite/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
3 changes: 3 additions & 0 deletions examples-next/next-lite/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const { withKeystone } = require('@keystone-next/keystone/next');

module.exports = withKeystone();
27 changes: 27 additions & 0 deletions examples-next/next-lite/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@keystone-next/example-next-lite",
"version": "1.0.3",
"private": true,
"license": "MIT",
"scripts": {
"dev": "next dev",
"start": "next start",
"build": "next build"
},
"dependencies": {
"@keystone-next/admin-ui": "^12.0.0",
"@keystone-next/fields": "^5.3.0",
"@keystone-next/keystone": "^14.0.0",
"dotenv": "^8.2.0",
"next": "^10.0.9",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"typescript": "^4.2.3"
},
"engines": {
"node": ">=10.0.0"
},
"repository": "https://github.com/keystonejs/keystone/tree/master/examples-next/next-lite"
}
2 changes: 2 additions & 0 deletions examples-next/next-lite/pages/api/graphql.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line import/no-unresolved
export { default, config } from '.keystone/graphql';
26 changes: 26 additions & 0 deletions examples-next/next-lite/pages/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import Link from 'next/link';
// eslint-disable-next-line import/no-unresolved
import { lists } from '.keystone/api';

export default function HomePage({ posts }) {
return (
<div>
<h1>Welcome to my blog</h1>
<ul>
{posts.map((post, i) => (
<li key={i}>
<Link href={`/post/${post.slug}`}>
<a>{post.title}</a>
</Link>
</li>
))}
</ul>
</div>
);
}

export async function getStaticProps() {
const posts = await lists.Post.findMany({ resolveFields: 'slug title' });
return { props: { posts } };
}
33 changes: 33 additions & 0 deletions examples-next/next-lite/pages/post/[slug].js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';

// eslint-disable-next-line import/no-unresolved
import { lists } from '.keystone/api';

export default function PostPage({ post }) {
return (
<div>
<h1>{post.title}</h1>
<div>{post.content}</div>
</div>
);
}

export async function getStaticPaths() {
const posts = await lists.Post.findMany({
resolveFields: 'slug',
});

const paths = posts.map(post => post.slug).map(slug => `/post/${slug}`);
return {
paths,
fallback: false,
};
}

export async function getStaticProps({ params: { slug } }) {
const [post] = await lists.Post.findMany({
where: { slug: slug },
resolveFields: 'title content',
});
return { props: { post } };
}
10 changes: 10 additions & 0 deletions examples-next/next-lite/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { list } from '@keystone-next/keystone/schema';
import { text } from '@keystone-next/fields';

export const Post = list({
fields: {
title: text({ isRequired: true }),
slug: text(),
content: text(),
},
});
19 changes: 19 additions & 0 deletions examples-next/next-lite/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "esnext",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"main": "dist/keystone.cjs.js",
"module": "dist/keystone.esm.js"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"main": "dist/keystone.cjs.js",
"module": "dist/keystone.esm.js"
}
4 changes: 4 additions & 0 deletions packages-next/keystone/next/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"main": "dist/keystone.cjs.js",
"module": "dist/keystone.esm.js"
}
3 changes: 3 additions & 0 deletions packages-next/keystone/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@keystone-next/keystone-legacy": "^22.0.0",
"@keystone-next/server-side-graphql-client-legacy": "3.0.0",
"@keystone-next/types": "^15.0.1",
"@preconstruct/next": "^2.0.0",
"@prisma/client": "2.19.0",
"@prisma/migrate": "2.19.0",
"@prisma/sdk": "2.19.0",
Expand Down Expand Up @@ -67,6 +68,8 @@
"preconstruct": {
"entrypoints": [
"index.ts",
"next.ts",
"___internal-do-not-use-will-break-in-patch/{node-api,next-graphql}.ts",
"artifacts.ts",
"migrations.ts",
"schema/index.ts",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { KeystoneConfig } from '@keystone-next/types';
import { initConfig } from '../lib/initConfig';
import { createSystem } from '../lib/createSystem';
import { createApolloServerMicro } from '../lib/createApolloServer';

export function nextGraphQLAPIRoute(keystoneConfig: KeystoneConfig, prismaClient: any) {
const initializedKeystoneConfig = initConfig(keystoneConfig);
const { graphQLSchema, keystone, createContext } = createSystem(
initializedKeystoneConfig,
prismaClient
);

const apolloServer = createApolloServerMicro({
graphQLSchema,
createContext,
sessionStrategy: initializedKeystoneConfig.session?.(),
apolloConfig: initializedKeystoneConfig.graphql?.apolloConfig,
connectionPromise: keystone.connect(),
});

return apolloServer.createHandler({ path: '/api/graphql' });
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { KeystoneConfig } from '@keystone-next/types';
import { createSystem } from '../lib/createSystem';
import { initConfig } from '../lib/initConfig';

export function createListsAPI(config: KeystoneConfig, prismaClient: any) {
const { createContext, keystone } = createSystem(initConfig(config), prismaClient);
keystone.connect();
return createContext().sudo().lists;
}
81 changes: 77 additions & 4 deletions packages-next/keystone/src/artifacts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import path from 'path';
import { printSchema, GraphQLSchema } from 'graphql';
import * as fs from 'fs-extra';
import type { BaseKeystone } from '@keystone-next/types';
import type { BaseKeystone, KeystoneConfig } from '@keystone-next/types';
import { getGenerator, formatSchema } from '@prisma/sdk';
import { confirmPrompt } from './lib/prompts';
import { printGeneratedTypes } from './lib/schema-type-printer';
Expand Down Expand Up @@ -105,20 +105,93 @@ export async function generateCommittedArtifacts(
return artifacts;
}

const nodeAPIJS = (
cwd: string,
config: KeystoneConfig
) => `import keystoneConfig from '../../keystone';
import { PrismaClient } from '.prisma/client';
import { createListsAPI } from '@keystone-next/keystone/___internal-do-not-use-will-break-in-patch/node-api';
${makeVercelIncludeTheSQLiteDB(cwd, path.join(cwd, 'node_modules/.keystone/next'), config)}
export const lists = createListsAPI(keystoneConfig, PrismaClient);
`;

const nodeAPIDTS = `import { KeystoneListsAPI } from '@keystone-next/types';
import { KeystoneListsTypeInfo } from './types';
export const lists: KeystoneListsAPI<KeystoneListsTypeInfo>;`;

const makeVercelIncludeTheSQLiteDB = (
cwd: string,
directoryOfFileToBeWritten: string,
config: KeystoneConfig
) => {
if (config.db.adapter === 'prisma_sqlite') {
const sqliteDbAbsolutePath = path.resolve(cwd, config.db.url.replace('file:', ''));

return `import path from 'path';
path.join(__dirname, ${JSON.stringify(
path.relative(directoryOfFileToBeWritten, sqliteDbAbsolutePath)
)});
path.join(process.cwd(), ${JSON.stringify(path.relative(cwd, sqliteDbAbsolutePath))});
`;
}
return '';
};

const nextGraphQLAPIJS = (
cwd: string,
config: KeystoneConfig
) => `import keystoneConfig from '../../../keystone';
import { PrismaClient } from '.prisma/client';
import { nextGraphQLAPIRoute } from '@keystone-next/keystone/___internal-do-not-use-will-break-in-patch/next-graphql';
${makeVercelIncludeTheSQLiteDB(cwd, path.join(cwd, 'node_modules/.keystone/next'), config)}
export const config = {
api: {
bodyParser: false,
},
};
export default nextGraphQLAPIRoute(keystoneConfig, PrismaClient);
`;

// note the export default config is just a lazy way of going "this is also any"
const nextGraphQLAPIDTS = `export const config: any;
export default config;
`;

export async function generateNodeModulesArtifacts(
graphQLSchema: GraphQLSchema,
keystone: BaseKeystone,
config: KeystoneConfig,
cwd: string
) {
const printedSchema = printSchema(graphQLSchema);

const dotKeystoneDir = path.join(cwd, 'node_modules/.keystone');
await Promise.all([
generatePrismaClient(cwd),
fs.outputFile(
path.join(cwd, 'node_modules/.keystone/types.d.ts'),
path.join(dotKeystoneDir, 'types.d.ts'),
printGeneratedTypes(printedSchema, keystone, graphQLSchema)
),
fs.outputFile(path.join(cwd, 'node_modules/.keystone/types.js'), ''),
fs.outputFile(path.join(dotKeystoneDir, 'types.js'), ''),
...(config.experimental?.generateNodeAPI
? [
fs.outputFile(path.join(dotKeystoneDir, 'api.js'), nodeAPIJS(cwd, config)),
fs.outputFile(path.join(dotKeystoneDir, 'api.d.ts'), nodeAPIDTS),
]
: []),
...(config.experimental?.generateNextGraphqlAPI
? [
fs.outputFile(
path.join(dotKeystoneDir, 'next/graphql-api.js'),
nextGraphQLAPIJS(cwd, config)
),
fs.outputFile(path.join(dotKeystoneDir, 'next/graphql-api.d.ts'), nextGraphQLAPIDTS),
]
: []),
]);
}

Expand Down
55 changes: 55 additions & 0 deletions packages-next/keystone/src/next.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import execa from 'execa';
// @ts-ignore
import withPreconstruct from '@preconstruct/next';

let hasAlreadyStarted = false;

export const withKeystone = (internalConfig: any = {}) => (
phase:
| 'phase-export'
| 'phase-production-build'
| 'phase-production-server'
| 'phase-development-server',
thing: any
) => {
if (phase === 'phase-production-build') {
execa.sync('node', [require.resolve('@keystone-next/keystone/bin/cli.js'), 'postinstall'], {
stdio: 'inherit',
});
}
if (phase === 'phase-development-server' && !hasAlreadyStarted) {
hasAlreadyStarted = true;

const cliPath = require.resolve('@keystone-next/keystone/bin/cli.js');

execa.sync('node', [cliPath, 'postinstall', '--fix'], {
stdio: 'inherit',
});
// for some reason things blow up with EADDRINUSE if the dev call happens synchronously here
// so we wait a sec and then do it
setTimeout(() => {
execa('node', [cliPath], {
stdio: 'inherit',
env: { ...process.env, PORT: process.env.PORT || '8000' },
});
}, 100);
}
let internalConfigObj =
typeof internalConfig === 'function' ? internalConfig(phase, thing) : internalConfig;

let originalWebpack = internalConfigObj.webpack;
internalConfigObj.webpack = (webpackConfig: any, options: any) => {
if (options.isServer) {
webpackConfig.externals = [
...webpackConfig.externals,
'@keystone-next/keystone/___internal-do-not-use-will-break-in-patch/api',
'@keystone-next/keystone/___internal-do-not-use-will-break-in-patch/next-graphql',
'@keystone-next/keystone/next',
'@keystone-next/keystone',
'.prisma/client',
];
}
return originalWebpack ? originalWebpack(webpackConfig, options) : webpackConfig;
};
return withPreconstruct(internalConfigObj);
};
Loading

1 comment on commit 1886b43

@vercel
Copy link

@vercel vercel bot commented on 1886b43 Apr 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.