import { Project , SourceFile , SyntaxKind , NodeFlags , ObjectLiteralExpression , Node , ArrayLiteralExpression , } from 'ts-morph' ; import Ajv from 'ajv' ; import { FromSchema , JSONSchema } from 'json-schema-to-ts' ; const ajv = new Ajv ( ) ; export const BaseFunctionConfigSchema = { type : 'object' , properties : { use : { type : 'string' } , memory : { type : 'number' } , maxDuration : { type : 'number' } , regions : { type : 'array' , items : { type : 'string' } , } , } , } as const ; export type BaseFunctionConfig = FromSchema < typeof BaseFunctionConfigSchema > ; export function getConfig < T extends JSONSchema = typeof BaseFunctionConfigSchema > ( project , sourcePath , schema ? ) { const sourceFile = project . addSourceFileAtPath ( sourcePath ) ; const configNode = getConfigNode ( sourceFile ) ; if ( ! configNode ) return null ; const config = getValue ( configNode ) ; return validate ( schema || BaseFunctionConfigSchema , config ) ; } function validate < T > ( schema , data ) { const isValid = ajv . compile ( schema ) ; if ( ! isValid ( data ) ) { throw new Error ( 'Invalid data' ) ; } return data as FromSchema < T > ; } function getConfigNode ( sourceFile ) { return sourceFile . getDescendantsOfKind ( SyntaxKind . ObjectLiteralExpression ) . find ( objectLiteral => { const varDec = objectLiteral . getParentIfKind ( SyntaxKind . VariableDeclaration ) ; if ( varDec ?. getName ( ) !== 'config' ) return false ; const varDecList = varDec . getParentIfKind ( SyntaxKind . VariableDeclarationList ) ; const isConst = ( varDecList ?. getFlags ( ) ?? 0 ) & NodeFlags . Const ; if ( ! isConst ) return false ; const exp = varDecList ?. getParentIfKind ( SyntaxKind . VariableStatement ) ; if ( ! exp ?. isExported ( ) ) return false ; return true ; } ) ; } function getValue ( valueNode ) { if ( Node . isStringLiteral ( valueNode ) ) { return eval ( valueNode . getText ( ) ) ; } else if ( Node . isNumericLiteral ( valueNode ) ) { return Number ( valueNode . getText ( ) ) ; } else if ( Node . isTrueLiteral ( valueNode ) ) { return true ; } else if ( Node . isFalseLiteral ( valueNode ) ) { return false ; } else if ( Node . isNullLiteral ( valueNode ) ) { return null ; } else if ( Node . isArrayLiteralExpression ( valueNode ) ) { return getArray ( valueNode ) ; } else if ( Node . isObjectLiteralExpression ( valueNode ) ) { return getObject ( valueNode ) ; } else if ( Node . isIdentifier ( valueNode ) && valueNode . getText ( ) === 'undefined' ) { return undefined ; } throw new Error ( `Unhandled type: "${ valueNode . getKindName ( ) }" ${ valueNode . getText ( ) }` ) ; } function getObject ( obj ) { const rtn = { } ; for ( const prop of obj . getProperties ( ) ) { if ( ! Node . isPropertyAssignment ( prop ) ) continue ; const [ nameNode , _colon , valueNode ] = prop . getChildren ( ) ; const name = nameNode . getText ( ) ; rtn [ name ] = getValue ( valueNode ) ; } return rtn ; } function getArray ( arr ) { const elementNodes = arr . getElements ( ) ; const rtn = new Array ( elementNodes . length ) ; for ( let i = 0 ; i < elementNodes . length ; i ++ ) { rtn [ i ] = getValue ( elementNodes [ i ] ) ; } return rtn ; }