From 3bd7853e8d1707abf2654b7535523f268d16a4a0 Mon Sep 17 00:00:00 2001 From: Bart Huijgen Date: Fri, 21 Jun 2024 11:25:01 +0200 Subject: [PATCH] feat(kadena-cli): refactor account code to a service --- .../src/commands/account/accountOptions.ts | 6 +- .../account/commands/accountAddFromKey.ts | 16 +-- .../account/commands/accountAddFromWallet.ts | 26 ++-- .../account/commands/accountDelete.ts | 82 +++--------- .../commands/account/commands/accountFund.ts | 12 +- .../commands/account/commands/accountList.ts | 54 +++----- .../commands/account/tests/accountAdd.test.ts | 5 + .../account/tests/accountDelete.test.ts | 8 +- .../account/tests/accountList.test.ts | 14 +- .../utils/__tests__/accountHelpers.test.ts | 25 ++-- .../utils/__tests__/addAccount.test.ts | 75 ----------- .../utils/__tests__/addHelpers.test.ts | 24 ---- .../__tests__/createAccountConfigFile.test.ts | 86 ------------ .../commands/account/utils/accountHelpers.ts | 95 +------------- .../src/commands/account/utils/addAccount.ts | 24 ---- .../src/commands/account/utils/addHelpers.ts | 27 +--- .../account/utils/createAccountConfigFile.ts | 39 ------ .../commands/config/commands/configInit.ts | 26 +--- .../commands/config/commands/configShow.ts | 5 - .../commands/config/tests/configShow.test.ts | 6 +- .../networks/commands/networkCreate.ts | 4 - .../networks/commands/networkDefault.ts | 12 +- .../networks/commands/networkManage.ts | 4 - .../commands/networks/utils/networkDisplay.ts | 4 - .../commands/networks/utils/networkHelpers.ts | 13 -- .../commands/networks/utils/networkPath.ts | 21 ++- .../tx/commands/templates/templates.ts | 10 -- .../tx/commands/txCreateTransaction.ts | 6 +- .../src/commands/tx/commands/txHistory.ts | 3 - .../src/commands/tx/utils/txHelpers.ts | 15 +-- .../commands/wallets/commands/walletAdd.ts | 27 ++-- .../commands/wallets/utils/walletHelpers.ts | 32 ++--- .../tools/kadena-cli/src/prompts/network.ts | 5 +- packages/tools/kadena-cli/src/prompts/tx.ts | 7 +- .../src/services/account/account.schemas.ts | 9 ++ .../src/services/account/account.service.ts | 55 ++++++++ .../src/services/account/account.types.ts | 21 +++ .../src/services/config/config.service.ts | 122 ++++++++++++++++-- .../tools/kadena-cli/src/services/index.ts | 4 + .../src/services/wallet/wallet.service.ts | 2 +- .../kadena-cli/src/utils/globalHelpers.ts | 7 +- .../tools/kadena-cli/src/utils/helpers.ts | 14 +- 42 files changed, 368 insertions(+), 684 deletions(-) delete mode 100644 packages/tools/kadena-cli/src/commands/account/utils/__tests__/addAccount.test.ts delete mode 100644 packages/tools/kadena-cli/src/commands/account/utils/__tests__/addHelpers.test.ts delete mode 100644 packages/tools/kadena-cli/src/commands/account/utils/__tests__/createAccountConfigFile.test.ts delete mode 100644 packages/tools/kadena-cli/src/commands/account/utils/addAccount.ts delete mode 100644 packages/tools/kadena-cli/src/commands/account/utils/createAccountConfigFile.ts create mode 100644 packages/tools/kadena-cli/src/services/account/account.schemas.ts create mode 100644 packages/tools/kadena-cli/src/services/account/account.service.ts create mode 100644 packages/tools/kadena-cli/src/services/account/account.types.ts diff --git a/packages/tools/kadena-cli/src/commands/account/accountOptions.ts b/packages/tools/kadena-cli/src/commands/account/accountOptions.ts index 92539da078..e3b5724d8a 100644 --- a/packages/tools/kadena-cli/src/commands/account/accountOptions.ts +++ b/packages/tools/kadena-cli/src/commands/account/accountOptions.ts @@ -4,11 +4,13 @@ import { z } from 'zod'; import { CHAIN_ID_RANGE_ERROR_MESSAGE } from '../../constants/account.js'; import { actionAskForDeployFaucet } from '../../prompts/genericActionPrompts.js'; import { account } from '../../prompts/index.js'; +import { services } from '../../services/index.js'; import { createOption } from '../../utils/createOption.js'; import { formatZodError, generateAllChainIds, } from '../../utils/globalHelpers.js'; +import { isEmpty } from '../../utils/helpers.js'; import { log } from '../../utils/logger.js'; import type { IAliasAccountData } from './types.js'; import { @@ -17,9 +19,7 @@ import { formatZodFieldErrors, isValidMaxAccountFundParams, parseChainIdRange, - readAccountFromFile, } from './utils/accountHelpers.js'; -import { isEmpty } from './utils/addHelpers.js'; export const accountOptions = { accountFromSelection: createOption({ @@ -83,7 +83,7 @@ export const accountOptions = { option: new Option('-a, --account ', 'Account alias name'), expand: async (accountAlias: string): Promise => { try { - const accountDetails = await readAccountFromFile(accountAlias); + const accountDetails = await services.account.getByAlias(accountAlias); return accountDetails; } catch (error) { if (error.message.includes('file not exist') === true) { diff --git a/packages/tools/kadena-cli/src/commands/account/commands/accountAddFromKey.ts b/packages/tools/kadena-cli/src/commands/account/commands/accountAddFromKey.ts index d6f356e084..35694da08f 100644 --- a/packages/tools/kadena-cli/src/commands/account/commands/accountAddFromKey.ts +++ b/packages/tools/kadena-cli/src/commands/account/commands/accountAddFromKey.ts @@ -1,14 +1,13 @@ import type { ChainId } from '@kadena/types'; import { KEYS_ALL_PRED_ERROR_MESSAGE } from '../../../constants/account.js'; import { networkDefaults } from '../../../constants/networks.js'; -import { assertCommandError } from '../../../utils/command.util.js'; +import { services } from '../../../services/index.js'; import type { CommandOption } from '../../../utils/createCommand.js'; import { isNotEmptyString } from '../../../utils/globalHelpers.js'; import { log } from '../../../utils/logger.js'; import type { options } from '../accountAddOptions.js'; import type { IAccountDetailsResult, Predicate } from '../types.js'; import { isValidForOnlyKeysAllPredicate } from '../utils/accountHelpers.js'; -import { addAccount } from '../utils/addAccount.js'; import { displayAddAccountSuccess } from '../utils/addHelpers.js'; import { createAccountName } from '../utils/createAccountName.js'; import { getAccountDetails } from '../utils/getAccountDetails.js'; @@ -112,14 +111,13 @@ export const addAccountFromKey = async ( : predicate, }; - const result = await addAccount({ - accountAlias, - accountName, + const result = await services.account.create({ + alias: accountAlias, + name: accountName, fungible, - ...accountGuard, + predicate, + publicKeys: accountGuard.publicKeysConfig, }); - assertCommandError(result); - - displayAddAccountSuccess(accountAlias, result.data); + displayAddAccountSuccess(result); }; diff --git a/packages/tools/kadena-cli/src/commands/account/commands/accountAddFromWallet.ts b/packages/tools/kadena-cli/src/commands/account/commands/accountAddFromWallet.ts index 29d8b91592..771fa6925c 100644 --- a/packages/tools/kadena-cli/src/commands/account/commands/accountAddFromWallet.ts +++ b/packages/tools/kadena-cli/src/commands/account/commands/accountAddFromWallet.ts @@ -2,16 +2,12 @@ import type { ChainId } from '@kadena/client'; import { networkDefaults } from '../../../constants/networks.js'; import { services } from '../../../services/index.js'; import type { IWallet } from '../../../services/wallet/wallet.types.js'; -import { - CommandError, - assertCommandError, -} from '../../../utils/command.util.js'; +import { CommandError } from '../../../utils/command.util.js'; import type { CommandOption } from '../../../utils/createCommand.js'; import { notEmpty } from '../../../utils/globalHelpers.js'; import { log } from '../../../utils/logger.js'; import { findFreeIndexes } from '../../wallets/utils/walletHelpers.js'; import type { options } from '../accountAddOptions.js'; -import { addAccount } from '../utils/addAccount.js'; import { displayAddAccountSuccess } from '../utils/addHelpers.js'; import { createAccountName } from '../utils/createAccountName.js'; @@ -122,22 +118,18 @@ export const addAccountFromWallet = async ( networkConfig: networkDefaults.testnet, }); - const addAccountConfig = { - accountName, - accountAlias, - fungible, - publicKeysConfig: filteredPublicKeys, - predicate, - }; - log.debug('create-account-add-from-wallet:action', { ...config, accountName, }); - const result = await addAccount(addAccountConfig); - - assertCommandError(result); + const result = await services.account.create({ + alias: accountAlias, + name: accountName, + fungible: fungible, + publicKeys: filteredPublicKeys, + predicate, + }); - displayAddAccountSuccess(config.accountAlias, result.data); + displayAddAccountSuccess(result); }; diff --git a/packages/tools/kadena-cli/src/commands/account/commands/accountDelete.ts b/packages/tools/kadena-cli/src/commands/account/commands/accountDelete.ts index 3de625fc73..a005a6b110 100644 --- a/packages/tools/kadena-cli/src/commands/account/commands/accountDelete.ts +++ b/packages/tools/kadena-cli/src/commands/account/commands/accountDelete.ts @@ -1,64 +1,8 @@ -import { join } from 'path'; -import { NO_ACCOUNTS_FOUND_ERROR_MESSAGE } from '../../../constants/account.js'; import { services } from '../../../services/index.js'; -import { KadenaError } from '../../../services/service-error.js'; -import type { CommandResult } from '../../../utils/command.util.js'; -import { assertCommandError } from '../../../utils/command.util.js'; import { createCommand } from '../../../utils/createCommand.js'; import { isNotEmptyString } from '../../../utils/globalHelpers.js'; import { log } from '../../../utils/logger.js'; import { accountOptions } from '../accountOptions.js'; -import { - ensureAccountAliasFilesExists, - getAccountDirectory, -} from '../utils/accountHelpers.js'; - -async function deleteAccountDir(): Promise> { - const accountDir = getAccountDirectory(); - if (accountDir === null) { - throw new KadenaError('no_kadena_directory'); - } - - try { - await services.filesystem.deleteDirectory(accountDir); - return { - data: null, - status: 'success', - }; - } catch (error) { - return { - status: 'error', - errors: ['Failed to delete all account aliases.'], - }; - } -} - -async function removeAccount( - accountAlias: string, -): Promise> { - const accountDir = getAccountDirectory(); - if (accountDir === null) { - throw new KadenaError('no_kadena_directory'); - } - - if (accountAlias === 'all') { - return await deleteAccountDir(); - } - - const filePath = join(accountDir, `${accountAlias}.yaml`); - if (await services.filesystem.fileExists(filePath)) { - await services.filesystem.deleteFile(filePath); - return { - data: null, - status: 'success', - }; - } else { - return { - status: 'error', - errors: [`The account alias "${accountAlias}" does not exist`], - }; - } -} export const createAccountDeleteCommand = createCommand( 'delete', @@ -68,11 +12,6 @@ export const createAccountDeleteCommand = createCommand( accountOptions.accountDeleteConfirmation({ isOptional: false }), ], async (option) => { - const isAccountAliasesExist = await ensureAccountAliasFilesExists(); - if (!isAccountAliasesExist) { - return log.error(NO_ACCOUNTS_FOUND_ERROR_MESSAGE); - } - const { accountAlias } = await option.accountAlias(); if (!isNotEmptyString(accountAlias.trim())) { @@ -92,8 +31,25 @@ export const createAccountDeleteCommand = createCommand( return log.warning('The account alias will not be deleted.'); } - const result = await removeAccount(accountAlias); - assertCommandError(result); + if (accountAlias === 'all') { + const accounts = await services.account.list(); + if (accounts.length === 0) { + return log.error( + 'No account aliases found. To add an account use `kadena account add` command.', + ); + } + for (const account of accounts) { + await services.account.delete(account.filepath); + } + } else { + const account = await services.account.getByAlias(accountAlias); + if (!account) { + return log.error(`Account "${accountAlias}" not found`); + } + + await services.account.delete(account.filepath); + } + const deleteSuccessMsg = accountAlias === 'all' ? 'All account aliases has been deleted' diff --git a/packages/tools/kadena-cli/src/commands/account/commands/accountFund.ts b/packages/tools/kadena-cli/src/commands/account/commands/accountFund.ts index b090d53f92..b3e9cbc4dd 100644 --- a/packages/tools/kadena-cli/src/commands/account/commands/accountFund.ts +++ b/packages/tools/kadena-cli/src/commands/account/commands/accountFund.ts @@ -2,7 +2,6 @@ import ora from 'ora'; import { CHAIN_ID_ACTION_ERROR_MESSAGE, MAX_FUND_AMOUNT, - NO_ACCOUNTS_FOUND_ERROR_MESSAGE, } from '../../../constants/account.js'; import { FAUCET_MODULE_NAME } from '../../../constants/devnets.js'; import { assertCommandError } from '../../../utils/command.util.js'; @@ -12,10 +11,7 @@ import { globalOptions } from '../../../utils/globalOptions.js'; import { log } from '../../../utils/logger.js'; import { checkHealth } from '../../devnet/utils/network.js'; import { accountOptions } from '../accountOptions.js'; -import { - ensureAccountAliasFilesExists, - sortChainIds, -} from '../utils/accountHelpers.js'; +import { sortChainIds } from '../utils/accountHelpers.js'; import { fund } from '../utils/fund.js'; import { deployFaucetsToChains, @@ -36,12 +32,6 @@ export const createAccountFundCommand = createCommand( accountOptions.deployFaucet(), ], async (option) => { - const isAccountAliasesExist = await ensureAccountAliasFilesExists(); - - if (!isAccountAliasesExist) { - return log.error(NO_ACCOUNTS_FOUND_ERROR_MESSAGE); - } - const { account, accountConfig } = await option.account(); const { network, networkConfig } = await option.network({ allowedNetworkIds: ['testnet', 'development'], diff --git a/packages/tools/kadena-cli/src/commands/account/commands/accountList.ts b/packages/tools/kadena-cli/src/commands/account/commands/accountList.ts index 6666e92e35..ec7c1faca1 100644 --- a/packages/tools/kadena-cli/src/commands/account/commands/accountList.ts +++ b/packages/tools/kadena-cli/src/commands/account/commands/accountList.ts @@ -2,17 +2,13 @@ import type { Table } from 'cli-table3'; import type { Command } from 'commander'; import { parse } from 'node:path'; import { NO_ACCOUNTS_FOUND_ERROR_MESSAGE } from '../../../constants/account.js'; +import { services } from '../../../services/index.js'; import { createCommand } from '../../../utils/createCommand.js'; import { isNotEmptyString } from '../../../utils/globalHelpers.js'; import { log } from '../../../utils/logger.js'; import { createTable } from '../../../utils/table.js'; import { accountOptions } from '../accountOptions.js'; import type { IAliasAccountData } from '../types.js'; -import { - ensureAccountAliasFilesExists, - getAllAccounts, - readAccountFromFile, -} from '../utils/accountHelpers.js'; function generateTabularData(accounts: IAliasAccountData[]): Table { const table = createTable({}); @@ -41,21 +37,6 @@ function generateTabularData(accounts: IAliasAccountData[]): Table { return table; } -async function accountList( - accountAlias: string, -): Promise { - try { - if (accountAlias === 'all') { - return await getAllAccounts(); - } else { - const account = await readAccountFromFile(accountAlias); - return [account]; - } - } catch (error) { - return; - } -} - export const createAccountListCommand: ( program: Command, version: string, @@ -64,29 +45,32 @@ export const createAccountListCommand: ( 'List all available accounts', [accountOptions.accountSelectWithAll()], async (option) => { - const isAccountAliasesExist = await ensureAccountAliasFilesExists(); - - if (!isAccountAliasesExist) { - return log.warning(NO_ACCOUNTS_FOUND_ERROR_MESSAGE); - } - const { accountAlias } = await option.accountAlias(); - log.debug('account-list:action', accountAlias); + log.debug('account-list:action', { accountAlias }); if (!isNotEmptyString(accountAlias)) { return log.error('No account alias is selected'); } - const accountsDetails = await accountList(accountAlias); + if (accountAlias === 'all') { + const accountsDetails = await services.account.list(); - if (!accountsDetails || accountsDetails.length === 0) { - return log.error(`Selected account alias "${accountAlias}" not found.`); - } + if (accountsDetails.length === 0) { + return log.warning(NO_ACCOUNTS_FOUND_ERROR_MESSAGE); + } - const data = generateTabularData(accountsDetails); - const accountsListJSONOutput = - accountsDetails.length === 1 ? accountsDetails[0] : accountsDetails; - log.output(data.toString(), accountsListJSONOutput); + const data = generateTabularData(accountsDetails); + log.output(data.toString(), accountsDetails); + } else { + const accountsDetails = await services.account.getByAlias(accountAlias); + + if (!accountsDetails) { + return log.error(`Selected account alias "${accountAlias}" not found.`); + } + + const data = generateTabularData([accountsDetails]); + log.output(data.toString(), accountsDetails); + } }, ); diff --git a/packages/tools/kadena-cli/src/commands/account/tests/accountAdd.test.ts b/packages/tools/kadena-cli/src/commands/account/tests/accountAdd.test.ts index a7538289eb..d803eb4b62 100644 --- a/packages/tools/kadena-cli/src/commands/account/tests/accountAdd.test.ts +++ b/packages/tools/kadena-cli/src/commands/account/tests/accountAdd.test.ts @@ -80,6 +80,7 @@ describe('account add manual type', () => { expect(await services.filesystem.fileExists(aliasFile)).toBe(true); const content = await services.filesystem.readFile(aliasFile); expect(jsYaml.load(content!)).toEqual({ + alias: 'account-add-test-chain', name: 'k:pubkey1', fungible: 'coin', publicKeys: ['publicKey1', 'publicKey2'], @@ -112,6 +113,7 @@ describe('account add manual type', () => { expect(await services.filesystem.fileExists(accountAliasFile)).toBe(true); const content = await services.filesystem.readFile(accountAliasFile); expect(jsYaml.load(content!)).toEqual({ + alias: 'account-add-test', name: 'w:FxlQEvb6qHb50NClEnpwbT2uoJHuAu39GTSwXmASH2k:keys-all', fungible: 'coin', publicKeys: ['pubkey1', 'pubkey2'], @@ -126,6 +128,7 @@ describe('account add manual type', () => { expect(await services.filesystem.fileExists(accountAliasFile)).toBe(true); const content = await services.filesystem.readFile(accountAliasFile); expect(jsYaml.load(content!)).toEqual({ + alias: 'account-add-test', name: 'k:pubkey1', fungible: 'coin', publicKeys: ['publicKey1', 'publicKey2'], @@ -219,6 +222,7 @@ describe('account add type wallet', () => { expect(await services.filesystem.fileExists(accountAliasFile)).toBe(true); const content = await services.filesystem.readFile(accountAliasFile); expect(jsYaml.load(content!)).toEqual({ + alias: 'account-add-test', name: 'w:FxlQEvb6qHb50NClEnpwbT2uoJHuAu39GTSwXmASH2k:keys-all', fungible: 'coin', publicKeys: [publicKey], @@ -251,6 +255,7 @@ describe('account add type wallet', () => { expect(await services.filesystem.fileExists(accountAliasFile)).toBe(true); const content = await services.filesystem.readFile(accountAliasFile); expect(jsYaml.load(content!)).toEqual({ + alias: 'account-add-test', name: 'w:FxlQEvb6qHb50NClEnpwbT2uoJHuAu39GTSwXmASH2k:keys-all', fungible: 'coin', publicKeys: [publicKey, generatedKey], diff --git a/packages/tools/kadena-cli/src/commands/account/tests/accountDelete.test.ts b/packages/tools/kadena-cli/src/commands/account/tests/accountDelete.test.ts index 0b00bd44ba..99f84b79f2 100644 --- a/packages/tools/kadena-cli/src/commands/account/tests/accountDelete.test.ts +++ b/packages/tools/kadena-cli/src/commands/account/tests/accountDelete.test.ts @@ -111,9 +111,7 @@ describe('account delete', () => { const res = await runCommand( 'account delete --account-alias=not-found-alias --confirm', ); - expect(res.stderr).toContain( - 'The account alias "not-found-alias" does not exist', - ); + expect(res.stderr).toContain('Account "not-found-alias" not found'); }); it('should throw invalid account alias when account passes as empty string', async () => { @@ -125,7 +123,9 @@ describe('account delete', () => { // deleting account alias file to make the folder empty await services.filesystem.deleteFile(accountAliasFile); - const res = await runCommand('account delete'); + const res = await runCommand( + 'account delete --account-alias=all --confirm', + ); expect(res.stderr).toContain( 'No account aliases found. To add an account use `kadena account add` command.', ); diff --git a/packages/tools/kadena-cli/src/commands/account/tests/accountList.test.ts b/packages/tools/kadena-cli/src/commands/account/tests/accountList.test.ts index a827b1506f..3a29c8803d 100644 --- a/packages/tools/kadena-cli/src/commands/account/tests/accountList.test.ts +++ b/packages/tools/kadena-cli/src/commands/account/tests/accountList.test.ts @@ -1,4 +1,6 @@ +import path from 'node:path'; import { beforeEach, describe, expect, it } from 'vitest'; +import { ACCOUNT_DIR, CWD_KADENA_DIR } from '../../../constants/config.js'; import { mockPrompts, runCommand, @@ -27,7 +29,8 @@ describe('account list', () => { expect(res).toEqual( expect.arrayContaining([ expect.objectContaining({ - alias: 'account-one.yaml', + alias: 'account-one', + filepath: path.join(CWD_KADENA_DIR, ACCOUNT_DIR, 'account-one.yaml'), fungible: 'coin', name: 'k:55e10019549e047e68efaa18489ed785eca271642e2d0ce41d56ced2a30ccb84', predicate: 'keys-all', @@ -36,7 +39,8 @@ describe('account list', () => { ], }), expect.objectContaining({ - alias: 'account-two.yaml', + alias: 'account-two', + filepath: path.join(CWD_KADENA_DIR, ACCOUNT_DIR, 'account-two.yaml'), fungible: 'coin', name: 'w:yCvUbeS6RqdKsY3WBDB3cgK-6q790xkj4Hb-ABpu3gg:keys-all', predicate: 'keys-all', @@ -59,7 +63,8 @@ describe('account list', () => { const res = await runCommandJson('account list'); expect(res).toEqual( expect.objectContaining({ - alias: 'account-one.yaml', + alias: 'account-one', + filepath: path.join(CWD_KADENA_DIR, ACCOUNT_DIR, 'account-one.yaml'), fungible: 'coin', name: 'k:55e10019549e047e68efaa18489ed785eca271642e2d0ce41d56ced2a30ccb84', predicate: 'keys-all', @@ -76,7 +81,8 @@ describe('account list', () => { ); expect(res).toEqual( expect.objectContaining({ - alias: 'account-two.yaml', + alias: 'account-two', + filepath: path.join(CWD_KADENA_DIR, ACCOUNT_DIR, 'account-two.yaml'), fungible: 'coin', name: 'w:yCvUbeS6RqdKsY3WBDB3cgK-6q790xkj4Hb-ABpu3gg:keys-all', predicate: 'keys-all', diff --git a/packages/tools/kadena-cli/src/commands/account/utils/__tests__/accountHelpers.test.ts b/packages/tools/kadena-cli/src/commands/account/utils/__tests__/accountHelpers.test.ts index 79c90fc1f0..42190baaff 100644 --- a/packages/tools/kadena-cli/src/commands/account/utils/__tests__/accountHelpers.test.ts +++ b/packages/tools/kadena-cli/src/commands/account/utils/__tests__/accountHelpers.test.ts @@ -6,7 +6,6 @@ import { WORKING_DIRECTORY, } from '../../../../constants/config.js'; import { services } from '../../../../services/index.js'; -import { readAccountFromFile } from '../accountHelpers.js'; describe('readAccountFromFile', () => { const configPath = path.join(WORKING_DIRECTORY, '.kadena'); @@ -33,26 +32,30 @@ describe('readAccountFromFile', () => { }); it('should read account data from file and return data with alias', async () => { - const account = await readAccountFromFile('account-add-test'); + const account = await services.account.getByAlias('account-add-test'); expect(account).toEqual({ ...accountData, - alias: 'account-add-test.yaml', + filepath: accountAliasFile, + alias: 'account-add-test', }); }); it('should throw an error when account alias file does not exist', async () => { await expect( - async () => await readAccountFromFile('account-add-test-2'), - ).rejects.toThrowError('Account alias "account-add-test-2" file not exist'); + async () => + await services.account.get( + path.join(accountPath, 'account-add-test2.yaml'), + ), + ).rejects.toThrowError( + 'Account file ".kadena/accounts/account-add-test2.yaml" not found', + ); }); it('should return file is empty error when file contains no content', async () => { await services.filesystem.writeFile(accountAliasFile, ''); await expect( - async () => await readAccountFromFile('account-add-test'), - ).rejects.toThrowError( - 'Error parsing alias file: account-add-test, file is empty', - ); + async () => await services.account.get(accountAliasFile), + ).rejects.toThrowError('Error parsing account file: Can not be empty'); }); it('should throw zod errors when file content is invalid format ', async () => { @@ -64,9 +67,9 @@ describe('readAccountFromFile', () => { }), ); await expect( - async () => await readAccountFromFile('account-add-test'), + async () => await services.account.get(accountAliasFile), ).rejects.toThrowError( - 'Error parsing alias file: account-add-test "publicKeys": expected array, received undefined\n"predicate": expected string, received undefined', + 'Error parsing account file: publicKeys: Required\npredicate: Required', ); }); }); diff --git a/packages/tools/kadena-cli/src/commands/account/utils/__tests__/addAccount.test.ts b/packages/tools/kadena-cli/src/commands/account/utils/__tests__/addAccount.test.ts deleted file mode 100644 index b6cbb30c6f..0000000000 --- a/packages/tools/kadena-cli/src/commands/account/utils/__tests__/addAccount.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -/// -import { afterEach, assert, describe, expect, it } from 'vitest'; - -import { server } from '../../../../mocks/server.js'; -import { services } from '../../../../services/index.js'; -import { addAccount } from '../addAccount.js'; -import { getAccountFilePath } from '../addHelpers.js'; -import { defaultConfigMock } from './mocks.js'; - -describe('addAccount', () => { - afterEach(async () => { - const filePath = getAccountFilePath(defaultConfigMock.accountAlias); - const fs = services.filesystem; - if (await fs.fileExists(filePath)) { - await fs.deleteFile(filePath); - } - server.resetHandlers(); - }); - - it('should write user config to file alias when account details are equal', async () => { - const config = { - ...defaultConfigMock, - publicKeys: 'publicKey1,publicKey2', - publicKeysConfig: ['publicKey1', 'publicKey2'], - accountDetailsFromChain: { - guard: { - keys: ['publicKey1', 'publicKey2'], - pred: 'keys-all', - }, - account: 'accountName', - balance: 0, - }, - }; - const filePath = getAccountFilePath(defaultConfigMock.accountAlias); - const result = await addAccount(config); - - assert(result.status === 'success'); - expect(result.data).toEqual(filePath); - }); - - it('should write user config to file when account details are undefined', async () => { - const config = { - ...defaultConfigMock, - accountName: 'k:3645365457567ghghdghf6534673', - publicKeys: 'publicKey1,publicKey2', - publicKeysConfig: ['publicKey1', 'publicKey2'], - }; - const filePath = getAccountFilePath(defaultConfigMock.accountAlias); - const result = await addAccount(config); - - assert(result.status === 'success'); - expect(result.data).toEqual(filePath); - }); - - it('should return error when file already exists', async () => { - const config = { - ...defaultConfigMock, - accountAlias: 'unit-test-alias', - accountName: 'accountName', - }; - const filePath = getAccountFilePath(config.accountAlias); - const fs = services.filesystem; - await fs.writeFile(filePath, 'test'); - expect(await fs.fileExists(filePath)).toBe(true); - - const result = await addAccount(config); - - assert(result.status === 'error'); - expect(result.errors).toEqual([ - `The account configuration "${filePath}" already exists.`, - ]); - - await fs.deleteFile(filePath); - }); -}); diff --git a/packages/tools/kadena-cli/src/commands/account/utils/__tests__/addHelpers.test.ts b/packages/tools/kadena-cli/src/commands/account/utils/__tests__/addHelpers.test.ts deleted file mode 100644 index 86b9bace20..0000000000 --- a/packages/tools/kadena-cli/src/commands/account/utils/__tests__/addHelpers.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Mock } from 'vitest'; -import { describe, expect, it, vi } from 'vitest'; -import { getAccountDirectory } from '../accountHelpers.js'; -import { getAccountFilePath } from '../addHelpers.js'; - -vi.mock('../accountHelpers.js', () => ({ - getAccountDirectory: vi.fn().mockReturnValue('test'), -})); - -describe('getAccountFilePath', () => { - it('should return the account file path', () => { - const fileName = 'test'; - const result = getAccountFilePath(fileName); - expect(result).toEqual('test/test.yaml'); - }); - - it('should throw an error when accountDirectory is null', () => { - (getAccountDirectory as Mock).mockReturnValue(null); - const fileName = 'test'; - expect(() => getAccountFilePath(fileName)).toThrowError( - 'Kadena directory not found. use `kadena config init` to create one.', - ); - }); -}); diff --git a/packages/tools/kadena-cli/src/commands/account/utils/__tests__/createAccountConfigFile.test.ts b/packages/tools/kadena-cli/src/commands/account/utils/__tests__/createAccountConfigFile.test.ts deleted file mode 100644 index 91502df641..0000000000 --- a/packages/tools/kadena-cli/src/commands/account/utils/__tests__/createAccountConfigFile.test.ts +++ /dev/null @@ -1,86 +0,0 @@ -import yaml from 'js-yaml'; -import path from 'path'; -import { beforeEach, describe, expect, it } from 'vitest'; - -import { ACCOUNT_DIR, CWD_KADENA_DIR } from '../../../../constants/config.js'; -import { services } from '../../../../services/index.js'; -import type { IAccountAliasFileConfig } from '../../types.js'; -import { createAccountConfigFile } from '../createAccountConfigFile.js'; -import { defaultConfigMock } from './mocks.js'; - -describe('createAccountConfigFile', () => { - const accountDir = path.join(CWD_KADENA_DIR, ACCOUNT_DIR); - const accountAlias = 'unit-test-alias'; - const filePath = path.join(accountDir, `${accountAlias}.yaml`); - const fs = services.filesystem; - beforeEach(async () => { - // To start fresh delete the file if it already exists - if (await fs.fileExists(filePath)) { - await fs.deleteFile(filePath); - } - }); - it('should write "config" in config file', async () => { - const config = { - ...defaultConfigMock, - publicKeysConfig: ['key1', 'key2'], - accountName: 'accountName', - accountAlias, - }; - - expect(await fs.fileExists(filePath)).toBe(false); - - await createAccountConfigFile(filePath, config); - - const fileContent = await fs.readFile(filePath); - expect(fileContent).toBe( - yaml.dump({ - name: config.accountName, - fungible: config.fungible, - publicKeys: config.publicKeysConfig.filter((key: string) => !!key), - predicate: config.predicate, - }), - ); - expect(await fs.fileExists(filePath)).toBe(true); - }); - - it('should return false with errors message', async () => { - const config = { - ...defaultConfigMock, - publicKeysConfig: ['key1', 'key2'], - accountName: 'accountName', - accountAlias, - }; - const filePath = path.join(accountDir, `${config.accountAlias}.yaml`); - const fs = services.filesystem; - // Create a file before writing - await fs.writeFile(filePath, 'test'); - expect(await fs.fileExists(filePath)).toBe(true); - - await expect(async () => { - await createAccountConfigFile(filePath, config); - }).rejects.toThrow( - `The account configuration "${filePath}" already exists.`, - ); - }); - - it('should thrown an error when zod validation account alias schema fails', async () => { - const config = { - accountAlias: 'unit-test-alias', - accountName: 'accountName', - predicate: 'keys-all', - publicKeysConfig: [], - }; - const filePath = path.join(accountDir, `${config.accountAlias}.yaml`); - const fs = services.filesystem; - expect(await fs.fileExists(filePath)).toBe(false); - - await expect(async () => { - await createAccountConfigFile( - filePath, - config as unknown as IAccountAliasFileConfig, - ); - }).rejects.toThrow( - `Error writing alias file: ${filePath}\n "fungible": expected string, received undefined\n"publicKeys": Array must contain at least 1 element(s)`, - ); - }); -}); diff --git a/packages/tools/kadena-cli/src/commands/account/utils/accountHelpers.ts b/packages/tools/kadena-cli/src/commands/account/utils/accountHelpers.ts index d24349ed74..264e7da915 100644 --- a/packages/tools/kadena-cli/src/commands/account/utils/accountHelpers.ts +++ b/packages/tools/kadena-cli/src/commands/account/utils/accountHelpers.ts @@ -1,5 +1,3 @@ -import yaml from 'js-yaml'; -import { extname, join } from 'node:path'; import type { ZodError, ZodIssue, @@ -7,22 +5,13 @@ import type { ZodIssueOptionalMessage, } from 'zod'; import { z } from 'zod'; -import type { IAliasAccountData } from '../types.js'; import type { ChainId } from '@kadena/types'; import { MAX_FUND_AMOUNT } from '../../../constants/account.js'; -import { ACCOUNT_DIR, MAX_CHAIN_VALUE } from '../../../constants/config.js'; +import { MAX_CHAIN_VALUE } from '../../../constants/config.js'; import { services } from '../../../services/index.js'; -import { KadenaError } from '../../../services/service-error.js'; import { isNotEmptyString, notEmpty } from '../../../utils/globalHelpers.js'; -export const accountAliasFileSchema = z.object({ - name: z.string(), - fungible: z.string(), - publicKeys: z.array(z.string()).nonempty(), - predicate: z.string(), -}); - export const formatZodErrors = (errors: ZodError): string => { return errors.errors .map((error) => { @@ -38,93 +27,13 @@ export const formatZodErrors = (errors: ZodError): string => { .join('\n'); }; -export const getAccountDirectory = (): string | null => { - const directory = services.config.getDirectory(); - return notEmpty(directory) ? join(directory, ACCOUNT_DIR) : null; -}; - -export const readAccountFromFile = async ( - accountFile: string, -): Promise => { - const accountDir = getAccountDirectory(); - if (accountDir === null) { - throw new KadenaError('no_kadena_directory'); - } - const ext = extname(accountFile); - const fileWithExt = - !ext || ext !== '.yaml' ? `${accountFile}.yaml` : accountFile; - const filePath = join(accountDir, fileWithExt); - - if (!(await services.filesystem.fileExists(filePath))) { - throw new Error(`Account alias "${accountFile}" file not exist`); - } - - const content = await services.filesystem.readFile(filePath); - const account = content !== null ? yaml.load(content) : null; - try { - const parsedContent = accountAliasFileSchema.parse(account); - return { - ...parsedContent, - alias: fileWithExt, - }; - } catch (error) { - if (!isNotEmptyString(content)) { - throw new Error( - `Error parsing alias file: ${accountFile}, file is empty`, - ); - } - - const errorMessage = formatZodErrors(error); - throw new Error(`Error parsing alias file: ${accountFile} ${errorMessage}`); - } -}; - -export async function ensureAccountAliasDirectoryExists(): Promise { - const accountDir = getAccountDirectory(); - if (accountDir === null) { - throw new KadenaError('no_kadena_directory'); - } - return await services.filesystem.directoryExists(accountDir); -} - -export async function ensureAccountAliasFilesExists(): Promise { - if (!(await ensureAccountAliasDirectoryExists())) { - return false; - } - const accountDir = getAccountDirectory(); - if (accountDir === null) { - throw new KadenaError('no_kadena_directory'); - } - const files = await services.filesystem.readDir(accountDir); - - return files.length > 0; -} - -export async function getAllAccounts(): Promise { - if (!(await ensureAccountAliasDirectoryExists())) { - return []; - } - - const accountDir = getAccountDirectory(); - if (accountDir === null) { - throw new KadenaError('no_kadena_directory'); - } - const files = await services.filesystem.readDir(accountDir); - - const allAccounts = await Promise.all( - files.map((file) => readAccountFromFile(file).catch(() => null)), - ); - - return allAccounts.flat().filter(notEmpty); -} - export async function getAllAccountNames(): Promise< { alias: string; name: string; }[] > { - const allAccountDetails = await getAllAccounts(); + const allAccountDetails = await services.account.list(); return allAccountDetails.map(({ alias, name }) => ({ alias, name })); } diff --git a/packages/tools/kadena-cli/src/commands/account/utils/addAccount.ts b/packages/tools/kadena-cli/src/commands/account/utils/addAccount.ts deleted file mode 100644 index c4344f6886..0000000000 --- a/packages/tools/kadena-cli/src/commands/account/utils/addAccount.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { CommandResult } from '../../../utils/command.util.js'; -import type { IAddAccountConfig } from '../types.js'; -import { getAccountFilePath } from './addHelpers.js'; -import { createAccountConfigFile } from './createAccountConfigFile.js'; - -export async function addAccount( - config: IAddAccountConfig, -): Promise> { - try { - const { accountAlias, ...rest } = config; - const filePath = getAccountFilePath(accountAlias); - - const result = await createAccountConfigFile(filePath, rest); - return { - status: 'success', - data: result, - }; - } catch (error) { - return { - status: 'error', - errors: [error.message], - }; - } -} diff --git a/packages/tools/kadena-cli/src/commands/account/utils/addHelpers.ts b/packages/tools/kadena-cli/src/commands/account/utils/addHelpers.ts index 9499fabbfa..7a4e7bed7c 100644 --- a/packages/tools/kadena-cli/src/commands/account/utils/addHelpers.ts +++ b/packages/tools/kadena-cli/src/commands/account/utils/addHelpers.ts @@ -1,31 +1,12 @@ -import path from 'path'; - -import { KadenaError } from '../../../services/service-error.js'; -import { sanitizeFilename } from '../../../utils/globalHelpers.js'; +import type { IAccount } from '../../../services/account/account.types.js'; import { log } from '../../../utils/logger.js'; import { relativeToCwd } from '../../../utils/path.util.js'; -import { getAccountDirectory } from './accountHelpers.js'; - -export const isEmpty = (value?: string | null): boolean => - value === undefined || value === '' || value === null; - -export const getAccountFilePath = (fileName: string): string => { - const accountDir = getAccountDirectory(); - if (accountDir === null) { - throw new KadenaError('no_kadena_directory'); - } - const sanitizedAlias = sanitizeFilename(fileName); - return path.join(accountDir, `${sanitizedAlias}.yaml`); -}; -export const displayAddAccountSuccess = ( - accountAlias: string, - path: string, -): void => { +export const displayAddAccountSuccess = (account: IAccount): void => { log.info( log.color.green( - `\nThe account configuration "${accountAlias}" has been saved in ${relativeToCwd( - path, + `\nThe account configuration "${account.alias}" has been saved in ${relativeToCwd( + account.filepath, )}\n`, ), ); diff --git a/packages/tools/kadena-cli/src/commands/account/utils/createAccountConfigFile.ts b/packages/tools/kadena-cli/src/commands/account/utils/createAccountConfigFile.ts deleted file mode 100644 index 98308dad44..0000000000 --- a/packages/tools/kadena-cli/src/commands/account/utils/createAccountConfigFile.ts +++ /dev/null @@ -1,39 +0,0 @@ -import yaml from 'js-yaml'; -import path from 'node:path'; -import { services } from '../../../services/index.js'; -import type { IAccountAliasFileConfig } from '../types.js'; -import { accountAliasFileSchema, formatZodErrors } from './accountHelpers.js'; - -export async function writeAccountAlias( - config: IAccountAliasFileConfig, - filePath: string, -): Promise { - const { publicKeysConfig, predicate, accountName, fungible } = config; - await services.filesystem.ensureDirectoryExists(path.dirname(filePath)); - try { - const aliasData = { - name: accountName, - fungible, - publicKeys: publicKeysConfig, - predicate, - }; - accountAliasFileSchema.parse(aliasData); - await services.filesystem.writeFile(filePath, yaml.dump(aliasData)); - } catch (error) { - const errorMessage = formatZodErrors(error); - throw new Error(`Error writing alias file: ${filePath}\n ${errorMessage}`); - } -} - -export async function createAccountConfigFile( - filePath: string, - config: IAccountAliasFileConfig, -): Promise { - if (await services.filesystem.fileExists(filePath)) { - throw new Error(`The account configuration "${filePath}" already exists.`); - } - - await writeAccountAlias(config, filePath); - - return filePath; -} diff --git a/packages/tools/kadena-cli/src/commands/config/commands/configInit.ts b/packages/tools/kadena-cli/src/commands/config/commands/configInit.ts index 3ebe1a59ec..cf1f670fa3 100644 --- a/packages/tools/kadena-cli/src/commands/config/commands/configInit.ts +++ b/packages/tools/kadena-cli/src/commands/config/commands/configInit.ts @@ -1,13 +1,7 @@ import type { Command } from 'commander'; -import path from 'path'; -import { - ACCOUNT_DIR, - CWD_KADENA_DIR, - HOME_KADENA_DIR, -} from '../../../constants/config.js'; +import { CWD_KADENA_DIR, HOME_KADENA_DIR } from '../../../constants/config.js'; import { getNetworkFiles } from '../../../constants/networks.js'; import { services } from '../../../services/index.js'; -import { KadenaError } from '../../../services/service-error.js'; import { createCommand } from '../../../utils/createCommand.js'; import { notEmpty } from '../../../utils/globalHelpers.js'; import { @@ -122,19 +116,13 @@ export const createConfigInitCommand: ( return; } - const directory = services.config.getDirectory(); - if (directory === null) { - throw new KadenaError('no_kadena_directory'); - } + services.config.getDirectory(); - const accountDir = path.join(directory, ACCOUNT_DIR); - const { accountName, accountFilepath } = - await createAccountAliasByPublicKey( - accountAlias, - wallet.keys[0].publicKey, - accountDir, - ); - logAccountCreation(accountName, accountFilepath); + const account = await createAccountAliasByPublicKey( + accountAlias, + wallet.keys[0].publicKey, + ); + logAccountCreation(account.name, account.filepath); } }, ); diff --git a/packages/tools/kadena-cli/src/commands/config/commands/configShow.ts b/packages/tools/kadena-cli/src/commands/config/commands/configShow.ts index e9d8209a3f..c1378c314f 100644 --- a/packages/tools/kadena-cli/src/commands/config/commands/configShow.ts +++ b/packages/tools/kadena-cli/src/commands/config/commands/configShow.ts @@ -8,7 +8,6 @@ import { WALLET_DIR, } from '../../../constants/config.js'; import { services } from '../../../services/index.js'; -import { KadenaError } from '../../../services/service-error.js'; import { createCommand } from '../../../utils/createCommand.js'; import { getDefaultNetworkName } from '../../../utils/helpers.js'; import { log } from '../../../utils/logger.js'; @@ -83,10 +82,6 @@ export const createConfigShowCommand: ( log.debug('config show'); const kadenaDir = services.config.getDirectory(); - if (kadenaDir === null) { - throw new KadenaError('no_kadena_directory'); - } - log.info(log.color.green('Currently using the following config:')); const configPaths = getConfigPaths(kadenaDir); const directoryFileCounts = await calculateDirectoryFileCounts(configPaths); diff --git a/packages/tools/kadena-cli/src/commands/config/tests/configShow.test.ts b/packages/tools/kadena-cli/src/commands/config/tests/configShow.test.ts index eef2d0c2a6..69fd7cda9d 100644 --- a/packages/tools/kadena-cli/src/commands/config/tests/configShow.test.ts +++ b/packages/tools/kadena-cli/src/commands/config/tests/configShow.test.ts @@ -7,6 +7,7 @@ import { WALLET_DIR, } from '../../../constants/config.js'; import { services } from '../../../services/index.js'; +import { KadenaError } from '../../../services/service-error.js'; import { runCommand, runCommandJson } from '../../../utils/test.util.js'; describe('config show', () => { @@ -61,8 +62,9 @@ describe('config show', () => { }); it('should return warning message to run "kadena config init" command when there is no configDirectory', async () => { - const mockGetDirectory = vi.fn().mockReturnValue(null); - services.config.getDirectory = mockGetDirectory; + services.config.getDirectory = vi.fn().mockImplementation(() => { + throw new KadenaError('no_kadena_directory'); + }); const { stderr } = await runCommand('config show'); expect(stderr).toContain( 'No kadena directory found. Run the following command to create one:', diff --git a/packages/tools/kadena-cli/src/commands/networks/commands/networkCreate.ts b/packages/tools/kadena-cli/src/commands/networks/commands/networkCreate.ts index d7a95910b4..b6455afdca 100644 --- a/packages/tools/kadena-cli/src/commands/networks/commands/networkCreate.ts +++ b/packages/tools/kadena-cli/src/commands/networks/commands/networkCreate.ts @@ -2,7 +2,6 @@ import type { Command } from 'commander'; import path from 'path'; import { services } from '../../../services/index.js'; -import { KadenaError } from '../../../services/service-error.js'; import { createCommand } from '../../../utils/createCommand.js'; import { log } from '../../../utils/logger.js'; import { networkOptions } from '../networkOptions.js'; @@ -25,9 +24,6 @@ export const createNetworksCommand: ( async (option, { collect }) => { const kadenaDir = services.config.getDirectory(); const networkDir = getNetworkDirectory(); - if (networkDir === null || kadenaDir === null) { - throw new KadenaError('no_kadena_directory'); - } const config = await collect(option); log.debug('network-create:action', config); diff --git a/packages/tools/kadena-cli/src/commands/networks/commands/networkDefault.ts b/packages/tools/kadena-cli/src/commands/networks/commands/networkDefault.ts index 4fdd0806f4..e9b11e6b8b 100644 --- a/packages/tools/kadena-cli/src/commands/networks/commands/networkDefault.ts +++ b/packages/tools/kadena-cli/src/commands/networks/commands/networkDefault.ts @@ -2,14 +2,12 @@ import type { Command } from 'commander'; import yaml from 'js-yaml'; import path from 'node:path'; import { services } from '../../../services/index.js'; -import { KadenaError } from '../../../services/service-error.js'; import type { CommandResult } from '../../../utils/command.util.js'; import { assertCommandError } from '../../../utils/command.util.js'; import { createCommand } from '../../../utils/createCommand.js'; import { isNotEmptyObject } from '../../../utils/globalHelpers.js'; -import { getDefaultNetworkName } from '../../../utils/helpers.js'; +import { getDefaultNetworkName, isEmpty } from '../../../utils/helpers.js'; import { log } from '../../../utils/logger.js'; -import { isEmpty } from '../../account/utils/addHelpers.js'; import { networkOptions } from '../networkOptions.js'; import { removeDefaultNetwork } from '../utils/networkHelpers.js'; import { @@ -24,13 +22,7 @@ export const setNetworkDefault = async ( const networkDir = getNetworkDirectory(); const networkSettingDir = getNetworksDefaultSettingsDirectory(); const networkSettingsFilePath = getNetworksSettingsFilePath(); - if ( - networkDir === null || - networkSettingDir === null || - networkSettingsFilePath === null - ) { - throw new KadenaError('no_kadena_directory'); - } + try { const filePath = path.join(networkDir, `${network}.yaml`); diff --git a/packages/tools/kadena-cli/src/commands/networks/commands/networkManage.ts b/packages/tools/kadena-cli/src/commands/networks/commands/networkManage.ts index 9056a1c122..0ef84d8081 100644 --- a/packages/tools/kadena-cli/src/commands/networks/commands/networkManage.ts +++ b/packages/tools/kadena-cli/src/commands/networks/commands/networkManage.ts @@ -1,7 +1,6 @@ import type { Command } from 'commander'; import { services } from '../../../services/index.js'; -import { KadenaError } from '../../../services/service-error.js'; import { createCommand } from '../../../utils/createCommand.js'; import { globalOptions } from '../../../utils/globalOptions.js'; import { log } from '../../../utils/logger.js'; @@ -32,9 +31,6 @@ export const manageNetworksCommand: ( ], async (option) => { const kadenaDir = services.config.getDirectory(); - if (kadenaDir === null) { - throw new KadenaError('no_kadena_directory'); - } const networkData = await option.network(); const networkName = await option.networkName(); diff --git a/packages/tools/kadena-cli/src/commands/networks/utils/networkDisplay.ts b/packages/tools/kadena-cli/src/commands/networks/utils/networkDisplay.ts index 3a7fa160e2..703d3e3fd7 100644 --- a/packages/tools/kadena-cli/src/commands/networks/utils/networkDisplay.ts +++ b/packages/tools/kadena-cli/src/commands/networks/utils/networkDisplay.ts @@ -17,15 +17,11 @@ import type { import yaml from 'js-yaml'; import path from 'path'; import { services } from '../../../services/index.js'; -import { KadenaError } from '../../../services/service-error.js'; import { createTable } from '../../../utils/table.js'; import { getNetworkDirectory } from './networkPath.js'; export async function displayNetworksConfig(): Promise { const networkDir = getNetworkDirectory(); - if (networkDir === null) { - throw new KadenaError('no_kadena_directory'); - } const table = createTable({ head: [ diff --git a/packages/tools/kadena-cli/src/commands/networks/utils/networkHelpers.ts b/packages/tools/kadena-cli/src/commands/networks/utils/networkHelpers.ts index 8961935655..532a2e7257 100644 --- a/packages/tools/kadena-cli/src/commands/networks/utils/networkHelpers.ts +++ b/packages/tools/kadena-cli/src/commands/networks/utils/networkHelpers.ts @@ -15,7 +15,6 @@ import yaml from 'js-yaml'; import path from 'path'; import { z } from 'zod'; import { services } from '../../../services/index.js'; -import { KadenaError } from '../../../services/service-error.js'; import { getNetworkDirectory, getNetworksSettingsFilePath, @@ -89,9 +88,6 @@ export async function removeNetwork( options: Pick, ): Promise { const networkDir = getNetworkDirectory(); - if (networkDir === null) { - throw new KadenaError('no_kadena_directory'); - } const { network } = options; const sanitizedNetwork = sanitizeFilename(network).toLowerCase(); const networkFilePath = path.join(networkDir, `${sanitizedNetwork}.yaml`); @@ -101,9 +97,6 @@ export async function removeNetwork( export async function removeDefaultNetwork(): Promise { const defaultNetworksSettingsFilePath = getNetworksSettingsFilePath(); - if (defaultNetworksSettingsFilePath === null) { - throw new KadenaError('no_kadena_directory'); - } if (await services.filesystem.fileExists(defaultNetworksSettingsFilePath)) { await services.filesystem.deleteFile(defaultNetworksSettingsFilePath); @@ -114,9 +107,6 @@ export async function loadNetworkConfig( network: string, ): Promise { const networkDir = getNetworkDirectory(); - if (networkDir === null) { - throw new KadenaError('no_kadena_directory'); - } const networkFilePath = path.join(networkDir, `${network}.yaml`); const file = await services.filesystem.readFile(networkFilePath); @@ -129,9 +119,6 @@ export async function loadNetworkConfig( export async function getNetworks(): Promise[]> { const networkDir = getNetworkDirectory(); - if (networkDir === null) { - throw new KadenaError('no_kadena_directory'); - } const files = await services.filesystem.readDir(networkDir); const networks = ( await Promise.all( diff --git a/packages/tools/kadena-cli/src/commands/networks/utils/networkPath.ts b/packages/tools/kadena-cli/src/commands/networks/utils/networkPath.ts index 41b328680f..afa6932a5d 100644 --- a/packages/tools/kadena-cli/src/commands/networks/utils/networkPath.ts +++ b/packages/tools/kadena-cli/src/commands/networks/utils/networkPath.ts @@ -4,28 +4,23 @@ import { NETWORKS_DIR, } from '../../../constants/config.js'; import { services } from '../../../services/index.js'; -import { notEmpty } from '../../../utils/globalHelpers.js'; -export const getDefaultSettingsDirectory = (): string | null => { +export const getDefaultSettingsDirectory = (): string => { const kadenaDir = services.config.getDirectory(); - return notEmpty(kadenaDir) - ? path.join(kadenaDir, DEFAULT_SETTINGS_PATH) - : null; + return path.join(kadenaDir, DEFAULT_SETTINGS_PATH); }; -export const getNetworkDirectory = (): string | null => { +export const getNetworkDirectory = (): string => { const kadenaDir = services.config.getDirectory(); - return notEmpty(kadenaDir) ? path.join(kadenaDir, NETWORKS_DIR) : null; + return path.join(kadenaDir, NETWORKS_DIR); }; -export const getNetworksDefaultSettingsDirectory = (): string | null => { +export const getNetworksDefaultSettingsDirectory = (): string => { const defaultDir = getDefaultSettingsDirectory(); - return notEmpty(defaultDir) ? path.join(defaultDir, NETWORKS_DIR) : null; + return path.join(defaultDir, NETWORKS_DIR); }; -export const getNetworksSettingsFilePath = (): string | null => { +export const getNetworksSettingsFilePath = (): string => { const defaultNetworksDir = getNetworksDefaultSettingsDirectory(); - return notEmpty(defaultNetworksDir) - ? path.join(defaultNetworksDir, '__default__.yaml') - : null; + return path.join(defaultNetworksDir, '__default__.yaml'); }; diff --git a/packages/tools/kadena-cli/src/commands/tx/commands/templates/templates.ts b/packages/tools/kadena-cli/src/commands/tx/commands/templates/templates.ts index 197ed3856f..0504f99ff4 100644 --- a/packages/tools/kadena-cli/src/commands/tx/commands/templates/templates.ts +++ b/packages/tools/kadena-cli/src/commands/tx/commands/templates/templates.ts @@ -1,6 +1,5 @@ import path from 'node:path'; import { services } from '../../../../services/index.js'; -import { KadenaError } from '../../../../services/service-error.js'; import { getTxTemplateDirectory } from '../../utils/txHelpers.js'; const transferTemplate = ` @@ -98,9 +97,6 @@ export const defaultTemplates = { export const writeTemplatesToDisk = async (): Promise => { const templateFolder = getTxTemplateDirectory(); - if (templateFolder === null) { - throw new KadenaError('no_kadena_directory'); - } await services.filesystem.ensureDirectoryExists(templateFolder); const templatesAdded = []; for (const [name, template] of Object.entries(defaultTemplates)) { @@ -116,9 +112,6 @@ export const writeTemplatesToDisk = async (): Promise => { export const getTemplate = async (filename: string): Promise => { const templateFolder = getTxTemplateDirectory(); - if (templateFolder === null) { - throw new KadenaError('no_kadena_directory'); - } const cwdFile = await services.filesystem.readFile(filename); if (cwdFile !== null) { return cwdFile; @@ -133,9 +126,6 @@ export const getTemplate = async (filename: string): Promise => { export const getTemplates = async (): Promise> => { const templateFolder = getTxTemplateDirectory(); - if (templateFolder === null) { - throw new KadenaError('no_kadena_directory'); - } const files = await services.filesystem.readDir(templateFolder); const templates: Record = {}; for (const file of files) { diff --git a/packages/tools/kadena-cli/src/commands/tx/commands/txCreateTransaction.ts b/packages/tools/kadena-cli/src/commands/tx/commands/txCreateTransaction.ts index 89ce4561e2..dd11a5c165 100644 --- a/packages/tools/kadena-cli/src/commands/tx/commands/txCreateTransaction.ts +++ b/packages/tools/kadena-cli/src/commands/tx/commands/txCreateTransaction.ts @@ -9,7 +9,6 @@ import { WORKING_DIRECTORY, } from '../../../constants/config.js'; import { services } from '../../../services/index.js'; -import { KadenaError } from '../../../services/service-error.js'; import type { CommandResult } from '../../../utils/command.util.js'; import { assertCommandError } from '../../../utils/command.util.js'; import { createCommand } from '../../../utils/createCommand.js'; @@ -110,10 +109,7 @@ export const createTransactionCommandNew = createCommand( globalOptions.outFileJson(), ], async (option, { values, stdin }) => { - if (TX_TEMPLATE_FOLDER === null) { - throw new KadenaError('no_kadena_directory'); - } - + services.config.getDirectory(); const templatesAdded = await writeTemplatesToDisk(); if (templatesAdded.length > 0) { log.info( diff --git a/packages/tools/kadena-cli/src/commands/tx/commands/txHistory.ts b/packages/tools/kadena-cli/src/commands/tx/commands/txHistory.ts index eeed256b2a..8b7394f2ac 100644 --- a/packages/tools/kadena-cli/src/commands/tx/commands/txHistory.ts +++ b/packages/tools/kadena-cli/src/commands/tx/commands/txHistory.ts @@ -2,7 +2,6 @@ import { createClient } from '@kadena/client'; import type { Command } from 'commander'; import path from 'node:path'; import { TRANSACTIONS_LOG_FILE } from '../../../constants/config.js'; -import { KadenaError } from '../../../services/service-error.js'; import { createCommand } from '../../../utils/createCommand.js'; import { notEmpty } from '../../../utils/globalHelpers.js'; import { globalOptions } from '../../../utils/globalOptions.js'; @@ -116,8 +115,6 @@ export const printTxLogs = (transactionLog: ITransactionLog): void => { export const txHistory = async (): Promise => { try { const transactionDir = getTransactionDirectory(); - if (!notEmpty(transactionDir)) throw new KadenaError('no_kadena_directory'); - const transactionFilePath = path.join( transactionDir, TRANSACTIONS_LOG_FILE, diff --git a/packages/tools/kadena-cli/src/commands/tx/utils/txHelpers.ts b/packages/tools/kadena-cli/src/commands/tx/utils/txHelpers.ts index 354c7125f3..0355443656 100644 --- a/packages/tools/kadena-cli/src/commands/tx/utils/txHelpers.ts +++ b/packages/tools/kadena-cli/src/commands/tx/utils/txHelpers.ts @@ -36,7 +36,6 @@ import { } from '../../../constants/config.js'; import { ICommandSchema } from '../../../prompts/tx.js'; import { services } from '../../../services/index.js'; -import { KadenaError } from '../../../services/service-error.js'; import type { IWallet, IWalletKey, @@ -698,9 +697,9 @@ export async function logTransactionDetails(command: ICommand): Promise { } } -export const getTxTemplateDirectory = (): string | null => { +export const getTxTemplateDirectory = (): string => { const kadenaDir = services.config.getDirectory(); - return notEmpty(kadenaDir) ? path.join(kadenaDir, TX_TEMPLATE_FOLDER) : null; + return path.join(kadenaDir, TX_TEMPLATE_FOLDER); }; /** @@ -791,11 +790,9 @@ export const createTransactionWithDetails = async ( return transactionsWithDetails; }; -export const getTransactionDirectory = (): string | null => { +export const getTransactionDirectory = (): string => { const kadenaDirectory = services.config.getDirectory(); - return notEmpty(kadenaDirectory) - ? path.join(kadenaDirectory, TRANSACTIONS_PATH) - : null; + return path.join(kadenaDirectory, TRANSACTIONS_PATH); }; export interface IUpdateTransactionsLogPayload { @@ -846,8 +843,6 @@ export const saveTransactionsToFile = async ( ): Promise => { try { const transactionDir = getTransactionDirectory(); - if (!notEmpty(transactionDir)) throw new KadenaError('no_kadena_directory'); - await services.filesystem.ensureDirectoryExists(transactionDir); const transactionFilePath = path.join( transactionDir, @@ -906,8 +901,6 @@ export const updateTransactionStatus = async ( ): Promise => { try { const transactionDir = getTransactionDirectory(); - if (!notEmpty(transactionDir)) throw new KadenaError('no_kadena_directory'); - const transactionFilePath = path.join( transactionDir, TRANSACTIONS_LOG_FILE, diff --git a/packages/tools/kadena-cli/src/commands/wallets/commands/walletAdd.ts b/packages/tools/kadena-cli/src/commands/wallets/commands/walletAdd.ts index f53a931fbf..e09c5ebeca 100644 --- a/packages/tools/kadena-cli/src/commands/wallets/commands/walletAdd.ts +++ b/packages/tools/kadena-cli/src/commands/wallets/commands/walletAdd.ts @@ -1,7 +1,6 @@ import type { Command } from 'commander'; import { services } from '../../../services/index.js'; -import { KadenaError } from '../../../services/service-error.js'; import { createCommand } from '../../../utils/createCommand.js'; import { notEmpty } from '../../../utils/globalHelpers.js'; import { @@ -11,7 +10,6 @@ import { import { handleNoKadenaDirectory } from '../../../utils/helpers.js'; import { log } from '../../../utils/logger.js'; import { accountOptions } from '../../account/accountOptions.js'; -import { getAccountDirectory } from '../../account/utils/accountHelpers.js'; import { createAccountAliasByPublicKey, logAccountCreation, @@ -42,20 +40,17 @@ export const createGenerateWalletCommand: ( accountOptions.accountAlias({ isOptional: true }), ], async (option) => { - const accountDir = getAccountDirectory(); - if (accountDir === null) { - throw new KadenaError('no_kadena_directory'); - } + services.config.getDirectory(); const walletName = await option.walletName(); const passwordFile = await option.passwordFile(); const legacy = await option.legacy(); const createAccount = await option.createAccount(); - let accountAlias = null; - if (createAccount.createAccount === 'true') { - accountAlias = await option.accountAlias(); - } + const accountAlias = + createAccount.createAccount === 'true' + ? await option.accountAlias() + : null; const config = { walletName: walletName.walletName, @@ -98,13 +93,11 @@ export const createGenerateWalletCommand: ( return; } - const { accountName, accountFilepath } = - await createAccountAliasByPublicKey( - config.accountAlias, - wallet.keys[0].publicKey, - accountDir, - ); - logAccountCreation(accountName, accountFilepath); + const account = await createAccountAliasByPublicKey( + config.accountAlias, + wallet.keys[0].publicKey, + ); + logAccountCreation(account.name, account.filepath); log.info(`\nTo fund the account, use the following command:`); log.info(`kadena account fund --account ${config.accountAlias}`); } diff --git a/packages/tools/kadena-cli/src/commands/wallets/utils/walletHelpers.ts b/packages/tools/kadena-cli/src/commands/wallets/utils/walletHelpers.ts index 4d3183cc60..d8fce072fe 100644 --- a/packages/tools/kadena-cli/src/commands/wallets/utils/walletHelpers.ts +++ b/packages/tools/kadena-cli/src/commands/wallets/utils/walletHelpers.ts @@ -1,7 +1,7 @@ -import path from 'node:path'; +import type { IAccount } from '../../../services/account/account.types.js'; +import { services } from '../../../services/index.js'; import { log } from '../../../utils/logger.js'; import { relativeToCwd } from '../../../utils/path.util.js'; -import { writeAccountAlias } from '../../account/utils/createAccountConfigFile.js'; export const logWalletInfo = ( words: string, @@ -39,27 +39,17 @@ export const logAccountCreation = ( export const createAccountAliasByPublicKey = async ( alias: string, publicKey: string, - directory: string, -): Promise<{ - accountName: string; - accountFilepath: string; -}> => { +): Promise => { const accountName = `k:${publicKey}`; - const accountFilepath = path.join(directory, `${alias}.yaml`); - await writeAccountAlias( - { - accountName, - fungible: 'coin', - predicate: `keys-all`, - publicKeysConfig: [publicKey], - }, - accountFilepath, - ); + const account = await services.account.create({ + alias: alias, + name: accountName, + fungible: 'coin', + predicate: 'keys-all', + publicKeys: [publicKey], + }); - return { - accountName, - accountFilepath, - }; + return account; }; /** find `amount` of free indexes starting at a `startIndex` while excluding indexes already in use by `existingIndexes` */ diff --git a/packages/tools/kadena-cli/src/prompts/network.ts b/packages/tools/kadena-cli/src/prompts/network.ts index 643aecac7b..3730b75ba2 100644 --- a/packages/tools/kadena-cli/src/prompts/network.ts +++ b/packages/tools/kadena-cli/src/prompts/network.ts @@ -12,7 +12,6 @@ import { MAX_CHAIN_VALUE } from '../constants/config.js'; import { INVALID_FILE_NAME_ERROR_MSG } from '../constants/global.js'; import { NO_NETWORKS_FOUND_ERROR_MESSAGE } from '../constants/networks.js'; import { services } from '../services/index.js'; -import { KadenaError } from '../services/service-error.js'; import type { IPrompt } from '../utils/createOption.js'; import { isNotEmptyString, isValidFilename } from '../utils/globalHelpers.js'; import { getExistingNetworks } from '../utils/helpers.js'; @@ -192,9 +191,7 @@ export const networkSelectPrompt: IPrompt = async ( const getEnsureExistingNetworks = async (): Promise => { const kadenaDir = services.config.getDirectory(); const networkDir = getNetworkDirectory(); - if (networkDir === null || kadenaDir === null) { - throw new KadenaError('no_kadena_directory'); - } + const isNetworksFolderExists = await services.filesystem.directoryExists(networkDir); if ( diff --git a/packages/tools/kadena-cli/src/prompts/tx.ts b/packages/tools/kadena-cli/src/prompts/tx.ts index 58eb261492..400a7b465c 100644 --- a/packages/tools/kadena-cli/src/prompts/tx.ts +++ b/packages/tools/kadena-cli/src/prompts/tx.ts @@ -10,7 +10,6 @@ import { } from '../commands/tx/utils/txHelpers.js'; import { basename } from 'node:path'; -import { getAllAccounts } from '../commands/account/utils/accountHelpers.js'; import { loadNetworkConfig } from '../commands/networks/utils/networkHelpers.js'; import { getTemplates } from '../commands/tx/commands/templates/templates.js'; import { MULTI_SELECT_INSTRUCTIONS } from '../constants/global.js'; @@ -188,7 +187,7 @@ const promptVariableValue = async ( ): Promise => { if (key.startsWith('account:')) { // search for account alias - needs account implementation - const accounts = await getAllAccounts().catch(() => []); + const accounts = await services.account.list(); const hasAccount = accounts.length > 0; let value: string | null = null; @@ -243,7 +242,7 @@ const promptVariableValue = async ( 0, ); const plainKeys = await services.plainKey.list(); - const accounts = await getAllAccounts().catch(() => []); + const accounts = await services.account.list(); const hasKeys = walletKeysCount > 0 || plainKeys.length > 0; const hasAccounts = accounts.length > 0; @@ -258,7 +257,7 @@ const promptVariableValue = async ( const accountMatch = variables[`account:${pkName}`]; if (accountMatch) { - const accounts = await getAllAccounts().catch(() => []); + const accounts = await services.account.list(); const accountConfig = accounts.find((x) => x.name === accountMatch); if (accountConfig) { const selection = await select({ diff --git a/packages/tools/kadena-cli/src/services/account/account.schemas.ts b/packages/tools/kadena-cli/src/services/account/account.schemas.ts new file mode 100644 index 0000000000..6db177bafa --- /dev/null +++ b/packages/tools/kadena-cli/src/services/account/account.schemas.ts @@ -0,0 +1,9 @@ +import { z } from 'zod'; + +export const accountSchema = z.object({ + alias: z.string(), + name: z.string(), + fungible: z.string(), + publicKeys: z.array(z.string()).nonempty(), + predicate: z.string(), +}); diff --git a/packages/tools/kadena-cli/src/services/account/account.service.ts b/packages/tools/kadena-cli/src/services/account/account.service.ts new file mode 100644 index 0000000000..0be358608d --- /dev/null +++ b/packages/tools/kadena-cli/src/services/account/account.service.ts @@ -0,0 +1,55 @@ +import { log } from '../../utils/logger.js'; +import type { Services } from '../index.js'; +import type { IAccount, IAccountCreate } from './account.types.js'; + +export interface IAccountService { + get: (filepath: string) => Promise; + getByAlias: (alias: string) => Promise; + create: (account: IAccountCreate) => Promise; + list: () => Promise; + delete: (filepath: string) => Promise; +} + +export class AccountService implements IAccountService { + // eslint-disable-next-line @typescript-eslint/parameter-properties, @typescript-eslint/naming-convention + public constructor(private services: Services) {} + + public async get(filepath: string): ReturnType { + return this.services.config.getAccount(filepath); + } + + public async getByAlias( + alias: string, + ): ReturnType { + const accounts = await this.services.config.getAccounts(); + const match = accounts.filter((account) => account.alias === alias); + if (match.length === 1) { + return match[0]; + } else if (match.length > 1) { + log.warning( + `Multiple accounts found with alias "${alias}", it is recommended to use unique aliases`, + ); + return match[0]; + } + return null; + } + + public async create( + account: IAccountCreate, + ): ReturnType { + const filepath = await this.services.config.setAccount(account, true); + const result = await this.get(filepath); + if (result === null) { + throw new Error(`Account "${filepath}" not found`); + } + return result; + } + + public async list(): ReturnType { + return this.services.config.getAccounts(); + } + + public async delete(filepath: string): ReturnType { + return this.services.config.deleteAccount(filepath); + } +} diff --git a/packages/tools/kadena-cli/src/services/account/account.types.ts b/packages/tools/kadena-cli/src/services/account/account.types.ts new file mode 100644 index 0000000000..222582bda3 --- /dev/null +++ b/packages/tools/kadena-cli/src/services/account/account.types.ts @@ -0,0 +1,21 @@ +import type { z } from 'zod'; +import type { accountSchema } from './account.schemas.js'; + +export interface IAccountCreate { + alias: string; + name: string; + fungible: string; + publicKeys: string[]; + predicate: string; +} + +export type IAccountFile = z.output; + +export interface IAccount { + alias: string; + name: string; + fungible: string; + publicKeys: string[]; + predicate: string; + filepath: string; +} diff --git a/packages/tools/kadena-cli/src/services/config/config.service.ts b/packages/tools/kadena-cli/src/services/config/config.service.ts index d1ad42915c..49903eb087 100644 --- a/packages/tools/kadena-cli/src/services/config/config.service.ts +++ b/packages/tools/kadena-cli/src/services/config/config.service.ts @@ -4,6 +4,7 @@ import { statSync } from 'node:fs'; import path from 'node:path'; import sanitize from 'sanitize-filename'; import { + ACCOUNT_DIR, ENV_KADENA_DIR, HOME_KADENA_DIR, IS_TEST, @@ -13,12 +14,20 @@ import { } from '../../constants/config.js'; import { detectArrayFileParseType, + formatZodError, getFileParser, notEmpty, safeYamlParse, } from '../../utils/globalHelpers.js'; +import { arrayNotEmpty } from '../../utils/helpers.js'; import { log } from '../../utils/logger.js'; import { relativeToCwd } from '../../utils/path.util.js'; +import { accountSchema } from '../account/account.schemas.js'; +import type { + IAccount, + IAccountCreate, + IAccountFile, +} from '../account/account.types.js'; import type { Services } from '../index.js'; import { KadenaError } from '../service-error.js'; import { @@ -56,7 +65,7 @@ const findKadenaDirectory = (searchDir: string): string | null => { }; export interface IConfigService { - getDirectory(): string | null; + getDirectory(): string; setDirectory(directory: string): void; // Key getPlainKey(filepath: string): Promise; @@ -67,6 +76,11 @@ export interface IConfigService { setWallet: (wallet: IWalletCreate, update?: boolean) => Promise; getWallets: () => Promise; deleteWallet: (filepath: string) => Promise; + // account + setAccount: (account: IAccountCreate, update?: boolean) => Promise; + getAccount: (filepath: string) => Promise; + getAccounts: () => Promise; + deleteAccount: (filepath: string) => Promise; } export class ConfigService implements IConfigService { @@ -77,7 +91,7 @@ export class ConfigService implements IConfigService { public constructor(services: Services, directory?: string) { this.services = services; - this.discoverDirectory(directory); + this._discoverDirectory(directory); } public setDirectory(directory: string): void { @@ -89,8 +103,7 @@ export class ConfigService implements IConfigService { this.directory = directory; } - // eslint-disable-next-line @typescript-eslint/naming-convention - private discoverDirectory(directory?: string): void { + private _discoverDirectory(directory?: string): void { // Priority 1: directory passed in constructor if (directory !== undefined) { this.directory = directory; @@ -126,7 +139,9 @@ export class ConfigService implements IConfigService { this.directory = null; } - public getDirectory(): string | null { + public getDirectory(): string { + if (this.directory === null) throw new KadenaError('no_kadena_directory'); + // console.log({ directory: this.directory }); return this.directory; } @@ -210,8 +225,7 @@ export class ConfigService implements IConfigService { wallet: IWalletCreate, update?: boolean, ): ReturnType { - const directory = await this.getDirectory(); - if (directory === null) throw new KadenaError('no_kadena_directory'); + const directory = this.getDirectory(); const filename = sanitize(wallet.alias); const filepath = path.join(directory, WALLET_DIR, `${filename}${YAML_EXT}`); const exists = await this.services.filesystem.fileExists(filepath); @@ -237,8 +251,7 @@ export class ConfigService implements IConfigService { } public async getWallets(): ReturnType { - const directory = await this.getDirectory(); - if (directory === null) throw new KadenaError('no_kadena_directory'); + const directory = this.getDirectory(); const walletPath = path.join(directory, WALLET_DIR); if (!(await this.services.filesystem.directoryExists(walletPath))) { return []; @@ -259,4 +272,95 @@ export class ConfigService implements IConfigService { ): ReturnType { await this.services.filesystem.deleteFile(filepath); } + + // Account + public async setAccount( + account: IAccountCreate, + update?: boolean, + ): ReturnType { + const directory = this.getDirectory(); + const filename = sanitize(account.alias); + const filepath = path.join( + directory, + ACCOUNT_DIR, + `${filename}${YAML_EXT}`, + ); + const exists = await this.services.filesystem.fileExists(filepath); + if (exists && update !== true) { + throw new Error(`Account "${relativeToCwd(filepath)}" already exists.`); + } + if (!arrayNotEmpty(account.publicKeys)) { + throw new Error('No public keys provided'); + } + + const data: IAccountFile = { + alias: account.alias, + name: account.name, + fungible: account.fungible, + predicate: account.predicate, + publicKeys: account.publicKeys, + }; + + await this.services.filesystem.ensureDirectoryExists( + path.join(directory, ACCOUNT_DIR), + ); + await this.services.filesystem.writeFile( + filepath, + yaml.dump(data, { lineWidth: -1 }), + ); + return filepath; + } + + public async getAccount( + filepath: string, + ): ReturnType { + const file = await this.services.filesystem.readFile(filepath); + if (file === null) { + throw new Error(`Account file "${relativeToCwd(filepath)}" not found.`); + } + const parsedYaml = safeYamlParse(file); + // Alias was added later, insert here for backwards compatibility + if (parsedYaml && parsedYaml.alias === undefined) { + parsedYaml.alias = path.basename(filepath, YAML_EXT); + } + + const parsed = accountSchema.safeParse(parsedYaml); + if (!parsed.success) { + throw new Error( + `Error parsing account file: ${formatZodError(parsed.error)}`, + ); + } + return { + alias: parsed.data.alias, + name: parsed.data.name, + fungible: parsed.data.fungible, + predicate: parsed.data.predicate, + publicKeys: parsed.data.publicKeys, + filepath, + }; + } + + public async getAccounts(): ReturnType { + const directory = this.getDirectory(); + const accountDir = path.join(directory, ACCOUNT_DIR); + if (!(await this.services.filesystem.directoryExists(accountDir))) { + return []; + } + const files = await this.services.filesystem.readDir(accountDir); + const filepaths = files.map((file) => path.join(accountDir, file)); + + const accounts = await Promise.all( + filepaths.map(async (filepath) => + this.getAccount(filepath).catch(() => null), + ), + ); + + return accounts.filter(notEmpty); + } + + public async deleteAccount( + filepath: string, + ): ReturnType { + await this.services.filesystem.deleteFile(filepath); + } } diff --git a/packages/tools/kadena-cli/src/services/index.ts b/packages/tools/kadena-cli/src/services/index.ts index 81fc934b4a..0e8275572d 100644 --- a/packages/tools/kadena-cli/src/services/index.ts +++ b/packages/tools/kadena-cli/src/services/index.ts @@ -8,6 +8,8 @@ import type { IConfigService } from './config/config.service.js'; import { ConfigService } from './config/config.service.js'; export * from './config/config.types.js'; +import type { IAccountService } from './account/account.service.js'; +import { AccountService } from './account/account.service.js'; import type { IPactJSService } from './pactjs/pactjs.service.js'; import { PactJSService } from './pactjs/pactjs.service.js'; import type { IPlainKeyService } from './plain-key/plainkey.service.js'; @@ -25,6 +27,7 @@ export class Services { public plainKey: IPlainKeyService; public wallet: IWalletService; public pactjs: IPactJSService; + public account: IAccountService; public location?: string; public constructor(options?: IServiceOptions) { @@ -33,6 +36,7 @@ export class Services { this.plainKey = new PlainKeyService(this); this.wallet = new WalletService(this); this.pactjs = new PactJSService(this); + this.account = new AccountService(this); } } diff --git a/packages/tools/kadena-cli/src/services/wallet/wallet.service.ts b/packages/tools/kadena-cli/src/services/wallet/wallet.service.ts index 9b80852c39..9cfb13987d 100644 --- a/packages/tools/kadena-cli/src/services/wallet/wallet.service.ts +++ b/packages/tools/kadena-cli/src/services/wallet/wallet.service.ts @@ -38,7 +38,7 @@ export interface IWalletService { import: (wallet: IWalletImport) => Promise; delete: (filepath: string) => Promise; generateKey: (data: IWalletKeyCreate) => Promise; - /** Stores given key and returns a new wallet. Note: wallet passed in arguments in not mutated. */ + /** Stores given key and returns a new wallet. Note: wallet passed in arguments in **not** mutated. */ storeKey: (wallet: IWallet, key: IWalletKey) => Promise; getKeyPair: ( wallet: IWallet, diff --git a/packages/tools/kadena-cli/src/utils/globalHelpers.ts b/packages/tools/kadena-cli/src/utils/globalHelpers.ts index bba943bf5b..c8de5b87be 100644 --- a/packages/tools/kadena-cli/src/utils/globalHelpers.ts +++ b/packages/tools/kadena-cli/src/utils/globalHelpers.ts @@ -163,7 +163,12 @@ export const formatZodError = (error: ZodError): string => { const format = error.format() as any; const formatted = Object.keys(format) .map((key) => { - if (key === '_errors') return null; + if (key === '_errors') { + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + return Array.isArray(format[key]) && format[key].includes('Required') + ? 'Can not be empty' + : null; + } return `${key}: ${format[key]?._errors.join(', ')}`; }) .filter(notEmpty); diff --git a/packages/tools/kadena-cli/src/utils/helpers.ts b/packages/tools/kadena-cli/src/utils/helpers.ts index f5e8c18563..712729ab15 100644 --- a/packages/tools/kadena-cli/src/utils/helpers.ts +++ b/packages/tools/kadena-cli/src/utils/helpers.ts @@ -36,9 +36,6 @@ export function handlePromptError(error: unknown): never { export async function getExistingNetworks(): Promise { const networkDir = getNetworkDirectory(); - if (networkDir === null) { - throw new KadenaError('no_kadena_directory'); - } await services.filesystem.ensureDirectoryExists(networkDir); @@ -208,3 +205,14 @@ export function handleNoKadenaDirectory(error: unknown): boolean { } return false; } + +export const arrayNotEmpty = ( + array: T[], +): array is [T, ...T[]] => array.length > 0; + +/** check against null, undefined, and empty string */ +export const isEmpty = (value: unknown): value is null | undefined | '' => + value === null || value === undefined || value === ''; + +export const isNotEmpty = (value: T | null | undefined): value is T => + value !== null && value !== undefined;