diff --git a/badges.svg b/badges.svg index b5ff2bf..847b7e3 100644 --- a/badges.svg +++ b/badges.svg @@ -1,14 +1,14 @@ coverage: 95.95% - + - - + + - + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ 95.8% + Statements + 936/977 +
+ + +
+ 88.8% + Branches + 349/393 +
+ + +
+ 97.57% + Functions + 161/165 +
+ + +
+ 95.95% + Lines + 925/964 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
src +
+
95.45%42/44100%14/14100%7/795.45%42/44
src/command +
+
98.42%375/38192.62%113/122100%64/6498.4%370/376
src/command/dappServer +
+
98.76%239/24291.11%82/9096.96%32/3399.15%236/238
src/rc +
+
100%47/47100%16/16100%17/17100%46/46
src/utils +
+
88.37%228/25882.11%124/15193.18%41/4488.62%226/255
test +
+
100%5/5100%0/0100%0/0100%5/5
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/prettify.css b/prettify.css new file mode 100644 index 0000000..b317a7c --- /dev/null +++ b/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/prettify.js b/prettify.js new file mode 100644 index 0000000..b322523 --- /dev/null +++ b/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/sort-arrow-sprite.png b/sort-arrow-sprite.png new file mode 100644 index 0000000..6ed6831 Binary files /dev/null and b/sort-arrow-sprite.png differ diff --git a/sorter.js b/sorter.js new file mode 100644 index 0000000..2bb296a --- /dev/null +++ b/sorter.js @@ -0,0 +1,196 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + if ( + row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()) + ) { + row.style.display = ''; + } else { + row.style.display = 'none'; + } + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/src/command/baseSubCommand.js.html b/src/command/baseSubCommand.js.html new file mode 100644 index 0000000..c9a0325 --- /dev/null +++ b/src/command/baseSubCommand.js.html @@ -0,0 +1,826 @@ + + + + + + Code coverage report for src/command/baseSubCommand.js + + + + + + + + + +
+
+

All files / src/command baseSubCommand.js

+
+ +
+ 100% + Statements + 80/80 +
+ + +
+ 100% + Branches + 32/32 +
+ + +
+ 100% + Functions + 22/22 +
+ + +
+ 100% + Lines + 77/77 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248  +  +22x +  +  +  +  +  +  +  +  +  +22x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +147x +147x +147x +147x +147x +147x +147x +147x +147x +147x +675x +  +  +  +  +  +  +  +  +  +  +11x +  +  +  +  +  +  +78x +  +78x +11x +  +78x +  +3x +1x +  +  +  +1x +1x +1x +1x +  +  +  +  +  +  +  +  +79x +  +119x +119x +119x +  +  +  +  +  +  +  +  +  +5x +  +5x +5x +  +  +  +  +  +  +  +  +  +48x +48x +240x +240x +159x +  +  +48x +  +  +  +  +  +  +  +  +  +283x +3x +  +280x +2x +  +278x +  +  +  +  +  +  +  +  +  +  +  +113x +113x +285x +5x +  +280x +  +113x +  +  +  +  +  +  +  +  +  +  +  +  +47x +47x +71x +70x +70x +  +  +  +47x +47x +47x +10x +  +47x +  +47x +47x +  +  +  +  +47x +141x +  +47x +47x +  +  +  +47x +47x +47x +  +1x +  +47x +47x +  +10x +  +  +  +  +  +37x +37x +1x +1x +  +  +  +  +37x +  +  +  +  +  +  +  +  +  +  +2x +  +  +  +  + 
import { interopImportCJSDefault } from 'node-cjs-interop';
+import asyncValidator from 'async-validator';
+const Schema = interopImportCJSDefault(asyncValidator);
+ 
+import inquirer from 'inquirer';
+import ora from 'ora';
+import { logger } from '../utils/myLogger.js';
+import { camelCase } from '../utils/utils.js';
+import { commonGlobalOptionValidatorDesc, globalOptionsPrompts, strictGlobalOptionValidatorDesc } from '../utils/constants.js';
+ 
+// Schema.warning = () => {}; // TypeError: Cannot add property warning, object is not extensible
+ 
+const defaultOraOptions = {
+  text: 'AElf loading...'
+};
+/**
+ * @typedef {import('commander').Command} Command
+ * @typedef {import('ora').Options} OraOptions
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+ 
+/**
+ * @class
+ */
+class BaseSubCommand {
+  /**
+   * @param {string} commandName sub command name
+   * @param {{ [key: string]: any }[]} parameters sub command parameters
+   * @param {string} description sub command description
+   * @param {{ [key: string]: any }[]} options sub command options
+   * @param {string[]} usage make examples
+   * @param {Registry} rc instance of Registry
+   * @param {{ [key: string]: any }} validatorDesc rules of async-validator
+   * @param {{ [key: string]: any }} oraOptions an ora options
+   */
+  constructor(
+    commandName,
+    parameters = [],
+    description,
+    options = [],
+    usage = [],
+    rc,
+    validatorDesc = strictGlobalOptionValidatorDesc,
+    oraOptions = defaultOraOptions
+  ) {
+    this.commandName = commandName;
+    this.parameters = parameters;
+    this.description = description;
+    this.options = options;
+    this.validatorDesc = {};
+    this.usage = usage;
+    this.rc = rc;
+    this.oraInstance = ora(oraOptions);
+    this.customPrompts = false;
+    Object.entries(validatorDesc).forEach(([key, value]) => {
+      this.validatorDesc[key] = {
+        ...strictGlobalOptionValidatorDesc[key],
+        ...value
+      };
+    });
+  }
+  /**
+   * Sets custom prompts.
+   * @param {any} val - The value to set for custom prompts.
+   */
+  setCustomPrompts(val) {
+    this.customPrompts = val;
+  }
+  /**
+   * Initializes the sub command with commander.
+   * @param {Command} commander - The commander instance.
+   */
+  init(commander) {
+    let command = commander.command(`${this.commandName} ${this.getParameters()}`).description(this.description);
+ 
+    for (const { flag, description } of this.options) {
+      command = command.option(flag, description);
+    }
+    command
+      .action(async (...args) => {
+        await this.run(commander, ...args);
+        this.oraInstance.stop();
+      })
+      .on('--help', () => {
+        // todo: chalk
+        console.info('');
+        console.info('Examples:');
+        console.info('');
+        console.info(`${this.makeExamples().join('\n')}`);
+      });
+  }
+ 
+  /**
+   * Retrieves parameters as a string.
+   * @returns {string} Parameters string.
+   */
+  getParameters() {
+    return this.parameters
+      .map(v => {
+        const { name, required = false, extraName = [] } = v;
+        const symbol = [name, ...extraName].join('|');
+        return required ? `<${symbol}>` : `[${symbol}]`;
+      })
+      .join(' ');
+  }
+ 
+  /**
+   * Handles errors related to universal options.
+   * @param {any} error - The error to handle.
+   */
+  handleUniOptionsError(error) {
+    const { errors = [] } = error;
+    // @ts-ignore
+    logger.error(errors.reduce((acc, i) => `${acc}${i.message}\n`, ''));
+    process.exit(1);
+  }
+ 
+  /**
+   * Retrieves universal configuration.
+   * @static
+   * @param {Command} commander - The commander instance.
+   * @returns {Record<string, any>} Universal configuration.
+   */
+  static getUniConfig(commander) {
+    const result = {};
+    Object.keys(commonGlobalOptionValidatorDesc).forEach(v => {
+      const options = commander.opts();
+      if (options[v]) {
+        result[v] = options[v];
+      }
+    });
+    return result;
+  }
+ 
+  /**
+   * Parses a boolean value.
+   * @static
+   * @param {any} val - The value to parse.
+   * @returns {any} Parsed boolean value.
+   */
+  static parseBoolean(val) {
+    if (val === 'true') {
+      return true;
+    }
+    if (val === 'false') {
+      return false;
+    }
+    return val;
+  }
+ 
+  /**
+   * Normalizes configuration object.
+   * @static
+   * @param {any} obj - The configuration object to normalize.
+   * @returns {Record<string, any>} Normalized configuration object.
+   */
+  static normalizeConfig(obj) {
+    // dash to camel-case
+    // 'true', 'false' to true, false
+    const result = {};
+    Object.entries(obj).forEach(([key, value]) => {
+      if (value === '' || value === null || value === undefined) {
+        return;
+      }
+      result[camelCase(key)] = BaseSubCommand.parseBoolean(value);
+    });
+    return result;
+  }
+  /**
+   * Runs the sub command.
+   * @param {Command} commander - The commander instance.
+   * @param {...any} args - Additional arguments.
+   * @returns {Promise<{
+   *   localOptions: { [key: string]: any },
+   *   options: { [key: string]: any },
+   *   subOptions: { [key: string]: any }
+   * } | void>} Promise resolving to options or void.
+   */
+  async run(commander, ...args) {
+    let subCommandOptions = {};
+    args.slice(0, this.parameters.length).forEach((v, i) => {
+      if (v !== undefined) {
+        const { name, filter = val => val } = this.parameters[i];
+        subCommandOptions[name] = filter(v);
+      }
+    });
+    // sub command options
+    const lastArg = args.slice(this.parameters.length)[0];
+    const localOptions = {};
+    this.options.forEach(({ name }) => {
+      localOptions[name] = lastArg?.[name] || undefined;
+    });
+    const uniOptions = BaseSubCommand.getUniConfig(commander);
+    // get options from global config and process.argv
+    const rc = await this.rc.getConfigs();
+    let options = BaseSubCommand.normalizeConfig({
+      ...rc,
+      ...uniOptions
+    });
+ 
+    const globalPrompts = globalOptionsPrompts.filter(
+      prompt => this.validatorDesc[prompt.name]?.required && !options[prompt.name]
+    );
+    const globalPromptsAns = await inquirer.prompt(globalPrompts);
+    options = {
+      ...options,
+      ...globalPromptsAns
+    };
+    this.validator = new Schema(this.validatorDesc);
+    try {
+      await this.validator.validate(options);
+    } catch (e) {
+      this.handleUniOptionsError(e);
+    }
+    subCommandOptions = BaseSubCommand.normalizeConfig(subCommandOptions);
+    if (this.customPrompts) {
+      // custom prompts process
+      return {
+        localOptions,
+        options,
+        subOptions: subCommandOptions
+      };
+    }
+    const subOptionsLength = Object.keys(subCommandOptions).length;
+    if (subOptionsLength < this.parameters.length) {
+      const response = BaseSubCommand.normalizeConfig(await inquirer.prompt(this.parameters.slice(subOptionsLength)));
+      subCommandOptions = {
+        ...subCommandOptions,
+        ...response
+      };
+    }
+    return {
+      localOptions,
+      options,
+      subOptions: subCommandOptions
+    };
+  }
+  /**
+   * Generates examples for usage.
+   * @returns {string[]} Array of example strings.
+   */
+  makeExamples() {
+    return this.usage.map(cmd => `aelf-command ${this.commandName} ${cmd}`);
+  }
+}
+ 
+export default BaseSubCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/call.js.html b/src/command/call.js.html new file mode 100644 index 0000000..39dbe76 --- /dev/null +++ b/src/command/call.js.html @@ -0,0 +1,598 @@ + + + + + + Code coverage report for src/command/call.js + + + + + + + + + +
+
+

All files / src/command call.js

+
+ +
+ 98.07% + Statements + 51/52 +
+ + +
+ 85.71% + Branches + 24/28 +
+ + +
+ 100% + Functions + 6/6 +
+ + +
+ 98.03% + Lines + 50/51 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +20x +  +  +  +  +  +  +  +  +5x +5x +5x +5x +  +  +  +  +  +  +  +  +  +2x +2x +2x +  +  +  +  +  +  +  +  +  +4x +  +4x +4x +  +  +  +  +  +  +  +  +  +5x +  +5x +5x +5x +5x +5x +5x +5x +5x +4x +6x +  +1x +  +  +  +  +  +  +  +  +1x +  +1x +  +1x +  +  +  +  +  +  +  +  +1x +  +3x +3x +3x +1x +1x +  +2x +1x +1x +1x +  +  +2x +  +1x +  +  +  +4x +4x +1x +  +3x +  +  +4x +4x +  +  +4x +1x +1x +  +  +3x +  +  +1x +  +1x +  +  +  +  +  + 
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 {
+  getContractMethods,
+  getContractInstance,
+  getMethod,
+  promptTolerateSeveralTimes,
+  getParams,
+  parseJSON,
+  parseCSV
+} from '../utils/utils.js';
+import { getWallet } from '../utils/wallet.js';
+import { logger } from '../utils/myLogger.js';
+ 
+/**
+ * @typedef {import('commander').Command} Command
+ * @typedef {import('ora').Options} OraOptions
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+class CallCommand extends BaseSubCommand {
+  /**
+   * Creates an instance of CallCommand.
+   * @param {Registry} rc The instance of the Registry.
+   * @param {string} [name] The name of the command.
+   * @param {string} [description] The description of the command.
+   * @param {Object[]} [parameters] The parameters for the command.
+   * @param {string[]} [usage] The usage examples for the command.
+   * @param {any[]} [options] The options for the command.
+   */
+  constructor(
+    rc,
+    name = 'call',
+    description = 'Call a read-only method on a contract.',
+    parameters = callCommandParameters,
+    usage = callCommandUsages,
+    options = []
+  ) {
+    super(name, parameters, description, options, usage, rc);
+  }
+  /**
+   * Calls a method with specified parameters.
+   * @param {any} method The method to call.
+   * @param {any} params The parameters for the method call.
+   * @returns {Promise<any>} A promise that resolves with the result of the method call.
+   */
+  async callMethod(method, params) {
+    this.oraInstance.start('Calling method...');
+    const result = await method.call(params);
+    this.oraInstance.succeed('Calling method successfully!');
+    return result;
+  }
+  /**
+   * Processes address after prompting for input.
+   * @param {any} aelf The AElf instance.
+   * @param {any} wallet The wallet instance.
+   * @param {Object.<string, any>} answerInput The input parameters.
+   * @returns {Promise<any>} A promise that resolves with the processed result.
+   */
+  async processAddressAfterPrompt(aelf, wallet, answerInput) {
+    let { contractAddress } = BaseSubCommand.normalizeConfig(answerInput);
+    contractAddress = await getContractInstance(contractAddress, aelf, wallet, this.oraInstance);
+    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<any>} 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.
+   * @param {...any[]} args Additional arguments passed to the command.
+   * @returns {Promise<void>} A promise that resolves when the command execution completes.
+   */
+  async run(commander, ...args) {
+    this.setCustomPrompts(true);
+    // @ts-ignore
+    const { options, subOptions } = await super.run(commander, ...args);
+    const subOptionsLength = Object.keys(subOptions).length;
+    const { endpoint, datadir, account, password, csv } = options;
+    const aelf = new AElf(new AElf.providers.HttpProvider(endpoint));
+    try {
+      let { contractAddress, method, params } = subOptions;
+      const wallet = getWallet(datadir, account, password);
+      if (subOptionsLength < this.parameters.length) {
+        for (const prompt of this.parameters.slice(subOptionsLength)) {
+          switch (prompt.name) {
+            case 'contract-address':
+              contractAddress = await promptTolerateSeveralTimes(
+                {
+                  times: 3,
+                  prompt,
+                  processAfterPrompt: this.processAddressAfterPrompt.bind(this, aelf, wallet),
+                  pattern: /^((?!null).)*$/
+                },
+                this.oraInstance
+              );
+              break;
+            case 'method':
+              contractAddress = await getContractInstance(contractAddress, aelf, wallet, this.oraInstance);
+ 
+              method = getMethod(
+                (
+                  await inquirer.prompt({
+                    ...prompt,
+                    choices: getContractMethods(contractAddress)
+                  })
+                ).method,
+                contractAddress
+              );
+              break;
+            case 'params':
+              contractAddress = await getContractInstance(contractAddress, aelf, wallet, this.oraInstance);
+              method = getMethod(method, contractAddress);
+              if (csv) {
+                const csvParams = await parseCSV(csv);
+                params = csvParams;
+              } else {
+                params = await getParams(method);
+                params = typeof params === 'string' ? params : BaseSubCommand.normalizeConfig(params);
+                Eif (Object.keys(params || {}).length > 0) {
+                  console.log(chalk.hex('#3753d3')(`The params you entered is:\n${JSON.stringify(params, null, 2)}`));
+                }
+              }
+              break;
+            default:
+              break;
+          }
+        }
+      }
+      contractAddress = await getContractInstance(contractAddress, aelf, wallet, this.oraInstance);
+      if (Array.isArray(params)) {
+        params.forEach(param => parseJSON(param));
+      } else {
+        params = parseJSON(params);
+      }
+ 
+      method = getMethod(method, contractAddress);
+      Iif (method.inputTypeInfo && (Object.keys(method.inputTypeInfo.fields).length === 0 || !method.inputTypeInfo.fields)) {
+        params = '';
+      }
+      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
+      logger.fatal(e);
+    }
+  }
+}
+ 
+export default CallCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/config.js.html b/src/command/config.js.html new file mode 100644 index 0000000..7b2b26f --- /dev/null +++ b/src/command/config.js.html @@ -0,0 +1,487 @@ + + + + + + Code coverage report for src/command/config.js + + + + + + + + + +
+
+

All files / src/command config.js

