Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix config (again) #229

Merged
merged 2 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
25 changes: 16 additions & 9 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
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,10 +18,9 @@ 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')
mixmix marked this conversation as resolved.
Show resolved Hide resolved
mixmix marked this conversation as resolved.
Show resolved Hide resolved
.addOption(accountOption())
.addOption(endpointOption())
// NOTE: I think this is currently unused
.addOption(
new Option(
'-d, --dev',
Expand All @@ -33,10 +33,17 @@ program
.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
Loading