From e62be74a103672f005bc89781a047c57fb02b5e7 Mon Sep 17 00:00:00 2001 From: Denis Davidyuk Date: Tue, 5 Jul 2022 12:34:13 +0300 Subject: [PATCH] feat: don't print stack traces for cli errors --- src/actions/account.js | 8 ++++---- src/actions/aens.js | 23 ++++++++++++----------- src/actions/chain.js | 3 ++- src/actions/inspect.js | 3 ++- src/actions/oracle.js | 3 ++- src/aecli-account.js | 5 +++-- src/aecli-chain.js | 5 +++-- src/aecli-contract.js | 5 +++-- src/aecli-crypto.js | 5 +++-- src/aecli-inspect.js | 5 +++-- src/aecli-name.js | 5 +++-- src/aecli-oracle.js | 5 +++-- src/aecli-tx.js | 5 +++-- src/aecli.js | 3 ++- src/commands/contract.js | 3 ++- src/commands/crypto.js | 3 ++- src/utils/CliError.js | 15 +++++++++++++++ src/utils/account.js | 3 ++- src/utils/helpers.js | 19 ++++++++++--------- 19 files changed, 79 insertions(+), 47 deletions(-) create mode 100644 src/utils/CliError.js diff --git a/src/actions/account.js b/src/actions/account.js index 7c70415b..385f0459 100644 --- a/src/actions/account.js +++ b/src/actions/account.js @@ -19,11 +19,11 @@ */ import { generateKeyPair, AE_AMOUNT_FORMATS } from '@aeternity/aepp-sdk'; - +import CliError from '../utils/CliError'; import { writeWallet } from '../utils/account'; import { initSdkByWalletFile, getAccountByWalletFile } from '../utils/cli'; import { print, printTransaction, printUnderscored } from '../utils/print'; -import { readFile } from '../utils/helpers'; +import { decode, readFile } from '../utils/helpers'; import { PROMPT_TYPE, prompt } from '../utils/prompt'; // ## `Sign message` function @@ -74,7 +74,7 @@ export async function verifyMessage(walletPath, hexSignature, data = [], options export async function sign(walletPath, tx, options) { const { json } = options; // Validate `tx` hash - if (tx.slice(0, 2) !== 'tx') { throw new Error('Invalid transaction hash'); } + decode(tx, 'tx'); const { account } = await getAccountByWalletFile(walletPath, options); @@ -232,7 +232,7 @@ export async function createSecureWalletByPrivKey(walletPath, secretKey, { // This function allow you to generate `keypair` from `private-key` and write it to secure `ethereum` like key-file export async function generateKeyPairs(count = 1, { forcePrompt, json }) { if (!Number.isInteger(+count)) { - throw new Error('Count must be an Number'); + throw new CliError('Count must be an Number'); } if (forcePrompt || await prompt(PROMPT_TYPE.confirm, { message: 'Are you sure you want print your secret key?' })) { const accounts = Array.from(Array(parseInt(count))).map(() => generateKeyPair(false)); diff --git a/src/actions/aens.js b/src/actions/aens.js index 5f7fc08e..9ca6b23f 100644 --- a/src/actions/aens.js +++ b/src/actions/aens.js @@ -22,6 +22,7 @@ import { isAddressValid, getDefaultPointerKey } from '@aeternity/aepp-sdk'; import { initSdk, initSdkByWalletFile } from '../utils/cli'; import { print, printName, printTransaction } from '../utils/print'; import { isAvailable, updateNameStatus, validateName } from '../utils/helpers'; +import CliError from '../utils/CliError'; // ## Claim `name` function export async function preClaim(walletPath, domain, options) { @@ -37,7 +38,7 @@ export async function preClaim(walletPath, domain, options) { // Check if that `name' available const name = await updateNameStatus(domain, sdk); if (!isAvailable(name)) { - throw new Error('Domain not available'); + throw new CliError('Domain not available'); } // Create `pre-claim` transaction const preClaimTx = await sdk.aensPreclaim(domain, { @@ -66,7 +67,7 @@ export async function claim(walletPath, domain, salt, options) { // Check if that `name' available const name = await updateNameStatus(domain, sdk); if (!isAvailable(name)) { - throw new Error('Domain not available'); + throw new CliError('Domain not available'); } // Wait for next block and create `claimName` transaction @@ -91,7 +92,7 @@ export async function updateName(walletPath, domain, addresses, options) { // Validate `address` const invalidAddresses = addresses.filter((address) => !isAddressValid(address)); - if (invalidAddresses.length) throw new Error(`Addresses "[${invalidAddresses}]" is not valid`); + if (invalidAddresses.length) throw new CliError(`Addresses "[${invalidAddresses}]" is not valid`); // Validate `name` validateName(domain); const sdk = await initSdkByWalletFile(walletPath, options); @@ -99,7 +100,7 @@ export async function updateName(walletPath, domain, addresses, options) { // Check if that `name` is unavailable and we can update it const name = await updateNameStatus(domain, sdk); if (isAvailable(name)) { - throw new Error(`Domain is ${name.status} and cannot be updated`); + throw new CliError(`Domain is ${name.status} and cannot be updated`); } // Create `updateName` transaction @@ -133,7 +134,7 @@ export async function extendName(walletPath, domain, nameTtl, options) { // Check if that `name` is unavailable and we can update it const name = await updateNameStatus(domain, sdk); if (isAvailable(name)) { - throw new Error(`Domain is ${name.status} and cannot be extended`); + throw new CliError(`Domain is ${name.status} and cannot be extended`); } // Create `updateName` transaction @@ -157,7 +158,7 @@ export async function transferName(walletPath, domain, address, options) { } = options; // Validate `address` - if (!isAddressValid(address)) throw new Error(`Address "${address}" is not valid`); + if (!isAddressValid(address)) throw new CliError(`Address "${address}" is not valid`); // Validate `name` validateName(domain); const sdk = await initSdkByWalletFile(walletPath, options); @@ -165,7 +166,7 @@ export async function transferName(walletPath, domain, address, options) { // Check if that `name` is unavailable and we can transfer it const name = await updateNameStatus(domain, sdk); if (isAvailable(name)) { - throw new Error('Domain is available, nothing to transfer'); + throw new CliError('Domain is available, nothing to transfer'); } // Create `transferName` transaction @@ -195,7 +196,7 @@ export async function revokeName(walletPath, domain, options) { // Check if `name` is unavailable and we can revoke it const name = await updateNameStatus(domain, sdk); if (isAvailable(name)) { - throw new Error('Domain is available, nothing to revoke'); + throw new CliError('Domain is available, nothing to revoke'); } // Create `revokeName` transaction @@ -224,7 +225,7 @@ export async function nameBid(walletPath, domain, nameFee, options) { // Check if that `name' available const name = await updateNameStatus(domain, sdk); if (!isAvailable(name)) { - throw new Error('Auction do not start or already end'); + throw new CliError('Auction do not start or already end'); } // Wait for next block and create `claimName` transaction @@ -246,14 +247,14 @@ export async function fullClaim(walletPath, domain, options) { ttl, fee, nonce, nameFee, json, nameTtl, clientTtl, } = options; validateName(domain); - if (domain.split('.')[0] < 13) throw new Error('Full name claiming works only with name longer then 12 symbol (not trigger auction)'); + if (domain.split('.')[0] < 13) throw new CliError('Full name claiming works only with name longer then 12 symbol (not trigger auction)'); const sdk = await initSdkByWalletFile(walletPath, options); // Check if that `name' available const name = await updateNameStatus(domain, sdk); if (!isAvailable(name)) { - throw new Error('Domain not available'); + throw new CliError('Domain not available'); } // Wait for next block and create `claimName` transaction diff --git a/src/actions/chain.js b/src/actions/chain.js index 97f6433c..ad476399 100644 --- a/src/actions/chain.js +++ b/src/actions/chain.js @@ -23,6 +23,7 @@ import { printBlock, print, printUnderscored, printTransaction, printValidation, } from '../utils/print'; import { getBlock } from '../utils/helpers'; +import CliError from '../utils/CliError'; // ## Retrieve `node` version export async function version(options) { @@ -119,7 +120,7 @@ export async function play(options) { const topHeader = await sdk.api.getTopHeader(); if (height && height > parseInt(topHeader.height)) { - throw new Error('Height is bigger then height of top block'); + throw new CliError('Height is bigger then height of top block'); } printBlock(topHeader, json); diff --git a/src/actions/inspect.js b/src/actions/inspect.js index e279379f..21b1fe56 100644 --- a/src/actions/inspect.js +++ b/src/actions/inspect.js @@ -33,6 +33,7 @@ import { import { checkPref, getBlock, updateNameStatus, validateName, } from '../utils/helpers'; +import CliError from '../utils/CliError'; // ## Inspect helper function's async function getBlockByHash(hash, options) { @@ -157,7 +158,7 @@ async function getOracle(oracleId, options) { // ## Inspect function // That function get the param(`hash`, `height` or `name`) and show you info about it export default async function inspect(hash, option) { - if (!hash) throw new Error('Hash required'); + if (!hash) throw new CliError('Hash required'); // Get `block` by `height` if (!isNaN(hash)) { diff --git a/src/actions/oracle.js b/src/actions/oracle.js index fabe6fe5..5d70d738 100644 --- a/src/actions/oracle.js +++ b/src/actions/oracle.js @@ -24,6 +24,7 @@ import { decode } from '../utils/helpers'; import { print, printOracle, printQueries, printTransaction, } from '../utils/print'; +import CliError from '../utils/CliError'; // ## Create Oracle export async function createOracle(walletPath, queryFormat, responseFormat, options) { @@ -56,7 +57,7 @@ export async function extendOracle(walletPath, oracleId, oracleTtl, options) { ttl, fee, nonce, waitMined, json, } = options; - if (isNaN(+oracleTtl)) throw new Error('Oracle Ttl should be a number'); + if (isNaN(+oracleTtl)) throw new CliError('Oracle Ttl should be a number'); decode(oracleId, 'ok'); const sdk = await initSdkByWalletFile(walletPath, options); const oracle = await sdk.getOracleObject(oracleId); diff --git a/src/aecli-account.js b/src/aecli-account.js index a0400a8b..0c0af54f 100644 --- a/src/aecli-account.js +++ b/src/aecli-account.js @@ -1,7 +1,7 @@ #!/usr/bin/env node /* * ISC License (ISC) - * Copyright (c) 2021 aeternity developers + * Copyright (c) 2022 aeternity developers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -16,5 +16,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ import program from './commands/account'; +import { runProgram } from './utils/CliError'; -program.parseAsync(); +await runProgram(program); diff --git a/src/aecli-chain.js b/src/aecli-chain.js index 4a3e63c0..cf62b0c3 100755 --- a/src/aecli-chain.js +++ b/src/aecli-chain.js @@ -1,7 +1,7 @@ #!/usr/bin/env node /* * ISC License (ISC) - * Copyright (c) 2021 aeternity developers + * Copyright (c) 2022 aeternity developers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -16,5 +16,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ import program from './commands/chain'; +import { runProgram } from './utils/CliError'; -program.parseAsync(); +await runProgram(program); diff --git a/src/aecli-contract.js b/src/aecli-contract.js index 44ac7f9e..b0a365d9 100644 --- a/src/aecli-contract.js +++ b/src/aecli-contract.js @@ -1,7 +1,7 @@ #!/usr/bin/env node /* * ISC License (ISC) - * Copyright (c) 2021 aeternity developers + * Copyright (c) 2022 aeternity developers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -16,5 +16,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ import program from './commands/contract'; +import { runProgram } from './utils/CliError'; -program.parseAsync(); +await runProgram(program); diff --git a/src/aecli-crypto.js b/src/aecli-crypto.js index b1d6a8ff..48b87824 100644 --- a/src/aecli-crypto.js +++ b/src/aecli-crypto.js @@ -1,7 +1,7 @@ #!/usr/bin/env node /* * ISC License (ISC) - * Copyright (c) 2021 aeternity developers + * Copyright (c) 2022 aeternity developers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -16,5 +16,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ import program from './commands/crypto'; +import { runProgram } from './utils/CliError'; -program.parseAsync(); +await runProgram(program); diff --git a/src/aecli-inspect.js b/src/aecli-inspect.js index f17ba15e..cf877282 100644 --- a/src/aecli-inspect.js +++ b/src/aecli-inspect.js @@ -1,7 +1,7 @@ #!/usr/bin/env node /* * ISC License (ISC) - * Copyright (c) 2021 aeternity developers + * Copyright (c) 2022 aeternity developers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -16,5 +16,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ import program from './commands/inspect'; +import { runProgram } from './utils/CliError'; -program.parseAsync(); +await runProgram(program); diff --git a/src/aecli-name.js b/src/aecli-name.js index 1ad84f6d..f7d7deea 100644 --- a/src/aecli-name.js +++ b/src/aecli-name.js @@ -1,7 +1,7 @@ #!/usr/bin/env node /* * ISC License (ISC) - * Copyright (c) 2021 aeternity developers + * Copyright (c) 2022 aeternity developers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -16,5 +16,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ import program from './commands/name'; +import { runProgram } from './utils/CliError'; -program.parseAsync(); +await runProgram(program); diff --git a/src/aecli-oracle.js b/src/aecli-oracle.js index dd64a6ea..11d2d7da 100644 --- a/src/aecli-oracle.js +++ b/src/aecli-oracle.js @@ -1,7 +1,7 @@ #!/usr/bin/env node /* * ISC License (ISC) - * Copyright (c) 2021 aeternity developers + * Copyright (c) 2022 aeternity developers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -16,5 +16,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ import program from './commands/oracle'; +import { runProgram } from './utils/CliError'; -program.parseAsync(); +await runProgram(program); diff --git a/src/aecli-tx.js b/src/aecli-tx.js index 0c7023f3..8e42c1bf 100644 --- a/src/aecli-tx.js +++ b/src/aecli-tx.js @@ -1,7 +1,7 @@ #!/usr/bin/env node /* * ISC License (ISC) - * Copyright (c) 2021 aeternity developers + * Copyright (c) 2022 aeternity developers * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -16,5 +16,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ import program from './commands/tx'; +import { runProgram } from './utils/CliError'; -program.parseAsync(); +await runProgram(program); diff --git a/src/aecli.js b/src/aecli.js index 4134efe2..ebfd5965 100755 --- a/src/aecli.js +++ b/src/aecli.js @@ -16,5 +16,6 @@ * PERFORMANCE OF THIS SOFTWARE. */ import program from './commands/main'; +import { runProgram } from './utils/CliError'; -program.parseAsync(); +await runProgram(program); diff --git a/src/commands/contract.js b/src/commands/contract.js index 98a5b569..c004761e 100644 --- a/src/commands/contract.js +++ b/src/commands/contract.js @@ -22,13 +22,14 @@ import { Argument, Option, Command } from 'commander'; import { TX_TTL, MIN_GAS_PRICE } from '@aeternity/aepp-sdk'; import { COMPILER_URL } from '../utils/constant'; import { getCmdFromArguments } from '../utils/cli'; +import CliError from '../utils/CliError'; import * as Contract from '../actions/contract'; import { nodeOption, jsonOption, gasOption } from '../arguments'; const callArgs = new Argument('[args]', 'JSON-encoded arguments array of contract call') .argParser((argsText) => { const args = JSON.parse(argsText); - if (!Array.isArray(args)) throw new Error(`Call arguments should be an array, got ${argsText} instead`); + if (!Array.isArray(args)) throw new CliError(`Call arguments should be an array, got ${argsText} instead`); return args; }) .default([]); diff --git a/src/commands/crypto.js b/src/commands/crypto.js index bfcebaf3..a9de3cf0 100644 --- a/src/commands/crypto.js +++ b/src/commands/crypto.js @@ -21,6 +21,7 @@ import { decryptKey, sign, buildTx, unpackTx, decode, TX_TYPE, } from '@aeternity/aepp-sdk'; import { print } from '../utils/print'; +import CliError from '../utils/CliError'; const program = new Command().name('aecli crypto'); @@ -47,7 +48,7 @@ program const binaryKey = (() => { if (file) return fs.readFileSync(file); if (privKey) return Buffer.from(privKey, 'hex'); - throw new Error('Must provide either [privkey] or [file]'); + throw new CliError('Must provide either [privkey] or [file]'); })(); const decryptedKey = password ? decryptKey(password, binaryKey) : binaryKey; const encodedTx = decode(tx, 'tx'); diff --git a/src/utils/CliError.js b/src/utils/CliError.js new file mode 100644 index 00000000..5e4b8781 --- /dev/null +++ b/src/utils/CliError.js @@ -0,0 +1,15 @@ +export default class CliError extends Error { + constructor(message) { + super(message); + this.name = 'CliError'; + } +} + +export async function runProgram(program) { + try { + await program.parseAsync(); + } catch (error) { + if (error instanceof CliError) program.error(error.message); + throw error; + } +} diff --git a/src/utils/account.js b/src/utils/account.js index 33ae73bb..f287d85c 100644 --- a/src/utils/account.js +++ b/src/utils/account.js @@ -23,11 +23,12 @@ import { } from '@aeternity/aepp-sdk'; import { readJSONFile } from './helpers'; import { PROMPT_TYPE, prompt } from './prompt'; +import CliError from './CliError'; export async function writeWallet(name, secretKey, output, password, overwrite) { const walletPath = path.resolve(process.cwd(), path.join(output, name)); if (!overwrite && fs.existsSync(walletPath) && !await prompt(PROMPT_TYPE.askOverwrite)) { - throw new Error(`Wallet already exist at ${walletPath}`); + throw new CliError(`Wallet already exist at ${walletPath}`); } password ||= await prompt(PROMPT_TYPE.askPassword); fs.writeFileSync(walletPath, JSON.stringify(await dump(name, password, secretKey))); diff --git a/src/utils/helpers.js b/src/utils/helpers.js index 964a27af..c7ea58b3 100644 --- a/src/utils/helpers.js +++ b/src/utils/helpers.js @@ -20,6 +20,7 @@ import fs from 'fs'; import { decode as _decode } from '@aeternity/aepp-sdk'; import { HASH_TYPES } from './constant'; +import CliError from './CliError'; export function readFile(filePath, encoding = null) { try { @@ -30,7 +31,7 @@ export function readFile(filePath, encoding = null) { } catch (e) { switch (e.code) { case 'ENOENT': - throw new Error('File not found'); + throw new CliError('File not found'); default: throw e; } @@ -56,21 +57,21 @@ export async function getBlock(hash, sdk) { ...await sdk.api.getMicroBlockTransactionsByHash(hash), }; default: - throw new Error(`Unknown block hash type: ${type}`); + throw new CliError(`Unknown block hash type: ${type}`); } } // ## Method which validate `hash` export function checkPref(hash, hashType) { if (hash.length < 3 || hash.indexOf('_') === -1) { - throw new Error('Invalid input, likely you forgot to escape the $ sign (use \\_)'); + throw new CliError('Invalid input, likely you forgot to escape the $ sign (use \\_)'); } /* block and micro block check */ if (Array.isArray(hashType)) { const res = hashType.find((ht) => hash.slice(0, 3) === `${ht}_`); if (res) return; - throw new Error('Invalid block hash, it should be like: mh_.... or kh._...'); + throw new CliError('Invalid block hash, it should be like: mh_.... or kh._...'); } if (hash.slice(0, 3) !== `${hashType}_`) { @@ -78,7 +79,7 @@ export function checkPref(hash, hashType) { [HASH_TYPES.transaction]: 'Invalid transaction hash, it should be like: th_....', [HASH_TYPES.account]: 'Invalid account address, it should be like: ak_....', }[hashType] || `Invalid hash, it should be like: ${hashType}_....`; - throw new Error(msg); + throw new CliError(msg); } } @@ -101,15 +102,15 @@ export function isAvailable(name) { return name.status === 'AVAILABLE'; } // Validate `name` export function validateName(name) { - if (typeof name !== 'string') throw new Error('Name must be a string'); - if (!name.endsWith('.chain')) throw new Error(`Name should end with .chain: ${name}`); + if (typeof name !== 'string') throw new CliError('Name must be a string'); + if (!name.endsWith('.chain')) throw new CliError(`Name should end with .chain: ${name}`); } export function decode(data, requiredPrefix) { - if (typeof data !== 'string') throw new Error('Data must be a string'); + if (typeof data !== 'string') throw new CliError('Data must be a string'); const prefix = data.split('_')[0]; if (prefix !== requiredPrefix) { - throw new Error(`Encoded string have a wrong type: ${prefix} (expected: ${requiredPrefix})`); + throw new CliError(`Encoded string have a wrong type: ${prefix} (expected: ${requiredPrefix})`); } return _decode(data); }