+
+ +
+ 93.93% + Statements + 31/33 +
+ + +
+ 100% + Branches + 12/12 +
+ + +
+ 100% + Functions + 6/6 +
+ + +
+ 93.93% + Lines + 31/33 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135  +  +3x +  +  +  +  +3x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +12x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +6x +6x +6x +  +1x +  +  +  +  +  +  +  +  +  +2x +  +43x +1x +  +42x +  +42x +  +  +  +  +  +  +  +  +  +4x +  +4x +  +4x +4x +4x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +4x +  +1x +  +1x +1x +  +1x +1x +1x +  +1x +1x +1x +  +1x +1x +1x +  +  +  +  +  +  +  +  +  +  + 
import { interopImportCJSDefault } from 'node-cjs-interop';
+import asyncValidator from 'async-validator';
+const Schema = interopImportCJSDefault(asyncValidator);
+import BaseSubCommand from './baseSubCommand.js';
+import { configCommandParameters, configCommandUsage, commonGlobalOptionValidatorDesc } from '../utils/constants.js';
+import { logger } from '../utils/myLogger.js';
+ 
+const configCommandValidatorDesc = {
+  ...commonGlobalOptionValidatorDesc,
+  endpoint: {
+    ...commonGlobalOptionValidatorDesc.endpoint,
+    required: false
+  }
+};
+ 
+/**
+ * @typedef {import('commander').Command} Command
+ * @typedef {import('async-validator').Rules} Rules
+ * @typedef {import('async-validator').Values} Values
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+class ConfigCommand extends BaseSubCommand {
+  /**
+   * Constructs a new ConfigCommand instance.
+   * @param {Registry} rc - The registry instance.
+   */
+  constructor(rc) {
+    super(
+      'config',
+      configCommandParameters,
+      'Get, set, delete or list aelf-command config',
+      [],
+      configCommandUsage,
+      rc,
+      configCommandValidatorDesc
+    );
+  }
+  /**
+   * Validates parameters based on the provided rules.
+   * @param {Rules} rule - The validation rules.
+   * @param {Values} parameters - The parameters to validate.
+   * @returns {Promise<void>} A promise that resolves when the validation is complete.
+   */
+  async validateParameters(rule, parameters) {
+    const validator = new Schema(rule);
+    try {
+      await validator.validate(parameters);
+    } catch (e) {
+      this.handleUniOptionsError(e);
+    }
+  }
+ 
+  /**
+   * Handles the list operation and returns the processed content as a string.
+   * @param {any} content - The content to process.
+   * @returns {string} The processed content.
+   */
+  handleList(content) {
+    return Object.entries(content)
+      .filter(([, value]) => {
+        if (value === '' || value === undefined || value === null) {
+          return false;
+        }
+        return true;
+      })
+      .map(([key, value]) => `${key}=${value}\n`)
+      .join('');
+  }
+  /**
+   * Executes the command.
+   * @param {Command} commander - The commander instance.
+   * @param {...any} args - Additional arguments.
+   * @returns {Promise<void>} A promise that resolves when the command execution is complete.
+   */
+  async run(commander, ...args) {
+    this.setCustomPrompts(true);
+    // @ts-ignore
+    const { subOptions } = await super.run(commander, ...args);
+    // todo: specified which .aelfrc file to read or write
+    const { flag, key, value } = subOptions;
+    try {
+      await this.validateParameters(
+        {
+          flag: {
+            type: 'enum',
+            enum: ['set', 'get', 'delete', 'list'],
+            required: true,
+            message: 'Flag must one of set, get, list, delete'
+          },
+          key: {
+            type: 'string',
+            required: ['get', 'set', 'delete'].includes(flag),
+            message: 'You need to enter the <key>'
+          },
+          value: {
+            type: 'string',
+            required: flag === 'set',
+            message: 'You need to enter the correct <value> for config set',
+            // The follow validator will get the pattern if the [key] in commonGlobalOptionValidatorDesc.
+            // At the same time avoid an error if [key] is not in.
+            pattern: (key in commonGlobalOptionValidatorDesc && commonGlobalOptionValidatorDesc[key].pattern) || null
+          }
+        },
+        subOptions
+      );
+      let result = null;
+      switch (flag) {
+        case 'get':
+          result = this.rc.getOption(key);
+          // @ts-ignore
+          logger.info(result);
+          break;
+        case 'set':
+          this.rc.saveOption(key, value);
+          this.oraInstance.succeed('Succeed!');
+          break;
+        case 'list':
+          result = this.rc.getFileConfigs();
+          console.log(`\n${this.handleList(result)}`);
+          break;
+        case 'delete':
+          result = this.rc.deleteConfig(key);
+          this.oraInstance.succeed('Succeed!');
+          break;
+      }
+    } catch (e) {
+      this.oraInstance.fail('Failed!');
+      // @ts-ignore
+      logger.error(e);
+    }
+  }
+}
+ 
+export default ConfigCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/console.js.html b/src/command/console.js.html new file mode 100644 index 0000000..b433887 --- /dev/null +++ b/src/command/console.js.html @@ -0,0 +1,340 @@ + + + + + + Code coverage report for src/command/console.js + + + + + + + + + +
+
+

All files / src/command console.js

+
+ +
+ 100% + Statements + 16/16 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 16/16 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +7x +  +  +  +  +  +  +  +  +  +  +2x +2x +2x +2x +2x +2x +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +2x +  +  +  +  +  +  +1x +  +  +1x +1x +1x +  +1x +  +1x +  +  +  +  +  + 
import repl from 'repl';
+import AElf from 'aelf-sdk';
+import columnify from 'columnify';
+import boxen from 'boxen';
+import BaseSubCommand from './baseSubCommand.js';
+import { getWallet } from '../utils/wallet.js';
+import { logger } from '../utils/myLogger.js';
+ 
+/**
+ * @typedef {import('commander').Command} Command
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+class ConsoleCommand extends BaseSubCommand {
+  /**
+   * Constructs a new ConsoleCommand instance.
+   * @param {Registry} rc - The registry instance.
+   * @param {string} [name] - The name of the command.
+   * @param {string} [description] - The description of the command.
+   */
+  constructor(rc, name = 'console', description = 'Open a node REPL') {
+    super(name, [], description, [], [''], rc);
+  }
+ 
+  /**
+   * Executes the command.
+   * @param {Command} commander - The commander instance.
+   * @param {...any} args - Additional arguments.
+   * @returns {Promise<void>} A promise that resolves when the command execution is complete.
+   */
+  async run(commander, ...args) {
+    // @ts-ignore
+    const { options } = await super.run(commander, ...args);
+    const { datadir, account, password, endpoint } = options;
+    try {
+      const aelf = new AElf(new AElf.providers.HttpProvider(endpoint));
+      const wallet = getWallet(datadir, account, password);
+      this.oraInstance.succeed('Succeed!');
+      const columns = columnify(
+        [
+          {
+            Name: 'AElf',
+            description: 'imported from aelf-sdk'
+          },
+          {
+            Name: 'aelf',
+            description: `instance of aelf-sdk, connect to ${endpoint}`
+          },
+          {
+            Name: '_account',
+            description: `instance of AElf wallet, wallet address is ${account}`
+          }
+        ],
+        {
+          minWidth: 10,
+          columnSplitter: ' | ',
+          config: {
+            description: { maxWidth: 40 }
+          }
+        }
+      );
+      // @ts-ignore
+      logger.info('Welcome to aelf interactive console. Ctrl + C to terminate the program. Double tap Tab to list objects');
+      // @ts-ignore
+      logger.info(
+        boxen(columns, {
+          padding: 1,
+          margin: 1,
+          borderStyle: 'double'
+        })
+      );
+      const r = repl.start({
+        prompt: '>'
+      });
+      r.context.AElf = AElf;
+      r.context.aelf = aelf;
+      r.context._account = wallet;
+    } catch (e) {
+      this.oraInstance.fail('Failed!');
+      // @ts-ignore
+      logger.error(e);
+    }
+  }
+}
+ 
+export default ConsoleCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/create.js.html b/src/command/create.js.html new file mode 100644 index 0000000..6fe29df --- /dev/null +++ b/src/command/create.js.html @@ -0,0 +1,328 @@ + + + + + + Code coverage report for src/command/create.js + + + + + + + + + +
+
+

All files / src/command create.js

+
+ +
+ 100% + Statements + 20/20 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 20/20 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82  +  +  +  +  +  +  +3x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +8x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +3x +  +3x +  +3x +  +3x +  +3x +  +3x +  +3x +3x +3x +3x +3x +3x +2x +1x +  +1x +  +  +1x +  +1x +  +  +  +  +  + 
import AElf from 'aelf-sdk';
+import chalk from 'chalk';
+import BaseSubCommand from './baseSubCommand.js';
+import { commonGlobalOptionValidatorDesc, createCommandParameters, createCommandUsage } from '../utils/constants.js';
+import { saveKeyStore } from '../utils/wallet.js';
+import { logger } from '../utils/myLogger.js';
+ 
+const createCommandValidatorDesc = {
+  ...commonGlobalOptionValidatorDesc,
+  endpoint: {
+    ...commonGlobalOptionValidatorDesc.endpoint,
+    required: false
+  }
+};
+/**
+ * @typedef {import('commander').Command} Command
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+ 
+class CreateCommand extends BaseSubCommand {
+  /**
+   * Constructs a new CreateCommand instance.
+   * @param {Registry} rc - The registry instance.
+   */
+  constructor(rc) {
+    super(
+      'create',
+      createCommandParameters,
+      'Create a new account',
+      [
+        {
+          flag: '-c, --cipher [cipher]',
+          name: 'cipher',
+          description: 'Which cipher algorithm to use, default to be aes-128-ctr'
+        }
+      ],
+      createCommandUsage,
+      rc,
+      createCommandValidatorDesc
+    );
+  }
+  /**
+   * Executes the create command.
+   * @param {Command} commander - The commander instance.
+   * @param {...any} args - Additional arguments.
+   * @returns {Promise<void>} A promise that resolves when the command execution is complete.
+   */
+  async run(commander, ...args) {
+    const wallet = AElf.wallet.createNewWallet();
+    wallet.publicKey = wallet.keyPair.getPublic().encode('hex');
+    // @ts-ignore
+    logger.info('Your wallet info is :');
+    // @ts-ignore
+    logger.info(`Mnemonic            : ${wallet.mnemonic}`);
+    // @ts-ignore
+    logger.info(`Private Key         : ${wallet.privateKey}`);
+    // @ts-ignore
+    logger.info(`Public Key          : ${wallet.publicKey}`);
+    // @ts-ignore
+    logger.info(`Address             : ${wallet.address}`);
+    // @ts-ignore
+    const { localOptions, options, subOptions } = await super.run(commander, ...args);
+    const { datadir } = options;
+    const { saveToFile } = subOptions;
+    const { cipher } = localOptions;
+    try {
+      if (saveToFile === true || saveToFile === 'true') {
+        const keyStorePath = await saveKeyStore(wallet, datadir, cipher);
+        this.oraInstance.succeed(`Account info has been saved to \"${chalk.underline(keyStorePath)}\"`);
+      } else {
+        this.oraInstance.succeed('Succeed!');
+      }
+    } catch (e) {
+      this.oraInstance.fail('Failed!');
+      // @ts-ignore
+      logger.error(e);
+    }
+  }
+}
+ 
+export default CreateCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/dappServer/HKDF.js.html b/src/command/dappServer/HKDF.js.html new file mode 100644 index 0000000..02eca5a --- /dev/null +++ b/src/command/dappServer/HKDF.js.html @@ -0,0 +1,247 @@ + + + + + + Code coverage report for src/command/dappServer/HKDF.js + + + + + + + + + +
+
+

All files / src/command/dappServer HKDF.js

+
+ +
+ 100% + Statements + 22/22 +
+ + +
+ 100% + Branches + 4/4 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 21/21 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55  +  +  +  +  +  +  +  +  +5x +1x +  +4x +4x +4x +4x +4x +4x +4x +  +  +  +  +  +  +  +  +  +  +3x +3x +3x +3x +3x +3x +3x +3x +3x +3x +  +3x +  +  +  +  +  +  +7x +  +  +  +  +  +  + 
import { createHmac } from 'crypto';
+ 
+class HKDF {
+  /**
+   * @param {string} hash
+   * @param {Buffer} salt
+   * @param {string} initialKey
+   */
+  constructor(hash, salt, initialKey) {
+    if (!HKDF.hashList[hash]) {
+      throw new Error('not supported hash method');
+    }
+    this.hashMethod = hash;
+    this.hashLength = HKDF.hashList[hash];
+    this.salt = salt;
+    this.initialKey = initialKey;
+    const hmac = createHmac(hash, this.salt);
+    hmac.update(this.initialKey);
+    this.prk = hmac.digest();
+  }
+ 
+  /**
+   * Expands the pseudorandom key to the desired length.
+   * @param {string} [info] - Optional context and application-specific information.
+   * @param {number} [size] - The length of the output keying material in bytes.
+   * @returns {Buffer} - The expanded keying material.
+   */
+  expand(info = '', size = 32) {
+    /** @type {string | Buffer} */
+    let pre = '';
+    const output = [];
+    const numBlocks = Math.ceil(size / this.hashLength);
+    for (let i = 0; i < numBlocks; i++) {
+      const hmac = createHmac(this.hashMethod, this.prk);
+      hmac.update(pre);
+      hmac.update(info);
+      hmac.update(Buffer.alloc(1, i + 1));
+      pre = hmac.digest();
+      output.push(pre);
+    }
+    return Buffer.concat(output, size);
+  }
+}
+/**
+ * A static property that maps hash algorithm names to their output lengths in bytes.
+ * @type {{ [key: string]: number }}
+ */
+HKDF.hashList = {
+  sha256: 32,
+  sha224: 54,
+  sha512: 64
+};
+ 
+export default HKDF;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/dappServer/constants.js.html b/src/command/dappServer/constants.js.html new file mode 100644 index 0000000..7d67a67 --- /dev/null +++ b/src/command/dappServer/constants.js.html @@ -0,0 +1,124 @@ + + + + + + Code coverage report for src/command/dappServer/constants.js + + + + + + + + + +
+
+

All files / src/command/dappServer constants.js

+
+ +
+ 100% + Statements + 1/1 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 100% + Lines + 1/1 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +145x +  +  +  +  +  +  +  +  +  +  +  +  + 
const CHAIN_APIS = {
+  '/api/blockChain/chainStatus': 'getChainStatus',
+  '/api/blockChain/blockState': 'getChainState',
+  '/api/blockChain/contractFileDescriptorSet': 'getContractFileDescriptorSet',
+  '/api/blockChain/blockHeight': 'getBlockHeight',
+  '/api/blockChain/block': 'getBlock',
+  '/api/blockChain/blockByHeight': 'getBlockByHeight',
+  '/api/blockChain/transactionResult': 'getTxResult',
+  '/api/blockChain/transactionResults': 'getTxResults',
+  '/api/blockChain/merklePathByTransactionId': 'getMerklePathByTxId'
+};
+ 
+export { CHAIN_APIS };
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/dappServer/encrypt.js.html b/src/command/dappServer/encrypt.js.html new file mode 100644 index 0000000..4c8f41d --- /dev/null +++ b/src/command/dappServer/encrypt.js.html @@ -0,0 +1,298 @@ + + + + + + Code coverage report for src/command/dappServer/encrypt.js + + + + + + + + + +
+
+

All files / src/command/dappServer encrypt.js

+
+ +
+ 100% + Statements + 21/21 +
+ + +
+ 100% + Branches + 3/3 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 21/21 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72  +  +  +  +  +6x +6x +6x +  +6x +  +  +  +  +  +  +  +  +  +  +  +  +2x +1x +  +2x +2x +2x +2x +2x +2x +  +  +  +  +  +  +  +  +1x +1x +1x +1x +1x +  +  +  +  +  +  +  +  +  +  +  +  +1x +1x +1x +  +  +  +  +  +  +  +1x +  +  +  +  + 
import Crypto from 'crypto';
+import elliptic from 'elliptic';
+import { randomId } from '../../utils/utils.js';
+import HKDF from './HKDF.js';
+ 
+const defaultEncryptAlgorithm = 'curve25519';
+const defaultCipher = 'aes-256-cbc';
+const defaultEc = elliptic.ec(defaultEncryptAlgorithm);
+ 
+const keyPairs = {
+  [defaultEncryptAlgorithm]: defaultEc.genKeyPair()
+};
+ 
+class Encrypt {
+  /**
+   * Creates an instance of Encrypt.
+   * @param {string} algorithm - The algorithm to use for encryption.
+   * @param {string} remotePublicKey - The public key of the remote party.
+   * @param {string} random - A random string used for key generation.
+   * @param {string} [cipher] - The cipher to use for encryption (optional).
+   */
+  constructor(algorithm, remotePublicKey, random, cipher = defaultCipher) {
+    if (!keyPairs[algorithm]) {
+      keyPairs[algorithm] = elliptic.ec(algorithm).genKeyPair();
+    }
+    this.keyPair = keyPairs[algorithm];
+    this.cipher = cipher;
+    this.remoteKeyPair = elliptic.ec(algorithm).keyFromPublic(remotePublicKey, 'hex');
+    this.sharedKey = Buffer.from(this.keyPair.derive(this.remoteKeyPair.getPublic()).toString('hex'), 'hex');
+    const hkdf = new HKDF('sha256', Buffer.from(random, 'hex'), this.sharedKey.toString('hex'));
+    this.derivedKey = hkdf.expand();
+  }
+ 
+  /**
+   * Encrypts data using the specified algorithm and shared key.
+   * @param {string} data - The data to encrypt.
+   * @returns {{ encryptedResult: string, iv: string }} - The encrypted data and initialization vector.
+   */
+  encrypt(data) {
+    const iv = randomId();
+    const cipher = Crypto.createCipheriv(this.cipher, this.derivedKey, Buffer.from(iv, 'hex'));
+    let encrypted = cipher.update(Buffer.from(data, 'base64'), undefined, 'base64');
+    encrypted += cipher.final('base64');
+    return {
+      encryptedResult: encrypted,
+      iv
+    };
+  }
+ 
+  /**
+   * Decrypts data using the specified algorithm and shared key.
+   * @param {string} encrypted - The encrypted data to decrypt.
+   * @param {string} iv - The initialization vector used during encryption.
+   * @returns {string} - The decrypted data.
+   */
+  decrypt(encrypted, iv) {
+    const decipher = Crypto.createDecipheriv(this.cipher, this.derivedKey, Buffer.from(iv, 'hex'));
+    const decrypted = Buffer.concat([decipher.update(Buffer.from(encrypted, 'base64')), decipher.final()]).toString('base64');
+    return decrypted;
+  }
+ 
+  /**
+   * Gets the public key of the key pair.
+   * @returns {string} - The public key in hexadecimal format.
+   */
+  getPublicKey() {
+    return this.keyPair.getPublic('hex');
+  }
+}
+ 
+export default Encrypt;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/dappServer/index.html b/src/command/dappServer/index.html new file mode 100644 index 0000000..bfa4506 --- /dev/null +++ b/src/command/dappServer/index.html @@ -0,0 +1,206 @@ + + + + + + Code coverage report for src/command/dappServer + + + + + + + + + +
+
+

