Skip to content
Closed

Test CI #10675

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
13 changes: 13 additions & 0 deletions dev-test/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,19 @@ const config: CodegenConfig = {
},
},
},
// #region documentsReadOnly
'./dev-test/documents-readonly/app/types.generated.ts': {
schema: './dev-test/documents-readonly/schema.graphqls',
documents: ['./dev-test/documents-readonly/app/*.graphql.ts'],
documentsReadOnly: ['./dev-test/documents-readonly/lib/*.graphql.ts'],
plugins: ['typescript-operations'],
},
'./dev-test/documents-readonly/lib/types.generated.ts': {
schema: './dev-test/documents-readonly/schema.graphqls',
documents: ['./dev-test/documents-readonly/lib/*.graphql.ts'],
plugins: ['typescript-operations'],
},
// #endregion
},
};

Expand Down
8 changes: 8 additions & 0 deletions dev-test/documents-readonly/app/User.graphql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/* GraphQL */ `
query User($id: ID!) {
user(id: $id) {
id
...UserFragment
}
}
`;
8 changes: 8 additions & 0 deletions dev-test/documents-readonly/app/types.generated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type UserQueryVariables = Exact<{
id: Scalars['ID']['input'];
}>;

export type UserQuery = {
__typename?: 'Query';
user?: { __typename?: 'User'; id: string; name: string; role: UserRole } | null;
};
7 changes: 7 additions & 0 deletions dev-test/documents-readonly/lib/UserFragment.graphql.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* GraphQL */ `
fragment UserFragment on User {
id
name
role
}
`;
1 change: 1 addition & 0 deletions dev-test/documents-readonly/lib/types.generated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type UserFragmentFragment = { __typename?: 'User'; id: string; name: string; role: UserRole };
14 changes: 14 additions & 0 deletions dev-test/documents-readonly/schema.graphqls
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
type Query {
user(id: ID!): User
}

type User {
id: ID!
name: String!
role: UserRole!
}

