import commander from 'commander' ; import { ICommandSettingAlone , ICommandSettings , IActionArgs } from './interface' ; import get from 'lodash.get' ; import merge from 'lodash.merge' ; import { log as leoLog , loadPkg } from '@jdfed/leo-utils' ; import { getUnexpectedOptions , rebuildCommanderArgs } from './utils' ; const log = leoLog . scope ( 'cli' ) ; type IActionHandler = ( commandName : string , args : IActionArgs , extendsCommandAction ? : ( params : { args : IActionArgs ; subCommandName : string ; [ key ] : any ; } ) => void ) => void | Promise < void > ; const commandFactory = ( params ) => { const { program , name , setting , actionCB , actionName } = params ; const c = program . command ( name ) ; if ( setting . arguments ) { c . arguments ( setting . arguments ) ; } c . description ( setting . cmdDesc , setting . argumentsDesc || { } ) ; if ( setting . allowUnknownOption !== false ) { c . allowUnknownOption ( ) ; } setting . options && setting . options . forEach ( ( option ) => { c . option ( ... option ) ; } ) ; if ( setting . helpTexts ) { setting . helpTexts . forEach ( ( helpInfo ) => { c . addHelpText ( helpInfo . position , helpInfo . text ) ; } ) ; } if ( setting . subCommands ) { Object . keys ( setting . subCommands ) . forEach ( ( subCommandName ) => { commandFactory ( { program : c , name : subCommandName , setting : setting . subCommands [ subCommandName ] , actionCB , actionName : `${ name }.${ subCommandName }` , } ) ; } ) ; return ; } c . action ( actionCB ( actionName || name ) ) ; } ; class CLI { commandSettings : ICommandSettings ; program : commander . Command ; leoConfig : { [ key ] : any ; } ; leoRC : { [ key ] : any ; } ; actionHandler : IActionHandler ; hooks : { beforeEveryAction : ( cli : CLI , commandName : string , args : any [ ] ) => void | Promise < void > ; shouldActionContinue : ( cli : CLI , commandName : string , args : any [ ] ) => boolean | Promise < boolean > ; afterEveryAction : ( cli : CLI , commandName : string , args : any [ ] ) => void ; } = { beforeEveryAction : null , shouldActionContinue : null , afterEveryAction : null , } ; version : string ; helpTexts : Array < { position : 'beforeAll' | 'afterAll' ; text : string ; } > ; constructor ( params ) { this . commandSettings = params . commands ; this . program = new commander . Command ( ) ; this . actionHandler = params . actionHandler ; this . version = params . version ; this . helpTexts = params . helpTexts || [ ] ; this . leoConfig = params . leoConfig ; this . leoRC = params . leoRC ; this . hooks = merge ( { } , this . hooks , get ( this . leoRC , 'cli.hooks' , { } ) ) ; } async start ( ) { this . program . version ( this . version ) ; await this . createCLI ( ) ; } async createCLI ( ) { const extendsCommands = await this . getExtendsCommands ( ) ; this . commandSettings = merge ( { } , extendsCommands , this . commandSettings ) ; Object . keys ( this . commandSettings ) . forEach ( ( name ) => { commandFactory ( { program : this . program , name , setting : this . commandSettings [ name ] , actionCB : ( commandName ) => async ( ... args ) => { this . hooks . beforeEveryAction && ( await this . hooks . beforeEveryAction ( this , commandName , args ) ) ; if ( this . hooks . shouldActionContinue && ( await this . hooks . shouldActionContinue ( this , commandName , args ) ) === false ) { return ; } const rebuildedArgs = rebuildCommanderArgs ( args ) ; await this . actionHandler ( commandName , { ... rebuildedArgs , unexpectedOptions : getUnexpectedOptions ( rebuildedArgs . command . options ) , } , get ( this . commandSettings [ name ] , 'action' ) ) ; this . hooks . afterEveryAction && this . hooks . afterEveryAction ( this , commandName , args ) ; } , } ) ; } ) ; this . helpTexts . forEach ( ( helpInfo ) => { this . program . addHelpText ( helpInfo . position , helpInfo . text ) ; } ) ; this . program . parse ( process . argv ) ; } async loadExtendsCommands ( pkgName , version ) { return new Promise ( ( resolve ) => { loadPkg ( pkgName , { version , private : true , } ) . then ( ( pkg ) => { resolve ( pkg ) ; } ) . catch ( ( e ) => { log . error ( e . message ) ; log . error ( `无法使用 ${ pkgName }@${ version } 提供的扩展命令，请联系开发者解决` ) ; resolve ( null ) ; } ) ; } ) ; } async getExtendsCommands ( ) { const extendsCommands = ( await Promise . all ( Object . entries ( { ... get ( this . leoConfig , 'cmds' , { } ) , ... get ( this . leoRC , 'cmds' , { } ) , } ) . map ( ( commandInfo ) => { const [ pkgName , version ] = commandInfo ; return this . loadExtendsCommands ( pkgName , version as string ) ; } ) ) ) . filter ( ( v ) => v ) ; return extendsCommands . reduce ( ( commandStore , extend ) => { commandStore [ extend . name ] = extend ; delete commandStore [ extend . name ] . name ; return commandStore ; } , { } ) ; } } export default CLI ;