All files src/command/dappServer

+
+ +
+ 98.76% + Statements + 239/242 +
+ + +
+ 91.11% + Branches + 82/90 +
+ + +
+ 96.96% + Functions + 32/33 +
+ + +
+ 99.15% + Lines + 236/238 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
HKDF.js +
+
100%22/22100%4/4100%2/2100%21/21
constants.js +
+
100%1/1100%0/0100%0/0100%1/1
encrypt.js +
+
100%21/21100%3/3100%4/4100%21/21
index.js +
+
100%13/13100%1/1100%2/2100%13/13
sign.js +
+
92.59%25/27100%2/2100%5/592.59%25/27
socket.js +
+
99.29%140/14188.73%63/7194.11%16/17100%138/138
utils.js +
+
100%17/17100%9/9100%3/3100%17/17
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/dappServer/index.js.html b/src/command/dappServer/index.js.html new file mode 100644 index 0000000..6537726 --- /dev/null +++ b/src/command/dappServer/index.js.html @@ -0,0 +1,262 @@ + + + + + + Code coverage report for src/command/dappServer/index.js + + + + + + + + + +
+
+

All files / src/command/dappServer index.js

+
+ +
+ 100% + Statements + 13/13 +
+ + +
+ 100% + Branches + 1/1 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 13/13 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60  +  +  +  +  +  +3x +  +  +  +  +  +  +  +3x +  +  +  +  +  +  +  +  +  +  +7x +  +  +  +  +  +  +  +  +  +2x +2x +2x +2x +2x +2x +2x +  +  +  +  +  +  +  +1x +  +1x +  +1x +  +  +  +  +  + 
import AElf from 'aelf-sdk';
+import BaseSubCommand from '../baseSubCommand.js';
+import { getWallet } from '../../utils/wallet.js';
+import { logger } from '../../utils/myLogger.js';
+import Socket from './socket.js';
+ 
+const commandOptions = [
+  {
+    flag: '--port [port]',
+    name: 'port',
+    description: 'Which port to listen on, the default port is 35443'
+  }
+];
+ 
+const commandUsage = ['-port port', ''];
+/**
+ * @typedef {import('commander').Command} Command
+ * @typedef {import('../../../types/rc/index.js').default} Registry
+ */
+class DeployCommand extends BaseSubCommand {
+  /**
+   * Creates an instance of DeployCommand.
+   * @param {Registry} rc - The registry instance.
+   */
+  constructor(rc) {
+    super('dapp-server', [], 'Start a dAPP SOCKET.IO server', commandOptions, commandUsage, rc);
+  }
+  /**
+   * Runs the dappServer command.
+   * @param {Command} commander - The commander instance.
+   * @param {...any} args - Additional arguments.
+   * @returns {Promise<void>} A promise that resolves when the command is complete.
+   */
+  async run(commander, ...args) {
+    // @ts-ignore
+    const { options, localOptions } = await super.run(commander, ...args);
+    const { endpoint, datadir, account, password } = options;
+    const { port = 35443 } = localOptions;
+    try {
+      const aelf = new AElf(new AElf.providers.HttpProvider(endpoint));
+      const wallet = getWallet(datadir, account, password);
+      const socket = new Socket({
+        port,
+        endpoint,
+        aelf,
+        wallet,
+        address: account
+      });
+      // @ts-ignore
+      logger.info(`DApp server is listening on port ${port}`);
+    } catch (e) {
+      this.oraInstance.fail('Failed!');
+      // @ts-ignore
+      logger.error(e);
+    }
+  }
+}
+ 
+export default DeployCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/dappServer/sign.js.html b/src/command/dappServer/sign.js.html new file mode 100644 index 0000000..a7eed00 --- /dev/null +++ b/src/command/dappServer/sign.js.html @@ -0,0 +1,346 @@ + + + + + + Code coverage report for src/command/dappServer/sign.js + + + + + + + + + +
+
+

All files / src/command/dappServer sign.js

+
+ +
+ 92.59% + Statements + 25/27 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 92.59% + Lines + 25/27 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88  +  +6x +6x +  +6x +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +1x +1x +1x +1x +  +  +  +  +1x +1x +1x +  +  +  +  +  +  +2x +1x +  +2x +2x +  +  +  +  +  +  +  +  +1x +1x +  +  +  +  +  +  +  +  +  +1x +1x +1x +1x +  +  +  +  +1x +1x +1x +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +  + 
import elliptic from 'elliptic';
+ 
+const defaultEncryptAlgorithm = 'secp256k1';
+const defaultEc = elliptic.ec(defaultEncryptAlgorithm);
+ 
+const keyPairs = {
+  [defaultEncryptAlgorithm]: defaultEc.genKeyPair()
+};
+ 
+class Sign {
+  /**
+   * static verify
+   * @param {string} algorithm ec algorithm
+   * @param {string} publicKey hex string, remote dapp public key
+   * @param {Buffer} msg message to be verified
+   * @param {string} signature remote dapp signature
+   * @return {boolean} result
+   */
+  static verify(algorithm, publicKey, msg, signature) {
+    const remoteKeyPair = elliptic.ec(algorithm).keyFromPublic(publicKey, 'hex');
+    const r = signature.slice(0, 64);
+    const s = signature.slice(64, 128);
+    const recoveryParam = signature.slice(128);
+    const signatureObj = {
+      r,
+      s,
+      recoveryParam
+    };
+    try {
+      const result = remoteKeyPair.verify(msg, signatureObj);
+      return result;
+    } catch (e) {
+      return false;
+    }
+  }
+ 
+  constructor(algorithm, publicKey) {
+    if (!keyPairs[algorithm]) {
+      keyPairs[algorithm] = elliptic.ec(algorithm).genKeyPair();
+    }
+    this.keyPair = keyPairs[algorithm];
+    this.remoteKeyPair = elliptic.ec(algorithm).keyFromPublic(publicKey, 'hex');
+  }
+ 
+  /**
+   * sign message
+   * @param {Buffer} msg message to be signed
+   * @return {string} signature
+   */
+  sign(msg) {
+    const signedMsg = this.keyPair.sign(msg);
+    return [signedMsg.r.toString(16, 64), signedMsg.s.toString(16, 64), `0${signedMsg.recoveryParam.toString()}`].join('');
+  }
+ 
+  /**
+   * verify signature
+   * @param {Buffer} msg message to be verified
+   * @param {string} signature hex string
+   * @return {boolean} result
+   */
+  verify(msg, signature) {
+    const r = signature.slice(0, 64);
+    const s = signature.slice(64, 128);
+    const recoveryParam = signature.slice(128);
+    const signatureObj = {
+      r,
+      s,
+      recoveryParam
+    };
+    try {
+      const result = this.remoteKeyPair.verify(msg, signatureObj);
+      return result;
+    } catch (e) {
+      return false;
+    }
+  }
+ 
+  /**
+   * Gets the public key.
+   * @returns {string} The public key.
+   */
+  getPublicKey() {
+    return this.keyPair.getPublic().encode('hex');
+  }
+}
+ 
+export default Sign;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/dappServer/socket.js.html b/src/command/dappServer/socket.js.html new file mode 100644 index 0000000..22c08a4 --- /dev/null +++ b/src/command/dappServer/socket.js.html @@ -0,0 +1,1513 @@ + + + + + + Code coverage report for src/command/dappServer/socket.js + + + + + + + + + +
+
+

All files / src/command/dappServer socket.js

+
+ +
+ 99.29% + Statements + 140/141 +
+ + +
+ 88.73% + Branches + 63/71 +
+ + +
+ 94.11% + Functions + 16/17 +
+ + +
+ 100% + Lines + 138/138 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477  +  +  +  +5x +  +  +  +  +  +  +  +  +  +  +  +5x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +5x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +5x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +5x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +5x +5x +5x +5x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +24x +24x +24x +24x +24x +24x +24x +  +  +  +24x +24x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +45x +13x +  +  +  +  +  +  +  +  +  +32x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +45x +45x +1x +1x +  +  +  +  +  +  +  +24x +45x +  +45x +45x +45x +45x +  +24x +21x +  +3x +2x +  +4x +2x +  +  +5x +4x +  +4x +2x +  +1x +1x +  +  +  +3x +  +1x +  +32x +32x +11x +  +32x +  +  +13x +  +13x +13x +13x +10x +  +13x +  +  +  +  +  +  +  +  +  +17x +17x +1x +  +16x +7x +7x +7x +7x +1x +  +6x +6x +5x +  +1x +  +9x +9x +9x +9x +9x +8x +  +1x +  +  +  +  +  +  +  +21x +7x +7x +7x +  +  +  +  +14x +14x +  +  +  +  +  +  +  +24x +24x +24x +14x +14x +14x +14x +14x +  +  +  +  +  +  +14x +14x +  +  +  +  +10x +9x +9x +9x +9x +1x +  +8x +1x +  +7x +  +  +  +  +  +  +7x +7x +7x +7x +7x +  +  +  +  +  +  +1x +1x +  +  +  +  +  +  +  +4x +2x +2x +2x +2x +198x +  +  +  +  +  +  +  +  +3x +3x +3x +3x +1x +  +2x +2x +2x +  +  +  +  +  +  +  +4x +4x +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +5x +5x +5x +5x +5x +5x +1x +  +  +4x +2x +  +2x +  +4x +  +  +  +  +  +  +  +  +  +1x +1x +1x +  +  +  +  + 
import { Server } from 'socket.io';
+import AElf from 'aelf-sdk';
+import { interopImportCJSDefault } from 'node-cjs-interop';
+import asyncValidator from 'async-validator';
+const Schema = interopImportCJSDefault(asyncValidator);
+import Sign from './sign.js';
+import Encrypt from './encrypt.js';
+import { logger } from '../../utils/myLogger.js';
+import { randomId } from '../../utils/utils.js';
+import { serializeMessage, deserializeMessage, checkTimestamp } from './utils.js';
+import { CHAIN_APIS } from './constants.js';
+ 
+/**
+ * @typedef {import ('async-validator').Rules} Rules
+ */
+/** @type {Rules} */
+const signRequestRules = {
+  id: {
+    required: true,
+    type: 'string',
+    min: 32
+  },
+  appId: {
+    required: true,
+    type: 'string',
+    min: 32
+  },
+  action: {
+    required: true,
+    type: 'string'
+  },
+  params: {
+    required: true,
+    type: 'object',
+    fields: {
+      originalParams: {
+        type: 'string',
+        required: true
+      },
+      signature: {
+        type: 'string',
+        required: true,
+        min: 129,
+        max: 130
+      }
+    }
+  }
+};
+/** @type {Rules} */
+const encryptRequestRules = {
+  ...signRequestRules,
+  params: {
+    type: 'object',
+    required: true,
+    fields: {
+      iv: {
+        type: 'string',
+        required: true
+      },
+      encryptedParams: {
+        type: 'string',
+        required: true
+      }
+    }
+  }
+};
+/** @type {Rules} */
+const connectSignRules = {
+  ...signRequestRules,
+  params: {
+    type: 'object',
+    required: true,
+    fields: {
+      encryptAlgorithm: {
+        type: 'enum',
+        enum: ['secp256k1'],
+        required: true
+      },
+      timestamp: {
+        type: 'number',
+        required: true
+      },
+      publicKey: {
+        type: 'string',
+        required: true
+      },
+      signature: {
+        type: 'string',
+        required: true,
+        min: 129,
+        max: 130
+      }
+    }
+  }
+};
+/** @type {Rules} */
+const connectEncryptRules = {
+  ...signRequestRules,
+  params: {
+    type: 'object',
+    required: true,
+    fields: {
+      encryptAlgorithm: {
+        type: 'enum',
+        enum: ['curve25519'],
+        required: true
+      },
+      cipher: {
+        type: 'string',
+        required: true
+      },
+      publicKey: {
+        type: 'string',
+        required: true
+      }
+    }
+  }
+};
+const signRequestValidator = new Schema(signRequestRules);
+const encryptRequestValidator = new Schema(encryptRequestRules);
+const connectSignValidator = new Schema(connectSignRules);
+const connectEncryptValidator = new Schema(connectEncryptRules);
+ 
+/**
+ * Represents the result of an operation.
+ * @typedef {Object} Result
+ * @property {number} code - The status code of the result.
+ * @property {string} msg - The message associated with the result.
+ * @property {any[]} error - An array of errors, if any.
+ * @property {any} data - The data returned by the operation.
+ */
+ 
+/**
+ * Represents a client connected to the server.
+ * @typedef {Object} Client
+ * @property {function(string, any): void} emit - Sends an event to the client.
+ * @property {function(boolean=): void} disconnect - Disconnects the client.
+ * @property {function(string, function): void} on - Sends an event to the client.
+ */
+ 
+/**
+ * Represents a message sent to the server.
+ * @typedef {Object} Message
+ * @property {string} id - The unique identifier for the message.
+ * @property {string} appId - The application ID sending the message.
+ * @property {string} action - The action to be performed.
+ * @property {any} params - The parameters for the action.
+ */
+class Socket {
+  /**
+   * Creates an instance of Socket.
+   * @param {Object} options - The socket options.
+   * @param {number} options.port - The port to run the socket server.
+   * @param {string} options.endpoint - The default endpoint for the socket server.
+   * @param {any} options.aelf - The AElf instance.
+   * @param {any} options.wallet - The wallet instance.
+   * @param {string} options.address - The address associated with the wallet.
+   */
+  constructor(options) {
+    const { port, endpoint, aelf, wallet, address } = options;
+    this.aelf = aelf;
+    this.defaultEndpoint = endpoint;
+    this.wallet = wallet;
+    this.address = address;
+    this.handleConnection = this.handleConnection.bind(this);
+    this.socket = new Server(port, {
+      transports: ['websocket']
+    });
+    // @ts-ignore
+    this.socket.on('connection', this.handleConnection);
+    this.clientConfig = {
+      // default: {
+      //   encryptWay: 'sign', // or 'encrypt'
+      //   encrypt: new Sign()
+      // }
+    };
+  }
+  /**
+   * Formats the response.
+   * @param {string} id - The request ID.
+   * @param {any} [result] - The result data.
+   * @param {any} [errors] - The errors array.
+   * @returns {{id: string, result: Result}} The formatted result.
+   */
+  responseFormat(id, result, errors) {
+    if (errors && (errors instanceof Error || (Array.isArray(errors) && errors.length > 0) || errors.Error)) {
+      return {
+        id,
+        result: {
+          error: Array.isArray(errors) ? errors : [errors.Error ? errors.Error : errors],
+          code: errors.code || 500,
+          msg: errors.message || 'err happened',
+          data: result
+        }
+      };
+    }
+    return {
+      id,
+      result: {
+        code: 0,
+        msg: 'success',
+        error: [],
+        data: result
+      }
+    };
+  }
+  /**
+   * Sends a response to the client.
+   * @param {Client} client - The client instance.
+   * @param {Result} result - The result object.
+   * @param {string} action - The action type.
+   * @param {string} appId - The application ID.
+   */
+  send(client, result, action, appId) {
+    client.emit('bridge', result);
+    if (action === 'disconnect') {
+      delete this.clientConfig[appId];
+      client.disconnect(true);
+    }
+  }
+  /**
+   * Handles new client connections.
+   * @param {Client} client - The client instance.
+   */
+  handleConnection(client) {
+    client.on('bridge', async data => {
+      logger.info('Message received');
+      /**@type {any} */
+      let result = {};
+      const { action, id, appId } = data;
+      try {
+        switch (action) {
+          case 'connect':
+            result = await this.handleConnect(data);
+            break;
+          case 'api':
+            result = await this.handleApi(data);
+            break;
+          case 'account':
+            result = await this.handleAccount(data);
+            break;
+          case 'invoke':
+          case 'invokeRead':
+            result = await this.handleInvoke(data, action === 'invokeRead');
+            break;
+          case 'getContractMethods':
+            result = await this.handleMethodList(data);
+            break;
+          case 'disconnect':
+            result = await this.handleDisconnect(data);
+            break;
+          case null:
+          case undefined:
+          case '':
+            throw new Error('You should set a action name');
+          default:
+            throw new Error(`${action} is not supported`);
+        }
+        result = this.responseFormat(id, result, null);
+        if (action !== 'connect') {
+          result.result = this.serializeResult(appId, result.result);
+        }
+        this.send(client, result, action, appId);
+      } catch (e) {
+        // @ts-ignore
+        logger.error('error happened');
+        // @ts-ignore
+        logger.error(e);
+        result = this.responseFormat(id, {}, e.errors ? e.errors : e);
+        if (action !== 'connect') {
+          result.result = this.serializeResult(appId, result.result);
+        }
+        this.send(client, result, action, appId);
+      }
+    });
+  }
+  /**
+   * Deserializes request parameters.
+   * @param {Message} request - The request message.
+   * @returns {Promise<any>} The deserialized parameters.
+   */
+  async deserializeParams(request) {
+    const { appId, params } = request;
+    if (!this.clientConfig[appId]) {
+      throw new Error(`AppId ${appId} has not connected`);
+    }
+    if (this.clientConfig[appId].encryptWay === 'sign') {
+      await signRequestValidator.validate(request);
+      const { signature, originalParams } = params;
+      const isValid = this.clientConfig[appId].encrypt.verify(Buffer.from(originalParams, 'base64'), signature);
+      if (!isValid) {
+        throw new Error('Signature is not valid');
+      }
+      const deserializeParams = deserializeMessage(originalParams);
+      if (checkTimestamp(deserializeParams.timestamp)) {
+        return deserializeParams;
+      }
+      throw new Error('Timestamp is not valid');
+    }
+    await encryptRequestValidator.validate(request);
+    const { iv, encryptedParams } = params;
+    const originalParams = this.clientConfig[appId].encrypt.decrypt(encryptedParams, iv);
+    const deserializeParams = deserializeMessage(originalParams);
+    if (checkTimestamp(deserializeParams.timestamp)) {
+      return deserializeParams;
+    }
+    throw new Error('Timestamp is not valid');
+  }
+ 
+  serializeResult(appId, result) {
+    // delete next line as function deserializeParams already has the logic
+    // if (!this.clientConfig[appId]) {
+    //   throw new Error(`AppId ${appId} has not connected`);
+    // }
+    if (this.clientConfig[appId]?.encryptWay === 'sign') {
+      const originalResult = serializeMessage(result);
+      const signature = this.clientConfig[appId].encrypt.sign(Buffer.from(originalResult, 'base64'));
+      return {
+        signature,
+        originalResult
+      };
+    }
+    const originalResult = serializeMessage(result);
+    return this.clientConfig[appId]?.encrypt.encrypt(originalResult);
+  }
+  /**
+   * Handles connect actions.
+   * @param {Message} message - The message object.
+   * @returns {Promise<any>} The result of the connect action.
+   */
+  async handleConnect(message) {
+    const { appId, params } = message;
+    const { encryptAlgorithm, publicKey } = params;
+    if (params.cipher) {
+      await connectEncryptValidator.validate(message);
+      const { cipher } = params;
+      const random = randomId();
+      const encrypt = new Encrypt(encryptAlgorithm, publicKey, random, cipher);
+      this.clientConfig = {
+        ...this.clientConfig,
+        [appId]: {
+          encryptWay: 'encrypt',
+          encrypt
+        }
+      };
+      logger.info(`App ${appId} has connected successfully in message encrypted way`);
+      return {
+        publicKey: encrypt.getPublicKey(),
+        random
+      };
+    }
+    if (params.signature) {
+      await connectSignValidator.validate(message);
+      const { timestamp, signature } = params;
+      const result = Sign.verify(encryptAlgorithm, publicKey, Buffer.from(String(timestamp)), signature);
+      if (!result) {
+        throw new Error('Not a valid signature');
+      }
+      if (!checkTimestamp(timestamp)) {
+        throw new Error('Timestamp is not valid');
+      }
+      this.clientConfig = {
+        ...this.clientConfig,
+        [appId]: {
+          encryptWay: 'sign',
+          encrypt: new Sign(encryptAlgorithm, publicKey)
+        }
+      };
+      const responseRandom = randomId();
+      const responseSignature = this.clientConfig[appId].encrypt.sign(Buffer.from(responseRandom, 'hex'));
+      const responsePublicKey = this.clientConfig[appId].encrypt.getPublicKey();
+      logger.info(`App ${appId} has connected successfully in message signed way`);
+      return {
+        publicKey: responsePublicKey,
+        random: responseRandom,
+        signature: responseSignature
+      };
+    }
+    // @ts-ignore
+    logger.error('Not support encrypt method or not enough params');
+    throw new Error('Not support encrypt method or not enough params');
+  }
+  /**
+   * Handles method list requests.
+   * @param {Message} message - The message object.
+   * @returns {Promise<string[]>} The list of methods.
+   */
+  async handleMethodList(message) {
+    const params = await this.deserializeParams(message);
+    const { endpoint = this.defaultEndpoint, address } = params;
+    this.aelf.setProvider(new AElf.providers.HttpProvider(endpoint || this.defaultEndpoint));
+    const contract = await this.aelf.chain.contractAt(address, this.wallet);
+    return Object.keys(contract)
+      .filter(v => /^[A-Z]/.test(v))
+      .sort();
+  }
+  /**
+   * Handles API requests.
+   * @param {Message} message - The message object.
+   * @returns {Promise<any>} The API result.
+   */
+  async handleApi(message) {
+    const params = await this.deserializeParams(message);
+    const { endpoint = this.defaultEndpoint, apiPath, arguments: apiArgs, methodName } = params;
+    logger.info(`Querying api ${apiPath}...`);
+    if (!CHAIN_APIS[apiPath]) {
+      throw new Error(`Not support api ${apiPath}`);
+    }
+    this.aelf.setProvider(new AElf.providers.HttpProvider(endpoint || this.defaultEndpoint));
+    const result = await this.aelf.chain[methodName](...apiArgs.map(v => v.value));
+    return result;
+  }
+  /**
+   * Handles account actions.
+   * @param {Message} message - The message object.
+   * @returns {Promise<any>} The account result.
+   */
+  async handleAccount(message) {
+    logger.info('Querying account information');
+    await this.deserializeParams(message);
+    // todo: support config file or passed by CLI parameters
+    return {
+      accounts: [
+        {
+          name: 'aelf-command',
+          address: this.address,
+          publicKey: this.wallet.keyPair.getPublic('hex')
+        }
+      ],
+      chains: [
+        {
+          url: this.defaultEndpoint,
+          isMainChain: true,
+          chainId: 'AELF'
+        }
+      ]
+    };
+  }
+  /**
+   * Handles invoke actions.
+   * @param {Message} message - The message object.
+   * @param {boolean} isReadOnly - If the invoke action is read-only.
+   * @returns {Promise<any>} The invoke result.
+   */
+  async handleInvoke(message, isReadOnly) {
+    const params = await this.deserializeParams(message);
+    const { endpoint = this.defaultEndpoint, contractAddress, contractMethod, arguments: contractArgs } = params;
+    logger.info(`${isReadOnly ? 'Calling' : 'Sending'} contract ${contractAddress} method ${contractMethod}...`);
+    this.aelf.setProvider(new AElf.providers.HttpProvider(endpoint || this.defaultEndpoint));
+    const contract = await this.aelf.chain.contractAt(contractAddress, this.wallet);
+    if (!contract[contractMethod]) {
+      throw new Error(`No such method ${contractMethod}`);
+    }
+    let result;
+    if (isReadOnly) {
+      result = await contract[contractMethod].call(...contractArgs.map(v => v.value));
+    } else {
+      result = await contract[contractMethod](...contractArgs.map(v => v.value));
+    }
+    return result;
+  }
+ 
+  /**
+   * Handles disconnect actions.
+   * @param {Message} message - The message object.
+   * @returns {Promise<{}>} The result of the disconnect action.
+   */
+  async handleDisconnect(message) {
+    // just to verify client
+    await this.deserializeParams(message);
+    logger.info(`App ${message.appId} disconnected`);
+    return {};
+  }
+}
+ 
+export default Socket;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/dappServer/utils.js.html b/src/command/dappServer/utils.js.html new file mode 100644 index 0000000..ba7155e --- /dev/null +++ b/src/command/dappServer/utils.js.html @@ -0,0 +1,211 @@ + + + + + + Code coverage report for src/command/dappServer/utils.js + + + + + + + + + +
+
+

