Skip to content

Commit

Permalink
feat: add select-node, select-compiler commands
Browse files Browse the repository at this point in the history
  • Loading branch information
davidyuk committed Sep 5, 2022
1 parent 78a9953 commit 87b2ede
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 15 deletions.
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@aeternity/aepp-sdk": "^12.1.2",
"bignumber.js": "^9.1.0",
"commander": "^9.3.0",
"env-paths": "^2.2.1",
"fs-extra": "^10.1.0",
"prompts": "^2.4.2"
},
Expand Down
114 changes: 105 additions & 9 deletions src/commands/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
*/
// We'll use `commander` for parsing options
import { Command } from 'commander';
import { NODE_URL, COMPILER_URL } from '../utils/constant';
import prompts from 'prompts';
import { Node, Compiler } from '@aeternity/aepp-sdk';
import { compilerOption, nodeOption } from '../arguments';
import { addToConfig } from '../utils/config';
import CliError from '../utils/CliError';

const program = new Command();

Expand All @@ -37,17 +41,109 @@ const EXECUTABLE_CMD = [
// You get get CLI version by exec `aecli version`
program.version(process.env.npm_package_version);

// ## Initialize `config` command
// ## Initialize `child` command's
EXECUTABLE_CMD.forEach(({ name, desc }) => program.command(name, desc));

async function getNodeDescription(url) {
// TODO: remove after fixing https://github.com/aeternity/aepp-sdk-js/issues/1673
const omitUncaughtExceptions = () => {};
process.on('uncaughtException', omitUncaughtExceptions);
const nodeInfo = await (new Node(url)).getNodeInfo().catch(() => {});
process.off('uncaughtException', omitUncaughtExceptions);
return nodeInfo
? `network id ${nodeInfo.nodeNetworkId}, version ${nodeInfo.version}`
: 'can\'t get node info';
}

async function getCompilerDescription(url) {
// TODO: remove after fixing https://github.com/aeternity/aepp-sdk-js/issues/1673
const omitUncaughtExceptions = () => {};
process.on('uncaughtException', omitUncaughtExceptions);
const { apiVersion } = await (new Compiler(url)).aPIVersion().catch(() => ({}));
process.off('uncaughtException', omitUncaughtExceptions);
return apiVersion ? `version ${apiVersion}` : 'can\'t get compiler version';
}

program
.command('config')
.description('Print the sdk default configuration')
.action(() => {
// TODO: show these values https://github.com/aeternity/aepp-cli-js/issues/174
console.log('NODE_URL', NODE_URL);
console.log('COMPILER_URL', COMPILER_URL);
.description('Print the current sdk configuration')
.addOption(nodeOption)
.addOption(compilerOption)
.action(async ({ url, compilerUrl }) => {
console.log('Node', url, await getNodeDescription(url));
console.log('Compiler', compilerUrl, await getCompilerDescription(compilerUrl));
});

// ## Initialize `child` command's
EXECUTABLE_CMD.forEach(({ name, desc }) => program.command(name, desc));
async function askUrl(entity, choices, getDescription, _url) {
let url = _url;
if (url == null) {
const getChoices = async (withDescriptions) => [
...await Promise.all(choices.map(async (choice) => ({
title: choice.name,
value: choice.url,
description: withDescriptions ? await getDescription(choice.url) : 'version loading...',
}))),
{
title: 'Enter URL',
value: 'custom-url',
},
];

let loadingDescription = false;
url = (await prompts({
type: 'select',
name: 'url',
message: `Select a ${entity} to use in other commands`,
choices: await getChoices(false),
async onRender() {
if (loadingDescription) return;
loadingDescription = true;
this.choices = await getChoices(true);
this.render();
},
})).url;

if (url === 'custom-url') {
url = (await prompts({
type: 'text',
name: 'url',
message: `Enter a ${entity} url to use in other commands`,
})).url;
}

if (url == null) process.exit(0);
}
try {
return (new URL(url)).toString();
} catch (error) {
throw new CliError(error.message);
}
}

program
.command('select-node')
.argument('[nodeUrl]', 'Node URL')
.description('Specify node to use in other commands')
.action(async (url) => {
const nodes = [
{ name: 'Mainnet', url: 'https://mainnet.aeternity.io/' },
{ name: 'Testnet', url: 'https://testnet.aeternity.io/' },
];
await addToConfig({ url: await askUrl('node', nodes, getNodeDescription, url) });
});

program
.command('select-compiler')
.argument('[compilerUrl]', 'Compiler URL')
.description('Specify compiler to use in other commands')
.action(async (url) => {
const compilers = [
{ name: 'Stable', url: 'https://compiler.aeternity.io/' },
{ name: 'Latest', url: 'https://latest.compiler.aeternity.io/' },
];
await addToConfig({
compilerUrl: await askUrl('compiler', compilers, getCompilerDescription, url),
});
});

export default program;
2 changes: 2 additions & 0 deletions src/utils/CliError.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { InvalidPasswordError } from '@aeternity/aepp-sdk';
import { setCommandOptions } from './config';

export default class CliError extends Error {
constructor(message) {
Expand All @@ -9,6 +10,7 @@ export default class CliError extends Error {

export async function runProgram(program) {
try {
await setCommandOptions(program);
await program.parseAsync();
} catch (error) {
if (
Expand Down
28 changes: 28 additions & 0 deletions src/utils/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import envPaths from 'env-paths';
import fs from 'fs-extra';
import path from 'path';

const configPath = path.resolve(envPaths('aecli').config, 'config.json');
const options = ['url', 'compilerUrl'];

async function readConfig() {
try {
return await fs.readJson(configPath);
} catch {
return {};
}
}

export async function addToConfig(configPart) {
await fs.outputJson(configPath, { ...await readConfig(), ...configPart });
}

const configPromise = readConfig();

export async function setCommandOptions(program) {
const config = await configPromise;
options
.filter((option) => option in config)
.forEach((option) => program.setOptionValueWithSource(option, config[option], 'config'));
program.commands.forEach(setCommandOptions);
}
4 changes: 3 additions & 1 deletion test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ export async function executeProgram(program, args) {
try {
const allArgs = [
...args.map((arg) => arg.toString()),
...['config', 'decode', 'sign', 'unpack'].includes(args[0]) ? [] : ['--url', url],
...[
'config', 'decode', 'sign', 'unpack', 'select-node', 'select-compiler',
].includes(args[0]) ? [] : ['--url', url],
...args[0] === 'contract' ? ['--compilerUrl', compilerUrl] : [],
];
if (allArgs.some((a) => !['string', 'number'].includes(typeof a))) {
Expand Down
24 changes: 19 additions & 5 deletions test/other.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,24 @@ import mainProgram from '../src/commands/main';

describe('Other tests', () => {
it('Config', async () => {
const config = await executeProgram(mainProgram, ['config']);
expect(config).to.include('NODE_URL');
expect(config).to.include('COMPILER_URL');
expect(config).to.not.include('undefined');
expect(config.split('http')).to.have.length(3);
expect(await executeProgram(mainProgram, ['config'])).to.equal(
'Node https://testnet.aeternity.io network id ae_uat, version 6.6.0\n'
+ 'Compiler https://compiler.aepps.com version 6.1.0',
);
});

it('selects node', async () => {
expect(await executeProgram(mainProgram, ['select-node', 'http://example.com/node']))
.to.equal('');
});

it('fails if invalid url', async () => {
await expect(executeProgram(mainProgram, ['select-node', 'example.com/node']))
.to.be.rejectedWith('Invalid URL');
});

it('selects compiler', async () => {
expect(await executeProgram(mainProgram, ['select-compiler', 'http://example.com/node']))
.to.equal('');
});
});

0 comments on commit 87b2ede

Please sign in to comment.