fnva 0.0.21 → 0.0.23

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
@@ -487,7 +487,7 @@ description = "AnyCC 代理服务"
487
487
  # 仓库配置
488
488
  [repositories]
489
489
  java = [
490
- "https://mirrors.aliyun.com/eclipse/temurin-compliance/temurin",
490
+ "https://mirrors.tuna.tsinghua.edu.cn/Adoptium",
491
491
  "https://api.adoptium.net/v3"
492
492
  ]
493
493
  maven = [
@@ -496,6 +496,8 @@ maven = [
496
496
  ]
497
497
  ```
498
498
 
499
+ > Java downloads default to the Tsinghua Adoptium mirror (https://mirrors.tuna.tsinghua.edu.cn/Adoptium). For fully stable setups, manually maintain versions in ~/.fnva/config.toml [[java_environments]] and JAVA_HOME.
500
+
499
501
  ### 常用命令速查
500
502
 
501
503
  | 命令 | 功能 | 示例 |
package/bin/fnva.js CHANGED
@@ -3,6 +3,7 @@
3
3
  const { spawnSync } = require('child_process');
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
+ const EncodingUtils = require('../lib/encoding-utils');
6
7
 
7
8
  function resolvePlatform() {
8
9
  switch (process.platform) {
@@ -132,21 +133,18 @@ function removeAutoFlag(args) {
132
133
  function createTempScriptFile(script, envType, envName) {
133
134
  try {
134
135
  const os = require('os');
135
- const fs = require('fs');
136
136
  const path = require('path');
137
137
 
138
- const tempDir = os.tmpdir();
139
- const scriptFile = path.join(tempDir, `fnva_${envType}_${envName}_${Date.now()}.ps1`);
140
-
141
- fs.writeFileSync(scriptFile, script, 'utf8');
138
+ const prefix = `fnva_${envType}_${envName}`;
139
+ const scriptFile = EncodingUtils.createTempPowerShellScript(script, prefix);
142
140
 
143
141
  console.log('');
144
- console.log('💡 环境已切换到当前进程。要在新的 PowerShell 窗口中使用此环境,运行:');
142
+ console.log('[INFO] 环境已切换到当前进程。要在新的 PowerShell 窗口中使用此环境,运行:');
145
143
  console.log(` ${scriptFile}`);
146
144
  console.log(' 或者: fnva', envType, 'use', envName, '--auto');
147
145
 
148
146
  } catch (error) {
149
- console.warn('⚠️ 无法创建临时脚本文件:', error.message);
147
+ console.warn('[WARN] 无法创建临时脚本文件:', error.message);
150
148
  }
151
149
  }
152
150
 
@@ -208,18 +206,18 @@ function applyEnvironmentVariables(envVars) {
208
206
  }
209
207
 
210
208
  function displaySuccessMessage(envType, envName, envVars) {
211
- console.log(`✅ Switched to ${envType} environment: ${envName}`);
209
+ console.log(`[OK] Switched to ${envType} environment: ${envName}`);
212
210
 
213
211
  if (envVars.JAVA_HOME) {
214
- console.log(`📁 JAVA_HOME: ${envVars.JAVA_HOME}`);
212
+ console.log(`[DIR] JAVA_HOME: ${envVars.JAVA_HOME}`);
215
213
  }
216
214
 
217
215
  if (envVars.ANTHROPIC_AUTH_TOKEN) {
218
- console.log(`🔑 ANTHROPIC_AUTH_TOKEN: [已设置]`);
216
+ console.log(`[KEY] ANTHROPIC_AUTH_TOKEN: [已设置]`);
219
217
  }
220
218
 
221
219
  if (envVars.OPENAI_API_KEY) {
222
- console.log(`🔑 OPENAI_API_KEY: [已设置]`);
220
+ console.log(`[KEY] OPENAI_API_KEY: [已设置]`);
223
221
  }
224
222
  }
225
223
 
@@ -227,7 +225,8 @@ function generateSimpleScript(envVars, envType, envName) {
227
225
  const lines = [];
228
226
 
229
227
  if (process.platform === 'win32') {
230
- // Windows PowerShell
228
+ // Windows PowerShell - 使用编码工具设置
229
+ lines.push(EncodingUtils.generatePowerShellEncodingSetup());
231
230
  lines.push(`Write-Host "Switched to ${envType} environment: ${envName}" -ForegroundColor Green`);
232
231
 
233
232
  if (envVars.JAVA_HOME) {
@@ -381,6 +380,9 @@ function handleNodeOnlyMode(args) {
381
380
  }
382
381
 
383
382
  function run() {
383
+ // 设置Windows控制台编码
384
+ EncodingUtils.setWindowsConsoleEncoding();
385
+
384
386
  const binaryPath = buildBinaryPath();
385
387
 
386
388
  if (!binaryPath) {
@@ -432,11 +434,11 @@ function run() {
432
434
 
433
435
  if (result.error) {
434
436
  if (result.error.code === 'EACCES' && process.platform !== 'win32') {
435
- console.error(`❌ Permission denied. The fnva binary is not executable.`);
436
- console.error(`💡 To fix this, run: sudo chmod +x "${binaryPath}"`);
437
- console.error(` Or reinstall: npm install -g fnva --force`);
437
+ console.error(`[ERROR] Permission denied. The fnva binary is not executable.`);
438
+ console.error(`[INFO] To fix this, run: sudo chmod +x "${binaryPath}"`);
439
+ console.error(`[INFO] Or reinstall: npm install -g fnva --force`);
438
440
  } else {
439
- console.error(`Failed to execute fnva: ${result.error.message}`);
441
+ console.error(`[ERROR] Failed to execute fnva: ${result.error.message}`);
440
442
  }
441
443
  process.exit(result.status ?? 1);
442
444
  }
@@ -456,16 +458,16 @@ function run() {
456
458
  // Windows:默认不启动新的会话;可通过 --session 开启旧行为
457
459
  if (process.platform === 'win32') {
458
460
  if (hasSessionFlag(args)) {
459
- console.log(`✅ Switched to ${envType} environment: ${envName}`);
460
- console.log(`🚀 Starting new PowerShell session with ${envName} environment...`);
461
+ console.log(`[OK] Switched to ${envType} environment: ${envName}`);
462
+ console.log(`[INFO] Starting new PowerShell session with ${envName} environment...`);
461
463
  console.log(`Type "exit" to return to previous session\n`);
462
464
 
463
465
  try {
464
466
  const os = require('os');
465
- const fs = require('fs');
466
467
  const tempScript = os.tmpdir() + '\\fnva_env_' + Date.now() + '.ps1';
467
468
  const fullScript = script + '\n';
468
- fs.writeFileSync(tempScript, fullScript, 'utf8');
469
+ // 使用编码工具写入文件
470
+ EncodingUtils.writeFileWithEncoding(tempScript, fullScript);
469
471
  const { spawn } = require('child_process');
470
472
  const ps = spawn('powershell', ['-NoExit', '-ExecutionPolicy', 'Bypass', '-File', tempScript], {
471
473
  stdio: 'inherit',
@@ -473,7 +475,7 @@ function run() {
473
475
  });
474
476
  ps.on('exit', () => {
475
477
  try { fs.unlinkSync(tempScript); } catch (_) {}
476
- console.log('👋 Returned to original session');
478
+ console.log('[INFO] Returned to original session');
477
479
  });
478
480
  return;
479
481
  } catch (error) {
@@ -501,7 +503,8 @@ function run() {
501
503
 
502
504
  try {
503
505
  const tempFile = path.join(os.tmpdir(), `fnva_auto_${Date.now()}.ps1`);
504
- fs.writeFileSync(tempFile, simpleScript, 'utf8');
506
+ // 使用编码工具写入文件
507
+ EncodingUtils.writeFileWithEncoding(tempFile, simpleScript);
505
508
 
506
509
  // 使用 PowerShell 执行脚本
507
510
  spawn('powershell', ['-ExecutionPolicy', 'Bypass', '-File', tempFile], {
@@ -511,10 +514,10 @@ function run() {
511
514
  try { fs.unlinkSync(tempFile); } catch (_) {}
512
515
  });
513
516
 
514
- console.log(' 环境已自动切换');
517
+ console.log('[OK] 环境已自动切换');
515
518
  return;
516
519
  } catch (error) {
517
- console.warn('⚠️ 自动执行失败,回退到脚本输出');
520
+ console.warn('[WARN] 自动执行失败,回退到脚本输出');
518
521
  }
519
522
  }
520
523
 
@@ -524,9 +527,9 @@ function run() {
524
527
  }
525
528
  } else {
526
529
  // Unix-like systems: 显示使用说明
527
- console.log(`✅ Switched to ${envType} environment: ${envName}`);
530
+ console.log(`[OK] Switched to ${envType} environment: ${envName}`);
528
531
  console.log('');
529
- console.log('💡 To apply this environment, run:');
532
+ console.log('[INFO] To apply this environment, run:');
530
533
  console.log(` node bin/fnva.js ${args.join(' ')} | bash`);
531
534
  }
532
535
  } else {
@@ -548,11 +551,11 @@ function run() {
548
551
 
549
552
  if (result.error) {
550
553
  if (result.error.code === 'EACCES' && process.platform !== 'win32') {
551
- console.error(`❌ Permission denied. The fnva binary is not executable.`);
552
- console.error(`💡 To fix this, run: sudo chmod +x "${binaryPath}"`);
553
- console.error(` Or reinstall: npm install -g fnva --force`);
554
+ console.error(`[ERROR] Permission denied. The fnva binary is not executable.`);
555
+ console.error(`[INFO] To fix this, run: sudo chmod +x "${binaryPath}"`);
556
+ console.error(`[INFO] Or reinstall: npm install -g fnva --force`);
554
557
  } else {
555
- console.error(`Failed to execute fnva: ${result.error.message}`);
558
+ console.error(`[ERROR] Failed to execute fnva: ${result.error.message}`);
556
559
  }
557
560
  process.exit(result.status ?? 1);
558
561
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fnva",
3
- "version": "0.0.21",
3
+ "version": "0.0.23",
4
4
  "description": "跨平台环境切换工具,支持 Java 和 LLM 环境配置",
5
5
  "author": "protagonistss",
6
6
  "license": "MIT",
@@ -48,4 +48,3 @@
48
48
  "node": ">=14.0.0"
49
49
  }
50
50
  }
51
-
package/platforms/fnva CHANGED
Binary file
Binary file
@@ -4,7 +4,7 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
6
  /**
7
- * 检查platforms目录中二进制文件的权限
7
+ * 检查 platforms 目录中二进制文件的权限
8
8
  */
9
9
  function checkPermissions() {
10
10
  console.log('🔍 检查二进制文件权限...');
@@ -12,13 +12,14 @@ function checkPermissions() {
12
12
  const platformsDir = path.join(__dirname, '..', 'platforms');
13
13
 
14
14
  if (!fs.existsSync(platformsDir)) {
15
- console.log('❌ platforms目录不存在');
15
+ console.log('❌ platforms 目录不存在');
16
16
  process.exit(1);
17
17
  }
18
18
 
19
19
  const platforms = fs.readdirSync(platformsDir);
20
20
  let allGood = true;
21
21
 
22
+ // 优先检查新的 platform-arch 目录结构
22
23
  for (const platform of platforms) {
23
24
  const platformDir = path.join(platformsDir, platform);
24
25
 
@@ -43,6 +44,22 @@ function checkPermissions() {
43
44
  }
44
45
  }
45
46
 
47
+ // 额外检查一次扁平结构: platforms/fnva
48
+ const flatBinaryName = process.platform === 'win32' ? 'fnva.exe' : 'fnva';
49
+ const flatBinaryPath = path.join(platformsDir, flatBinaryName);
50
+
51
+ if (fs.existsSync(flatBinaryPath)) {
52
+ const stats = fs.statSync(flatBinaryPath);
53
+ const hasExecPermission = (stats.mode & 0o111) !== 0;
54
+ const mode = stats.mode.toString(8).padStart(4, '0');
55
+
56
+ console.log(` (legacy)/${flatBinaryName}: ${mode} ${hasExecPermission ? '✅' : '❌'}`);
57
+
58
+ if (!hasExecPermission && flatBinaryName !== 'fnva.exe') {
59
+ allGood = false;
60
+ }
61
+ }
62
+
46
63
  console.log(`\n${allGood ? '✅' : '❌'} 权限检查${allGood ? '通过' : '失败'}`);
47
64
 
48
65
  if (!allGood) {
@@ -56,4 +73,5 @@ if (require.main === module) {
56
73
  checkPermissions();
57
74
  }
58
75
 
59
- module.exports = { checkPermissions };
76
+ module.exports = { checkPermissions };
77
+
@@ -0,0 +1,72 @@
1
+ #!/bin/bash
2
+
3
+ # 代码风格检查脚本
4
+ # 用于检查和格式化fnva项目的代码风格
5
+
6
+ set -e
7
+
8
+ echo "🔍 开始代码风格检查..."
9
+
10
+ # 检查是否安装了必要的工具
11
+ check_tool() {
12
+ if ! command -v $1 &> /dev/null; then
13
+ echo "❌ $1 未安装,请先安装 $1"
14
+ echo "安装命令: cargo install $1"
15
+ exit 1
16
+ fi
17
+ }
18
+
19
+ echo "📦 检查工具是否安装..."
20
+ check_tool "rustfmt"
21
+ check_tool "clippy"
22
+
23
+ # 运行rustfmt格式化代码
24
+ echo "🎨 格式化代码..."
25
+ cargo fmt --all
26
+
27
+ # 运行clippy检查
28
+ echo "🔍 运行Clippy静态分析..."
29
+ cargo clippy --all-targets --all-features -- -D warnings
30
+
31
+ # 检查是否有未提交的格式化更改
32
+ echo "📝 检查格式化结果..."
33
+ if ! git diff --exit-code --quiet; then
34
+ echo "⚠️ 代码格式化产生了更改,请提交这些更改"
35
+ echo "运行 'git add .' 和 'git commit' 来提交格式化结果"
36
+ exit 1
37
+ fi
38
+
39
+ # 检查文档注释
40
+ echo "📚 检查文档注释..."
41
+ cargo doc --no-deps --document-private-items 2>/dev/null | grep -E "(warning|error)" || true
42
+
43
+ # 检查重复的代码
44
+ echo "🔄 检查重复代码..."
45
+ if command -v cargo-dup &> /dev/null; then
46
+ cargo dup
47
+ else
48
+ echo "💡 提示: 安装 cargo-dup 可以检查重复代码 (cargo install cargo-dup)"
49
+ fi
50
+
51
+ # 检查依赖安全性
52
+ echo "🔒 检查依赖安全性..."
53
+ if command -v cargo-audit &> /dev/null; then
54
+ cargo audit
55
+ else
56
+ echo "💡 提示: 安装 cargo-audit 可以检查依赖安全性 (cargo install cargo-audit)"
57
+ fi
58
+
59
+ # 检查未使用的依赖
60
+ echo "🧹 检查未使用的依赖..."
61
+ if command -v cargo-udeps &> /dev/null; then
62
+ cargo udeps --all-targets
63
+ else
64
+ echo "💡 提示: 安装 cargo-udeps 可以检查未使用的依赖 (cargo install cargo-udeps)"
65
+ fi
66
+
67
+ # 统计代码行数
68
+ echo "📊 代码统计:"
69
+ echo "总Rust代码行数: $(find src -name '*.rs' -exec wc -l {} + | tail -1)"
70
+ echo "测试代码行数: $(find tests -name '*.rs' -exec wc -l {} + 2>/dev/null | tail -1 || echo "0")"
71
+
72
+ echo "✅ 代码风格检查完成!"
@@ -4,8 +4,8 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
6
  /**
7
- * 确保fnva二进制文件有可执行权限
8
- * 这是一个全面的postinstall脚本,处理本地安装和全局安装的权限问题
7
+ * 确保 fnva 二进制文件有可执行权限
8
+ * 这是一个全局的 postinstall 脚本,处理本地安装和全局安装的权限问题
9
9
  */
10
10
  function ensureExecutablePermissions() {
11
11
  try {
@@ -13,82 +13,92 @@ function ensureExecutablePermissions() {
13
13
  const projectRoot = path.resolve(scriptDir, '..');
14
14
  const platformsDir = path.join(projectRoot, 'platforms');
15
15
 
16
- console.log('🔧 Ensuring fnva binary permissions...');
16
+ console.log(' Ensuring fnva binary permissions...');
17
17
 
18
- // 如果没有platforms目录,说明是开发模式,不需要处理
18
+ // 如果没有 platforms 目录,说明是开发模式,不需要处理
19
19
  if (!fs.existsSync(platformsDir)) {
20
20
  console.log('ℹ️ No platforms directory found, skipping permission check');
21
21
  return;
22
22
  }
23
23
 
24
- // 检测当前平台
25
24
  const platform = process.platform;
26
25
  const arch = process.arch === 'arm64' ? 'arm64' : 'x64';
27
26
  const platformDir = `${platform}-${arch}`;
28
27
 
29
- // 确定二进制文件名和路径
30
28
  const binaryName = platform === 'win32' ? 'fnva.exe' : 'fnva';
31
- const binaryPath = path.join(platformsDir, platformDir, binaryName);
29
+ const archBinaryPath = path.join(platformsDir, platformDir, binaryName);
30
+ const flatBinaryPath = path.join(platformsDir, binaryName);
32
31
 
33
- // 如果二进制文件存在且不是Windows,设置可执行权限
34
- if (fs.existsSync(binaryPath) && platform !== 'win32') {
32
+ /**
33
+ * 确保指定路径的二进制文件具有可执行权限,并做一次简单的运行测试
34
+ */
35
+ function ensureExecutable(binaryPath, label) {
35
36
  try {
36
37
  const stats = fs.statSync(binaryPath);
37
38
  const hasExecPermission = (stats.mode & 0o111) !== 0;
38
39
 
39
- console.log(`📍 Checking binary: ${binaryPath}`);
40
+ console.log(`📍 Checking binary (${label}): ${binaryPath}`);
40
41
  console.log(` Current permissions: ${(stats.mode & 0o777).toString(8)}`);
41
42
 
42
43
  if (!hasExecPermission) {
43
- console.log(`🔧 Setting executable permissions...`);
44
+ console.log('🔧 Setting executable permissions...');
44
45
  fs.chmodSync(binaryPath, 0o755); // rwxr-xr-x
45
46
 
46
- // 验证权限设置成功
47
47
  const newStats = fs.statSync(binaryPath);
48
48
  const newHasExecPermission = (newStats.mode & 0o111) !== 0;
49
49
 
50
50
  if (newHasExecPermission) {
51
- console.log(`✅ Successfully set executable permissions (${platformDir})`);
51
+ console.log(`✅ Successfully set executable permissions (${label})`);
52
52
  } else {
53
- console.log(`❌ Failed to set executable permissions (${platformDir})`);
53
+ console.log(`❌ Failed to set executable permissions (${label})`);
54
54
  console.log(` New permissions: ${(newStats.mode & 0o777).toString(8)}`);
55
55
  console.log(` Manual fix may be required: chmod +x "${binaryPath}"`);
56
56
  }
57
57
  } else {
58
- console.log(`✅ fnva binary already has executable permissions (${platformDir})`);
58
+ console.log(`✅ fnva binary already has executable permissions (${label})`);
59
59
  }
60
60
 
61
- // 尝试测试二进制文件是否可以执行(简单测试)
61
+ // 尝试执行一次 --version 做简单验证
62
62
  try {
63
63
  const { spawnSync } = require('child_process');
64
64
  const testResult = spawnSync(binaryPath, ['--version'], {
65
65
  encoding: 'utf8',
66
66
  timeout: 3000,
67
- stdio: 'pipe'
67
+ stdio: 'pipe',
68
68
  });
69
69
 
70
- if (testResult.status === 0 || testResult.status === 1) { // status 1 可能是正常的错误状态
71
- console.log(`✅ fnva binary is executable and responding`);
70
+ if (testResult.status === 0 || testResult.status === 1) {
71
+ console.log('✅ fnva binary is executable and responding');
72
72
  } else if (testResult.error && testResult.error.code === 'EACCES') {
73
- console.log(`❌ fnva binary still has permission issues`);
73
+ console.log('❌ fnva binary still has permission issues');
74
74
  console.log(` Manual fix required: chmod +x "${binaryPath}"`);
75
75
  }
76
- } catch (testError) {
77
- // 测试失败不算严重错误,可能是因为二进制文件本身有问题
76
+ } catch {
77
+ // 测试失败不视为致命错误,可能是二进制本身的问题
78
78
  }
79
-
80
79
  } catch (error) {
81
- console.warn(`⚠️ Could not fix binary permissions: ${error.message}`);
80
+ console.warn(`⚠️ Could not fix binary permissions (${label}): ${error.message}`);
82
81
  console.log(` Manual fix required: chmod +x "${binaryPath}"`);
83
82
  }
84
- } else if (platform === 'win32') {
85
- console.log(`ℹ️ Windows platform detected, skipping permission check`);
83
+ }
84
+
85
+ // Windows 不需要 chmod,可直接跳过
86
+ if (platform === 'win32') {
87
+ console.log('ℹ️ Windows platform detected, skipping permission check');
88
+ } else if (fs.existsSync(archBinaryPath)) {
89
+ // 优先处理新的平台子目录结构: platforms/<platform>-<arch>/fnva
90
+ ensureExecutable(archBinaryPath, platformDir);
91
+ } else if (fs.existsSync(flatBinaryPath)) {
92
+ // 兼容旧版本扁平结构: platforms/fnva
93
+ console.log('ℹ️ Platform-specific binary not found, falling back to legacy flat layout');
94
+ ensureExecutable(flatBinaryPath, 'platforms/fnva');
86
95
  } else {
87
- console.log(`❌ Binary not found: ${binaryPath}`);
88
- console.log(` This might indicate an incomplete installation`);
96
+ console.log(`❌ Binary not found: ${archBinaryPath}`);
97
+ console.log(` Also checked legacy path: ${flatBinaryPath}`);
98
+ console.log(' This might indicate an incomplete installation');
89
99
  }
90
100
 
91
- // 额外检查:如果是全局安装,也检查全局路径中的fnva
101
+ // 额外检查:如果是全局安装,也尝试检查路径上的 fnva 权限
92
102
  if (process.env.npm_config_global === 'true') {
93
103
  try {
94
104
  const { execSync } = require('child_process');
@@ -101,22 +111,21 @@ function ensureExecutablePermissions() {
101
111
  const globalHasExecPermission = (globalStats.mode & 0o111) !== 0;
102
112
 
103
113
  if (!globalHasExecPermission) {
104
- console.log(`🔧 Global fnva binary lacks executable permissions`);
114
+ console.log('❌ Global fnva binary lacks executable permissions');
105
115
  console.log(` Please run: sudo chmod +x "${globalFnvaPath}"`);
106
116
  } else {
107
- console.log(`✅ Global fnva binary has correct permissions`);
117
+ console.log('✅ Global fnva binary has correct permissions');
108
118
  }
109
119
  }
110
- } catch (globalError) {
111
- // 无法检查全局安装,不视为错误
112
- console.log(`ℹ️ Could not verify global installation`);
120
+ } catch {
121
+ console.log('ℹ️ Could not verify global installation');
113
122
  }
114
123
  }
115
-
116
124
  } catch (error) {
117
125
  console.warn(`⚠️ Permission check failed: ${error.message}`);
118
126
  }
119
127
  }
120
128
 
121
129
  // 运行权限检查
122
- ensureExecutablePermissions();
130
+ ensureExecutablePermissions();
131
+
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+
3
+ # 代码格式化脚本
4
+ # 自动格式化fnva项目的代码
5
+
6
+ set -e
7
+
8
+ echo "🎨 开始格式化代码..."
9
+
10
+ # 格式化所有Rust代码
11
+ cargo fmt --all
12
+
13
+ # 检查格式化结果
14
+ if git diff --exit-code --quiet; then
15
+ echo "✅ 代码已经是标准格式"
16
+ else
17
+ echo "📝 代码已格式化,请查看更改:"
18
+ git diff --stat
19
+ echo ""
20
+ echo "💡 提示: 使用 'git add .' 和 'git commit' 提交格式化更改"
21
+ fi
22
+
23
+ echo "✅ 格式化完成!"