diff --git a/.changeset/fifty-jars-end.md b/.changeset/fifty-jars-end.md new file mode 100644 index 00000000000..a5f060874a7 --- /dev/null +++ b/.changeset/fifty-jars-end.md @@ -0,0 +1,5 @@ +--- +"@graphql-codegen/cli": patch +--- + +Fix/multi project diff --git a/packages/graphql-codegen-cli/src/codegen.ts b/packages/graphql-codegen-cli/src/codegen.ts index bc269fefcdd..bc60a315878 100644 --- a/packages/graphql-codegen-cli/src/codegen.ts +++ b/packages/graphql-codegen-cli/src/codegen.ts @@ -218,7 +218,7 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom let outputSchemaAst: GraphQLSchema; let outputSchema: DocumentNode; const outputFileTemplateConfig = outputConfig.config || {}; - const outputDocuments: Types.DocumentFile[] = []; + let outputDocuments: Types.DocumentFile[] = []; const outputSpecificSchemas = normalizeInstanceOrArray(outputConfig.schema); const outputSpecificDocuments = normalizeInstanceOrArray(outputConfig.documents); @@ -264,25 +264,25 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom debugLog(`[CLI] Loading Documents`); // get different cache for shared docs and output specific docs - const results = await Promise.all( - [rootDocuments, outputSpecificDocuments].map(docs => { - const hash = JSON.stringify(docs); - return cache('documents', hash, async () => { - const documents = await context.loadDocuments(docs); - return { - documents, - }; - }); - }) - ); - - const documents: Types.DocumentFile[] = []; + const documentPointerMap: any = {}; + const allDocumentsUnnormalizedPointers = [...rootDocuments, ...outputSpecificDocuments]; + for (const unnormalizedPtr of allDocumentsUnnormalizedPointers) { + if (typeof unnormalizedPtr === 'string') { + documentPointerMap[unnormalizedPtr] = {}; + } else if (typeof unnormalizedPtr === 'object') { + Object.assign(documentPointerMap, unnormalizedPtr); + } + } - results.forEach(source => documents.push(...source.documents)); + const hash = JSON.stringify(documentPointerMap); + const result = await cache('documents', hash, async () => { + const documents = await context.loadDocuments(documentPointerMap); + return { + documents, + }; + }); - if (documents.length > 0) { - outputDocuments.push(...documents); - } + outputDocuments = await result.documents; }, filename, `Load GraphQL documents: ${filename}` @@ -321,7 +321,6 @@ export async function executeCodegen(input: CodegenContext | Types.Config): Prom }; let outputs: Types.GenerateOptions[] = []; - if (hasPreset) { outputs = await context.profiler.run( async () => diff --git a/packages/graphql-codegen-cli/tests/codegen.spec.ts b/packages/graphql-codegen-cli/tests/codegen.spec.ts index 62c11ae7fed..0bba5d728ce 100644 --- a/packages/graphql-codegen-cli/tests/codegen.spec.ts +++ b/packages/graphql-codegen-cli/tests/codegen.spec.ts @@ -1,8 +1,8 @@ import { useMonorepo } from '@graphql-codegen/testing'; -import { GraphQLObjectType, buildSchema, buildASTSchema, parse, print } from 'graphql'; import { mergeTypeDefs } from '@graphql-tools/merge'; -import { executeCodegen } from '../src'; +import { buildASTSchema, buildSchema, GraphQLObjectType, parse, print } from 'graphql'; import { join } from 'path'; +import { createContext, executeCodegen } from '../src'; const SHOULD_NOT_THROW_STRING = 'SHOULD_NOT_THROW'; const SIMPLE_TEST_SCHEMA = `type MyType { f: String } type Query { f: String }`; @@ -961,6 +961,7 @@ describe('Codegen Executor', () => { throw new Error('This should not throw as the invalid file is excluded via glob.'); } }); + it('Should allow plugins to extend schema with custom root', async () => { try { const output = await executeCodegen({ @@ -1067,4 +1068,20 @@ describe('Codegen Executor', () => { expect(error.message).toContain('Failed to load schema for "out1.graphql"'); } }); + + it('Should generate documents output even if prj1/documents and prj1/extensions/codegen/generate/xxx/documents are both definded with the same glob files', async () => { + const prj1 = await createContext({ + config: './tests/test-files/graphql.config.js', + project: 'prj1', + errorsOnly: true, + overwrite: true, + profile: true, + require: [], + silent: false, + watch: false, + }); + const config = prj1.getConfig(); + const output = await executeCodegen(config); + expect(output[0].content).toContain('DocumentNode'); + }); }); diff --git a/packages/graphql-codegen-cli/tests/test-files/graphql.config.js b/packages/graphql-codegen-cli/tests/test-files/graphql.config.js new file mode 100755 index 00000000000..84a3d87a5e8 --- /dev/null +++ b/packages/graphql-codegen-cli/tests/test-files/graphql.config.js @@ -0,0 +1,21 @@ +/** @type {import('graphql-config').IGraphQLConfig } */ +module.exports = { + generates: {}, + projects: { + prj1: { + schema: ['**/test-documents/schema.graphql'], + documents: ['**/test-documents/valid.graphql'], + extensions: { + codegen: { + generates: { + 'graphqlTypes.ts': { + schema: ['**/test-documents/schema.graphql'], + documents: ['**/test-documents/valid.graphql'], + plugins: ['typed-document-node'], + }, + }, + }, + }, + }, + }, +}; diff --git a/website/docs/config-reference/codegen-config.mdx b/website/docs/config-reference/codegen-config.mdx index ee985280d53..7f03a3f6cfb 100644 --- a/website/docs/config-reference/codegen-config.mdx +++ b/website/docs/config-reference/codegen-config.mdx @@ -151,6 +151,8 @@ The Codegen also supports several CLI flags that allow you to override the defau - **`--profile`** - Use the profiler to measure performance. _(see "Profiler" in "Advanced Usage")_ +- **`--project` (`-p`)** - To generate only one project out of a [Multi Project](multiproject-config) config file. +

 

