Skip to content

Commit

Permalink
fix(deps): update prisma monorepo to v5.15.1 (#9164)
Browse files Browse the repository at this point in the history
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Cousens <413395+dcousens@users.noreply.github.com>
  • Loading branch information
renovate[bot] and dcousens authored Jun 25, 2024
1 parent 295d401 commit 4262e50
Show file tree
Hide file tree
Showing 20 changed files with 526 additions and 423 deletions.
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

0 comments on commit 4262e50

Please sign in to comment.