// Re-export the features modules in this folder, which each add, remove, and admininster one AO feature (imported in project index.js) // Also contains the Features menus to control these features in this directory import chalk from 'chalk' import inquirer from 'inquirer' import fs from 'fs' import { lsFolder } from '../files.js' import { fileURLToPath } from 'url' import path from 'path' import { headerStyle, greenChalk } from '../styles.js' import { spinner } from '../console.js' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) const loadFeatures = async () => { let filenames = lsFolder(path.join(__dirname)) let features = {} for(let i = 0; i < filenames.length; i++) { const filename = filenames[i] if(filename === 'index.js') continue const moduleShortname = filename.replace(/\.js$/, '') const path = './' + filename features[moduleShortname] = (await import(path)).default } return features } const features = await loadFeatures() export default features // Returns a colored capitalized status word const styledStatus = (fullWord) => { const lookup = { unknown: ' ' + chalk.grey('Unknown') + ' ', off: ' ' + chalk.grey('Off') + ' ', installed: chalk.blue('Installed') + ' ', enabled: ' ' + greenChalk('Enabled') + ' ', running: ' ' + greenChalk('Running') + ' ', synced: ' ' + greenChalk('Synced') + ' ', error: ' ' + chalk.red('Error') + ' ' } return lookup[fullWord.toLowerCase()] + ' ' } // Prints the Configure AO Features menu and executes the user's choice let featuresChoices export async function featuresMenu(previousMenuChoice = 0) { console.log(`\n${headerStyle('Configure AO Features')}`) const stopSpinner = spinner('Loading status...') let loadedFeatures = 0 if(!featuresChoices) { featuresChoices = Object.entries(features).map(([featureKey, feature]) => { let featureName = featureKey if(feature.hasOwnProperty('name') && feature.name.length >= 1) { featureName = feature.name } const nameColumn = featureName.padEnd(17) const status = feature.status() || 'Unknown' if(status !== 'Unknown') { loadedFeatures++ } const statusColumn = styledStatus(status) const descriptionColumn = feature.description || '' const choice = { name: nameColumn + statusColumn + descriptionColumn, value: featureKey, short: featureKey} return choice }) featuresChoices.push( 'Back to Main Menu' ) } else { loadedFeatures = featuresChoices.filter(feature => { return typeof feature === 'object' && !feature.name.includes('Unknown') }).length } stopSpinner('Loaded status for ' + loadedFeatures + '/' + (featuresChoices.length - 1) + ' features.') let answer try { answer = await inquirer.prompt({ name: 'features_menu', type: 'list', message: 'Please choose:', choices: featuresChoices, default: previousMenuChoice, pageSize: featuresChoices.length }) } catch(error) { if (error === 'EVENT_INTERRUPTED') { console.log('\nESC') return false } } if(answer.features_menu === 'Back to Main Menu') { return false } const chosenFeature = features[answer.features_menu] const chosenFeatureName = (chosenFeature.hasOwnProperty('name') && chosenFeature.name.length >= 1) ? chosenFeature.name : answer.features_menu while(await oneFeatureMenu(chosenFeatureName, chosenFeature)) {} return answer.features_menu } // Prints the menu options for a specific feature. // Each feature module can export functions for status, install, version, update and these will be listed in the menu based on context // If the module also has a menu: field, these menu items will each be appended if the name is truthy when calculated // Prints all the standard-named features plus features listed under 'menu' export async function oneFeatureMenu(name, feature) { console.log(`\n${headerStyle(name)}`) if(feature.description && feature.description?.length >= 1) { console.log('\n' + feature.description + '\n') } const featureChoices = [] const stopSpinner = spinner('Loading status...') const status = feature?.status() || false stopSpinner('Loaded ' + name + ' status: ' + (feature.status() || 'Unknown') + '\n') if(!status) { console.log("This AO feature module lacks a status() function, not sure which menu items to display.") return false } if(status === 'off') { if(feature.hasOwnProperty('install')) { featureChoices.push({ name: 'Install ' + name, value: 'install' }) menuItemCount++ } } else { if(feature.hasOwnProperty('update')) { featureChoices.push({ name: 'Update ' + name, value: 'update'}) } if(feature.hasOwnProperty('uninstall')) { featureChoices.push({ name: 'Uninstall ' + name, value: 'uninstall'}) } } if(feature.hasOwnProperty('menu')) { feature.menu.forEach(menuItem => { const menuItemName = typeof menuItem.name === 'function' ? menuItem.name() : menuItem.name if(!menuItemName) { return } const menuItemValue = typeof menuItem.value === 'function' ? menuItem.value() : menuItem.value // todo: uninstall option will go here also featureChoices.push({ name: menuItemName, value: menuItemValue }) }) } if(featureChoices.length < 1) { console.log("Nothing to do yet on this feature, please check back soon.") return false } featureChoices.push( 'Back to Features' ) let answer try { answer = await inquirer.prompt({ name: 'feature_menu', type: 'list', message: 'Please choose:', choices: featureChoices }) } catch(error) { if (error === 'EVENT_INTERRUPTED') { console.log('\nESC') return false } } if(answer.feature_menu === 'Back to Features') { return false } if(Object.keys(feature).includes(answer.feature_menu)) { await feature[answer.feature_menu]() return true } console.log('Not yet implemented') return true }