UNPKG

@sprucelabs/spruce-cli

Version:

Command line interface for building Spruce skills.

212 lines (180 loc) • 7.35 kB
import { SchemaValues, validateSchemaValues } from '@sprucelabs/schema' import { diskUtil } from '@sprucelabs/spruce-skill-utils' import { SpruceSchemas } from '#spruce/schemas/schemas.types' import skillFeatureSchema from '#spruce/schemas/spruceCli/v2020_07_22/skillFeature.schema' import { FileDescription, NpmPackage } from '../../types/cli.types' import ScriptUpdaterImpl from '../../updaters/ScriptUpdater' import AbstractFeature, { FeatureOptions } from '../AbstractFeature' import { FeatureCode } from '../features.types' import universalDevDependencies from '../universalDevDependencies' import universalFileDescriptions from '../universalFileDescriptions' import universalScripts from '../universalScripts' import Updater from './updaters/Updater' export default class SkillFeature< S extends SkillFeatureOptionsSchema = SkillFeatureOptionsSchema, > extends AbstractFeature<S> { public nameReadable = 'Skill' public code: FeatureCode = 'skill' public description = 'The scaffolding needed to run a Skill' public readonly installOrderWeight = 100 public packageDependencies: NpmPackage[] = [ { name: '@sprucelabs/error' }, { name: '@sprucelabs/spruce-skill-utils' }, { name: '@sprucelabs/spruce-skill-booter' }, { name: '@sprucelabs/spruce-event-utils' }, { name: '@sprucelabs/spruce-event-plugin' }, { name: '@sprucelabs/spruce-core-schemas' }, { name: 'dotenv' }, { name: '@sprucelabs/globby' }, { name: '@sprucelabs/mercury-types', }, { name: 'tsconfig-paths', isDev: true }, { name: '@sprucelabs/spruce-test-fixtures@latest', isDev: true }, { name: 'ts-node', isDev: true }, ...universalDevDependencies, ] public optionsSchema = skillFeatureSchema as S public actionsDir = diskUtil.resolvePath(__dirname, 'actions') private engines = { yarn: '1.x', } public scripts = { boot: 'node build/index', 'boot.local': 'node -r ts-node/register -r tsconfig-paths/register ./src/index', health: 'yarn boot --health', 'health.local': 'yarn boot.local --health', ...universalScripts, build: 'yarn clean && yarn run build.copy-files && tsc -p tsconfig.prod.json && yarn build.resolve-paths && rm -rf build/__tests__', } public readonly fileDescriptions: FileDescription[] = [ ...universalFileDescriptions, { path: 'src/index.ts', description: 'The file that "boots" the skill.', shouldOverwriteWhenChanged: true, }, { path: 'errors/SpruceError.ts', description: 'Starting error class that you can edit.', shouldOverwriteWhenChanged: false, }, { path: '.spruce/skill.ts', description: 'Used to support booting the skill.', shouldOverwriteWhenChanged: true, }, { path: '.spruce/errors/options.types.ts', description: 'Holds all possible error codes and options. Will be updated as you create more errors (spruce create.error).', shouldOverwriteWhenChanged: true, }, { path: 'src/.spruce/features/event.plugin.ts', description: 'Gives your skill event support through local boot events and optionall Mercury (spruce event.listen).', shouldOverwriteWhenChanged: true, }, { path: 'tsconfig.prod.json', description: 'Configuration for building for production.', shouldOverwriteWhenChanged: true, }, ] public constructor(options: FeatureOptions) { super(options) void this.emitter.on( `test.register-abstract-test-classes`, this.handleRegisterAbstractTestClasses.bind(this) ) void this.emitter.on( 'feature.will-execute', this.handleWillExecute.bind(this) ) } private async handleRegisterAbstractTestClasses() { return { abstractClasses: [ { name: 'AbstractSpruceFixtureTest', label: 'AbstractSpruceFixtureTest', import: '@sprucelabs/spruce-test-fixtures', featureCode: 'node', }, ], } } public async beforePackageInstall(options: SkillFeatureOptions) { const { files } = await this.install(options) return { files, cwd: this.resolveDestination(options) } } public async afterPackageInstall(options: SkillFeatureOptions) { const destination = this.resolveDestination(options) await this.Store('skill', { cwd: destination, }).setCurrentSkillsNamespace(options.name) return {} } private async install(options: SkillFeatureOptions) { validateSchemaValues(skillFeatureSchema, options) const destination = this.resolveDestination(options) if (!diskUtil.doesDirExist(destination)) { diskUtil.createDir(destination) } const skillGenerator = this.Writer('skill') const files = await skillGenerator.writeSkill(destination, options) await this.installScripts(destination) this.setEngines(destination) const env = this.Service('env', destination) env.set('SKILL_NAME', options.name) return { files } } private resolveDestination(options: SkillFeatureOptions) { return diskUtil.resolvePath(this.cwd, options.destination ?? '') } public async installScripts(destination = this.cwd) { const scriptUpdater = ScriptUpdaterImpl.FromFeature(this, { cwd: destination, }) await scriptUpdater.update() } public setEngines(destination: string) { const pkg = this.Service('pkg', destination) const engines = (pkg.get('engines') as Record<string, string>) || {} for (const name in this.engines) { const all = this.engines engines[name] = this.engines[name as keyof typeof all] } pkg.set({ path: 'engines', value: engines }) } public async handleWillExecute(options: { featureCode: string actionCode: string options?: UpgradeOptions }) { const { featureCode, actionCode, options: upgradeOptions } = options const isInstalled = await this.features.isInstalled('skill') if (isInstalled && featureCode === 'node' && actionCode === 'upgrade') { const updater = new Updater(this, this.emitter) const files = await updater.updateFiles(upgradeOptions ?? {}) return { files, } } return {} } } type SkillFeatureOptionsSchema = SpruceSchemas.SpruceCli.v2020_07_22.SkillFeatureSchema type SkillFeatureOptions = SpruceSchemas.SpruceCli.v2020_07_22.SkillFeature declare module '../../features/features.types' { interface FeatureMap { skill: SkillFeature } interface FeatureOptionsMap { skill: SchemaValues<SkillFeatureOptionsSchema> } } type UpgradeOptions = SpruceSchemas.SpruceCli.v2020_07_22.UpgradeSkillOptions