--- diff --git a/website/docs/config-reference/multiproject-config.mdx b/website/docs/config-reference/multiproject-config.mdx new file mode 100644 index 00000000000..3494c7757cd --- /dev/null +++ b/website/docs/config-reference/multiproject-config.mdx @@ -0,0 +1,62 @@ +--- +id: multiproject-config +title: 'Multi Project' +--- + +In some cases, it's useful to have multiple projects in the same config file. + +For example, in a monorepo, the VSCode GraphQL plugin will automatically pick up the right config for the right folder. + +

 

+ +--- + +

 

+ +## Configuration file format + +Here's an example for a possible config file `graphql.config.js`: + +```js +/** @type {import('graphql-config').IGraphQLConfig } */ +module.exports = { + projects: { + prj1: { + schema: ['prj1/**/*.graphql'], + documents: ['prj1/**/*.gql'], + extensions: { + codegen: { + generates: { + 'graphqlTypes.ts': { + plugins: ['typescript', 'typed-document-node'] + } + } + } + } + }, + prj2: { + schema: ['prj2/**/*.graphql'], + documents: ['prj2/**/*.gql'], + extensions: { + codegen: { + generates: { + 'graphqlTypes.ts': { + plugins: ['typescript', 'typed-document-node'] + } + } + } + } + } + } +} +``` + +

 

+ +## Command to generate files + +With the previous config, you can generate files with the following command: + +```bash +graphql-codegen --config graphql.config.js -project prj1 +``` diff --git a/website/routes.ts b/website/routes.ts index a1ccfffce0a..b80b1460935 100644 --- a/website/routes.ts +++ b/website/routes.ts @@ -35,6 +35,7 @@ export function getRoutes(): IRoutes { ['require-field', 'require field'], ['naming-convention', 'Naming Convention'], ['lifecycle-hooks', 'Lifecycle Hooks'], + ['multiproject-config', 'Multi Project'], ], }, 'docs/advanced': {