diff --git a/.changeset/eighty-news-swim.md b/.changeset/eighty-news-swim.md new file mode 100644 index 00000000000..b679b201dfc --- /dev/null +++ b/.changeset/eighty-news-swim.md @@ -0,0 +1,5 @@ +--- +'@keystone-next/test-utils-legacy': major +--- + +Removed usage of `getDbSchemaName`, `getPrismaPath`, `migrationMode` and `dropDatabase` adapter options. Note this means that dropping the database and running migrations will now only happen when creating a keystone instance from `setupFromConfig` rather than on every `keystone.connect` diff --git a/.changeset/happy-mugs-reply.md b/.changeset/happy-mugs-reply.md new file mode 100644 index 00000000000..87a728ec48a --- /dev/null +++ b/.changeset/happy-mugs-reply.md @@ -0,0 +1,5 @@ +--- +'@keystone-next/keystone': minor +--- + +Added `artifacts` entrypoint. diff --git a/packages-next/keystone/artifacts/package.json b/packages-next/keystone/artifacts/package.json new file mode 100644 index 00000000000..6cbbe144d0c --- /dev/null +++ b/packages-next/keystone/artifacts/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 24a977a362c..a243bde9786 100644 --- a/packages-next/keystone/package.json +++ b/packages-next/keystone/package.json @@ -63,6 +63,7 @@ "preconstruct": { "entrypoints": [ "index.ts", + "artifacts.ts", "schema/index.ts", "session/index.ts", "scripts/index.ts" diff --git a/packages-next/keystone/src/lib/artifacts.ts b/packages-next/keystone/src/artifacts.ts similarity index 97% rename from packages-next/keystone/src/lib/artifacts.ts rename to packages-next/keystone/src/artifacts.ts index 3fd05cd3ea7..d4706629ce9 100644 --- a/packages-next/keystone/src/lib/artifacts.ts +++ b/packages-next/keystone/src/artifacts.ts @@ -3,8 +3,8 @@ 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'; +import { confirmPrompt } from './lib/prompts'; +import { printGeneratedTypes } from './lib/schema-type-printer'; export function getSchemaPaths(cwd: string) { return { diff --git a/packages-next/keystone/src/scripts/build/build.ts b/packages-next/keystone/src/scripts/build/build.ts index c6581e44f86..000aaaaaa4a 100644 --- a/packages-next/keystone/src/scripts/build/build.ts +++ b/packages-next/keystone/src/scripts/build/build.ts @@ -6,7 +6,7 @@ import { AdminFileToWrite } from '@keystone-next/types'; import { createSystem } from '../../lib/createSystem'; import { initConfig } from '../../lib/initConfig'; import { requireSource } from '../../lib/requireSource'; -import { generateNodeModulesArtifacts, validateCommittedArtifacts } from '../../lib/artifacts'; +import { generateNodeModulesArtifacts, validateCommittedArtifacts } from '../../artifacts'; import { CONFIG_PATH, getAdminPath } from '../utils'; // FIXME: Duplicated from admin-ui package. Need to decide on a common home. diff --git a/packages-next/keystone/src/scripts/postinstall.ts b/packages-next/keystone/src/scripts/postinstall.ts index 326af41a869..6b5540ab1e7 100644 --- a/packages-next/keystone/src/scripts/postinstall.ts +++ b/packages-next/keystone/src/scripts/postinstall.ts @@ -3,7 +3,7 @@ import { generateCommittedArtifacts, generateNodeModulesArtifacts, validateCommittedArtifacts, -} from '../lib/artifacts'; +} from '../artifacts'; import { requireSource } from '../lib/requireSource'; import { initConfig } from '../lib/initConfig'; import { CONFIG_PATH } from './utils'; diff --git a/packages-next/keystone/src/scripts/prisma.ts b/packages-next/keystone/src/scripts/prisma.ts index ac208fc18f7..53b983da7ac 100644 --- a/packages-next/keystone/src/scripts/prisma.ts +++ b/packages-next/keystone/src/scripts/prisma.ts @@ -1,6 +1,6 @@ import execa from 'execa'; import { createSystem } from '../lib/createSystem'; -import { generateNodeModulesArtifacts, validateCommittedArtifacts } from '../lib/artifacts'; +import { generateNodeModulesArtifacts, validateCommittedArtifacts } from '../artifacts'; import { requireSource } from '../lib/requireSource'; import { initConfig } from '../lib/initConfig'; import { CONFIG_PATH } from './utils'; diff --git a/packages-next/keystone/src/scripts/run/dev.ts b/packages-next/keystone/src/scripts/run/dev.ts index adbe80507be..dc18152fba4 100644 --- a/packages-next/keystone/src/scripts/run/dev.ts +++ b/packages-next/keystone/src/scripts/run/dev.ts @@ -11,7 +11,7 @@ import { generateNodeModulesArtifacts, getSchemaPaths, requirePrismaClient, -} from '../../lib/artifacts'; +} from '../../artifacts'; import { CONFIG_PATH, getAdminPath } from '../utils'; // TODO: Don't generate or start an Admin UI if it isn't configured!! diff --git a/packages-next/keystone/src/scripts/run/start.ts b/packages-next/keystone/src/scripts/run/start.ts index 1caa7b4a39f..d6e4875799d 100644 --- a/packages-next/keystone/src/scripts/run/start.ts +++ b/packages-next/keystone/src/scripts/run/start.ts @@ -4,7 +4,7 @@ import { createSystem } from '../../lib/createSystem'; import { initConfig } from '../../lib/initConfig'; import { createExpressServer } from '../../lib/createExpressServer'; import { getAdminPath } from '../utils'; -import { requirePrismaClient } from '../../lib/artifacts'; +import { requirePrismaClient } from '../../artifacts'; export const start = async (cwd: string) => { console.log('✨ Starting Keystone'); diff --git a/packages/adapter-prisma/src/migrations.ts b/packages/adapter-prisma/src/migrations.ts index 3a17f2e8e2a..d27fa75edcf 100644 --- a/packages/adapter-prisma/src/migrations.ts +++ b/packages/adapter-prisma/src/migrations.ts @@ -37,19 +37,27 @@ async function withMigrate( } } -export async function runPrototypeMigrations(dbUrl: string, schema: string, schemaPath: string) { +export async function runPrototypeMigrations( + dbUrl: string, + schema: string, + schemaPath: string, + shouldDropDatabase = false +) { let before = Date.now(); - let migration = await withMigrate(dbUrl, schemaPath, async migrate => - runMigrateWithDbUrl(dbUrl, () => + let migration = await withMigrate(dbUrl, schemaPath, async migrate => { + if (shouldDropDatabase) { + await runMigrateWithDbUrl(dbUrl, () => migrate.engine.reset()); + } + return runMigrateWithDbUrl(dbUrl, () => migrate.engine.schemaPush({ // TODO: we probably want to do something like db push does where either there's // a prompt or an argument needs to be passed to make it force(i.e. lose data) force: true, schema, }) - ) - ); + ); + }); if (migration.warnings.length === 0 && migration.executedSteps === 0) { console.info(`✨ The database is already in sync with the Prisma schema.`); diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index c1be0352574..bcb2ca6f27c 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -10,6 +10,7 @@ "node": ">=10.0.0" }, "dependencies": { + "@keystone-next/adapter-prisma-legacy": "^4.0.1", "@keystone-next/keystone": "14.0.1", "@keystone-next/keystone-legacy": "^22.0.0", "express": "^4.17.1", diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts index 65d9e79e07a..91993a66a70 100644 --- a/packages/test-utils/src/index.ts +++ b/packages/test-utils/src/index.ts @@ -1,12 +1,20 @@ import path from 'path'; import crypto from 'crypto'; import { ServerResponse } from 'http'; +import fs from 'fs'; import express from 'express'; // @ts-ignore 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 { + getCommittedArtifacts, + writeCommittedArtifacts, + requirePrismaClient, + generateNodeModulesArtifacts, +} from '@keystone-next/keystone/artifacts'; import type { KeystoneConfig, BaseKeystone, KeystoneContext } from '@keystone-next/types'; import memoizeOne from 'memoize-one'; @@ -18,27 +26,17 @@ const hashPrismaSchema = memoizeOne(prismaSchema => const argGenerator = { prisma_postgresql: () => ({ - migrationMode: 'prototype', - dropDatabase: true, - url: process.env.DATABASE_URL || '', + migrationMode: 'none-skip-client-generation', + url: process.env.DATABASE_URL!, provider: 'postgresql', - // Put the generated client at a unique path - getPrismaPath: ({ prismaSchema }: { prismaSchema: string }) => - path.join('.api-test-prisma-clients', hashPrismaSchema(prismaSchema)), - // Slice down to the hash make a valid postgres schema name - getDbSchemaName: ({ prismaSchema }: { prismaSchema: string }) => - hashPrismaSchema(prismaSchema).slice(0, 16), + getDbSchemaName: () => null as any, // Turn this on if you need verbose debug info enableLogging: false, }), prisma_sqlite: () => ({ - migrationMode: 'prototype', - dropDatabase: true, - url: process.env.DATABASE_URL || '', + migrationMode: 'none-skip-client-generation', + url: process.env.DATABASE_URL!, provider: 'sqlite', - // Put the generated client at a unique path - getPrismaPath: ({ prismaSchema }: { prismaSchema: string }) => - path.join('.api-test-prisma-clients', hashPrismaSchema(prismaSchema)), // Turn this on if you need verbose debug info enableLogging: false, }), @@ -50,9 +48,11 @@ const argGenerator = { type TestKeystoneConfig = Omit; export const testConfig = (config: TestKeystoneConfig) => config; +const alreadyGeneratedProjects = new Set(); + async function setupFromConfig({ adapterName, - config, + config: _config, }: { adapterName: AdapterName; config: TestKeystoneConfig; @@ -64,13 +64,41 @@ async function setupFromConfig({ } else if (adapterName === 'prisma_sqlite') { const adapterArgs = await argGenerator[adapterName](); db = { adapter: adapterName, ...adapterArgs }; - config.experimental = { prismaSqlite: true }; + _config = { ..._config, experimental: { prismaSqlite: true } }; } - const _config = initConfig({ ...config, db: db!, ui: { isDisabled: true } }); - - const { keystone, createContext, graphQLSchema } = createSystem(_config, 'dev'); - const app = await createExpressServer(_config, graphQLSchema, createContext, true, '', false); + const config = initConfig({ ..._config, db: db!, ui: { isDisabled: true } }); + + const prismaClient = await (async () => { + const { keystone, graphQLSchema } = createSystem(config, 'none-skip-client-generation'); + const artifacts = await getCommittedArtifacts(graphQLSchema, keystone); + const hash = hashPrismaSchema(artifacts.prisma); + if (adapterName === 'prisma_postgresql') { + config.db.url = `${config.db.url}?schema=${hash.toString()}`; + } + const cwd = path.resolve('.api-test-prisma-clients', hash); + if (!alreadyGeneratedProjects.has(hash)) { + alreadyGeneratedProjects.add(hash); + fs.mkdirSync(cwd, { recursive: true }); + await writeCommittedArtifacts(artifacts, cwd); + await generateNodeModulesArtifacts(graphQLSchema, keystone, cwd); + } + await runPrototypeMigrations( + config.db.url, + artifacts.prisma, + path.join(cwd, 'schema.prisma'), + true + ); + return requirePrismaClient(cwd); + })(); + + const { keystone, createContext, graphQLSchema } = createSystem( + config, + 'none-skip-client-generation', + prismaClient + ); + + const app = await createExpressServer(config, graphQLSchema, createContext, true, '', false); return { keystone, context: createContext().sudo(), app }; } @@ -126,7 +154,6 @@ function _keystoneRunner(adapterName: AdapterName, tearDownFunction: () => Promi } const setup = await setupKeystoneFn(adapterName); const { keystone } = setup; - await keystone.connect(); try { diff --git a/tests/api-tests/fields/filter.test.ts b/tests/api-tests/fields/filter.test.ts index 338ac147c65..7b3af9265af 100644 --- a/tests/api-tests/fields/filter.test.ts +++ b/tests/api-tests/fields/filter.test.ts @@ -2,7 +2,6 @@ import path from 'path'; import globby from 'globby'; import { multiAdapterRunners, setupFromConfig, testConfig } from '@keystone-next/test-utils-legacy'; import { createItem, getItems } from '@keystone-next/server-side-graphql-client-legacy'; -import memoizeOne from 'memoize-one'; import { createSchema, list } from '@keystone-next/keystone/schema'; import { KeystoneContext } from '@keystone-next/types'; @@ -23,10 +22,7 @@ multiAdapterRunners().map(({ runner, adapterName }) => (mod.testMatrix || ['default']).forEach((matrixValue: string) => { const listKey = 'Test'; - // we want to memoize the server creation since it's the same fields - // for all the tests and setting up a keystone instance for prisma - // can be a bit slow - const _getServer = () => + const getServer = () => setupFromConfig({ adapterName, config: testConfig({ @@ -36,8 +32,6 @@ multiAdapterRunners().map(({ runner, adapterName }) => }), }); - const getServer = memoizeOne(_getServer); - const withKeystone = (testFn: (args: any) => void = () => {}) => runner(getServer, async ({ context, ...rest }) => { // Populate the database before running the tests