From 32f7a6ad4a5a51a58988e7be495c96b06771b9b7 Mon Sep 17 00:00:00 2001 From: Marek R <60657309+marekryb@users.noreply.github.com> Date: Mon, 21 Aug 2023 09:50:09 +0900 Subject: [PATCH] Add graphql.schemaPath configuration (#8773) Co-authored-by: Marek Rybczynski Co-authored-by: Daniel Cousens <413395+dcousens@users.noreply.github.com> --- .changeset/add-graphql-schema-path.md | 5 + .prettierignore | 2 +- docs/pages/docs/config/config.md | 1 + examples/custom-output-paths/keystone.ts | 4 + .../custom-output-paths/my-graphql.graphql | 232 ++++++++++++++++++ packages/core/src/artifacts.ts | 6 +- packages/core/src/types/config/index.ts | 6 + tests/cli-tests/artifacts.test.ts | 14 +- .../custom-prisma-project/schema.graphql | 2 +- tests/cli-tests/utils.tsx | 5 + 10 files changed, 261 insertions(+), 16 deletions(-) create mode 100644 .changeset/add-graphql-schema-path.md create mode 100644 examples/custom-output-paths/my-graphql.graphql diff --git a/.changeset/add-graphql-schema-path.md b/.changeset/add-graphql-schema-path.md new file mode 100644 index 00000000000..a9b7e103b85 --- /dev/null +++ b/.changeset/add-graphql-schema-path.md @@ -0,0 +1,5 @@ +--- +'@keystone-6/core': minor +--- + +Adds `config.graphql.schemaPath` diff --git a/.prettierignore b/.prettierignore index 5044385a744..e80b48597de 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,7 +2,7 @@ **/.next **/__generated__ **/dist -**/schema.graphql +**/*.graphql *.vscode .changeset/**/* .keystone/tests diff --git a/docs/pages/docs/config/config.md b/docs/pages/docs/config/config.md index 466def57c54..4f123ab1d73 100644 --- a/docs/pages/docs/config/config.md +++ b/docs/pages/docs/config/config.md @@ -356,6 +356,7 @@ Options: - `false` - Add `ApolloServerPluginLandingPageDisabled` to the Apollo Server plugins - `'apollo'` - Do not add any plugins to the Apollo config, this will use [Apollo Sandbox](https://www.apollographql.com/docs/apollo-server/testing/build-run-queries/#apollo-sandbox) - `apolloConfig` (default: `undefined`): Allows you to pass [extra options](https://www.apollographql.com/docs/apollo-server/api/apollo-server/#constructor) into the `ApolloServer` constructor. +- `schemaPath` (default: `schema.graphql`): The path of the generated GraphQL API schema. ```typescript export default config({ diff --git a/examples/custom-output-paths/keystone.ts b/examples/custom-output-paths/keystone.ts index 5b151f22e19..9b46c955f27 100644 --- a/examples/custom-output-paths/keystone.ts +++ b/examples/custom-output-paths/keystone.ts @@ -12,6 +12,10 @@ export default config({ }, lists, + graphql: { + schemaPath: 'my-graphql.graphql', + }, + // when working in a monorepo environment you may want to output the types elsewhere // you can use .types.path to configure where that is types: { diff --git a/examples/custom-output-paths/my-graphql.graphql b/examples/custom-output-paths/my-graphql.graphql new file mode 100644 index 00000000000..fc3788e549e --- /dev/null +++ b/examples/custom-output-paths/my-graphql.graphql @@ -0,0 +1,232 @@ +# This file is automatically generated by Keystone, do not modify it manually. +# Modify your Keystone config when you want to change this. + +type Post { + id: ID! + title: String + content: String + publishDate: DateTime +} + +scalar DateTime @specifiedBy(url: "https://datatracker.ietf.org/doc/html/rfc3339#section-5.6") + +input PostWhereUniqueInput { + id: ID +} + +input PostWhereInput { + AND: [PostWhereInput!] + OR: [PostWhereInput!] + NOT: [PostWhereInput!] + id: IDFilter + title: StringFilter + content: StringFilter + publishDate: DateTimeNullableFilter +} + +input IDFilter { + equals: ID + in: [ID!] + notIn: [ID!] + lt: ID + lte: ID + gt: ID + gte: ID + not: IDFilter +} + +input StringFilter { + equals: String + in: [String!] + notIn: [String!] + lt: String + lte: String + gt: String + gte: String + contains: String + startsWith: String + endsWith: String + not: NestedStringFilter +} + +input NestedStringFilter { + equals: String + in: [String!] + notIn: [String!] + lt: String + lte: String + gt: String + gte: String + contains: String + startsWith: String + endsWith: String + not: NestedStringFilter +} + +input DateTimeNullableFilter { + equals: DateTime + in: [DateTime!] + notIn: [DateTime!] + lt: DateTime + lte: DateTime + gt: DateTime + gte: DateTime + not: DateTimeNullableFilter +} + +input PostOrderByInput { + id: OrderDirection + title: OrderDirection + content: OrderDirection + publishDate: OrderDirection +} + +enum OrderDirection { + asc + desc +} + +input PostUpdateInput { + title: String + content: String + publishDate: DateTime +} + +input PostUpdateArgs { + where: PostWhereUniqueInput! + data: PostUpdateInput! +} + +input PostCreateInput { + title: String + content: String + publishDate: DateTime +} + +""" +The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). +""" +scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") + +type Mutation { + createPost(data: PostCreateInput!): Post + createPosts(data: [PostCreateInput!]!): [Post] + updatePost(where: PostWhereUniqueInput!, data: PostUpdateInput!): Post + updatePosts(data: [PostUpdateArgs!]!): [Post] + deletePost(where: PostWhereUniqueInput!): Post + deletePosts(where: [PostWhereUniqueInput!]!): [Post] +} + +type Query { + posts(where: PostWhereInput! = {}, orderBy: [PostOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: PostWhereUniqueInput): [Post!] + post(where: PostWhereUniqueInput!): Post + postsCount(where: PostWhereInput! = {}): Int + keystone: KeystoneMeta! +} + +type KeystoneMeta { + adminMeta: KeystoneAdminMeta! +} + +type KeystoneAdminMeta { + lists: [KeystoneAdminUIListMeta!]! + list(key: String!): KeystoneAdminUIListMeta +} + +type KeystoneAdminUIListMeta { + key: String! + itemQueryName: String! + listQueryName: String! + hideCreate: Boolean! + hideDelete: Boolean! + path: String! + label: String! + singular: String! + plural: String! + description: String + initialColumns: [String!]! + pageSize: Int! + labelField: String! + fields: [KeystoneAdminUIFieldMeta!]! + groups: [KeystoneAdminUIFieldGroupMeta!]! + initialSort: KeystoneAdminUISort + isHidden: Boolean! + isSingleton: Boolean! +} + +type KeystoneAdminUIFieldMeta { + path: String! + label: String! + description: String + isOrderable: Boolean! + isFilterable: Boolean! + isNonNull: [KeystoneAdminUIFieldMetaIsNonNull!] + fieldMeta: JSON + viewsIndex: Int! + customViewsIndex: Int + createView: KeystoneAdminUIFieldMetaCreateView! + listView: KeystoneAdminUIFieldMetaListView! + itemView(id: ID): KeystoneAdminUIFieldMetaItemView + search: QueryMode +} + +enum KeystoneAdminUIFieldMetaIsNonNull { + read + create + update +} + +type KeystoneAdminUIFieldMetaCreateView { + fieldMode: KeystoneAdminUIFieldMetaCreateViewFieldMode! +} + +enum KeystoneAdminUIFieldMetaCreateViewFieldMode { + edit + hidden +} + +type KeystoneAdminUIFieldMetaListView { + fieldMode: KeystoneAdminUIFieldMetaListViewFieldMode! +} + +enum KeystoneAdminUIFieldMetaListViewFieldMode { + read + hidden +} + +type KeystoneAdminUIFieldMetaItemView { + fieldMode: KeystoneAdminUIFieldMetaItemViewFieldMode + fieldPosition: KeystoneAdminUIFieldMetaItemViewFieldPosition +} + +enum KeystoneAdminUIFieldMetaItemViewFieldMode { + edit + read + hidden +} + +enum KeystoneAdminUIFieldMetaItemViewFieldPosition { + form + sidebar +} + +enum QueryMode { + default + insensitive +} + +type KeystoneAdminUIFieldGroupMeta { + label: String! + description: String + fields: [KeystoneAdminUIFieldMeta!]! +} + +type KeystoneAdminUISort { + field: String! + direction: KeystoneAdminUISortDirection! +} + +enum KeystoneAdminUISortDirection { + ASC + DESC +} diff --git a/packages/core/src/artifacts.ts b/packages/core/src/artifacts.ts index 07b03345ddf..e8bb31f35dd 100644 --- a/packages/core/src/artifacts.ts +++ b/packages/core/src/artifacts.ts @@ -100,6 +100,10 @@ export function getSystemPaths(cwd: string, config: KeystoneConfig) { ? `./${posixify(path.relative(path.dirname(builtTypesPath), prismaClientPath))}` : '@prisma/client'; + const builtGraphqlPath = config.graphql?.schemaPath + ? path.join(cwd, config.graphql.schemaPath) + : path.join(cwd, 'schema.graphql'); + return { config: getBuiltKeystoneConfigurationPath(cwd), admin: path.join(cwd, '.keystone/admin'), @@ -110,7 +114,7 @@ export function getSystemPaths(cwd: string, config: KeystoneConfig) { schema: { types: builtTypesPath, prisma: path.join(cwd, 'schema.prisma'), - graphql: path.join(cwd, 'schema.graphql'), + graphql: builtGraphqlPath, }, }; } diff --git a/packages/core/src/types/config/index.ts b/packages/core/src/types/config/index.ts index a4e04c8c6f4..68caee38f2a 100644 --- a/packages/core/src/types/config/index.ts +++ b/packages/core/src/types/config/index.ts @@ -282,6 +282,12 @@ export type GraphQLConfig { expect(files).toEqual(await getFiles(`${__dirname}/fixtures/basic-project`, schemasMatch)); expect(recording()).toMatchInlineSnapshot(`"? Generated GraphQL and Prisma schemas"`); }); - test('customising primsa schema through extendPrisma works', async () => { - const tmp = await testdir({ - ...symlinkKeystoneDeps, - 'keystone.js': customPrismaKeystoneConfig, - }); - const recording = recordConsole(); - await runCommand(tmp, ['postinstall', '--fix']); - const files = await getFiles(tmp, ['schema.prisma']); - expect(files).toEqual( - await getFiles(`${__dirname}/fixtures/custom-prisma-project`, ['schema.prisma']) - ); - expect(recording()).toMatchInlineSnapshot(`"? Generated GraphQL and Prisma schemas"`); - }); test("does not prompt, error or modify the schemas if they're already up to date", async () => { const tmp = await testdir({ ...symlinkKeystoneDeps, diff --git a/tests/cli-tests/fixtures/custom-prisma-project/schema.graphql b/tests/cli-tests/fixtures/custom-prisma-project/schema.graphql index 2967440a834..51a4bdd115c 100644 --- a/tests/cli-tests/fixtures/custom-prisma-project/schema.graphql +++ b/tests/cli-tests/fixtures/custom-prisma-project/schema.graphql @@ -95,7 +95,7 @@ type Mutation { } type Query { - todos(where: TodoWhereInput! = {}, orderBy: [TodoOrderByInput!]! = [], take: Int, skip: Int! = 0): [Todo!] + todos(where: TodoWhereInput! = {}, orderBy: [TodoOrderByInput!]! = [], take: Int, skip: Int! = 0, cursor: TodoWhereUniqueInput): [Todo!] todo(where: TodoWhereUniqueInput!): Todo todosCount(where: TodoWhereInput! = {}): Int keystone: KeystoneMeta! diff --git a/tests/cli-tests/utils.tsx b/tests/cli-tests/utils.tsx index 3846d02e6db..ff1a8c4c6f3 100644 --- a/tests/cli-tests/utils.tsx +++ b/tests/cli-tests/utils.tsx @@ -40,6 +40,11 @@ export const customPrismaKeystoneConfig = fs.readFileSync( 'utf8' ); +export const customGraphqlPathKeystoneConfig = fs.readFileSync( + `${__dirname}/fixtures/custom-graphql-path/keystone.ts`, + 'utf8' +); + export function recordConsole() { let oldConsole = { ...console }; const contents: string[] = [];