All files / src/command/dappServer utils.js

+
+ +
+ 100% + Statements + 17/17 +
+ + +
+ 100% + Branches + 9/9 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 17/17 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43  +  +  +  +  +6x +25x +25x +2x +  +25x +  +  +  +  +  +  +  +6x +9x +9x +9x +  +9x +  +  +  +  +  +  +  +6x +4x +4x +1x +  +3x +3x +3x +  +  +  + 
/**
+ * Serializes a message object to a string.
+ * @param {any} data - The message object to serialize.
+ * @returns {string} The serialized message as a string.
+ */
+const serializeMessage = data => {
+  let result = JSON.stringify(data);
+  if (data === null || data === undefined) {
+    result = '';
+  }
+  return Buffer.from(encodeURIComponent(result)).toString('base64');
+};
+ 
+/**
+ * Deserializes a string to a message object.
+ * @param {string} str - The string to deserialize.
+ * @returns {any} The deserialized message object.
+ */
+const deserializeMessage = str => {
+  let result = decodeURIComponent(Buffer.from(str, 'base64').toString());
+  try {
+    result = JSON.parse(result);
+  } catch (e) {}
+  return result;
+};
+/**
+ * Checks if the given timestamp is within the specified time buffer from the current time.
+ * @param {string} time - The timestamp to check, either as a number or a string.
+ * @param {number} [timeBuffer=300] - The time buffer in seconds. Default is 300 seconds.
+ * @returns {boolean} True if the timestamp is within the time buffer, false otherwise.
+ */
+const checkTimestamp = (time, timeBuffer = 4 * 60) => {
+  const checkTime = parseInt(time, 10);
+  if (!checkTime) {
+    return false;
+  }
+  const now = Math.ceil(new Date().getTime() / 1000);
+  const diff = now - checkTime;
+  return diff >= 0 && diff <= timeBuffer;
+};
+ 
+export { serializeMessage, deserializeMessage, checkTimestamp };
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/deploy.js.html b/src/command/deploy.js.html new file mode 100644 index 0000000..063df99 --- /dev/null +++ b/src/command/deploy.js.html @@ -0,0 +1,184 @@ + + + + + + Code coverage report for src/command/deploy.js + + + + + + + + + +
+
+

All files / src/command deploy.js

+
+ +
+ 100% + Statements + 3/3 +
+ + +
+ 100% + Branches + 3/3 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 3/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34  +  +  +  +3x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +6x +  +  +  +  +  +  +1x +  +  +  +  + 
import chalk from 'chalk';
+import BaseSubCommand from './baseSubCommand.js';
+import { deployCommandParameters, deployCommandUsage } from '../utils/constants.js';
+ 
+const tips = chalk.redBright(
+  'Deprecated! Please use ',
+  chalk.yellowBright('`aelf-command send`'),
+  ', check details in aelf-command `README.md`'
+);
+/**
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+class DeployCommand extends BaseSubCommand {
+  /**
+   * Constructs a new DeployCommand instance.
+   * @param {Registry} rc - The registry instance.
+   * @param {string} [name] - The name of the command.
+   * @param {string} [description] - The description of the command.
+   * @param {string[]} [usage] - The usage information for the command.
+   */
+  constructor(rc, name = 'deploy', description = tips, usage = deployCommandUsage) {
+    super(name, deployCommandParameters, description, [], usage, rc);
+  }
+  /**
+   * Executes the deploy command.
+   * @returns {Promise<void>} A promise that resolves when the command execution is complete.
+   */
+  async run() {
+    console.log(tips);
+  }
+}
+ 
+export default DeployCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/event.js.html b/src/command/event.js.html new file mode 100644 index 0000000..b49a243 --- /dev/null +++ b/src/command/event.js.html @@ -0,0 +1,301 @@ + + + + + + Code coverage report for src/command/event.js + + + + + + + + + +
+
+

All files / src/command event.js

+
+ +
+ 95.23% + Statements + 20/21 +
+ + +
+ 83.33% + Branches + 5/6 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 95% + Lines + 19/20 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +8x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +3x +3x +3x +3x +3x +  +2x +  +1x +1x +  +  +  +1x +1x +1x +1x +  +  +  +1x +  +1x +  +  +  +1x +  +  +1x +  +1x +  +  +  +  +  + 
import AElf from 'aelf-sdk';
+import BaseSubCommand from './baseSubCommand.js';
+import { commonGlobalOptionValidatorDesc, eventCommandParameters, eventCommandUsage } from '../utils/constants.js';
+import { deserializeLogs } from '../utils/utils.js';
+import { logger, plainLogger } from '../utils/myLogger.js';
+/**
+ * @typedef {import('commander').Command} Command
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+class EventCommand extends BaseSubCommand {
+  /**
+   * Constructs a new EventCommand instance.
+   * @param {Registry} rc - The registry instance.
+   */
+ 
+  constructor(rc) {
+    super(
+      'event',
+      eventCommandParameters,
+      'Deserialize the result returned by executing a transaction',
+      [],
+      eventCommandUsage,
+      rc,
+      commonGlobalOptionValidatorDesc
+    );
+  }
+  /**
+   * Executes the event command.
+   * @param {Command} commander - The commander instance.
+   * @param {...any} args - Additional arguments.
+   * @returns {Promise<void>} A promise that resolves when the command execution is complete.
+   */
+  async run(commander, ...args) {
+    // @ts-ignore
+    const { options, subOptions } = await super.run(commander, ...args);
+    const { endpoint } = options;
+    try {
+      const { txId } = subOptions;
+      const aelf = new AElf(new AElf.providers.HttpProvider(endpoint));
+      const txResult = await aelf.chain.getTxResult(txId);
+      // console.log(plainLogger.info(`Transaction ${txId}'s Logs: \n ${JSON.stringify(txResult.Logs, null, 2)}`));
+      if (!txResult.Status || txResult.Status.toUpperCase() !== 'MINED') {
+        // @ts-ignore
+        console.log(plainLogger.info(`Transaction ${txId} is not mined`));
+      } else Iif (!txResult.Logs) {
+        // @ts-ignore
+        console.log(plainLogger.info(`Transaction ${txId} returns void`));
+      } else {
+        this.oraInstance.start('Deserialize Transaction Logs...');
+        let logs = txResult.Logs;
+        const results = await deserializeLogs(aelf, logs);
+        logs = logs.map((item, index) => ({
+          ...item,
+          Result: results[index]
+        }));
+        this.oraInstance.clear();
+ 
+        console.log(
+          // @ts-ignore
+          `\n${plainLogger.info(`\nThe results returned by \nTransaction: ${txId} is: \n${JSON.stringify(logs, null, 2)}`)}`
+        );
+        this.oraInstance.succeed('Succeed!');
+      }
+    } catch (e) {
+      this.oraInstance.fail('Failed!');
+      // @ts-ignore
+      logger.error(e);
+    }
+  }
+}
+ 
+export default EventCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/getBlkHeight.js.html b/src/command/getBlkHeight.js.html new file mode 100644 index 0000000..e5b54cc --- /dev/null +++ b/src/command/getBlkHeight.js.html @@ -0,0 +1,211 @@ + + + + + + Code coverage report for src/command/getBlkHeight.js + + + + + + + + + +
+
+

All files / src/command getBlkHeight.js

+
+ +
+ 100% + Statements + 9/9 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 9/9 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +7x +  +  +  +  +  +  +  +  +  +  +2x +2x +2x +2x +1x +1x +  +  +1x +  +1x +  +  +  +  +  + 
import AElf from 'aelf-sdk';
+import BaseSubCommand from './baseSubCommand.js';
+import { commonGlobalOptionValidatorDesc } from '../utils/constants.js';
+import { logger } from '../utils/myLogger.js';
+ 
+/**
+ * @typedef {import('commander').Command} Command
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+class GetBlkHeightCommand extends BaseSubCommand {
+  /**
+   * Constructs a new GetBlkHeightCommand instance.
+   * @param {Registry} rc - The registry instance.
+   */
+  constructor(rc) {
+    super('get-blk-height', [], 'Get the current block height of specified chain', [], [''], rc, commonGlobalOptionValidatorDesc);
+  }
+ 
+  /**
+   * Executes the get block height command.
+   * @param {Command} commander - The commander instance.
+   * @param {...any} args - Additional arguments.
+   * @returns {Promise<void>} A promise that resolves when the command execution is complete.
+   */
+  async run(commander, ...args) {
+    // @ts-ignore
+    const { options } = await super.run(commander, ...args);
+    const aelf = new AElf(new AElf.providers.HttpProvider(options.endpoint));
+    try {
+      this.oraInstance.start();
+      const height = await aelf.chain.getBlockHeight();
+      this.oraInstance.succeed(`> ${height}`);
+      // todo: chalk or a custom reporter
+    } catch (e) {
+      this.oraInstance.fail('Failed!');
+      // @ts-ignore
+      logger.error(e);
+    }
+  }
+}
+ 
+export default GetBlkHeightCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/getBlkInfo.js.html b/src/command/getBlkInfo.js.html new file mode 100644 index 0000000..44e23da --- /dev/null +++ b/src/command/getBlkInfo.js.html @@ -0,0 +1,355 @@ + + + + + + Code coverage report for src/command/getBlkInfo.js + + + + + + + + + +
+
+

All files / src/command getBlkInfo.js

+
+ +
+ 100% + Statements + 19/19 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 19/19 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91  +  +  +3x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +8x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +3x +3x +  +1x +  +  +  +  +  +  +  +  +  +  +3x +3x +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +3x +3x +3x +  +  +3x +1x +  +2x +  +2x +  +2x +  +1x +  +1x +  +  +  +  +  + 
import AElf from 'aelf-sdk';
+import { interopImportCJSDefault } from 'node-cjs-interop';
+import asyncValidator from 'async-validator';
+const Schema = interopImportCJSDefault(asyncValidator);
+import BaseSubCommand from './baseSubCommand.js';
+import { commonGlobalOptionValidatorDesc, blkInfoCommandParameters, blkInfoCommandUsage } from '../utils/constants.js';
+import { logger } from '../utils/myLogger.js';
+ 
+/**
+ * @typedef {import('commander').Command} Command
+ * @typedef {import('async-validator').Rules} Rules
+ * @typedef {import('async-validator').Values} Values
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+class GetBlkInfoCommand extends BaseSubCommand {
+  /**
+   * Constructs a new GetBlkInfoCommand instance.
+   * @param {Registry} rc - The registry instance.
+   */
+  constructor(rc) {
+    super(
+      'get-blk-info',
+      blkInfoCommandParameters,
+      'Get a block info',
+      [],
+      blkInfoCommandUsage,
+      rc,
+      commonGlobalOptionValidatorDesc
+    );
+  }
+  /**
+   * Validates the provided parameters against the given rules.
+   * @param {Rules} rule - The validation rules.
+   * @param {Values} parameters - The parameters to validate.
+   * @returns {Promise<void>} A promise that resolves when validation is complete.
+   */
+  async validateParameters(rule, parameters) {
+    const validator = new Schema(rule);
+    try {
+      await validator.validate(parameters);
+    } catch (e) {
+      this.handleUniOptionsError(e);
+    }
+  }
+  /**
+   * Executes the get block info command.
+   * @param {Command} commander - The commander instance.
+   * @param {...any} args - Additional arguments.
+   * @returns {Promise<void>} A promise that resolves when the command execution is complete.
+   */
+  async run(commander, ...args) {
+    // @ts-ignore
+    const { options, subOptions } = await super.run(commander, ...args);
+    await this.validateParameters(
+      {
+        height: {
+          type: 'string',
+          required: true,
+          message: 'Input a valid <height|block-hash>'
+        },
+        includeTxs: {
+          type: 'boolean',
+          message: 'Input a valid <include-txs>'
+        }
+      },
+      subOptions
+    );
+    const aelf = new AElf(new AElf.providers.HttpProvider(options.endpoint));
+    const { height, includeTxs } = subOptions;
+    try {
+      this.oraInstance.start();
+      let blockInfo;
+      // usually block hash is encoded with hex and has 64 characters
+      if (String(height).trim().length > 50) {
+        blockInfo = await aelf.chain.getBlock(height, includeTxs);
+      } else {
+        blockInfo = await aelf.chain.getBlockByHeight(height, includeTxs);
+      }
+      this.oraInstance.succeed('Succeed!');
+      // @ts-ignore
+      logger.info(blockInfo);
+    } catch (e) {
+      this.oraInstance.fail('Failed!');
+      // @ts-ignore
+      logger.error(e);
+    }
+  }
+}
+ 
+export default GetBlkInfoCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/getChainStatus.js.html b/src/command/getChainStatus.js.html new file mode 100644 index 0000000..5e445ee --- /dev/null +++ b/src/command/getChainStatus.js.html @@ -0,0 +1,208 @@ + + + + + + Code coverage report for src/command/getChainStatus.js + + + + + + + + + +
+
+

