diff --git a/.changeset/flat-beds-help.md b/.changeset/flat-beds-help.md new file mode 100644 index 00000000000..ed0459871a5 --- /dev/null +++ b/.changeset/flat-beds-help.md @@ -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` diff --git a/.changeset/gentle-flies-invent.md b/.changeset/gentle-flies-invent.md new file mode 100644 index 00000000000..83245fdaafd --- /dev/null +++ b/.changeset/gentle-flies-invent.md @@ -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. diff --git a/.changeset/gentle-garlics-learn.md b/.changeset/gentle-garlics-learn.md new file mode 100644 index 00000000000..6c65fe9b5fa --- /dev/null +++ b/.changeset/gentle-garlics-learn.md @@ -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. diff --git a/.changeset/giant-buses-sip.md b/.changeset/giant-buses-sip.md new file mode 100644 index 00000000000..7bd987ab2b9 --- /dev/null +++ b/.changeset/giant-buses-sip.md @@ -0,0 +1,5 @@ +--- +'@keystone-next/adapter-prisma-legacy': minor +--- + +Added `devMigrations` and `runPrototypeMigrations` exports. diff --git a/.changeset/grumpy-needles-give.md b/.changeset/grumpy-needles-give.md new file mode 100644 index 00000000000..fca290c0a16 --- /dev/null +++ b/.changeset/grumpy-needles-give.md @@ -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. diff --git a/.changeset/modern-cycles-rule.md b/.changeset/modern-cycles-rule.md new file mode 100644 index 00000000000..4d2c30cc741 --- /dev/null +++ b/.changeset/modern-cycles-rule.md @@ -0,0 +1,6 @@ +--- +'@keystone-next/keystone': major +'@keystone-next/test-utils-legacy': patch +--- + +Removed `dotKeystonePath` argument from `createSystem` diff --git a/.changeset/plenty-jars-swim.md b/.changeset/plenty-jars-swim.md new file mode 100644 index 00000000000..dda55caa2b2 --- /dev/null +++ b/.changeset/plenty-jars-swim.md @@ -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. diff --git a/.changeset/purple-gifts-accept.md b/.changeset/purple-gifts-accept.md new file mode 100644 index 00000000000..f0e8a548040 --- /dev/null +++ b/.changeset/purple-gifts-accept.md @@ -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. diff --git a/.changeset/tiny-impalas-grab.md b/.changeset/tiny-impalas-grab.md new file mode 100644 index 00000000000..a609b92855c --- /dev/null +++ b/.changeset/tiny-impalas-grab.md @@ -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. diff --git a/.gitignore b/.gitignore index 6d14263a45a..5e526db20f0 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/packages-next/admin-ui/src/templates/api.ts b/packages-next/admin-ui/src/templates/api.ts index de163f46fe0..01d75117313 100644 --- a/packages-next/admin-ui/src/templates/api.ts +++ b/packages-next/admin-ui/src/templates/api.ts @@ -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, diff --git a/packages-next/keystone/package.json b/packages-next/keystone/package.json index 1423c38031a..38c0e30fef2 100644 --- a/packages-next/keystone/package.json +++ b/packages-next/keystone/package.json @@ -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", @@ -31,6 +32,7 @@ "@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", @@ -38,6 +40,7 @@ "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", @@ -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", diff --git a/packages-next/keystone/src/lib/artifacts.ts b/packages-next/keystone/src/lib/artifacts.ts new file mode 100644 index 00000000000..3fd05cd3ea7 --- /dev/null +++ b/packages-next/keystone/src/lib/artifacts.ts @@ -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 { + 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; +} diff --git a/packages-next/keystone/src/lib/createKeystone.ts b/packages-next/keystone/src/lib/createKeystone.ts index d03a8b83d9c..4e1f2a76652 100644 --- a/packages-next/keystone/src/lib/createKeystone.ts +++ b/packages-next/keystone/src/lib/createKeystone.ts @@ -1,4 +1,3 @@ -import path from 'path'; // @ts-ignore import { Keystone } from '@keystone-next/keystone-legacy'; import { PrismaAdapter } from '@keystone-next/adapter-prisma-legacy'; @@ -6,7 +5,6 @@ import type { KeystoneConfig, BaseKeystone, MigrationAction } from '@keystone-ne export function createKeystone( config: KeystoneConfig, - dotKeystonePath: string, migrationAction: MigrationAction, prismaClient?: any ) { @@ -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, @@ -32,7 +29,6 @@ export function createKeystone( ); } adapter = new PrismaAdapter({ - getPrismaPath: () => path.join(dotKeystonePath, 'prisma'), prismaClient, migrationMode: migrationAction === 'dev' ? (db.useMigrations ? 'dev' : 'prototype') : migrationAction, diff --git a/packages-next/keystone/src/lib/createSystem.ts b/packages-next/keystone/src/lib/createSystem.ts index fa4117acc91..564fa414577 100644 --- a/packages-next/keystone/src/lib/createSystem.ts +++ b/packages-next/keystone/src/lib/createSystem.ts @@ -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'); diff --git a/packages-next/keystone/src/lib/prompts.ts b/packages-next/keystone/src/lib/prompts.ts new file mode 100644 index 00000000000..089e2c3868c --- /dev/null +++ b/packages-next/keystone/src/lib/prompts.ts @@ -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 { + const { value } = await prompts({ + name: 'value', + type: 'confirm', + message, + initial: true, + }); + if (value === undefined) { + process.exit(1); + } + return value; +} diff --git a/packages-next/keystone/src/lib/saveSchemaAndTypes.ts b/packages-next/keystone/src/lib/saveSchemaAndTypes.ts deleted file mode 100644 index 5a9b900cc07..00000000000 --- a/packages-next/keystone/src/lib/saveSchemaAndTypes.ts +++ /dev/null @@ -1,18 +0,0 @@ -import path from 'path'; -import { printSchema, GraphQLSchema } from 'graphql'; -import { outputFile } from 'fs-extra'; -import type { BaseKeystone } from '@keystone-next/types'; -import { printGeneratedTypes } from './schema-type-printer'; - -export async function saveSchemaAndTypes( - graphQLSchema: GraphQLSchema, - keystone: BaseKeystone, - dotKeystonePath: string -) { - const printedSchema = printSchema(graphQLSchema); - await outputFile(path.join(dotKeystonePath, 'schema.graphql'), printedSchema); - await outputFile( - path.join(dotKeystonePath, 'schema-types.ts'), - printGeneratedTypes(printedSchema, keystone, graphQLSchema) - ); -} diff --git a/packages-next/keystone/src/scripts/build/build.ts b/packages-next/keystone/src/scripts/build/build.ts index 439d41148d5..c6581e44f86 100644 --- a/packages-next/keystone/src/scripts/build/build.ts +++ b/packages-next/keystone/src/scripts/build/build.ts @@ -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) { @@ -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)); } diff --git a/packages-next/keystone/src/scripts/index.ts b/packages-next/keystone/src/scripts/index.ts index be7e9fc24af..8e2e48919ea 100644 --- a/packages-next/keystone/src/scripts/index.ts +++ b/packages-next/keystone/src/scripts/index.ts @@ -1,15 +1,11 @@ -import path from 'path'; import meow from 'meow'; import { dev } from './run/dev'; import { start } from './run/start'; import { build } from './build/build'; -import { deploy } from './migrate/deploy'; -import { generate } from './migrate/generate'; -import { reset } from './migrate/reset'; +import { prisma } from './prisma'; +import { postinstall } from './postinstall'; -export type StaticPaths = { dotKeystonePath: string; projectAdminPath: string }; - -const commands = { dev, start, build, deploy, generate, reset }; +const commands = { dev, start, build, prisma, postinstall }; function cli() { const { input, help, flags } = meow( @@ -17,21 +13,15 @@ function cli() { Usage $ keystone-next [command] Commands - Run dev (default) start the project in development mode start start the project in production mode - Build build build the project (must be done before using start) - Migrate (Prisma only) - reset reset the database (this will drop all data!) - generate generate a migration - deploy deploy all migrations + prisma run the prisma CLI + postinstall `, { flags: { - allowEmpty: { default: false, type: 'boolean' }, - acceptDataLoss: { default: false, type: 'boolean' }, - name: { type: 'string' }, + fix: { default: false, type: 'boolean' }, }, } ); @@ -42,16 +32,14 @@ function cli() { process.exit(1); } - // These paths are non-configurable, as we need to use them - // to find the config file (for `start`) itself! - const dotKeystonePath = path.resolve('.keystone'); - const projectAdminPath = path.join(dotKeystonePath, 'admin'); - const staticPaths: StaticPaths = { dotKeystonePath, projectAdminPath }; + const cwd = process.cwd(); - if (command === 'generate') { - generate(staticPaths, flags); + if (command === 'prisma') { + prisma(cwd, process.argv.slice(3)); + } else if (command === 'postinstall') { + postinstall(cwd, flags.fix); } else { - commands[command](staticPaths); + commands[command](cwd); } } diff --git a/packages-next/keystone/src/scripts/migrate/deploy.ts b/packages-next/keystone/src/scripts/migrate/deploy.ts deleted file mode 100644 index 989876e65ec..00000000000 --- a/packages-next/keystone/src/scripts/migrate/deploy.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { createKeystone } from '../../lib/createKeystone'; -import { initConfig } from '../../lib/initConfig'; -import { requireSource } from '../../lib/requireSource'; -import { CONFIG_PATH } from '../utils'; -import type { StaticPaths } from '..'; - -export const deploy = async ({ dotKeystonePath }: StaticPaths) => { - const config = initConfig(requireSource(CONFIG_PATH).default); - - if (config.db.adapter !== 'prisma_postgresql' && config.db.adapter !== 'prisma_sqlite') { - console.log('keystone-next deploy only supports Prisma'); - process.exit(1); - } - - if (!config.db.useMigrations) { - console.log('db.useMigrations must be set to true in your config to use keystone-next deploy'); - process.exit(1); - } - - const keystone = createKeystone(config, dotKeystonePath, 'none'); - - console.log('✨ Deploying migrations'); - await keystone.adapter.deploy(keystone._consolidateRelationships()); - console.log('✅ Deployed migrations'); -}; diff --git a/packages-next/keystone/src/scripts/migrate/generate.ts b/packages-next/keystone/src/scripts/migrate/generate.ts deleted file mode 100644 index 01b25bff899..00000000000 --- a/packages-next/keystone/src/scripts/migrate/generate.ts +++ /dev/null @@ -1,38 +0,0 @@ -import path from 'path'; -import { - CLIOptionsForCreateMigration, - createMigration, -} from '@keystone-next/adapter-prisma-legacy'; -import { createSystem } from '../../lib/createSystem'; -import { initConfig } from '../../lib/initConfig'; -import { requireSource } from '../../lib/requireSource'; -import { CONFIG_PATH } from '../utils'; -import type { StaticPaths } from '..'; - -export const generate = async ( - { dotKeystonePath }: StaticPaths, - args: CLIOptionsForCreateMigration -) => { - const config = initConfig(requireSource(CONFIG_PATH).default); - if (config.db.adapter !== 'prisma_postgresql' && config.db.adapter !== 'prisma_sqlite') { - console.log('keystone-next generate only supports Prisma'); - process.exit(1); - } - - if (!config.db.useMigrations) { - console.log( - 'db.useMigrations must be set to true in your config to use keystone-next generate' - ); - process.exit(1); - } - - const { keystone } = createSystem(config, dotKeystonePath, 'none'); - - await keystone.adapter._generateClient(keystone._consolidateRelationships()); - await createMigration( - config.db.url, - keystone.adapter.prismaSchema, - path.resolve(keystone.adapter.schemaPath), - args - ); -}; diff --git a/packages-next/keystone/src/scripts/migrate/reset.ts b/packages-next/keystone/src/scripts/migrate/reset.ts deleted file mode 100644 index d7346c1049b..00000000000 --- a/packages-next/keystone/src/scripts/migrate/reset.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { createKeystone } from '../../lib/createKeystone'; -import { initConfig } from '../../lib/initConfig'; -import { requireSource } from '../../lib/requireSource'; -import { CONFIG_PATH } from '../utils'; -import type { StaticPaths } from '..'; - -export const reset = async ({ dotKeystonePath }: StaticPaths) => { - const config = initConfig(requireSource(CONFIG_PATH).default); - const keystone = createKeystone(config, dotKeystonePath, 'none'); - - console.log('✨ Resetting database'); - await keystone.adapter._prepareSchema(keystone._consolidateRelationships()); - await keystone.adapter.dropDatabase(); - console.log('✅ Database reset'); -}; diff --git a/packages-next/keystone/src/scripts/postinstall.ts b/packages-next/keystone/src/scripts/postinstall.ts new file mode 100644 index 00000000000..326af41a869 --- /dev/null +++ b/packages-next/keystone/src/scripts/postinstall.ts @@ -0,0 +1,24 @@ +import { createSystem } from '../lib/createSystem'; +import { + generateCommittedArtifacts, + generateNodeModulesArtifacts, + validateCommittedArtifacts, +} from '../lib/artifacts'; +import { requireSource } from '../lib/requireSource'; +import { initConfig } from '../lib/initConfig'; +import { CONFIG_PATH } from './utils'; + +export async function postinstall(cwd: string, shouldFix: boolean) { + const config = initConfig(requireSource(CONFIG_PATH).default); + + const { keystone, graphQLSchema } = createSystem(config, 'none-skip-client-generation'); + + if (shouldFix) { + await generateCommittedArtifacts(graphQLSchema, keystone, cwd); + console.log('✨ Generated GraphQL and Prisma schemas'); + } else { + await validateCommittedArtifacts(graphQLSchema, keystone, cwd); + console.log('✨ GraphQL and Prisma schemas are up to date'); + } + await generateNodeModulesArtifacts(graphQLSchema, keystone, cwd); +} diff --git a/packages-next/keystone/src/scripts/prisma.ts b/packages-next/keystone/src/scripts/prisma.ts new file mode 100644 index 00000000000..ac208fc18f7 --- /dev/null +++ b/packages-next/keystone/src/scripts/prisma.ts @@ -0,0 +1,24 @@ +import execa from 'execa'; +import { createSystem } from '../lib/createSystem'; +import { generateNodeModulesArtifacts, validateCommittedArtifacts } from '../lib/artifacts'; +import { requireSource } from '../lib/requireSource'; +import { initConfig } from '../lib/initConfig'; +import { CONFIG_PATH } from './utils'; + +export async function prisma(cwd: string, args: string[]) { + const config = initConfig(requireSource(CONFIG_PATH).default); + + const { keystone, graphQLSchema } = createSystem(config, 'none-skip-client-generation'); + + await validateCommittedArtifacts(graphQLSchema, keystone, cwd); + await generateNodeModulesArtifacts(graphQLSchema, keystone, cwd); + + await execa('node', [require.resolve('prisma'), ...args], { + cwd, + stdio: 'inherit', + env: { + ...process.env, + DATABASE_URL: config.db.url, + }, + }); +} diff --git a/packages-next/keystone/src/scripts/run/dev.ts b/packages-next/keystone/src/scripts/run/dev.ts index 8a42db25885..adbe80507be 100644 --- a/packages-next/keystone/src/scripts/run/dev.ts +++ b/packages-next/keystone/src/scripts/run/dev.ts @@ -1,13 +1,18 @@ import path from 'path'; import express from 'express'; import { generateAdminUI } from '@keystone-next/admin-ui/system'; +import { devMigrations, runPrototypeMigrations } from '@keystone-next/adapter-prisma-legacy'; import { createSystem } from '../../lib/createSystem'; import { initConfig } from '../../lib/initConfig'; import { requireSource } from '../../lib/requireSource'; import { createExpressServer } from '../../lib/createExpressServer'; -import { saveSchemaAndTypes } from '../../lib/saveSchemaAndTypes'; -import { CONFIG_PATH } from '../utils'; -import type { StaticPaths } from '..'; +import { + generateCommittedArtifacts, + generateNodeModulesArtifacts, + getSchemaPaths, + requirePrismaClient, +} from '../../lib/artifacts'; +import { CONFIG_PATH, getAdminPath } from '../utils'; // TODO: Don't generate or start an Admin UI if it isn't configured!! const devLoadingHTMLFilepath = path.join( @@ -17,7 +22,7 @@ const devLoadingHTMLFilepath = path.join( 'dev-loading.html' ); -export const dev = async ({ dotKeystonePath, projectAdminPath }: StaticPaths) => { +export const dev = async (cwd: string) => { console.log('✨ Starting Keystone'); const server = express(); @@ -25,10 +30,29 @@ export const dev = async ({ dotKeystonePath, projectAdminPath }: StaticPaths) => const config = initConfig(requireSource(CONFIG_PATH).default); const initKeystone = async () => { - const { keystone, graphQLSchema, createContext } = createSystem(config, dotKeystonePath, 'dev'); + { + const { keystone, graphQLSchema } = createSystem(config, 'none-skip-client-generation'); - console.log('✨ Generating graphQL schema'); - await saveSchemaAndTypes(graphQLSchema, keystone, dotKeystonePath); + console.log('✨ Generating GraphQL and Prisma schemas'); + const prismaSchema = (await generateCommittedArtifacts(graphQLSchema, keystone, cwd)).prisma; + await generateNodeModulesArtifacts(graphQLSchema, keystone, cwd); + + if (config.db.adapter === 'prisma_postgresql' || config.db.adapter === 'prisma_sqlite') { + if (config.db.useMigrations) { + await devMigrations(config.db.url, prismaSchema, getSchemaPaths(cwd).prisma); + } else { + await runPrototypeMigrations(config.db.url, prismaSchema, getSchemaPaths(cwd).prisma); + } + } + } + + const prismaClient = requirePrismaClient(cwd); + + const { keystone, graphQLSchema, createContext } = createSystem( + config, + 'none-skip-client-generation', + prismaClient + ); console.log('✨ Connecting to the database'); await keystone.connect({ context: createContext().sudo() }); @@ -37,7 +61,7 @@ export const dev = async ({ dotKeystonePath, projectAdminPath }: StaticPaths) => 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('✨ Creating server'); @@ -46,7 +70,7 @@ export const dev = async ({ dotKeystonePath, projectAdminPath }: StaticPaths) => graphQLSchema, createContext, true, - projectAdminPath + getAdminPath(cwd) ); console.log(`👋 Admin UI and graphQL API ready`); }; diff --git a/packages-next/keystone/src/scripts/run/start.ts b/packages-next/keystone/src/scripts/run/start.ts index c3be0406234..1caa7b4a39f 100644 --- a/packages-next/keystone/src/scripts/run/start.ts +++ b/packages-next/keystone/src/scripts/run/start.ts @@ -3,22 +3,23 @@ import * as fs from 'fs-extra'; import { createSystem } from '../../lib/createSystem'; import { initConfig } from '../../lib/initConfig'; import { createExpressServer } from '../../lib/createExpressServer'; -import type { StaticPaths } from '..'; +import { getAdminPath } from '../utils'; +import { requirePrismaClient } from '../../lib/artifacts'; -export const start = async ({ dotKeystonePath, projectAdminPath }: StaticPaths) => { +export const start = async (cwd: string) => { console.log('✨ Starting Keystone'); // This is the compiled version of the configuration which was generated during the build step. // See reexportKeystoneConfig(). - const apiFile = path.join(projectAdminPath, '.next/server/pages/api/__keystone_api_build.js'); + const apiFile = path.join(getAdminPath(cwd), '.next/server/pages/api/__keystone_api_build.js'); if (!fs.existsSync(apiFile)) { throw new Error('keystone-next build must be run before running keystone-next start'); } const config = initConfig(require(apiFile).config); const { keystone, graphQLSchema, createContext } = createSystem( config, - dotKeystonePath, - 'none-skip-client-generation' + 'none-skip-client-generation', + requirePrismaClient(cwd) ); console.log('✨ Connecting to the database'); @@ -30,7 +31,7 @@ export const start = async ({ dotKeystonePath, projectAdminPath }: StaticPaths) graphQLSchema, createContext, false, - projectAdminPath + getAdminPath(cwd) ); if (config.ui?.isDisabled) { console.log(`👋 GraphQL API ready`); diff --git a/packages-next/keystone/src/scripts/utils.ts b/packages-next/keystone/src/scripts/utils.ts index d6f5aa106c1..54879d2dde2 100644 --- a/packages-next/keystone/src/scripts/utils.ts +++ b/packages-next/keystone/src/scripts/utils.ts @@ -1,3 +1,7 @@ import path from 'path'; // TODO: Read config path from process args export const CONFIG_PATH = path.join(process.cwd(), 'keystone'); + +export function getAdminPath(cwd: string) { + return path.join(cwd, '.keystone/admin'); +} diff --git a/packages-next/types/src/core.ts b/packages-next/types/src/core.ts index 4fb2b6f0a89..0aecfdd0b96 100644 --- a/packages-next/types/src/core.ts +++ b/packages-next/types/src/core.ts @@ -67,4 +67,4 @@ export function getGqlNames({ }; } -export type MigrationAction = 'none-skip-client-generation' | 'none' | 'dev'; +export type MigrationAction = 'none-skip-client-generation' | 'dev'; diff --git a/packages/adapter-prisma/src/index.ts b/packages/adapter-prisma/src/index.ts index 80b3e352461..3526376d795 100644 --- a/packages/adapter-prisma/src/index.ts +++ b/packages/adapter-prisma/src/index.ts @@ -1,3 +1,3 @@ export { PrismaAdapter, PrismaListAdapter, PrismaFieldAdapter } from './adapter-prisma'; -export { createMigration } from './migrations'; +export { createMigration, devMigrations, runPrototypeMigrations } from './migrations'; export type { CLIOptionsForCreateMigration } from './migrations'; diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts index 61b551ea23f..54c5646c94d 100644 --- a/packages/test-utils/src/index.ts +++ b/packages/test-utils/src/index.ts @@ -78,11 +78,7 @@ async function setupFromConfig({ } const _config = initConfig({ ...config, db: db!, ui: { isDisabled: true } }); - const { keystone, createContext, graphQLSchema } = createSystem( - _config, - path.resolve('.keystone'), - 'dev' - ); + const { keystone, createContext, graphQLSchema } = createSystem(_config, 'dev'); const app = await createExpressServer(_config, graphQLSchema, createContext, true, '', false); diff --git a/tests/examples-smoke-tests/utils.ts b/tests/examples-smoke-tests/utils.ts index 7479e8d641c..e5a1dbb02e9 100644 --- a/tests/examples-smoke-tests/utils.ts +++ b/tests/examples-smoke-tests/utils.ts @@ -5,7 +5,7 @@ import _treeKill from 'tree-kill'; import * as playwright from 'playwright'; async function deleteAllData(projectDir: string) { - const { PrismaClient } = require(path.join(projectDir, './.keystone/prisma/generated-client')); + const { PrismaClient } = require(path.join(projectDir, 'node_modules/.prisma/client')); let prisma = new PrismaClient(); diff --git a/yarn.lock b/yarn.lock index c5ddfa25187..96b72788ad4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11351,6 +11351,13 @@ prism-react-renderer@^1.2.0: resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.2.0.tgz#5ad4f90c3e447069426c8a53a0eafde60909cdf4" integrity sha512-GHqzxLYImx1iKN1jJURcuRoA/0ygCcNhfGw1IT8nPIMzarmKQ3Nc+JcG0gi8JXQzuh0C5ShE4npMIoqNin40hg== +prisma@2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/prisma/-/prisma-2.19.0.tgz#2c14f9cbbfb0ab69c8a9473e16736759713d29ad" + integrity sha512-iartCNVrtR4XT20ABN3zrSi3R/pCBe75Y0ZH8681QIGm8qjRQzf3DnbscPZgZ9iY4KFuVxL8ZrBQVDmRhpN0EQ== + dependencies: + "@prisma/engines" "2.19.0-39.c1455d0b443d66b0d9db9bcb1bb9ee0d5bbc511d" + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"