import chalk from 'chalk' ; import * as fs from 'fs-extra' ; import { GraphQLConfig , GraphQLProjectConfig } from 'graphql-config' ; import { importSchema } from 'graphql-import' ; import { generateCode } from 'graphql-static-binding' ; import { get , has , merge } from 'lodash' ; import * as path from 'path' ; import { Arguments } from 'yargs' ; export class Prepare { private config : GraphQLConfig ; private bundleExtensionConfig : { 'prepare-bundle' : string ; } | undefined ; private projectName : string ; private project : GraphQLProjectConfig ; constructor ( private context , private argv ) { } public async handle ( ) { this . config = await this . context . getConfig ( ) ; const projects = this . getProjectConfig ( ) ; for ( const projectName of Object . keys ( projects ) ) { const project = projects [ projectName ] ; this . setCurrentProject ( project , projectName ) ; if ( this . argv . bundle ) { this . bundle ( ) ; } if ( this . argv . bindings ) { this . bindings ( ) ; } this . save ( ) ; } } private setCurrentProject ( project , projectName ) { this . project = project ; this . projectName = projectName ; this . bundleExtensionConfig = undefined ; } private bindings ( ) { let bindingExtensionConfig ; if ( this . argv . project || ( ! this . argv . project && ( has ( this . project . config , 'extensions.prepare-binding' ) || has ( this . project . config , 'extensions.binding' ) ) ) ) { this . context . spinner . start ( `Generating bindings for project ${ this . projectDisplayName ( ) }...` ) ; bindingExtensionConfig = this . processBindings ( this . bundleExtensionConfig ? this . bundleExtensionConfig [ 'prepare-bundle' ] : undefined ) ; merge ( this . project . extensions , bindingExtensionConfig ) ; this . context . spinner . succeed ( `Bindings for project ${ this . projectDisplayName ( ) } written to ${ chalk . green ( bindingExtensionConfig [ 'prepare-binding' ] . output ) }` ) ; } else if ( this . argv . verbose ) { this . context . spinner . info ( `Binding not configured for project ${ this . projectDisplayName ( ) }. Skipping` ) ; } } private bundle ( ) { if ( this . argv . project || ( ! this . argv . project && ( has ( this . project . config , 'extensions.prepare-bundle' ) || has ( this . project . config , 'extensions.bundle' ) ) ) ) { this . context . spinner . start ( `Processing schema imports for project ${ this . projectDisplayName ( ) }...` ) ; this . bundleExtensionConfig = this . processBundle ( ) ; merge ( this . project . extensions , this . bundleExtensionConfig ) ; this . context . spinner . succeed ( `Bundled schema for project ${ this . projectDisplayName ( ) } written to ${ chalk . green ( this . bundleExtensionConfig [ 'prepare-bundle' ] ) }` ) ; } else if ( this . argv . verbose ) { this . context . spinner . info ( `Bundling not configured for project ${ this . projectDisplayName ( ) }. Skipping` ) ; } } private save ( ) { if ( this . argv . save ) { const configFile = path . basename ( this . config . configPath ) ; this . context . spinner . start ( `Saving configuration for project ${ this . projectDisplayName ( ) } to ${ chalk . green ( configFile ) }...` ) ; this . saveConfig ( ) ; this . context . spinner . succeed ( `Configuration for project ${ this . projectDisplayName ( ) } saved to ${ chalk . green ( configFile ) }` ) ; } } private getProjectConfig ( ) { let projects ; if ( this . argv . project ) { if ( Array . isArray ( this . argv . project ) ) { projects = { } ; this . argv . project . map ( ( p ) => merge ( projects , { [ p ] : this . config . getProjectConfig ( p ) } ) ) ; } else { projects = { [ this . argv . project ] : this . config . getProjectConfig ( this . argv . project ) } ; } } else { projects = this . config . getProjects ( ) ; } if ( ! projects ) { throw new Error ( 'No projects defined in config file' ) ; } return projects ; } private processBundle ( ) { const outputPath = this . determineBundleOutputPath ( ) ; const schemaPath = this . determineSchemaPath ( ) ; const finalSchema = importSchema ( schemaPath ) ; fs . writeFileSync ( outputPath , finalSchema , { flag : 'w' } ) ; return { 'prepare-bundle' : outputPath } ; } private processBindings ( schemaPath ) { const generator = this . determineGenerator ( ) ; const extension = generator . endsWith ( 'ts' ) ? 'ts' : 'js' ; const outputPath = this . determineBindingOutputPath ( extension ) ; const schema = this . determineInputSchema ( schemaPath ) ; const schemaContents = importSchema ( schema ) ; const finalSchema = generateCode ( schemaContents , generator ) ; fs . writeFileSync ( outputPath , finalSchema , { flag : 'w' } ) ; return { 'prepare-binding' : { output : outputPath , generator : generator } } ; } private saveConfig ( ) { if ( has ( this . project . config , 'extensions.bundle' ) ) { delete this . project . config . extensions ! . bundle ; } if ( has ( this . project . config , 'extensions.binding' ) ) { delete this . project . config . extensions ! . binding ; } this . config . saveConfig ( this . project . config , this . projectName ) ; } param { ( string | undefined ) } schemaPath returns { string } private determineInputSchema ( schemaPath ) { const bundleDefined = has ( this . project . config , 'extensions.prepare-bundle.output' ) ; const oldBundleDefined = has ( this . project . config , 'extensions.bundle.output' ) ; if ( ! schemaPath ) { if ( bundleDefined ) { schemaPath = get ( this . project . config , 'extensions.prepare-bundle.output' ) ; } else if ( oldBundleDefined ) { schemaPath = get ( this . project . config , 'extensions.bundle.output' ) ; } else if ( this . project . schemaPath ) { schemaPath = this . project . schemaPath ; } else { throw new Error ( `Input schema cannot be determined.` ) ; } } if ( fs . existsSync ( schemaPath ! ) ) { return schemaPath ! ; } else { throw new Error ( `Schema '${ schemaPath ! }' not found.${ bundleDefined ? ' Did you run bundle first?' : '' }` ) ; } } returns { string } private determineSchemaPath ( ) { if ( this . project . schemaPath ) { return this . project . schemaPath ; } throw new Error ( `No schemaPath defined for project '${ this . projectName }' in config file.` ) ; } param { string } generator returns { string } private determineGenerator ( ) { if ( this . argv . generator ) { return this . argv . generator ; } if ( has ( this . project . config , 'extensions.binding.generator' ) ) { if ( ! this . argv . save ) { this . context . spinner . warn ( `Deprecated extension key 'binding.generator' found in config file. Use '--save' to update to 'prepare-binding.generator'.` ) ; } return get ( this . project . config , 'extensions.binding.generator' ) ; } if ( has ( this . project . config , 'extensions.prepare-binding.generator' ) ) { return get ( this . project . config , 'extensions.prepare-binding.generator' ) ; } throw new Error ( 'Generator cannot be determined. No existing configuration found and no generator parameter specified.' ) ; } param { string } extension returns private determineBindingOutputPath ( extension ) { let outputPath ; if ( this . argv . output ) { outputPath = path . join ( this . argv . output , `${ this . projectName }.${ extension }` ) ; } else if ( has ( this . project . config , `extensions.binding.output` ) ) { if ( ! this . argv . save ) { this . context . spinner . warn ( `Deprecated extension key 'binding.output' found in config file. Use '--save' to update to 'prepare-binding.output'.` ) ; } outputPath = get ( this . project . config , `extensions.binding.output` ) ; } else if ( has ( this . project . config , `extensions.prepare-binding.output` ) ) { outputPath = get ( this . project . config , `extensions.prepare-binding.output` ) ; } else { throw new Error ( 'Output path cannot be determined. No existing configuration found and no output parameter specified.' ) ; } fs . ensureDirSync ( path . dirname ( outputPath ) ) ; return outputPath ; } returns private determineBundleOutputPath ( ) { let outputPath ; if ( this . argv . output ) { outputPath = path . join ( this . argv . output , `${ this . projectName }.graphql` ) ; } else if ( has ( this . project . config , `extensions.bundle` ) ) { if ( ! this . argv . save ) { this . context . spinner . warn ( `Deprecated extension key 'bundle' found in config file. Use '--save' to update to 'prepare-bundle'.` ) ; } outputPath = get ( this . project . config , `extensions.bundle` ) ; } else if ( has ( this . project . config , `extensions.prepare-bundle` ) ) { outputPath = get ( this . project . config , `extensions.prepare-bundle` ) ; } else { throw new Error ( 'Output path cannot be determined. No existing configuration found and no output parameter specified.' ) ; } fs . ensureDirSync ( path . dirname ( outputPath ) ) ; return outputPath ; } private projectDisplayName = ( ) => chalk . green ( this . projectName ) ; }