soluser 1.0.10 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -56,4 +56,7 @@ Signature: 4BLUt5uxutbEwVywBTbAoBnG4EKb6QgsHgk3JRfjy6uJCoNjxdyYodbAhsWPXquBBwVzu
56
56
 
57
57
  500000005 SOL
58
58
  ```
59
-
59
+ ## import 助记词
60
+ ```shell
61
+ $ soluser import "mnemonic words" --alias someone
62
+ ```
package/bin/index.js CHANGED
@@ -8,9 +8,10 @@ const removeAccount = require('../src/commands/remove');
8
8
  const { showExamples } = require('../src/utils/example');
9
9
  const pruneAccount = require('../src/commands/prune');
10
10
  const clear = require('../src/commands/clear');
11
+ const restoreAccount = require('../src/commands/restore');
11
12
  // 导入 keyfile 命令
12
13
  const showKeyfilePath = require('../src/commands/keyfile');
13
-
14
+ const importMnemonic = require( '../src/commands/from_mnemonic');
14
15
 
15
16
  // 导入地址查询命令
16
17
  const showAddress = require('../src/commands/address');
@@ -46,6 +47,16 @@ program
46
47
  requestAirdrop(amount, alias);
47
48
  });
48
49
 
50
+ program
51
+ .command('import')
52
+ .description('Import a Solana account from mnemonic')
53
+ .argument('<mnemonic>', 'mnemoic oconsole.logf the account to import')
54
+ .option('--alias <alias>', 'alias of account')
55
+ .action((mnemonic,{alias}) => {
56
+ console.log("alias:" ,alias)
57
+ importMnemonic(mnemonic, alias);
58
+ });
59
+
49
60
  // ... existing code ...
50
61
  // 定义余额查询命令
51
62
  program
@@ -107,7 +118,10 @@ program
107
118
  .command('list')
108
119
  .alias('l')
109
120
  .description('List all Solana accounts')
110
- .action(listAccounts);
121
+ .action(() =>{
122
+ listAccounts();
123
+ //process.exit(0);
124
+ });
111
125
 
112
126
  // 定义删除账号命令
113
127
  program
@@ -133,6 +147,14 @@ program
133
147
  .description('prune a Solana account (prune the private key file)')
134
148
  .action((alias) => {
135
149
  pruneAccount(alias);
150
+
151
+ });
152
+ // 定义删除账号命令
153
+ program
154
+ .command('restore ') // <alias> 为必填参数
155
+ .description('restore file from backup directory')
156
+ .action(() => {
157
+ restoreAccount();
136
158
  });
137
159
 
138
160
 
@@ -141,7 +163,7 @@ program
141
163
  program.on('--help', () => {
142
164
  console.log("version:" ,version);
143
165
  showExamples();
144
- process.exit(1);
166
+ //process.exit(1);
145
167
  });
146
168
 
147
169
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "soluser",
3
- "version": "1.0.10",
3
+ "version": "1.0.14",
4
4
  "description": "A CLI tool to manage Solana accounts (new, switch, list)",
5
5
  "main": "bin/index.js",
6
6
  "bin": {
@@ -9,7 +9,8 @@
9
9
  "scripts": {
10
10
  "test": "mocha test/**/*.test.js --noparallel",
11
11
  "test:watch": "mocha test/**/*.test.js --watch",
12
- "test:coverage": "nyc mocha test/**/*.test.js"
12
+ "test:coverage": "nyc mocha test/**/*.test.js",
13
+ "clear": "bin/index.js clear"
13
14
  },
14
15
  "keywords": [
15
16
  "solana",
@@ -20,14 +21,18 @@
20
21
  "author": "guahuzi",
21
22
  "license": "MIT",
22
23
  "dependencies": {
24
+ "@solana/web3.js": "^1.98.4",
25
+ "bip39": "^3.1.0",
26
+ "bs58": "^6.0.0",
23
27
  "chalk": "4.1.2",
24
28
  "cli-table3": "^0.6.5",
25
29
  "commander": "^14.0.2",
30
+ "ed25519-hd-key": "^1.3.0",
26
31
  "readline": "^1.3.0"
27
32
  },
28
33
  "devDependencies": {
29
34
  "chai": "^4.3.7",
30
- "mocha": "^10.2.0",
35
+ "mocha": "^10.8.2",
31
36
  "nyc": "^15.1.0"
32
37
  },
33
38
  "files": [
@@ -37,6 +42,7 @@
37
42
  "package-lock.json",
38
43
  "README.md"
39
44
  ],
45
+ "type": "commonjs",
40
46
  "repository": {
41
47
  "type": "git",
42
48
  "url": "https://github.com/nextuser/soluser.git"
@@ -1,3 +1,5 @@
1
+ const {KEYS_DIR, getBackupDir, addBackupSuffix } = require('../utils/path');
2
+ const {getAddressByFile} = require('../utils/solana');
1
3
  // src/commands/clear.js
2
4
  const clear = async () => {
3
5
  const fs = require('fs');
@@ -33,11 +35,11 @@ const clear = async () => {
33
35
 
34
36
  // 获取当前时间戳为 YYMMDDHHMMSS 格式
35
37
  const now = new Date();
36
- const timestamp = `${now.getFullYear().toString().slice(-2)}${(now.getMonth() + 1).toString().padStart(2, '0')}${now.getDate().toString().padStart(2, '0')}${now.getHours().toString().padStart(2, '0')}${now.getMinutes().toString().padStart(2, '0')}${now.getSeconds().toString().padStart(2, '0')}`;
37
38
 
38
39
  // 定义密钥目录和备份目录
39
- const keysDir = path.join(require('os').homedir(), '.config', 'solana', 'keys');
40
- const backupDir = path.join(require('os').homedir(), '.config', 'solana', '.bak');
40
+ const keysDir = KEYS_DIR;
41
+ const backupDir = getBackupDir();
42
+ console.log(`Backup directory: ${backupDir}`);
41
43
 
42
44
  // 确保备份目录存在
43
45
  if (!fs.existsSync(backupDir)) {
@@ -49,33 +51,33 @@ const clear = async () => {
49
51
  const files = fs.readdirSync(keysDir);
50
52
 
51
53
  // 过滤出 .json 文件并移动
52
- const jsonFiles = files.filter(file => path.extname(file) === '.json');
54
+ const jsonFiles = files.filter(file => path.extname(file) === '.json' && file.length > '.json'.length);
53
55
 
54
56
  if (jsonFiles.length > 0) {
55
57
  let successCount = 0;
56
58
  jsonFiles.forEach(file => {
57
59
  const sourceFile = path.join(keysDir, file);
58
- const fileNameWithoutExt = path.basename(file, '.json');
59
- const destFile = path.join(backupDir, `${fileNameWithoutExt}_${timestamp}.json`);
60
+ const address = getAddressByFile(sourceFile);
61
+ const baseName = path.basename(file, '.json');
62
+ const destFile = path.join(backupDir, `${addBackupSuffix(baseName,address)}.json`);
60
63
 
61
64
  try {
62
65
  fs.renameSync(sourceFile, destFile);
63
- console.log(`Moved ${file} to backup directory as ${fileNameWithoutExt}_${timestamp}.json`);
64
- console.log('All accounts have been removed.')
66
+ console.log(`Moved ${file} to backup directory as ${destFile}`);
65
67
  successCount++;
66
68
  } catch (error) {
67
- console.error(`Failed to move ${file}: ${error.message}`);
69
+ console.log(`Failed to move ${file}: ${error.message}`);
68
70
  }
69
71
  });
70
72
 
71
73
  console.log(`\nSuccessfully moved ${successCount}/${jsonFiles.length} account(s) to backup directory`);
74
+ console.log('All accounts have been removed.')
72
75
  } else {
73
76
 
74
- console.log('All accounts have been removed.')
75
77
  console.log('No account files found to move');
76
78
  }
77
79
  } else {
78
- console.error('Keys directory does not exist');
80
+ console.log('Keys directory does not exist');
79
81
  process.exit(1);
80
82
  }
81
83
  };
@@ -0,0 +1,77 @@
1
+ const { Keypair } = require( '@solana/web3.js');
2
+ const bip39 = require( 'bip39');
3
+ const { Buffer } = require( 'buffer');
4
+ const bs58 = require( 'bs58');
5
+ const fs = require( 'fs');
6
+ const path = require( 'path');
7
+ const {debug} = require('../utils/debug');
8
+ //根据助记词推算密钥(secretkey)和地址(publickey)
9
+ ///const ed25519 = = require('ed25519-hd-keyed25519-hd-key')
10
+ const ed25519 = require( 'ed25519-hd-key');
11
+ const { fileURLToPath } = require('url');
12
+ // BIP44路径
13
+ const BIP44_SOLANA_PATH = "m/44'/501'/0'/0'";
14
+
15
+
16
+ function generate_keypair_path(mnemonic,bip44path) {
17
+ // 生成种子
18
+ const seed = bip39.mnemonicToSeedSync(mnemonic);
19
+ //console.log('seed ', seed)
20
+ const hex_bytes= seed.toString('hex');
21
+ //console.log("hex seed byte",Buffer.from(hex_bytes));
22
+ debug("****BIP44_PATH:",bip44path);
23
+ // 派生密钥
24
+ const derivedSeed = ed25519.derivePath(bip44path,hex_bytes).key
25
+ // 创建 ED25519 密钥对
26
+ const keypair = Keypair.fromSeed(derivedSeed.slice(0, 32)); // 只取前32字节
27
+ return keypair;
28
+ }
29
+
30
+ function importMnemonic(mnemonic,alias) {
31
+ // 验证助记词
32
+ if (!bip39.validateMnemonic(mnemonic)) {
33
+ debug("mnemonic is ",mnemonic);
34
+ throw new Error('无效的助记词');
35
+ }
36
+ let keypair = generate_keypair_path(mnemonic,BIP44_SOLANA_PATH);
37
+
38
+ // 对公钥进行哈希处理
39
+ // 输出公钥和私钥
40
+ console.log('Address:', keypair.publicKey.toBase58());
41
+ //console.log('SOLANA Private Key:', bs58.encode(keypair.secretKey)); // Base64 编码私钥
42
+
43
+ write_secretekey(keypair,alias);
44
+ }
45
+
46
+ function test() {
47
+ // 输入助记词
48
+ const mnemonic = process.env.MNEMONIC; // 替换为你的助记词
49
+ if (!mnemonic) {
50
+ console.log('Your need export MNEMONIC= ')
51
+ process.exit(-1)
52
+ }
53
+
54
+
55
+ importMnemonic(mnemonic,'abc');
56
+ }
57
+
58
+
59
+ function write_secretekey(keypair ,alias){
60
+ let arr = [];
61
+ keypair.secretKey.forEach((item)=>{
62
+ arr.push(item)
63
+ })
64
+
65
+ let account= keypair.publicKey.toBase58()
66
+ let msg = JSON.stringify(arr);
67
+ alias = alias || account;
68
+ //console.log('msg ', msg)
69
+ let file = path.resolve(process.env.HOME,".config/solana/keys/" , alias + ".json");
70
+ fs.writeFileSync(file,msg)
71
+ console.log("write file :",file);
72
+ }
73
+
74
+ if (process.env.TEST == "true"){
75
+ test()
76
+ }
77
+ module.exports = importMnemonic;
@@ -11,7 +11,8 @@ function listAccounts() {
11
11
  return;
12
12
  }
13
13
 
14
- const files = fs.readdirSync(KEYS_DIR).filter(file => file.endsWith('.json'));
14
+ const JSON_SUFFIX = '.json';
15
+ const files = fs.readdirSync(KEYS_DIR).filter(file => file.endsWith(JSON_SUFFIX) && file.length > JSON_SUFFIX.length);
15
16
  if (files.length === 0) {
16
17
  console.log('No accounts found. Create one with "soluser new <alias name>".');
17
18
  return;
@@ -32,6 +33,9 @@ function listAccounts() {
32
33
  // 4. 填充表格数据
33
34
  files.forEach(file => {
34
35
  const alias = path.basename(file, '.json');
36
+ if(alias.trim() === ''){
37
+ return ;
38
+ }
35
39
  const address = getAddress(alias);
36
40
  const isActive = alias === activeAlias ? '*' : '';
37
41
  //console.log("alias: ", alias, "address: ", address, "isActive: ", isActive,'activeAlias',activeAlias )
@@ -14,8 +14,8 @@ function newAccount(alias, wordLength = 12, noPassphrase = false) {
14
14
 
15
15
  // 3. 新增:检查账号是否已存在
16
16
  if (existsAccount(alias)) {
17
- console.error(`Error: Account "${alias}" already exists.`);
18
- console.error(` Use "soluser list" to view existing accounts, or choose a different alias.`);
17
+ console.log(`Error: Account "${alias}" already exists.`);
18
+ console.log(` Use "soluser list" to view existing accounts, or choose a different alias.`);
19
19
  process.exit(1);
20
20
  }
21
21
 
@@ -40,7 +40,7 @@ function newAccount(alias, wordLength = 12, noPassphrase = false) {
40
40
  execCommand(command);
41
41
  console.log(`Successfully created account: ${alias} (saved to ${getKeyFilePath(alias)})`);
42
42
  } catch (err) {
43
- console.error(`Error: Failed to generate key pair: ${err.message}`);
43
+ console.log(`Error: Failed to generate key pair: ${err.message}`);
44
44
  process.exit(1);
45
45
  }
46
46
  }
@@ -0,0 +1,18 @@
1
+ const { getAddress, execCommand ,getActiveKeyPath} = require('../utils/solana');
2
+
3
+ const {encode, decode} = require('bs58').default;
4
+ const fs = require('fs');
5
+ function exportPrivateKey(alias){
6
+ const keyPath = getActiveKeyPath(alias);
7
+ const buffer = fs.readFileSync(keyPath);
8
+ const str = encode(buffer);
9
+ console.log(str);
10
+ console.log(decode(str));
11
+ }
12
+
13
+
14
+ function test(){
15
+ exportPrivateKey('dev');
16
+ }
17
+
18
+ test();
@@ -1,3 +1,6 @@
1
+ const { getBackupDir, addBackupSuffix } = require('../utils/path');
2
+ const { getAddressByFile } = require('../utils/solana');
3
+ const {debug} = require('../utils/debug');
1
4
  async function confirm(alias){
2
5
  const readline = require('readline');
3
6
 
@@ -49,20 +52,20 @@ const removeAccount = async (alias) => {
49
52
 
50
53
  // 获取当前时间戳
51
54
  const now = new Date();
52
- const timestamp = `${now.getFullYear().toString().slice(-2)}${(now.getMonth() + 1).toString().padStart(2, '0')}${now.getDate().toString().padStart(2, '0')}${now.getHours().toString().padStart(2, '0')}${now.getMinutes().toString().padStart(2, '0')}${now.getSeconds().toString().padStart(2, '0')}`;
53
55
 
54
56
 
55
57
  // 确保备份目录存在
56
- const backupDir = path.join(require('os').homedir(), '.config', 'solana', 'keys', '.bak');
57
- if (!fs.existsSync(backupDir)) {
58
- fs.mkdirSync(backupDir, { recursive: true });
59
- }
58
+ const backupDir = getBackupDir();
60
59
 
61
60
  // 构造源文件和目标文件路径
62
61
  const sourceFile = path.join(require('os').homedir(), '.config', 'solana', 'keys', `${alias}.json`);
63
- const destFile = path.join(backupDir, `${alias}_${timestamp}.json`);
62
+
64
63
 
65
64
  if (fs.existsSync(sourceFile)) {
65
+ const address = getAddressByFile(sourceFile);
66
+
67
+ const destFile = path.join(backupDir, `${addBackupSuffix(alias,address)}.json`);
68
+ console.log(`remove Address: ${address} destFile: ${destFile}`)
66
69
  const confirmed = await confirm(alias);
67
70
  if (!confirmed) {
68
71
  console.log('Operation cancelled.');
@@ -71,7 +74,7 @@ const removeAccount = async (alias) => {
71
74
  fs.renameSync(sourceFile, destFile);
72
75
  console.log(`Removed account "${alias}" from list`);
73
76
  } else {
74
- console.error(`Account "${alias}" not found`);
77
+ console.log(`Account "${alias}" not found`);
75
78
  process.exit(1);
76
79
  }
77
80
  };
@@ -0,0 +1,65 @@
1
+ const {KEYS_DIR, getBackupDir } = require('../utils/path');
2
+ const { stripBackupSuffix } = require('../utils/path');
3
+ const { debug } = require('../utils/debug');
4
+ // src/commands/clear.js
5
+ const restore = async () => {
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+
10
+
11
+ // 定义密钥目录和备份目录
12
+ const keysDir = KEYS_DIR;
13
+ const backupDir = getBackupDir( true);
14
+ debug("");
15
+ // 确保备份目录存在
16
+ if (!fs.existsSync(backupDir)) {
17
+ console.log('Backup directory does not exist for', backupDir);
18
+ return ;
19
+ }
20
+
21
+
22
+ if(!fs.existsSync(keysDir)){
23
+ fs.mkdirSync(keysDir, { recursive: true });
24
+ }
25
+
26
+ // 读取密钥目录中的所有文件
27
+ if (fs.existsSync(backupDir)) {
28
+ const files = fs.readdirSync(backupDir);
29
+
30
+ // 过滤出 .json 文件并移动
31
+ const jsonFiles = files.filter(file => path.extname(file) === '.json');
32
+
33
+ if (jsonFiles.length > 0) {
34
+ let successCount = 0;
35
+ jsonFiles.forEach(backFile => {
36
+ const sourceFile = path.join(backupDir, backFile);
37
+ const baseName = path.basename(backFile,".json");
38
+ const originName = stripBackupSuffix(baseName);
39
+ debug("orginal name",originName, "basename",baseName);
40
+ let destFile = path.join(keysDir, `${originName}.json`);
41
+ if( fs.existsSync(destFile)){
42
+ destFile = path.join(keysDir, backFile);
43
+ }
44
+
45
+ try {
46
+ fs.renameSync(sourceFile, destFile);
47
+ console.log(`Moved ${baseName} ${backFile} => as ${destFile}`);
48
+ successCount++;
49
+ } catch (error) {
50
+ console.log(`Failed to move ${backFile}: ${error.message}`);
51
+ }
52
+ });
53
+
54
+
55
+ console.log(`\nSuccessfully moved ${successCount}/${jsonFiles.length} account(s) to backup directory`);
56
+ console.log('All accounts have been restored.')
57
+ } else {
58
+ console.log('No account files found to restore.');
59
+ }
60
+ } else {
61
+ console.log('backup directory does not exist');
62
+ }
63
+ };
64
+
65
+ module.exports = restore;
@@ -1,15 +1,30 @@
1
1
  //const {config}= require('dotenv');
2
2
  //config({debug:false});
3
+ const path = require('path');
3
4
 
4
5
  let debugEnable = false;
5
6
  if(process.env.DEBUG){
6
- console.log("enable debug logging");
7
+ //console.log("enable debug logging");
7
8
  debugEnable = true;
8
9
  }
10
+
11
+ function prevLine(index = 1) {
12
+ const obj = {};
13
+ Error.captureStackTrace(obj, prevLine); // 排除本函数的栈信息
14
+ const stackLine = obj.stack.split('\n')[index];
15
+ const [file_str, lineNumber] = stackLine.split(':');
16
+
17
+ //console.log("file:",file_str,"lineNumber:",lineNumber, "type ", typeof(file_str));
18
+ return [ path.basename(file_str),lineNumber];
19
+ }
20
+
9
21
  function debug(...args ){
10
22
  if(!debugEnable) return;
11
-
12
- console.error(... args )
23
+ const [file,lineNumber] = prevLine(2);
24
+ console.error("\n-------------------")
25
+ console.error(`Debugging: ${file}:${lineNumber}`)
26
+ console.error( ...args);
27
+ console.error("-------------------")
13
28
  }
14
29
 
15
30
  function handleError(err){
@@ -22,5 +37,6 @@ function handleError(err){
22
37
 
23
38
  module.exports = {
24
39
  debug,
40
+ prevLine,
25
41
  handleError
26
42
  }
@@ -1,42 +1,45 @@
1
1
  const examples = `
