UNPKG

@sprucelabs/spruce-cli

Version:

Command line interface for building Spruce skills.

217 lines (184 loc) • 7.01 kB
import globby from '@sprucelabs/globby' import { SpruceSchemas } from '@sprucelabs/mercury-types' import { normalizeSchemaToIdWithVersion, Schema, SchemaTemplateItem, } from '@sprucelabs/schema' import { diskUtil, namesUtil } from '@sprucelabs/spruce-skill-utils' import { isEqual } from 'lodash' import DependencyService from '../../../services/DependencyService' import EventTemplateItemBuilder from '../../../templateItemBuilders/EventTemplateItemBuilder' import { GraphicsInterface } from '../../../types/cli.types' import { FeatureActionResponse } from '../../features.types' import SkillStoreImpl from '../../skill/stores/SkillStore' import validateAndNormalizer from '../../validateAndNormalize.utility' import EventStore from '../stores/EventStore' import EventWriter from '../writers/EventWriter' export default class EventContractBuilder { private optionsSchema: OptionsSchema private ui: GraphicsInterface private eventWriter: EventWriter private cwd: string private eventStore: EventStore private skillStore: SkillStoreImpl private dependencyService: DependencyService public constructor(options: { optionsSchema: OptionsSchema ui: GraphicsInterface eventGenerator: EventWriter cwd: string eventStore: EventStore skillStore: SkillStoreImpl dependencyService: DependencyService }) { this.optionsSchema = options.optionsSchema this.ui = options.ui this.eventWriter = options.eventGenerator this.cwd = options.cwd this.eventStore = options.eventStore this.skillStore = options.skillStore this.dependencyService = options.dependencyService } public async fetchAndWriteContracts( options: Options & { shouldOnlySyncRemoteEvents?: boolean } ): Promise<FeatureActionResponse> { const { shouldOnlySyncRemoteEvents, ...rest } = options const normalizedOptions = validateAndNormalizer.validateAndNormalize( this.optionsSchema, rest ) const { contractDestinationDir } = normalizedOptions const resolvedDestination = diskUtil.resolvePath( this.cwd, contractDestinationDir ) const { errors, schemaTemplateItems, eventContractTemplateItems } = await this.fetchAndBuildTemplateItems({ shouldSyncOnlyCoreEvents: options.shouldSyncOnlyCoreEvents ?? false, eventBuilderFile: normalizedOptions.eventBuilderFile, shouldOnlySyncRemoteEvents, }) if (errors && errors?.length > 0) { return { errors, } } this.ui.startLoading('Generating contracts...') const files = await this.eventWriter.writeContracts( resolvedDestination, { ...normalizedOptions, eventContractTemplateItems, schemaTemplateItems, shouldImportCoreEvents: !options.shouldSyncOnlyCoreEvents, } ) await this.deleteOrphanedEventContracts( resolvedDestination, files.map((a) => a.path) ) return { files, } } private async deleteOrphanedEventContracts( lookupDir: string, existingContracts: string[] ) { const matches = await globby(lookupDir + '/**/*.contract.ts') const diff = matches.filter((m) => !existingContracts.includes(m)) diff.forEach((f) => diskUtil.deleteFile(f)) diskUtil.deleteEmptyDirs(lookupDir) } public async fetchContractsAndGenerateUniqueSchemas( existingSchemas: Schema[], shouldSyncOnlyCoreEvents: boolean, shouldOnlySyncRemoteEvents?: boolean ): Promise<FeatureActionResponse & { schemas?: Schema[] }> { const { errors, schemaTemplateItems } = await this.fetchAndBuildTemplateItems({ shouldSyncOnlyCoreEvents, shouldOnlySyncRemoteEvents, }) if (errors && errors?.length > 0) { return { errors, } } const filteredSchemas = this.filterSchemasBasedOnCallbackPayload( existingSchemas, schemaTemplateItems ) return { schemas: filteredSchemas, } } private filterSchemasBasedOnCallbackPayload( existingSchemas: Schema[], schemaTemplateItems: SchemaTemplateItem[] ) { const schemas = schemaTemplateItems.map((i) => i.schema) const filteredSchemas = schemas.filter((schema) => { const idWithVersion = normalizeSchemaToIdWithVersion(schema) return !existingSchemas.find((s) => isEqual(normalizeSchemaToIdWithVersion(s), idWithVersion) ) }) return filteredSchemas } private async fetchAndBuildTemplateItems(options: { shouldSyncOnlyCoreEvents?: boolean eventBuilderFile?: string shouldOnlySyncRemoteEvents?: boolean }) { const { shouldSyncOnlyCoreEvents, eventBuilderFile, shouldOnlySyncRemoteEvents, } = options this.ui.startLoading('Loading skill details...') let namespace: string | undefined = await this.skillStore.loadCurrentSkillsNamespace() this.ui.startLoading('Fetching event contracts...') const namespaces = shouldSyncOnlyCoreEvents ? ['core'] : this.dependencyService.get().map((d) => d.namespace) const contractResults = await this.eventStore.fetchEventContracts({ localNamespace: namespace, namespaces, shouldOnlySyncRemoteEvents, didUpdateHandler: (msg) => { this.ui.startLoading(msg) }, }) if (contractResults.errors.length > 0) { return { errors: contractResults.errors, eventContractTemplateItems: [], schemaTemplateItems: [], } } if (shouldSyncOnlyCoreEvents) { namespace = undefined } else { namespace = namesUtil.toKebab(namespace) } this.ui.startLoading('Building contracts...') const itemBuilder = new EventTemplateItemBuilder() const { eventContractTemplateItems, schemaTemplateItems } = itemBuilder.buildTemplateItems({ contracts: contractResults.contracts, localNamespace: namespace, eventBuilderFile, }) return { eventContractTemplateItems, schemaTemplateItems, errors: [], } } } type OptionsSchema = SpruceSchemas.SpruceCli.v2020_07_22.SyncEventOptionsSchema type Options = SpruceSchemas.SpruceCli.v2020_07_22.SyncEventOptions