UNPKG

@sprucelabs/spruce-cli

Version:

Command line interface for building Spruce skills.

135 lines (116 loc) 4.51 kB
import { Schema } from '@sprucelabs/schema' import { namesUtil } from '@sprucelabs/spruce-skill-utils' import { diskUtil } from '@sprucelabs/spruce-skill-utils' import SpruceError from '../errors/SpruceError' import { FeatureAction, ActionOptions, FeatureCode, } from '../features/features.types' import { GlobalEmitter } from '../GlobalEmitter' import { BlockedCommands, OptionOverrides } from '../types/cli.types' import ActionExecuter from './ActionExecuter' import FeatureInstaller from './FeatureInstaller' import OverrideActionDecorator from './OverrideActionDecorator' export default class ActionFactory { private actionOptions: FeatureActionFactoryOptions private blockedCommands?: BlockedCommands private optionOverrides?: OptionOverrides private static overrides: Record<string, ActionConstructor> = {} public constructor(options: FeatureActionFactoryOptions) { const { blockedCommands, optionOverrides, ...actionOptions } = options this.actionOptions = actionOptions this.blockedCommands = blockedCommands this.optionOverrides = optionOverrides } public Action<F extends FeatureCode, S extends Schema = Schema>(options: { featureCode: F actionCode: string actionExecuter: ActionExecuter featureInstaller: FeatureInstaller }): FeatureAction<S> { const { featureCode, actionCode, actionExecuter, featureInstaller } = options const feature = featureInstaller.getFeature(featureCode) if (!feature.actionsDir) { throw new Error( `Your ${featureCode} features needs \`public actionsDir = diskUtil.resolvePath(__dirname, 'actions')\`` ) } let Class: new (options: ActionOptions) => FeatureAction | undefined let originalError: Error | undefined const key = ActionFactory.overrideKey(featureCode, actionCode) if (ActionFactory.overrides[key]) { Class = ActionFactory.overrides[key] } else { const classPath = diskUtil.resolvePath( feature.actionsDir, `${namesUtil.toPascal(actionCode)}Action` ) try { Class = require(classPath).default } catch (err: any) { originalError = err } } //@ts-ignore if (!Class) { throw new SpruceError({ code: 'GENERIC', friendlyMessage: `I could not find any action named '${actionCode}' for the ${feature.code} feature. Make sure it's the default export and extends AbstractAction.`, originalError, }) } const action = new Class({ ...this.actionOptions, actionExecuter, parent: feature, featureInstaller, }) if (!action) { throw new SpruceError({ code: 'GENERIC', friendlyMessage: `I could not instantiate ${actionCode} action on ${feature.code} feature.`, }) } if (!action.execute) { throw new SpruceError({ code: 'GENERIC', friendlyMessage: `It looks like the ${feature.code} feature's '${actionCode}' action does not properly extend AbstractAction.`, }) } const actionDecorator = new OverrideActionDecorator({ action, feature, blockedCommands: this.blockedCommands, optionOverrides: this.optionOverrides, ui: this.actionOptions.ui, actionCode, }) return actionDecorator as FeatureAction<S> } public static setActionClass( featureCode: FeatureCode, action: string, ExecuteTrackingAction: ActionConstructor ) { this.overrides[ActionFactory.overrideKey(featureCode, action)] = ExecuteTrackingAction } private static overrideKey(featureCode: string, action: string) { return `${featureCode}${action}` } public static clearActionOverrides() { this.overrides = {} } } export interface FeatureActionFactoryOptions extends Omit< ActionOptions, 'parent' | 'actionExecuter' | 'featureInstaller' > { emitter: GlobalEmitter blockedCommands?: BlockedCommands optionOverrides?: OptionOverrides } type ActionConstructor = new (options: ActionOptions) => FeatureAction