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 +11 -0
- package/bin/fnva.js +247 -15
- package/package.json +1 -1
- package/platforms/fnva +0 -0
- package/platforms/fnva.exe +0 -0
- package/platforms/fnva-0.0.10.tgz +0 -0
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.
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
451
|
+
// 检查是否使用了 --apply 参数
|
|
452
|
+
if (hasApplyFlag(args)) {
|
|
453
|
+
// 直接应用环境变量到当前进程
|
|
454
|
+
const envVars = parseEnvironmentScript(script);
|
|
455
|
+
applyEnvironmentVariables(envVars);
|
|
456
|
+
displaySuccessMessage(envType, envName, envVars);
|
|
256
457
|
} else {
|
|
257
|
-
|
|
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
package/platforms/fnva
CHANGED
|
Binary file
|
package/platforms/fnva.exe
CHANGED
|
Binary file
|
|
Binary file
|