Skip to content

Commit

Permalink
fix config (again), rename > accountOption
Browse files Browse the repository at this point in the history
  • Loading branch information
mixmix committed Sep 24, 2024
1 parent fa335b4 commit 16bb90e
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 63 deletions.
4 changes: 2 additions & 2 deletions src/account/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { EntropyAccount } from "./main";
import { selectAndPersistNewAccount } from "./utils";
import { ACCOUNTS_CONTENT } from './constants'
import * as config from '../config'
import { cliWrite, currentAccountAddressOption, endpointOption, loadEntropy, passwordOption } from "../common/utils-cli";
import { cliWrite, accountOption, endpointOption, loadEntropy, passwordOption } from "../common/utils-cli";
import { findAccountByAddressOrName } from "src/common/utils";

export function entropyAccountCommand () {
Expand Down Expand Up @@ -87,7 +87,7 @@ function entropyAccountRegister () {
.description('Register an entropy account with a program')
.addOption(passwordOption())
.addOption(endpointOption())
.addOption(currentAccountAddressOption())
.addOption(accountOption())
// Removing these options for now until we update the design to accept program configs
// .addOption(
// new Option(
Expand Down
44 changes: 26 additions & 18 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#! /usr/bin/env node

/* NOTE: calling this file entropy.ts helps commander parse process.argv */
import { Command, Option } from 'commander'
import { Command, /* Option */ } from 'commander'

import { EntropyTuiOptions } from './types'
import { currentAccountAddressOption, endpointOption, loadEntropy } from './common/utils-cli'
import { accountOption, endpointOption, loadEntropy } from './common/utils-cli'
import * as config from './config'

import launchTui from './tui'
import { entropyAccountCommand } from './account/command'
Expand All @@ -17,26 +18,33 @@ const program = new Command()
/* no command */
program
.name('entropy')
.description('CLI interface for interacting with entropy.xyz. Running this binary without any commands or arguments starts a text-based interface.')
.addOption(currentAccountAddressOption())
.description('CLI interface for interacting with entropy.xyz. Running without commands starts an interactive ui')
.addOption(accountOption())
.addOption(endpointOption())
// NOTE: I think this is currently unused
.addOption(
new Option(
'-d, --dev',
'Runs entropy in a developer mode uses the dev endpoint as the main endpoint and allows for faucet option to be available in the main menu'
)
.env('DEV_MODE')
.hideHelp()
)
// NOTE: currently unused
// .addOption(
// new Option(
// '-d, --dev',
// 'Runs entropy in a developer mode uses the dev endpoint as the main endpoint and allows for faucet option to be available in the main menu'
// )
// .env('DEV_MODE')
// .hideHelp()
// )
.addCommand(entropyBalanceCommand())
.addCommand(entropyAccountCommand())
.addCommand(entropyTransferCommand())
.addCommand(entropySignCommand())
.action(async (options: EntropyTuiOptions) => {
const { account, endpoint } = options
const entropy = await loadEntropy(account, endpoint)
launchTui(entropy, options)
.action(async (opts: EntropyTuiOptions) => {
const { account, endpoint } = opts
const entropy = account
? await loadEntropy(account, endpoint)
: undefined
// NOTE: on initial startup you have no account
launchTui(entropy, opts)
})
.hook('preAction', async () => {
// set up config file, run migrations
return config.init()
})

program.parseAsync().then(() => {})
program.parseAsync()
49 changes: 32 additions & 17 deletions src/common/utils-cli.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import Entropy from '@entropyxyz/sdk'
import { Option } from 'commander'
import { findAccountByAddressOrName, stringify } from './utils'
import * as config from '../config'
import Entropy from '@entropyxyz/sdk'
import { initializeEntropy } from './initializeEntropy'

export function cliWrite (result) {
const prettyResult = stringify(result, 0)
process.stdout.write(prettyResult)
}

function getConfigOrNull () {
try {
return config.getSync()
} catch (err) {
if (config.isDangerousReadError(err)) throw err
return null
}
}

export function endpointOption () {
return new Option(
'-e, --endpoint <endpoint>',
Expand All @@ -17,14 +26,14 @@ export function endpointOption () {
'Can also be given a stored endpoint name from config eg: `entropy --endpoint test-net`.'
].join(' ')
)
.env('ENDPOINT')
.env('ENTROPY_ENDPOINT')
.argParser(aliasOrEndpoint => {
/* see if it's a raw endpoint */
if (aliasOrEndpoint.match(/^wss?:\/\//)) return aliasOrEndpoint

/* look up endpoint-alias */
const storedConfig = config.getSync()
const endpoint = storedConfig.endpoints[aliasOrEndpoint]
const storedConfig = getConfigOrNull()
const endpoint = storedConfig?.endpoints?.[aliasOrEndpoint]
if (!endpoint) throw Error('unknown endpoint alias: ' + aliasOrEndpoint)

return endpoint
Expand All @@ -40,31 +49,37 @@ export function passwordOption (description?: string) {
)
}

export function currentAccountAddressOption () {
const storedConfig = config.getSync()
export function accountOption () {
const storedConfig = getConfigOrNull()

return new Option(
'-a, --account <address>',
'Sets the current account for the session or defaults to the account stored in the config'
'-a, --account <accountAddressOrName>',
[
'Sets the account for the session.',
'Defaults to the last set account (or the first account if one has not been set before).'
].join(' ')
)
.env('ACCOUNT_ADDRESS')
.env('ENTROPY_ACCOUNT')
.argParser(async (account) => {
if (account === storedConfig.selectedAccount) return account
// Updated selected account in config with new address from this option
const newConfigUpdates = { selectedAccount: account }
await config.set({ ...storedConfig, ...newConfigUpdates })
if (storedConfig && storedConfig.selectedAccount !== account) {
// Updated selected account in config with new address from this option
await config.set({
...storedConfig,
selectedAccount: account
})
}

return account
})
.default(storedConfig.selectedAccount)
.default(storedConfig?.selectedAccount)
// TODO: display the *name* not address
// TODO: standardise whether selectedAccount is name or address.
}

export async function loadEntropy (addressOrName: string, endpoint: string, password?: string): Promise<Entropy> {
const storedConfig = config.getSync()
const selectedAccount = findAccountByAddressOrName(storedConfig.accounts, addressOrName)
if (!selectedAccount) throw new Error(`AddressError: No account with name or address "${addressOrName}"`)
const accounts = getConfigOrNull()?.accounts || []
const selectedAccount = findAccountByAddressOrName(accounts, addressOrName)
if (!selectedAccount) throw new Error(`No account with name or address: "${addressOrName}"`)

// check if data is encrypted + we have a password
if (typeof selectedAccount.data === 'string' && !password) {
Expand Down
38 changes: 18 additions & 20 deletions src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { readFile, writeFile, rm } from 'node:fs/promises'
import { readFileSync, writeFileSync } from 'node:fs'
import { readFileSync } from 'node:fs'
import { mkdirp } from 'mkdirp'
import { join, dirname } from 'path'
import envPaths from 'env-paths'
Expand Down Expand Up @@ -35,9 +35,10 @@ function hasRunMigration (config: any, version: number) {

export async function init (configPath = CONFIG_PATH, oldConfigPath = OLD_CONFIG_PATH) {
const currentConfig = await get(configPath)
.catch(async (err) => {
if (err.code !== 'ENOENT') throw err
.catch(async (err ) => {
if (isDangerousReadError(err)) throw err

// If there is no current config, try loading the old one
const oldConfig = await get(oldConfigPath).catch(noop) // drop errors
if (oldConfig) {
// move the config
Expand All @@ -58,33 +59,30 @@ export async function init (configPath = CONFIG_PATH, oldConfigPath = OLD_CONFIG
export async function get (configPath = CONFIG_PATH) {
return readFile(configPath, 'utf-8')
.then(deserialize)
.catch(makeGetErrorHandler(configPath))
}

export function getSync (configPath = CONFIG_PATH): EntropyConfig {
try {
const configBuffer = readFileSync(configPath, 'utf8')
return deserialize(configBuffer)
} catch (err) {
return makeGetErrorHandler(configPath)(err)
}
export function getSync (configPath = CONFIG_PATH) {
const configStr = readFileSync(configPath, 'utf8')
return deserialize(configStr)
}

export async function set (config: EntropyConfig, configPath = CONFIG_PATH) {
assertConfigPath(configPath)

await mkdirp(dirname(configPath))
await writeFile(configPath, serialize(config))
}

/* util */
function noop () {}

function makeGetErrorHandler (configPath) {
return function getErrorHandler (err) {
if (err.code !== 'ENOENT') throw err

const newConfig = migrateData(allMigrations, {})
mkdirp.sync(dirname(configPath))
writeFileSync(configPath, serialize(newConfig))
return newConfig
function assertConfigPath (configPath) {
if (!configPath.endsWith('.json')) {
throw Error(`configPath must be of form *.json, got ${configPath}`)
}
}
export function isDangerousReadError (err) {
// file not found:
if (err.code === 'ENOENT') return false

return true
}
4 changes: 2 additions & 2 deletions src/sign/command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Command, /* Option */ } from 'commander'
import { cliWrite, currentAccountAddressOption, endpointOption, loadEntropy, passwordOption } from '../common/utils-cli'
import { cliWrite, accountOption, endpointOption, loadEntropy, passwordOption } from '../common/utils-cli'
import { EntropySign } from './main'

export function entropySignCommand () {
Expand All @@ -8,7 +8,7 @@ export function entropySignCommand () {
.argument('msg', 'Message you would like to sign (string)')
.addOption(passwordOption('Password for the source account (if required)'))
.addOption(endpointOption())
.addOption(currentAccountAddressOption())
.addOption(accountOption())
// .addOption(
// new Option(
// '-r, --raw',
Expand Down
4 changes: 2 additions & 2 deletions src/transfer/command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Command } from "commander"
import { currentAccountAddressOption, endpointOption, loadEntropy, passwordOption } from "src/common/utils-cli"
import { accountOption, endpointOption, loadEntropy, passwordOption } from "src/common/utils-cli"
import { EntropyTransfer } from "./main"

export function entropyTransferCommand () {
Expand All @@ -10,7 +10,7 @@ export function entropyTransferCommand () {
.argument('amount', 'Amount of funds to be moved')
.addOption(passwordOption('Password for the source account (if required)'))
.addOption(endpointOption())
.addOption(currentAccountAddressOption())
.addOption(accountOption())
.action(async (destination, amount, opts) => {
const entropy = await loadEntropy(opts.account, opts.endpoint)
const transferService = new EntropyTransfer(entropy, opts.endpoint)
Expand Down
6 changes: 4 additions & 2 deletions tests/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,11 @@ test('config - get', async t => {
const result = await get(configPath)
t.deepEqual(result, config, 'get works')

const MSG = 'path that does not exist fails'
await get('/tmp/junk')
.then(() => t.fail('bad path should fail'))
.then(() => t.fail(MSG))
.catch(err => {
t.match(err.message, /no such file/, 'bad path should fail')
t.match(err.message, /ENOENT/, MSG)
})
})

Expand All @@ -95,6 +96,7 @@ test('config - set', async t => {
dog: true,
secretKey: makeKey()
}
// @ts-expect-error : this is a breaking test
await set(config, configPath)
const actual = await get(configPath)

Expand Down

0 comments on commit 16bb90e

Please sign in to comment.