import Command , { flags } from '@oclif/command' ; import { OutputArgs , OutputFlags } from '@oclif/parser' ; import { APIClient } from './api/APIClient' ; import { config } from './utils/config' ; import { formatBrokerage , formatDate , formatLiveAlgorithmStatus , formatProjectName } from './utils/format' ; import { logger } from './utils/logger' ; import { terminateProgressBar } from './utils/promises' ; type Flag < T > = { [ key ] : flags . IOptionFlag < T > ; } ; export abstract class BaseCommand extends Command { public static flags = { help : flags . help ( { char : 'h' , description : 'display usage information' , } ) , version : flags . version ( { char : 'v' , description : 'display version information' , } ) , verbose : flags . boolean ( { description : 'display API requests as they happen' , default : false , } ) , } ; protected args : OutputArgs < any > ; protected flags : OutputFlags < any > ; protected api : APIClient ; protected abstract execute ( ) ; protected async init ( ) { await super . init ( ) ; const data = this . parse ( this . constructor as any ) ; this . args = data . args ; this . flags = data . flags ; } public async run ( ) { if ( this . flags . verbose ) { logger . enableVerboseMessages ( ) ; } process . on ( 'SIGINT' , async ( ) => { try { terminateProgressBar ( ) ; await this . onInterrupt ( ) ; } catch ( err ) { if ( err . code !== 'EEXIT' ) { logger . error ( err . message ) ; } process . exit ( 2 ) ; } } ) ; if ( this . constructor . name !== 'InitCommand' ) { if ( config . fileExists ( ) ) { this . api = new APIClient ( config . get ( 'userId' ) , config . get ( 'apiToken' ) ) ; } else { logger . error ( "You're not in a QuantConnect CLI project, you can create one by running 'qcli init'" ) ; process . exit ( 1 ) ; } } try { await this . execute ( ) ; } catch ( err ) { if ( err . code !== 'EEXIT' ) { logger . error ( err . message ) ; } process . exit ( 1 ) ; } } protected async onInterrupt ( ) { process . exit ( 2 ) ; } protected static createProjectFlag ( ) { return { project : flags . string ( { char : 'p' , description : 'project id or name (optional, interactive selector opens if not specified)' , } ) , } ; } protected static createBacktestFlag ( ) { return { backtest : flags . string ( { char : 'b' , description : 'backtest id or name (optional, interactive selector opens if not specified)' , } ) , } ; } protected static createOrganizationFlag ( ) { return { organization : flags . string ( { char : 'o' , description : 'organization id (optional, interactive selector opens if not specified)' , } ) , } ; } protected static createNodeFlag ( ) { return { node : flags . string ( { char : 'n' , description : 'node id or name (optional, interactive selector opens if not specified)' , } ) , } ; } protected static createDeploymentFlag ( ) { return { deployment : flags . string ( { char : 'd' , description : 'deployment id (optional, interactive selector opens if not specified)' , } ) , } ; } protected async parseProjectFlag ( ) { const projects = await this . api . projects . getAll ( ) ; return this . selectItem ( 'project' , projects , this . flags . project , ( project , input ) => project . projectId === Number ( input ) || project . name === formatProjectName ( input ) , ( a , b ) => b . modified . getTime ( ) - a . modified . getTime ( ) , project => `${ project . projectId } - ${ project . name }` ) ; } protected async parseBacktestFlag ( projectId ) { const backtests = await this . api . backtests . getAll ( projectId ) ; return this . selectItem ( 'backtest' , backtests , this . flags . backtest , ( backtest , input ) => backtest . backtestId === input || backtest . name === input , ( a , b ) => b . created . getTime ( ) - a . created . getTime ( ) , backtest => `${ backtest . backtestId } - ${ formatDate ( backtest . created ) } - ${ backtest . name }` ) ; } protected async parseOrganizationFlag ( ) { const projects = await this . api . projects . getAll ( ) ; const organizations = [ ... new Set ( projects . map ( project => project . organizationId ) ) ] ; return this . selectItem ( 'organization' , organizations , this . flags . organization , ( organization , input ) => organization === input , ( a , b ) => a . localeCompare ( b ) , organization => organization ) ; } protected async parseNodeFlag ( organizationId , type ? ) { const nodes = await this . api . nodes . getAll ( organizationId ) ; const allNodes = type !== undefined ? nodes [ type ] : nodes . backtest . concat ( nodes . research ) . concat ( nodes . live ) ; return this . selectItem ( 'node' , allNodes , this . flags . node , ( node , input ) => node . id === input || node . name === input , ( a , b ) => a . sku . localeCompare ( b . sku ) , node => { let type ; switch ( node . sku [ 0 ] ) { case 'B' : type = 'Backtest' ; break ; case 'R' : type = 'Research' ; break ; case 'L' : type = 'Live' ; break ; default : type = 'Unknown' ; break ; } return `${ type } node - ${ node . id } - ${ node . name } - ${ node . description }` ; } ) ; } protected async parseDeploymentFlag ( ) { const liveProjects = await this . api . live . getAll ( ) ; const allProjects = await this . api . projects . getAll ( ) ; return this . selectItem ( 'deployment' , liveProjects , this . flags . deployment , ( project , input ) => project . deployId === input , ( a , b ) => a . deployId . localeCompare ( b . deployId ) , liveProject => { const fullProject = allProjects . find ( project => project . projectId === liveProject . projectId ) ; const parts = [ liveProject . deployId , fullProject ? fullProject . name : `Project ${ liveProject . projectId }` , formatBrokerage ( liveProject . brokerage ) , formatLiveAlgorithmStatus ( liveProject . status ) , `launched ${ formatDate ( liveProject . launched ) }` , ] ; if ( liveProject . stopped ) { parts . push ( `stopped ${ formatDate ( liveProject . stopped ) }` ) ; } return parts . join ( ' - ' ) ; } ) ; } private async selectItem < T > ( itemName , allItems , input , inputChecker , itemSorter , itemLabeler ) { if ( input !== undefined ) { const matchingItems = allItems . filter ( item => inputChecker ( item , input ) ) ; if ( matchingItems . length === 0 ) { throw new Error ( `No ${ itemName } with the given id or name exists` ) ; } if ( matchingItems . length > 1 ) { throw new Error ( `There are multiple ${ itemName }s with the given name, please specify an id` ) ; } return matchingItems [ 0 ] ; } if ( allItems . length === 0 ) { throw new Error ( `No ${ itemName }s found to select from` ) ; } const options = allItems . sort ( ( a , b ) => itemSorter ( a , b ) ) . map ( item => [ item , itemLabeler ( item ) ] ) ; const aStr = 'aeiou' . split ( '' ) . includes ( itemName [ 0 ] ) ? 'an' : 'a' ; return logger . askAutocomplete ( `Select ${ aStr } ${ itemName }` , options ) ; } protected static formatExamples ( examples ) { return examples . map ( ( example , index ) => { const suffix = index !== examples . length - 1 ? '\n' : '' ; return example . trim ( ) + suffix ; } ) ; } }