All files / src/command getChainStatus.js

+
+ +
+ 100% + Statements + 9/9 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 9/9 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +7x +  +  +  +  +  +  +  +  +  +  +4x +4x +4x +4x +3x +1x +  +1x +  +1x +  +  +  +  +  + 
import AElf from 'aelf-sdk';
+import BaseSubCommand from './baseSubCommand.js';
+import { commonGlobalOptionValidatorDesc } from '../utils/constants.js';
+import { logger } from '../utils/myLogger.js';
+ 
+/**
+ * @typedef {import('commander').Command} Command
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+class GetChainStatusCommand extends BaseSubCommand {
+  /**
+   * Constructs a new GetChainStatusCommand instance.
+   * @param {Registry} rc - The registry instance.
+   */
+  constructor(rc) {
+    super('get-chain-status', [], 'Get the current chain status', [], [], rc, commonGlobalOptionValidatorDesc);
+  }
+ 
+  /**
+   * Executes the get chain status command.
+   * @param {Command} commander - The commander instance.
+   * @param {...any} args - Additional arguments.
+   * @returns {Promise<void>} A promise that resolves when the command execution is complete.
+   */
+  async run(commander, ...args) {
+    // @ts-ignore
+    const { options } = await super.run(commander, ...args);
+    const aelf = new AElf(new AElf.providers.HttpProvider(options.endpoint));
+    try {
+      this.oraInstance.start();
+      const status = await aelf.chain.getChainStatus();
+      this.oraInstance.succeed(`Succeed\n${JSON.stringify(status, null, 2)}`);
+    } catch (e) {
+      this.oraInstance.fail('Failed!');
+      // @ts-ignore
+      logger.error(e);
+    }
+  }
+}
+ 
+export default GetChainStatusCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/getTxResult.js.html b/src/command/getTxResult.js.html new file mode 100644 index 0000000..7a5de29 --- /dev/null +++ b/src/command/getTxResult.js.html @@ -0,0 +1,331 @@ + + + + + + Code coverage report for src/command/getTxResult.js + + + + + + + + + +
+
+

All files / src/command getTxResult.js

+
+ +
+ 100% + Statements + 17/17 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 17/17 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83  +  +  +3x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +7x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +2x +2x +  +1x +  +  +  +  +  +  +  +  +  +  +  +2x +2x +2x +  +  +  +  +  +  +  +  +  +2x +2x +2x +2x +1x +  +1x +  +1x +  +1x +  +  +  +  +  + 
import AElf from 'aelf-sdk';
+import { interopImportCJSDefault } from 'node-cjs-interop';
+import asyncValidator from 'async-validator';
+const Schema = interopImportCJSDefault(asyncValidator);
+import BaseSubCommand from './baseSubCommand.js';
+import { commonGlobalOptionValidatorDesc, txResultCommandParameters, txResultCommandUsage } from '../utils/constants.js';
+import { logger } from '../utils/myLogger.js';
+ 
+/**
+ * @typedef {import('commander').Command} Command
+ * @typedef {import('async-validator').Rules} Rules
+ * @typedef {import('async-validator').Values} Values
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+class GetTxResultCommand extends BaseSubCommand {
+  /**
+   * Constructs a new GetTxResultCommand instance.
+   * @param {Registry} rc - The registry instance.
+   */
+  constructor(rc) {
+    super(
+      'get-tx-result',
+      txResultCommandParameters,
+      'Get a transaction result',
+      [],
+      txResultCommandUsage,
+      rc,
+      commonGlobalOptionValidatorDesc
+    );
+  }
+ 
+  /**
+   * Validates the parameters against given rules.
+   * @param {Rules} rule - The validation rules.
+   * @param {Values} parameters - The parameters to validate.
+   * @returns {Promise<void>} A promise that resolves when validation is complete.
+   */
+  async validateParameters(rule, parameters) {
+    const validator = new Schema(rule);
+    try {
+      await validator.validate(parameters);
+    } catch (e) {
+      this.handleUniOptionsError(e);
+    }
+  }
+ 
+  /**
+   * Executes the get transaction result command.
+   * @param {Command} commander - The commander instance.
+   * @param {...any} args - Additional arguments.
+   * @returns {Promise<void>} A promise that resolves when the command execution is complete.
+   */
+  async run(commander, ...args) {
+    // @ts-ignore
+    const { options, subOptions } = await super.run(commander, ...args);
+    try {
+      await this.validateParameters(
+        {
+          txId: {
+            type: 'string',
+            required: true,
+            message: 'Input a valid <tx-id>'
+          }
+        },
+        subOptions
+      );
+      const aelf = new AElf(new AElf.providers.HttpProvider(options.endpoint));
+      const { txId } = subOptions;
+      this.oraInstance.start();
+      const txResult = await aelf.chain.getTxResult(txId);
+      this.oraInstance.succeed('Succeed!');
+      // @ts-ignore
+      logger.info(JSON.stringify(txResult, null, 2));
+    } catch (e) {
+      this.oraInstance.fail('Failed to run this command');
+      // @ts-ignore
+      logger.error(e);
+    }
+  }
+}
+ 
+export default GetTxResultCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/index.html b/src/command/index.html new file mode 100644 index 0000000..afbdb83 --- /dev/null +++ b/src/command/index.html @@ -0,0 +1,326 @@ + + + + + + Code coverage report for src/command + + + + + + + + + +
+
+

All files src/command

+
+ +
+ 98.42% + Statements + 375/381 +
+ + +
+ 92.62% + Branches + 113/122 +
+ + +
+ 100% + Functions + 64/64 +
+ + +
+ 98.4% + Lines + 370/376 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
baseSubCommand.js +
+
100%80/80100%32/32100%22/22100%77/77
call.js +
+
98.07%51/5285.71%24/28100%6/698.03%50/51
config.js +
+
93.93%31/33100%12/12100%6/693.93%31/33
console.js +
+
100%16/16100%2/2100%2/2100%16/16
create.js +
+
100%20/20100%4/4100%2/2100%20/20
deploy.js +
+
100%3/3100%3/3100%2/2100%3/3
event.js +
+
95.23%20/2183.33%5/6100%3/395%19/20
getBlkHeight.js +
+
100%9/9100%0/0100%2/2100%9/9
getBlkInfo.js +
+
100%19/19100%2/2100%3/3100%19/19
getChainStatus.js +
+
100%9/9100%0/0100%2/2100%9/9
getTxResult.js +
+
100%17/17100%0/0100%3/3100%17/17
load.js +
+
100%25/25100%8/8100%2/2100%25/25
proposal.js +
+
96.49%55/5782.6%19/23100%5/596.49%55/57
send.js +
+
100%5/5100%0/0100%2/2100%5/5
wallet.js +
+
100%15/15100%2/2100%2/2100%15/15
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/load.js.html b/src/command/load.js.html new file mode 100644 index 0000000..0152528 --- /dev/null +++ b/src/command/load.js.html @@ -0,0 +1,337 @@ + + + + + + Code coverage report for src/command/load.js + + + + + + + + + +
+
+

All files / src/command load.js

+
+ +
+ 100% + Statements + 25/25 +
+ + +
+ 100% + Branches + 8/8 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 25/25 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85  +  +  +  +  +  +3x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +10x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +5x +5x +5x +5x +5x +  +5x +5x +2x +  +1x +1x +  +1x +  +1x +  +3x +  +4x +  +4x +  +4x +  +4x +4x +2x +1x +  +2x +  +  +1x +  +1x +  +  +  +  +  + 
import AElf from 'aelf-sdk';
+import BaseSubCommand from './baseSubCommand.js';
+import { commonGlobalOptionValidatorDesc, loadCommandParameters, loadCommandUsage } from '../utils/constants.js';
+import { saveKeyStore } from '../utils/wallet.js';
+import { logger } from '../utils/myLogger.js';
+ 
+const loadCommandValidatorDesc = {
+  ...commonGlobalOptionValidatorDesc,
+  endpoint: {
+    ...commonGlobalOptionValidatorDesc.endpoint,
+    required: false
+  }
+};
+ 
+/**
+ * @typedef {import('commander').Command} Command
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+class LoadCommand extends BaseSubCommand {
+  /**
+   * Constructs a new LoadCommand instance.
+   * @param {Registry} rc - The registry instance.
+   */
+  constructor(rc) {
+    super(
+      'load',
+      loadCommandParameters,
+      'Load wallet from a private key or mnemonic',
+      [],
+      loadCommandUsage,
+      rc,
+      loadCommandValidatorDesc
+    );
+  }
+ 
+  /**
+   * Executes the load command.
+   * @param {Command} commander - The commander instance.
+   * @param {...any} args - Additional arguments.
+   * @returns {Promise<void>} A promise that resolves when the command execution is complete.
+   */
+  async run(commander, ...args) {
+    // @ts-ignore
+    const { options, subOptions } = await super.run(commander, ...args);
+    const { datadir } = options;
+    const { privateKey, saveToFile, createdByOld } = subOptions;
+    try {
+      let wallet = null;
+      // @ts-ignore
+      logger.info('Your wallet info is :');
+      if (privateKey.trim().split(' ').length > 1) {
+        if (createdByOld) {
+          // old version sdk
+          this.oraInstance.fail('Please install older versions of aelf-command before v1.0.0!');
+          return;
+        }
+        wallet = AElf.wallet.getWalletByMnemonic(privateKey.trim());
+        // @ts-ignore
+        logger.info(`Mnemonic            : ${wallet.mnemonic}`);
+      } else {
+        wallet = AElf.wallet.getWalletByPrivateKey(privateKey.trim());
+      }
+      wallet.publicKey = wallet.keyPair.getPublic().encode('hex');
+      // @ts-ignore
+      logger.info(`Private Key         : ${wallet.privateKey}`);
+      // @ts-ignore
+      logger.info(`Public Key          : ${wallet.publicKey}`);
+      // @ts-ignore
+      logger.info(`Address             : ${wallet.address}`);
+      if (saveToFile === true || saveToFile === 'true') {
+        const keyStorePath = await saveKeyStore(wallet, datadir);
+        this.oraInstance.succeed(`Account info has been saved to \"${keyStorePath}\"`);
+      } else {
+        this.oraInstance.succeed('Succeed!');
+      }
+    } catch (e) {
+      this.oraInstance.fail('Failed!');
+      // @ts-ignore
+      logger.error(e);
+    }
+  }
+}
+ 
+export default LoadCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/proposal.js.html b/src/command/proposal.js.html new file mode 100644 index 0000000..a45c5e4 --- /dev/null +++ b/src/command/proposal.js.html @@ -0,0 +1,712 @@ + + + + + + Code coverage report for src/command/proposal.js + + + + + + + + + +
+
+

All files / src/command proposal.js

+
+ +
+ 96.49% + Statements + 55/57 +
+ + +
+ 82.6% + Branches + 19/23 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 96.49% + Lines + 55/57 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +4x +3x +  +1x +1x +1x +1x +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +11x +11x +  +  +  +  +  +  +  +  +  +  +4x +4x +4x +4x +  +  +  +  +  +  +  +  +  +  +5x +5x +5x +5x +5x +1x +  +  +  +  +4x +1x +  +3x +  +3x +3x +3x +3x +3x +3x +  +  +  +  +3x +6x +  +3x +  +  +  +  +  +  +  +  +3x +  +3x +  +3x +  +  +  +  +  +3x +3x +  +  +  +  +3x +3x +3x +3x +  +  +  +  +  +  +  +  +  +  +  +  +  +3x +3x +3x +2x +2x +  +1x +  +  +  +  +  +1x +1x +  +1x +  +1x +  +1x +1x +  +  +3x +  +3x +  +  +  +  +  + 
import assert from 'assert';
+import AElf from 'aelf-sdk';
+import moment from 'moment';
+import chalk from 'chalk';
+import inquirer from 'inquirer';
+import BaseSubCommand from './baseSubCommand.js';
+import { proposalCommandUsage, proposalCommandParameters } from '../utils/constants.js';
+import {
+  isAElfContract,
+  getContractMethods,
+  getContractInstance,
+  getMethod,
+  promptTolerateSeveralTimes,
+  getTxResult,
+  getParams,
+  deserializeLogs
+} from '../utils/utils.js';
+import { getWallet } from '../utils/wallet.js';
+import { logger } from '../utils/myLogger.js';
+ 
+/**
+ * @typedef {import('commander').Command} Command
+ * @typedef {import('async-validator').Rules} Rules
+ * @typedef {import('async-validator').Values} Values
+ * @typedef {import ('inquirer').InputQuestion } InputQuestion
+ * @typedef {import ('inquirer').ListQuestion } ListQuestion
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+/**
+ * @type {Array<InputQuestion | ListQuestion>}
+ */
+const toContractPrompts = [
+  {
+    type: 'input',
+    name: 'contract-address',
+    // @ts-ignore
+    extraName: ['contract-name'],
+    message: 'Enter a contract address or name',
+    suffix: ':'
+  },
+  {
+    type: 'list',
+    name: 'method',
+    message: 'Pick up a contract method',
+    pageSize: 10,
+    choices: [],
+    suffix: ':'
+  }
+];
+ 
+async function getContractAddress(aelf, wallet, address) {
+  if (!isAElfContract(address)) {
+    return address;
+  }
+  try {
+    const { GenesisContractAddress } = await aelf.chain.getChainStatus();
+    const genesisContract = await aelf.chain.contractAt(GenesisContractAddress, wallet);
+    const toContractAddress = await genesisContract.GetContractAddressByName.call(AElf.utils.sha256(address));
+    return toContractAddress;
+  } catch (error) {
+    return null;
+  }
+}
+ 
+class ProposalCommand extends BaseSubCommand {
+  /**
+   * Constructs a new ProposalCommand instance.
+   * @param {Registry} rc - The registry instance.
+   * @param {string} [name] - Optional name of the command.
+   * @param {string} [description] - Optional description of the command.
+   * @param {Array<Object>} [parameters] - Optional array of parameter objects.
+   * @param {string[]} [usage] - Optional array of usage strings.
+   * @param {any[]} [options] - Optional array of options.
+   */
+  constructor(
+    rc,
+    name = 'proposal',
+    description = 'Send a proposal to an origination with a specific contract method',
+    parameters = proposalCommandParameters,
+    usage = proposalCommandUsage,
+    options = []
+  ) {
+    super(name, parameters, description, options, usage, rc);
+    this.aelfMock = {};
+  }
+ 
+  /**
+   * Processes address after prompting.
+   * @param {any} aelf - The AElf instance.
+   * @param {any} wallet - The wallet instance.
+   * @param {{ [key: string]: any }} answerInput - Input from the user.
+   * @returns {Promise<any>} A promise that resolves with the processed address.
+   */
+  async processAddressAfterPrompt(aelf, wallet, answerInput) {
+    this.toContractAddress = await getContractAddress(aelf, wallet, answerInput['contract-address']);
+    let { contractAddress } = BaseSubCommand.normalizeConfig(answerInput);
+    contractAddress = await getContractInstance(contractAddress, aelf, wallet, this.oraInstance);
+    return contractAddress;
+  }
+ 
+  /**
+   * Runs the ProposalCommand.
+   * @param {Command} commander - The commander instance.
+   * @param {...any} args - Additional arguments.
+   * @returns {Promise<void>} A promise that resolves when the command execution is complete.
+   */
+  async run(commander, ...args) {
+    // @ts-ignore
+    const { options, subOptions } = await super.run(commander, ...args);
+    const { endpoint, datadir, account, password } = options;
+    const { descriptionUrl, proposalContract, organization, expiredTime } = subOptions;
+    try {
+      if (!proposalCommandParameters[0].choices?.includes(proposalContract)) {
+        throw new Error(
+          // eslint-disable-next-line max-len
+          `${proposalContract} is not in the list of proposal contracts, choice one of \`AElf.ContractNames.Parliament\`, \`AElf.ContractNames.Referendum\` and \`AElf.ContractNames.Association\``
+        );
+      }
+      if (!moment(expiredTime).isValid || moment(expiredTime).isBefore(moment().add(0, 'hours'))) {
+        throw new Error(`Expired Time has to be later than ${moment().add(1, 'hours').format('YYYY/MM/DD HH:mm:ss')}`);
+      }
+      let aelf = new AElf(new AElf.providers.HttpProvider(endpoint));
+      // for test mock
+      aelf = { ...aelf, ...this.aelfMock };
+      const wallet = getWallet(datadir, account, password);
+      const { GenesisContractAddress } = await aelf.chain.getChainStatus();
+      const genesisContract = await aelf.chain.contractAt(GenesisContractAddress, wallet);
+      const address = await genesisContract.GetContractAddressByName.call(AElf.utils.sha256(proposalContract));
+      const parliament = await aelf.chain.contractAt(address, wallet);
+      let toContractAddress;
+      let method;
+      let methodName;
+ 
+      for (const prompt of toContractPrompts) {
+        switch (prompt.name) {
+          case 'contract-address':
+            toContractAddress = await promptTolerateSeveralTimes(
+              {
+                times: 3,
+                prompt,
+                processAfterPrompt: this.processAddressAfterPrompt.bind(this, aelf, wallet),
+                pattern: /^((?!null).)*$/
+              },
+              this.oraInstance
+            );
+            break;
+          case 'method':
+            toContractAddress = await getContractInstance(toContractAddress, aelf, wallet, this.oraInstance);
+ 
+            methodName = (
+              await inquirer.prompt({
+                ...prompt,
+                choices: getContractMethods(toContractAddress)
+              })
+            ).method;
+            method = getMethod(methodName, toContractAddress);
+            break;
+          default:
+            break;
+        }
+      }
+      let params = await getParams(method);
+      params = typeof params === 'string' ? params : BaseSubCommand.normalizeConfig(params);
+      const result = method.packInput(params);
+      const txId = await parliament.CreateProposal(
+        BaseSubCommand.normalizeConfig({
+          contractMethodName: methodName,
+          toAddress: this.toContractAddress,
+          params: result,
+          organizationAddress: organization,
+          expiredTime: {
+            seconds: moment(expiredTime).unix(),
+            nanos: moment(expiredTime).milliseconds() * 1000
+          },
+          proposalDescriptionUrl: descriptionUrl
+        })
+      );
+      // @ts-ignore
+      logger.info(txId);
+      this.oraInstance.start('loading proposal id...');
+      const tx = await getTxResult(aelf, txId.TransactionId);
+      this.oraInstance.succeed();
+      if (tx.Status === 'PENDING') {
+        // @ts-ignore
+        logger.info(
+          `Transaction is still pending, you can get proposal id later by running ${chalk.yellow(
+            `aelf-command event ${txId.TransactionId}`
+          )}`
+        );
+      } else {
+        const { Logs = [] } = tx;
+        const results = await deserializeLogs(
+          aelf,
+          (Logs || []).filter(v => v.Name === 'ProposalCreated')
+        );
+        assert.strictEqual(results.length, 1, 'No related log');
+        // @ts-ignore
+        logger.info(`Proposal id: ${results[0].proposalId}.`);
+        this.oraInstance.succeed('Succeed!');
+      }
+    } catch (e) {
+      this.oraInstance.fail('Failed!');
+      // @ts-ignore
+      logger.fatal(e);
+    }
+  }
+}
+ 
+export default ProposalCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/send.js.html b/src/command/send.js.html new file mode 100644 index 0000000..e0c1c03 --- /dev/null +++ b/src/command/send.js.html @@ -0,0 +1,172 @@ + + + + + + Code coverage report for src/command/send.js + + + + + + + + + +
+
+

