import { CustomSchemaType , SchemaType , ThemeStyleType , } from '@eva-design/dss' ; import { SchemaProcessor } from '@eva-design/processor' ; import Crypto from 'crypto' ; import Fs from 'fs' ; import LodashMerge from 'lodash.merge' ; import EvaConfigService , { EvaConfig } from './eva-config.service' ; import LogService from './log.service' ; import ProjectService from './project.service' ; const DEFAULT_CHECKSUM = 'default' ; const CACHE_FILE_NAME = 'generated.json' ; const CACHE_EXPORT_SIGNATURE = `\n\nexports.styles = require('./${ CACHE_FILE_NAME }').styles` ; const RELATIVE_PATHS = { evaPackage : ( evaPackage ) : string => { return `node_modules/${ evaPackage }` ; } , evaMapping : ( evaPackage ) : string => { return `node_modules/${ evaPackage }/mapping.json` ; } , evaIndex : ( evaPackage ) : string => { return `node_modules/${ evaPackage }/index.js` ; } , cache : ( evaPackage ) : string => { return `node_modules/${ evaPackage }/${ CACHE_FILE_NAME }` ; } , } ; const schemaProcessor = new SchemaProcessor ( ) ; interface EvaCache { checksum : string ; styles : ThemeStyleType ; } see EvaConfig see EvaConfigService . MAPPING_PACKAGE_NAMES see { CACHE_FILE_NAME } see { EvaCache } export default class BootstrapService { static run = ( config ) : void => { const hasAtLeastOneEvaPackage = BootstrapService . ensureEvaPackagesInstalledOrWarn ( ) ; const isValidConfig = EvaConfigService . validateConfigOrWarn ( config ) ; if ( hasAtLeastOneEvaPackage && isValidConfig ) { BootstrapService . processMappingIfNeeded ( config ) ; } } ; private static ensureEvaPackagesInstalledOrWarn = ( ) : boolean => { const numberOfInstalledEvaPackages = EvaConfigService . MAPPING_PACKAGE_NAMES . reduce ( ( acc , packageName ) : number => { const evaPackageRelativePath = RELATIVE_PATHS . evaPackage ( packageName ) ; const isEvaPackageInstalled = ProjectService . hasModule ( evaPackageRelativePath ) ; return isEvaPackageInstalled ? acc + 1 : acc ; } , 0 ) ; if ( numberOfInstalledEvaPackages === 0 ) { LogService . warn ( 'This project has no Eva packages installed.' , '' , 'Consider installing one of the following packages:' , '' , ... EvaConfigService . MAPPING_PACKAGE_NAMES ) ; return false ; } return true ; } ; private static processMappingIfNeeded = ( config ) : void => { const evaMappingPath = RELATIVE_PATHS . evaMapping ( config . evaPackage ) ; const outputCachePath = RELATIVE_PATHS . cache ( config . evaPackage ) ; const evaMapping = ProjectService . requireModule ( evaMappingPath ) ; const actualCacheString = ProjectService . requireActualModule ( outputCachePath ) ; const actualCache = JSON . parse ( actualCacheString ) ; let customMapping ; let actualChecksum = DEFAULT_CHECKSUM ; let nextChecksum = DEFAULT_CHECKSUM ; if ( actualCache && actualCache . checksum ) { actualChecksum = actualCache . checksum ; } if ( config . customMappingPath ) { const customMappingString = ProjectService . requireActualModule ( config . customMappingPath ) ; customMapping = JSON . parse ( customMappingString ) ; nextChecksum = BootstrapService . createChecksum ( customMappingString ) ; } if ( actualChecksum === DEFAULT_CHECKSUM || actualChecksum !== nextChecksum ) { const mapping = LodashMerge ( { } , evaMapping , customMapping ) ; const styles = schemaProcessor . process ( mapping ) ; const writableCache = BootstrapService . createWritableCache ( nextChecksum , styles ) ; Fs . writeFileSync ( outputCachePath , writableCache ) ; } const hasCacheExports = BootstrapService . hasCacheExports ( config ) ; if ( ! hasCacheExports ) { const evaIndexPath = RELATIVE_PATHS . evaIndex ( config . evaPackage ) ; Fs . appendFileSync ( evaIndexPath , CACHE_EXPORT_SIGNATURE ) ; LogService . success ( `Successfully bootstrapped ${ config . evaPackage }` ) ; } } ; private static hasCacheExports = ( config ) : boolean => { const evaIndexPath = RELATIVE_PATHS . evaIndex ( config . evaPackage ) ; const evaIndexString = ProjectService . requireActualModule ( evaIndexPath ) ; return evaIndexString . includes ( CACHE_EXPORT_SIGNATURE ) ; } ; private static createWritableCache = ( checksum , styles ) : string => { const cache = { checksum , styles , } ; return JSON . stringify ( cache , null , 2 ) ; } ; private static createChecksum = ( target ) : string => { return Crypto . createHash ( 'sha1' ) . update ( target ) . digest ( 'hex' ) ; } ; }