From baef0863b7f7a19192f72ca670b4343ff59c7ce9 Mon Sep 17 00:00:00 2001
From: AbigailDeng
Date: Thu, 22 Aug 2024 18:50:58 +0800
Subject: [PATCH 1/5] feat: csv
---
package.json | 3 +-
src/command/baseSubCommand.js | 4 +--
src/command/call.js | 53 ++++++++++++++++++++++++++---------
src/index.js | 2 +-
src/utils/constants.js | 9 ++++--
src/utils/utils.js | 22 ++++++++++++++-
types/utils/constants.d.ts | 6 ++++
yarn.lock | 7 +++++
8 files changed, 86 insertions(+), 20 deletions(-)
diff --git a/package.json b/package.json
index 013cac6..d9d0b05 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "aelf-command",
- "version": "0.1.48",
+ "version": "0.1.49-alpha.0",
"description": "A CLI tools for AElf",
"main": "src/index.js",
"type": "module",
@@ -56,6 +56,7 @@
"check-node-version": "^4.2.1",
"columnify": "^1.6.0",
"commander": "^12.1.0",
+ "csv-parser": "^3.0.0",
"elliptic": "^6.5.5",
"inquirer": "^9.2.22",
"inquirer-date-prompt": "^3.0.0",
diff --git a/src/command/baseSubCommand.js b/src/command/baseSubCommand.js
index 20752af..9e0ef64 100644
--- a/src/command/baseSubCommand.js
+++ b/src/command/baseSubCommand.js
@@ -6,7 +6,7 @@ import inquirer from 'inquirer';
import ora from 'ora';
import { logger } from '../utils/myLogger.js';
import { camelCase } from '../utils/utils.js';
-import { globalOptionsPrompts, strictGlobalOptionValidatorDesc } from '../utils/constants.js';
+import { commonGlobalOptionValidatorDesc, globalOptionsPrompts, strictGlobalOptionValidatorDesc } from '../utils/constants.js';
// Schema.warning = () => {}; // TypeError: Cannot add property warning, object is not extensible
@@ -123,7 +123,7 @@ class BaseSubCommand {
*/
static getUniConfig(commander) {
const result = {};
- ['password', 'endpoint', 'account', 'datadir'].forEach(v => {
+ Object.keys(commonGlobalOptionValidatorDesc).forEach(v => {
const options = commander.opts();
if (options[v]) {
result[v] = options[v];
diff --git a/src/command/call.js b/src/command/call.js
index 3779bdb..3c85282 100644
--- a/src/command/call.js
+++ b/src/command/call.js
@@ -1,6 +1,8 @@
import AElf from 'aelf-sdk';
import inquirer from 'inquirer';
import chalk from 'chalk';
+import { createReadStream } from 'fs';
+import csv from 'csv-parser';
import BaseSubCommand from './baseSubCommand.js';
import { callCommandUsages, callCommandParameters } from '../utils/constants.js';
import {
@@ -9,7 +11,8 @@ import {
getMethod,
promptTolerateSeveralTimes,
getParams,
- parseJSON
+ parseJSON,
+ parseCSV
} from '../utils/utils.js';
import { getWallet } from '../utils/wallet.js';
import { logger } from '../utils/myLogger.js';
@@ -64,6 +67,19 @@ class CallCommand extends BaseSubCommand {
return contractAddress;
}
+ /**
+ * Calls a method with specified parameters.
+ * @param {any} method The method to call.
+ * @param {any} params The parameters for the method call.
+ * @returns {Promise} A promise that resolves with the result of the method call.
+ */
+ async showRes(method, params) {
+ const result = await this.callMethod(method, params);
+ // @ts-ignore
+ logger.info(`\nResult:\n${JSON.stringify(result, null, 2)}`);
+ this.oraInstance.succeed('Succeed!');
+ }
+
/**
* Runs the command.
* @param {Command} commander The Commander instance.
@@ -75,7 +91,7 @@ class CallCommand extends BaseSubCommand {
// @ts-ignore
const { options, subOptions } = await super.run(commander, ...args);
const subOptionsLength = Object.keys(subOptions).length;
- const { endpoint, datadir, account, password } = options;
+ const { endpoint, datadir, account, password, csv } = options;
const aelf = new AElf(new AElf.providers.HttpProvider(endpoint));
try {
let { contractAddress, method, params } = subOptions;
@@ -109,13 +125,16 @@ class CallCommand extends BaseSubCommand {
break;
case 'params':
contractAddress = await getContractInstance(contractAddress, aelf, wallet, this.oraInstance);
-
method = getMethod(method, contractAddress);
-
- params = await getParams(method);
- params = typeof params === 'string' ? params : BaseSubCommand.normalizeConfig(params);
- if (Object.keys(params || {}).length > 0) {
- console.log(chalk.hex('#3753d3')(`The params you entered is:\n${JSON.stringify(params, null, 2)}`));
+ if (csv) {
+ const csvParams = await parseCSV(csv);
+ params = csvParams;
+ } else {
+ params = await getParams(method);
+ params = typeof params === 'string' ? params : BaseSubCommand.normalizeConfig(params);
+ if (Object.keys(params || {}).length > 0) {
+ console.log(chalk.hex('#3753d3')(`The params you entered is:\n${JSON.stringify(params, null, 2)}`));
+ }
}
break;
default:
@@ -124,15 +143,23 @@ class CallCommand extends BaseSubCommand {
}
}
contractAddress = await getContractInstance(contractAddress, aelf, wallet, this.oraInstance);
- params = parseJSON(params);
+ if (Array.isArray(params)) {
+ params.forEach(param => parseJSON(param));
+ } else {
+ params = parseJSON(params);
+ }
+
method = getMethod(method, contractAddress);
if (method.inputTypeInfo && (Object.keys(method.inputTypeInfo.fields).length === 0 || !method.inputTypeInfo.fields)) {
params = '';
}
- const result = await this.callMethod(method, params);
- // @ts-ignore
- logger.info(`\nResult:\n${JSON.stringify(result, null, 2)}`);
- this.oraInstance.succeed('Succeed!');
+ if (Array.isArray(params)) {
+ for (const param of params) {
+ await this.showRes(method, param);
+ }
+ } else {
+ await this.showRes(method, params);
+ }
} catch (e) {
this.oraInstance.fail('Failed!');
// @ts-ignore
diff --git a/src/index.js b/src/index.js
index ccc1528..29b198c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -51,11 +51,11 @@ function init(options) {
commander.option('-e, --endpoint ', 'The URI of an AElf node. Eg: http://127.0.0.1:8000');
commander.option('-a, --account ', 'The address of AElf wallet');
commander.option('-p, --password ', 'The password of encrypted keyStore');
-
commander.option(
'-d, --datadir ',
`The directory that contains the AElf related files. Default to be ${userHomeDir}/aelf`
);
+ commander.option('-c, --csv ', 'The location of the CSV file containing the parameters.');
const rc = new RC();
Object.values(commands).forEach(Value => {
const command = new Value(rc);
diff --git a/src/utils/constants.js b/src/utils/constants.js
index 1448bb4..6a7859c 100644
--- a/src/utils/constants.js
+++ b/src/utils/constants.js
@@ -366,6 +366,11 @@ const commonGlobalOptionValidatorDesc = {
type: 'string',
required: false,
message: 'set a valid account address in global config file or passed by -a '
+ },
+ csv: {
+ type: 'string',
+ required: false,
+ message: 'set params in csv file by -c '
}
};
@@ -374,8 +379,8 @@ const strictGlobalOptionValidatorDesc = /**@type {CommonGlobalOptionValidatorDes
// @ts-ignore
Object.entries(commonGlobalOptionValidatorDesc).forEach((/** @type {[CommonGlobalOptionKey, any]} */ [key, value]) => {
strictGlobalOptionValidatorDesc[key] = {
- ...value,
- required: true
+ ...value
+ // required: true
};
});
diff --git a/src/utils/utils.js b/src/utils/utils.js
index f9c510c..7649f8d 100644
--- a/src/utils/utils.js
+++ b/src/utils/utils.js
@@ -9,6 +9,8 @@ import _camelCase from 'camelcase';
import inquirer from 'inquirer';
import { plainLogger } from './myLogger.js';
import protobuf from '@aelfqueen/protobufjs';
+import { createReadStream } from 'fs';
+import csv from 'csv-parser';
const { load } = protobuf;
/**
@@ -462,6 +464,23 @@ async function deserializeLogs(aelf, logs = []) {
return results;
}
+const parseCSV = async address => {
+ let results = [];
+ const stream = createReadStream(address).pipe(csv());
+ for await (const data of stream) {
+ const cleanData = {};
+ for (const key in data) {
+ const cleanKey = key.replace(/\n/g, '').trim();
+ const cleanValue = typeof data[key] === 'string' ? data[key].replace(/\n/g, '').trim() : data[key];
+ if (cleanValue !== '') {
+ cleanData[cleanKey] = cleanValue;
+ }
+ }
+ Object.keys(cleanData).length && results.push(cleanData);
+ }
+ return results;
+};
+
export {
promisify,
camelCase,
@@ -474,5 +493,6 @@ export {
parseJSON,
randomId,
getParams,
- deserializeLogs
+ deserializeLogs,
+ parseCSV
};
diff --git a/types/utils/constants.d.ts b/types/utils/constants.d.ts
index 27b245d..f75d942 100644
--- a/types/utils/constants.d.ts
+++ b/types/utils/constants.d.ts
@@ -36,11 +36,17 @@ export interface AccountValidatorDesc {
required: boolean;
message: string;
}
+export interface CSVValidatorDesc {
+ type: string;
+ required: boolean;
+ message: string;
+}
export interface CommonGlobalOptionValidatorDesc {
password: PasswordValidatorDesc;
endpoint: EndpointValidatorDesc;
datadir: DatadirValidatorDesc;
account: AccountValidatorDesc;
+ csv: CSVValidatorDesc;
}
export const commonGlobalOptionValidatorDesc: CommonGlobalOptionValidatorDesc;
diff --git a/yarn.lock b/yarn.lock
index 142e902..9e95dd7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3085,6 +3085,13 @@ crypto-random-string@^4.0.0:
dependencies:
type-fest "^1.0.1"
+csv-parser@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz#b88a6256d79e090a97a1b56451f9327b01d710e7"
+ integrity sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==
+ dependencies:
+ minimist "^1.2.0"
+
cz-conventional-changelog@3.3.0, cz-conventional-changelog@^3.3.0:
version "3.3.0"
resolved "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-3.3.0.tgz#9246947c90404149b3fe2cf7ee91acad3b7d22d2"
From e88e75423b627e0a1b327ceded8ae249e91df1f9 Mon Sep 17 00:00:00 2001
From: AbigailDeng
Date: Fri, 23 Aug 2024 16:17:42 +0800
Subject: [PATCH 2/5] feat: unit test csv
---
jest.config.js | 2 +-
test/command/call.test.js | 22 +++++++++++++++++++++-
test/command/proposal.test.js | 2 --
test/constants.js | 1 +
test/test.csv | 3 +++
5 files changed, 26 insertions(+), 4 deletions(-)
create mode 100644 test/test.csv
diff --git a/jest.config.js b/jest.config.js
index 58bb7d5..f8e3fac 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -146,7 +146,7 @@ export default {
// The glob patterns Jest uses to detect test files
testMatch: [
- // '**/test/utils/Logger.test.js'
+ // '**/test/command/proposal.test.js'
'**/test/command/dappServer/socket-sign.test.js',
'**/test/**/?(*.)+(spec|test).[jt]s?(x)'
// "**/?(*.)+(spec|test).[tj]s?(x)"
diff --git a/test/command/call.test.js b/test/command/call.test.js
index e4e52af..dcfc744 100644
--- a/test/command/call.test.js
+++ b/test/command/call.test.js
@@ -7,7 +7,7 @@ import { callCommandUsages, callCommandParameters } from '../../src/utils/consta
import { getContractInstance } from '../../src/utils/utils.js';
import { userHomeDir } from '../../src/utils/userHomeDir.js';
import { logger } from '../../src/utils/myLogger.js';
-import { endpoint as endPoint, account, password, dataDir } from '../constants.js';
+import { endpoint as endPoint, account, password, dataDir, csvDir } from '../constants.js';
const sampleRc = { getConfigs: jest.fn() };
jest.mock('../../src/utils/myLogger');
@@ -102,6 +102,26 @@ describe('CallCommand', () => {
expect(logger.info).toHaveBeenCalled();
});
+ test('should run with csv', async () => {
+ inquirer.prompt = questions =>
+ Promise.resolve({
+ symbol: 'ELF',
+ owner: 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk'
+ });
+ const commander = new Command();
+ commander.option('-e, --endpoint ', 'The URI of an AElf node. Eg: http://127.0.0.1:8000');
+ commander.option('-a, --account ', 'The address of AElf wallet');
+ commander.option('-p, --password ', 'The password of encrypted keyStore');
+ commander.option(
+ '-d, --datadir ',
+ `The directory that contains the AElf related files. Default to be ${userHomeDir}/aelf`
+ );
+ commander.option('-c, --csv ', 'The location of the CSV file containing the parameters.');
+ commander.parse([process.argv[0], '', 'call', '-e', endPoint, '-a', account, '-p', password, '-d', dataDir, '-c', csvDir]);
+ await callCommand.run(commander, 'AElf.ContractNames.Token', 'GetBalance');
+ expect(logger.info).toHaveBeenCalled();
+ });
+
test('should run with invalid parameters', async () => {
inquirer.prompt = backup;
callCommand = new CallCommand(sampleRc, 'call', 'Call a read-only method on a contract.', [
diff --git a/test/command/proposal.test.js b/test/command/proposal.test.js
index e1bf002..1703561 100644
--- a/test/command/proposal.test.js
+++ b/test/command/proposal.test.js
@@ -1,9 +1,7 @@
/* eslint-disable max-len */
import { Command } from 'commander';
-import path from 'path';
import inquirer from 'inquirer';
import AElf from 'aelf-sdk';
-import chalk from 'chalk';
import moment from 'moment';
import ProposalCommand from '../../src/command/proposal.js';
import { userHomeDir } from '../../src/utils/userHomeDir.js';
diff --git a/test/constants.js b/test/constants.js
index 943dc4e..14f8e0e 100644
--- a/test/constants.js
+++ b/test/constants.js
@@ -4,3 +4,4 @@ export const endpoint = 'https://tdvw-test-node.aelf.io/';
export const account = 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk';
export const password = '1234*Qwer';
export const dataDir = path.resolve(__dirname, './dataDir/aelf');
+export const csvDir = path.resolve(__dirname, './test.csv');
diff --git a/test/test.csv b/test/test.csv
new file mode 100644
index 0000000..d326ec4
--- /dev/null
+++ b/test/test.csv
@@ -0,0 +1,3 @@
+symbol,"owner
+"
+ELF,GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk
From 5aa83301d6e9fb364df4b1ae7a168733302ef410 Mon Sep 17 00:00:00 2001
From: AbigailDeng
Date: Fri, 23 Aug 2024 16:44:36 +0800
Subject: [PATCH 3/5] feat: parseCSV unit test
---
jest.config.js | 4 ++--
test/utils/utils.test.js | 12 ++++++++++--
2 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/jest.config.js b/jest.config.js
index f8e3fac..a2105fd 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -33,7 +33,7 @@ export default {
coveragePathIgnorePatterns: ['/node_modules/', '/src/utils/constants.js', '/src/command/index.js'],
// A list of reporter names that Jest uses when writing coverage reports
- coverageReporters: ['text', 'json-summary'],
+ coverageReporters: ['text', 'json-summary', 'html'],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: null,
@@ -146,7 +146,7 @@ export default {
// The glob patterns Jest uses to detect test files
testMatch: [
- // '**/test/command/proposal.test.js'
+ // '**/test/utils/utils.test.js'
'**/test/command/dappServer/socket-sign.test.js',
'**/test/**/?(*.)+(spec|test).[jt]s?(x)'
// "**/?(*.)+(spec|test).[tj]s?(x)"
diff --git a/test/utils/utils.test.js b/test/utils/utils.test.js
index d6301e4..74a19ba 100644
--- a/test/utils/utils.test.js
+++ b/test/utils/utils.test.js
@@ -15,10 +15,11 @@ import {
parseJSON,
randomId,
getParams,
- deserializeLogs
+ deserializeLogs,
+ parseCSV
} from '../../src/utils/utils';
import { plainLogger } from '../../src/utils/myLogger';
-import { endpoint, account, password, dataDir } from '../constants.js';
+import { endpoint, account, password, dataDir, csvDir } from '../constants.js';
jest.mock('inquirer');
@@ -391,4 +392,11 @@ describe('utils', () => {
expect(result).toEqual(null);
});
});
+
+ describe('parseCSV', () => {
+ test('test parse csv file', async () => {
+ const results = await parseCSV(csvDir);
+ expect(results).toEqual([{ owner: 'GyQX6t18kpwaD9XHXe1ToKxfov8mSeTLE9q9NwUAeTE8tULZk', symbol: 'ELF' }]);
+ });
+ });
});
From c1b8b211476ee9ed1991c777f225804d52acd9f8 Mon Sep 17 00:00:00 2001
From: AbigailDeng
Date: Fri, 23 Aug 2024 16:50:58 +0800
Subject: [PATCH 4/5] feat: delete coverage in workflow
---
.github/workflows/coverage.yml | 3 ---
package.json | 2 +-
2 files changed, 1 insertion(+), 4 deletions(-)
diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml
index af3ea8a..f1c2b57 100644
--- a/.github/workflows/coverage.yml
+++ b/.github/workflows/coverage.yml
@@ -4,9 +4,6 @@ on:
push:
branches:
- master
- pull_request:
- branches:
- - master
jobs:
test:
diff --git a/package.json b/package.json
index d9d0b05..048dc16 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "aelf-command",
- "version": "0.1.49-alpha.0",
+ "version": "0.1.49-beta.0",
"description": "A CLI tools for AElf",
"main": "src/index.js",
"type": "module",
From c6e1d8f12f53aca5a846e7f0b7c56536c6213fb4 Mon Sep 17 00:00:00 2001
From: AbigailDeng
Date: Fri, 23 Aug 2024 17:03:02 +0800
Subject: [PATCH 5/5] feat: readme
---
README.md | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index 8f9ee57..75f41b2 100644
--- a/README.md
+++ b/README.md
@@ -11,20 +11,17 @@
- | Branch | Tests | Coverage |
-|--------------|-----------------|----------------|
+| Branch | Tests | Coverage |
+| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| `master` | ![Tests](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/AElfProject/aelf-command/feature/badge-json/master-test-results.json) | ![Coverage](https://AElfProject.github.io/aelf-command/badges.svg) |
-
-
-
## Descriptions
_A CLI tools built for AElf_
## Features
-- Get or Set common configs, `endpoint`, `account`, `datadir`, `password`.
+- Get or Set common configs, `endpoint`, `account`, `datadir`, `password`, `csv`.
- For new users who are not familiar with the CLI parameters, any missing parameters will be asked in a prompting way.
- Create a new `account`.
- Load an account from a given `private key` or `mnemonic`.
@@ -157,6 +154,7 @@ Options:
-a, --account The address of AElf wallet
-p, --password The password of encrypted keyStore
-d, --datadir The directory that contains the AElf related files. Defaults to {home}/.local/share/aelf
+ -c, --csv The location of the CSV file containing the parameters.
-h, --help output usage information
Commands:
@@ -218,6 +216,7 @@ aelf-command console
- `endpoint`: The endpoint for the RPC service.
- `account`: The account to be used to interact with the blockchain `endpoint`.
- `password`: The password for unlocking the given `account`.
+- `csv>`: The location of the CSV file containing the parameters.
You can specified options above in several ways, and the priority is in the order of low to high.