All files / src/command send.js

+
+ +
+ 100% + Statements + 5/5 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 5/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30  +  +  +  +  +  +  +  +  +  +  +6x +  +  +  +  +  +  +  +  +  +1x +1x +1x +1x +  +  +  +  + 
import CallCommand from './call.js';
+ 
+/**
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+class SendCommand extends CallCommand {
+  /**
+   * Creates an instance of SendCommand.
+   * @param {Registry} rc - The registry instance.
+   */
+  constructor(rc) {
+    super(rc, 'send', 'Execute a method on a contract.');
+  }
+ 
+  /**
+   * Asynchronously calls a method and handles transaction progress.
+   * @param {any} method - The method to call.
+   * @param {any} params - The parameters for the method.
+   * @returns {Promise<any>} A promise that resolves to the result of the method call.
+   */
+  async callMethod(method, params) {
+    this.oraInstance.start('sending the transaction');
+    const result = await method(params);
+    this.oraInstance.succeed('Succeed!');
+    return result;
+  }
+}
+ 
+export default SendCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/command/wallet.js.html b/src/command/wallet.js.html new file mode 100644 index 0000000..768e736 --- /dev/null +++ b/src/command/wallet.js.html @@ -0,0 +1,307 @@ + + + + + + Code coverage report for src/command/wallet.js + + + + + + + + + +
+
+

All files / src/command wallet.js

+
+ +
+ 100% + Statements + 15/15 +
+ + +
+ 100% + Branches + 2/2 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 15/15 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75  +  +  +  +  +3x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +7x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +2x +2x +2x +2x +  +1x +  +2x +  +1x +  +1x +  +1x +1x +  +1x +  +1x +  +  +  +  +  + 
import BaseSubCommand from './baseSubCommand.js';
+import { commonGlobalOptionValidatorDesc } from '../utils/constants.js';
+import { getWallet } from '../utils/wallet.js';
+import { logger } from '../utils/myLogger.js';
+ 
+const walletCommandValidatorDesc = {
+  ...commonGlobalOptionValidatorDesc,
+  endpoint: {
+    ...commonGlobalOptionValidatorDesc.endpoint,
+    required: false
+  },
+  password: {
+    ...commonGlobalOptionValidatorDesc.password,
+    required: true
+  },
+  account: {
+    ...commonGlobalOptionValidatorDesc.account,
+    required: true
+  }
+};
+/**
+ * @typedef {import('commander').Command} Command
+ * @typedef {import('../../types/rc/index.js').default} Registry
+ */
+class WalletCommand extends BaseSubCommand {
+  /**
+   * Constructs a new WalletCommand instance.
+   * @param {Registry} rc - The Registry instance for configuration.
+   */
+  constructor(rc) {
+    super(
+      'wallet',
+      [],
+      'Show wallet details which include private key, address, public key and mnemonic',
+      [],
+      ['-a <account> -p <password>', ''],
+      rc,
+      walletCommandValidatorDesc
+    );
+  }
+ 
+  /**
+   * Runs the wallet command logic.
+   * @param {Command} commander - The Commander instance for command handling.
+   * @param {...any} args - Additional arguments for command execution.
+   * @returns {Promise<void>} A promise that resolves when the command execution is complete.
+   */
+  async run(commander, ...args) {
+    // @ts-ignore
+    const { options } = await super.run(commander, ...args);
+    const { datadir, account, password } = options;
+    try {
+      const wallet = getWallet(datadir, account, password);
+      if (wallet.mnemonic) {
+        // @ts-ignore
+        logger.info(`Mnemonic            : ${wallet.mnemonic}`);
+      }
+      wallet.publicKey = wallet.keyPair.getPublic().encode('hex');
+      // @ts-ignore
+      logger.info(`Private Key         : ${wallet.privateKey}`);
+      // @ts-ignore
+      logger.info(`Public Key          : ${wallet.publicKey}`);
+      // @ts-ignore
+      logger.info(`Address             : ${wallet.address}`);
+      this.oraInstance.succeed('Succeed!');
+    } catch (e) {
+      this.oraInstance.fail('Failed!');
+      // @ts-ignore
+      logger.error(e);
+    }
+  }
+}
+ 
+export default WalletCommand;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..7e69b92 --- /dev/null +++ b/src/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for src + + + + + + + + + +
+
+

All files src

+
+ +
+ 95.45% + Statements + 42/44 +
+ + +
+ 100% + Branches + 14/14 +
+ + +
+ 100% + Functions + 7/7 +
+ + +
+ 95.45% + Lines + 42/44 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
index.js +
+
95.45%42/44100%14/14100%7/795.45%42/44
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/src/index.js.html b/src/index.js.html new file mode 100644 index 0000000..54187e7 --- /dev/null +++ b/src/index.js.html @@ -0,0 +1,409 @@ + + + + + + Code coverage report for src/index.js + + + + + + + + + +
+
+

All files / src index.js

+
+ +
+ 95.45% + Statements + 42/44 +
+ + +
+ 100% + Branches + 14/14 +
+ + +
+ 100% + Functions + 7/7 +
+ + +
+ 95.45% + Lines + 42/44 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +12x +  +12x +  +  +  +  +12x +12x +12x +12x +  +  +  +5x +5x +  +5x +  +4x +  +5x +4x +2x +  +  +5x +5x +5x +5x +5x +5x +  +  +  +5x +5x +5x +75x +75x +  +5x +  +  +1x +  +1x +  +5x +5x +3x +  +  +  +  +3x +1x +  +  +  +  +  +5x +  +  +  +7x +7x +  +1x +1x +  +6x +5x +5x +  +  +  +  +  +1x +  +  +  +  +  + 
/**
+ * @file command index
+ * @author atom-yang
+ */
+import { Command } from 'commander';
+import chalk from 'chalk';
+// @ts-ignore
+import updateNotifier from 'update-notifier';
+import check from 'check-node-version';
+import { execSync } from 'child_process';
+import commands from './command/index.js';
+import RC from './rc/index.js';
+import { readFileSync } from 'fs';
+import { fileURLToPath } from 'url';
+import path from 'path';
+import { logger } from './utils/myLogger.js';
+import { userHomeDir } from './utils/userHomeDir.js';
+ 
+const minVersion = '10.9.0';
+ 
+export function getPackageJson() {
+  let dirname;
+  try {
+    // for test as we cannot use import.meta.url in Jest
+    dirname = __dirname;
+  } catch {
+    const __filename = fileURLToPath(import.meta.url);
+    dirname = path.dirname(__filename);
+  }
+  const filePath = path.resolve(dirname, '../package.json');
+  const data = readFileSync(filePath, 'utf-8');
+  const packageJson = JSON.parse(data);
+  return packageJson;
+}
+ 
+function init(options) {
+  const pkg = getPackageJson();
+  const commander = new Command();
+  // Configuration for test
+  if (options?.exitOverride) {
+    // throws a JavaScript error instead of the original process exit
+    commander.exitOverride();
+  }
+  if (options?.suppressOutput) {
+    commander.configureOutput({
+      writeOut: str => process.stdout.write(`[OUT] ${str}`)
+    });
+  }
+  commander.version(pkg.version, '-v, --version');
+  commander.usage('[command] [options]');
+  commander.option('-e, --endpoint <URI>', 'The URI of an AElf node. Eg: http://127.0.0.1:8000');
+  commander.option('-a, --account <account>', 'The address of AElf wallet');
+  commander.option('-p, --password <password>', 'The password of encrypted keyStore');
+  commander.option(
+    '-d, --datadir <directory>',
+    `The directory that contains the AElf related files. Default to be ${userHomeDir}/aelf`
+  );
+  commander.option('-c, --csv <csv>', 'The location of the CSV file containing the parameters.');
+  const rc = new RC();
+  Object.values(commands).forEach(Value => {
+    const command = new Value(rc);
+    command.init(commander);
+  });
+  commander.command('*').action(() => {
+    // change into help
+    // @ts-ignore
+    logger.warn('not a valid command\n');
+    // @ts-ignore
+    logger.info(execSync('aelf-command -h').toString());
+  });
+  const isTest = process.env.NODE_ENV === 'test';
+  if (!isTest) {
+    const notifier = updateNotifier({
+      pkg,
+      distTag: 'latest',
+      updateCheckInterval: 1000 * 60 * 60 * 1 // one hours
+    });
+    if (notifier.update) {
+      notifier.notify({
+        message: `Update available ${chalk.dim(pkg.version)} ${chalk.reset('→')} ${chalk.green(notifier.update.latest)}
+      Run ${chalk.cyan('npm i aelf-command -g')} to update`
+      });
+    }
+  }
+  return commander;
+}
+ 
+function run(args, options) {
+  check({ node: `>= ${minVersion}` }, (error, results) => {
+    if (error) {
+      // @ts-ignore
+      logger.error(error);
+      return;
+    }
+    if (results.isSatisfied) {
+      const isTest = process.env.NODE_ENV === 'test';
+      init({ exitOverride: options?.exitOverride, suppressOutput: options?.suppressOutput }).parse(
+        args,
+        isTest ? { from: 'user' } : undefined
+      );
+    } else {
+      // @ts-ignore
+      logger.error('Your Node.js version is needed to >= %s', minVersion);
+    }
+  });
+}
+ 
+export { run };
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/rc/index.html b/src/rc/index.html new file mode 100644 index 0000000..36f1c3d --- /dev/null +++ b/src/rc/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for src/rc + + + + + + + + + +
+
+

All files src/rc

+
+ +
+ 100% + Statements + 47/47 +
+ + +
+ 100% + Branches + 16/16 +
+ + +
+ 100% + Functions + 17/17 +
+ + +
+ 100% + Lines + 46/46 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
index.js +
+
100%47/47100%16/16100%17/17100%46/46
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/src/rc/index.js.html b/src/rc/index.js.html new file mode 100644 index 0000000..89b6ed8 --- /dev/null +++ b/src/rc/index.js.html @@ -0,0 +1,622 @@ + + + + + + Code coverage report for src/rc/index.js + + + + + + + + + +
+
+

All files / src/rc index.js

+
+ +
+ 100% + Statements + 47/47 +
+ + +
+ 100% + Branches + 16/16 +
+ + +
+ 100% + Functions + 17/17 +
+ + +
+ 100% + Lines + 46/46 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180  +  +  +  +  +14x +  +  +  +  +  +  +14x +  +  +  +  +  +14x +  +  +  +10x +10x +2x +  +  +  +  +  +10x +10x +  +  +  +  +  +  +  +  +  +31x +20x +  +11x +  +  +  +  +  +  +  +  +16x +14x +  +2x +2x +  +  +  +  +  +  +  +27x +27x +  +83x +  +6x +6x +  +27x +  +  +  +  +  +  +11x +11x +33x +1x +  +  +11x +  +  +  +  +  +  +  +  +6x +6x +6x +  +  +  +  +  +  +10x +10x +10x +10x +  +  +  +  +  +10x +10x +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +4x +4x +4x +4x +  +  +  +  +  +  +  +  +  +1x +1x +1x +  +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +3x +  +  +  +  + 
import path from 'path';
+import fs from 'fs';
+import { mkdirpSync } from 'mkdirp';
+import { userHomeDir } from '../utils/userHomeDir.js';
+ 
+const REGISTRY_DEFAULT_OPTIONS = {
+  endpoint: '',
+  datadir: path.resolve(userHomeDir, 'aelf'),
+  password: '', // this is not suggested stored in config file
+  account: '' // the public address of aelf wallet, encoded with base58
+};
+ 
+const ENV_RC_KEYS = {
+  endpoint: 'AELF_CLI_ENDPOINT',
+  datadir: 'AELF_CLI_DATADIR',
+  account: 'AELF_CLI_ACCOUNT'
+};
+ 
+const rcHeader = '# THIS IS AN AUTOGENERATED FILE FOR AELF-COMMAND OPTIONS. DO NOT EDIT THIS FILE DIRECTLY.\n\n\n';
+ 
+class Registry {
+  constructor() {
+    this.globalConfigLoc = path.resolve(userHomeDir, 'aelf/.aelfrc');
+    if (!fs.existsSync(path.resolve(userHomeDir, 'aelf'))) {
+      mkdirpSync(path.resolve(userHomeDir, 'aelf'));
+    }
+    /**
+     * AELF configuration object.
+     * @type {Object.<string, any>}
+     */
+    this.aelfConfig = {};
+    this.init();
+  }
+ 
+  /**
+   * Retrieves file content or default content if file doesn't exist.
+   * @param {string} file - The file path.
+   * @param {string} [defaultContent] - Optional default content if file doesn't exist.
+   * @returns {*} The content of the file or default content.
+   */
+  static getFileOrNot(file, defaultContent = '') {
+    if (fs.existsSync(file)) {
+      return fs.readFileSync(file).toString();
+    }
+    return defaultContent;
+  }
+ 
+  /**
+   * Retrieves file content or creates the file if it doesn't exist.
+   * @param {string} file - The file path.
+   * @returns {string} The file content or an empty string if the file is created.
+   */
+  static getFileOrCreate(file) {
+    if (fs.existsSync(file)) {
+      return fs.readFileSync(file).toString();
+    }
+    fs.writeFileSync(file, rcHeader);
+    return '';
+  }
+  /**
+   * Loads configuration from provided content.
+   * @param {string} [content] - Optional content to load configuration from.
+   * @returns {Object.<string, any>} The loaded configuration object.
+   */
+  static loadConfig(content = '') {
+    const result = {};
+    content
+      .split('\n')
+      .filter(v => !v.startsWith('#') && v.length > 0)
+      .forEach(v => {
+        const [key, value] = v.split(' ');
+        result[key] = value;
+      });
+    return result;
+  }
+  /**
+   * Retrieves configuration from environment variables.
+   * @returns {Object.<string, any>} The configuration object retrieved from environment variables.
+   */
+  static getConfigFromEnv() {
+    const result = {};
+    Object.entries(ENV_RC_KEYS).forEach(([key, value]) => {
+      if (process.env[value]) {
+        result[key] = process.env[value];
+      }
+    });
+    return result;
+  }
+ 
+  /**
+   * Converts a one-level object into an array of content.
+   * @param {Object.<string, any>} [obj] - The object to stringify.
+   * @returns {string[]} Array of content from the object's fields.
+   */
+  static stringify(obj = {}) {
+    let result = Object.entries(obj).map(([key, value]) => `${key} ${value}`);
+    result = rcHeader.split('\n').concat(result);
+    return result;
+  }
+  /**
+   * Initializes and retrieves initial configuration options.
+   * @returns {{ endpoint: string, datadir: string, password: string, account: string }} Initial configuration values.
+   */
+  init() {
+    const pwdRc = Registry.loadConfig(Registry.getFileOrNot(path.resolve(process.cwd(), '.aelfrc')));
+    const globalRc = Registry.loadConfig(Registry.getFileOrCreate(this.globalConfigLoc));
+    const envRc = Registry.getConfigFromEnv();
+    const rc = {
+      ...REGISTRY_DEFAULT_OPTIONS,
+      ...envRc,
+      ...globalRc,
+      ...pwdRc
+    };
+    this.aelfConfig = rc;
+    return rc;
+  }
+ 
+  /**
+   * Retrieves a configuration option by key.
+   * @param {string} key - The option key.
+   * @returns {*} The value of the option.
+   */
+  getOption(key) {
+    return this.aelfConfig[key];
+  }
+ 
+  /**
+   * Sets a configuration option.
+   * @param {string} key - The option key.
+   * @param {*} value - The value to set.
+   */
+  setOption(key, value) {
+    this.aelfConfig[key] = value;
+  }
+ 
+  /**
+   * Saves an option to configuration file.
+   * @param {string} key - The option key.
+   * @param {*} value - The value to save.
+   * @param {*} [filePath] - Optional file path to save configuration.
+   * @returns {*} Result of saving operation.
+   */
+  saveOption(key, value, filePath = this.globalConfigLoc) {
+    this.aelfConfig[key] = value;
+    const rc = Registry.loadConfig(Registry.getFileOrCreate(filePath));
+    rc[key] = value;
+    return fs.writeFileSync(filePath, `${Registry.stringify(rc).join('\n')}\n`);
+  }
+ 
+  /**
+   * Deletes a configuration key from file.
+   * @param {string} key - The option key to delete.
+   * @param {*} [filePath] - Optional file path to delete from.
+   * @returns {*} Result of deletion operation.
+   */
+  deleteConfig(key, filePath = this.globalConfigLoc) {
+    const rc = Registry.loadConfig(Registry.getFileOrCreate(filePath));
+    delete rc[key];
+    return fs.writeFileSync(filePath, `${Registry.stringify(rc).join('\n')}\n`);
+  }
+ 
+  /**
+   * Retrieves configurations from file.
+   * @param {string} [filePath] - Optional file path to retrieve configurations.
+   * @returns {Object.<string, any>} The configurations retrieved from file.
+   */
+  getFileConfigs(filePath = this.globalConfigLoc) {
+    return Registry.loadConfig(Registry.getFileOrCreate(filePath));
+  }
+  /**
+   * Retrieves all configurations.
+   * @returns {Object.<string, any>} All configurations.
+   */
+  getConfigs() {
+    return this.aelfConfig;
+  }
+}
+ 
+export default Registry;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/utils/Logger.js.html b/src/utils/Logger.js.html new file mode 100644 index 0000000..3665ebd --- /dev/null +++ b/src/utils/Logger.js.html @@ -0,0 +1,379 @@ + + + + + + Code coverage report for src/utils/Logger.js + + + + + + + + + +
+
+

