From 1e6d12f47076816d2a2441b42471176c5a7f2f8c Mon Sep 17 00:00:00 2001 From: Mitchell Hamilton Date: Wed, 31 Mar 2021 13:21:50 +1000 Subject: [PATCH] Move usages of @prisma/migrate and @prisma/sdk from adapter to @keystone-next/keystone (#5302) --- .changeset/dirty-moons-drive.md | 5 + .changeset/giant-buses-sip.md | 5 - .changeset/serious-dryers-argue.md | 5 + .changeset/ten-jokes-clean.md | 5 + .../keystone/migrations/package.json | 4 + packages-next/keystone/package.json | 5 + packages-next/keystone/src/artifacts.ts | 10 +- .../keystone/src/lib}/migrations.ts | 212 +----------------- packages-next/keystone/src/lib/prompts.ts | 12 + packages-next/keystone/src/migrations.ts | 1 + packages-next/keystone/src/scripts/run/dev.ts | 4 +- packages/adapter-prisma/package.json | 9 +- packages/adapter-prisma/src/adapter-prisma.js | 5 +- packages/adapter-prisma/src/index.ts | 2 - packages/adapter-prisma/src/prompts.ts | 29 --- packages/test-utils/src/index.ts | 4 +- 16 files changed, 51 insertions(+), 266 deletions(-) create mode 100644 .changeset/dirty-moons-drive.md delete mode 100644 .changeset/giant-buses-sip.md create mode 100644 .changeset/serious-dryers-argue.md create mode 100644 .changeset/ten-jokes-clean.md create mode 100644 packages-next/keystone/migrations/package.json rename {packages/adapter-prisma/src => packages-next/keystone/src/lib}/migrations.ts (52%) create mode 100644 packages-next/keystone/src/migrations.ts delete mode 100644 packages/adapter-prisma/src/prompts.ts diff --git a/.changeset/dirty-moons-drive.md b/.changeset/dirty-moons-drive.md new file mode 100644 index 00000000000..9f53b5ed3cd --- /dev/null +++ b/.changeset/dirty-moons-drive.md @@ -0,0 +1,5 @@ +--- +'@keystone-next/adapter-prisma-legacy': major +--- + +Removed `CLIOptionsForCreateMigration` and `createMigration` exports diff --git a/.changeset/giant-buses-sip.md b/.changeset/giant-buses-sip.md deleted file mode 100644 index 7bd987ab2b9..00000000000 --- a/.changeset/giant-buses-sip.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@keystone-next/adapter-prisma-legacy': minor ---- - -Added `devMigrations` and `runPrototypeMigrations` exports. diff --git a/.changeset/serious-dryers-argue.md b/.changeset/serious-dryers-argue.md new file mode 100644 index 00000000000..b955dd17dda --- /dev/null +++ b/.changeset/serious-dryers-argue.md @@ -0,0 +1,5 @@ +--- +'@keystone-next/adapter-prisma-legacy': major +--- + +Removed formatting of Prisma schema returned from `_generatePrismaSchema` method and made it return synchronously diff --git a/.changeset/ten-jokes-clean.md b/.changeset/ten-jokes-clean.md new file mode 100644 index 00000000000..dd72cecedf4 --- /dev/null +++ b/.changeset/ten-jokes-clean.md @@ -0,0 +1,5 @@ +--- +'@keystone-next/keystone': minor +--- + +Added `migrations` entrypoint with `pushPrismaSchemaToDatabase` export. diff --git a/packages-next/keystone/migrations/package.json b/packages-next/keystone/migrations/package.json new file mode 100644 index 00000000000..6cbbe144d0c --- /dev/null +++ b/packages-next/keystone/migrations/package.json @@ -0,0 +1,4 @@ +{ + "main": "dist/keystone.cjs.js", + "module": "dist/keystone.esm.js" +} diff --git a/packages-next/keystone/package.json b/packages-next/keystone/package.json index 9cc2509e9cd..92ad0c5be48 100644 --- a/packages-next/keystone/package.json +++ b/packages-next/keystone/package.json @@ -21,7 +21,10 @@ "@keystone-next/keystone-legacy": "^22.0.0", "@keystone-next/server-side-graphql-client-legacy": "3.0.0", "@keystone-next/types": "^15.0.1", + "@prisma/client": "2.19.0", + "@prisma/migrate": "2.19.0", "@prisma/sdk": "2.19.0", + "@sindresorhus/slugify": "^1.1.0", "@types/babel__core": "^7.1.14", "@types/cookie": "^0.4.0", "@types/express": "^4.17.11", @@ -36,6 +39,7 @@ "apollo-server-express": "^2.22.2", "apollo-server-micro": "^2.22.2", "apollo-server-types": "^0.7.0", + "chalk": "^4.1.0", "cookie": "^0.4.1", "cors": "^2.8.5", "execa": "^5.0.0", @@ -64,6 +68,7 @@ "entrypoints": [ "index.ts", "artifacts.ts", + "migrations.ts", "schema/index.ts", "session/index.ts", "scripts/index.ts" diff --git a/packages-next/keystone/src/artifacts.ts b/packages-next/keystone/src/artifacts.ts index d4706629ce9..65719876c48 100644 --- a/packages-next/keystone/src/artifacts.ts +++ b/packages-next/keystone/src/artifacts.ts @@ -2,7 +2,7 @@ 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 { getGenerator, formatSchema } from '@prisma/sdk'; import { confirmPrompt } from './lib/prompts'; import { printGeneratedTypes } from './lib/schema-type-printer'; @@ -24,9 +24,11 @@ export async function getCommittedArtifacts( ): Promise { return { graphql: printSchema(graphQLSchema), - prisma: await keystone.adapter._generatePrismaSchema({ - rels: keystone._consolidateRelationships(), - clientDir: 'node_modules/.prisma/client', + prisma: await formatSchema({ + schema: keystone.adapter._generatePrismaSchema({ + rels: keystone._consolidateRelationships(), + clientDir: 'node_modules/.prisma/client', + }), }), }; } diff --git a/packages/adapter-prisma/src/migrations.ts b/packages-next/keystone/src/lib/migrations.ts similarity index 52% rename from packages/adapter-prisma/src/migrations.ts rename to packages-next/keystone/src/lib/migrations.ts index d27fa75edcf..609354a0527 100644 --- a/packages/adapter-prisma/src/migrations.ts +++ b/packages-next/keystone/src/lib/migrations.ts @@ -37,7 +37,7 @@ async function withMigrate( } } -export async function runPrototypeMigrations( +export async function pushPrismaSchemaToDatabase( dbUrl: string, schema: string, schemaPath: string, @@ -68,216 +68,6 @@ export async function runPrototypeMigrations( } } -// https://github.com/prisma/prisma/blob/527b6bd35e7fe4dbe854653f872a07b25febeb65/src/packages/migrate/src/commands/MigrateDeploy.ts -export async function deployMigrations(dbUrl: string, schemaPath: string) { - return withMigrate(dbUrl, schemaPath, async migrate => { - const diagnoseResult = await runMigrateWithDbUrl(dbUrl, () => - migrate.diagnoseMigrationHistory({ - optInToShadowDatabase: false, - }) - ); - const listMigrationDirectoriesResult = await runMigrateWithDbUrl(dbUrl, () => - migrate.listMigrationDirectories() - ); - - console.info(); // empty line - if (listMigrationDirectoriesResult.migrations.length > 0) { - const migrations = listMigrationDirectoriesResult.migrations; - console.info( - `${migrations.length} migration${ - migrations.length > 1 ? 's' : '' - } found in .keystone/prisma/migrations` - ); - } else { - console.info(`No migration found in .keystone/prisma/migrations`); - } - - const editedMigrationNames = diagnoseResult.editedMigrationNames; - if (editedMigrationNames.length > 0) { - console.info( - `${chalk.yellow( - 'WARNING The following migrations have been modified since they were applied:' - )} -${editedMigrationNames.join('\n')}` - ); - } - - const { appliedMigrationNames: migrationIds } = await runMigrateWithDbUrl(dbUrl, () => - migrate.applyMigrations() - ); - - console.info(); // empty line - if (migrationIds.length === 0) { - console.log(chalk.greenBright(`No pending migrations to apply.`)); - } else { - console.log(`The following migration${ - migrationIds.length > 1 ? 's' : '' - } have been applied:\n\n${printFilesFromMigrationIds(migrationIds)} - -${chalk.greenBright('All migrations have been successfully applied.')}`); - } - }); -} - -// https://github.com/prisma/prisma/blob/527b6bd35e7fe4dbe854653f872a07b25febeb65/src/packages/migrate/src/commands/MigrateReset.ts -export async function resetDatabaseWithMigrations(dbUrl: string, schemaPath: string) { - return withMigrate(dbUrl, schemaPath, async migrate => { - await runMigrateWithDbUrl(dbUrl, () => migrate.reset()); - - const { appliedMigrationNames: migrationIds } = await runMigrateWithDbUrl(dbUrl, () => - migrate.applyMigrations() - ); - - console.log(`${chalk.green('Database reset successful')}`); - - if (migrationIds.length) { - console.info( - `\nThe following migration(s) have been applied:\n\n${printFilesFromMigrationIds( - migrationIds - )}` - ); - } - }); -} - -export type CLIOptionsForCreateMigration = { - allowEmpty: boolean; - acceptDataLoss: boolean; - name: string | undefined; -}; - -// TODO: don't have process.exit calls here -export async function createMigration( - dbUrl: string, - prismaSchema: string, - schemaPath: string, - cliOptions: CLIOptionsForCreateMigration -) { - return withMigrate(dbUrl, schemaPath, async migrate => { - // see if we need to reset the database - // note that the other action devDiagnostic can return is createMigration - // that doesn't necessarily mean that we need to create a migration - // it only means that we don't need to reset the database - const devDiagnostic = await runMigrateWithDbUrl(dbUrl, () => migrate.devDiagnostic()); - // when the action is reset, the database is somehow inconsistent with the migrations so we need to reset it - // (not just some migrations need to be applied but there's some inconsistency) - if (devDiagnostic.action.tag === 'reset') { - const credentials = uriToCredentials(dbUrl); - if (cliOptions.acceptDataLoss === false) { - console.log(`${devDiagnostic.action.reason} - - We need to reset the ${credentials.type} database "${ - credentials.database - }" at ${getDbLocation(credentials)}.`); - - if (!process.stdout.isTTY) { - console.log( - "We've detected that you're in a non-interactive environment so you need to pass --accept-data-loss to reset the database" - ); - process.exit(1); - } - const confirmedReset = await confirmPrompt( - `Do you want to continue? ${chalk.red('All data will be lost')}.` - ); - console.info(); // empty line - - if (!confirmedReset) { - console.info('Reset cancelled.'); - process.exit(0); - } - } - - // Do the reset - await migrate.reset(); - } - - let { appliedMigrationNames } = await runMigrateWithDbUrl(dbUrl, () => - migrate.applyMigrations() - ); - // Inform user about applied migrations now - if (appliedMigrationNames.length) { - console.info( - `✨ The following migration(s) have been applied:\n\n${printFilesFromMigrationIds( - appliedMigrationNames - )}` - ); - } - // evaluateDataLoss basically means "try to create a migration but don't write it" - // so we can tell the user whether it can be executed and if there will be data loss - const evaluateDataLossResult = await runMigrateWithDbUrl(dbUrl, () => - migrate.evaluateDataLoss() - ); - - // there are no steps to the migration so we need to make sure the user wants to create an empty migration - if (!evaluateDataLossResult.migrationSteps.length && cliOptions.allowEmpty === false) { - console.log('There have been no changes to your schema that require a migration'); - if (process.stdout.isTTY) { - if (!(await confirmPrompt('Are you sure that you want to create an empty migration?'))) { - process.exit(0); - } - } else { - console.log( - "We've detected that you're in a non-interactive environment so you need to pass --allow-empty to create an empty migration" - ); - // note this is a failure even though the migrations are up to date - // since the user said "i want to create a migration" and we've said no - process.exit(1); - } - } - - let migrationCanBeApplied = !evaluateDataLossResult.unexecutableSteps.length; - // see the link below for what "unexecutable steps" are - // https://github.com/prisma/prisma-engines/blob/c65d20050f139a7917ef2efc47a977338070ea61/migration-engine/connectors/sql-migration-connector/src/sql_destructive_change_checker/unexecutable_step_check.rs - // the tl;dr is "making things non null when there are nulls in the db" - if (!migrationCanBeApplied) { - console.log(`${chalk.bold.red('\n⚠️ We found changes that cannot be executed:\n')}`); - for (const item of evaluateDataLossResult.unexecutableSteps) { - console.log(` • Step ${item.stepIndex} ${item.message}`); - } - } - // warnings mean "if the migration was applied to the database you're connected to, you will lose x data" - // note that if you have a field where all of the values are null on your local db and you've removed it, you won't get a warning here. - // there will be a warning in a comment in the generated migration though. - if (evaluateDataLossResult.warnings.length) { - console.log(chalk.bold(`\n⚠️ Warnings:\n`)); - for (const warning of evaluateDataLossResult.warnings) { - console.log(` • ${warning.message}`); - } - } - - console.log(); // for an empty line - - let migrationName = await (() => { - if (cliOptions.name) { - return cliOptions.name; - } - if (process.stdout.isTTY) { - return getMigrationName(); - } - console.log( - "We've detected that you're in a non-interactive environment so you need to pass --name to provide a migration name" - ); - process.exit(1); - })(); - - // note this only creates the migration, it does not apply it - let { generatedMigrationName } = await runMigrateWithDbUrl(dbUrl, () => - migrate.createMigration({ - migrationsDirectoryPath: migrate.migrationsDirectoryPath, - // https://github.com/prisma/prisma-engines/blob/11dfcc85d7f9b55235e31630cd87da7da3aed8cc/migration-engine/core/src/commands/create_migration.rs#L16-L17 - // draft means "create an empty migration even if there are no changes rather than exiting" - draft: true, - prismaSchema, - migrationName, - }) - ); - - console.log( - `✨ A migration has been created at .keystone/prisma/migrations/${generatedMigrationName}` - ); - }); -} - // TODO: don't have process.exit calls here export async function devMigrations(dbUrl: string, prismaSchema: string, schemaPath: string) { return withMigrate(dbUrl, schemaPath, async migrate => { diff --git a/packages-next/keystone/src/lib/prompts.ts b/packages-next/keystone/src/lib/prompts.ts index 089e2c3868c..55758c38c5b 100644 --- a/packages-next/keystone/src/lib/prompts.ts +++ b/packages-next/keystone/src/lib/prompts.ts @@ -15,3 +15,15 @@ export async function confirmPrompt(message: string): Promise { } return value; } + +export async function textPrompt(message: string): Promise { + const { value } = await prompts({ + name: 'value', + type: 'text', + message, + }); + if (value === undefined) { + process.exit(1); + } + return value; +} diff --git a/packages-next/keystone/src/migrations.ts b/packages-next/keystone/src/migrations.ts new file mode 100644 index 00000000000..3732cefe34b --- /dev/null +++ b/packages-next/keystone/src/migrations.ts @@ -0,0 +1 @@ +export { pushPrismaSchemaToDatabase } from './lib/migrations'; diff --git a/packages-next/keystone/src/scripts/run/dev.ts b/packages-next/keystone/src/scripts/run/dev.ts index 76f22bcb285..0aea8062572 100644 --- a/packages-next/keystone/src/scripts/run/dev.ts +++ b/packages-next/keystone/src/scripts/run/dev.ts @@ -1,7 +1,7 @@ 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 { devMigrations, pushPrismaSchemaToDatabase } from '../../lib/migrations'; import { createSystem } from '../../lib/createSystem'; import { initConfig } from '../../lib/initConfig'; import { requireSource } from '../../lib/requireSource'; @@ -41,7 +41,7 @@ export const dev = async (cwd: string) => { if (config.db.useMigrations) { await devMigrations(config.db.url, prismaSchema, getSchemaPaths(cwd).prisma); } else { - await runPrototypeMigrations(config.db.url, prismaSchema, getSchemaPaths(cwd).prisma); + await pushPrismaSchemaToDatabase(config.db.url, prismaSchema, getSchemaPaths(cwd).prisma); } } } diff --git a/packages/adapter-prisma/package.json b/packages/adapter-prisma/package.json index e817109edb4..b5bd61f2edc 100644 --- a/packages/adapter-prisma/package.json +++ b/packages/adapter-prisma/package.json @@ -13,14 +13,7 @@ "@keystone-next/fields-auto-increment-legacy": "^9.0.0", "@keystone-next/keystone-legacy": "^22.0.0", "@keystone-next/utils-legacy": "^8.0.0", - "@prisma/client": "2.19.0", - "@prisma/migrate": "2.19.0", - "@prisma/sdk": "2.19.0", - "@sindresorhus/slugify": "^1.1.0", - "@types/prompts": "^2.0.9", - "chalk": "^4.1.0", - "p-waterfall": "^2.1.1", - "prompts": "^2.4.0" + "p-waterfall": "^2.1.1" }, "repository": "https://github.com/keystonejs/keystone/tree/master/packages/adapter-prisma" } diff --git a/packages/adapter-prisma/src/adapter-prisma.js b/packages/adapter-prisma/src/adapter-prisma.js index b8f4ed357a7..489b9ee0937 100644 --- a/packages/adapter-prisma/src/adapter-prisma.js +++ b/packages/adapter-prisma/src/adapter-prisma.js @@ -1,5 +1,4 @@ import pWaterfall from 'p-waterfall'; -import { formatSchema } from '@prisma/sdk'; import { defaultObj, mapKeys, identity, flatten } from '@keystone-next/utils-legacy'; class PrismaAdapter { @@ -77,7 +76,7 @@ class PrismaAdapter { await this.prisma.$connect(); } - async _generatePrismaSchema({ rels, clientDir }) { + _generatePrismaSchema({ rels, clientDir }) { const models = Object.values(this.listAdapters).map(listAdapter => { const scalarFields = flatten( listAdapter.fieldAdapters.filter(f => !f.field.isRelationship).map(f => f.getPrismaSchema()) @@ -177,7 +176,7 @@ class PrismaAdapter { provider = "prisma-client-js" output = "${clientDir}" }`; - return await formatSchema({ schema: header + models.join('\n') + '\n' + enums.join('\n') }); + return header + models.join('\n') + '\n' + enums.join('\n'); } async postConnect({ rels }) { diff --git a/packages/adapter-prisma/src/index.ts b/packages/adapter-prisma/src/index.ts index 3526376d795..ada3115c6e1 100644 --- a/packages/adapter-prisma/src/index.ts +++ b/packages/adapter-prisma/src/index.ts @@ -1,3 +1 @@ export { PrismaAdapter, PrismaListAdapter, PrismaFieldAdapter } from './adapter-prisma'; -export { createMigration, devMigrations, runPrototypeMigrations } from './migrations'; -export type { CLIOptionsForCreateMigration } from './migrations'; diff --git a/packages/adapter-prisma/src/prompts.ts b/packages/adapter-prisma/src/prompts.ts deleted file mode 100644 index 55758c38c5b..00000000000 --- a/packages/adapter-prisma/src/prompts.ts +++ /dev/null @@ -1,29 +0,0 @@ -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; -} - -export async function textPrompt(message: string): Promise { - const { value } = await prompts({ - name: 'value', - type: 'text', - message, - }); - if (value === undefined) { - process.exit(1); - } - return value; -} diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts index 0d62d2fa8b6..d927d5234a6 100644 --- a/packages/test-utils/src/index.ts +++ b/packages/test-utils/src/index.ts @@ -8,7 +8,7 @@ import supertest from 'supertest-light'; // @ts-ignore import { Keystone } from '@keystone-next/keystone-legacy'; import { initConfig, createSystem, createExpressServer } from '@keystone-next/keystone'; -import { runPrototypeMigrations } from '@keystone-next/adapter-prisma-legacy'; +import { pushPrismaSchemaToDatabase } from '@keystone-next/keystone/migrations'; import { getCommittedArtifacts, writeCommittedArtifacts, @@ -81,7 +81,7 @@ async function setupFromConfig({ await writeCommittedArtifacts(artifacts, cwd); await generateNodeModulesArtifacts(graphQLSchema, keystone, cwd); } - await runPrototypeMigrations( + await pushPrismaSchemaToDatabase( config.db.url, artifacts.prisma, path.join(cwd, 'schema.prisma'),