Skip to content

Commit

Permalink
Add config.ui.isDisabled option (#4866)
Browse files Browse the repository at this point in the history
  • Loading branch information
timleslie authored Feb 17, 2021
1 parent 9ddf95e commit 2655c0b
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 20 deletions.
7 changes: 7 additions & 0 deletions .changeset/kind-buses-mix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@keystone-next/keystone': minor
'@keystone-next/types': minor
'@keystone-next/website': patch
---

Added a `config.ui.isDisabled` option to completely disable the Admin UI.
2 changes: 2 additions & 0 deletions docs-next/pages/apis/config.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ Fine grained configuration of how lists and fields behave in the Admin UI is han

Options:

- `isDisabled` (default: `false`): If `isDisabled` is set to `true` then the Admin UI will be completely disabled.
- `isAccessAllowed` (default: `(context) => !!context.session`): This function controls whether a user is able to access the Admin UI.
It takes a [`KeystoneContext`](./context) object as an argument.

Expand All @@ -154,6 +155,7 @@ Advanced configuration:
```typescript
export default config({
ui: {
isDisabled: false,
isAccessAllowed: async context => true,
// Optional advanced configuration
enableSessionItem: true,
Expand Down
12 changes: 8 additions & 4 deletions packages-next/keystone/src/lib/createExpressServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,14 @@ export const createExpressServer = async (
console.log('✨ Preparing GraphQL Server');
addApolloServer({ server, graphQLSchema, createContext, sessionStrategy });

console.log('✨ Preparing Next.js app');
server.use(
await createAdminUIServer(config.ui, createContext, dev, projectAdminPath, sessionStrategy)
);
if (config.ui?.isDisabled) {
console.log('✨ Skipping Admin UI app');
} else {
console.log('✨ Preparing Admin UI Next.js app');
server.use(
await createAdminUIServer(config.ui, createContext, dev, projectAdminPath, sessionStrategy)
);
}

return server;
};
73 changes: 60 additions & 13 deletions packages-next/keystone/src/scripts/build/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,32 @@ import Path from 'path';
import prettier from 'prettier';
import fs from 'fs-extra';
import { buildAdminUI, generateAdminUI } from '@keystone-next/admin-ui/system';
import { AdminFileToWrite } from '@keystone-next/types';
import { createSystem } from '../../lib/createSystem';
import { initConfig } from '../../lib/initConfig';
import { requireSource } from '../../lib/requireSource';
import { saveSchemaAndTypes } from '../../lib/saveSchemaAndTypes';
import { CONFIG_PATH } from '../utils';
import type { StaticPaths } from '..';

// FIXME: Duplicated from admin-ui package. Need to decide on a common home.
async function writeAdminFile(file: AdminFileToWrite, projectAdminPath: string) {
const outputFilename = Path.join(projectAdminPath, file.outputPath);
if (file.mode === 'copy') {
if (!Path.isAbsolute(file.inputPath)) {
throw new Error(
`An inputPath of "${file.inputPath}" was provided to copy but inputPaths must be absolute`
);
}
await fs.ensureDir(Path.dirname(outputFilename));
// TODO: should we use copyFile or copy?
await fs.copyFile(file.inputPath, outputFilename);
}
if (file.mode === 'write') {
await fs.outputFile(outputFilename, formatSource(file.src));
}
}

// FIXME: Duplicated from admin-ui package. Need to decide on a common home.
export function serializePathForImport(path: string) {
// JSON.stringify is important here because it will escape windows style paths(and any thing else that might potentionally be in there)
Expand All @@ -23,21 +42,45 @@ export function serializePathForImport(path: string) {
export const formatSource = (src: string, parser: 'babel' | 'babel-ts' = 'babel') =>
prettier.format(src, { parser, trailingComma: 'es5', singleQuote: true });

const reexportKeystoneConfig = async (projectAdminPath: string) => {
const reexportKeystoneConfig = async (projectAdminPath: string, isDisabled?: boolean) => {
if (isDisabled) {
// Nuke any existing files in our target directory
await fs.remove(projectAdminPath);
}

// We re-export the Keystone config file into the Admin UI project folder
// so that when we run the build step, we will end up with a compiled version
// of the configuration file in the .next/ directory. Even if we're not building
// an Admin UI, we still need to run the `build()` function so that this config
// file is correctly compiled.
const outputDir = Path.join(projectAdminPath, 'pages', 'api');
const pathToConfig = Path.relative(outputDir, CONFIG_PATH);
const file = `
export { default as config } from ${serializePathForImport(pathToConfig)}
export default function (req, res) {
return res.status(500)
}`;

await fs.outputFile(Path.join(outputDir, '__keystone_api_build.js'), formatSource(file));
const pkgDir = Path.dirname(require.resolve('@keystone-next/admin-ui/package.json'));
const files: AdminFileToWrite[] = [
{
mode: 'write',
src: `export { default as config } from ${serializePathForImport(
Path.relative(Path.join(projectAdminPath, 'pages', 'api'), CONFIG_PATH)
)}
export default function (req, res) { return res.status(500) }`,
outputPath: Path.join('pages', 'api', '__keystone_api_build.js'),
},
];
if (isDisabled) {
// These are the basic files required to have a valid Next.js project. If the
// Admin UI is disabled then we need to do this ourselves here.
files.push(
{
mode: 'copy' as const,
inputPath: Path.join(pkgDir, 'static', 'next.config.js'),
outputPath: 'next.config.js',
},
{
mode: 'copy' as const,
inputPath: Path.join(pkgDir, 'static', 'tsconfig.json'),
outputPath: 'tsconfig.json',
}
);
}
await Promise.all(files.map(file => writeAdminFile(file, projectAdminPath)));
};

export async function build({ dotKeystonePath, projectAdminPath }: StaticPaths) {
Expand All @@ -50,11 +93,15 @@ export async function build({ dotKeystonePath, projectAdminPath }: StaticPaths)
console.log('✨ Generating graphQL schema');
await saveSchemaAndTypes(graphQLSchema, keystone, dotKeystonePath);

console.log('✨ Generating Admin UI code');
await generateAdminUI(config, graphQLSchema, keystone, projectAdminPath);
if (config.ui?.isDisabled) {
console.log('✨ Skipping Admin UI code generation');
} else {
console.log('✨ Generating Admin UI code');
await generateAdminUI(config, graphQLSchema, keystone, projectAdminPath);
}

console.log('✨ Generating Keystone config code');
await reexportKeystoneConfig(projectAdminPath);
await reexportKeystoneConfig(projectAdminPath, config.ui?.isDisabled);

console.log('✨ Building Admin UI');
await buildAdminUI(projectAdminPath);
Expand Down
8 changes: 6 additions & 2 deletions packages-next/keystone/src/scripts/run/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,12 @@ export const dev = async ({ dotKeystonePath, projectAdminPath }: StaticPaths, sc
console.log('✨ Connecting to the database');
await keystone.connect({ context: createContext().sudo() });

console.log('✨ Generating Admin UI code');
await generateAdminUI(config, graphQLSchema, keystone, projectAdminPath);
if (config.ui?.isDisabled) {
console.log('✨ Skipping Admin UI code generation');
} else {
console.log('✨ Generating Admin UI code');
await generateAdminUI(config, graphQLSchema, keystone, projectAdminPath);
}

console.log('✨ Creating server');
expressServer = await createExpressServer(
Expand Down
6 changes: 5 additions & 1 deletion packages-next/keystone/src/scripts/run/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ export const start = async ({ dotKeystonePath, projectAdminPath }: StaticPaths)
false,
projectAdminPath
);
console.log(`👋 Admin UI and graphQL API ready`);
if (config.ui?.isDisabled) {
console.log(`👋 GraphQL API ready`);
} else {
console.log(`👋 Admin UI and GraphQL API ready`);
}

const port = config.server?.port || process.env.PORT || 3000;
server.listen(port, (err?: any) => {
Expand Down
2 changes: 2 additions & 0 deletions packages-next/types/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ export type DatabaseConfig = DatabaseCommon &
// config.ui

export type AdminUIConfig = {
/** Completely disables the Admin UI */
isDisabled?: boolean;
/** Enables certain functionality in the Admin UI that expects the session to be an item */
enableSessionItem?: boolean;
/** A function that can be run to validate that the current session should have access to the Admin UI */
Expand Down

1 comment on commit 2655c0b

@vercel
Copy link

@vercel vercel bot commented on 2655c0b Feb 17, 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.