Skip to content

Commit

Permalink
Merge branch 'main' into feat/move-create-keystone-app
Browse files Browse the repository at this point in the history
  • Loading branch information
iamandrewluca committed Apr 24, 2024
2 parents a7ef4d1 + 2613769 commit aa5a82f
Show file tree
Hide file tree
Showing 21 changed files with 604 additions and 411 deletions.
5 changes: 5 additions & 0 deletions .changeset/add-keystone-migrate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-6/core': minor
---

Adds `keystone migrate create` and `keystone migrate apply`
2 changes: 0 additions & 2 deletions .devcontainer/Dockerfile

This file was deleted.

30 changes: 20 additions & 10 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,30 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "Keystone",
"build": {
"dockerfile": "Dockerfile",
"args": {
"VARIANT": "14"
}
},

"settings": {},
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye",

"extensions": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode", "prisma.prisma"],
// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "pnpm install",

"remoteUser": "node"
// Configure tool-specific properties.
"customizations": {
"vscode": {
"settings": {},
"extensions": [
"prisma.prisma",
"github.vscode-pull-request-github"
]
}
}

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
2 changes: 1 addition & 1 deletion docs/pages/enterprise.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ export default function ForOrganisations () {

<Quote
name="Kevin Stafford"
img="https://thinkmill.com.au/_astro/kevin-stafford-rugby-au@1280w.24c4530d.webp"
img="/assets/kevin-stafford.jpg"
title="CTO, Rugby Australia"
grad="grad6"
css={{
Expand Down
3 changes: 1 addition & 2 deletions examples/extend-express-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
"dev": "keystone dev",
"start": "keystone start",
"build": "keystone build",
"postinstall": "keystone postinstall",
"seed-data": "tsx seed-data.ts"
"postinstall": "keystone postinstall"
},
"dependencies": {
"@keystone-6/core": "^6.0.0",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"license": "MIT",
"repository": "https://github.com/keystonejs/keystone",
"homepage": "https://github.com/keystonejs/keystone",
"packageManager": "pnpm@9.0.1",
"packageManager": "pnpm@9.0.5",
"scripts": {
"coverage": "jest --coverage",
"test": "jest",
Expand Down
2 changes: 1 addition & 1 deletion packages/auth/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ export function createAuth<ListTypeInfo extends BaseListTypeInfo> ({
return isAccessAllowed(context)
},

pageMiddleware: async args => {
pageMiddleware: async (args) => {
const shouldRedirect = await authMiddleware(args)
if (shouldRedirect) return shouldRedirect
return pageMiddleware?.(args)
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/admin-ui/components/SignoutButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const END_SESSION = gql`
}
`

const SignoutButton = ({ children }: { children?: ReactNode }) => {
function SignoutButton ({ children }: { children?: ReactNode }) {
const [endSession, { loading, data }] = useMutation(END_SESSION)
useEffect(() => {
if (data?.endSession) {
Expand Down
13 changes: 4 additions & 9 deletions packages/core/src/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,12 @@ async function readFileButReturnNothingIfDoesNotExist (path: string) {
}
}

async function validatePrismaAndGraphQLSchemas (
export async function validateArtifacts (
cwd: string,
config: __ResolvedKeystoneConfig,
graphQLSchema: GraphQLSchema
system: System,
) {
const paths = getSystemPaths(cwd, config)
const artifacts = await getCommittedArtifacts(config, graphQLSchema)
const paths = system.getPaths(cwd)
const artifacts = await getCommittedArtifacts(system.config, system.graphQLSchema)
const [writtenGraphQLSchema, writtenPrismaSchema] = await Promise.all([
readFileButReturnNothingIfDoesNotExist(paths.schema.graphql),
readFileButReturnNothingIfDoesNotExist(paths.schema.prisma),
Expand Down Expand Up @@ -118,7 +117,3 @@ export async function generatePrismaClient (cwd: string, system: System) {
})
)
}

export async function validateArtifacts (cwd: string, system: System) {
return await validatePrismaAndGraphQLSchemas(cwd, system.config, system.graphQLSchema)
}
16 changes: 7 additions & 9 deletions packages/core/src/lib/context/createContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,14 @@ export function createContext ({
res?: ServerResponse
}) => {
const schema = sudo ? graphQLSchemaSudo : graphQLSchema
const rawGraphQL: KeystoneGraphQLAPI['raw'] = ({ query, variables }) => {
const rawGraphQL: KeystoneGraphQLAPI['raw'] = async ({ query, variables }) => {
const source = typeof query === 'string' ? query : print(query)
return Promise.resolve(
graphql({
schema,
source,
contextValue: context,
variableValues: variables,
}) as ExecutionResult<any>
)
return await graphql({
schema,
source,
contextValue: context,
variableValues: variables,
}) as ExecutionResult<any>
}

const runGraphQL: KeystoneGraphQLAPI['run'] = async ({ query, variables }) => {
Expand Down
9 changes: 6 additions & 3 deletions packages/core/src/lib/core/queries/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,12 @@ export async function findMany (
})

if (list.cacheHint) {
maybeCacheControlFromInfo(info)?.setCacheHint(
list.cacheHint({ results, operationName: info.operation.name?.value, meta: false }) as any
)
maybeCacheControlFromInfo(info)
?.setCacheHint(list.cacheHint({
results,
operationName: info.operation.name?.value,
meta: false
}))
}
return results
}
Expand Down
165 changes: 86 additions & 79 deletions packages/core/src/lib/migrations.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import path from 'path'
import { type ChildProcess } from 'node:child_process'
import path from 'node:path'

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

import { type System } from './createSystem'

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

Expand All @@ -16,17 +21,20 @@ import { confirmPrompt } from './prompts'
// 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
export function runMigrateWithDbUrl<T> (
dbUrl: string,
shadowDbUrl: string | undefined,
function runMigrateWithDbUrl<T> (
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 = dbUrl
setOrRemoveEnvVariable('SHADOW_DATABASE_URL', shadowDbUrl)
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 {
Expand All @@ -44,119 +52,118 @@ function setOrRemoveEnvVariable (name: string, value: string | undefined) {
}
}

export async function withMigrate<T> (schemaPath: string, cb: (migrate: Migrate) => Promise<T>) {
async function withMigrate<T> (schemaPath: string, cb: (migrate: Migrate) => Promise<T>) {
const migrate = new Migrate(schemaPath)
try {
return await cb(migrate)
} finally {
const closePromise = new Promise<void>(resolve => {
const child = (migrate.engine as any).child as import('child_process').ChildProcess
const child = (migrate.engine as any).child as 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 (
dbUrl: string,
shadowDbUrl: string | undefined,
schema: string,
schemaPath: string,
resetDb: boolean,
interactive: boolean = true
cwd: string,
system: System,
prismaSchema: string, // already exists
interactive: boolean = false
) {
const created = await createDatabase(dbUrl, path.dirname(schemaPath))
const paths = system.getPaths(cwd)

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

const migration = await withMigrate(schemaPath, async migrate => {
if (resetDb) {
await runMigrateWithDbUrl(dbUrl, shadowDbUrl, () => migrate.engine.reset())
let migration = await runMigrateWithDbUrl(dbUrl, shadowDbUrl, () =>
migrate.engine.schemaPush({
force: true,
schema,
})
)
if (interactive) console.log('✨ Your database has been reset')
return migration
}
const migration = await withMigrate(paths.schema.prisma, async migrate => {
// what does force on migrate.engine.schemaPush mean?
// - true: ignore warnings but will not run anything if there are unexecutable steps(so the database needs to be reset before)
// - false: if there are warnings or unexecutable steps, don't run the migration
// https://github.com/prisma/prisma-engines/blob/a2de6b71267b45669d25c3a27ad30998862a275c/migration-engine/core/src/commands/schema_push.rs
const migration = await runMigrateWithDbUrl(dbUrl, shadowDbUrl, () =>
migrate.engine.schemaPush({
force: false,
schema,
})
)

// if there are unexecutable steps, we need to reset the database or the user can switch to using migrations
// there's no point in asking if they're okay with the warnings separately after asking if they're okay with
// resetting their db since their db is already empty so they don't have any data to lose
// - 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)
}
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.error('Reset cancelled')
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(dbUrl, shadowDbUrl, () => migrate.reset())
return runMigrateWithDbUrl(dbUrl, shadowDbUrl, () =>
migrate.engine.schemaPush({
force: false,
schema,
})
)

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.error('Push cancelled')
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(dbUrl, shadowDbUrl, () =>
migrate.engine.schemaPush({
force: true,
schema,
})
)
return runMigrateWithDbUrl(system, () => migrate.engine.schemaPush({ force: true, schema: prismaSchema }))
}

return migration
})

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

Expand Down
Loading

0 comments on commit aa5a82f

Please sign in to comment.