Skip to content
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/curly-cats-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@keystone-6/core': patch
---

Adds support for Keystone projects using `"type": "module"` in their `package.json` by generating an empty `package.json` in `.keystone` so Node continues inferring module types in `.keystone`
20 changes: 10 additions & 10 deletions examples/custom-output-paths/my-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,22 +119,22 @@ export type KeystoneAdminUISortDirection =
| 'DESC'

type ResolvedPostCreateInput = {
id?: import('./generated/custom-prisma/client').Prisma.PostCreateInput['id']
title?: import('./generated/custom-prisma/client').Prisma.PostCreateInput['title']
content?: import('./generated/custom-prisma/client').Prisma.PostCreateInput['content']
publishDate?: import('./generated/custom-prisma/client').Prisma.PostCreateInput['publishDate']
id?: import('./generated/custom-prisma/client.js').Prisma.PostCreateInput['id']
title?: import('./generated/custom-prisma/client.js').Prisma.PostCreateInput['title']
content?: import('./generated/custom-prisma/client.js').Prisma.PostCreateInput['content']
publishDate?: import('./generated/custom-prisma/client.js').Prisma.PostCreateInput['publishDate']
}
type ResolvedPostUpdateInput = {
id?: undefined
title?: import('./generated/custom-prisma/client').Prisma.PostUpdateInput['title']
content?: import('./generated/custom-prisma/client').Prisma.PostUpdateInput['content']
publishDate?: import('./generated/custom-prisma/client').Prisma.PostUpdateInput['publishDate']
title?: import('./generated/custom-prisma/client.js').Prisma.PostUpdateInput['title']
content?: import('./generated/custom-prisma/client.js').Prisma.PostUpdateInput['content']
publishDate?: import('./generated/custom-prisma/client.js').Prisma.PostUpdateInput['publishDate']
}

