Skip to content

Commit

Permalink
Fix keystone dev not showing ESM errors (#9219)
Browse files Browse the repository at this point in the history
  • Loading branch information
dcousens authored Jul 17, 2024
1 parent ccc8117 commit 2849031
Show file tree
Hide file tree
Showing 12 changed files with 112 additions and 162 deletions.
6 changes: 4 additions & 2 deletions docs/content/docs/guides/auth-and-access-control.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Here's an example:

```ts
const Person = list({
access: allowAll,
fields: {
name: text(),
email: text({ isIndexed: 'unique' }),
Expand Down Expand Up @@ -71,15 +72,15 @@ const session = statelessSessions({
});
```

Keystone also comes with a Redis session adapter, which uses a cookie to store a session ID that is looked up in a Redis database; or you can use your own session adapter (for example, if you are using OAuth sessions).
You can use your own session strategy if for example, if you want to use use OAuth sessions.

{% hint kind="tip" %}
Read more about [Session Stores in the Session API Docs](../config/session#session-stores).
{% /hint %}

### Putting it all together

Your entire Keystone config should now look like this:
Your Keystone config should now look like this:

```ts
import { config, list } from '@keystone-6/core';
Expand All @@ -104,6 +105,7 @@ const session = statelessSessions({

const lists = {
Person: list({
access: allowAll,
fields: {
name: text(),
email: text({ isIndexed: 'unique' }),
Expand Down
4 changes: 2 additions & 2 deletions examples/omit/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export const lists = {
person: relationship({ ref: 'Person' }),
},

// this list is partially omitted, it will partly be in the public GraphQL schema
// this list is partially omitted -> it will partially be in the public GraphQL schema
graphql: {
omit: {
// query: false, // default allowed
Expand All @@ -33,7 +33,7 @@ export const lists = {
person: relationship({ ref: 'Person' }),
},

// this list is completely omitted, it won't be in the public GraphQL schema
// this list is completely omitted -> it won't be in the public GraphQL schema
graphql: {
omit: true,
},
Expand Down
10 changes: 5 additions & 5 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
"module": "./context/dist/keystone-6-core-context.esm.js",
"default": "./context/dist/keystone-6-core-context.cjs.js"
},
"./session": {
"module": "./session/dist/keystone-6-core-session.esm.js",
"default": "./session/dist/keystone-6-core-session.cjs.js"
},
"./testing": {
"module": "./testing/dist/keystone-6-core-testing.esm.js",
"default": "./testing/dist/keystone-6-core-testing.cjs.js"
Expand All @@ -38,10 +42,6 @@
"module": "./scripts/dist/keystone-6-core-scripts.esm.js",
"default": "./scripts/dist/keystone-6-core-scripts.cjs.js"
},
"./session": {
"module": "./session/dist/keystone-6-core-session.esm.js",
"default": "./session/dist/keystone-6-core-session.cjs.js"
},
"./admin-ui/image": {
"module": "./admin-ui/image/dist/keystone-6-core-admin-ui-image.esm.js",
"default": "./admin-ui/image/dist/keystone-6-core-admin-ui-image.cjs.js"
Expand Down Expand Up @@ -267,7 +267,7 @@
"___internal-do-not-use-will-break-in-patch/admin-ui/id-field-view.tsx",
"context.ts",
"testing.ts",
"session/index.ts",
"session.ts",
"scripts/index.ts",
"scripts/cli.ts",
"admin-ui/components/index.ts",
Expand Down
20 changes: 8 additions & 12 deletions packages/core/src/admin-ui/system/generateAdminUI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,6 @@ export async function generateAdminUI (

// Write out the built-in admin UI files. Don't overwrite any user-defined pages.
const configFileExists = getDoesAdminConfigExist()
let adminFiles = writeAdminFiles(config, graphQLSchema, adminMeta, configFileExists)

// Add files to pages/ which point to any files which exist in admin/pages
const adminConfigDir = Path.join(process.cwd(), 'admin')
Expand All @@ -112,11 +111,10 @@ export async function generateAdminUI (
entryFilter: entry => entry.dirent.isFile() && pageExtensions.has(Path.extname(entry.name)),
})
} catch (err: any) {
if (err.code !== 'ENOENT') {
throw err
}
if (err.code !== 'ENOENT') throw err
}

let adminFiles = writeAdminFiles(config, graphQLSchema, adminMeta, configFileExists)
for (const { path } of userPagesEntries) {
const outputFilename = Path.relative(adminConfigDir, path)
const importPath = Path.relative(
Expand Down Expand Up @@ -145,14 +143,12 @@ export async function generateAdminUI (
// - we'll remove them when the user restarts the process
if (isLiveReload) {
const ignoredDir = Path.resolve(projectAdminPath, '.next')
const ignoredFiles = new Set(
[
...adminFiles.map(x => x.outputPath),
...uniqueFiles,
'next-env.d.ts',
'pages/api/__keystone_api_build.js',
].map(x => Path.resolve(projectAdminPath, x))
)
const ignoredFiles = new Set([
...adminFiles.map(x => x.outputPath),
...uniqueFiles,
'next-env.d.ts',
'pages/api/__keystone_api_build.js',
].map(x => Path.resolve(projectAdminPath, x)))

const entries = await walk(projectAdminPath, {
deepFilter: entry => entry.path !== ignoredDir,
Expand Down
28 changes: 13 additions & 15 deletions packages/core/src/admin-ui/templates/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import * as Path from 'path'
import type { GraphQLSchema } from 'graphql'
import { type GraphQLSchema } from 'graphql'
import {
type AdminFileToWrite,
type __ResolvedKeystoneConfig
} from '../../types'
import type { AdminMetaRootVal } from '../../lib/create-admin-meta'
import { type AdminMetaRootVal } from '../../lib/create-admin-meta'
import { appTemplate } from './app'
import { homeTemplate } from './home'
import { listTemplate } from './list'
Expand All @@ -15,26 +14,25 @@ import { nextConfigTemplate } from './next-config'

const pkgDir = Path.dirname(require.resolve('@keystone-6/core/package.json'))

export const writeAdminFiles = (
config: __ResolvedKeystoneConfig,
export function writeAdminFiles (config: __ResolvedKeystoneConfig,
graphQLSchema: GraphQLSchema,
adminMeta: AdminMetaRootVal,
configFileExists: boolean
): AdminFileToWrite[] => {
) {
return [
{
mode: 'write',
mode: 'write' as const,
src: nextConfigTemplate(config.ui?.basePath),
outputPath: 'next.config.js',
},
{
mode: 'copy',
mode: 'copy' as const,
inputPath: Path.join(pkgDir, 'static', 'favicon.ico'),
outputPath: 'public/favicon.ico',
},
{ mode: 'write', src: noAccessTemplate(config.session), outputPath: 'pages/no-access.js' },
{ mode: 'write' as const, src: noAccessTemplate(config.session), outputPath: 'pages/no-access.js' },
{
mode: 'write',
mode: 'write' as const,
src: appTemplate(
adminMeta,
graphQLSchema,
Expand All @@ -43,11 +41,11 @@ export const writeAdminFiles = (
),
outputPath: 'pages/_app.js',
},
{ mode: 'write', src: homeTemplate, outputPath: 'pages/index.js' },
...adminMeta.lists.flatMap(({ path, key }): AdminFileToWrite[] => [
{ mode: 'write', src: listTemplate(key), outputPath: `pages/${path}/index.js` },
{ mode: 'write', src: itemTemplate(key), outputPath: `pages/${path}/[id].js` },
{ mode: 'write', src: createItemTemplate(key), outputPath: `pages/${path}/create.js` },
{ mode: 'write' as const, src: homeTemplate, outputPath: 'pages/index.js' },
...adminMeta.lists.flatMap(({ path, key }) => [
{ mode: 'write' as const, src: listTemplate(key), outputPath: `pages/${path}/index.js` },
{ mode: 'write' as const, src: itemTemplate(key), outputPath: `pages/${path}/[id].js` },
{ mode: 'write' as const, src: createItemTemplate(key), outputPath: `pages/${path}/create.js` },
]),
]
}
42 changes: 17 additions & 25 deletions packages/core/src/artifacts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,11 @@ export function getFormattedGraphQLSchema (schema: string) {
)
}

async function readFileButReturnNothingIfDoesNotExist (path: string) {
async function readFileOrUndefined (path: string) {
try {
return await fs.readFile(path, 'utf8')
} catch (err: any) {
if (err.code === 'ENOENT') {
return
}
if (err.code === 'ENOENT') return
throw err
}
}
Expand All @@ -41,30 +39,24 @@ export async function validateArtifacts (
const paths = system.getPaths(cwd)
const artifacts = await getArtifacts(system)
const [writtenGraphQLSchema, writtenPrismaSchema] = await Promise.all([
readFileButReturnNothingIfDoesNotExist(paths.schema.graphql),
readFileButReturnNothingIfDoesNotExist(paths.schema.prisma),
readFileOrUndefined(paths.schema.graphql),
readFileOrUndefined(paths.schema.prisma),
])
const outOfDateSchemas = (() => {
if (writtenGraphQLSchema !== artifacts.graphql && writtenPrismaSchema !== artifacts.prisma) {
return 'both'
}
if (writtenGraphQLSchema !== artifacts.graphql) {
return 'graphql'
}
if (writtenPrismaSchema !== artifacts.prisma) {
return 'prisma'
}
})()
if (!outOfDateSchemas) return

const message = {
both: 'Your Prisma and GraphQL schemas are not up to date',
graphql: 'Your GraphQL schema is not up to date',
prisma: 'Your Prisma schema is not up to date',
}[outOfDateSchemas]
console.error(message)
if (writtenGraphQLSchema !== artifacts.graphql && writtenPrismaSchema !== artifacts.prisma) {
console.error('Your Prisma and GraphQL schemas are not up to date')
throw new ExitError(1)
}

if (writtenGraphQLSchema !== artifacts.graphql) {
console.error('Your GraphQL schema is not up to date')
throw new ExitError(1)
}

throw new ExitError(1)
if (writtenPrismaSchema !== artifacts.prisma) {
console.error('Your Prisma schema is not up to date')
throw new ExitError(1)
}
}

export async function getArtifacts (system: System) {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/fields/types/relationship/views/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ type RelationshipController = FieldController<
many: boolean
}

export const controller = (
export function controller (
config: FieldControllerConfig<
{
refFieldKey?: string
Expand All @@ -383,7 +383,7 @@ export const controller = (
}
)
>
): RelationshipController => {
): RelationshipController {
const cardsDisplayOptions =
config.fieldMeta.displayMode === 'cards'
? {
Expand Down
79 changes: 34 additions & 45 deletions packages/core/src/lib/admin-meta-resolver.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import type { GraphQLResolveInfo } from 'graphql'
import type { ScalarType, EnumType, EnumValue } from '@graphql-ts/schema'
import type { KeystoneContext, BaseItem, MaybePromise } from '../types'
import {
type GraphQLResolveInfo
} from 'graphql'
import {
type EnumType,
type EnumValue,
type ScalarType,
} from '@graphql-ts/schema'
import {
type BaseItem,
type KeystoneContext,
type MaybePromise
} from '../types'
import { QueryMode } from '../types'
import { graphql as graphqlBoundToKeystoneContext } from '../types/schema'
import type {
FieldMetaRootVal,
ListMetaRootVal,
AdminMetaRootVal,
FieldGroupMeta,
import {
type AdminMetaRootVal,
type FieldGroupMeta,
type FieldMetaRootVal,
type ListMetaRootVal,
} from './create-admin-meta'

type Context = KeystoneContext | { isAdminUIBuildProcess: true }
Expand Down Expand Up @@ -88,26 +98,17 @@ const KeystoneAdminUIFieldMeta = graphql.object<FieldMetaRootVal>()({
values: graphql.enumValues(['edit', 'read', 'hidden']),
}),
resolve ({ fieldMode, itemId, listKey }, args, context, info) {
if (itemId !== null) {
assertInRuntimeContext(context, info)
}

if (typeof fieldMode === 'string') {
return fieldMode
}

if (itemId === null) {
return null
}
if (itemId !== null) assertInRuntimeContext(context, info)
if (typeof fieldMode === 'string') return fieldMode
if (itemId === null) return null

// we need to re-assert this because typescript doesn't understand the relation between
// rootVal.itemId !== null and the context being a runtime context
assertInRuntimeContext(context, info)

return fetchItemForItemViewFieldMode(context)(listKey, itemId).then(item => {
if (item === null) {
return 'hidden' as const
}
if (item === null) return 'hidden' as const

return fieldMode({
session: context.session,
context,
Expand All @@ -122,15 +123,10 @@ const KeystoneAdminUIFieldMeta = graphql.object<FieldMetaRootVal>()({
values: graphql.enumValues(['form', 'sidebar']),
}),
resolve ({ fieldPosition, itemId, listKey }, args, context, info) {
if (itemId !== null) {
assertInRuntimeContext(context, info)
}
if (typeof fieldPosition === 'string') {
return fieldPosition
}
if (itemId === null) {
return null
}
if (itemId !== null) assertInRuntimeContext(context, info)
if (typeof fieldPosition === 'string') return fieldPosition
if (itemId === null) return null

assertInRuntimeContext(context, info)
return fetchItemForItemViewFieldMode(context)(listKey, itemId).then(item => {
if (item === null) {
Expand Down Expand Up @@ -285,30 +281,23 @@ function assertInRuntimeContext (
{ parentType, fieldName }: GraphQLResolveInfo
): asserts context is KeystoneContext {
if ('isAdminUIBuildProcess' in context) {
throw new Error(
`${parentType}.${fieldName} cannot be resolved during the build process`
)
throw new Error(`${parentType}.${fieldName} cannot be resolved during the build process`)
}
}

// TypeScript doesn't infer a mapped type when using a computed property that's a type parameter
function objectFromKeyVal<Key extends string, Val> (key: Key, val: Val): { [_ in Key]: Val } {
return { [key]: val } as { [_ in Key]: Val }
}

function contextFunctionField<Key extends string, Type extends string | boolean> (
key: Key,
type: ScalarType<Type> | EnumType<Record<string, EnumValue<Type>>>
) {
type Source = { [_ in Key]: (context: KeystoneContext) => MaybePromise<Type> }
return objectFromKeyVal(
key,
graphql.field({
return {
[key]: graphql.field({
type: graphql.nonNull(type),
resolve (source: Source, args, context, info) {
resolve (source: {
[_ in Key]: (context: KeystoneContext) => MaybePromise<Type>
}, args, context, info) {
assertInRuntimeContext(context, info)
return source[key](context)
},
})
)
}
}
Loading

0 comments on commit 2849031

Please sign in to comment.