2
2
 
3
- ## 查看版本
3
+ ## Check version
4
4
 
5
5
  $ soluser --version
6
6
 
7
7
 
8
- ## 新建账号
8
+ ## create account
9
9
 
10
10
  $ soluser new alice
11
11
  $ soluser new bob --word-length 12
12
12
  $ soluser new charlie --word-length 24 --without-passphrase
13
13
 
14
14
 
15
- ## 切换账号
15
+ ## Switch account
16
16
 
17
17
  $ soluser switch bob
18
18
 
19
-
20
- ## 列出账号
19
+ ## List all accounts
21
20
 
22
21
  $ soluser list
23
22
 
24
-
25
- ## 删除账号
23
+ ## Delete account
26
24
 
27
25
  $ soluser remove alice
28
26
 
29
- ## 查看alias对应的地址
27
+ ## View address of alias
30
28
 
31
29
  $ soluser address alice
32
30
 
33
- ## 查看alias对应的余额
31
+ ## View balance of alias
34
32
 
35
33
  $ soluser balance alice
36
34
 
37
- ## airdrop alias
35
+ ## airdrop to alias
38
36
 
39
37
  $ soluser airdrop 5 alice
38
+
39
+ ## import Mnemonic
40
+
41
+ $ soluser import "mnemonic ... (12words or 24words)" --alias someone
42
+
40
43
  `;
41
44
 
42
45
  const chalk = require('chalk');
@@ -1,41 +1,52 @@
1
1
  const { expect } = require('chai');
2
2
  const { execSync, spawn } = require('child_process');
3
3
  const {debug} =require('./debug');
4
- const { stdout } = require('process');
5
- function execExpectInput(inputArgs, prompt , input,expectOut,done){
4
+ const { PassThrough } = require('stream');
5
+
6
+ function execExpectInput(inputArgs, prompt , input,expectOut,check_callback){
6
7
 
7
8
  return new Promise((resolve, reject) => {
8
- const child = spawn('node',inputArgs);
9
+ const task = spawn('node',inputArgs , {stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf8'});
9
10
  const command = "node " + inputArgs.join(" ");
11
+
12
+ const stdoutStream = new PassThrough({encoding: 'utf8'});
13
+ task.stdout.pipe(stdoutStream);
14
+ task.stderr.pipe(stdoutStream);
15
+
16
+
10
17
  debug("execExpectInput command:",command);
11
18
  let output = '';
12
19
  let errMsg = '';
13
20
 
14
- child.stdout.on('data', (data) => {
15
- //debug("stdout:",data.toString());
16
- output += data.toString();
17
- if (output.includes(prompt)) {
18
- //debug("incluse prompt:",prompt);
19
- child.stdin.write( input + '\n');
21
+ stdoutStream.on('data', (chunk) => {
22
+ output += chunk.toString();
23
+ if (output.includes(output)) {
24
+ //output = '';//reset output when output match
25
+ task.stdin.write( input + '\n');
26
+ //output = '';
20
27
  }
21
28
  });
22
29
 
23
- child.stderr.on('data', (data) => {
24
- errMsg += data.toString();
25
- output += data.toString();
30
+ task.stderr.on('data', (chunk) => {
31
+ console.error("error:",chunk.toString().trim(),"command:",command);
26
32
  });
27
33
 
28
- child.on('error', (err) => {
34
+ task.on('error', (err) => {
35
+ console.error("error to execute:,command",command );
29
36
  reject(err);
30
37
  });
31
38
 
32
- child.on('close', (code) => {
39
+ task.on('close', (code) => {
33
40
 
34
41
  try{
35
- expect(output).to.include(expectOut);
36
- if(done) done(output);
37
- debug("execute sucessfuly,command:",command )
42
+ debug("executeExpectInput close ,command:",command,"expectOut:",expectOut,"output :[[[",output,"]]]" )
43
+ if(expectOut){
44
+ expect(output).to.include(expectOut);
45
+ }
46
+
47
+ if(check_callback) check_callback(output);
38
48
  resolve(output);
49
+
39
50
  } catch(err){
40
51
  console.error("error to execute : node ", inputArgs, `output=${output},prompt=${prompt} expectOut: ${expectOut} errMsg=${errMsg}` );
41
52
  reject(err);
@@ -49,56 +60,50 @@ function execExpectInput(inputArgs, prompt , input,expectOut,done){
49
60
  function execExpectOutput(inputArgs, expectOut,finish_callback){
50
61
 
51
62
  return new Promise((resolve, reject) => {
52
- const child = spawn('node',inputArgs);
53
-
63
+ const task = spawn('node',inputArgs,{stdio: ['pipe', 'pipe', 'pipe'], encoding: 'utf8'});
64
+ const command = "node " + inputArgs.join(" ");
65
+ const taskStream = new PassThrough({encoding: 'utf8'});
54
66
  let output = '';
55
67
  let errMsg = '';
56
- child.stdout.resume();
57
- child.stdout.on('data', (data) => {
68
+ task.stdout.pipe(taskStream);
69
+ //task.stderr.pipe(taskStream);
70
+
71
+ taskStream.on('data', (data) => {
58
72
  output += data.toString();
59
73
  });
60
74
 
61
- child.stderr.on('data', (data) => {
75
+ taskStream.on('data', (data) => {
62
76
  errMsg += data.toString();
63
- output += data.toString();
77
+ ///output += data.toString();
64
78
  });
65
79
 
80
+ task.on('error', (err) => {
81
+ console.error("error to execute:,command",command );
82
+ console.error("errMsg:",errMsg);
83
+ reject(err);
84
+ });
85
+
66
86
 
67
87
 
68
- child.on('close', (code) => {
88
+ task.on('close', (code) => {
69
89
  try{
70
- //debug("output :",output);
71
- //debug("code",code);
90
+ debug("ExpectOutput close: command : ", command, "expectOut", expectOut,
91
+ "output :[[[",output,"]]] \n errMsg:",
92
+ errMsg , "callback exist:", !!finish_callback);
93
+ ////let out = output + errMsg;
72
94
  if(expectOut) expect(output).to.include(expectOut);
73
95
  if(finish_callback) finish_callback(output);
74
96
  debug("execute sucessfuly,command:",command )
75
97
 
76
98
  resolve(output);
77
99
  } catch(err){
78
- console.error("error to execute : node ", inputArgs, `output=${output}, expectOut: ${expectOut} errMsg=${errMsg}` );
100
+ console.error("ExpectOutput:error to execute :", command, `output=${output}, expectOut: ${expectOut} errMsg=${errMsg}` );
79
101
  reject(err);
80
102
  }
81
103
  });
82
- const command = "node " + inputArgs.join(" ");
83
- debug("execExpectOutput command:",command);
104
+ //debug("execExpectOutput command:",command);
84
105
  });
85
106
 
86
107
  }
87
108
 
88
-
89
- function execExpectOutputOld(inputArgs, expectOut,finish_callback){
90
- let command = "node " + inputArgs.join(" ");
91
- debug("command:",command);
92
- let out = execSync(command)
93
- try{
94
- if(expectOut) expect(output).to.include(expectOut);
95
- if(finish_callback) finish_callback(output);
96
- } catch(err){
97
- console.error("error to execute : node ", `command:${command}, expectOut: ${expectOut}` );
98
- throw err;
99
- }
100
-
101
- }
102
-
103
-
104
109
  module.exports = {execExpectInput,execExpectOutput};
package/src/utils/path.js CHANGED
@@ -1,9 +1,50 @@
1
1
  const path = require('path');
2
2
  const os = require('os');
3
+ const {debug} = require('../utils/debug');
3
4
  const ThrowErorr = require('../utils/throw_error');
5
+
4
6
  // 密钥存储目录:~/.config/solana/keys
5
7
  const KEYS_DIR = path.join(os.homedir(), '.config', 'solana', 'keys');
8
+ const BACKUP_DIR = path.join(os.homedir(), '.config', 'solana', '.bak');
9
+ const padZero = (num) => num.toString().padStart(2, '0');
10
+ function generateBackupSuffix(){
11
+ const now = new Date();
12
+ let minutes = padZero(now.getMinutes());
13
+ let secondes = padZero(now.getSeconds());
14
+ return `_${minutes}-${secondes}`;
15
+ }
16
+ function addBackupSuffix(baseName, suffix = null){
17
+ if(suffix){
18
+ return `${baseName}_${suffix}`;
19
+ }
20
+ const new_suffix = generateBackupSuffix();
21
+ return `${baseName}_${new_suffix}`;
22
+ }
23
+
24
+ function stripBackupSuffix(basename){
25
+ let index = basename.lastIndexOf('_');
26
+ console.log("index:",index);
27
+ if(index > 0 ){
28
+ return basename.substring(0,index );
29
+ } else {
30
+ return basename;
31
+ }
32
+ }
33
+ /**
34
+ * 将日期格式化为 YYYY-mm-dd 字符串
35
+ * @param {Date} [date=new Date()] - 要格式化的日期对象,默认当前日期
36
+ * @returns {string} 格式化后的日期字符串(YYYY-mm-dd)
37
+ */
38
+ function formatDateToYmd(date = new Date()) {
39
+ // 补零工具函数(简化重复逻辑)
6
40
 
41
+
42
+ const year = date.getFullYear();
43
+ const month = padZero(date.getMonth() + 1); // 月份+1并补零
44
+ const day = padZero(date.getDate());
45
+
46
+ return `${year}-${month}-${day}`;
47
+ }
7
48
  // 验证别名格式(字母开头,仅含字母、数字、-、_)
8
49
  function validateAlias(alias) {
9
50
  const regex = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
@@ -12,6 +53,15 @@ function validateAlias(alias) {
12
53
  }
13
54
  }
14
55
 
56
+ function getBackupDir( create = true) {
57
+ let dir = BACKUP_DIR;
58
+ console.log("backup dir:",dir);
59
+ if (create && !fs.existsSync(dir)) {
60
+ fs.mkdirSync(dir, { recursive: true });
61
+ }
62
+ return dir;
63
+ }
64
+
15
65
  // 获取密钥文件路径
16
66
  function getKeyFilePath(alias) {
17
67
  return path.join(KEYS_DIR, `${alias}.json`);
@@ -28,8 +78,13 @@ const fs = require('fs');
28
78
 
29
79
  module.exports = {
30
80
  KEYS_DIR,
81
+ getBackupDir,
31
82
  validateAlias,
32
83
  getKeyFilePath,
84
+
33
85
  existsAccount,
86
+ addBackupSuffix,
87
+ stripBackupSuffix,
88
+ formatDateToYmd
34
89
  };
35
90
 
@@ -1,6 +1,8 @@
1
1
  const { execSync } = require('child_process');
2
2
  const { getKeyFilePath } = require('./path');
3
3
  const ThrowErorr = require('../utils/throw_error');
4
+ const fs = require('fs');
5
+ const { Keypair } = require('@solana/web3.js');
4
6
  // 执行 shell 命令并返回输出
5
7
  function execCommand(command) {
6
8
  try {
@@ -16,6 +18,13 @@ function getAddress(alias) {
16
18
  return execCommand(`solana-keygen pubkey ${keyPath}`);
17
19
  }
18
20
 
21
+ function getAddressByFile(file) {
22
+ const data = fs.readFileSync(file, 'utf8');
23
+ const jsonData = JSON.parse(data);
24
+ const keypair = Keypair.fromSecretKey(Uint8Array.from(jsonData));
25
+ return keypair.publicKey.toBase58();
26
+ }
27
+
19
28
  // 获取当前活跃的密钥路径
20
29
  function getActiveKeyPath() {
21
30
  const config = execCommand('solana config get keypair');
@@ -29,5 +38,6 @@ module.exports = {
29
38
  execCommand,
30
39
  getAddress,
31
40
  getActiveKeyPath,
41
+ getAddressByFile
32
42
  };
33
43