fnva 0.0.10 → 0.0.12

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
@@ -28,6 +28,17 @@ yarn global add fnva
28
28
 
29
29
  # 使用 pnpm
30
30
  pnpm add -g fnva
31
+
32
+ function fnva {
33
+ 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
+ $tempFile = "$env:TEMP\fnva_script_$(Get-Random).ps1"
35
+ & node bin\fnva.js $args | Out-File -FilePath $tempFile -Encoding UTF8
36
+ & $tempFile
37
+ Remove-Item $tempFile -ErrorAction SilentlyContinue
38
+ } else {
39
+ & node bin\fnva.js $args
40
+ }
41
+ }
31
42
  ```
32
43
 
33
44
  ### 方式二:从 Releases 下载二进制文件
package/bin/fnva.js CHANGED
@@ -38,6 +38,11 @@ function platformBinaryPath(platformOverride) {
38
38
  }
39
39
 
40
40
  function buildBinaryPath() {
41
+ // 如果设置了 FNVA_SKIP_NATIVE,跳过原生二进制查找
42
+ if (process.env.FNVA_SKIP_NATIVE === '1') {
43
+ return null;
44
+ }
45
+
41
46
  const platform = resolvePlatform();
42
47
  const binaryCandidates = [];
43
48
 
@@ -103,6 +108,43 @@ function hasSessionFlag(args) {
103
108
  return args.includes('--session');
104
109
  }
105
110
 
111
+ function hasApplyFlag(args) {
112
+ return args.includes('--apply');
113
+ }
114
+
115
+ function hasAutoExecuteFlag(args) {
116
+ return args.includes('--auto');
117
+ }
118
+
119
+ function removeAutoFlag(args) {
120
+ const index = args.indexOf('--auto');
121
+ if (index > -1) {
122
+ return args.slice(0, index).concat(args.slice(index + 1));
123
+ }
124
+ return args;
125
+ }
126
+
127
+ function createTempScriptFile(script, envType, envName) {
128
+ try {
129
+ const os = require('os');
130
+ const fs = require('fs');
131
+ const path = require('path');
132
+
133
+ const tempDir = os.tmpdir();
134
+ const scriptFile = path.join(tempDir, `fnva_${envType}_${envName}_${Date.now()}.ps1`);
135
+
136
+ fs.writeFileSync(scriptFile, script, 'utf8');
137
+
138
+ console.log('');
139
+ console.log('💡 环境已切换到当前进程。要在新的 PowerShell 窗口中使用此环境,运行:');
140
+ console.log(` ${scriptFile}`);
141
+ console.log(' 或者: fnva', envType, 'use', envName, '--auto');
142
+
143
+ } catch (error) {
144
+ console.warn('⚠️ 无法创建临时脚本文件:', error.message);
145
+ }
146
+ }
147
+
106
148
  function parseEnvironmentScript(scriptContent) {
107
149
  if (!scriptContent || scriptContent.trim() === '') {
108
150
  return {};
@@ -120,10 +162,25 @@ function parseEnvironmentScript(scriptContent) {
120
162
  const trimmedLine = line.trim();
121
163
 
122
164
  // 解析 PowerShell 环境变量设置
123
- if (trimmedLine.startsWith('$env:')) {
124
- const match = trimmedLine.match(/\$env:(\w+)\s*=\s*"([^"]*)"/);
165
+ if (trimmedLine.includes('$env:')) {
166
+ // 匹配 $env:VARNAME = "value" 格式
167
+ let match = trimmedLine.match(/\$env:(\w+)\s*=\s*"([^"]*)"/);
168
+ if (match) {
169
+ envVars[match[1]] = match[2];
170
+ continue;
171
+ }
172
+
173
+ // 匹配 $env:VARNAME = 'value' 格式
174
+ match = trimmedLine.match(/\$env:(\w+)\s*=\s*'([^']*)'/);
125
175
  if (match) {
126
176
  envVars[match[1]] = match[2];
177
+ continue;
178
+ }
179
+
180
+ // 匹配 $env:VARNAME = value 格式(不带引号)
181
+ match = trimmedLine.match(/\$env:(\w+)\s*=\s*([^;]+)/);
182
+ if (match) {
183
+ envVars[match[1]] = match[2].trim().replace(/['"]/g, '');
127
184
  }
128
185
  }
129
186
 
@@ -134,12 +191,6 @@ function parseEnvironmentScript(scriptContent) {
134
191
  envVars[match[1]] = match[2];
135
192
  }
136
193
  }
137
-
138
- // 解析不带引号的环境变量设置
139
- const unquotedMatch = trimmedLine.match(/\$env:(\w+)\s*=\s*([^;]+)/);
140
- if (unquotedMatch) {
141
- envVars[unquotedMatch[1]] = unquotedMatch[2].trim();
142
- }
143
194
  }
144
195
 
145
196
  return envVars;
@@ -167,20 +218,169 @@ function displaySuccessMessage(envType, envName, envVars) {
167
218
  }
168
219
  }
169
220
 
221
+ function generateSimpleScript(envVars, envType, envName) {
222
+ const lines = [];
223
+
224
+ if (process.platform === 'win32') {
225
+ // Windows PowerShell
226
+ lines.push(`Write-Host "Switched to ${envType} environment: ${envName}" -ForegroundColor Green`);
227
+
228
+ if (envVars.JAVA_HOME) {
229
+ lines.push(`$env:JAVA_HOME = "${envVars.JAVA_HOME}"`);
230
+ // 对于 PATH,我们需要智能处理:移除旧的 Java 路径,添加新的
231
+ lines.push(`# Remove existing Java paths from PATH`);
232
+ lines.push(`$pathParts = $env:PATH -split ';'`);
233
+ lines.push(`$cleanPath = @()`);
234
+ lines.push(`foreach ($part in $pathParts) {`);
235
+ lines.push(` if ($part -notmatch 'java' -and $part -notmatch 'jdk') {`);
236
+ lines.push(` $cleanPath += $part`);
237
+ lines.push(` }`);
238
+ lines.push(`}`);
239
+ lines.push(`$env:PATH = "${envVars.JAVA_HOME}\\bin;" + ($cleanPath -join ';')`);
240
+ lines.push(`Write-Host "JAVA_HOME: $env:JAVA_HOME" -ForegroundColor Yellow`);
241
+ }
242
+
243
+ if (envVars.ANTHROPIC_AUTH_TOKEN) {
244
+ lines.push(`$env:ANTHROPIC_AUTH_TOKEN = "${envVars.ANTHROPIC_AUTH_TOKEN}"`);
245
+ lines.push(`Write-Host "ANTHROPIC_AUTH_TOKEN: [已设置]" -ForegroundColor Yellow`);
246
+ }
247
+
248
+ if (envVars.OPENAI_API_KEY) {
249
+ lines.push(`$env:OPENAI_API_KEY = "${envVars.OPENAI_API_KEY}"`);
250
+ lines.push(`Write-Host "OPENAI_API_KEY: [已设置]" -ForegroundColor Yellow`);
251
+ }
252
+ } else {
253
+ // Unix-like systems
254
+ lines.push(`echo "Switched to ${envType} environment: ${envName}"`);
255
+
256
+ if (envVars.JAVA_HOME) {
257
+ lines.push(`export JAVA_HOME="${envVars.JAVA_HOME}"`);
258
+ // 对于 PATH,我们也需要智能处理
259
+ lines.push(`# Remove existing Java paths from PATH`);
260
+ lines.push(`echo $PATH | tr ':' '\\n' | grep -v java | grep -v jdk | tr '\\n' ':' | sed 's/:$//' > /tmp/clean_path`);
261
+ lines.push(`export PATH="${envVars.JAVA_HOME}/bin:$(cat /tmp/clean_path)"`);
262
+ lines.push(`rm -f /tmp/clean_path`);
263
+ lines.push(`echo "JAVA_HOME: $JAVA_HOME"`);
264
+ }
265
+
266
+ if (envVars.ANTHROPIC_AUTH_TOKEN) {
267
+ lines.push(`export ANTHROPIC_AUTH_TOKEN="${envVars.ANTHROPIC_AUTH_TOKEN}"`);
268
+ lines.push(`echo "ANTHROPIC_AUTH_TOKEN: [已设置]"`);
269
+ }
270
+
271
+ if (envVars.OPENAI_API_KEY) {
272
+ lines.push(`export OPENAI_API_KEY="${envVars.OPENAI_API_KEY}"`);
273
+ lines.push(`echo "OPENAI_API_KEY: [已设置]"`);
274
+ }
275
+ }
276
+
277
+ return lines.join('\n');
278
+ }
279
+
280
+ function handleNodeOnlyMode(args) {
281
+ const fs = require('fs');
282
+ const path = require('path');
283
+ const os = require('os');
284
+
285
+ // 简单的命令处理
286
+ if (args.length === 0) {
287
+ console.log('fnva - 环境管理工具 (Node.js 模式)');
288
+ console.log('');
289
+ console.log('支持的命令:');
290
+ console.log(' java list - 列出 Java 环境');
291
+ console.log(' java use <n> - 切换 Java 环境');
292
+ console.log('');
293
+ console.log('注意: Node.js 模式功能有限,建议使用原生二进制版本。');
294
+ return;
295
+ }
296
+
297
+ if (args[0] === 'java') {
298
+ const homeDir = os.homedir();
299
+ const fnvaDir = path.join(homeDir, '.fnva', 'java-packages');
300
+
301
+ if (args[1] === 'list') {
302
+ if (!fs.existsSync(fnvaDir)) {
303
+ console.log('No Java environments found');
304
+ return;
305
+ }
306
+
307
+ const versions = fs.readdirSync(fnvaDir, { withFileTypes: true })
308
+ .filter(dirent => dirent.isDirectory())
309
+ .map(dirent => dirent.name)
310
+ .sort();
311
+
312
+ if (versions.length === 0) {
313
+ console.log('No Java environments found');
314
+ } else {
315
+ console.log('Available java environments:');
316
+ versions.forEach(version => {
317
+ const jdkPath = path.join(fnvaDir, version);
318
+ if (fs.existsSync(path.join(jdkPath, 'release'))) {
319
+ try {
320
+ const releaseContent = fs.readFileSync(path.join(jdkPath, 'release'), 'utf8');
321
+ const versionMatch = releaseContent.match(/JAVA_VERSION="(.+)"/);
322
+ const javaVersion = versionMatch ? versionMatch[1].replace(/"/g, '') : 'Unknown';
323
+ console.log(` ${version}: Java ${javaVersion} (${jdkPath})`);
324
+ } catch (e) {
325
+ console.log(` ${version}: (${jdkPath})`);
326
+ }
327
+ }
328
+ });
329
+ }
330
+ } else if (args[1] === 'use' && args[2]) {
331
+ const version = args[2];
332
+ const jdkPath = path.join(fnvaDir, version);
333
+
334
+ if (!fs.existsSync(jdkPath)) {
335
+ console.error(`Java environment '${version}' not found`);
336
+ process.exit(1);
337
+ }
338
+
339
+ // 生成环境切换脚本
340
+ const envVars = {
341
+ JAVA_HOME: jdkPath
342
+ };
343
+
344
+ const script = generateSimpleScript(envVars, 'java', version);
345
+ console.log(script);
346
+ } else {
347
+ console.error('Usage: fnva java <list|use <version>>');
348
+ process.exit(1);
349
+ }
350
+ } else {
351
+ console.error(`Command '${args[0]}' not supported in Node.js mode`);
352
+ process.exit(1);
353
+ }
354
+ }
355
+
170
356
  function run() {
171
357
  const binaryPath = buildBinaryPath();
172
358
 
173
359
  if (!binaryPath) {
360
+ if (process.env.FNVA_SKIP_NATIVE === '1') {
361
+ // 纯 Node.js 模式 - 实现基本的环境切换功能
362
+ const args = process.argv.slice(2);
363
+ handleNodeOnlyMode(args);
364
+ return;
365
+ }
366
+
174
367
  console.error('Error: fnva native binary not found.');
175
368
  console.error('');
176
369
  console.error("Please either:");
177
370
  console.error(" 1) Run 'npm run build' (or 'npm run build:all') to produce platform binaries,");
178
371
  console.error(" 2) Install a release package that includes the platforms directory, or");
179
372
  console.error(" 3) Set FNVA_NATIVE_PATH to the full path of an existing fnva executable.");
373
+ console.error(" 4) Set FNVA_SKIP_NATIVE=1 to use Node.js mode (limited functionality).");
180
374
  process.exit(1);
181
375
  }
182
376
 
183
- const args = process.argv.slice(2);
377
+ let args = process.argv.slice(2);
378
+
379
+ // 如果设置了 FNVA_AUTO_EXECUTE,则为环境切换命令启用自动执行
380
+ if (process.env.FNVA_AUTO_EXECUTE === '1' && isEnvironmentSwitchCommand(args) && !hasSessionFlag(args)) {
381
+ // 添加 --auto 标志来启用自动执行
382
+ args = args.concat('--auto');
383
+ }
184
384
  const isSwitchCommand = isEnvironmentSwitchCommand(args);
185
385
 
186
386
  if (isSwitchCommand) {
@@ -248,13 +448,45 @@ function run() {
248
448
  console.log(`📝 Script was: ${script}`);
249
449
  }
250
450
  } else {
251
- console.log(`✅ Switched to ${envType} environment: ${envName}`);
252
- if (process.stdout.isTTY) {
253
- console.log('');
254
- console.log('💡 在当前会话应用环境:');
255
- console.log(` fnva ${envType} use ${envName} --shell powershell | Invoke-Expression`);
451
+ // 检查是否使用了 --apply 参数
452
+ if (hasApplyFlag(args)) {
453
+ // 直接应用环境变量到当前进程
454
+ const envVars = parseEnvironmentScript(script);
455
+ applyEnvironmentVariables(envVars);
456
+ displaySuccessMessage(envType, envName, envVars);
256
457
  } else {
257
- process.stdout.write(script);
458
+ // 在 Windows 中,智能处理环境设置
459
+ const envVars = parseEnvironmentScript(script);
460
+ const simpleScript = generateSimpleScript(envVars, envType, envName);
461
+
462
+ // 尝试自动执行(如果可能)
463
+ if (process.env.FNVA_AUTO_EXECUTE === '1') {
464
+ const os = require('os');
465
+ const fs = require('fs');
466
+ const path = require('path');
467
+ const { spawn } = require('child_process');
468
+
469
+ try {
470
+ const tempFile = path.join(os.tmpdir(), `fnva_auto_${Date.now()}.ps1`);
471
+ fs.writeFileSync(tempFile, simpleScript, 'utf8');
472
+
473
+ // 使用 PowerShell 执行脚本
474
+ spawn('powershell', ['-ExecutionPolicy', 'Bypass', '-File', tempFile], {
475
+ stdio: 'inherit',
476
+ shell: false
477
+ }).on('exit', () => {
478
+ try { fs.unlinkSync(tempFile); } catch (_) {}
479
+ });
480
+
481
+ console.log('✅ 环境已自动切换');
482
+ return;
483
+ } catch (error) {
484
+ console.warn('⚠️ 自动执行失败,回退到脚本输出');
485
+ }
486
+ }
487
+
488
+ // 默认输出脚本
489
+ process.stdout.write(simpleScript);
258
490
  }
259
491
  }
260
492
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fnva",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "跨平台环境切换工具,支持 Java 和 LLM 环境配置",
5
5
  "author": "protagonistss",
6
6
  "license": "MIT",
package/platforms/fnva CHANGED
Binary file
Binary file
Binary file