import React from 'react'; import { PositionalOptions, Argv, MiddlewareFunction } from "yargs"; import fs from 'fs'; import path from 'path'; import { render } from 'ink'; import { ArgShape, DEFAULT_CONFIG_PATH, UniversalArgs } from "../cli"; import User from "@eximchain/dappbot-types/spec/user"; import { AUTH_FILE_PATH, initAuthFile } from "./authStorage"; import { ErrorBox } from '../ui'; /** * Given a desired command name and an example of the argument shape * the command needs to output, return a yargs `command` string * which includes all of the sample's keys as required arguments. * @param command * @param sampleArg */ export function commandFromSampleArgs(command:string, sampleArg:any) { // Sorts arg names alphabetically, except for username, which // always ought to be at the front of the list. let args = Object.keys(sampleArg).sort(); if (args.includes('username')) { args = ['username', ...args.filter(arg => arg !== 'username')] } // If this delimiter used [ ] instead of < >, the args would be // marked as optional. let delimiter = (name:string) => `<${name}>`; return `${command} ${args.map(delimiter).join(' ')}` } type DescriptionMap = Partial>; /** * Given a yargs instance, a sample argument shape, and a map of * descriptions, call the `yargs.positional()` function with the * appropriate args to instrument the command with help text for * all arguments. **Note**: This does not supported nested * arguments; if a key's value type is not `string`, `number`, * or `boolean`, it is ignored. * @param yargs * @param argShape * @param descriptions */ export function describePositionalArgs(yargs:Argv, argShape:Shape, descriptions?:DescriptionMap){ Object.keys(argShape).forEach((reqdKey) => { let keyName = reqdKey as keyof Shape; let keyType = typeof argShape[keyName]; if ( keyType === 'string' || keyType === 'number' || keyType === 'boolean' ) { let keyDesc = { type : keyType } as PositionalOptions; if (descriptions && descriptions[keyName]) { keyDesc.describe = descriptions[keyName] } yargs.positional(reqdKey, keyDesc) } }) } /** * Middlware: Listen for any options whose name ends in "Path", and if found, * read the file's contents as a string and add it as an argument * whose name ends in "File". For instance, "authPath" will yield * an "authFile" string, which can be JSON.parse()d to get the * actual authData. * @param args */ export function loadFileFromPath(args:ArgShape): ArgShape { const pathKeys = Object.keys(args).filter(key => key.indexOf('Path') > -1); pathKeys.forEach(pathKey => { const fullPath = path.resolve(process.cwd(), args[pathKey]); if (!fs.existsSync(fullPath)) throw new Error(`The specified path (${pathKey}, ${args[pathKey]}) does not have a file!`); const fileKey = `${pathKey.slice(0, pathKey.indexOf('Path'))}File`; args[fileKey] = fs.readFileSync(fullPath).toString(); }) return args; } export function addDefaultConfigFile(args:ArgShape): ArgShape { // If the user is manually providing the path to an auth file, // let that override the default -- just return. if (args.config) return args; // Check if there's a `dappbotConfig.json` file, // load its contents if so. const defaultPath = path.resolve(process.cwd(), DEFAULT_CONFIG_PATH); if (!fs.existsSync(defaultPath)) return args; Object.assign(args, JSON.parse(fs.readFileSync(defaultPath).toString())) return args; } export function addDefaultAuthPath(args:ArgShape): ArgShape { // If the user is manually providing the path to an auth file, // do not overwrite it -- just return. if (args.authPath) return args; // The load function will initialize the file if it isn't present, // so this pre-load guarantees that it will be successfully loaded // by the `loadFileFromPath` middleware. initAuthFile(); args.authPath = AUTH_FILE_PATH; return args; } export const requireAuthData:MiddlewareFunction = (args) => { function authErr() { render( ) process.exit(1); } if (!args.authFile) { authErr(); return; } const authData = JSON.parse(args.authFile); if (!User.isAuthData(authData)) { authErr(); return; } if (User.authStatus(authData).isEmpty) { authErr() return; } return args; }