From b37cbffc886a4317793a97b7a8afd95639f59ce0 Mon Sep 17 00:00:00 2001 From: Mitchell Hamilton Date: Mon, 22 Mar 2021 08:15:58 +1000 Subject: [PATCH] Replace keystone-next prototype with keystone-next dev and a config option to determine if migrations should be used or not (#5163) * Replace keystone-next prototype with keystone-next dev and a config option to determine if migrations should be used or not * Fix test utils * Update cypress test * Fix a thing * Fix the tests * Make smoke tests run dev * Update purple-swans-sort.md * Apply suggestions from code review Co-authored-by: Tim Leslie Co-authored-by: Tim Leslie --- .changeset/purple-swans-sort.md | 6 +++++ .changeset/tame-frogs-exercise.md | 7 ++++++ docs-next/cypress/integration/copy-link.js | 4 +-- docs-next/pages/apis/config.mdx | 4 +++ docs-next/pages/guides/cli.mdx | 25 ++++++++----------- .../graphql-api-endpoint/package.json | 2 +- examples-next/sandbox/package.json | 2 +- examples-next/todo/package.json | 1 - .../keystone/src/lib/createKeystone.ts | 9 ++++--- .../keystone/src/lib/createSystem.ts | 6 ++--- packages-next/keystone/src/scripts/index.ts | 8 +++--- .../keystone/src/scripts/migrate/deploy.ts | 14 +++++++++-- .../keystone/src/scripts/migrate/generate.ts | 12 +++++++++ .../keystone/src/scripts/migrate/reset.ts | 3 +-- packages-next/keystone/src/scripts/run/dev.ts | 12 ++------- .../keystone/src/scripts/run/prototype.ts | 4 --- packages-next/types/src/config/index.ts | 2 ++ packages-next/types/src/core.ts | 2 +- packages/test-utils/src/index.ts | 2 +- tests/examples-smoke-tests/utils.ts | 4 +-- 20 files changed, 76 insertions(+), 53 deletions(-) create mode 100644 .changeset/purple-swans-sort.md create mode 100644 .changeset/tame-frogs-exercise.md delete mode 100644 packages-next/keystone/src/scripts/run/prototype.ts diff --git a/.changeset/purple-swans-sort.md b/.changeset/purple-swans-sort.md new file mode 100644 index 00000000000..5b5e47cd054 --- /dev/null +++ b/.changeset/purple-swans-sort.md @@ -0,0 +1,6 @@ +--- +'@keystone-next/keystone': major +'@keystone-next/types': minor +--- + +Added `db.useMigrations` option to replace using `keystone-next dev` and `keystone-next prototype` depending on what kind of migration strategy you want to use. If you were previously using `keystone-next dev`, you should set `db.useMigrations` to true in your config and continue using `keystone-next dev`. If you were previously using `keystone-next prototype`, you should now use `keystone-next dev`. diff --git a/.changeset/tame-frogs-exercise.md b/.changeset/tame-frogs-exercise.md new file mode 100644 index 00000000000..0783c08204b --- /dev/null +++ b/.changeset/tame-frogs-exercise.md @@ -0,0 +1,7 @@ +--- +'@keystone-next/types': major +'@keystone-next/keystone': major +'@keystone-next/test-utils-legacy': patch +--- + +Replaced `MigrationMode` type with `MigrationAction` that `createSystem` and `createKeystone` now accept. diff --git a/docs-next/cypress/integration/copy-link.js b/docs-next/cypress/integration/copy-link.js index 1d89e3d4faa..6e124c598c8 100644 --- a/docs-next/cypress/integration/copy-link.js +++ b/docs-next/cypress/integration/copy-link.js @@ -16,10 +16,10 @@ describe('on click of a link icon', () => { .then(text => expect(text).to.equal('http://localhost:8000/guides/cli#run')); }); it('changes the URL to the specified anchor href', () => { - cy.get('#keystone-next-prototype-default a').click(); + cy.get('#keystone-next-dev-default a').click(); return cy.window().then(win => { expect(win.location.href).to.equal( - 'http://localhost:8000/guides/cli#keystone-next-prototype-default' + 'http://localhost:8000/guides/cli#keystone-next-dev-default' ); }); }); diff --git a/docs-next/pages/apis/config.mdx b/docs-next/pages/apis/config.mdx index 333dbeafb12..31571bb346c 100644 --- a/docs-next/pages/apis/config.mdx +++ b/docs-next/pages/apis/config.mdx @@ -73,6 +73,7 @@ Advanced configuration: - `enableLogging` (default: `false`): Enable logging from the Prisma client. - `getPrismaPath` (default: `() => '.keystone/prisma'` ): Set the location of the generated Prisma schema and client. - `getDbSchemaName` (default: `() => 'public'` ): Set the schema named used in the database. +- `useMigrations` (default: `false`): Determines whether to use migrations or automatically force-update the database with the latest schema and potentially lose data. The functions for `getPrismaPath` and `getDbSchemaName` are each provided with the generated Prisma schema as a `string` in the `{ prismaSchema }` argument. @@ -86,6 +87,7 @@ export default config({ enableLogging: true, getPrismaPath: ({ prismaSchema }) => '.prisma', getDbSchemaName: ({ prismaSchema }) => 'prisma', + useMigrations: true, }, /* ... */ }); @@ -100,6 +102,7 @@ Advanced configuration: - `enableLogging` (default: `false`): Enable logging from the Prisma client. - `getPrismaPath` (default: `() => '.keystone/prisma'` ): Set the location of the generated Prisma schema and client. +- `useMigrations` (default: `false`): Determines whether to use migrations or automatically force-update the database with the latest schema and potentially lose data. The function for `getPrismaPath` is provided with the generated Prisma schema as a `string` in the `{ prismaSchema }` argument. @@ -112,6 +115,7 @@ export default config({ // Optional advanced configuration enableLogging: true, getPrismaPath: ({ prismaSchema }) => '.prisma', + useMigrations: true, }, /* ... */ }); diff --git a/docs-next/pages/guides/cli.mdx b/docs-next/pages/guides/cli.mdx index 4ff15f2f25c..beb810daa6b 100644 --- a/docs-next/pages/guides/cli.mdx +++ b/docs-next/pages/guides/cli.mdx @@ -10,8 +10,7 @@ Usage $ keystone-next [command] Commands Run - prototype (default) start the project in prototyping mode - dev start the project in development mode + 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) @@ -26,25 +25,17 @@ The CLI supports commands in three categories; Run, Build and Migrate. ### Run The **run** commands are used to prepare and then start your Keystone server. -Keystone can be run in three different modes; **prototyping**, **dev** and **production**. -These different modes support different phases of your project life-cycle. -The different modes differ in how they interact with your database and with your Admin UI application. +Keystone can be run in two different modes; **dev** and **production**. Note: All the **run** commands expect to find a module called `keystone.ts` with a default export which returns a system configuration `config()` from `@keystone-next/keystone/schema`. See the [System Configuration API](../apis/config) for details on how to configure a Keystone system. -#### keystone-next prototype (default) +#### keystone-next dev (default) -In **prototyping** mode, Keystone will try its hardest to put your database into a state which is consistent with your schema. -This might require Keystone to delete data in your database. -This mode of operation should only be used when you are first getting started with Keystone and are not yet working with real data. -In prototyping mode you can quickly change your schema and immediately see the changes reflected in your database and Admin UI when you restart. +In **dev** mode, Keystone will start the dev server and update your database is a different way depending on `db.useMigrations`: -#### keystone-next dev - -**Dev** mode is identical to **prototyping** mode when using the `knex` of `mongoose` adapters. -When using the `prisma` adapter, Keystone will use Prisma's migration framework to generate and locally apply migrations when you start your system. -If you are using `prisma` and your database already includes migrations then you must use **dev** mode, and not **prototyping** mode. +- If `db.useMigrations` is `false` (the default), Keystone will use Prisma Migrate to update your database so that it matches your schema. It may lose data while updating your database so you should only use this mode in initial development. +- If `db.useMigrations` is `true`, Keystone will use Prisma Migrate to apply any existing migrations and prompt you to create a migration if there was a change to your schema. #### keystone-next start @@ -69,6 +60,8 @@ You generally shouldn't need to use `keystone-next reset` directly unless you wa #### keystone-next generate +> keystone-next generate is only usable when db.useMigrations is set to true in your config + This command will generate a migration based on the current state of your database and your Keystone schema. The generated migration artefact should be added to your repository so that it can be shared with other developers and deployed in production. You should generally use `keystone-next dev` instead of `keystone-next generate` because it will prompt you to create a migration when you need to. @@ -81,6 +74,8 @@ You only need to use `keystone-next generate` when you want to generate an empty #### keystone-next deploy +> keystone-next deploy is only usable when db.useMigrations is set to true in your config + This command will apply any migrations in the migrations directory. It should be used in production to apply migrations before starting the server. diff --git a/examples-next/graphql-api-endpoint/package.json b/examples-next/graphql-api-endpoint/package.json index 062290b0210..4e8c09ec447 100644 --- a/examples-next/graphql-api-endpoint/package.json +++ b/examples-next/graphql-api-endpoint/package.json @@ -3,7 +3,7 @@ "version": "0.0.2", "private": true, "scripts": { - "dev": "keystone-next prototype", + "dev": "keystone-next dev", "start": "keystone-next start", "build": "keystone-next build" }, diff --git a/examples-next/sandbox/package.json b/examples-next/sandbox/package.json index 31db644fc6f..5602be7c109 100644 --- a/examples-next/sandbox/package.json +++ b/examples-next/sandbox/package.json @@ -4,7 +4,7 @@ "private": true, "license": "MIT", "scripts": { - "dev": "keystone-next prototype", + "dev": "keystone-next dev", "sandbox": "yarn && yarn dev" }, "dependencies": { diff --git a/examples-next/todo/package.json b/examples-next/todo/package.json index 2c47b38a740..1a890d4babf 100644 --- a/examples-next/todo/package.json +++ b/examples-next/todo/package.json @@ -4,7 +4,6 @@ "private": true, "license": "MIT", "scripts": { - "prototype": "keystone-next prototype", "dev": "keystone-next dev", "start": "keystone-next start", "build": "keystone-next build", diff --git a/packages-next/keystone/src/lib/createKeystone.ts b/packages-next/keystone/src/lib/createKeystone.ts index c22460dc0b8..26de13f8f54 100644 --- a/packages-next/keystone/src/lib/createKeystone.ts +++ b/packages-next/keystone/src/lib/createKeystone.ts @@ -7,12 +7,12 @@ import { MongooseAdapter } from '@keystone-next/adapter-mongoose-legacy'; import { KnexAdapter } from '@keystone-next/adapter-knex-legacy'; // @ts-ignore import { PrismaAdapter } from '@keystone-next/adapter-prisma-legacy'; -import type { KeystoneConfig, BaseKeystone, MigrationMode } from '@keystone-next/types'; +import type { KeystoneConfig, BaseKeystone, MigrationAction } from '@keystone-next/types'; export function createKeystone( config: KeystoneConfig, dotKeystonePath: string, - migrationMode: MigrationMode, + migrationAction: MigrationAction, prismaClient?: any ) { // Note: For backwards compatibility we may want to expose @@ -31,7 +31,8 @@ export function createKeystone( } else if (db.adapter === 'prisma_postgresql') { adapter = new PrismaAdapter({ getPrismaPath: () => path.join(dotKeystonePath, 'prisma'), - migrationMode, + migrationMode: + migrationAction === 'dev' ? (db.useMigrations ? 'dev' : 'prototype') : migrationAction, prismaClient, ...db, provider: 'postgresql', @@ -45,6 +46,8 @@ export function createKeystone( adapter = new PrismaAdapter({ getPrismaPath: () => path.join(dotKeystonePath, 'prisma'), prismaClient, + migrationMode: + migrationAction === 'dev' ? (db.useMigrations ? 'dev' : 'prototype') : migrationAction, ...db, provider: 'sqlite', }); diff --git a/packages-next/keystone/src/lib/createSystem.ts b/packages-next/keystone/src/lib/createSystem.ts index 731c1916b67..fa4117acc91 100644 --- a/packages-next/keystone/src/lib/createSystem.ts +++ b/packages-next/keystone/src/lib/createSystem.ts @@ -1,4 +1,4 @@ -import type { KeystoneConfig, MigrationMode } from '@keystone-next/types'; +import type { KeystoneConfig, MigrationAction } from '@keystone-next/types'; import { createGraphQLSchema } from './createGraphQLSchema'; import { makeCreateContext } from './createContext'; @@ -7,10 +7,10 @@ import { createKeystone } from './createKeystone'; export function createSystem( config: KeystoneConfig, dotKeystonePath: string, - migrationMode: MigrationMode, + migrationAction: MigrationAction, prismaClient?: any ) { - const keystone = createKeystone(config, dotKeystonePath, migrationMode, prismaClient); + const keystone = createKeystone(config, dotKeystonePath, migrationAction, prismaClient); const graphQLSchema = createGraphQLSchema(config, keystone, 'public'); diff --git a/packages-next/keystone/src/scripts/index.ts b/packages-next/keystone/src/scripts/index.ts index 7460f20c039..be7e9fc24af 100644 --- a/packages-next/keystone/src/scripts/index.ts +++ b/packages-next/keystone/src/scripts/index.ts @@ -1,6 +1,5 @@ import path from 'path'; import meow from 'meow'; -import { prototype } from './run/prototype'; import { dev } from './run/dev'; import { start } from './run/start'; import { build } from './build/build'; @@ -10,7 +9,7 @@ import { reset } from './migrate/reset'; export type StaticPaths = { dotKeystonePath: string; projectAdminPath: string }; -const commands = { prototype, dev, start, build, deploy, generate, reset }; +const commands = { dev, start, build, deploy, generate, reset }; function cli() { const { input, help, flags } = meow( @@ -19,8 +18,7 @@ function cli() { $ keystone-next [command] Commands Run - prototype (default) start the project in prototyping mode - dev start the project in development mode + 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) @@ -37,7 +35,7 @@ function cli() { }, } ); - const command = input[0] || 'prototype'; + const command = input[0] || 'dev'; if (!isCommand(command)) { console.log(`${command} is not a command that keystone-next accepts`); console.log(help); diff --git a/packages-next/keystone/src/scripts/migrate/deploy.ts b/packages-next/keystone/src/scripts/migrate/deploy.ts index f8f52b661c9..989876e65ec 100644 --- a/packages-next/keystone/src/scripts/migrate/deploy.ts +++ b/packages-next/keystone/src/scripts/migrate/deploy.ts @@ -5,11 +5,21 @@ import { CONFIG_PATH } from '../utils'; import type { StaticPaths } from '..'; export const deploy = async ({ dotKeystonePath }: StaticPaths) => { - console.log('🤞 Migrating Keystone'); - 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 index deb0932f21e..01b25bff899 100644 --- a/packages-next/keystone/src/scripts/migrate/generate.ts +++ b/packages-next/keystone/src/scripts/migrate/generate.ts @@ -14,6 +14,18 @@ export const generate = async ( 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()); diff --git a/packages-next/keystone/src/scripts/migrate/reset.ts b/packages-next/keystone/src/scripts/migrate/reset.ts index d40518ed9de..d7346c1049b 100644 --- a/packages-next/keystone/src/scripts/migrate/reset.ts +++ b/packages-next/keystone/src/scripts/migrate/reset.ts @@ -5,12 +5,11 @@ import { CONFIG_PATH } from '../utils'; import type { StaticPaths } from '..'; export const reset = async ({ dotKeystonePath }: StaticPaths) => { - console.log('🤞 Migrating Keystone'); - 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/run/dev.ts b/packages-next/keystone/src/scripts/run/dev.ts index f45cf3fa7e7..8fccb457c5b 100644 --- a/packages-next/keystone/src/scripts/run/dev.ts +++ b/packages-next/keystone/src/scripts/run/dev.ts @@ -1,7 +1,6 @@ import path from 'path'; import express from 'express'; import { generateAdminUI } from '@keystone-next/admin-ui/system'; -import { MigrationMode } from '@keystone-next/types'; import { createSystem } from '../../lib/createSystem'; import { initConfig } from '../../lib/initConfig'; import { requireSource } from '../../lib/requireSource'; @@ -18,10 +17,7 @@ const devLoadingHTMLFilepath = path.join( 'dev-loading.html' ); -export const dev = async ( - { dotKeystonePath, projectAdminPath }: StaticPaths, - migrationMode: MigrationMode = 'dev' -) => { +export const dev = async ({ dotKeystonePath, projectAdminPath }: StaticPaths) => { console.log('🤞 Starting Keystone'); const server = express(); @@ -29,11 +25,7 @@ export const dev = async ( const config = initConfig(requireSource(CONFIG_PATH).default); const initKeystone = async () => { - const { keystone, graphQLSchema, createContext } = createSystem( - config, - dotKeystonePath, - migrationMode - ); + const { keystone, graphQLSchema, createContext } = createSystem(config, dotKeystonePath, 'dev'); console.log('✨ Generating graphQL schema'); await saveSchemaAndTypes(graphQLSchema, keystone, dotKeystonePath); diff --git a/packages-next/keystone/src/scripts/run/prototype.ts b/packages-next/keystone/src/scripts/run/prototype.ts deleted file mode 100644 index 594480b6900..00000000000 --- a/packages-next/keystone/src/scripts/run/prototype.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { StaticPaths } from '..'; -import { dev } from './dev'; - -export const prototype = async (staticPaths: StaticPaths) => dev(staticPaths, 'prototype'); diff --git a/packages-next/types/src/config/index.ts b/packages-next/types/src/config/index.ts index 56c7a0d3ac4..43f0b73352f 100644 --- a/packages-next/types/src/config/index.ts +++ b/packages-next/types/src/config/index.ts @@ -64,12 +64,14 @@ export type DatabaseConfig = DatabaseCommon & ( | { adapter: 'prisma_postgresql'; + useMigrations?: boolean; enableLogging?: boolean; getPrismaPath?: (arg: { prismaSchema: any }) => string; getDbSchemaName?: (arg: { prismaSchema: any }) => string; } | { adapter: 'prisma_sqlite'; + useMigrations?: boolean; enableLogging?: boolean; getPrismaPath?: (arg: { prismaSchema: any }) => string; } diff --git a/packages-next/types/src/core.ts b/packages-next/types/src/core.ts index b8d0a3a67c5..4fb2b6f0a89 100644 --- a/packages-next/types/src/core.ts +++ b/packages-next/types/src/core.ts @@ -67,4 +67,4 @@ export function getGqlNames({ }; } -export type MigrationMode = 'none-skip-client-generation' | 'none' | 'dev' | 'prototype'; +export type MigrationAction = 'none-skip-client-generation' | 'none' | 'dev'; diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts index 98ffb7a9c96..bfa065328c9 100644 --- a/packages/test-utils/src/index.ts +++ b/packages/test-utils/src/index.ts @@ -95,7 +95,7 @@ async function setupFromConfig({ const { keystone, createContext, graphQLSchema } = createSystem( _config, path.resolve('.keystone'), - 'prototype' + '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 f3111838bfb..7479e8d641c 100644 --- a/tests/examples-smoke-tests/utils.ts +++ b/tests/examples-smoke-tests/utils.ts @@ -54,7 +54,7 @@ export const exampleProjectTests = ( await cleanupKeystoneProcess(); }); - async function startKeystone(command: 'start' | 'prototype') { + async function startKeystone(command: 'start' | 'dev') { let keystoneProcess = execa('yarn', ['keystone-next', command], { cwd: projectDir, env: process.env, @@ -84,7 +84,7 @@ export const exampleProjectTests = ( if (mode === 'dev') { test('start keystone in dev', async () => { - await startKeystone('prototype'); + await startKeystone('dev'); }); }