Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(deps): update prisma monorepo to v5.15.1 #9164

Merged
merged 18 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-keystone-prisma.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-6/core': patch
---

Fix `keystone prisma ...` not returning the same error code as the Prisma engine
8 changes: 4 additions & 4 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -204,9 +204,9 @@
"@keystone-ui/toast": "workspace:^",
"@keystone-ui/tooltip": "workspace:^",
"@nodelib/fs.walk": "^2.0.0",
"@prisma/client": "5.14.0",
"@prisma/internals": "5.14.0",
"@prisma/migrate": "5.14.0",
"@prisma/client": "5.15.1",
"@prisma/internals": "5.15.1",
"@prisma/migrate": "5.15.1",
"@sindresorhus/slugify": "^1.1.2",
"apollo-upload-client": "^17.0.0",
"bcryptjs": "^2.4.3",
Expand Down Expand Up @@ -235,7 +235,7 @@
"meow": "^9.0.0",
"next": "^14.2.0",
"pluralize": "^8.0.0",
"prisma": "5.14.0",
"prisma": "5.15.1",
"prompts": "^2.4.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ export {
createSystem,
} from '../lib/createSystem'
export {
pushPrismaSchemaToDatabase,
withMigrate
} from '../lib/migrations'
export {
generateArtifacts,
getArtifacts,
} from '../artifacts'
export {
ExitError
} from '../scripts/utils'
25 changes: 13 additions & 12 deletions packages/core/src/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from 'node:fs/promises'
import path from 'node:path'
import { type ChildProcess } from 'node:child_process'

