UNPKG

@sprucelabs/spruce-cli

Version:

Command line interface for building Spruce skills.

265 lines • 10.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const merge_1 = __importDefault(require("lodash/merge")); const FormComponent_1 = __importDefault(require("../components/FormComponent")); const constants_1 = require("../constants"); const SpruceError_1 = __importDefault(require("../errors/SpruceError")); const form_utility_1 = __importDefault(require("../utilities/form.utility")); const feature_utilities_1 = __importDefault(require("./feature.utilities")); class ActionQuestionAskerImpl { ui; static Class; featureInstaller; actionCode; feature; shouldAutoHandleDependencies = true; constructor(options) { this.ui = options.ui; this.featureInstaller = options.featureInstaller; this.actionCode = options.actionCode; this.feature = options.feature; this.shouldAutoHandleDependencies = options.shouldAutoHandleDependencies; } static Asker(options) { return new (this.Class ?? ActionQuestionAskerImpl)(options); } async installOrMarkAsSkippedMissingDependencies() { const notInstalled = await this.getDependenciesNotInstalled(); let response = {}; let installCount = 0; if (notInstalled.length > 0) { this.ui.renderLine(this.generateConfirmInstallMessage(notInstalled) + '\n'); while (notInstalled.length > 0) { const toInstall = notInstalled.shift(); if (!toInstall) { // for typescript throw new Error('Dependent feature error'); } const wasInstalled = await this.featureInstaller.isInstalled(toInstall.code); if (!wasInstalled && !this.featureInstaller.isMarkedAsSkipped(toInstall.code)) { const installResults = await this.installOrMarkAsSkippedMissingDependency(toInstall); response = (0, merge_1.default)(response, installResults); installCount++; } } if (installCount > 0) { this.ui.clear(); await this.ui.waitForEnter(`Phew, now that we're done with that, lets get back to ${this.getCommandName()}!`); this.ui.clear(); } } return response; } async getDependenciesNotInstalled() { const dependencies = this.featureInstaller.getFeatureDependencies(this.feature.code); const installedStatuses = await Promise.all(dependencies.map(async (dependency) => { const feature = this.featureInstaller.getFeature(dependency.code); const isInstalled = await this.featureInstaller.isInstalled(dependency.code); const isMarkedAsSkipped = this.featureInstaller.isMarkedAsSkipped(feature.code); if (isMarkedAsSkipped) { return null; } return !isInstalled ? { feature, ...dependency } : null; })); const notInstalled = installedStatuses.filter((feature) => !!feature); return notInstalled; } async askAboutMissingFeatureOptionsIfFeatureIsNotInstalled(isInstalled, options) { let installOptions = { ...options }; if (!isInstalled) { if (this.feature.optionsSchema) { const answers = await this.collectAnswers(this.feature.optionsSchema, options); installOptions = { ...installOptions, ...answers }; } } return installOptions; } async askAboutMissingActionOptions(action, options) { let answers; const schema = action.optionsSchema; if (schema) { answers = await this.collectAnswers(schema, options); } return answers; } async installOurFeature(installOptions) { if (!this.shouldAutoHandleDependencies) { throw new SpruceError_1.default({ code: 'FEATURE_NOT_INSTALLED', featureCode: this.feature.code, friendlyMessage: `You need to install the \`${this.feature.code}\` feature.`, }); } let isFirstUpdate = true; const installResults = await this.featureInstaller.install({ installFeatureDependencies: false, features: [ { code: this.feature.code, options: installOptions, }, ], didUpdateHandler: (message) => { if (isFirstUpdate) { this.renderHeading(this.feature.code); isFirstUpdate = false; } this.ui.startLoading(message); }, }); this.ui.stopLoading(); return installResults; } renderHeading(code) { this.ui.clear(); this.ui.renderHero(constants_1.CLI_HERO); this.ui.renderHeadline(`Installing ${code} feature...`); } async installOrMarkAsSkippedMissingDependency(toInstall) { const { feature, isRequired } = toInstall; if (isRequired) { const confirm = await this.ui.confirm(`Install the ${feature.nameReadable} feature?`); if (!confirm) { throw new SpruceError_1.default({ code: 'COMMAND_ABORTED', command: this.getCommandName(), }); } } else { const response = await this.ui.prompt({ type: 'select', defaultValue: 'yes', label: `Install the ${feature.nameReadable} feature? (optional)`, options: { choices: [ { value: 'yes', label: 'Yes', }, { value: 'skip', label: 'Skip', }, { value: 'alwaysSkip', label: 'Always skip', }, ], }, }); if (response !== 'yes') { this.ui.renderLine(response === 'alwaysSkip' ? 'Skipping forever!' : 'Cool, skipping for now.'); if (response === 'skip') { this.featureInstaller.markAsSkippedThisRun(feature.code); } else { this.featureInstaller.markAsPermanentlySkipped(feature.code); } const installResponse = {}; return installResponse; } } let installOptions = {}; if (feature.optionsSchema) { installOptions = await this.collectAnswers(feature.optionsSchema, undefined); } if (!this.shouldAutoHandleDependencies) { throw new SpruceError_1.default({ code: 'FEATURE_NOT_INSTALLED', featureCode: this.feature.code, friendlyMessage: `You need to install the \`${this.feature.code}\` feature.`, }); } let isFirstUpdate = true; const installResults = await this.featureInstaller.install({ installFeatureDependencies: false, didUpdateHandler: (message) => { if (isFirstUpdate) { this.renderHeading(this.feature.code); isFirstUpdate = false; } this.ui.startLoading(message); }, features: [ { code: feature.code, options: installOptions, }, ], }); this.ui.stopLoading(); return installResults; } async collectAnswers(schema, options) { const cleaned = {}; Object.keys(options ?? {}).forEach((key) => { //@ts-ignore if (typeof options[key] !== 'undefined') { //@ts-ignore cleaned[key] = options[key]; } }); const fieldNames = Object.keys(schema.fields ?? {}); const providedFieldNames = cleaned ? Object.keys(cleaned ?? {}) : []; const fieldsToPresent = fieldNames.filter((name) => providedFieldNames.indexOf(name) === -1 && schema.fields?.[name].isRequired === true && schema.fields?.[name].isPrivate !== true); let answers = {}; if (fieldsToPresent.length > 0) { const featureForm = new FormComponent_1.default({ ui: this.ui, schema, //@ts-ignore initialValues: cleaned, onWillAskQuestion: form_utility_1.default.onWillAskQuestionHandler.bind(form_utility_1.default), }); answers = await featureForm.present({ showOverview: false, // @ts-ignore fields: fieldsToPresent, }); } return { ...(cleaned ?? {}), ...answers }; } getCommandName() { return feature_utilities_1.default.generateCommand(this.feature.code, this.actionCode); } generateConfirmInstallMessage(notInstalled) { const required = []; const optional = []; notInstalled.forEach((feat) => { if (feat.isRequired) { required.push(feat); } else { optional.push(feat); } }); const requiredMessage = `I'll need to install ${required.length} feature${s(required)}.`; const optionalMessage = `there ${areIs(optional)} ${optional.length} optional feature${s(optional)} that could be installed.`; const mixedMessage = `I found ${required.length} required and ${optional.length} optional feature${s(optional)} to install.`; let message = mixedMessage; if (optional.length === 0) { message = requiredMessage; } else if (optional.length > 0 && required.length === 0) { message = optionalMessage; } return `Before you can run \`${this.getCommandName()}\`, ${message} Don't worry, I'll walk you through it!`; } } exports.default = ActionQuestionAskerImpl; function s(array) { return array.length === 1 ? '' : ''; } function areIs(array) { return array.length === 1 ? 'is' : 'are'; } //# sourceMappingURL=ActionQuestionAsker.js.map