soluser 1.0.11 → 1.0.15
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 +5 -2
- package/bin/index.js +24 -3
- package/package.json +5 -4
- package/src/commands/clear.js +13 -11
- package/src/commands/export.js +38 -0
- package/src/commands/from_mnemonic.js +0 -2
- package/src/commands/list.js +5 -1
- package/src/commands/new.js +3 -3
- package/src/commands/privateKey.js +18 -0
- package/src/commands/remove.js +10 -7
- package/src/commands/restore.js +65 -0
- package/src/utils/debug.js +19 -3
- package/src/utils/example.js +3 -0
- package/src/utils/execExpectInput.js +50 -45
- package/src/utils/path.js +55 -0
- package/src/utils/solana.js +10 -0
package/README.md
CHANGED
|
@@ -56,9 +56,12 @@ Signature: 4BLUt5uxutbEwVywBTbAoBnG4EKb6QgsHgk3JRfjy6uJCoNjxdyYodbAhsWPXquBBwVzu
|
|
|
56
56
|
|
|
57
57
|
500000005 SOL
|
|
58
58
|
```
|
|
59
|
-
|
|
60
|
-
|
|
61
59
|
## import 助记词
|
|
62
60
|
```shell
|
|
63
61
|
$ soluser import "mnemonic words" --alias someone
|
|
64
62
|
```
|
|
63
|
+
|
|
64
|
+
## 导出私钥为base58编码
|
|
65
|
+
```shell
|
|
66
|
+
$ soluser export someone
|
|
67
|
+
```
|
package/bin/index.js
CHANGED
|
@@ -8,6 +8,7 @@ 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');
|
|
@@ -18,6 +19,7 @@ const showAddress = require('../src/commands/address');
|
|
|
18
19
|
const checkBalance = require('../src/commands/balance');
|
|
19
20
|
// Import the airdrop command
|
|
20
21
|
const requestAirdrop = require('../src/commands/airdrop');
|
|
22
|
+
const exportPrivateKey = require('../src/commands/export');
|
|
21
23
|
|
|
22
24
|
// Get version from package.json
|
|
23
25
|
const packageJson = require('../package.json');
|
|
@@ -117,7 +119,10 @@ program
|
|
|
117
119
|
.command('list')
|
|
118
120
|
.alias('l')
|
|
119
121
|
.description('List all Solana accounts')
|
|
120
|
-
.action(
|
|
122
|
+
.action(() =>{
|
|
123
|
+
listAccounts();
|
|
124
|
+
//process.exit(0);
|
|
125
|
+
});
|
|
121
126
|
|
|
122
127
|
// 定义删除账号命令
|
|
123
128
|
program
|
|
@@ -143,15 +148,31 @@ program
|
|
|
143
148
|
.description('prune a Solana account (prune the private key file)')
|
|
144
149
|
.action((alias) => {
|
|
145
150
|
pruneAccount(alias);
|
|
151
|
+
|
|
152
|
+
});
|
|
153
|
+
// 定义删除账号命令
|
|
154
|
+
program
|
|
155
|
+
.command('restore ') // <alias> 为必填参数
|
|
156
|
+
.description('restore file from backup directory')
|
|
157
|
+
.action(() => {
|
|
158
|
+
restoreAccount();
|
|
146
159
|
});
|
|
147
160
|
|
|
148
|
-
|
|
161
|
+
// 定义 export 命令
|
|
162
|
+
program
|
|
163
|
+
.command('export')
|
|
164
|
+
.alias('e')
|
|
165
|
+
.description('Output the base58 private key of a Solana account')
|
|
166
|
+
.argument('<alias>', 'Alias of the account to export private key')
|
|
167
|
+
.action((alias) => {
|
|
168
|
+
exportPrivateKey(alias);
|
|
169
|
+
});
|
|
149
170
|
|
|
150
171
|
|
|
151
172
|
program.on('--help', () => {
|
|
152
173
|
console.log("version:" ,version);
|
|
153
174
|
showExamples();
|
|
154
|
-
process.exit(1);
|
|
175
|
+
//process.exit(1);
|
|
155
176
|
});
|
|
156
177
|
|
|
157
178
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "soluser",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.15",
|
|
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",
|
|
@@ -22,16 +23,16 @@
|
|
|
22
23
|
"dependencies": {
|
|
23
24
|
"@solana/web3.js": "^1.98.4",
|
|
24
25
|
"bip39": "^3.1.0",
|
|
26
|
+
"bs58": "^6.0.0",
|
|
25
27
|
"chalk": "4.1.2",
|
|
26
28
|
"cli-table3": "^0.6.5",
|
|
27
29
|
"commander": "^14.0.2",
|
|
28
|
-
"dotenv": "^17.2.3",
|
|
29
30
|
"ed25519-hd-key": "^1.3.0",
|
|
30
31
|
"readline": "^1.3.0"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"chai": "^4.3.7",
|
|
34
|
-
"mocha": "^10.2
|
|
35
|
+
"mocha": "^10.8.2",
|
|
35
36
|
"nyc": "^15.1.0"
|
|
36
37
|
},
|
|
37
38
|
"files": [
|
package/src/commands/clear.js
CHANGED
|
@@ -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 =
|
|
40
|
-
const backupDir =
|
|
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
|
|
59
|
-
const
|
|
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 ${
|
|
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.
|
|
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.
|
|
80
|
+
console.log('Keys directory does not exist');
|
|
79
81
|
process.exit(1);
|
|
80
82
|
}
|
|
81
83
|
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const { existsAccount, getKeyFilePath } = require('../utils/path');
|
|
2
|
+
const { Keypair } = require('@solana/web3.js');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const bs58 = require('bs58').default;
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
function exportPrivateKey(alias) {
|
|
8
|
+
// Check if account exists
|
|
9
|
+
if (!existsAccount(alias)) {
|
|
10
|
+
console.error(`Error: Account "${alias}" does not exist.`);
|
|
11
|
+
console.error(` Use "soluser list" to view all available accounts.`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Read and parse the key file
|
|
17
|
+
const keyPath = getKeyFilePath(alias);
|
|
18
|
+
const keyData = fs.readFileSync(keyPath, 'utf8');
|
|
19
|
+
const secretKeyArray = JSON.parse(keyData);
|
|
20
|
+
|
|
21
|
+
// Convert to Uint8Array and create Keypair
|
|
22
|
+
const secretKey = Uint8Array.from(secretKeyArray);
|
|
23
|
+
const keypair = Keypair.fromSecretKey(secretKey);
|
|
24
|
+
|
|
25
|
+
// Encode to base58 and output
|
|
26
|
+
const base58PrivateKey = bs58.encode(secretKey );
|
|
27
|
+
process.stdout.write(`address for "${alias}" ${keypair.publicKey.toBase58()}:\n`);
|
|
28
|
+
process.stdout.write(`private key for "${alias}": ${base58PrivateKey}\n`);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error(`Error: Failed to export private key for "${alias}": ${err.message}`);
|
|
31
|
+
for( k in bs58) {
|
|
32
|
+
console.log(k,":", bs58[k]);
|
|
33
|
+
}
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = exportPrivateKey;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const { Keypair } = require( '@solana/web3.js');
|
|
2
2
|
const bip39 = require( 'bip39');
|
|
3
3
|
const { Buffer } = require( 'buffer');
|
|
4
|
-
const dotenv = require( 'dotenv');
|
|
5
4
|
const bs58 = require( 'bs58');
|
|
6
5
|
const fs = require( 'fs');
|
|
7
6
|
const path = require( 'path');
|
|
@@ -10,7 +9,6 @@ const {debug} = require('../utils/debug');
|
|
|
10
9
|
///const ed25519 = = require('ed25519-hd-keyed25519-hd-key')
|
|
11
10
|
const ed25519 = require( 'ed25519-hd-key');
|
|
12
11
|
const { fileURLToPath } = require('url');
|
|
13
|
-
dotenv.config()
|
|
14
12
|
// BIP44路径
|
|
15
13
|
const BIP44_SOLANA_PATH = "m/44'/501'/0'/0'";
|
|
16
14
|
|
package/src/commands/list.js
CHANGED
|
@@ -11,7 +11,8 @@ function listAccounts() {
|
|
|
11
11
|
return;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
const
|
|
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 )
|
package/src/commands/new.js
CHANGED
|
@@ -14,8 +14,8 @@ function newAccount(alias, wordLength = 12, noPassphrase = false) {
|
|
|
14
14
|
|
|
15
15
|
// 3. 新增:检查账号是否已存在
|
|
16
16
|
if (existsAccount(alias)) {
|
|
17
|
-
console.
|
|
18
|
-
console.
|
|
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.
|
|
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();
|
package/src/commands/remove.js
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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.
|
|
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;
|
package/src/utils/debug.js
CHANGED
|
@@ -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(
|
|
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
|
}
|
package/src/utils/example.js
CHANGED
|
@@ -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 {
|
|
5
|
-
|
|
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
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
output
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
output += data.toString();
|
|
30
|
+
task.stderr.on('data', (chunk) => {
|
|
31
|
+
console.error("error:",chunk.toString().trim(),"command:",command);
|
|
26
32
|
});
|
|
27
33
|
|
|
28
|
-
|
|
34
|
+
task.on('error', (err) => {
|
|
35
|
+
console.error("error to execute:,command",command );
|
|
29
36
|
reject(err);
|
|
30
37
|
});
|
|
31
38
|
|
|
32
|
-
|
|
39
|
+
task.on('close', (code) => {
|
|
33
40
|
|
|
34
41
|
try{
|
|
35
|
-
|
|
36
|
-
if(
|
|
37
|
-
|
|
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
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
88
|
+
task.on('close', (code) => {
|
|
69
89
|
try{
|
|
70
|
-
debug("
|
|
71
|
-
|
|
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 :
|
|
100
|
+
console.error("ExpectOutput:error to execute :", command, `output=${output}, expectOut: ${expectOut} errMsg=${errMsg}` );
|
|
79
101
|
reject(err);
|
|
80
102
|
}
|
|
81
103
|
});
|
|
82
|
-
|
|
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
|
|
package/src/utils/solana.js
CHANGED
|
@@ -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
|
|