fnva 0.0.23 → 0.0.25
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.
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 编码处理工具模块
|
|
3
|
+
* 用于统一处理跨平台字符编码问题
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 编码处理工具类
|
|
11
|
+
*/
|
|
12
|
+
class EncodingUtils {
|
|
13
|
+
/**
|
|
14
|
+
* 设置Windows控制台编码为UTF-8
|
|
15
|
+
*/
|
|
16
|
+
static setWindowsConsoleEncoding() {
|
|
17
|
+
if (process.platform === 'win32') {
|
|
18
|
+
try {
|
|
19
|
+
// 尝试设置控制台编码为UTF-8
|
|
20
|
+
const { execSync } = require('child_process');
|
|
21
|
+
execSync('chcp 65001 > nul 2>&1', { stdio: 'ignore' });
|
|
22
|
+
|
|
23
|
+
// 设置Node.js输出编码
|
|
24
|
+
if (process.stdout._handle && process.stdout._handle.setEncoding) {
|
|
25
|
+
process.stdout._handle.setEncoding('utf8');
|
|
26
|
+
}
|
|
27
|
+
if (process.stderr._handle && process.stderr._handle.setEncoding) {
|
|
28
|
+
process.stderr._handle.setEncoding('utf8');
|
|
29
|
+
}
|
|
30
|
+
} catch (error) {
|
|
31
|
+
// 静默忽略错误,避免影响正常功能
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 为文件内容添加适当的编码标记
|
|
38
|
+
* @param {string} content - 文件内容
|
|
39
|
+
* @param {string} filePath - 文件路径(用于确定文件类型)
|
|
40
|
+
* @returns {string} - 处理后的内容
|
|
41
|
+
*/
|
|
42
|
+
static addEncodingSignature(content, filePath) {
|
|
43
|
+
// 为Windows PowerShell脚本添加BOM
|
|
44
|
+
if (process.platform === 'win32' && this.isPowerShellScript(filePath)) {
|
|
45
|
+
return '\ufeff' + content;
|
|
46
|
+
}
|
|
47
|
+
return content;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 检查文件是否为PowerShell脚本
|
|
52
|
+
* @param {string} filePath - 文件路径
|
|
53
|
+
* @returns {boolean} - 是否为PowerShell脚本
|
|
54
|
+
*/
|
|
55
|
+
static isPowerShellScript(filePath) {
|
|
56
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
57
|
+
return ext === '.ps1';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 安全地写入文件,自动处理编码
|
|
62
|
+
* @param {string} filePath - 文件路径
|
|
63
|
+
* @param {string} content - 文件内容
|
|
64
|
+
* @param {string} encoding - 编码格式,默认为utf8
|
|
65
|
+
*/
|
|
66
|
+
static writeFileWithEncoding(filePath, content, encoding = 'utf8') {
|
|
67
|
+
const processedContent = this.addEncodingSignature(content, filePath);
|
|
68
|
+
fs.writeFileSync(filePath, processedContent, encoding);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 生成PowerShell编码设置脚本
|
|
73
|
+
* @returns {string} - PowerShell编码设置代码
|
|
74
|
+
*/
|
|
75
|
+
static generatePowerShellEncodingSetup() {
|
|
76
|
+
return [
|
|
77
|
+
'# 设置UTF-8编码以正确显示中文',
|
|
78
|
+
'[Console]::OutputEncoding = [System.Text.Encoding]::UTF8',
|
|
79
|
+
'$OutputEncoding = [System.Console]::OutputEncoding',
|
|
80
|
+
''
|
|
81
|
+
].join('\n');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 检测系统默认编码
|
|
86
|
+
* @returns {string} - 系统编码名称
|
|
87
|
+
*/
|
|
88
|
+
static detectSystemEncoding() {
|
|
89
|
+
if (process.platform === 'win32') {
|
|
90
|
+
try {
|
|
91
|
+
const { execSync } = require('child_process');
|
|
92
|
+
const result = execSync('chcp', { encoding: 'utf8' });
|
|
93
|
+
const match = result.match(/活动代码页: (\d+)/);
|
|
94
|
+
return match ? `cp${match[1]}` : 'utf8';
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return 'cp936'; // Windows中文默认编码
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
return 'utf8'; // Unix-like系统默认UTF-8
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* 创建临时PowerShell脚本文件
|
|
105
|
+
* @param {string} content - 脚本内容
|
|
106
|
+
* @param {string} prefix - 文件名前缀
|
|
107
|
+
* @returns {string} - 临时文件路径
|
|
108
|
+
*/
|
|
109
|
+
static createTempPowerShellScript(content, prefix = 'fnva') {
|
|
110
|
+
const os = require('os');
|
|
111
|
+
const path = require('path');
|
|
112
|
+
|
|
113
|
+
const tempDir = os.tmpdir();
|
|
114
|
+
const timestamp = Date.now();
|
|
115
|
+
const scriptFile = path.join(tempDir, `${prefix}_${timestamp}.ps1`);
|
|
116
|
+
|
|
117
|
+
// 在脚本开头添加编码设置
|
|
118
|
+
const encodingSetup = this.generatePowerShellEncodingSetup();
|
|
119
|
+
const fullContent = encodingSetup + content;
|
|
120
|
+
|
|
121
|
+
this.writeFileWithEncoding(scriptFile, fullContent);
|
|
122
|
+
|
|
123
|
+
return scriptFile;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* 安全地执行PowerShell脚本,确保编码正确
|
|
128
|
+
* @param {string} scriptPath - 脚本路径
|
|
129
|
+
* @param {Array} args - 传递给PowerShell的参数
|
|
130
|
+
* @param {Object} options - 执行选项
|
|
131
|
+
* @returns {Object} - 执行结果
|
|
132
|
+
*/
|
|
133
|
+
static executePowerShellScript(scriptPath, args = [], options = {}) {
|
|
134
|
+
const { spawn } = require('child_process');
|
|
135
|
+
|
|
136
|
+
const defaultOptions = {
|
|
137
|
+
stdio: 'inherit',
|
|
138
|
+
shell: false,
|
|
139
|
+
...options
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const psArgs = [
|
|
143
|
+
'-ExecutionPolicy', 'Bypass',
|
|
144
|
+
'-File', scriptPath,
|
|
145
|
+
...args
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
return spawn('powershell', psArgs, defaultOptions);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = EncodingUtils;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fnva",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.25",
|
|
4
4
|
"description": "跨平台环境切换工具,支持 Java 和 LLM 环境配置",
|
|
5
5
|
"author": "protagonistss",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
"uninstall-shell": "node scripts/uninstall-shell-integration.js",
|
|
15
15
|
"build": "scripts/build-local.sh",
|
|
16
16
|
"build:all": "scripts/build-all.sh",
|
|
17
|
-
"check-permissions": "node scripts/check-permissions.js"
|
|
17
|
+
"check-permissions": "node scripts/check-permissions.js",
|
|
18
|
+
"preuninstall": "node scripts/uninstall-shell-integration.js"
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"bin/",
|
|
22
|
+
"lib/",
|
|
21
23
|
"platforms/",
|
|
22
24
|
"scripts/",
|
|
23
25
|
"README.md",
|
|
@@ -48,3 +50,4 @@
|
|
|
48
50
|
"node": ">=14.0.0"
|
|
49
51
|
}
|
|
50
52
|
}
|
|
53
|
+
|
package/platforms/fnva
CHANGED
|
Binary file
|
package/platforms/fnva.exe
CHANGED
|
Binary file
|
|
@@ -77,11 +77,11 @@ fnva() {
|
|
|
77
77
|
local temp_file=\$(mktemp)
|
|
78
78
|
chmod +x "\$temp_file"
|
|
79
79
|
|
|
80
|
-
FNVA_AUTO_MODE=1 fnva "\$@" > "\$temp_file"
|
|
80
|
+
FNVA_AUTO_MODE=1 command fnva "\$@" > "\$temp_file"
|
|
81
81
|
source "\$temp_file"
|
|
82
82
|
rm -f "\$temp_file"
|
|
83
83
|
else
|
|
84
|
-
FNVA_AUTO_MODE=1 fnva "\$@"
|
|
84
|
+
FNVA_AUTO_MODE=1 command fnva "\$@"
|
|
85
85
|
fi
|
|
86
86
|
}
|
|
87
87
|
`;
|
|
@@ -94,11 +94,11 @@ function fnva
|
|
|
94
94
|
if test (count \$argv) -ge 2; and string match -q -r "^(java|llm|cc)\$" \$argv[1]; and test \$argv[2] = "use"
|
|
95
95
|
set temp_file (mktemp)
|
|
96
96
|
chmod +x \$temp_file
|
|
97
|
-
env FNVA_AUTO_MODE=1 fnva \$argv > \$temp_file
|
|
97
|
+
env FNVA_AUTO_MODE=1 command fnva \$argv > \$temp_file
|
|
98
98
|
source \$temp_file
|
|
99
99
|
rm -f \$temp_file
|
|
100
100
|
else
|
|
101
|
-
env FNVA_AUTO_MODE=1 fnva \$argv
|
|
101
|
+
env FNVA_AUTO_MODE=1 command fnva \$argv
|
|
102
102
|
end
|
|
103
103
|
end
|
|
104
104
|
`;
|
|
@@ -242,4 +242,4 @@ module.exports = {
|
|
|
242
242
|
getShellFunction,
|
|
243
243
|
isFunctionInstalled,
|
|
244
244
|
installShellIntegration
|
|
245
|
-
};
|
|
245
|
+
};
|
|
@@ -11,126 +11,121 @@ function detectShell() {
|
|
|
11
11
|
return process.env.SHELL?.split('/').pop() || 'bash';
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
function
|
|
14
|
+
function getShellConfigPaths(shell) {
|
|
15
15
|
switch (shell) {
|
|
16
16
|
case 'powershell':
|
|
17
|
-
return path.join(process.env.USERPROFILE || os.homedir(), 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1');
|
|
17
|
+
return [path.join(process.env.USERPROFILE || os.homedir(), 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1')];
|
|
18
18
|
case 'bash':
|
|
19
|
-
return path.join(os.homedir(), '.bashrc');
|
|
19
|
+
return [path.join(os.homedir(), '.bashrc')];
|
|
20
20
|
case 'zsh':
|
|
21
|
-
return
|
|
21
|
+
return [
|
|
22
|
+
path.join(os.homedir(), '.zshrc'),
|
|
23
|
+
path.join(os.homedir(), '.oh-my-zsh', 'custom', '.zshrc'),
|
|
24
|
+
];
|
|
22
25
|
case 'fish':
|
|
23
|
-
return path.join(os.homedir(), '.config', 'fish', 'config.fish');
|
|
26
|
+
return [path.join(os.homedir(), '.config', 'fish', 'config.fish')];
|
|
24
27
|
default:
|
|
25
|
-
return
|
|
28
|
+
return [];
|
|
26
29
|
}
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return false;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
let content = fs.readFileSync(configPath, 'utf8');
|
|
37
|
-
const originalContent = content;
|
|
32
|
+
function cleanConfigFile(cfgPath) {
|
|
33
|
+
let content = fs.readFileSync(cfgPath, 'utf8');
|
|
34
|
+
const originalContent = content;
|
|
38
35
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const startIndex = content.indexOf(marker);
|
|
36
|
+
const marker = '# fnva 自动化函数 - 用 npm 安装自动添加';
|
|
37
|
+
const startIndex = content.indexOf(marker);
|
|
42
38
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
39
|
+
if (startIndex !== -1) {
|
|
40
|
+
const beforeMarker = content.substring(0, startIndex).trimEnd();
|
|
41
|
+
const afterMarker = content.substring(startIndex);
|
|
42
|
+
const lines = afterMarker.split('\n');
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
let functionEndIndex = -1;
|
|
45
|
+
let braceCount = 0;
|
|
46
|
+
let foundFunction = false;
|
|
50
47
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
for (let i = 0; i < lines.length; i++) {
|
|
49
|
+
const line = lines[i];
|
|
50
|
+
if (line.includes('function fnva') || line.includes('fnva(')) {
|
|
51
|
+
foundFunction = true;
|
|
52
|
+
}
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
const line
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
if (foundFunction) {
|
|
55
|
+
for (const char of line) {
|
|
56
|
+
if (char === '{') braceCount++;
|
|
57
|
+
if (char === '}') braceCount--;
|
|
59
58
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
for (const char of line) {
|
|
64
|
-
if (char === '{') braceCount++;
|
|
65
|
-
if (char === '}') braceCount--;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// 当大括号平衡时,函数结束
|
|
69
|
-
if (braceCount === 0) {
|
|
70
|
-
functionEndIndex = i + 1;
|
|
71
|
-
break;
|
|
72
|
-
}
|
|
59
|
+
if (braceCount === 0) {
|
|
60
|
+
functionEndIndex = i + 1;
|
|
61
|
+
break;
|
|
73
62
|
}
|
|
74
63
|
}
|
|
75
|
-
|
|
76
|
-
if (functionEndIndex !== -1) {
|
|
77
|
-
// 重建内容
|
|
78
|
-
const afterFunction = lines.slice(functionEndIndex).join('\n');
|
|
79
|
-
content = beforeMarker + '\n' + afterFunction;
|
|
80
|
-
} else {
|
|
81
|
-
console.log('⚠️ 无法确定函数结束位置');
|
|
82
|
-
return false;
|
|
83
|
-
}
|
|
84
64
|
}
|
|
85
65
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
content = content
|
|
90
|
-
// 删除标记到函数结束的所有内容
|
|
91
|
-
.replace(/# fnva 自动化函数 - 由 npm 安装自动添加[\s\S]*?(?=\n\S|\n$)/g, '')
|
|
92
|
-
// 删除剩余的 fnva 相关行
|
|
93
|
-
.replace(/.*fnva.*\n?/g, '')
|
|
94
|
-
// 删除 FNVAAUTOMODE 相关行
|
|
95
|
-
.replace(/.*FNVAAUTOMODE.*\n?/g, '')
|
|
96
|
-
// 删除 cmd.exe 调用 fnva 的行
|
|
97
|
-
.replace(/.*cmd\.exe.*fnva.*\n?/g, '')
|
|
98
|
-
// 清理多余的空行
|
|
99
|
-
.replace(/\n{3,}/g, '\n\n')
|
|
100
|
-
.trim() + '\n';
|
|
66
|
+
if (functionEndIndex !== -1) {
|
|
67
|
+
const afterFunction = lines.slice(functionEndIndex).join('\n');
|
|
68
|
+
content = beforeMarker + '\n' + afterFunction;
|
|
101
69
|
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 正则兜底:移除残留 fnva 片段
|
|
73
|
+
if (content === originalContent) {
|
|
74
|
+
content = content
|
|
75
|
+
.replace(/# fnva 自动化函数 - 用 npm 安装自动添加[\s\S]*?(?=\n\S|\n$)/g, '')
|
|
76
|
+
.replace(/.*fnva.*\n?/g, '')
|
|
77
|
+
.replace(/.*FNVAAUTOMODE.*\n?/g, '')
|
|
78
|
+
.replace(/.*cmd\.exe.*fnva.*\n?/g, '')
|
|
79
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
80
|
+
.trim() + '\n';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (content !== originalContent) {
|
|
84
|
+
fs.writeFileSync(cfgPath, content);
|
|
85
|
+
console.log(`✅ fnva shell 集成已从 ${cfgPath} 移除`);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
console.log(`⚠️ 未在 ${cfgPath} 找到需要清理的内容`);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
102
92
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
93
|
+
function removeShellIntegration(configPath, shell) {
|
|
94
|
+
const paths = getShellConfigPaths(shell);
|
|
95
|
+
if (configPath) paths.unshift(configPath); // 兼容传入单一路径
|
|
96
|
+
|
|
97
|
+
let removedAny = false;
|
|
98
|
+
for (const cfgPath of paths) {
|
|
99
|
+
if (!cfgPath || !fs.existsSync(cfgPath)) continue;
|
|
100
|
+
try {
|
|
101
|
+
const removed = cleanConfigFile(cfgPath);
|
|
102
|
+
removedAny = removedAny || removed;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.log(`❌ 移除失败 (${cfgPath}): ${error.message}`);
|
|
111
105
|
}
|
|
112
|
-
} catch (error) {
|
|
113
|
-
console.log(`❌ 移除失败: ${error.message}`);
|
|
114
|
-
return false;
|
|
115
106
|
}
|
|
107
|
+
|
|
108
|
+
if (!removedAny) {
|
|
109
|
+
console.log('⚠️ 未找到可清理的 shell 配置文件或未匹配到 fnva 片段');
|
|
110
|
+
}
|
|
111
|
+
return removedAny;
|
|
116
112
|
}
|
|
117
113
|
|
|
118
114
|
function main() {
|
|
119
|
-
console.log('
|
|
115
|
+
console.log('🧹 fnva shell 集成卸载');
|
|
120
116
|
|
|
121
117
|
const shell = detectShell();
|
|
122
|
-
const
|
|
118
|
+
const paths = getShellConfigPaths(shell);
|
|
123
119
|
|
|
124
|
-
if (
|
|
125
|
-
console.log(
|
|
120
|
+
if (paths.length === 0) {
|
|
121
|
+
console.log(`⚠️ 不支持的 shell: ${shell}`);
|
|
126
122
|
return;
|
|
127
123
|
}
|
|
128
124
|
|
|
129
|
-
const success = removeShellIntegration(
|
|
125
|
+
const success = removeShellIntegration(null, shell);
|
|
130
126
|
|
|
131
127
|
if (success) {
|
|
132
128
|
console.log('🔄 请重新加载你的 shell 配置:');
|
|
133
|
-
|
|
134
129
|
switch (shell) {
|
|
135
130
|
case 'powershell':
|
|
136
131
|
console.log(' . $PROFILE');
|
|
@@ -154,6 +149,6 @@ if (require.main === module) {
|
|
|
154
149
|
|
|
155
150
|
module.exports = {
|
|
156
151
|
detectShell,
|
|
157
|
-
|
|
158
|
-
removeShellIntegration
|
|
159
|
-
};
|
|
152
|
+
getShellConfigPaths,
|
|
153
|
+
removeShellIntegration,
|
|
154
|
+
};
|