UNPKG

@sprucelabs/spruce-cli

Version:

Command line interface for building Spruce skills.

242 lines (198 loc) • 7.43 kB
import { Schema } from '@sprucelabs/schema' import { diskUtil } from '@sprucelabs/spruce-skill-utils' import syncEventActionSchema from '#spruce/schemas/spruceCli/v2020_07_22/syncEventOptions.schema' import TerminalInterface from '../../interfaces/TerminalInterface' import { FileDescription } from '../../types/cli.types' import actionUtil from '../../utilities/action.utility' import AbstractFeature, { FeatureDependency, FeatureOptions, } from '../AbstractFeature' import { FeatureActionResponse, FeatureCode } from '../features.types' import EventContractBuilder from './builders/EventContractBuilder' import EventStore from './stores/EventStore' export default class EventFeature extends AbstractFeature { public code: FeatureCode = 'event' public nameReadable = 'Events' public description = 'Connect to the Mercury Event Engine.' public dependencies: FeatureDependency[] = [ { code: 'schema', isRequired: true }, { code: 'permission', isRequired: true }, ] public _packageDependencies = [ { name: '@sprucelabs/mercury-client', }, { name: '@sprucelabs/mercury-types', }, { name: '@sprucelabs/spruce-event-utils', }, { name: '@sprucelabs/spruce-event-plugin', }, { name: '@sprucelabs/mercury-core-events', }, ] public actionsDir = diskUtil.resolvePath(__dirname, 'actions') public readonly fileDescriptions: FileDescription[] = [] private contractBuilder?: EventContractBuilder private initiatingAction?: string public constructor(options: FeatureOptions) { super(options) void this.emitter.on( 'schema.did-fetch-schemas', this.handleDidFetchSchemas.bind(this) ) void this.emitter.on( 'feature.will-execute', this.handleWillExecute.bind(this) ) void this.emitter.on( 'feature.did-execute', this.handleDidExecute.bind(this) ) } public async afterPackageInstall() { diskUtil.createDir(diskUtil.resolvePath(this.cwd, 'src', 'events')) return this.optionallySyncListeners() } private async optionallySyncListeners() { let results: FeatureActionResponse = {} const isSkillInstalled = await this.features.isInstalled('skill') if (isSkillInstalled) { results = await this.Action('event', 'sync.listeners').execute({}) } return results } private async handleWillExecute(payload: { featureCode: string actionCode: string }): Promise<FeatureActionResponse> { const { featureCode, actionCode } = payload let results: FeatureActionResponse = {} if (!this.initiatingAction) { EventStore.clearCache() const combined = this.combineCodes(featureCode, actionCode) this.initiatingAction = combined } if (featureCode === 'node' || featureCode === 'upgrade') { const settings = this.Service('eventCache') settings.clearListenerCache() } const isInstalled = await this.features.isInstalled('event') const isRemoteRelevant = isInstalled && (featureCode === 'event' || featureCode === 'eventContract' || actionCode === 'login') && this.initiatingAction !== 'event.setRemote' && actionCode !== 'setRemote' if (isRemoteRelevant) { const remoteResults = await this.appendRemoteToResultsOrPrompt() results = actionUtil.mergeActionResults(results, remoteResults) } return results } private async syncListenersAndMixinResults(results: FeatureActionResponse) { const syncResults = await this.optionallySyncListeners() results = actionUtil.mergeActionResults(results, syncResults) return results } private async handleDidExecute(payload: { featureCode: string actionCode: string }) { const { featureCode, actionCode } = payload const isInstalled = await this.features.isInstalled('event') let results = {} const isUpgrade = isInstalled && featureCode === 'node' && actionCode === 'upgrade' if (isUpgrade) { results = await this.Action('event', 'sync').execute({}) results = await this.syncListenersAndMixinResults(results) } if ( this.initiatingAction === this.combineCodes(featureCode, actionCode) ) { EventStore.clearCache() } return results } private combineCodes(featureCode: string, actionCode: string) { return `${featureCode}.${actionCode}` } private async appendRemoteToResultsOrPrompt(): Promise<FeatureActionResponse> { const remote = this.Service('remote') const r = remote.getRemote() if (!r) { if (!TerminalInterface.doesSupportColor()) { throw new Error( `Dang! I couldn't find env.HOST. Once that is set, lets try again!` ) } this.ui.stopLoading() this.ui.renderLine( `Uh oh! It looks like you haven't configured your remote! We gotta do that.` ) const results = await this.Action('event', 'setRemote').execute({}) return results } else { return { summaryLines: [`Remote: ${r}`, `Host: ${remote.getHost()}`], } } } private async handleDidFetchSchemas(payload: { schemas?: Schema[] | null }) { const isInstalled = await this.features.isInstalled(this.code) const lastSync = this.Service('eventCache').getLastSyncOptions() if (lastSync && isInstalled) { const writer = this.getEventContractBuilder() const uniqueSchemas = await writer.fetchContractsAndGenerateUniqueSchemas( payload.schemas ?? [], lastSync.shouldSyncOnlyCoreEvents, lastSync.shouldOnlySyncRemoteEvents ) return { schemas: uniqueSchemas.schemas ?? [], } } return { schemas: [], } } public getEventContractBuilder() { if (!this.contractBuilder) { this.contractBuilder = new EventContractBuilder({ cwd: this.cwd, optionsSchema: syncEventActionSchema, ui: this.ui, eventGenerator: this.Writer('event'), eventStore: this.Store('event'), skillStore: this.Store('skill'), dependencyService: this.Service('dependency'), }) } return this.contractBuilder } public hasBeenSynced() { if (diskUtil.doesHashSprucePathExist(this.cwd)) { const writer = this.Writer('event') return writer.hasCombinedContractBeenWritten(this.cwd) } return false } } declare module '../../features/features.types' { interface FeatureMap { event: EventFeature } interface FeatureOptionsMap { event: undefined } }