fnva 0.0.27 → 0.0.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fnva",
3
- "version": "0.0.27",
3
+ "version": "0.0.28",
4
4
  "description": "跨平台环境切换工具,支持 Java 和 LLM 环境配置",
5
5
  "author": "protagonistss",
6
6
  "license": "MIT",
package/platforms/fnva CHANGED
Binary file
Binary file
@@ -2,130 +2,81 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
+ const { spawnSync } = require('child_process');
5
6
 
6
- /**
7
- * 确保 fnva 二进制文件有可执行权限
8
- * 这是一个全局的 postinstall 脚本,处理本地安装和全局安装的权限问题
9
- */
10
- function ensureExecutablePermissions() {
11
- try {
12
- const scriptDir = __dirname;
13
- const projectRoot = path.resolve(scriptDir, '..');
14
- const platformsDir = path.join(projectRoot, 'platforms');
15
-
16
- console.log('✅ Ensuring fnva binary permissions...');
7
+ function log(msg) {
8
+ console.log(msg);
9
+ }
17
10
 
18
- // 如果没有 platforms 目录,说明是开发模式,不需要处理
19
- if (!fs.existsSync(platformsDir)) {
20
- console.log('ℹ️ No platforms directory found, skipping permission check');
21
- return;
11
+ function ensureExecutable(filePath, label) {
12
+ try {
13
+ const stats = fs.statSync(filePath);
14
+ const hasExec = (stats.mode & 0o111) !== 0;
15
+ log(`Checking ${label}: ${filePath}`);
16
+ log(` Current permissions: ${(stats.mode & 0o777).toString(8)}`);
17
+
18
+ if (!hasExec) {
19
+ fs.chmodSync(filePath, 0o755);
20
+ const newStats = fs.statSync(filePath);
21
+ log(` Updated permissions: ${(newStats.mode & 0o777).toString(8)}`);
22
+ } else {
23
+ log(' Executable bit already set');
22
24
  }
23
25
 
24
- const platform = process.platform;
25
- const arch = process.arch === 'arm64' ? 'arm64' : 'x64';
26
- const platformDir = `${platform}-${arch}`;
27
-
28
- const binaryName = platform === 'win32' ? 'fnva.exe' : 'fnva';
29
- const archBinaryPath = path.join(platformsDir, platformDir, binaryName);
30
- const flatBinaryPath = path.join(platformsDir, binaryName);
31
-
32
- /**
33
- * 确保指定路径的二进制文件具有可执行权限,并做一次简单的运行测试
34
- */
35
- function ensureExecutable(binaryPath, label) {
36
- try {
37
- const stats = fs.statSync(binaryPath);
38
- const hasExecPermission = (stats.mode & 0o111) !== 0;
39
-
40
- console.log(`📍 Checking binary (${label}): ${binaryPath}`);
41
- console.log(` Current permissions: ${(stats.mode & 0o777).toString(8)}`);
42
-
43
- if (!hasExecPermission) {
44
- console.log('🔧 Setting executable permissions...');
45
- fs.chmodSync(binaryPath, 0o755); // rwxr-xr-x
46
-
47
- const newStats = fs.statSync(binaryPath);
48
- const newHasExecPermission = (newStats.mode & 0o111) !== 0;
49
-
50
- if (newHasExecPermission) {
51
- console.log(`✅ Successfully set executable permissions (${label})`);
52
- } else {
53
- console.log(`❌ Failed to set executable permissions (${label})`);
54
- console.log(` New permissions: ${(newStats.mode & 0o777).toString(8)}`);
55
- console.log(` Manual fix may be required: chmod +x "${binaryPath}"`);
56
- }
57
- } else {
58
- console.log(`✅ fnva binary already has executable permissions (${label})`);
59
- }
26
+ const res = spawnSync(filePath, ['--version'], { encoding: 'utf8', timeout: 3000, stdio: 'pipe' });
27
+ if (res.error && res.error.code === 'EACCES') {
28
+ log('WARNING: still not executable; please chmod +x manually');
29
+ }
30
+ } catch (err) {
31
+ log(`WARNING: could not ensure permissions for ${label}: ${err.message}`);
32
+ }
33
+ }
60
34
 
61
- // 尝试执行一次 --version 做简单验证
62
- try {
63
- const { spawnSync } = require('child_process');
64
- const testResult = spawnSync(binaryPath, ['--version'], {
65
- encoding: 'utf8',
66
- timeout: 3000,
67
- stdio: 'pipe',
68
- });
35
+ function ensureExecutablePermissions() {
36
+ const scriptDir = __dirname;
37
+ const projectRoot = path.resolve(scriptDir, '..');
38
+ const platformsDir = path.join(projectRoot, 'platforms');
69
39
 
70
- if (testResult.status === 0 || testResult.status === 1) {
71
- console.log('✅ fnva binary is executable and responding');
72
- } else if (testResult.error && testResult.error.code === 'EACCES') {
73
- console.log('❌ fnva binary still has permission issues');
74
- console.log(` Manual fix required: chmod +x "${binaryPath}"`);
75
- }
76
- } catch {
77
- // 测试失败不视为致命错误,可能是二进制本身的问题
78
- }
79
- } catch (error) {
80
- console.warn(`⚠️ Could not fix binary permissions (${label}): ${error.message}`);
81
- console.log(` Manual fix required: chmod +x "${binaryPath}"`);
82
- }
83
- }
40
+ log('Ensuring fnva binary permissions...');
84
41
 
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');
95
- } else {
96
- console.log(`❌ Binary not found: ${archBinaryPath}`);
97
- console.log(` Also checked legacy path: ${flatBinaryPath}`);
98
- console.log(' This might indicate an incomplete installation');
99
- }
42
+ if (!fs.existsSync(platformsDir)) {
43
+ log('Info: no platforms directory found; skipping (dev install)');
44
+ return;
45
+ }
100
46
 
101
- // 额外检查:如果是全局安装,也尝试检查路径上的 fnva 权限
102
- if (process.env.npm_config_global === 'true') {
103
- try {
104
- const { execSync } = require('child_process');
105
- const globalFnvaPath = execSync('which fnva', { encoding: 'utf8' }).trim();
47
+ const platform = process.platform;
48
+ const arch = process.arch === 'arm64' ? 'arm64' : 'x64';
49
+ const platformDir = `${platform}-${arch}`;
50
+ const binaryName = platform === 'win32' ? 'fnva.exe' : 'fnva';
51
+ const archBinaryPath = path.join(platformsDir, platformDir, binaryName);
52
+ const flatBinaryPath = path.join(platformsDir, binaryName);
106
53
 
107
- if (globalFnvaPath && fs.existsSync(globalFnvaPath)) {
108
- console.log(`📍 Checking globally installed binary: ${globalFnvaPath}`);
54
+ if (platform === 'win32') {
55
+ log('Info: Windows detected; chmod not required; skipping permission changes');
56
+ return;
57
+ }
109
58
 
110
- const globalStats = fs.statSync(globalFnvaPath);
111
- const globalHasExecPermission = (globalStats.mode & 0o111) !== 0;
59
+ if (fs.existsSync(archBinaryPath)) {
60
+ ensureExecutable(archBinaryPath, platformDir);
61
+ } else if (fs.existsSync(flatBinaryPath)) {
62
+ log('Info: falling back to legacy platforms/fnva layout');
63
+ ensureExecutable(flatBinaryPath, 'platforms/fnva');
64
+ } else {
65
+ log(`Warning: binary not found: ${archBinaryPath}`);
66
+ log(` Also checked: ${flatBinaryPath}`);
67
+ }
112
68
 
113
- if (!globalHasExecPermission) {
114
- console.log('❌ Global fnva binary lacks executable permissions');
115
- console.log(` Please run: sudo chmod +x "${globalFnvaPath}"`);
116
- } else {
117
- console.log('✅ Global fnva binary has correct permissions');
118
- }
119
- }
120
- } catch {
121
- console.log('ℹ️ Could not verify global installation');
69
+ if (process.env.npm_config_global === 'true') {
70
+ try {
71
+ const which = spawnSync('which', ['fnva'], { encoding: 'utf8' });
72
+ const globalPath = which.stdout?.trim();
73
+ if (globalPath && fs.existsSync(globalPath)) {
74
+ ensureExecutable(globalPath, 'global fnva');
122
75
  }
76
+ } catch {
77
+ // ignore
123
78
  }
124
- } catch (error) {
125
- console.warn(`⚠️ Permission check failed: ${error.message}`);
126
79
  }
127
80
  }
128
81
 
129
- // 运行权限检查
130
- ensureExecutablePermissions();
131
-
82
+ ensureExecutablePermissions();
@@ -1,4 +1,8 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
+
3
+ // Shell integration installer for fnva (npm postinstall helper).
4
+ // Generates a lightweight function wrapper per shell that calls the real
5
+ // `fnva` binary and applies environment switch scripts.
2
6
 
3
7
  const fs = require('fs');
4
8
  const path = require('path');
@@ -14,7 +18,12 @@ function detectShell() {
14
18
  function getShellConfigPath(shell) {
15
19
  switch (shell) {
16
20
  case 'powershell':
17
- return path.join(process.env.USERPROFILE || os.homedir(), 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1');
21
+ return path.join(
22
+ process.env.USERPROFILE || os.homedir(),
23
+ 'Documents',
24
+ 'WindowsPowerShell',
25
+ 'Microsoft.PowerShell_profile.ps1'
26
+ );
18
27
  case 'bash':
19
28
  return path.join(os.homedir(), '.bashrc');
20
29
  case 'zsh':
@@ -28,41 +37,28 @@ function getShellConfigPath(shell) {
28
37
 
29
38
  function getPowerShellFunction() {
30
39
  return `
31
- # fnva 自动化函数 - npm 安装自动添加
40
+ # fnva auto integration (added by npm install)
32
41
  function fnva {
33
42
  if ($args.Count -ge 2 -and ($args[0] -eq "java" -or $args[0] -eq "llm" -or $args[0] -eq "cc") -and ($args[1] -eq "use")) {
34
43
  $tempFile = Join-Path $env:TEMP ("fnva_script_" + (Get-Random) + ".ps1")
35
44
 
36
45
  $env:FNVAAUTOMODE = "1"
37
46
  try {
38
- # 捕获 fnva 输出并保存到临时文件
39
47
  $output = cmd.exe /c "set FNVA_AUTO_MODE=%FNVAAUTOMODE% && fnva $args" 2>&1
40
48
 
41
- # 如果输出包含 PowerShell 脚本内容,则保存并执行
42
- if ($output -match '\$env:' -or $output -match 'Write-Host') {
49
+ if ($output -match '\\$env:' -or $output -match 'Write-Host') {
43
50
  $output | Out-File -FilePath $tempFile -Encoding UTF8
44
- try {
45
- & $tempFile
46
- } catch {
47
- Write-Host "执行脚本时出错: $_" -ForegroundColor Red
48
- }
51
+ try { & $tempFile } catch { Write-Host "Error executing script: $_" -ForegroundColor Red }
49
52
  } else {
50
- # 如果不是脚本内容,直接输出
51
53
  $output
52
54
  }
53
55
  } finally {
54
56
  $env:FNVAAUTOMODE = ""
55
- if (Test-Path $tempFile) {
56
- Remove-Item $tempFile -ErrorAction SilentlyContinue
57
- }
57
+ if (Test-Path $tempFile) { Remove-Item $tempFile -ErrorAction SilentlyContinue }
58
58
  }
59
59
  } else {
60
60
  $env:FNVAAUTOMODE = "1"
61
- try {
62
- cmd.exe /c "set FNVA_AUTO_MODE=%FNVAAUTOMODE% && fnva $args"
63
- } finally {
64
- $env:FNVAAUTOMODE = ""
65
- }
61
+ try { cmd.exe /c "set FNVA_AUTO_MODE=%FNVAAUTOMODE% && fnva $args" } finally { $env:FNVAAUTOMODE = "" }
66
62
  }
67
63
  }
68
64
  `;
@@ -70,7 +66,7 @@ function fnva {
70
66
 
71
67
  function getBashFunction() {
72
68
  return `
73
- # fnva 自动化函数 - npm 安装自动添加
69
+ # fnva auto integration (added by npm install)
74
70
  fnva() {
75
71
  local __fnva_bin
76
72
  __fnva_bin="$(command -v fnva | head -n 1)"
@@ -96,7 +92,7 @@ fnva() {
96
92
 
97
93
  function getFishFunction() {
98
94
  return `
99
- # fnva 自动化函数 - npm 安装自动添加
95
+ # fnva auto integration (added by npm install)
100
96
  function fnva
101
97
  set __fnva_bin (command -v fnva | head -n 1)
102
98
  if test -z "$__fnva_bin"
@@ -122,9 +118,8 @@ function getShellFunction(shell) {
122
118
  case 'powershell':
123
119
  return getPowerShellFunction();
124
120
  case 'bash':
125
- return getBashFunction();
126
121
  case 'zsh':
127
- return getBashFunction(); // zsh 使用与 bash 相同的函数
122
+ return getBashFunction();
128
123
  case 'fish':
129
124
  return getFishFunction();
130
125
  default:
@@ -136,9 +131,8 @@ function isFunctionInstalled(configPath) {
136
131
  if (!fs.existsSync(configPath)) {
137
132
  return false;
138
133
  }
139
-
140
134
  const content = fs.readFileSync(configPath, 'utf8');
141
- return content.includes('fnva 自动化函数 - npm 安装自动添加');
135
+ return content.includes('fnva auto integration (added by npm install)');
142
136
  }
143
137
 
144
138
  function installShellIntegration() {
@@ -146,13 +140,13 @@ function installShellIntegration() {
146
140
  const configPath = getShellConfigPath(shell);
147
141
 
148
142
  if (!configPath) {
149
- console.log(`⚠️ 不支持的 shell: ${shell}`);
150
- console.log('请手动配置 fnva,详见 README');
143
+ console.log(`⚠️ Unsupported shell: ${shell}`);
144
+ console.log('Please configure fnva manually (see README).');
151
145
  return false;
152
146
  }
153
147
 
154
148
  if (isFunctionInstalled(configPath)) {
155
- console.log(`✅ fnva shell 集成已存在: ${configPath}`);
149
+ console.log(`✅ fnva shell integration already present: ${configPath}`);
156
150
  return true;
157
151
  }
158
152
 
@@ -171,9 +165,8 @@ function installShellIntegration() {
171
165
  fs.writeFileSync(configPath, functionCode);
172
166
  }
173
167
 
174
- console.log(`✅ fnva shell 集成已安装到: ${configPath}`);
175
- console.log('🔄 请重新加载你的 shell 配置:');
176
-
168
+ console.log(`✅ fnva shell integration installed at: ${configPath}`);
169
+ console.log('🔄 Reload your shell config:');
177
170
  switch (shell) {
178
171
  case 'powershell':
179
172
  console.log(' . $PROFILE');
@@ -188,29 +181,28 @@ function installShellIntegration() {
188
181
  console.log(' source ~/.config/fish/config.fish');
189
182
  break;
190
183
  }
191
-
192
184
  return true;
193
185
  } catch (error) {
194
- console.log(`❌ 安装失败: ${error.message}`);
195
- console.log('请手动配置 fnva');
186
+ console.log(`❌ Install failed: ${error.message}`);
187
+ console.log('Please configure fnva manually (see README).');
196
188
  return false;
197
189
  }
198
190
  }
199
191
 
200
192
  function promptInstallation() {
201
193
  if (process.env.FNVA_SKIP_SHELL_SETUP === '1') {
202
- console.log('⏭️ 跳过 shell 集成安装');
194
+ console.log('⏭️ Skipping shell integration (FNVA_SKIP_SHELL_SETUP=1)');
203
195
  return;
204
196
  }
205
197
 
206
198
  const shell = detectShell();
207
- console.log(`🔍 检测到 shell: ${shell}`);
208
- console.log('❓ 是否安装 fnva shell 集成? (y/N)');
199
+ console.log(`🔍 Detected shell: ${shell}`);
200
+ console.log('❓ Install fnva shell integration? (y/N)');
209
201
 
210
202
  const readline = require('readline');
211
203
  const rl = readline.createInterface({
212
204
  input: process.stdin,
213
- output: process.stdout
205
+ output: process.stdout,
214
206
  });
215
207
 
216
208
  rl.question('> ', (answer) => {
@@ -218,21 +210,21 @@ function promptInstallation() {
218
210
  if (normalized === 'y' || normalized === 'yes') {
219
211
  installShellIntegration();
220
212
  } else {
221
- console.log('⏩ 已跳过 shell 集成安装');
213
+ console.log('⏩ Skipped shell integration.');
222
214
  }
223
215
  rl.close();
224
216
  });
225
217
  }
226
218
 
227
219
  function main() {
228
- console.log('🛠️ fnva shell 集成安装器');
229
- console.log(`📦 Node.js 版本: ${process.version}`);
230
- console.log(`📍 进程工作目录: ${process.cwd()}`);
220
+ console.log('🛠️ fnva shell integration installer');
221
+ console.log(`📦 Node.js version: ${process.version}`);
222
+ console.log(`📍 CWD: ${process.cwd()}`);
231
223
 
232
224
  if (process.argv.includes('--auto') || process.argv.includes('--yes')) {
233
- console.log('🤖 自动模式启动安装...');
225
+ console.log('🤖 Auto mode: installing...');
234
226
  const result = installShellIntegration();
235
- console.log(`📄 安装结果: ${result ? '成功' : '失败'}`);
227
+ console.log(`📄 Install result: ${result ? 'success' : 'failed'}`);
236
228
  } else {
237
229
  promptInstallation();
238
230
  }
@@ -247,5 +239,5 @@ module.exports = {
247
239
  getShellConfigPath,
248
240
  getShellFunction,
249
241
  isFunctionInstalled,
250
- installShellIntegration
242
+ installShellIntegration,
251
243
  };
@@ -1,13 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // Shell integration uninstaller for fnva (npm postuninstall helper).
4
+ // Cleans fnva wrapper functions from common shell config files.
5
+
3
6
  const fs = require('fs');
4
7
  const path = require('path');
5
8
  const os = require('os');
6
9
 
7
10
  function detectShell() {
8
- if (process.platform === 'win32') {
9
- return 'powershell';
10
- }
11
+ if (process.platform === 'win32') return 'powershell';
11
12
  return process.env.SHELL?.split('/').pop() || 'bash';
12
13
  }
13
14
 
@@ -33,7 +34,7 @@ function cleanConfigFile(cfgPath) {
33
34
  let content = fs.readFileSync(cfgPath, 'utf8');
34
35
  const originalContent = content;
35
36
 
36
- const marker = '# fnva 自动化函数 - npm 安装自动添加';
37
+ const marker = '# fnva auto integration (added by npm install)';
37
38
  const startIndex = content.indexOf(marker);
38
39
 
39
40
  if (startIndex !== -1) {
@@ -69,10 +70,9 @@ function cleanConfigFile(cfgPath) {
69
70
  }
70
71
  }
71
72
 
72
- // 正则兜底:移除残留 fnva 片段
73
73
  if (content === originalContent) {
74
74
  content = content
75
- .replace(/# fnva 自动化函数 - npm 安装自动添加[\s\S]*?(?=\n\S|\n$)/g, '')
75
+ .replace(/# fnva auto integration \(added by npm install\)[\s\S]*?(?=\n\S|\n$)/g, '')
76
76
  .replace(/.*fnva.*\n?/g, '')
77
77
  .replace(/.*FNVAAUTOMODE.*\n?/g, '')
78
78
  .replace(/.*cmd\.exe.*fnva.*\n?/g, '')
@@ -82,50 +82,50 @@ function cleanConfigFile(cfgPath) {
82
82
 
83
83
  if (content !== originalContent) {
84
84
  fs.writeFileSync(cfgPath, content);
85
- console.log(`✅ fnva shell 集成已从 ${cfgPath} 移除`);
85
+ console.log(`✅ fnva shell integration removed from ${cfgPath}`);
86
86
  return true;
87
87
  }
88
88
 
89
- console.log(`⚠️ 未在 ${cfgPath} 找到需要清理的内容`);
89
+ console.log(`⚠️ No fnva block found in ${cfgPath}`);
90
90
  return false;
91
91
  }
92
92
 
93
93
  function removeShellIntegration(configPath, shell) {
94
- const paths = getShellConfigPaths(shell);
95
- if (configPath) paths.unshift(configPath); // 兼容传入单一路径
96
-
94
+ const paths = configPath ? [configPath] : getShellConfigPaths(shell);
97
95
  let removedAny = false;
96
+
98
97
  for (const cfgPath of paths) {
99
98
  if (!cfgPath || !fs.existsSync(cfgPath)) continue;
100
99
  try {
101
100
  const removed = cleanConfigFile(cfgPath);
102
101
  removedAny = removedAny || removed;
103
102
  } catch (error) {
104
- console.log(`❌ 移除失败 (${cfgPath}): ${error.message}`);
103
+ console.log(`❌ Remove failed (${cfgPath}): ${error.message}`);
105
104
  }
106
105
  }
107
106
 
108
107
  if (!removedAny) {
109
- console.log('⚠️ 未找到可清理的 shell 配置文件或未匹配到 fnva 片段');
108
+ console.log('⚠️ No shell config cleaned (file missing or no fnva block)');
110
109
  }
110
+
111
111
  return removedAny;
112
112
  }
113
113
 
114
114
  function main() {
115
- console.log('🧹 fnva shell 集成卸载');
115
+ console.log('🧹 fnva shell integration uninstaller');
116
116
 
117
117
  const shell = detectShell();
118
118
  const paths = getShellConfigPaths(shell);
119
119
 
120
- if (paths.length === 0) {
121
- console.log(`⚠️ 不支持的 shell: ${shell}`);
120
+ if (!paths.length) {
121
+ console.log(`⚠️ Unsupported shell: ${shell}`);
122
122
  return;
123
123
  }
124
124
 
125
125
  const success = removeShellIntegration(null, shell);
126
126
 
127
127
  if (success) {
128
- console.log('🔄 请重新加载你的 shell 配置:');
128
+ console.log('🔄 Reload your shell config:');
129
129
  switch (shell) {
130
130
  case 'powershell':
131
131
  console.log(' . $PROFILE');
@@ -151,4 +151,4 @@ module.exports = {
151
151
  detectShell,
152
152
  getShellConfigPaths,
153
153
  removeShellIntegration,
154
- };
154
+ };