An interactive command-line interface (CLI) tool to help you install, use, and administer an AO instance.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

174 lines
6.0 KiB

// 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
}