From 9a6a27e3069633cb00280b8f0614bb7e00861d8c Mon Sep 17 00:00:00 2001 From: deicidus <> Date: Tue, 14 Jun 2022 04:22:26 -0700 Subject: [PATCH] prettier install wizard --- index.js | 6 ++-- scripts/ao.js | 6 ++-- scripts/features/index.js | 18 ++--------- scripts/strings.js | 2 ++ scripts/styles.js | 26 +++++++++++++++ scripts/welcome.js | 2 +- scripts/wizard.js | 68 ++++++++++++++++++++++++++++----------- 7 files changed, 87 insertions(+), 41 deletions(-) diff --git a/index.js b/index.js index 56f8fed..43132bb 100644 --- a/index.js +++ b/index.js @@ -177,14 +177,16 @@ async function main() { exitIfRoot() // Loading screen, display some quick info during the fun animation + console.log('ao-cli v' + (await aoCli.version())) const DISTRO = aoEnv('DISTRO') if(!DISTRO) { setAoEnv('DISTRO', detectOS()) } + const friendlyEnvPath = AO_ENV_FILE_PATH.replace(/^\/home\/\w+/, '~') if(checkAoEnvFile()) { - console.log('AO .env file exists at', AO_ENV_FILE_PATH) + console.log(friendlyEnvPath, 'exists') } else { - console.log('AO .env file does not exist at', AO_ENV_FILE_PATH) + console.log(friendlyEnvPath, 'does not exist') } await unicornPortal(650) diff --git a/scripts/ao.js b/scripts/ao.js index 1b57199..c02d286 100644 --- a/scripts/ao.js +++ b/scripts/ao.js @@ -85,11 +85,11 @@ async function chatMenu() { // They previously enabled public bootstrapping, so check to make sure it is working and then hide the option // todo: start and then verify functioning of p2p boostrap method here // if it's already started don't start it again - if(!publicBootstrapStarted) { + //if(!publicBootstrapStarted) { console.log("\nBootstrapping public AO swarm...") startPublicBootstrap() console.log("Bootstrapped (just kidding)") - } + //} //answers['chat_menu'] = 'Enable p2p bootstrap' } const chatChoices = [ @@ -130,4 +130,4 @@ async function chatMenu() { return false } return true -} \ No newline at end of file +} diff --git a/scripts/features/index.js b/scripts/features/index.js index d0cff65..b4fb796 100644 --- a/scripts/features/index.js +++ b/scripts/features/index.js @@ -6,7 +6,7 @@ import fs from 'fs' import { lsFolder } from '../files.js' import { fileURLToPath } from 'url' import path from 'path' -import { headerStyle, greenChalk } from '../styles.js' +import { headerStyle, greenChalk, styledStatus } from '../styles.js' import { spinner } from '../console.js' const __filename = fileURLToPath(import.meta.url) @@ -29,20 +29,6 @@ const loadFeatures = async () => { 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) { @@ -60,7 +46,7 @@ export async function featuresMenu(previousMenuChoice = 0) { if(status !== 'Unknown') { loadedFeatures++ } - const statusColumn = styledStatus(status) + const statusColumn = styledStatus(status) + ' ' const descriptionColumn = feature.description || '' const choice = { name: nameColumn + statusColumn + descriptionColumn, value: featureKey, short: featureKey} return choice diff --git a/scripts/strings.js b/scripts/strings.js index 319f759..13a83b4 100644 --- a/scripts/strings.js +++ b/scripts/strings.js @@ -15,6 +15,8 @@ export const repeatString = (str, n) => { return new Array(1 + (n || 0)).join(str) } +String.prototype.repeat = repeatString + String.prototype.centerInConsole = function (width = 80) { const consoleWidth = process.stdout.columns const padding = Math.floor((consoleWidth - width) / 2) diff --git a/scripts/styles.js b/scripts/styles.js index d97840f..ae00cbf 100644 --- a/scripts/styles.js +++ b/scripts/styles.js @@ -1,4 +1,5 @@ import chalk from 'chalk' +import { repeatString } from './strings.js' // Chalk styles export const greenChalk = chalk.hex('#008800') @@ -10,3 +11,28 @@ export const heading2 = chalk.underline // Preformatted phrases that can be used in backticked console.log strings export const theAO = `the ${greenChalk.bold('AO')}` export const theMenu = `the ${greenChalk.bold('Menu')}` + +// Returns a colored capitalized status word +export const styledStatus = (fullWord, width) => { + const title = fullWord.toTitleCase() + const lookup = { + unknown: chalk.grey(title), + off: chalk.grey(title), + 'not installed': chalk.grey(title), + blank: chalk.grey(title), + downloaded: chalk.yellow(title), + installed: chalk.blue(title), + initialized: chalk.green(title), + created: chalk.green(title), + enabled: greenChalk(title), + running: greenChalk(title), + synced: greenChalk(title), + error: chalk.red(title), + missing: chalk.red(title) + } + let styledWord = lookup[fullWord.toLowerCase()] + const halfDiff = (width - fullWord.length) / 2 + const left = halfDiff % 2 === 0 ? repeatString(' ', halfDiff) : repeatString(' ', Math.ceil(halfDiff)) + const right = repeatString(' ', Math.floor(halfDiff)) + return left + styledWord + right +} \ No newline at end of file diff --git a/scripts/welcome.js b/scripts/welcome.js index 72ff965..9162cab 100644 --- a/scripts/welcome.js +++ b/scripts/welcome.js @@ -22,7 +22,7 @@ const welcomeMessages = [ `Dipping your brush in ink, you draw a perfect circle. This is ${theAO}.`, `You are offered a choice between two pills. However, you have secretly built up an immunity to both pills, and trick your opponent into taking one. Inconceviably, you are in ${theAO}.`, `A young man with spiky hair and golden skin appears before you in a halo of light. He guides you to ${theAO}.`, - `You enter a gap between two hedges and, after struggling through the brush, emerge into a sunny estate garden. You've found the AO.`, + `Looking for a shortcut, you worm your way through through the hedges, and, after struggling through the brush, emerge into a sunny estate garden. You've found the AO.`, `You find a small animal burrow dug-out near the riverside. Crawling in, you find a network of caves that lead to ${theAO}.`, `You receive a handwritten letter in the mail, which reads, in fine calligraphy:, "Dear —, You are in ${theAO}."`, `An unexpected calm settles over you. The ${greenChalk.bold('AO')} is here: Here is ${theAO}.`, diff --git a/scripts/wizard.js b/scripts/wizard.js index 1cdd18f..364ebe8 100644 --- a/scripts/wizard.js +++ b/scripts/wizard.js @@ -7,10 +7,12 @@ import { detectOS, updateSoftware, isInstalled } from './system.js' import { isFolder, isFile } from './files.js' import { aoIsInstalled } from './features/ao-server.js' import { asciiArt } from './console.js' -import { headerStyle, heading2 } from './styles.js' +import { headerStyle, heading2, styledStatus } from './styles.js' import features from './features/index.js' +import { yesOrNo } from './welcome.js' -const AO_MEMES_PATH = path.join(process.env.HOME, '.ao/memes') +const AO_PATH = path.join(process.env.HOME, '.ao') +const AO_MEMES_PATH = path.join(AO_PATH, 'memes') const commonPackages = ['curl', 'wget', 'git', 'make', 'sqlite3', 'python', 'autoconf-archive'] const debianPackages = ['build-essential', 'zlib1g-dev', 'libtool-bin', 'autoconf', 'automake autotools-dev', 'libgmp-dev', 'libsqlite3-dev', 'python3', 'python3-mako', 'libsodium-dev', 'pkg-config', 'libev-dev', 'libcurl4-gnutls-dev', 'libssl-dev', 'fakeroot', 'devscripts'] @@ -183,37 +185,57 @@ export function checkRequired() { } // Prints out a summary of the AO's installation status. Returns true if the AO is installed (Standard + ao-svelte or ao-3, not ao-cli-only) -export function checkAo() { - const width = 24 +export async function checkAo() { + const width = 19 + const columnize = (status) => styledStatus(status, width) // Print status of each required package individually console.log(`\n${heading2('Required Software Packages')}`) - const summary = checkRequired() - Object.entries(summary).forEach(([packageName, isInstalled]) => { - console.log(packageName.padEnd(width) + (isInstalled ? 'Installed' : 'Missing')) + const summary = Object.entries(checkRequired()) + let installedRequirementCount = 0 + summary.forEach(([packageName, isInstalled]) => { + if(isInstalled) installedRequirementCount++ + console.log(packageName.padEnd(width) + columnize(isInstalled ? 'Installed' : 'Missing')) }) - const prerequisitesInstalled = Object.entries(summary).every(([packageName, isInstalled]) => isInstalled) + const prerequisitesInstalled = summary.every(([packageName, isInstalled]) => isInstalled) // Check for existence of required directories console.log(`\n${heading2('Folders & Config File')}`) const requiredDirectoriesExist = checkAoDirectories() - console.log('~/.ao & ~/.ao/memes'.padEnd(width) + (requiredDirectoriesExist ? ' Created ' : 'Missing')) - + console.log('~/.ao'.padEnd(width) + (isFolder(AO_PATH) ? columnize('Created') : columnize('Missing'))) + // Check for .env file const aoEnvFilePath = path.join(process.env.HOME, '.ao/.env') const hasEnvFile = isFile(aoEnvFilePath) - console.log('~/.ao/.env'.padEnd(width) + (hasEnvFile ? 'Initialized' : 'Blank'), '\n') + console.log('~/.ao/.env'.padEnd(width) + columnize(hasEnvFile ? 'Initialized' : 'Blank')) + + console.log('~/.ao/memes'.padEnd(width) + columnize(requiredDirectoriesExist ? 'Created' : 'Missing')) - // Check for ao-server folder with node_modules folder (can do a better check?) + // Check for node project folders and locally installed packages (npm i) console.log(`\n${heading2('AO Server + Client Version Installed')}`) const homePathsToCheck = ['ao-server', 'ao-svelte', 'ao-3'] let homePathsExist = [] + let npmInstalled = [] homePathsToCheck.forEach(folderName => { - const nodeModulesPath = path.join(process.env.HOME, folderName, 'node_modules') - const exists = isFolder(nodeModulesPath) - console.log(folderName.padEnd(width) + (exists ? 'Installed' : 'Not Installed')) + const folderPath = path.join(process.env.HOME, folderName) + const exists = isFolder(folderPath) + let npmI = false if(exists) { homePathsExist.push(folderName) + try { + const stdout = execSync('cd ' + folderPath + ' && npm list 2>&1') + npmInstalled.push(folderName) + npmI = true + } catch(error) { + const missingLocalPackagesCount = error.output.toString().match(/npm ERR! missing:/g).length || 0 + if(missingLocalPackagesCount) { + // Confirmation that not all dependencies have been installed + //console.log(folderName, 'has', missingLocalPackagesCount, 'npm packages left to install.') + } else { + // Unknown error + } + } } + console.log(folderName.padEnd(width) + columnize(exists ? npmI ? 'Installed' : 'Downloaded' : 'Not Installed')) }) const hasAnAo = (homePathsExist.includes('ao-server') && homePathsExist.includes('ao-svelte')) || homePathsExist.includes('ao-3') @@ -224,7 +246,7 @@ export function checkAo() { const name = feature?.name || shortname const isInstalled = feature.hasOwnProperty('isInstalled') ? feature.isInstalled() : ['installed', 'enabled', 'running', 'synced'].includes(feature.status()) if(isInstalled) optionalInstalls.push(shortname) - console.log(name.padEnd(width) + (isInstalled ? 'Installed' : 'Not Installed')) + console.log(name.padEnd(width) + columnize(isInstalled ? 'Installed' : 'Not Installed')) }) console.log(`\n${heading2('Summary')}`) @@ -239,15 +261,23 @@ export function checkAo() { if(installAttained === 'Standard' && fullFeatures.every(shortname => optionalInstalls.includes(shortname))) { installAttained = 'Full' } + console.log(installedRequirementCount + '/' + summary.length, 'required packages installed.') console.log(optionalInstalls.length + '/' + Object.keys(features).length, 'optional features installed.') if(!installAttained) { console.log("You have not installed the AO; the required packages were not detected.") } else { - console.log('You have the packages installed for a', installAttained, 'install.') + console.log('Recognized this set of packages as a', installAttained, 'install.') } - console.log('Selected AO_VERSION is', aoEnv('AO_VERSION')) + console.log('Selected AO_VERSION is', aoEnv('AO_VERSION') + '\n') - // Is it possible to check if npm i has already been called in all the node project folders? + if(installAttained) console.log('The AO is installed.') + else { + console.log('The AO is not installed yet. Would you like to install it now?') + const answer = await yesOrNo('Start AO install wizard?', true) + if(answer) { + await aoInstallWizard() + } + } } // Installs core dependencies required by Alchemy and the AO