Skip to content

Commit

Permalink
Move usages of @prisma/migrate and @prisma/sdk from adapter to @keyst…
Browse files Browse the repository at this point in the history
…one-next/keystone (#5302)
  • Loading branch information
emmatown authored Mar 31, 2021
1 parent c00e20b commit 1e6d12f
Show file tree
Hide file tree
Showing 16 changed files with 51 additions and 266 deletions.
5 changes: 5 additions & 0 deletions .changeset/dirty-moons-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/adapter-prisma-legacy': major
---

Removed `CLIOptionsForCreateMigration` and `createMigration` exports
5 changes: 0 additions & 5 deletions .changeset/giant-buses-sip.md

This file was deleted.

5 changes: 5 additions & 0 deletions .changeset/serious-dryers-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/adapter-prisma-legacy': major
---

Removed formatting of Prisma schema returned from `_generatePrismaSchema` method and made it return synchronously
5 changes: 5 additions & 0 deletions .changeset/ten-jokes-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-next/keystone': minor
---

Added `migrations` entrypoint with `pushPrismaSchemaToDatabase` export.
4 changes: 4 additions & 0 deletions packages-next/keystone/migrations/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"main": "dist/keystone.cjs.js",
"module": "dist/keystone.esm.js"
}
5 changes: 5 additions & 0 deletions packages-next/keystone/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -64,6 +68,7 @@
"entrypoints": [
"index.ts",
"artifacts.ts",
"migrations.ts",
"schema/index.ts",
"session/index.ts",
"scripts/index.ts"
Expand Down
10 changes: 6 additions & 4 deletions packages-next/keystone/src/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -24,9 +24,11 @@ export async function getCommittedArtifacts(
): Promise<CommittedArtifacts> {
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',
}),
}),
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ async function withMigrate<T>(
}
}

export async function runPrototypeMigrations(
export async function pushPrismaSchemaToDatabase(
dbUrl: string,
schema: string,
schemaPath: string,
Expand Down Expand Up @@ -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 => {
Expand Down
12 changes: 12 additions & 0 deletions packages-next/keystone/src/lib/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,15 @@ export async function confirmPrompt(message: string): Promise<boolean> {
}
return value;
}

export async function textPrompt(message: string): Promise<string> {
const { value } = await prompts({
name: 'value',
type: 'text',
message,
});
if (value === undefined) {
process.exit(1);
}
return value;
}
1 change: 1 addition & 0 deletions packages-next/keystone/src/migrations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { pushPrismaSchemaToDatabase } from './lib/migrations';
4 changes: 2 additions & 2 deletions packages-next/keystone/src/scripts/run/dev.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
}
}
}
Expand Down
9 changes: 1 addition & 8 deletions packages/adapter-prisma/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
5 changes: 2 additions & 3 deletions packages/adapter-prisma/src/adapter-prisma.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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 }) {
Expand Down
2 changes: 0 additions & 2 deletions packages/adapter-prisma/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
export { PrismaAdapter, PrismaListAdapter, PrismaFieldAdapter } from './adapter-prisma';
export { createMigration, devMigrations, runPrototypeMigrations } from './migrations';
export type { CLIOptionsForCreateMigration } from './migrations';
Loading

1 comment on commit 1e6d12f

@vercel
Copy link

@vercel vercel bot commented on 1e6d12f Mar 31, 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.