import type { ComponentOptions , Operation } from './options' ; import Case from 'case' ; import { promisify } from 'util' ; import { fileURLToPath } from 'url' ; import { dirname , join , relative } from 'path' ; import fs from 'fs' ; import mkdirp from 'mkdirp' ; import { codegen } from './codegen.js' ; import { processTemplate , readFile , writeFile } from './files.js' ; import Chalk from 'chalk' ; import inquirer from 'inquirer' ; const { green , greenBright } = Chalk ; const __dirname = dirname ( fileURLToPath ( import . meta . url ) ) ; type InterpolationKeys = 'BASE_CLASS' | 'CONTROLLER_CLASS' | 'CLASS_NAME' | 'CSS_ARRAY' | 'CSS_IMPORT' | 'CSS_IMPORT' | 'OPERATION' | 'OPERATION_FILE_NAME' | 'OPERATION_NAME' | 'OPERATION_TYPE' | 'OPERATION_VARIABLES' | 'SCHEMA_PATH' | 'TAG_NAME' | 'UNPREFIXED' ; type Interpolations = Record < InterpolationKeys , string > ; enum FileKey { component = 'component' , style = 'style' , operation = 'operation' , index = 'index' } const stat = promisify ( fs . stat ) ; async function exists ( path ) { try { return ! ! ( await stat ( path ) ) ; } catch { return false ; } } const TEMPLATE_BASENAMES = { component : '_component.ts' , style : '_style.css' , operation : '_operation.graphql' , index : 'index.ts' , } ; const DEFAULT_QUERY_FIELDS = `  # replace with your fields
  __schema {
    types {
      name
      kind
    }
  }` ; const DEFAULT_SUBSCRIPTION_FIELDS = DEFAULT_QUERY_FIELDS ; function memoize < T extends ( ... args : any [ ] ) => unknown > ( fn ) { if ( ! fn ) throw new Error ( 'Trying to memoize non-function' ) ; const cache = new Map < unknown , unknown > ( ) ; const memoized = ( function ( ... args ) { const [ key ] = args ; if ( ! cache . has ( key ) ) cache . set ( key , fn ( ... args ) ) ; return cache . get ( key ) ; } ) as T ; if ( fn . name ) Object . defineProperty ( memoized , 'name' , { value : fn . name , writable : false } ) ; return memoized ; } const getBaseClass = memoize ( ( options ) : string => `Apollo${ Case . capital ( options . type ) }` ) ; const getControllerClass = memoize ( ( options ) : string => `Apollo${ Case . capital ( options . type ) }Controller` ) ; const getClassName = memoize ( ( options ) : string => `${ getOperation ( options ) }Element` ) ; const getCssArray = memoize ( ( options ) : string => `[${ getCSSImport ( options ) ? 'shared, ' : '' }style]` ) ; export const getUnprefixedTagName = memoize ( ( options ) : string => options . name . split ( '-' ) . slice ( 1 ) . join ( '-' ) ) ; const getOperation = memoize ( ( options ) : string => options . operationName ?? Case . pascal ( getUnprefixedTagName ( options ) ) ) ; const getOperationName = memoize ( ( options ) : string => getDefaultOperationNames ( getOperation ( options ) ) [ options . type ] ) ; const getOperationVariables = memoize ( function getOperationVariables ( options ) : string { if ( options . variables ) return `(${ options . variables })` ; else return getDefaultVariables ( getOperation ( options ) ) [ options . type ] ?? '' ; } ) ; const getOperationArgs = memoize ( function getOperationArgs ( params ) { const variables = params . match ( /\$(\w+)/g ) ?? [ ] ; const args = variables . map ( x => `${ x . replace ( '$' , '' ) }: ${ x }` ) . join ( ', ' ) ; return args && `(${ args })` ; } ) ; const getComponentPathFromDirectoryOption = memoize ( ( options ) : string => join ( 'src' , 'components' , options . subdir ?? '' , getUnprefixedTagName ( options ) ) ) ; const getComponentAbsPath = memoize ( ( options ) : string => join ( options . directory , getComponentPathFromDirectoryOption ( options ) ) ) ; const getSchemaPath = memoize ( ( options ) : string => options . schemaPath ?? `./${ options . operationName }.${ options . type }.graphql` ) ; const getCSSImport = memoize ( function getCSSImport ( options ) : string { if ( options . sharedCssPath === '' ) return '' ; else { return ( `\nimport shared from '${ ( options . sharedCssPath ?? relative ( getComponentAbsPath ( options ) , join ( options . directory , 'src' , 'components' , 'shared.css' ) ) ) }';` ) ; } } ) ; const getDefaultOperationNames = memoize ( ( operationName ) : Record < Operation , string > => ( { mutation : `${ operationName }` , query : operationName , subscription : operationName , } ) ) ; const getDefaultVariables = memoize ( ( operationName ) : Partial < Record < Operation , string > > => ( { mutation : `($input: ${ operationName }Input!)` , } ) ) ; const getFieldArgs = memoize ( function getFieldArgs ( options ) : string { if ( options . fields ) return '' ; else { const OPERATION_VARIABLES = getOperationVariables ( options ) ; return ( ! OPERATION_VARIABLES ? '' : getOperationArgs ( OPERATION_VARIABLES ) ) ; } } ) ; const getFields = memoize ( ( options ) : string => ( { subscription : DEFAULT_SUBSCRIPTION_FIELDS , query : DEFAULT_QUERY_FIELDS , mutation : `${ Case . camel ( getOperation ( options ) ) }${ getFieldArgs ( options ) } {\n\t\tid\n\t}` , } ) [ options . type ] ) ; const getOperationFields = memoize ( ( options ) : string => options . fields ?? getFields ( options ) ) ; const getOperationString = memoize ( ( options ) : string => `${ options . type } ${ options . operationName }${ getOperationVariables ( options ) } {
  ${ getOperationFields ( options ) }
}` ) ; export const getOperationFileName = memoize ( ( options ) : string => `${ getOperation ( options ) }.${ options . type }.graphql` ) ; const getInterpolations = memoize ( ( options ) : Interpolations => ( { BASE_CLASS : getBaseClass ( options ) , CONTROLLER_CLASS : getControllerClass ( options ) , CLASS_NAME : getClassName ( options ) , CSS_ARRAY : getCssArray ( options ) , CSS_IMPORT : getCSSImport ( options ) , OPERATION : options . operation ?? getOperationString ( options ) , OPERATION_FILE_NAME : getOperationFileName ( options ) , OPERATION_NAME : getOperationName ( options ) , OPERATION_TYPE : options . type , OPERATION_VARIABLES : getOperationVariables ( options ) , SCHEMA_PATH : getSchemaPath ( options ) , TAG_NAME : options . name , UNPREFIXED : getUnprefixedTagName ( options ) , } ) ) ; const getFileBasenames = memoize ( ( options ) : Record < FileKey , string > => ( { component : `${ getUnprefixedTagName ( options ) }.ts` , style : `${ getUnprefixedTagName ( options ) }.css` , operation : getInterpolations ( options ) . OPERATION_FILE_NAME , index : 'index.ts' , } ) ) ; function getFileBasename ( key , options ) { return getFileBasenames ( options ) [ key ] ; } function getFilePath ( key , options ) { return join ( getComponentAbsPath ( options ) , getFileBasename ( key , options ) ) ; } async function shouldWriteToDir ( options ) { if ( options . overwrite || ! await exists ( getComponentAbsPath ( options ) ) ) return true ; else { return await inquirer . prompt ( [ { type : 'confirm' , name : 'overwrite' , default : false , message : `Directory ${ getComponentPathFromDirectoryOption ( options ) } exists. Overwrite?` , } ] ) . then ( ( { overwrite = false } ) => overwrite ) ; } } async function getTemplate ( key ) { const TEMPLATE_DIR = join ( __dirname , 'template' , 'component' ) ; return await readFile ( join ( TEMPLATE_DIR , TEMPLATE_BASENAMES [ key ] ) , 'utf8' ) ; } async function writeComponentFile ( key , options ) { const PATH = getFilePath ( key , options ) ; const OUTPUT = processTemplate ( await getTemplate ( key ) , getInterpolations ( options ) ) ; await writeFile ( PATH , OUTPUT , 'utf-8' ) ; if ( ! options . silent ) console . log ( `\tWrote ${ green ( relative ( options . directory , PATH ) ) }` ) ; } async function writeComponent ( options ) { if ( ! await shouldWriteToDir ( options ) ) return ; if ( ! options . silent ) console . log ( `\nCreating ${ green ( options . name ) } in ${ getComponentPathFromDirectoryOption ( options ) }\n` ) ; await mkdirp ( getComponentAbsPath ( options ) ) ; for ( const key of Object . keys ( FileKey ) as FileKey [ ] ) await writeComponentFile ( key , options ) ; if ( ! options . silent ) console . log ( `\n${ greenBright ( 'Done!' ) }` ) ; } /**
 * Generate an Apollo Element
 */ export async function component ( options ) { if ( ! options ) return ; try { fs . statSync ( join ( options . directory , 'package.json' ) ) ; } catch ( error ) { return console . log ( '‼️ No package.json found.' , '👉 Scaffold an app first' ) ; } await writeComponent ( options ) ; await codegen ( options ) ; }