import { DetailedError , Types , isComplexPluginOutput , federationSpec , getCachedDocumentNodeFromSchema , AddToSchemaResult , } from '@graphql-codegen/plugin-helpers' ; import { visit , DefinitionNode , Kind , print , NameNode , specifiedRules , DocumentNode } from 'graphql' ; import { executePlugin } from './execute-plugin' ; import { checkValidationErrors , validateGraphQlDocuments , Source , asArray } from '@graphql-tools/utils' ; import { mergeSchemas } from '@graphql-tools/schema' ; import { getSkipDocumentsValidationOption , hasFederationSpec , pickFlag , prioritize , shouldValidateDocumentsAgainstSchema , shouldValidateDuplicateDocuments , } from './utils' ; export async function codegen ( options ) { const documents = options . documents || [ ] ; const skipDocumentsValidation = getSkipDocumentsValidationOption ( options ) ; if ( documents . length > 0 && shouldValidateDuplicateDocuments ( skipDocumentsValidation ) ) { validateDuplicateDocuments ( documents ) ; } const pluginPackages = Object . keys ( options . pluginMap ) . map ( key => options . pluginMap [ key ] ) ; const additionalTypeDefs = [ ] ; for ( const plugin of pluginPackages ) { const addToSchema = typeof plugin . addToSchema === 'function' ? plugin . addToSchema ( options . config ) : plugin . addToSchema ; if ( addToSchema ) { additionalTypeDefs . push ( addToSchema ) ; } } const federationInConfig = pickFlag ( 'federation' , options . config ) ; const isFederation = prioritize ( federationInConfig , false ) ; if ( isFederation && ! hasFederationSpec ( options . schemaAst || options . schema ) ) { additionalTypeDefs . push ( federationSpec ) ; } const mergeNeeded = ! options . schemaAst || additionalTypeDefs . length > 0 ; const schemaInstance = mergeNeeded ? mergeSchemas ( { schemas : options . schemaAst ? [ options . schemaAst ] : [ ] , typeDefs : options . schemaAst ? additionalTypeDefs : [ options . schema , ... additionalTypeDefs ] , convertExtensions : true , assumeValid : true , assumeValidSDL : true , ... options . config , } as any ) : options . schemaAst ; const schemaDocumentNode = mergeNeeded || ! options . schema ? getCachedDocumentNodeFromSchema ( schemaInstance ) : options . schema ; if ( schemaInstance && documents . length > 0 && shouldValidateDocumentsAgainstSchema ( skipDocumentsValidation ) ) { const ignored = [ 'NoUnusedFragments' , 'NoUnusedVariables' , 'KnownDirectives' ] ; if ( typeof skipDocumentsValidation === 'object' && skipDocumentsValidation . ignoreRules ) { ignored . push ( ... asArray ( skipDocumentsValidation . ignoreRules ) ) ; } const extraFragments = pickFlag ( 'externalFragments' , options . config ) || [ ] ; const errors = await validateGraphQlDocuments ( schemaInstance , [ ... documents , ... extraFragments . map ( f => ( { location : f . importFrom , document : { kind : Kind . DOCUMENT , definitions : [ f . node ] } as DocumentNode , } ) ) , ] , specifiedRules . filter ( rule => ! ignored . some ( ignoredRule => rule . name . startsWith ( ignoredRule ) ) ) ) ; checkValidationErrors ( errors ) ; } const prepend = new Set < string > ( ) ; const append = new Set < string > ( ) ; const output = await Promise . all ( options . plugins . map ( async ( plugin ) => { const name = Object . keys ( plugin ) [ 0 ] ; const pluginPackage = options . pluginMap [ name ] ; const pluginConfig = plugin [ name ] || { } ; const execConfig = typeof pluginConfig !== 'object' ? pluginConfig : { ... options . config , ... pluginConfig , } ; const result = await executePlugin ( { name , config : execConfig , parentConfig : options . config , schema : schemaDocumentNode , schemaAst : schemaInstance , documents : options . documents , outputFilename : options . filename , allPlugins : options . plugins , skipDocumentsValidation : options . skipDocumentsValidation , pluginContext : options . pluginContext , } , pluginPackage ) ; if ( typeof result === 'string' ) { return result || '' ; } else if ( isComplexPluginOutput ( result ) ) { if ( result . append && result . append . length > 0 ) { for ( const item of result . append ) { if ( item ) { append . add ( item ) ; } } } if ( result . prepend && result . prepend . length > 0 ) { for ( const item of result . prepend ) { if ( item ) { prepend . add ( item ) ; } } } return result . content || '' ; } return '' ; } ) ) ; return [ ... sortPrependValues ( Array . from ( prepend . values ( ) ) ) , ... output , ... Array . from ( append . values ( ) ) ] . filter ( Boolean ) . join ( '\n' ) ; } function resolveCompareValue ( a ) { if ( a . startsWith ( '/*' ) || a . startsWith ( '//' ) || a . startsWith ( ' *' ) || a . startsWith ( ' */' ) || a . startsWith ( '*/' ) ) { return 0 ; } else if ( a . startsWith ( 'package' ) ) { return 1 ; } else if ( a . startsWith ( 'import' ) ) { return 2 ; } else { return 3 ; } } export function sortPrependValues ( values ) { return values . sort ( ( a , b ) => { const aV = resolveCompareValue ( a ) ; const bV = resolveCompareValue ( b ) ; if ( aV < bV ) { return - 1 ; } if ( aV > bV ) { return 1 ; } return 0 ; } ) ; } function validateDuplicateDocuments ( files ) { const definitionMap = { } ; function addDefinition ( file , node , deduplicatedDefinitions ) { if ( typeof node . name !== 'undefined' ) { if ( ! definitionMap [ node . kind ] ) { definitionMap [ node . kind ] = { } ; } if ( ! definitionMap [ node . kind ] [ node . name . value ] ) { definitionMap [ node . kind ] [ node . name . value ] = { paths : new Set ( ) , contents : new Set ( ) , } ; } const definitionKindMap = definitionMap [ node . kind ] ; const length = definitionKindMap [ node . name . value ] . contents . size ; definitionKindMap [ node . name . value ] . paths . add ( file . location ) ; definitionKindMap [ node . name . value ] . contents . add ( print ( node ) ) ; if ( length === definitionKindMap [ node . name . value ] . contents . size ) { return null ; } } return deduplicatedDefinitions . add ( node ) ; } files . forEach ( file => { const deduplicatedDefinitions = new Set < DefinitionNode > ( ) ; visit ( file . document , { OperationDefinition ( node ) { addDefinition ( file , node , deduplicatedDefinitions ) ; } , FragmentDefinition ( node ) { addDefinition ( file , node , deduplicatedDefinitions ) ; } , } ) ; ( file . document as any ) . definitions = Array . from ( deduplicatedDefinitions ) ; } ) ; const kinds = Object . keys ( definitionMap ) ; kinds . forEach ( kind => { const definitionKindMap = definitionMap [ kind ] ; const names = Object . keys ( definitionKindMap ) ; if ( names . length ) { const duplicated = names . filter ( name => definitionKindMap [ name ] . contents . size > 1 ) ; if ( ! duplicated . length ) { return ; } const list = duplicated . map ( name => `
        * ${ name } found in:
          ${ [ ... definitionKindMap [ name ] . paths ] . map ( filepath => { return `
              - ${ filepath }
            ` . trimRight ( ) ; } ) . join ( '' ) }
    ` . trimRight ( ) ) . join ( '' ) ; const definitionKindName = kind . replace ( 'Definition' , '' ) . toLowerCase ( ) ; throw new DetailedError ( `Not all ${ definitionKindName }s have an unique name: ${ duplicated . join ( ', ' ) }` , `
          Not all ${ definitionKindName }s have an unique name
          ${ list }
        ` ) ; } } ) ; }