Skip to content

Commit

Permalink
CLI refresh (#5266)
Browse files Browse the repository at this point in the history
  • Loading branch information
emmatown authored Mar 30, 2021
1 parent 399e6db commit c28e765
Show file tree
Hide file tree
Showing 32 changed files with 343 additions and 176 deletions.
9 changes: 9 additions & 0 deletions .changeset/flat-beds-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@keystone-next/keystone': major
---

Replaced `deploy`, `reset` and `generate` commands with `keystone-next prisma`. You can use these commands as replacements for the old commands:

- `keystone-next deploy` -> `keystone-next prisma migrate deploy`
- `keystone-next reset` -> `keystone-next prisma migrate reset`
- `keystone-next generate` -> `keystone-next prisma migrate dev`
6 changes: 6 additions & 0 deletions .changeset/gentle-flies-invent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@keystone-next/keystone': major
'@keystone-next/types': major
---

Removed the `none` case in `MigrationAction` and require that the PrismaClient is passed to be able to connect to the database for the `none-skip-client-generation` case.
5 changes: 5 additions & 0 deletions .changeset/gentle-garlics-learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/keystone': major
---

Updated `keystone-next build` command to validate that the GraphQL and Prisma schemas are up to date.
5 changes: 5 additions & 0 deletions .changeset/giant-buses-sip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/adapter-prisma-legacy': minor
---

Added `devMigrations` and `runPrototypeMigrations` exports.
5 changes: 5 additions & 0 deletions .changeset/grumpy-needles-give.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/keystone': major
---

Moved generated `schema.prisma` to the root of the project directory. Note that this also moves the location of migrations from `.keystone/prisma/migrations` to `migrations` at the root of the project.
6 changes: 6 additions & 0 deletions .changeset/modern-cycles-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@keystone-next/keystone': major
'@keystone-next/test-utils-legacy': patch
---

Removed `dotKeystonePath` argument from `createSystem`
5 changes: 5 additions & 0 deletions .changeset/plenty-jars-swim.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/admin-ui': major
---

Updated Next API route template to use `createSystem` without the `dotKeystonePath` argument and import from the new Prisma Client location.
5 changes: 5 additions & 0 deletions .changeset/purple-gifts-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/keystone': minor
---

Added `keystone-next postinstall` command which verifies that the Prisma and GraphQL schemas are up to date with a `--fix` flag to automatically update them without a prompt.
5 changes: 5 additions & 0 deletions .changeset/tiny-impalas-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/keystone': major
---

Moved generated GraphQL schema to `schema.graphql` to the root of the project. We recommend that you commit this file to your repo.
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,11 @@ projects/
temp/

.api-test-prisma-clients

# SQLite databases
*.db

# These are here temporarily until the examples properly use the new CLI
# This should be removed and these files should be committed
examples-next/*/schema.graphql
examples-next/*/schema.prisma
11 changes: 2 additions & 9 deletions packages-next/admin-ui/src/templates/api.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
// so you might be thinking "that ../../../../../prisma/generated-client path looks very wrong, it's a directory above the user's keystone config?"
// and the answer to that is "yes, it's wrong from one perspective but it's also right because webpack things"
// in our next config (in this package at src/next-config.ts), we mark anything matching `prisma/generated-client` external
// this means that webpack will naively leave it as a require to ../../../../../prisma/generated-client
// ../../../../../prisma/generated-client is the exact right path to get to the generated client
// from where the bundled version of this file will be generated at so the require will end up working

export const apiTemplate = `
import keystoneConfig from '../../../../keystone';
import { initConfig, createSystem, createApolloServerMicro } from '@keystone-next/keystone';
import { PrismaClient } from '../../../../../prisma/generated-client';
import { PrismaClient } from '.prisma/client';
const initializedKeystoneConfig = initConfig(keystoneConfig);
const { graphQLSchema, keystone, createContext } = createSystem(initializedKeystoneConfig, '.keystone', 'none', PrismaClient);
const { graphQLSchema, keystone, createContext } = createSystem(initializedKeystoneConfig, 'none', PrismaClient);
const apolloServer = createApolloServerMicro({
graphQLSchema,
createContext,
Expand Down
5 changes: 5 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",
"@prisma/sdk": "2.19.0",
"@types/babel__core": "^7.1.14",
"@types/cookie": "^0.4.0",
"@types/express": "^4.17.11",
Expand All @@ -31,13 +32,15 @@
"@types/keystonejs__keystone": "^7.0.1",
"@types/pluralize": "^0.0.29",
"@types/prettier": "^2.2.3",
"@types/prompts": "^2.0.9",
"@types/source-map-support": "^0.5.3",
"@types/uid-safe": "^2.1.2",
"apollo-server-express": "^2.22.2",
"apollo-server-micro": "^2.22.2",
"apollo-server-types": "^0.6.3",
"cookie": "^0.4.1",
"cors": "^2.8.5",
"execa": "^5.0.0",
"express": "^4.17.1",
"fs-extra": "^9.1.0",
"graphql": "^15.5.0",
Expand All @@ -48,6 +51,8 @@
"pirates": "^4.0.1",
"pluralize": "^8.0.0",
"prettier": "^2.2.1",
"prisma": "2.19.0",
"prompts": "^2.4.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"source-map-support": "^0.5.19",
Expand Down
131 changes: 131 additions & 0 deletions packages-next/keystone/src/lib/artifacts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import path from 'path';
import { printSchema, GraphQLSchema } from 'graphql';
import * as fs from 'fs-extra';
import type { BaseKeystone } from '@keystone-next/types';
import { getGenerator } from '@prisma/sdk';
import { confirmPrompt } from './prompts';
import { printGeneratedTypes } from './schema-type-printer';

export function getSchemaPaths(cwd: string) {
return {
prisma: path.join(cwd, 'schema.prisma'),
graphql: path.join(cwd, 'schema.graphql'),
};
}

type CommittedArtifacts = {
graphql: string;
prisma: string;
};

export async function getCommittedArtifacts(
graphQLSchema: GraphQLSchema,
keystone: BaseKeystone
): Promise<CommittedArtifacts> {
return {
graphql: printSchema(graphQLSchema),
prisma: await keystone.adapter._generatePrismaSchema({
rels: keystone._consolidateRelationships(),
clientDir: 'node_modules/.prisma/client',
}),
};
}

async function readFileButReturnNothingIfDoesNotExist(filename: string) {
try {
return await fs.readFile(filename, 'utf8');
} catch (err) {
if (err.code === 'ENOENT') {
return;
}
throw err;
}
}

export async function validateCommittedArtifacts(
graphQLSchema: GraphQLSchema,
keystone: BaseKeystone,
cwd: string
) {
const artifacts = await getCommittedArtifacts(graphQLSchema, keystone);
const schemaPaths = getSchemaPaths(cwd);
const [writtenGraphQLSchema, writtenPrismaSchema] = await Promise.all([
readFileButReturnNothingIfDoesNotExist(schemaPaths.graphql),
readFileButReturnNothingIfDoesNotExist(schemaPaths.prisma),
]);
const outOfDateSchemas = (() => {
if (writtenGraphQLSchema !== artifacts.graphql && writtenPrismaSchema !== artifacts.prisma) {
return 'both';
}
if (writtenGraphQLSchema !== artifacts.graphql) {
return 'graphql';
}
if (writtenPrismaSchema !== artifacts.prisma) {
return 'prisma';
}
})();
if (outOfDateSchemas) {
const message = {
both: 'Your Prisma and GraphQL schemas are not up to date',
graphql: 'Your GraphQL schema is not up to date',
prisma: 'Your GraphQL schema is not up to date',
}[outOfDateSchemas];
console.log(message);
const term = {
both: 'Prisma and GraphQL schemas',
prisma: 'Prisma schema',
graphql: 'GraphQL schema',
}[outOfDateSchemas];
if (process.stdout.isTTY && (await confirmPrompt(`Would you like to update your ${term}?`))) {
await writeCommittedArtifacts(artifacts, cwd);
} else {
console.log(`Please run keystone-next postinstall --fix to update your ${term}`);
process.exit(1);
}
}
}

export async function writeCommittedArtifacts(artifacts: CommittedArtifacts, cwd: string) {
const schemaPaths = getSchemaPaths(cwd);
await Promise.all([
fs.writeFile(schemaPaths.graphql, artifacts.graphql),
fs.writeFile(schemaPaths.prisma, artifacts.prisma),
]);
}

export async function generateCommittedArtifacts(
graphQLSchema: GraphQLSchema,
keystone: BaseKeystone,
cwd: string
) {
const artifacts = await getCommittedArtifacts(graphQLSchema, keystone);
await writeCommittedArtifacts(artifacts, cwd);
return artifacts;
}

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

await Promise.all([
generatePrismaClient(cwd),
fs.outputFile(
path.join(cwd, 'node_modules/.keystone/types.d.ts'),
printGeneratedTypes(printedSchema, keystone, graphQLSchema)
),
fs.outputFile(path.join(cwd, 'node_modules/.keystone/types.js'), ''),
]);
}

async function generatePrismaClient(cwd: string) {
const generator = await getGenerator({ schemaPath: getSchemaPaths(cwd).prisma });
await generator.generate();
generator.stop();
}

export function requirePrismaClient(cwd: string) {
return require(path.join(cwd, 'node_modules/.prisma/client')).PrismaClient;
}
4 changes: 0 additions & 4 deletions packages-next/keystone/src/lib/createKeystone.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import path from 'path';
// @ts-ignore
import { Keystone } from '@keystone-next/keystone-legacy';
import { PrismaAdapter } from '@keystone-next/adapter-prisma-legacy';
import type { KeystoneConfig, BaseKeystone, MigrationAction } from '@keystone-next/types';

export function createKeystone(
config: KeystoneConfig,
dotKeystonePath: string,
migrationAction: MigrationAction,
prismaClient?: any
) {
Expand All @@ -18,7 +16,6 @@ export function createKeystone(
let adapter;
if (db.adapter === 'prisma_postgresql') {
adapter = new PrismaAdapter({
getPrismaPath: () => path.join(dotKeystonePath, 'prisma'),
migrationMode:
migrationAction === 'dev' ? (db.useMigrations ? 'dev' : 'prototype') : migrationAction,
prismaClient,
Expand All @@ -32,7 +29,6 @@ export function createKeystone(
);
}
adapter = new PrismaAdapter({
getPrismaPath: () => path.join(dotKeystonePath, 'prisma'),
prismaClient,
migrationMode:
migrationAction === 'dev' ? (db.useMigrations ? 'dev' : 'prototype') : migrationAction,
Expand Down
3 changes: 1 addition & 2 deletions packages-next/keystone/src/lib/createSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import { createKeystone } from './createKeystone';

export function createSystem(
config: KeystoneConfig,
dotKeystonePath: string,
migrationAction: MigrationAction,
prismaClient?: any
) {
const keystone = createKeystone(config, dotKeystonePath, migrationAction, prismaClient);
const keystone = createKeystone(config, migrationAction, prismaClient);

const graphQLSchema = createGraphQLSchema(config, keystone, 'public');

Expand Down
17 changes: 17 additions & 0 deletions packages-next/keystone/src/lib/prompts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import prompts from 'prompts';

// prompts is badly typed so we have some more specific typed APIs
// prompts also returns an undefined value on SIGINT which we really just want to exit on

export async function confirmPrompt(message: string): Promise<boolean> {
const { value } = await prompts({
name: 'value',
type: 'confirm',
message,
initial: true,
});
if (value === undefined) {
process.exit(1);
}
return value;
}
18 changes: 0 additions & 18 deletions packages-next/keystone/src/lib/saveSchemaAndTypes.ts

This file was deleted.

31 changes: 13 additions & 18 deletions packages-next/keystone/src/scripts/build/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ 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 '..';
import { generateNodeModulesArtifacts, validateCommittedArtifacts } from '../../lib/artifacts';
import { CONFIG_PATH, getAdminPath } from '../utils';

// FIXME: Duplicated from admin-ui package. Need to decide on a common home.
async function writeAdminFile(file: AdminFileToWrite, projectAdminPath: string) {
Expand Down Expand Up @@ -83,34 +82,30 @@ const reexportKeystoneConfig = async (projectAdminPath: string, isDisabled?: boo
await Promise.all(files.map(file => writeAdminFile(file, projectAdminPath)));
};

export async function build({ dotKeystonePath, projectAdminPath }: StaticPaths) {
export async function build(cwd: string) {
console.log('✨ Building Keystone');

const config = initConfig(requireSource(CONFIG_PATH).default);

const { keystone, graphQLSchema } = createSystem(config, dotKeystonePath, 'none');
const { keystone, graphQLSchema } = createSystem(config, 'none-skip-client-generation');

console.log('✨ Generating graphQL schema');
await saveSchemaAndTypes(graphQLSchema, keystone, dotKeystonePath);
await validateCommittedArtifacts(graphQLSchema, keystone, cwd);

console.log('✨ Generating database client');
// FIXME: This needs to generate clients for the correct build target using binaryTarget
// https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#binarytargets-options
await generateNodeModulesArtifacts(graphQLSchema, keystone, cwd);

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

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

console.log('✨ Generating database client');
// FIXME: This should never generate a migratration... right?
// FIXME: This needs to generate clients for the correct build target using binaryTarget
// https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#binarytargets-options
if (keystone.adapter.name === 'prisma') {
await keystone.adapter._generateClient(keystone._consolidateRelationships());
}
await reexportKeystoneConfig(getAdminPath(cwd), config.ui?.isDisabled);

console.log('✨ Building Admin UI');
await buildAdminUI(projectAdminPath);
await buildAdminUI(getAdminPath(cwd));
}
Loading

1 comment on commit c28e765

@vercel
Copy link

@vercel vercel bot commented on c28e765 Mar 30, 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.