All files / src/utils Logger.js

+
+ +
+ 100% + Statements + 20/20 +
+ + +
+ 93.75% + Branches + 15/16 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 20/20 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99  +  +  +  +  +  +  +  +22x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +46x +  +  +  +  +46x +  +  +  +  +46x +46x +24x +24x +  +  +  +  +  +  +22x +132x +132x +  +  +  +  +  +  +  +  +  +132x +  +  +4x +4x +2x +2x +2x +  +2x +  +  +  +2x +1x +  +2x +  +  +  +  + 
import chalk from 'chalk';
+ 
+// - Trace: The unimportant detail about how the process run. You may hardly use it.
+// - Debug: A debug message for processing information to help during troubleshooting.
+// - Info: Generally useful information to log (service start/stop, configuration assumptions, etc).
+// - Warn: Anything that can potentially cause application oddities.
+// - Error: Error has happend, but the user can still use the system after fixing the error.
+// - Fatal: The system is unusable.
+const levels = [
+  {
+    level: 'Trace',
+    color: chalk.gray
+  },
+  {
+    level: 'Debug',
+    color: chalk.hex('#D3D3D3')
+  },
+  {
+    level: 'Info',
+    color: chalk.hex('#3753d3')
+  },
+  {
+    level: 'Warn',
+    color: chalk.yellow
+  },
+  {
+    level: 'Error',
+    color: chalk.hex('#cf342f')
+  },
+  {
+    level: 'Fatal',
+    color: chalk.hex('#cf0014')
+  }
+];
+ 
+class Logger {
+  /**
+   * Constructs a new Logger instance.
+   * @param {Object.<string, any>} props - Logger properties.
+   */
+  constructor(props) {
+    /**
+     * Symbol for the logger.
+     * @type {string}
+     */
+    this.symbol = '';
+    /**
+     * Name of the logger.
+     * @type {string}
+     */
+    this.name = '';
+    /**
+     * Determines whether to log messages.
+     * @type {boolean}
+     */
+    this.log = props.log !== undefined ? props.log : true; // determin whether console.log or not
+    if (!props.onlyWords) {
+      this.symbol = '';
+      this.name = props.name;
+    }
+  }
+}
+ 
+// The Logger's prototype's method 'info' 'warn' etc. are compatible with console.log
+// So you can use it as console.log
+levels.forEach(item => {
+  const { level, color } = item;
+  const fnName = level.toLocaleLowerCase();
+ 
+  /**
+   * Logs an error message.
+   * @function
+   * @memberof Logger.prototype
+   * @param {string} firstParam - The first parameter to log.
+   * @param {...any} rest - Additional parameters to log.
+   * @returns {string} - The formatted log message.
+   */
+  Logger.prototype[fnName] = function fn(firstParam, ...rest) {
+    // if (typeof params === 'obejct') params = JSON.stringify(params);
+ 
+    let prefix = `${this.symbol ? this.symbol + ' ' : ''}${this.name ? this.name + ' ' : ''}[${level}]: `;
+    if (typeof firstParam === 'object' && firstParam !== null) {
+      prefix += '\n';
+      Eif (this.log) {
+        console.log(color(prefix), firstParam, ...rest);
+      }
+      return chalk(color(prefix), firstParam, ...rest);
+    }
+    // To compatible with the situation below, We need to surround the rest with method color
+    // logger.error('Your Node.js version is needed to >= %s', '10.1');
+    if (this.log) {
+      console.log(color(prefix + firstParam), color(...rest));
+    }
+    return chalk(color(prefix + firstParam), color(...rest));
+  };
+});
+ 
+export default Logger;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/utils/fs.js.html b/src/utils/fs.js.html new file mode 100644 index 0000000..c19f9cb --- /dev/null +++ b/src/utils/fs.js.html @@ -0,0 +1,259 @@ + + + + + + Code coverage report for src/utils/fs.js + + + + + + + + + +
+
+

All files / src/utils fs.js

+
+ +
+ 84.21% + Statements + 16/19 +
+ + +
+ 80% + Branches + 8/10 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 83.33% + Lines + 15/18 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59  +  +  +  +  +1x +1x +  +  +  +  +  +1x +  +  +  +  +1x +  +  +  +  +  +  +  +2x +1x +  +  +1x +  +1x +7x +  +  +7x +1x +  +  +  +  +  +  +  +  +  +  +  +  +2x +2x +2x +  +  +2x +  +  +  + 
import fs from 'fs';
+import os from 'os';
+ 
+import { promisify } from './utils.js';
+ 
+const readFile = promisify(fs.readFile);
+const writeFile = promisify(fs.writeFile);
+ 
+/**
+ * Carriage return character code.
+ * @type {number}
+ */
+const cr = '\r'.charCodeAt(0);
+/**
+ * Line feed character code.
+ * @type {number}
+ */
+const lf = '\n'.charCodeAt(0);
+ 
+/**
+ * Retrieves the end-of-line (EOL) sequence from a file.
+ * @param {string} path - The path to the file.
+ * @returns {Promise<string | undefined>} A promise that resolves with the EOL sequence found in the file, or undefined.
+ */
+async function getEolFromFile(path) {
+  if (!fs.existsSync(path)) {
+    return undefined;
+  }
+ 
+  const buffer = await readFile(path);
+ 
+  for (let i = 0; i < buffer.length; ++i) {
+    Iif (buffer[i] === cr) {
+      return '\r\n';
+    }
+    if (buffer[i] === lf) {
+      return '\n';
+    }
+  }
+  return undefined;
+}
+ 
+/**
+ * Writes data to a file while preserving the original end-of-line (EOL) sequence.
+ * @param {string} path - The path to the file.
+ * @param {string} data - The data to write.
+ * @returns {Promise<void>} A promise that resolves when the file is successfully written.
+ */
+async function writeFilePreservingEol(path, data) {
+  const eol = (await getEolFromFile(path)) || os.EOL;
+  let result = data;
+  Iif (eol !== '\n') {
+    result = result.replace(/\n/g, eol);
+  }
+  await writeFile(path, result);
+}
+ 
+export { writeFilePreservingEol };
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/utils/index.html b/src/utils/index.html new file mode 100644 index 0000000..259642b --- /dev/null +++ b/src/utils/index.html @@ -0,0 +1,191 @@ + + + + + + Code coverage report for src/utils + + + + + + + + + +
+
+

All files src/utils

+
+ +
+ 88.37% + Statements + 228/258 +
+ + +
+ 82.11% + Branches + 124/151 +
+ + +
+ 93.18% + Functions + 41/44 +
+ + +
+ 88.62% + Lines + 226/255 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
Logger.js +
+
100%20/2093.75%15/16100%3/3100%20/20
fs.js +
+
84.21%16/1980%8/10100%2/283.33%15/18
myLogger.js +
+
100%2/2100%0/0100%0/0100%2/2
userHomeDir.js +
+
100%11/11100%8/8100%4/4100%11/11
utils.js +
+
85.56%160/18777.35%82/10690.9%30/3385.94%159/185
wallet.js +
+
100%19/19100%11/11100%2/2100%19/19
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/src/utils/myLogger.js.html b/src/utils/myLogger.js.html new file mode 100644 index 0000000..228013e --- /dev/null +++ b/src/utils/myLogger.js.html @@ -0,0 +1,145 @@ + + + + + + Code coverage report for src/utils/myLogger.js + + + + + + + + + +
+
+

All files / src/utils myLogger.js

+
+ +
+ 100% + Statements + 2/2 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 100% + Lines + 2/2 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21  +  +  +  +  +  +21x +  +  +  +  +  +  +  +21x +  +  +  +  +  + 
import Logger from './Logger.js';
+ 
+/**
+ * Instance of Logger with full logging enabled.
+ * @type {Logger}
+ */
+const logger = new Logger({
+  name: 'AElf',
+  log: true
+});
+/**
+ * Instance of Logger that logs only words.
+ * @type {Logger}
+ */
+const plainLogger = new Logger({
+  onlyWords: true,
+  log: false
+});
+ 
+export { logger, plainLogger };
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/utils/userHomeDir.js.html b/src/utils/userHomeDir.js.html new file mode 100644 index 0000000..d3cace3 --- /dev/null +++ b/src/utils/userHomeDir.js.html @@ -0,0 +1,274 @@ + + + + + + Code coverage report for src/utils/userHomeDir.js + + + + + + + + + +
+
+

All files / src/utils userHomeDir.js

+
+ +
+ 100% + Statements + 11/11 +
+ + +
+ 100% + Branches + 8/8 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 11/11 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64  +  +  +  +  +  +  +27x +  +  +  +  +  +  +27x +26x +  +1x +  +  +  +  +  +  +  +4x +  +  +  +  +  +  +  +  +29x +  +  +  +  +  +  +  +27x +  +  +  +  +  +  +27x +  +  +  +  +  +  +27x +1x +  +26x +  +  +  + 
import path from 'path';
+import { homedir } from 'os';
+ 
+/**
+ * Path to the home directory.
+ * @type {string}
+ */
+const home = homedir();
+ 
+/**
+ * Retrieves the user ID (UID) of the current process.
+ * @returns {number | null} The UID of the current process if available, otherwise null.
+ */
+function getUid() {
+  if (process.platform !== 'win32' && process.getuid) {
+    return process.getuid();
+  }
+  return null;
+}
+ 
+/**
+ * Checks if the current environment is a fake root environment.
+ * @returns {boolean} True if the environment is a fake root, false otherwise.
+ */
+function isFakeRoot() {
+  return Boolean(process.env.FAKEROOTKEY);
+}
+ 
+/**
+ * Checks if a given user ID belongs to the root user.
+ * @param {number | null} uid - The user ID to check.
+ * @returns {boolean} True if the user ID belongs to the root user, false otherwise.
+ */
+function isRootUser(uid) {
+  return uid === 0;
+}
+ 
+/**
+ * Checks if the current operating system is Windows.
+ * @returns {boolean} True if the operating system is Windows, false otherwise.
+ */
+function isWindows() {
+  return process.platform === 'win32';
+}
+ 
+/**
+ * Indicates whether the current environment is running as the root user.
+ * @type {boolean}
+ */
+const ROOT_USER = isRootUser(getUid()) && !isFakeRoot();
+ 
+/**
+ * User's home directory path.
+ * @type {any}
+ */
+let userHomeDir;
+if (isWindows()) {
+  userHomeDir = path.resolve(home, './AppData/Local');
+} else {
+  userHomeDir = path.resolve(home, './.local/share');
+}
+ 
+export { userHomeDir, home, isFakeRoot, isRootUser, ROOT_USER };
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/utils/utils.js.html b/src/utils/utils.js.html new file mode 100644 index 0000000..5edca93 --- /dev/null +++ b/src/utils/utils.js.html @@ -0,0 +1,1579 @@ + + + + + + Code coverage report for src/utils/utils.js + + + + + + + + + +
+
+

All files / src/utils utils.js

