@sprucelabs/spruce-cli
Version:
Command line interface for building Spruce skills.
265 lines • 10.7 kB
JavaScript
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
;