@sprucelabs/spruce-cli
Version:
Command line interface for building Spruce skills.
166 lines (133 loc) • 5.17 kB
text/typescript
import { Schema, SchemaEntityFactory } from '@sprucelabs/schema'
import { CommanderStatic } from 'commander'
import SpruceError from '../errors/SpruceError'
import PkgService from '../services/PkgService'
import { GraphicsInterface } from '../types/cli.types'
import commanderUtil from '../utilities/commander.utility'
import uiUtil from '../utilities/ui.utility'
import AbstractFeature from './AbstractFeature'
import ActionExecuter from './ActionExecuter'
import featuresUtil from './feature.utilities'
import { FeatureAction, FeatureActionResponse } from './features.types'
export default class FeatureCommandAttacher {
private program: CommanderStatic['program']
private ui: GraphicsInterface
private actionExecuter: ActionExecuter
private pkg: PkgService
public constructor(options: {
program: CommanderStatic['program']
ui: GraphicsInterface
actionExecuter: ActionExecuter
pkgService: PkgService
}) {
const { program, ui: term, actionExecuter, pkgService } = options
this.program = program
this.ui = term
this.pkg = pkgService
this.actionExecuter = actionExecuter
}
public async attachFeature(feature: AbstractFeature) {
const actionCodes = await feature.getAvailableActionCodes()
for (const actionCode of actionCodes) {
this.attachCode(actionCode, feature)
}
}
private attachCode(code: string, feature: AbstractFeature) {
let commandStr = featuresUtil.generateCommand(feature.code, code)
const action = this.actionExecuter.Action(feature.code, code)
const aliases = action.commandAliases ? [...action.commandAliases] : []
if (aliases.length > 0) {
commandStr = aliases.shift() as string
}
let command = this.program.command(commandStr)
if (aliases.length > 0) {
command = command.aliases(aliases)
}
command = command.action(async (...args: any[]) => {
this.clearAndRenderMasthead(action)
const startTime = new Date().getTime()
const options = commanderUtil.mapIncomingToOptions(
...args,
feature.optionsSchema ?? action.optionsSchema
)
const results = await action.execute({
...options,
})
const endTime = new Date().getTime()
const totalTime = endTime - startTime
this.clearAndRenderResults({
featureCode: feature.code,
actionCode: code,
totalTime,
results,
action,
})
return results as any
})
const description = action.optionsSchema?.description
if (description) {
command = command.description(description)
}
const schema = action.optionsSchema
if (schema) {
this.attachOptions(command, schema)
}
}
protected clearAndRenderResults(
options: ClearResultsAndRenderResultsOptions
) {
const { featureCode, actionCode, results, totalTime, action } = options
this.ui.stopLoading()
this.ui.clear()
this.ui.renderActionSummary({
namespace: this.pkg.doesExist()
? this.pkg.getSkillNamespace()
: undefined,
featureCode,
actionCode,
totalTime,
action,
...results,
headline: results.headline ?? `${actionCode} finished!`,
})
}
private clearAndRenderMasthead(action: FeatureAction<Schema>) {
uiUtil.renderActionMastHead(this.ui, action)
}
private attachOptions(command: CommanderStatic['program'], schema: Schema) {
const entity = SchemaEntityFactory.Entity(schema)
let theProgram = command
const fields = entity.getNamedFields()
const aliases = featuresUtil.generateOptionAliases(schema)
fields.forEach(({ field, name }) => {
try {
theProgram = theProgram.option(
aliases[name],
field.hint,
field.definition.defaultValue
? `${field.definition.defaultValue}`
: undefined
)
} catch (err: any) {
throw new SpruceError({
//@ts-ignore
code: 'FAILED_TO_ATTACH_COMMAND',
fieldName: name,
id: entity.schemaId,
originalError: err,
friendlyMessage: `Could not attach option ${aliases[name]} from ${entity.schemaId}.${name} to the command`,
})
}
})
}
}
export type OptionOverrides = Record<string, Record<string, any>>
export type BlockedCommands = Record<string, string>
export interface ClearResultsAndRenderResultsOptions {
namespace?: string
featureCode: string
actionCode: string
totalTime: number
action: FeatureAction
results: FeatureActionResponse
}