diff --git a/packages/cli/src/lib/codegen.ts b/packages/cli/src/lib/codegen.ts index cb569806e1..644c4ceafe 100644 --- a/packages/cli/src/lib/codegen.ts +++ b/packages/cli/src/lib/codegen.ts @@ -194,6 +194,24 @@ async function generateDefaultConfig( const defaultGlob = '*!(*.d).{ts,tsx,js,jsx}'; // No d.ts files const appDirRelative = relativePath(rootDirectory, appDirectory); + const caapiSchema = getSchema('customer-account'); + const caapiProject = findGqlProject(caapiSchema, gqlConfig); + + const customerAccountAPIConfig = caapiProject?.documents + ? { + ['customer-accountapi.generated.d.ts']: { + preset, + schema: caapiSchema, + documents: caapiProject?.documents, + presetConfig: { + gqlSuffix: + caapiProject?.extensions?.languageService?.gqlTagOptions + ?.annotationSuffix, + }, + }, + } + : undefined; + return { filepath: 'virtual:codegen', config: { @@ -207,6 +225,11 @@ async function generateDefaultConfig( defaultGlob, // E.g. ./server.(t|j)s joinPath(appDirRelative, '**', defaultGlob), // E.g. app/routes/_index.(t|j)sx ], + presetConfig: { + gqlSuffix: + sfapiProject?.extensions?.languageService?.gqlTagOptions + ?.annotationSuffix, + }, ...(!!forceSfapiVersion && { presetConfig: {importTypes: false}, @@ -228,6 +251,7 @@ async function generateDefaultConfig( }, }), }, + ...customerAccountAPIConfig, }, }, }; diff --git a/packages/hydrogen-codegen/src/pluck.ts b/packages/hydrogen-codegen/src/pluck.ts index 4827d8de5c..5c484c51cb 100644 --- a/packages/hydrogen-codegen/src/pluck.ts +++ b/packages/hydrogen-codegen/src/pluck.ts @@ -11,7 +11,7 @@ export const pluckConfig = { // Check for internal gql comment: const QUERY = `#graphql ...` const hasInternalGqlComment = node.type === 'TemplateLiteral' && - /\s*#graphql\s*\n/i.test(node.quasis[0]?.value?.raw || ''); + /\s*#graphql(:[\w.\-]+)?\s*\n/i.test(node.quasis[0]?.value?.raw || ''); if (hasInternalGqlComment) return true; diff --git a/packages/hydrogen-codegen/src/preset.ts b/packages/hydrogen-codegen/src/preset.ts index 0f7fc47fae..f024dc5d09 100644 --- a/packages/hydrogen-codegen/src/preset.ts +++ b/packages/hydrogen-codegen/src/preset.ts @@ -2,7 +2,7 @@ import type {Types} from '@graphql-codegen/plugin-helpers'; import * as addPlugin from '@graphql-codegen/add'; import * as typescriptPlugin from '@graphql-codegen/typescript'; import * as typescriptOperationPlugin from '@graphql-codegen/typescript-operations'; -import {processSources} from './sources.js'; +import {processSources, type BuildTypeName} from './sources.js'; import {getDefaultOptions} from './defaults.js'; import { plugin as dtsPlugin, @@ -38,6 +38,19 @@ export type HydrogenPresetConfig = { queryType: string; mutationType: string; }) => string; + /** + * Suffix to filter query documents: `#graphql:`. + * Documents that don't match the prefix are excluded. Passing + * `undefined` or `''` matches only plain comments: `#graphl` + */ + gqlSuffix?: string; + /** + * Override the way that type names are created from queries. + * The names must match the ones generated in typescript-operations. + * Use this option to keep the names in sync if you are customizing them in typescript-operations. + * https://the-guild.dev/graphql/codegen/plugins/typescript/typescript-operations + */ + buildTypeName?: BuildTypeName; }; export const preset: Types.OutputPreset = { @@ -55,8 +68,10 @@ export const preset: Types.OutputPreset = { '[hydrogen-preset] providing additional typescript-based `plugins` leads to duplicated generated types', ); } - - const sourcesWithOperations = processSources(options.documents); + const sourcesWithOperations = processSources( + options.documents, + options.presetConfig, + ); const sources = sourcesWithOperations.map(({source}) => source); const defaultOptions = getDefaultOptions(options.baseOutputDir); diff --git a/packages/hydrogen-codegen/src/sources.ts b/packages/hydrogen-codegen/src/sources.ts index f42ef3b712..0f2df3e14f 100644 --- a/packages/hydrogen-codegen/src/sources.ts +++ b/packages/hydrogen-codegen/src/sources.ts @@ -17,15 +17,26 @@ const capitalizeQueries = ( return capitalize(node.name!.value) + capitalize(node.operation); }; +export type BuildTypeName = typeof capitalizeQueries; + export function processSources( sources: Array, - buildName = capitalizeQueries, + { + gqlSuffix = '', + buildTypeName = capitalizeQueries, + }: {gqlSuffix?: string; buildTypeName?: BuildTypeName} = {}, ) { + const gqlComment = '#graphql'; + const suffixedGqlComment = `#graphql${gqlSuffix ? `:${gqlSuffix}` : ''}\n`; const sourcesWithOperations: Array = []; - for (const originalSource of sources) { - const source = fixLinebreaks(originalSource); - const {document} = source; + for (const {rawSDL, document, ...rest} of sources) { + if (rawSDL) { + const hasGqlComment = rawSDL.startsWith(gqlComment); + const hasGqlSuffix = rawSDL.startsWith(suffixedGqlComment); + if (!hasGqlSuffix && (gqlSuffix || hasGqlComment)) continue; + } + const operations: Array = []; for (const definition of document?.definitions ?? []) { @@ -38,7 +49,7 @@ export function processSources( if (definition.name?.kind !== `Name`) continue; operations.push({ - initialName: buildName(definition), + initialName: buildTypeName(definition), definition, }); } @@ -46,7 +57,7 @@ export function processSources( if (operations.length === 0) continue; sourcesWithOperations.push({ - source, + source: {rawSDL: fixLinebreaks(rawSDL), document, ...rest}, operations, }); } @@ -97,10 +108,6 @@ export function processSources( * * @param source */ -function fixLinebreaks(source: Source) { - const fixedSource = {...source}; - - fixedSource.rawSDL = source.rawSDL?.replace(/\r\n/g, '\n'); - - return fixedSource; +function fixLinebreaks(rawSDL: Source['rawSDL']) { + return rawSDL?.replace(/\r\n/g, '\n'); } diff --git a/packages/hydrogen-codegen/tests/codegen.test.ts b/packages/hydrogen-codegen/tests/codegen.test.ts index da76d7f62e..ce9603587f 100644 --- a/packages/hydrogen-codegen/tests/codegen.test.ts +++ b/packages/hydrogen-codegen/tests/codegen.test.ts @@ -4,17 +4,18 @@ import path from 'node:path'; describe('Hydrogen Codegen', async () => { // Patch dependency before importing the Codegen CLI await import('../src/patch.js'); - const {preset, schema, pluckConfig} = await import('../src/index.js'); + const {preset, getSchema, pluckConfig} = await import('../src/index.js'); const {getDefaultOptions} = await import('../src/defaults.js'); const {executeCodegen} = await import('@graphql-codegen/cli'); const getCodegenOptions = (fixture: string, output = 'out.d.ts') => ({ pluckConfig: pluckConfig as any, + documents: path.join(__dirname, `fixtures/${fixture}`), generates: { [output]: { preset, - schema, - documents: path.join(__dirname, `fixtures/${fixture}`), + schema: getSchema('storefront'), + presetConfig: {}, }, }, }); @@ -206,4 +207,87 @@ describe('Hydrogen Codegen', async () => { " `); }); + + it('generates types for mixed queries using suffixes', async () => { + const codegenConfig = getCodegenOptions( + 'mixed-operations.ts', + 'storefront.out.d.ts', + ); + + codegenConfig.generates['customer-account.out.d.ts'] = { + preset, + schema: getSchema('customer-account'), + presetConfig: { + gqlSuffix: 'customer', + }, + }; + + const result = await executeCodegen(codegenConfig); + + expect(result).toHaveLength(2); + + const generatedStorefrontCode = result.find( + (file) => file.filename === 'storefront.out.d.ts', + )!.content; + + const generatedCustomerAccountCode = result.find( + (file) => file.filename === 'customer-account.out.d.ts', + )!.content; + + expect(generatedStorefrontCode).toMatchInlineSnapshot(` + "/* eslint-disable eslint-comments/disable-enable-pair */ + /* eslint-disable eslint-comments/no-unlimited-disable */ + /* eslint-disable */ + import * as StorefrontAPI from '@shopify/hydrogen/storefront-api-types'; + + export type ShopIdQueryVariables = StorefrontAPI.Exact<{ [key: string]: never; }>; + + + export type ShopIdQuery = { shop: Pick }; + + export type ShopNameQueryVariables = StorefrontAPI.Exact<{ [key: string]: never; }>; + + + export type ShopNameQuery = { shop: Pick }; + + interface GeneratedQueryTypes { + "#graphql\\n query ShopId {\\n shop {\\n id\\n }\\n }\\n": {return: ShopIdQuery, variables: ShopIdQueryVariables}, + "\\n query ShopName {\\n shop {\\n name\\n }\\n }\\n": {return: ShopNameQuery, variables: ShopNameQueryVariables}, + } + + interface GeneratedMutationTypes { + } + + declare module '@shopify/hydrogen' { + interface StorefrontQueries extends GeneratedQueryTypes {} + interface StorefrontMutations extends GeneratedMutationTypes {} + } + " + `); + + expect(generatedCustomerAccountCode).toMatchInlineSnapshot(` + "/* eslint-disable eslint-comments/disable-enable-pair */ + /* eslint-disable eslint-comments/no-unlimited-disable */ + /* eslint-disable */ + import * as CustomerAccountAPI from '@shopify/hydrogen/customer-account-api-types'; + + export type CustomerNameQueryVariables = CustomerAccountAPI.Exact<{ [key: string]: never; }>; + + + export type CustomerNameQuery = { customer: Pick }; + + interface GeneratedQueryTypes { + "#graphql:customer\\n query CustomerName {\\n customer {\\n firstName\\n }\\n }\\n": {return: CustomerNameQuery, variables: CustomerNameQueryVariables}, + } + + interface GeneratedMutationTypes { + } + + declare module '@shopify/hydrogen' { + interface CustomerAccountQueries extends GeneratedQueryTypes {} + interface CustomerAccountMutations extends GeneratedMutationTypes {} + } + " + `); + }); }); diff --git a/packages/hydrogen-codegen/tests/fixtures/mixed-operations.ts b/packages/hydrogen-codegen/tests/fixtures/mixed-operations.ts new file mode 100644 index 0000000000..58ba33738e --- /dev/null +++ b/packages/hydrogen-codegen/tests/fixtures/mixed-operations.ts @@ -0,0 +1,23 @@ +export const A = `#graphql + query ShopId { + shop { + id + } + } +`; + +export const B = /* GraphQL */ ` + query ShopName { + shop { + name + } + } +`; + +export const C = `#graphql:customer + query CustomerName { + customer { + firstName + } + } +`; diff --git a/templates/skeleton/.graphqlrc.yml b/templates/skeleton/.graphqlrc.yml index f1509f16cf..e4ba3f9e9b 100644 --- a/templates/skeleton/.graphqlrc.yml +++ b/templates/skeleton/.graphqlrc.yml @@ -2,6 +2,16 @@ projects: default: schema: 'node_modules/@shopify/hydrogen/storefront.schema.json' documents: - - '!*.d.ts' - - '*.{ts,tsx,js,jsx}' - - 'app/**/*.{ts,tsx,js,jsx}' \ No newline at end of file + - '!*.d.ts' + - '*.{ts,tsx,js,jsx}' + - 'app/**/*.{ts,tsx,js,jsx}' + customer-account: + schema: 'node_modules/@shopify/hydrogen/customer-account.schema.json' + documents: + - '!*.d.ts' + - '*.{ts,tsx,js,jsx}' + - 'app/**/*.{ts,tsx,js,jsx}' + extensions: + languageService: + gqlTagOptions: + annotationSuffix: customer