+
+ +
+ 85.56% + Statements + 160/187 +
+ + +
+ 77.35% + Branches + 82/106 +
+ + +
+ 90.9% + Functions + 30/33 +
+ + +
+ 85.94% + Lines + 159/185 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360 +361 +362 +363 +364 +365 +366 +367 +368 +369 +370 +371 +372 +373 +374 +375 +376 +377 +378 +379 +380 +381 +382 +383 +384 +385 +386 +387 +388 +389 +390 +391 +392 +393 +394 +395 +396 +397 +398 +399 +400 +401 +402 +403 +404 +405 +406 +407 +408 +409 +410 +411 +412 +413 +414 +415 +416 +417 +418 +419 +420 +421 +422 +423 +424 +425 +426 +427 +428 +429 +430 +431 +432 +433 +434 +435 +436 +437 +438 +439 +440 +441 +442 +443 +444 +445 +446 +447 +448 +449 +450 +451 +452 +453 +454 +455 +456 +457 +458 +459 +460 +461 +462 +463 +464 +465 +466 +467 +468 +469 +470 +471 +472 +473 +474 +475 +476 +477 +478 +479 +480 +481 +482 +483 +484 +485 +486 +487 +488 +489 +490 +491 +492 +493 +494 +495 +496 +497 +498 +499  +  +  +  +  +  +  +  +  +  +  +  +  +25x +  +  +  +  +  +  +  +  +  +  +  +  +3x +3x +3x +3x +3x +  +3x +3x +  +  +3x +1x +1x +  +  +3x +1x +  +2x +  +  +  +3x +  +  +  +  +  +  +  +  +  +281x +  +  +  +  +  +17x +  +  +  +  +  +  +  +  +8x +  +  +  +  +  +  +  +  +6x +  +1x +1x +  +6x +5x +  +  +  +  +  +  +  +  +  +  +  +  +17x +6x +  +11x +11x +11x +11x +5x +  +6x +6x +6x +6x +  +  +  +1x +1x +  +11x +11x +  +  +  +13x +5x +  +8x +7x +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +10x +1x +  +9x +1x +  +8x +  +8x +14x +14x +13x +  +13x +4x +  +9x +  +1x +1x +  +  +8x +  +1x +1x +  +8x +  +  +  +2x +  +  +2x +2x +2x +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +8x +8x +  +8x +8x +  +  +  +8x +8x +4x +4x +  +4x +1x +  +3x +2x +  +1x +  +  +  +  +  +  +  +  +12x +12x +12x +3x +  +  +  +9x +  +12x +  +  +  +  +  +  +  +22x +  +  +25x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  +  +3x +  +  +  +  +  +  +  +  +2x +2x +2x +  +  +  +  +2x +2x +  +  +2x +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +  +  +  +  +  +  +  +4x +3x +3x +3x +  +  +  +  +3x +3x +  +  +  +1x +1x +  +  +  +  +  +1x +1x +  +2x +2x +2x +2x +2x +  +  +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +  +2x +  +  +  +3x +  +  +  +4x +  +  +  +13x +13x +13x +13x +  +  +  +4x +13x +13x +  +  +  +  +  +  +  +  +13x +  +  +  +  +  +4x +4x +4x +  +  +  +  +  +  +  +  +5x +1x +  +  +4x +  +4x +  +  +  +  +4x +4x +4x +4x +4x +4x +4x +3x +  +4x +  +1x +  +1x +1x +  +  +  +  +  +  +  +3x +3x +  +  +4x +  +  +25x +2x +2x +2x +2x +2x +4x +4x +4x +4x +  +  +2x +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import AElf from 'aelf-sdk';
+import moment from 'moment';
+import chalk from 'chalk';
+import path from 'path';
+import { v4 as uuid } from 'uuid';
+import fs from 'fs';
+import { fileURLToPath } from 'url';
+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;
+ 
+/**
+ * @typedef {import('ora').Ora} Ora
+ * @typedef {import('inquirer').DistinctQuestion} DistinctQuestion
+ */
+/**
+ * Promisifies a function.
+ * @param {Function} fn - The function to promisify.
+ * @param {boolean} [firstData] - Whether to pass first data.
+ * @returns {Function} A promisified function.
+ */
+function promisify(fn, firstData) {
+  return (...args) =>
+    new Promise((resolve, reject) => {
+      args.push((err, ...result) => {
+        let res = result;
+        let error = err;
+ 
+        Eif (result.length <= 1) {
+          res = result[0];
+        }
+ 
+        if (firstData) {
+          res = error;
+          error = null;
+        }
+ 
+        if (error) {
+          reject(error);
+        } else {
+          resolve(res);
+        }
+      });
+ 
+      fn.call(null, ...args);
+    });
+}
+ 
+/**
+ * Converts a string to camelCase.
+ * @param {string} str - The input string.
+ * @returns {string} The camelCase version of the string.
+ */
+function camelCase(str) {
+  return _camelCase(str);
+}
+ 
+// todo: repository aelf-sdk, add a method that return all contract's name
+// so that we can develop a better method to help us identify the aelf's contract
+function isAElfContract(str) {
+  return str.trim().toLowerCase().startsWith('aelf.');
+}
+ 
+/**
+ * @description judge if the input is regular expression
+ * @param {*} o
+ * @returns boolean flag
+ */
+function isRegExp(o) {
+  return o && Object.prototype.toString.call(o) === '[object RegExp]';
+}
+ 
+/**
+ * Retrieves the list of methods of a contract.
+ * @param {Object.<string, any>} [contract] - The contract object.
+ * @returns {string[]} An array of method names.
+ */
+function getContractMethods(contract = {}) {
+  if (!contract) {
+    // @ts-ignore
+    plainLogger.fatal('There is no such contract');
+    process.exit(1);
+  }
+  return Object.keys(contract)
+    .filter(v => /^[A-Z]/.test(v))
+    .sort();
+}
+ 
+/**
+ * Retrieves an instance of a contract.
+ * @param {string} contractAddress - The address of the contract.
+ * @param {any} aelf - The AElf instance.
+ * @param {any} wallet - The wallet instance.
+ * @param {Ora} oraInstance - The ora instance for logging.
+ * @returns {Promise<any>} A promise that resolves to the contract instance.
+ */
+async function getContractInstance(contractAddress, aelf, wallet, oraInstance) {
+  if (typeof contractAddress !== 'string') {
+    return contractAddress;
+  }
+  oraInstance.start('Fetching contract');
+  let contract = null;
+  try {
+    if (!isAElfContract(contractAddress)) {
+      contract = await aelf.chain.contractAt(contractAddress, wallet);
+    } else {
+      const { GenesisContractAddress } = await aelf.chain.getChainStatus();
+      const genesisContract = await aelf.chain.contractAt(GenesisContractAddress, wallet);
+      const address = await genesisContract.GetContractAddressByName.call(AElf.utils.sha256(contractAddress));
+      contract = await aelf.chain.contractAt(address, wallet);
+    }
+  } catch (e) {
+    // @ts-ignore
+    oraInstance.fail(plainLogger.error('Failed to find the contract, please enter the correct contract name!'));
+    process.exit(1);
+  }
+  oraInstance.succeed('Fetching contract successfully!');
+  return contract;
+}
+ 
+function getMethod(method, contract) {
+  if (typeof method !== 'string') {
+    return method;
+  }
+  if (contract[method]) {
+    return contract[method];
+  }
+  throw new Error(`Not exist method ${method}`);
+}
+ 
+/**
+ * Prompts with tolerance for multiple attempts.
+ * @param {Object} options - Prompt options.
+ * @param {Function} options.processAfterPrompt - Function to process after prompt.
+ * @param {string | RegExp} options.pattern - Pattern for the prompt.
+ * @param {number} options.times - Number of times to prompt.
+ * @param {DistinctQuestion} options.prompt - prompt.
+ * @param {Ora} oraInstance - The ora instance for logging.
+ * @returns {Promise<Object.<string, any>>} The result of the prompt.
+ */
+async function promptTolerateSeveralTimes({ processAfterPrompt = () => {}, pattern, times = 3, prompt = [] }, oraInstance) {
+  if (pattern && !isRegExp(pattern)) {
+    throw new Error("param 'pattern' must be a regular expression!");
+  }
+  if (processAfterPrompt && typeof processAfterPrompt !== 'function') {
+    throw new Error("Param 'processAfterPrompt' must be a function!");
+  }
+  let askTimes = 0;
+  let answerInput;
+  while (askTimes < times) {
+    try {
+      answerInput = await inquirer.prompt(prompt);
+      answerInput = await processAfterPrompt(answerInput);
+      // @ts-ignore
+      if (!pattern || pattern.test(answerInput)) {
+        break;
+      }
+      askTimes++;
+    } catch (e) {
+      oraInstance.fail('Failed');
+      break;
+    }
+  }
+  if (askTimes >= times && answerInput === null) {
+    // @ts-ignore
+    oraInstance.fail(plainLogger.fatal(`You has entered wrong message ${times} times!`));
+    process.exit(1);
+  }
+  return answerInput;
+}
+ 
+function isFilePath(val) {
+  Iif (!val) {
+    return false;
+  }
+  const filePath = path.resolve(process.cwd(), val);
+  try {
+    const stat = fs.statSync(filePath);
+    return stat.isFile();
+  } catch (e) {
+    return false;
+  }
+}
+ 
+/**
+ * Retrieves the result of a transaction.
+ * @param {*} aelf - The AElf instance.
+ * @param {string} txId - The transaction ID.
+ * @param {number} [times] - Number of times to retry.
+ * @param {number} [delay] - Delay between retries.
+ * @param {number} [timeLimit] - Time limit for retries.
+ * @returns {Promise<any>} The transaction result.
+ */
+async function getTxResult(aelf, txId, times = 0, delay = 3000, timeLimit = 3) {
+  const currentTime = times + 1;
+  await /** @type {Promise<void>} */ (
+    new Promise(resolve => {
+      setTimeout(() => {
+        resolve();
+      }, delay);
+    })
+  );
+  const tx = await aelf.chain.getTxResult(txId);
+  if (tx.Status === 'PENDING' && currentTime <= timeLimit) {
+    const result = await getTxResult(aelf, txId, currentTime, delay, timeLimit);
+    return result;
+  }
+  if (tx.Status === 'PENDING' && currentTime > timeLimit) {
+    return tx;
+  }
+  if (tx.Status === 'MINED') {
+    return tx;
+  }
+  throw tx;
+}
+ 
+/**
+ * Parses a JSON string.
+ * @param {string} [str] - The JSON string to parse.
+ * @returns {*} The parsed JSON object.
+ */
+function parseJSON(str = '') {
+  let result = null;
+  try {
+    result = JSON.parse(str);
+    Iif (typeof result === 'number' && /^-?\d+(\.\d+)?[eE][+-]?\d+$/.test(String(result))) {
+      result = str;
+    }
+  } catch (e) {
+    result = str;
+  }
+  return result;
+}
+ 
+/**
+ * Generates a random ID.
+ * @returns {string} The random ID.
+ */
+function randomId() {
+  return uuid().replace(/-/g, '');
+}
+ 
+const PROTO_TYPE_PROMPT_TYPE = {
+  '.google.protobuf.Timestamp': {
+    type: 'datetime',
+    format: ['yyyy', '/', 'mm', '/', 'dd', ' ', 'HH', ':', 'MM'],
+    initial: moment()
+      .add({
+        hours: 1,
+        minutes: 5
+      })
+      .toDate(),
+    transformFunc(time) {
+      return {
+        seconds: moment(time).unix(),
+        nanos: moment(time).milliseconds() * 1000
+      };
+    }
+  },
+  default: {
+    type: 'input',
+    transformFunc: v => v
+  }
+};
+ 
+function isSpecialParameters(inputType) {
+  return (
+    inputType.fieldsArray &&
+    inputType.fieldsArray.length === 1 &&
+    ['Hash', 'Address'].includes(inputType.name) &&
+    inputType.fieldsArray[0].type === 'bytes'
+  );
+}
+ 
+async function getParamValue(type, fieldName, rule) {
+  let prompts = PROTO_TYPE_PROMPT_TYPE[type] || PROTO_TYPE_PROMPT_TYPE.default;
+  const fieldNameWithoutDot = fieldName.replace('.', '');
+  prompts = {
+    ...prompts,
+    name: fieldNameWithoutDot,
+    message: `Enter the required param <${fieldName}>:`
+  };
+  const promptValue = (await inquirer.prompt(prompts))[fieldNameWithoutDot];
+  Iif (rule === 'repeated') {
+    prompts.transformFunc = v => JSON.parse(v.replace(/'/g, '"'));
+  }
+  let value = parseJSON(await prompts.transformFunc(promptValue));
+  Iif (typeof value === 'string' && isFilePath(value)) {
+    const filePath = path.resolve(process.cwd(), value);
+ 
+    const { read } = await inquirer.prompt({
+      type: 'confirm',
+      name: 'read',
+      // eslint-disable-next-line max-len
+      message: `It seems that you have entered a file path, do you want to read the file content and take it as the value of <${fieldName}>`
+    });
+    if (read) {
+      try {
+        fs.accessSync(filePath, fs.constants.R_OK);
+      } catch (err) {
+        throw new Error(`permission denied, no read access to file ${filePath}!`);
+      }
+      value = fs.readFileSync(filePath).toString('base64');
+    }
+  }
+  return value;
+}
+ 
+/**
+ * Retrieves parameters of a method.
+ * @param {*} method - The method.
+ * @returns {Promise<Object.<string, any>>} A promise that resolves to the parameters object.
+ */
+async function getParams(method) {
+  const fields = Object.entries(method.inputTypeInfo.fields || {});
+  let result = {};
+  Eif (fields.length > 0) {
+    console.log(
+      chalk.yellow(
+        '\nIf you need to pass file contents as a parameter, you can enter the relative or absolute path of the file\n'
+      )
+    );
+    console.log('Enter the params one by one, type `Enter` to skip optional param:');
+    if (isSpecialParameters(method.inputType)) {
+      /**
+       * @type {Object.<string, any>}
+       */
+      let prompts = PROTO_TYPE_PROMPT_TYPE.default;
+      prompts = {
+        ...prompts,
+        name: 'value',
+        message: 'Enter the required param <value>:'
+      };
+ 
+      const promptValue = (await inquirer.prompt(prompts)).value;
+      result = parseJSON(promptValue);
+    } else {
+      for (const [fieldName, fieldType] of fields) {
+        const { type, rule } = fieldType;
+        let innerType = null;
+        try {
+          innerType = method.inputType.lookupType(type);
+        } catch (e) {}
+        let paramValue;
+        // todo: use recursion
+        Iif (
+          rule !== 'repeated' &&
+          innerType &&
+          !isSpecialParameters(innerType) &&
+          (type || '').indexOf('google.protobuf.Timestamp') === -1
+        ) {
+          let innerResult = {};
+          const innerInputTypeInfo = innerType.toJSON();
+          const innerFields = Object.entries(innerInputTypeInfo.fields || {});
+          if (isSpecialParameters(innerFields)) {
+            /**
+             * @type {Object.<string, any>}
+             */
+            let prompts = PROTO_TYPE_PROMPT_TYPE.default;
+            prompts = {
+              ...prompts,
+              name: 'value',
+              message: `Enter the required param <${fieldName}.value>:`
+            };
+ 
+            innerResult = (await inquirer.prompt(prompts)).value;
+          } else {
+            for (const [innerFieldName, innerFieldType] of innerFields) {
+              innerResult[innerFieldName] = parseJSON(await getParamValue(innerFieldType.type, `${fieldName}.${innerFieldName}`));
+            }
+          }
+          paramValue = innerResult;
+        } else {
+          paramValue = await getParamValue(type, fieldName, rule);
+        }
+        result[fieldName] = parseJSON(paramValue);
+      }
+    }
+  }
+  return result;
+}
+ 
+async function getProto(aelf, address) {
+  return AElf.pbjs.Root.fromDescriptor(await aelf.chain.getContractFileDescriptorSet(address));
+}
+ 
+function decodeBase64(str) {
+  const { util } = AElf.pbjs;
+  const buffer = util.newBuffer(util.base64.length(str));
+  util.base64.decode(str, buffer, 0);
+  return buffer;
+}
+ 
+function getDeserializeLogResult(serializedData, dataType) {
+  let deserializeLogResult = serializedData.reduce((acc, v) => {
+    let deserialize = dataType.decode(decodeBase64(v));
+    deserialize = dataType.toObject(deserialize, {
+      enums: String, // enums as string names
+      longs: String, // longs as strings (requires long.js)
+      bytes: String, // bytes as base64 encoded strings
+      defaults: false, // includes default values
+      arrays: true, // populates empty arrays (repeated fields) even if defaults=false
+      objects: true, // populates empty objects (map fields) even if defaults=false
+      oneofs: true // includes virtual oneof fields set to the present field's name
+    });
+    return {
+      ...acc,
+      ...deserialize
+    };
+  }, {});
+ 
+  deserializeLogResult = AElf.utils.transform.transform(dataType, deserializeLogResult, AElf.utils.transform.OUTPUT_TRANSFORMERS);
+  deserializeLogResult = AElf.utils.transform.transformArrayToMap(dataType, deserializeLogResult);
+  return deserializeLogResult;
+}
+/**
+ * Deserializes logs from AElf.
+ * @param {*} aelf - The AElf instance.
+ * @param {Array} [logs] - The logs array to deserialize.
+ * @returns {Promise<any>} A promise that resolves to the deserialized logs.
+ */
+async function deserializeLogs(aelf, logs = []) {
+  if (!logs || logs.length === 0) {
+    return null;
+  }
+  let dirname;
+  try {
+    // for test as we cannot use import.meta.url in Jest
+    dirname = __dirname;
+  } catch {
+    const __filename = fileURLToPath(import.meta.url);
+    dirname = path.dirname(__filename);
+  }
+  const filePath = path.resolve(dirname, '../package.json');
+  const Root = await load(path.resolve(dirname, '../protobuf/virtual_transaction.proto'));
+  let results = await Promise.all(logs.map(v => getProto(aelf, v.Address)));
+  results = results.map((proto, index) => {
+    const { Name, NonIndexed, Indexed = [] } = logs[index];
+    const serializedData = [...Indexed];
+    if (NonIndexed) {
+      serializedData.push(NonIndexed);
+    }
+    if (Name === 'VirtualTransactionCreated') {
+      // VirtualTransactionCreated is system-default
+      try {
+        // @ts-ignore
+        const dataType = Root.VirtualTransactionCreated;
+        return getDeserializeLogResult(serializedData, dataType);
+      } catch (e) {
+        // if normal contract has a method called VirtualTransactionCreated
+        const dataType = proto.lookupType(Name);
+        return getDeserializeLogResult(serializedData, dataType);
+      }
+    } else {
+      // other method
+      const dataType = proto.lookupType(Name);
+      return getDeserializeLogResult(serializedData, dataType);
+    }
+  });
+  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];
+      Eif (cleanValue !== '') {
+        cleanData[cleanKey] = cleanValue;
+      }
+    }
+    Object.keys(cleanData).length && results.push(cleanData);
+  }
+  return results;
+};
+ 
+export {
+  promisify,
+  camelCase,
+  getContractMethods,
+  getContractInstance,
+  getMethod,
+  promptTolerateSeveralTimes,
+  isAElfContract,
+  getTxResult,
+  parseJSON,
+  randomId,
+  getParams,
+  deserializeLogs,
+  parseCSV
+};
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/src/utils/wallet.js.html b/src/utils/wallet.js.html new file mode 100644 index 0000000..e29ef2b --- /dev/null +++ b/src/utils/wallet.js.html @@ -0,0 +1,256 @@ + + + + + + Code coverage report for src/utils/wallet.js + + + + + + + + + +
+
+

All files / src/utils wallet.js

+
+ +
+ 100% + Statements + 19/19 +
+ + +
+ 100% + Branches + 11/11 +
+ + +
+ 100% + Functions + 2/2 +
+ + +
+ 100% + Lines + 19/19 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +20x +20x +20x +1x +  +19x +19x +18x +  +1x +  +  +  +  +  +  +  +  +  +  +  +4x +4x +1x +  +3x +1x +  +2x +  +  +2x +2x +1x +  +2x +2x +  +  +  + 
import AElf from 'aelf-sdk';
+import fs from 'fs';
+import path from 'path';
+import { mkdirpSync } from 'mkdirp';
+import inquirer from 'inquirer';
+import Registry from '../rc/index.js';
+import { passwordPrompts } from './constants.js';
+import BaseSubCommand from '../command/baseSubCommand.js';
+ 
+/**
+ * Retrieves a wallet based on the provided command root, address, and password.
+ * @param {string} commandRoot - The root directory of the command.
+ * @param {string} address - The address of the wallet.
+ * @param {string} password - The password for the wallet.
+ * @returns {any} - The wallet instance.
+ */
+function getWallet(commandRoot, address, password) {
+  const keyStoreFile = path.resolve(commandRoot, `keys/${address}.json`);
+  const keyStore = JSON.parse(Registry.getFileOrNot(keyStoreFile, '{}').toString());
+  if (Object.keys(keyStore).length === 0) {
+    throw new Error('Make sure you entered the correct account address');
+  }
+  try {
+    const { privateKey, mnemonic } = AElf.wallet.keyStore.unlockKeystore(keyStore, password);
+    return { ...AElf.wallet.getWalletByPrivateKey(privateKey), mnemonic };
+  } catch (e) {
+    throw new Error(e.message || 'Make sure you entered the correct password');
+  }
+}
+ 
+/**
+ * Saves the wallet keystore to the specified directory.
+ * @param {any} wallet - The wallet instance to be saved.
+ * @param {string} datadir - The directory to save the wallet keystore.
+ * @param {string} [cipher] - Optional cipher to be used for encryption.
+ * @returns {Promise<string>} - A promise that resolves to the path of the saved keystore file.
+ */
+async function saveKeyStore(wallet, datadir, cipher = 'aes-128-ctr') {
+  const { password, confirmPassword } = BaseSubCommand.normalizeConfig(await inquirer.prompt(passwordPrompts));
+  if (password !== confirmPassword) {
+    throw new Error('Passwords are different');
+  }
+  if (password.length <= 6) {
+    throw new Error('password is too short');
+  }
+  const keyStore = AElf.wallet.keyStore.getKeystore(wallet, password, {
+    cipher
+  });
+  const keyStorePath = path.resolve(datadir, `keys/${wallet.address}.json`);
+  if (!fs.existsSync(path.resolve(datadir, 'keys'))) {
+    mkdirpSync(path.resolve(datadir, 'keys'));
+  }
+  fs.writeFileSync(keyStorePath, JSON.stringify(keyStore, null, 2));
+  return keyStorePath;
+}
+ 
+export { getWallet, saveKeyStore };
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/test/constants.js.html b/test/constants.js.html new file mode 100644 index 0000000..1a59dcd --- /dev/null +++ b/test/constants.js.html @@ -0,0 +1,106 @@ + + + + + + Code coverage report for test/constants.js + + + + + + + + + +
+
+

All files / test constants.js

+
+ +
+ 100% + Statements + 5/5 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 100% + Lines + 5/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8  +  +18x +18x +18x +18x +18x + 
import path from 'path';
+ 
+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');
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/test/index.html b/test/index.html new file mode 100644 index 0000000..44ae386 --- /dev/null +++ b/test/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for test + + + + + + + + + +
+
+

All files test

+
+ +
+ 100% + Statements + 5/5 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 0/0 +
+ + +
+ 100% + Lines + 5/5 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
constants.js +
+
100%5/5100%0/0100%0/0100%5/5
+
+
+
+ + + + + + + + \ No newline at end of file