import { printSchema, type GraphQLSchema } from 'graphql'
import { printSchema } from 'graphql'
import { getGenerators, formatSchema } from '@prisma/internals'
import { ExitError } from './scripts/utils'
import { type __ResolvedKeystoneConfig } from './types'
Expand Down Expand Up @@ -39,7 +39,7 @@ export async function validateArtifacts (
system: System,
) {
const paths = system.getPaths(cwd)
const artifacts = await getCommittedArtifacts(system.config, system.graphQLSchema)
const artifacts = await getArtifacts(system)
const [writtenGraphQLSchema, writtenPrismaSchema] = await Promise.all([
readFileButReturnNothingIfDoesNotExist(paths.schema.graphql),
readFileButReturnNothingIfDoesNotExist(paths.schema.prisma),
Expand Down Expand Up @@ -67,22 +67,23 @@ export async function validateArtifacts (
throw new ExitError(1)
}

async function getCommittedArtifacts (config: __ResolvedKeystoneConfig, graphQLSchema: GraphQLSchema) {
const lists = initialiseLists(config)
const prismaSchema = printPrismaSchema(config, lists)
export async function getArtifacts (system: System) {
const lists = initialiseLists(system.config)
const prismaSchema = await formatSchema({
schemas: [
[system.config.db.prismaSchemaPath, printPrismaSchema(system.config, lists)]
]
})

return {
graphql: getFormattedGraphQLSchema(printSchema(graphQLSchema)),
prisma: (await formatSchema({ schemas: [[config.db.prismaSchemaPath, prismaSchema]] }))[0][1],
graphql: getFormattedGraphQLSchema(printSchema(system.graphQLSchema)),
prisma: prismaSchema[0][1],
}
}

export async function getArtifacts (system: System) {
return await getCommittedArtifacts(system.config, system.graphQLSchema)
}

export async function generateArtifacts (cwd: string, system: System) {
const paths = getSystemPaths(cwd, system.config)
const artifacts = await getCommittedArtifacts(system.config, system.graphQLSchema)
const artifacts = await getArtifacts(system)
await fs.writeFile(paths.schema.graphql, artifacts.graphql)
await fs.writeFile(paths.schema.prisma, artifacts.prisma)
return artifacts
Expand Down
9 changes: 4 additions & 5 deletions packages/core/src/lib/createSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,16 @@ import { resolveDefaults } from './defaults'
import { createAdminMeta } from './create-admin-meta'
import { createGraphQLSchema } from './createGraphQLSchema'
import { createContext } from './context/createContext'
import { initialiseLists, type InitialisedList } from './core/initialise-lists'
import {
type InitialisedList,
initialiseLists,
} from './core/initialise-lists'

// TODO: this cannot be changed for now, circular dependency with getSystemPaths, getEsbuildConfig
export function getBuiltKeystoneConfigurationPath (cwd: string) {
return path.join(cwd, '.keystone/config.js')
}

export function getBuiltKeystoneConfiguration (cwd: string) {
return require(getBuiltKeystoneConfigurationPath(cwd)).default
}

function posixify (s: string) {
return s.split(path.sep).join('/')
}
Expand Down
216 changes: 49 additions & 167 deletions packages/core/src/lib/migrations.ts
Original file line number Diff line number Diff line change
@@ -1,190 +1,72 @@
import { type ChildProcess } from 'node:child_process'
import path from 'node:path'
import { toSchemasContainer } from '@prisma/internals'

import chalk from 'chalk'
import { createDatabase, uriToCredentials, type DatabaseCredentials } from '@prisma/internals'
// @ts-expect-error
import { Migrate } from '@prisma/migrate'

import { type System } from './createSystem'

import { ExitError } from '../scripts/utils'
import { confirmPrompt } from './prompts'

// we don't want to pollute process.env.DATABASE_URL so we're
// setting the env variable _just_ long enough for Migrate to
// read it and then we reset it immediately after.
// Migrate reads the env variables a single time when it starts the child process that it talks to

// note that we could only run this once per Migrate instance but we're going to do it consistently for all migrate calls
// so that calls can moved around freely without implictly relying on some other migrate command being called before it
function setOrRemoveEnvVariable (name: string, value: string | undefined) {
if (value === undefined) {
delete process.env[name]
return
}
process.env[name] = value
}

// We also want to silence messages from Prisma about available updates, since the developer is
// not in control of their Prisma version.
// https://www.prisma.io/docs/reference/api-reference/environment-variables-reference#prisma_hide_update_message
function runMigrateWithDbUrl<T> (
export async function withMigrate<T> (
prismaSchemaPath: string,
system: {
config: {
db: Pick<System['config']['db'], 'url' | 'shadowDatabaseUrl'>
}
},
cb: () => T
): T {
const prevDBURLFromEnv = process.env.DATABASE_URL
const prevShadowDBURLFromEnv = process.env.SHADOW_DATABASE_URL
const prevHiddenUpdateMessage = process.env.PRISMA_HIDE_UPDATE_MESSAGE
try {
process.env.DATABASE_URL = system.config.db.url
setOrRemoveEnvVariable('SHADOW_DATABASE_URL', system.config.db.shadowDatabaseUrl)
process.env.PRISMA_HIDE_UPDATE_MESSAGE = '1'
return cb()
} finally {
setOrRemoveEnvVariable('DATABASE_URL', prevDBURLFromEnv)
setOrRemoveEnvVariable('SHADOW_DATABASE_URL', prevShadowDBURLFromEnv)
setOrRemoveEnvVariable('PRISMA_HIDE_UPDATE_MESSAGE', prevHiddenUpdateMessage)
}
}

function setOrRemoveEnvVariable (name: string, value: string | undefined) {
if (value === undefined) {
delete process.env[name]
} else {
process.env[name] = value
cb: (operations: {
apply: () => Promise<any>
diagnostic: () => Promise<any>
push: (force: boolean) => Promise<any>
reset: () => Promise<any>
schema: (_: string, force: boolean) => Promise<any>
}) => Promise<T>
) {
const migrate = new Migrate(prismaSchemaPath)
function run <T> (f: () => T): T {
// only required once - on child process start - but easiest to do this always
const prevDBURLFromEnv = process.env.DATABASE_URL
const prevShadowDBURLFromEnv = process.env.SHADOW_DATABASE_URL
const prevHiddenUpdateMessage = process.env.PRISMA_HIDE_UPDATE_MESSAGE
try {
process.env.DATABASE_URL = system.config.db.url
setOrRemoveEnvVariable('SHADOW_DATABASE_URL', system.config.db.shadowDatabaseUrl)
process.env.PRISMA_HIDE_UPDATE_MESSAGE = '1' // temporarily silence
return f()
} finally {
setOrRemoveEnvVariable('DATABASE_URL', prevDBURLFromEnv)
setOrRemoveEnvVariable('SHADOW_DATABASE_URL', prevShadowDBURLFromEnv)
setOrRemoveEnvVariable('PRISMA_HIDE_UPDATE_MESSAGE', prevHiddenUpdateMessage)
}
}
}

async function withMigrate<T> (schemaPath: string, cb: (migrate: Migrate) => Promise<T>) {
const migrate = new Migrate(schemaPath)
try {
return await cb(migrate)
return await cb({
async apply () { return run(() => migrate.applyMigrations()) },
async diagnostic () { return run(() => migrate.devDiagnostic()) },
async push (force) { return run(() => migrate.push({ force })) },
async reset () { return run(() => migrate.reset()) },
async schema (schema, force) {
const schemaContainer = toSchemasContainer([
[prismaSchemaPath, schema]
])

return run(() => migrate.engine.schemaPush({ force, schema: schemaContainer }))
}
})
} finally {
const closePromise = new Promise<void>(resolve => {
const child = (migrate.engine as any).child as ChildProcess
const { child } = migrate.engine as { child: ChildProcess }
child.once('exit', () => resolve())
})
migrate.stop()
await closePromise
}
}

export async function runMigrationsOnDatabase (cwd: string, system: System) {
const paths = system.getPaths(cwd)
return await withMigrate(paths.schema.prisma, async (migrate) => {
const { appliedMigrationNames } = await runMigrateWithDbUrl(system, () => migrate.applyMigrations())
return appliedMigrationNames
})
}

export async function runMigrationsOnDatabaseMaybeReset (cwd: string, system: System) {
const paths = system.getPaths(cwd)

return await withMigrate(paths.schema.prisma, async (migrate) => {
const diagnostic = await runMigrateWithDbUrl(system, () => migrate.devDiagnostic())

if (diagnostic.action.tag === 'reset') {
console.log(diagnostic.action.reason)
const consent = await confirmPrompt(`Do you want to continue? ${chalk.red('All data will be lost')}`)
if (!consent) throw new ExitError(1)

await runMigrateWithDbUrl(system, () => migrate.reset())
}

const { appliedMigrationNames } = await runMigrateWithDbUrl(system, () => migrate.applyMigrations())
return appliedMigrationNames
})
}

export async function resetDatabase (dbUrl: string, prismaSchemaPath: string) {
await createDatabase(dbUrl, path.dirname(prismaSchemaPath))
const config = {
db: {
url: dbUrl,
shadowDatabaseUrl: ''
}
}

await withMigrate(prismaSchemaPath, async (migrate) => {
await runMigrateWithDbUrl({ config }, () => migrate.reset())
await runMigrateWithDbUrl({ config }, () => migrate.push({ force: true }))
})
}

export async function pushPrismaSchemaToDatabase (
cwd: string,
system: System,
prismaSchema: string, // already exists
interactive: boolean = false
) {
const paths = system.getPaths(cwd)

const created = await createDatabase(system.config.db.url, path.dirname(paths.schema.prisma))
if (interactive && created) {
const credentials = uriToCredentials(system.config.db.url)
console.log(`✨ ${credentials.type} database "${credentials.database}" created at ${getDbLocation(credentials)}`)
}

const migration = await withMigrate(paths.schema.prisma, async migrate => {
// what does force on migrate.engine.schemaPush mean?
// - true: ignore warnings, but unexecutable steps will block
// - false: warnings or unexecutable steps will block
const migration = await runMigrateWithDbUrl(system, () => migrate.engine.schemaPush({ force: false, schema: prismaSchema }))

// if there are unexecutable steps, we need to reset the database [or the user can use migrations]
if (migration.unexecutable.length) {
if (!interactive) throw new ExitError(1)

logUnexecutableSteps(migration.unexecutable)
if (migration.warnings.length) logWarnings(migration.warnings)

console.log('\nTo apply this migration, we need to reset the database')
if (!(await confirmPrompt(`Do you want to continue? ${chalk.red('All data will be lost')}`, false))) {
console.log('Reset cancelled')
throw new ExitError(0)
}

await runMigrateWithDbUrl(system, () => migrate.reset())
return runMigrateWithDbUrl(system, () => migrate.engine.schemaPush({ force: false, schema: prismaSchema }))
}

if (migration.warnings.length) {
if (!interactive) throw new ExitError(1)

logWarnings(migration.warnings)
if (!(await confirmPrompt(`Do you want to continue? ${chalk.red('Some data will be lost')}`, false))) {
console.log('Push cancelled')
throw new ExitError(0)
}
return runMigrateWithDbUrl(system, () => migrate.engine.schemaPush({ force: true, schema: prismaSchema }))
}

return migration
})

if (!interactive) return
if (migration.warnings.length === 0 && migration.executedSteps === 0) {
console.log(`✨ Database unchanged`)
} else {
console.log(`✨ Database synchronized with Prisma schema`)
}
}

function logUnexecutableSteps (unexecutableSteps: string[]) {
console.log(`${chalk.bold.red('\n⚠️ We found changes that cannot be executed:\n')}`)
for (const item of unexecutableSteps) {
console.log(` • ${item}`)
}
}

function logWarnings (warnings: string[]) {
console.warn(chalk.bold(`\n⚠️ Warnings:\n`))
for (const warning of warnings) {
console.warn(` • ${warning}`)
}
}

function getDbLocation (credentials: DatabaseCredentials): string {
if (credentials.type === 'sqlite') {
return credentials.uri!
}

return `${credentials.host}${credentials.port === undefined ? '' : `:${credentials.port}`}`
}
7 changes: 3 additions & 4 deletions packages/core/src/scripts/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import nextBuild from 'next/dist/build'
import { generateAdminUI } from '../admin-ui/system'
import {
createSystem,
getBuiltKeystoneConfiguration
} from '../lib/createSystem'
import {
generateArtifacts,
Expand All @@ -13,16 +12,16 @@ import {
} from '../artifacts'
import { getEsbuildConfig } from '../lib/esbuild'
import type { Flags } from './cli'
import { importBuiltKeystoneConfiguration } from './utils'

export async function build (
cwd: string,
{ frozen, prisma, ui }: Pick<Flags, 'frozen' | 'prisma' | 'ui'>
) {
// TODO: should this happen if frozen?
await esbuild.build(getEsbuildConfig(cwd))

// TODO: this cannot be changed for now, circular dependency with getSystemPaths, getEsbuildConfig
const system = createSystem(getBuiltKeystoneConfiguration(cwd))

const system = createSystem(await importBuiltKeystoneConfiguration(cwd))
if (prisma) {
if (frozen) {
await validateArtifacts(cwd, system)
Expand Down
Loading
Loading