import { getLeoRC , getCorePackageJSON , getProjectPackageJSON , runInstall , extendsProcessEnv , getRemotePkgTagVersion , } from './utils' ; import { IRC } from './defaultRC' ; import fs from 'fs-extra' ; import semver from 'semver' ; import set from 'lodash.set' ; import unset from 'lodash.unset' ; import merge from 'lodash.merge' ; import simpleGit from 'simple-git' ; import inquirer from 'inquirer' ; import axios from 'axios' ; import { loadPkg , log as leoLog } from '@jdfed/leo-utils' ; import { getDefaultConfig , IConfig , IActionArgs , localStaticConfig , configStore } from './config' ; import { IBuilder , IVirtualPath , ICommonParams , ILeoCaller } from './interface' ; export interface IHooks { beforeStart ? : ( this : Core ) => any ; afterCommandExecute ? : ( this : Core ) => any ; } const log = leoLog . scope ( 'core' ) ; class Core { leoRC : IRC ; leoConfig : IConfig ; cli : ILeoCaller = null ; generator : ILeoCaller = null ; builder : IBuilder = null ; rootName : string ; hooks : IHooks = { } ; virtualPath : IVirtualPath ; constructor ( { config , hooks = { } } = { } ) { this . hooks = hooks ; this . leoConfig = configStore . init ( getDefaultConfig ( config ) ) . getConfig ( ) as IConfig ; extendsProcessEnv ( this . leoConfig ) ; this . initLocalConfig ( ) ; } async start ( ) { try { this ?. hooks ?. beforeStart ?. call ( this ) ; this . leoRC = getLeoRC ( this . leoConfig . rcFileName ) ; log . debug ( `${ this . leoConfig . rcFileName }` , JSON . stringify ( this . leoRC , null , 2 ) ) ; log . debug ( 'config' , JSON . stringify ( this . leoConfig , null , 2 ) ) ; await this . initCLI ( ) ; await this . cli . start ( ) ; } catch ( e ) { log . error ( this . leoConfig . isDev ? e : ( ( e as unknown ) as Error ) ?. message ) ; process . exit ( - 1 ) ; } } cliActionHandler = async ( command , args , extendsCommandAction ? ) => { log . debug ( 'cliActionHandler' , 'commandName:' , command , 'args:' , args ) ; try { const [ commandName , subCommandName ] = command . split ( '.' ) ; await this . doStartCheck ( ) ; switch ( commandName ) { case 'init' : await this . commandInitAction ( args ) ; break ; case 'dev' : await this . commandDevAction ( args ) ; break ; case 'build' : await this . commandBuildAction ( args ) ; break ; case 'test' : await this . commandTestAction ( args ) ; break ; case 'lint' : await this . commandLintAction ( args ) ; break ; case 'clear' : await this . commandClearAction ( args ) ; break ; case 'config' : await this . commandConfigAction ( subCommandName , args ) ; break ; default : await this . extendsCommandAction ( commandName , subCommandName , args , extendsCommandAction ) ; break ; } this ?. hooks ?. afterCommandExecute ?. call ( this ) ; } catch ( error ) { log . error ( this . leoConfig . isDev ? error : ( ( error as unknown ) as Error ) ?. message ) ; process . exit ( - 1 ) ; } } ; async commandInitAction ( args ) { let { arguments : [ templateName ] , } = args ; const { options , unexpectedOptions } = args ; if ( templateName ) { const projectList = await this . getProjectsLikeName ( templateName ) ; if ( projectList . length <= 0 ) { throw new Error ( `未找到对应的模板，请校对模板名是否正确(${ templateName })` ) ; } if ( ( projectList . length === 1 && projectList [ 0 ] . value !== templateName ) || projectList . length > 1 ) { log . info ( '如需关闭模板模糊查询功能，可使用 leo config set fuzzyTemp=false' ) ; const res = await this . askQuestions ( [ { name : 'templateName' , type : 'list' , message : `查询到与 ${ templateName } 相关的模板，请选择：` , choices : projectList , } , ] ) ; templateName = res . templateName ; } } if ( ! templateName ) { const res = await this . askQuestions ( this . leoConfig . questions . chooseTemplate ) ; templateName = res . templateName ; } const { useInstall , projectName } = await this . askQuestions ( this . leoConfig . questions . init ) ; log . debug ( 'commandInitAction' , templateName , options ) ; log . info ( '开始创建项目' ) ; await this . initGenerator ( templateName , { ... options , ... unexpectedOptions } , projectName ) ; if ( useInstall !== 'custom' ) { log . await ( '开始安装依赖' ) ; await runInstall ( useInstall === 'yarn' , projectName ) ; log . success ( '安装完成！' ) ; } log . info ( `初始化成功！\n 使用以下指令来开始项目:\n cd ${ projectName }\n ${ this . leoConfig . rootName } dev\n` ) ; this . leoConfig . helpTexts . forEach ( ( helpInfo ) => { log . info ( helpInfo . text ) ; } ) ; } async commandDevAction ( args ) { log . debug ( 'commandDevAction' , args ) ; const { options , unexpectedOptions } = args ; log . info ( '启动调试' ) ; await this . initBuilder ( { ... options , ... unexpectedOptions } ) ; await this . leoRC ?. builder ?. hooks ?. beforeDev ?. ( this . builder ) ; await this . builder . start ( ) ; await this . leoRC ?. builder ?. hooks ?. afterDev ?. ( this . builder ) ; } async commandBuildAction ( args ) { log . debug ( 'commandBuildAction' , args ) ; const { options , unexpectedOptions } = args ; log . info ( '开始构建' ) ; await this . initBuilder ( { ... options , ... unexpectedOptions } ) ; await this . leoRC ?. builder ?. hooks ?. beforeBuild ?. ( this . builder ) ; await this . builder . build ( ) ; await this . leoRC ?. builder ?. hooks ?. afterBuild ?. ( this . builder ) ; } async commandTestAction ( args ) { log . debug ( 'commandTestAction' , args ) ; const { options , unexpectedOptions } = args ; log . info ( '开始测试' ) ; return await this . doTestAction ( { ... options , ... unexpectedOptions } ) ; } async doTestAction ( options = { } ) { if ( this . leoRC . testAction ) { const testRes = await this . leoRC . testAction ( options ) ; if ( testRes !== true ) { throw new Error ( 'test 失败' ) ; } return testRes ; } else { log . warn ( '未找到 leorc 中 testAction 配置' ) ; } return true ; } async commandLintAction ( args ) { log . debug ( 'commandLintAction' , args ) ; const { options , unexpectedOptions } = args ; log . info ( '开始Lint' ) ; return await this . doLintAction ( { ... options , ... unexpectedOptions } ) ; } async doLintAction ( options = { } ) { if ( this . leoRC . lintAction ) { const lintRes = await this . leoRC . lintAction ( options ) ; if ( lintRes !== true ) { throw new Error ( 'lint 失败' ) ; } return lintRes ; } else { log . warn ( '未找到 leorc 中 lintAction 配置' ) ; } return true ; } async commandClearAction ( args ) { const { options } = args ; log . info ( '开始清理' ) ; if ( Object . values ( options ) . every ( ( v ) => v === false ) ) { return this . doClearAction ( ) ; } await Promise . all ( Object . entries ( options ) . filter ( ( keyValue ) => keyValue [ 1 ] === true ) . map ( ( keyValue ) => this . doClearAction ( keyValue [ 0 ] ) ) ) ; } async prePublishAction ( args ) { log . debug ( 'prePublishAction' , this . leoRC . isPrePublishParallel ) ; if ( this . leoRC . isPrePublishParallel ) { return Promise . all ( [ this . commandLintAction ( args ) , this . commandTestAction ( args ) , this . commandBuildAction ( args ) , ] ) ; } const lintResult = await this . commandLintAction ( args ) ; if ( lintResult !== true ) return ; const testResult = await this . commandTestAction ( args ) ; if ( testResult !== true ) return ; await this . commandBuildAction ( args ) ; } async doClearAction ( clearName ? ) { const { virtualPath } = this . leoConfig ; switch ( clearName ) { case 'config' : await fs . remove ( virtualPath . configPath ) ; log . success ( '清除 leoConfig 缓存成功' ) ; break ; case 'template' : await fs . remove ( virtualPath . templatePath ) ; log . success ( '清除模板缓存成功' ) ; break ; case 'node_modules' : await fs . remove ( virtualPath . nodeModulesPath ) ; log . success ( '清除 npm 包缓存成功' ) ; break ; case 'all' : default : await fs . remove ( virtualPath . entry ) ; log . success ( '清除所有缓存成功' ) ; } } async commandConfigAction ( subCommandName , args ) { const { virtualPath : { configPath } , } = this . leoConfig ; await fs . ensureFile ( configPath ) ; const localConfig = ( await fs . readJson ( configPath ) ) || { } ; const { arguments : [ key ] , } = args ; switch ( subCommandName ) { case 'set' : await this . doSetConfigAction ( args , localConfig ) ; break ; case 'get' : log . info ( localConfig [ key ] ) ; break ; case 'delete' : unset ( localConfig , key ) ; await fs . writeJson ( this . leoConfig . virtualPath . configPath , localConfig ) ; log . success ( '删除成功' ) ; break ; case 'list' : log . info ( localConfig ) ; break ; default : break ; } } async doSetConfigAction ( args , localConfig ) { const keyValueMap = args . arguments . reduce ( ( map , keyValue ) => { if ( keyValue ) { const [ key , value ] = keyValue . split ( '=' ) ; const v = value === 'true' || value === 'false' ? value === 'true' : value ; set ( map , key , v ) ; } return map ; } , { } ) ; await fs . writeJson ( this . leoConfig . virtualPath . configPath , merge ( { } , localConfig , keyValueMap ) ) ; log . success ( '设置成功' ) ; } async extendsCommandAction ( commandName , subCommandName , args , extendsCommandAction ) { await extendsCommandAction ?. ( { args , subCommandName , ... this . getCommonParams ( ) , } ) ; } askQuestions ( questions ) { return inquirer . prompt ( questions ) ; } returns { Promise } async doStartCheck ( ) { const checkList = [ this . checkNodeVersion ( ) , this . checkVersion ( ) ] ; return Promise . all ( checkList ) ; } private async initBuilder ( options = { } ) { log . debug ( 'initBuilder' , 'before builder create' ) ; const { leoRC } = this ; const Builder = ( await loadPkg ( leoRC . builder . name , { version : leoRC . builder . version , private : true , } ) ) as any ; this . builder = new Builder ( { options , ... this . getCommonParams ( ) , } ) ; } private async initCLI ( ) { log . debug ( 'initCLI' , 'before CLI create' ) ; const { leoConfig } = this ; const { version } = getCorePackageJSON ( ) ; const CLI = ( await loadPkg ( leoConfig . cli . name , leoConfig . cli . version ) ) as any ; this . cli = new CLI ( { commands : this . leoConfig . commands , actionHandler : this . cliActionHandler , version , helpTexts : this . leoConfig . helpTexts , ... this . getCommonParams ( ) , } ) ; } private async initGenerator ( templateName , options , projectName ) { log . debug ( 'initGenerator' , 'start init generator' ) ; const { leoConfig } = this ; const Generator = ( await loadPkg ( leoConfig . generator . name , leoConfig . generator . version ) ) as any ; this . generator = new Generator ( { templateName , projectName , options , cachePath : this . leoConfig . virtualPath . templatePath , ... this . getCommonParams ( ) , } ) ; await this . generator . start ( ) ; } private returns { Promise } private checkNodeVersion ( ) { const { engines } = getCorePackageJSON ( ) ; const nodeVersionSatisfy = semver . satisfies ( process . version , engines . node ) ; if ( nodeVersionSatisfy ) return Promise . resolve ( 0 ) ; return Promise . reject ( new Error ( `请使用${ engines . node }版本的 node` ) ) ; } private returns { Promise } private async checkVersion ( ) { const pkg = getCorePackageJSON ( ) ; const pkgTagVersion = await getRemotePkgTagVersion ( pkg . name ) ; const { latest } = pkgTagVersion ; if ( semver . gt ( latest , pkg . version ) && this . leoConfig . forceUpdate ) { return Promise . reject ( new Error ( '当前版本过旧，请升级后使用' ) ) ; } } private async checkPublishStatus ( ) { const git = simpleGit ( ) ; const [ { files } ] = await Promise . all ( [ git . status ( ) ] ) ; if ( files . length ) { return 'git 全部提交后才能进行发布' ; } return '' ; } description private memberof private getProjectsLikeName = async ( name ) : Promise < [ { [ key ] : any ; } ] > => { const { data : templateList } = await axios . get ( `${ this . leoConfig . gitTemplateGroupQueryURL }` , { headers : this . leoConfig . gitQueryHeader , } ) . catch ( ( ) => { return { data : [ ] } ; } ) ; return templateList . map ( ( item ) => { const templateName = item . path . replace ( /-template$/ , '' ) ; return { name : `${ templateName }(${ item . name })` , value : templateName , } ; } ) . filter ( ( item ) => { return this . leoConfig . fuzzyTemp ? item . value . indexOf ( name ) !== - 1 : item . value === name ; } ) . sort ( ( item ) => ( item . value === name ? - 1 : 0 ) ) ; } ; description private memberof private initLocalConfig = ( ) => { const { virtualPath : { configPath } , } = this . leoConfig ; if ( fs . existsSync ( configPath ) ) { return ; } fs . outputJsonSync ( configPath , localStaticConfig ) ; } ; description private private getCommonParams ( ) { return { leoConfig : this . leoConfig , leoRC : this . leoRC , leoUtils : { } , pkg : getProjectPackageJSON ( ) , } ; } } export default Core ;