UNPKG

@holographxyz/cli

Version:
283 lines (282 loc) 12.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const inquirer = tslib_1.__importStar(require("inquirer")); const fs = tslib_1.__importStar(require("fs-extra")); const path = tslib_1.__importStar(require("node:path")); const core_1 = require("@oclif/core"); const wallet_1 = require("@ethersproject/wallet"); const networks_1 = require("@holographxyz/networks"); const config_1 = require("../../utils/config"); const utils_1 = require("../../utils/utils"); const aes_encryption_1 = tslib_1.__importDefault(require("../../utils/aes-encryption")); class Config extends core_1.Command { static description = 'Initialize the Holograph CLI with a config file. If no flags are passed, the CLI will prompt you for the required information.'; static examples = [ '$ <%= config.bin %> <%= command.id %> --privateKey abc...def', '$ <%= config.bin %> <%= command.id %> --fromFile ./config.json', '$ <%= config.bin %> <%= command.id %> --fromJson \'{"version": "beta3", ...}', ]; static flags = { network: core_1.Flags.string({ options: networks_1.supportedShortNetworks, description: 'Network to set', }), url: core_1.Flags.string({ description: 'Provider URL of network to set', dependsOn: ['network'], }), privateKey: core_1.Flags.string({ description: 'Default account to use when sending all transactions' }), fromFile: core_1.Flags.string({ description: 'Path to the config file to load' }), fromJson: core_1.Flags.string({ description: 'JSON object to use as the config' }), }; /** * Command Entry Point */ async run() { const { flags } = await this.parse(Config); let privateKey = flags.privateKey; let userWallet = null; let currentConfigFile = null; let encryption; let iv = ''; const configPath = path.join(this.config.configDir, config_1.CONFIG_FILE_NAME); this.debug(`Configuration path ${configPath}`); let updateNetworksPrompt = { update: false }; let privateKeyPrompt = { update: false }; await this.loadConfigPath(configPath, flags.fromFile); await this.loadConfigJson(configPath, flags.fromJson); // Check if config file exists const isConfigExist = await (0, config_1.checkFileExists)(configPath); let userConfigTemplate = { version: 'beta3', networks: {}, user: { credentials: { iv: iv, privateKey: privateKey, address: '', }, }, }; if (isConfigExist) { this.log(`Updating existing configuration file at ${configPath}`); currentConfigFile = await (0, config_1.ensureConfigFileIsValid)(configPath, undefined, false); userConfigTemplate = Object.assign({}, userConfigTemplate, currentConfigFile.configFile); const prompt = await inquirer.prompt([ { name: 'shouldContinue', message: 'Configuration already exist, are you sure you want to override existing values?', type: 'confirm', default: false, }, ]); if (!prompt.shouldContinue) { this.log('No files were modified'); this.exit(); } } else { this.log(`Creating a new config file file at ${configPath}`); } if (isConfigExist) { // See if the user wants to update network config updateNetworksPrompt = await inquirer.prompt([ { name: 'update', message: 'Would you like to update your network config?', type: 'confirm', default: false, }, ]); } if (updateNetworksPrompt.update || !isConfigExist) { // Check what networks the user wants to operate on const prompt = await inquirer.prompt([ { type: 'checkbox', name: 'networks', message: 'Which networks do you want to operate?', choices: (0, config_1.generateSupportedNetworksOptions)(), validate: async (input) => { if (input.length > 0) { return true; } return 'Please select at least 1 network. Use the arrow keys and space-bar to select.'; }, }, ]); const providedNetworks = prompt.networks; // Remove networks the user doesn't want to operate on for (const network of Object.keys(userConfigTemplate.networks)) { if (!providedNetworks.includes(network)) { delete userConfigTemplate.networks[network]; } } // Add networks to the user config // It's okay to await in loop because this is a synchronous operation for (const network of providedNetworks) { const prompt = await inquirer.prompt([ { name: 'providerUrl', message: `Enter the provider url for ${networks_1.networks[network].shortKey}. Leave blank to use ${userConfigTemplate.networks[network]?.providerUrl || networks_1.networks[network].rpc} :`, type: 'input', validate: async (input) => { if ((0, utils_1.isStringAValidURL)(input) || input === '') { return true; } return 'Input is not a valid and secure URL (https or wss)'; }, }, ]); // Leave existing providerUrl if user didn't enter a new one if (prompt.providerUrl !== '') { userConfigTemplate.networks[network] = { providerUrl: prompt.providerUrl }; } else if (!(network in userConfigTemplate.networks)) { userConfigTemplate.networks[network] = { providerUrl: networks_1.networks[network].rpc }; } } } if (isConfigExist) { // See if the user wants to update network config privateKeyPrompt = await inquirer.prompt([ { name: 'update', message: 'Would you like to update your private key?', type: 'confirm', default: false, }, ]); } if (privateKeyPrompt.update || !isConfigExist) { // Collect private key value let keyProtected = true; if (privateKey === undefined) { keyProtected = false; const prompt = await inquirer.prompt([ { name: 'privateKey', message: 'Default private key to use when sending all transactions (will be password encrypted)', type: 'password', validate: async (input) => { try { const w = new wallet_1.Wallet(input); this.debug(w); return true; } catch (error) { this.debug(error); return 'Input is not a valid private key'; } }, }, ]); privateKey = prompt.privateKey; userWallet = new wallet_1.Wallet(prompt.privateKey); iv = (0, utils_1.randomASCII)(12); } else { iv = currentConfigFile.user.credentials.iv; } await inquirer.prompt([ { name: 'encryptionPassword', message: 'Please enter the password to ' + (keyProtected ? 'decrypt' : 'encrypt') + ' the private key with', type: 'password', validate: async (input) => { try { encryption = new aes_encryption_1.default(input, iv); if (keyProtected) { // We need to check that key decoded userWallet = new wallet_1.Wallet(encryption.decrypt(currentConfigFile.user.credentials.privateKey)); } else { privateKey = encryption.encrypt(privateKey || ''); } return true; } catch (error) { this.debug(error); return 'Input is not a valid password'; } }, }, ]); userConfigTemplate.user.credentials.iv = iv; userConfigTemplate.user.credentials.privateKey = privateKey; userConfigTemplate.user.credentials.address = userWallet?.address; } // Save config object try { await fs.outputJSON(configPath, userConfigTemplate, { spaces: 2 }); } catch (error) { this.log(`Failed to save file in ${configPath}. Please enable debugger and try again.`); this.debug(error); } this.exit(); } /** * Load the config file from the path provided by the user */ async loadConfigPath(configPath, filePath) { // Check if config Dir flag is empty if (filePath !== undefined) { try { const stats = fs.lstatSync(filePath); this.debug(`Is file: ${stats.isFile()}`); this.debug(`Is directory: ${stats.isDirectory()}`); this.debug(`Is symbolic link: ${stats.isSymbolicLink()}`); this.debug(`Is FIFO: ${stats.isFIFO()}`); this.debug(`Is socket: ${stats.isSocket()}`); this.debug(`Is character device: ${stats.isCharacterDevice()}`); this.debug(`Is block device: ${stats.isBlockDevice()}`); if (stats.isFile() && !stats.isDirectory() && !stats.isSymbolicLink() && !stats.isFIFO() && !stats.isSocket() && !stats.isCharacterDevice() && !stats.isBlockDevice()) { const ensureCheck = await (0, config_1.ensureConfigFileIsValid)(filePath, undefined, false); // Since the json at the desired path is valid, we save it! await fs.outputJSON(configPath, ensureCheck.configFile, { spaces: 2 }); } else { this.error(`filePath is NOT VALID FAIL`); } } catch (error) { // Handle error if (error.code === 'ENOENT') { this.error(`The input ${filePath} is not a valid file path`); // eslint-disable-next-line no-negated-condition } else if (typeof error.message !== 'undefined') { this.error(error.message); } else { this.error(`Failed to load ${filePath}`); } } this.exit(); } } /** * Load the config file from the JSON provided by the user */ async loadConfigJson(configPath, jsonString) { // Check if config Json flag is empty if (jsonString !== undefined) { this.log(`checking jsonString input`); const output = JSON.parse(jsonString); await (0, config_1.validateBeta3Schema)(output); this.log(output); // Since the json at the desired path is valid, we save it! await fs.outputJSON(configPath, output, { spaces: 2 }); this.exit(); } } } exports.default = Config;