enum UserRole {
ADMIN
CUSTOMER
}
128 changes: 97 additions & 31 deletions packages/graphql-codegen-cli/src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
normalizeOutputParam,
Types,
} from '@graphql-codegen/plugin-helpers';
import { NoTypeDefinitionsFound } from '@graphql-tools/load';
import { NoTypeDefinitionsFound, type UnnormalizedTypeDefPointer } from '@graphql-tools/load';
import { mergeTypeDefs } from '@graphql-tools/merge';
import { CodegenContext, ensureContext } from './config.js';
import { getDocumentTransform } from './documentTransforms.js';
Expand Down Expand Up @@ -86,6 +86,7 @@ export async function executeCodegen(
let rootConfig: { [key: string]: any } = {};
let rootSchemas: Types.Schema[];
let rootDocuments: Types.OperationDocument[];
let rootDocumentsReadOnly: Types.OperationDocument[];
const generates: { [filename: string]: Types.ConfiguredOutput } = {};

const cache = createCache();
Expand Down Expand Up @@ -136,6 +137,11 @@ export async function executeCodegen(
/* Normalize root "documents" field */
rootDocuments = normalizeInstanceOrArray<Types.OperationDocument>(config.documents);

/* Normalize root "documentsReadOnly" field */
rootDocumentsReadOnly = normalizeInstanceOrArray<Types.OperationDocument>(
config.documentsReadOnly,
);

/* Normalize "generators" field */
const generateKeys = Object.keys(config.generates || {});

Expand Down Expand Up @@ -228,13 +234,15 @@ export async function executeCodegen(
let outputSchemaAst: GraphQLSchema;
let outputSchema: DocumentNode;
const outputFileTemplateConfig = outputConfig.config || {};
let outputDocuments: Types.DocumentFile[] = [];
const outputDocuments: Types.DocumentFile[] = [];
const outputSpecificSchemas = normalizeInstanceOrArray<Types.Schema>(
outputConfig.schema,
);
let outputSpecificDocuments = normalizeInstanceOrArray<Types.OperationDocument>(
outputConfig.documents,
);
let outputSpecificDocumentsReadOnly =
normalizeInstanceOrArray<Types.OperationDocument>(outputConfig.documentsReadOnly);

const preset: Types.OutputPreset | null = hasPreset
? typeof outputConfig.preset === 'string'
Expand All @@ -247,6 +255,10 @@ export async function executeCodegen(
filename,
outputSpecificDocuments,
);
outputSpecificDocumentsReadOnly = await preset.prepareDocuments(
filename,
outputSpecificDocumentsReadOnly,
);
}

return subTask.newListr(
Expand Down Expand Up @@ -308,41 +320,97 @@ export async function executeCodegen(
task: wrapTask(
async () => {
debugLog(`[CLI] Loading Documents`);
const documentPointerMap: any = {};

const populateDocumentPointerMap = (
allDocumentsDenormalizedPointers: Types.OperationDocument[],
): UnnormalizedTypeDefPointer => {
const pointer: UnnormalizedTypeDefPointer = {};
for (const denormalizedPtr of allDocumentsDenormalizedPointers) {
if (typeof denormalizedPtr === 'string') {
pointer[denormalizedPtr] = {};
} else if (typeof denormalizedPtr === 'object') {
Object.assign(pointer, denormalizedPtr);
}
}
return pointer;
};

const allDocumentsDenormalizedPointers = [
...rootDocuments,
...outputSpecificDocuments,
];
for (const denormalizedPtr of allDocumentsDenormalizedPointers) {
if (typeof denormalizedPtr === 'string') {
documentPointerMap[denormalizedPtr] = {};
} else if (typeof denormalizedPtr === 'object') {
Object.assign(documentPointerMap, denormalizedPtr);
}
}
const documentPointerMap = populateDocumentPointerMap(
allDocumentsDenormalizedPointers,
);

const hash = JSON.stringify(documentPointerMap);
const result = await cache('documents', hash, async () => {
try {
const documents = await context.loadDocuments(documentPointerMap);
return {
documents,
};
} catch (error) {
if (
error instanceof NoTypeDefinitionsFound &&
config.ignoreNoDocuments
) {
return {
documents: [],
};
const outputDocumentsStandard = await cache(
'documents',
hash,
async (): Promise<Types.DocumentFile[]> => {
try {
const documents = await context.loadDocuments(
documentPointerMap,
'standard',
);
return documents;
} catch (error) {
if (
error instanceof NoTypeDefinitionsFound &&
config.ignoreNoDocuments
) {
return [];
}
throw error;
}
},
);

const allReadOnlyDenormalizedPointers = [
...rootDocumentsReadOnly,
...outputSpecificDocumentsReadOnly,
];

const readOnlyPointerMap = populateDocumentPointerMap(
allReadOnlyDenormalizedPointers,
);

throw error;
const readOnlyHash = JSON.stringify(readOnlyPointerMap);
const outputDocumentsReadOnly = await cache(
'documents',
readOnlyHash,
async (): Promise<Types.DocumentFile[]> => {
try {
const documents = await context.loadDocuments(
readOnlyPointerMap,
'read-only',
);
return documents;
} catch (error) {
if (
error instanceof NoTypeDefinitionsFound &&
config.ignoreNoDocuments
) {
return [];
}
throw error;
}
},
);

const processedFile: Record<string, true> = {};
[...outputDocumentsStandard, ...outputDocumentsReadOnly].reduce<
Types.DocumentFile[]
>((prev, file) => {
if (processedFile[file.hash]) {
return prev;
}
});

outputDocuments = result.documents;
outputDocuments.push(file);
processedFile[file.hash] = true;

return prev;
}, []);
},
filename,
`Load GraphQL documents: ${filename}`,
Expand Down Expand Up @@ -437,7 +505,7 @@ export async function executeCodegen(
pluginContext,
profiler: context.profiler,
documentTransforms,
},
} satisfies Types.GenerateOptions,
];

const process = async (outputArgs: Types.GenerateOptions) => {
Expand Down Expand Up @@ -519,9 +587,7 @@ export async function executeCodegen(
const errors = executedContext.errors.map(subErr => subErr.message || subErr.toString());
error = new AggregateError(executedContext.errors, String(errors.join('\n\n')));
// Best-effort to all stack traces for debugging
error.stack = `${error.stack}\n\n${executedContext.errors
.map(subErr => subErr.stack)
.join('\n\n')}`;
error.stack = `${error.stack}\n\n${executedContext.errors.map(subErr => subErr.stack).join('\n\n')}`;
}

return { result, error };
Expand Down
40 changes: 24 additions & 16 deletions packages/graphql-codegen-cli/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createRequire } from 'module';
import { resolve } from 'path';
import { cosmiconfig, defaultLoaders } from 'cosmiconfig';
import { GraphQLSchema, GraphQLSchemaExtensions, print } from 'graphql';
import { GraphQLConfig } from 'graphql-config';
import { GraphQLConfig, type Source } from 'graphql-config';
import { createJiti } from 'jiti';
import { env } from 'string-env-interpolation';
import yaml from 'yaml';
Expand All @@ -16,6 +16,7 @@ import {
Profiler,
Types,
} from '@graphql-codegen/plugin-helpers';
import type { UnnormalizedTypeDefPointer } from '@graphql-tools/load';
import { findAndLoadGraphQLConfig } from './graphql-config.js';
import {
defaultDocumentsLoadOptions,
Expand Down Expand Up @@ -464,18 +465,22 @@ export class CodegenContext {
return addHashToSchema(loadSchema(pointer, config));
}

async loadDocuments(pointer: Types.OperationDocument[]): Promise<Types.DocumentFile[]> {
async loadDocuments(
pointer: UnnormalizedTypeDefPointer,
type: 'standard' | 'read-only',
): Promise<Types.DocumentFile[]> {
const config = this.getConfig(defaultDocumentsLoadOptions);
if (this._graphqlConfig) {
// TODO: pointer won't work here
return addHashToDocumentFiles(
this._graphqlConfig
.getProject(this._project)
.loadDocuments(pointer, { ...config, ...config.config }),
type,
);
}

return addHashToDocumentFiles(loadDocuments(pointer, config));
return addHashToDocumentFiles(loadDocuments(pointer, config), type);
}
}

Expand All @@ -502,24 +507,27 @@ function addHashToSchema(schemaPromise: Promise<GraphQLSchema>): Promise<GraphQL
});
}

function hashDocument(doc: Types.DocumentFile) {
if (doc.rawSDL) {
return hashContent(doc.rawSDL);
}
async function addHashToDocumentFiles(
documentFilesPromise: Promise<Source[]>,
type: 'standard' | 'read-only',
): Promise<Types.DocumentFile[]> {
function hashDocument(doc: Source) {
if (doc.rawSDL) {
return hashContent(doc.rawSDL);
}

if (doc.document) {
return hashContent(print(doc.document));
}
if (doc.document) {
return hashContent(print(doc.document));
}

return null;
}
return null;
}

function addHashToDocumentFiles(
documentFilesPromise: Promise<Types.DocumentFile[]>,
): Promise<Types.DocumentFile[]> {
return documentFilesPromise.then(documentFiles =>
documentFiles.map(doc => {
// Note: `doc` here is technically `Source`, but by the end of the funciton it's `Types.DocumentFile`. This re-declaration makes TypeScript happy.
documentFiles.map((doc: Types.DocumentFile): Types.DocumentFile => {
doc.hash = hashDocument(doc);
doc.type = type;

return doc;
}),
Expand Down
3 changes: 2 additions & 1 deletion packages/graphql-codegen-cli/src/load.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { extname, join } from 'path';
import { GraphQLError, GraphQLSchema } from 'graphql';
import type { Source } from 'graphql-config';
import { Types } from '@graphql-codegen/plugin-helpers';
import { ApolloEngineLoader } from '@graphql-tools/apollo-engine-loader';
import { CodeFileLoader } from '@graphql-tools/code-file-loader';
Expand Down Expand Up @@ -69,7 +70,7 @@ export async function loadSchema(
export async function loadDocuments(
documentPointers: UnnormalizedTypeDefPointer | UnnormalizedTypeDefPointer[],
config: Types.Config,
): Promise<Types.DocumentFile[]> {
): Promise<Source[]> {
const loaders = [
new CodeFileLoader({
pluckConfig: {
Expand Down
Loading
Loading