export declare namespace Lists {
export type Post<Session = any> = import('@keystone-6/core/types').ListConfig<Lists.Post.TypeInfo<Session>>
namespace Post {
export type Item = import('./generated/custom-prisma/client').Post
export type Item = import('./generated/custom-prisma/client.js').Post
export type TypeInfo<Session = any> = {
key: 'Post'
isSingleton: false
Expand Down Expand Up @@ -163,8 +163,8 @@ export type TypeInfo<Session = any> = {
lists: {
readonly Post: Lists.Post.TypeInfo<Session>
}
prisma: import('./generated/custom-prisma/client').PrismaClient
prismaClientOptions: import('./generated/custom-prisma/client').Prisma.PrismaClientOptions
prisma: import('./generated/custom-prisma/client.js').PrismaClient
prismaClientOptions: import('./generated/custom-prisma/client.js').Prisma.PrismaClientOptions
session: Session
dbProvider: 'sqlite'
}
Expand Down
40 changes: 20 additions & 20 deletions examples/extend-graphql-schema-nexus/keystone-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,36 +200,36 @@ export type KeystoneAdminUISortDirection =
| 'DESC'

type ResolvedPostCreateInput = {
id?: import('./generated/prisma/client').Prisma.PostCreateInput['id']
title?: import('./generated/prisma/client').Prisma.PostCreateInput['title']
status?: import('./generated/prisma/client').Prisma.PostCreateInput['status']
content?: import('./generated/prisma/client').Prisma.PostCreateInput['content']
publishDate?: import('./generated/prisma/client').Prisma.PostCreateInput['publishDate']
author?: import('./generated/prisma/client').Prisma.PostCreateInput['author']
id?: import('./generated/prisma/client.js').Prisma.PostCreateInput['id']
title?: import('./generated/prisma/client.js').Prisma.PostCreateInput['title']
status?: import('./generated/prisma/client.js').Prisma.PostCreateInput['status']
content?: import('./generated/prisma/client.js').Prisma.PostCreateInput['content']
publishDate?: import('./generated/prisma/client.js').Prisma.PostCreateInput['publishDate']
author?: import('./generated/prisma/client.js').Prisma.PostCreateInput['author']
}
type ResolvedPostUpdateInput = {
id?: undefined
title?: import('./generated/prisma/client').Prisma.PostUpdateInput['title']
status?: import('./generated/prisma/client').Prisma.PostUpdateInput['status']
content?: import('./generated/prisma/client').Prisma.PostUpdateInput['content']
publishDate?: import('./generated/prisma/client').Prisma.PostUpdateInput['publishDate']
author?: import('./generated/prisma/client').Prisma.PostUpdateInput['author']
title?: import('./generated/prisma/client.js').Prisma.PostUpdateInput['title']
status?: import('./generated/prisma/client.js').Prisma.PostUpdateInput['status']
content?: import('./generated/prisma/client.js').Prisma.PostUpdateInput['content']
publishDate?: import('./generated/prisma/client.js').Prisma.PostUpdateInput['publishDate']
author?: import('./generated/prisma/client.js').Prisma.PostUpdateInput['author']
}
type ResolvedAuthorCreateInput = {
id?: import('./generated/prisma/client').Prisma.AuthorCreateInput['id']
name?: import('./generated/prisma/client').Prisma.AuthorCreateInput['name']
posts?: import('./generated/prisma/client').Prisma.AuthorCreateInput['posts']
id?: import('./generated/prisma/client.js').Prisma.AuthorCreateInput['id']
name?: import('./generated/prisma/client.js').Prisma.AuthorCreateInput['name']
posts?: import('./generated/prisma/client.js').Prisma.AuthorCreateInput['posts']
}
type ResolvedAuthorUpdateInput = {
id?: undefined
name?: import('./generated/prisma/client').Prisma.AuthorUpdateInput['name']
posts?: import('./generated/prisma/client').Prisma.AuthorUpdateInput['posts']
name?: import('./generated/prisma/client.js').Prisma.AuthorUpdateInput['name']
posts?: import('./generated/prisma/client.js').Prisma.AuthorUpdateInput['posts']
}

export declare namespace Lists {
export type Post<Session = any> = import('@keystone-6/core/types').ListConfig<Lists.Post.TypeInfo<Session>>
namespace Post {
export type Item = import('./generated/prisma/client').Post
export type Item = import('./generated/prisma/client.js').Post
export type TypeInfo<Session = any> = {
key: 'Post'
isSingleton: false
Expand All @@ -252,7 +252,7 @@ export declare namespace Lists {
}
export type Author<Session = any> = import('@keystone-6/core/types').ListConfig<Lists.Author.TypeInfo<Session>>
namespace Author {
export type Item = import('./generated/prisma/client').Author
export type Item = import('./generated/prisma/client.js').Author
export type TypeInfo<Session = any> = {
key: 'Author'
isSingleton: false
Expand Down Expand Up @@ -282,8 +282,8 @@ export type TypeInfo<Session = any> = {
readonly Post: Lists.Post.TypeInfo<Session>
readonly Author: Lists.Author.TypeInfo<Session>
}
prisma: import('./generated/prisma/client').PrismaClient
prismaClientOptions: import('./generated/prisma/client').Prisma.PrismaClientOptions
prisma: import('./generated/prisma/client.js').PrismaClient
prismaClientOptions: import('./generated/prisma/client.js').Prisma.PrismaClientOptions
session: Session
dbProvider: 'sqlite'
}
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/lib/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ function getSystemPaths(cwd: string, config: KeystoneConfig) {

const builtPrismaPath = path.resolve(cwd, config.db.prismaSchemaPath)

const relativePrismaPath = relativeModulePath(path.dirname(builtTypesPath), prismaClientPath)
// note using .js is the best way to generically import TypeScript types since TS:
// - allows .js imports to .ts files in all module resolution modes with any options
// - doesn't allow bare imports to .ts files in with moduleResolution: nodenext + "type": "module" (like Node)
// - only allows .ts imports to .ts files when using allowImportingTsExtensions: true
const relativePrismaPath =
relativeModulePath(path.dirname(builtTypesPath), prismaClientPath) + '.js'

const builtGraphqlPath = config.graphql?.schemaPath
? path.join(cwd, config.graphql.schemaPath) // TODO: enforce initConfig before getSystemPaths
Expand Down
19 changes: 13 additions & 6 deletions packages/core/src/scripts/esbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ function identity(x: BuildOptions) {
return x
}

async function ensureKeystoneDirectory(cwd: string) {
const directory = nodePath.join(cwd, '.keystone')
await fs.mkdir(directory, { recursive: true })
// this is to reset the module type to be ambiguous in `.keystone`
// so that Keystone users can use "type": "module" in their package.json
// we use an empty object instead of "type": "commonjs"
// because inside `.keystone/admin` the ambiguous behaviour is expected
// e.g. next.config.js is commonjs but pages are ESM
// even though Node doesn't load the pages, Turbopack errors on the ESM pages if "type": "commonjs" is set
await fs.writeFile(nodePath.join(directory, 'package.json'), '{}\n')
}

async function getEsbuildConfigFn(
cwd: string
): Promise<((x: BuildOptions) => BuildOptions | Promise<BuildOptions>) | undefined> {
Expand Down Expand Up @@ -40,15 +52,10 @@ async function getEsbuildConfigForEntry(
outfile: string,
define?: BuildOptions['define']
): Promise<BuildOptions> {
await ensureKeystoneDirectory(cwd)
const esbuildFn = (await getEsbuildConfigFn(cwd)) ?? identity
const resolveDir = nodePath.join(cwd, '.keystone')
const importer = outfile
// we need the .keystone directory to exist so when we resolve from it below, it actually exists
await fs.mkdir(resolveDir, {
// while we don't need to actually make this recursive,
// this will make mkdir not error when the directory already exists
recursive: true,
})
return esbuildFn({
entryPoints: [entryPoint],
absWorkingDir: cwd,
Expand Down
12 changes: 6 additions & 6 deletions tests/cli-tests/__snapshots__/artifacts.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -104,18 +104,18 @@ export type KeystoneAdminUISortDirection =
| 'DESC'

type ResolvedTodoCreateInput = {
id?: import('../prisma/client').Prisma.TodoCreateInput['id']
title?: import('../prisma/client').Prisma.TodoCreateInput['title']
id?: import('../prisma/client.js').Prisma.TodoCreateInput['id']
title?: import('../prisma/client.js').Prisma.TodoCreateInput['title']
}
type ResolvedTodoUpdateInput = {
id?: undefined
title?: import('../prisma/client').Prisma.TodoUpdateInput['title']
title?: import('../prisma/client.js').Prisma.TodoUpdateInput['title']
}

export declare namespace Lists {
export type Todo<Session = any> = import('@keystone-6/core/types').ListConfig<Lists.Todo.TypeInfo<Session>>
namespace Todo {
export type Item = import('../prisma/client').Todo
export type Item = import('../prisma/client.js').Todo
export type TypeInfo<Session = any> = {
key: 'Todo'
isSingleton: false
Expand Down Expand Up @@ -144,8 +144,8 @@ export type TypeInfo<Session = any> = {
lists: {
readonly Todo: Lists.Todo.TypeInfo<Session>
}
prisma: import('../prisma/client').PrismaClient
prismaClientOptions: import('../prisma/client').Prisma.PrismaClientOptions
prisma: import('../prisma/client.js').PrismaClient
prismaClientOptions: import('../prisma/client.js').Prisma.PrismaClientOptions
session: Session
dbProvider: 'sqlite'
}
Expand Down
8 changes: 6 additions & 2 deletions tests/cli-tests/build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ test("start errors when a build hasn't happened", async () => {
await expect(cliMock(cwd, 'start')).rejects.toEqual(new Error('You need to run "keystone build"'))
})

test('build works with typescript without the user defining a babel config', async () => {
test('build works in a Node ESM project without the user defining a babel config', async () => {
const cwd = await testdir({
...symlinkKeystoneDeps,
...schemas,
'package.json': JSON.stringify({ type: 'module' }),
// this is just to show it gets overridden
'.keystone/package.json': JSON.stringify({ type: 'module' }),
'keystone.ts': await readFile(`${__dirname}/fixtures/with-ts.ts`, 'utf8'),
})
const result = await execa('node', [cliBinPath, 'build'], {
Expand All @@ -36,10 +39,11 @@ test('build works with typescript without the user defining a babel config', asy
NEXT_TELEMETRY_DISABLED: '1',
} as any,
})
expect(result.exitCode, result.all).toBe(0)
expect(result.stdout.includes('Compiled successfully')).toBe(true)
expect(result.stdout.includes('Generating static pages')).toBe(true)
expect(result.stdout.includes('Finalizing page optimization')).toBe(true)
expect(result.exitCode).toBe(0)
expect(await readFile(`${cwd}/.keystone/package.json`, 'utf8')).toEqual('{}\n')
expect(require(`${cwd}/.keystone/config.js`)).toEqual(
expect.objectContaining({ default: expect.any(Object) })
)
Expand Down
Loading